In my plugin I define a few materials that I would like to protect from deletion. For example when the CleanUp plugin is used.

To do this I have created a MaterialsObserver

module RG
  class MyMaterialsObserver < Sketchup::MaterialsObserver
    def onMaterialRemove(materials, material)
      if == "area_of_interest"
        $AOI_MAT = materials.add("area_of_interest")
        $AOI_MAT.texture = File.join(PLUGIN_PATH, "images", "focus_area.jpg")
        $AOI_MAT.texture.size = [25.m, 25.m]

At first I thought that it would have been possible to add this observer to the app with


No error was reported (add_observer returns true), but it was not working. After several attempts I have realized that I needed to add the MaterialsObserver to every new or open model:

module RG
  class MyAppObserver < Sketchup::AppObserver
    def onNewModel(mod)
      puts "onNewModel: " + mod.to_s
    def onOpenModel(mod)
      puts "onOpenModel: " + mod.to_s

Is this the correct approach? Sketchup responds to add_observer, but the observer is not triggered. What am I missing?


Because the SketchUp engine does NOT do class checking on observers anywhere. This means when they are added or removed or called. The engine simply asks the attached object if it responds to a certain callback, and if it returns true then it calls the callback.

So, regarding your 1st attempt, you told SketchUp to treat an instance object as a Sketchup::AppObserver subclass object, which it may have tried to do, but whenever SketchUp asked the object if it responds to onNewModel() or any other Sketchup::AppObserver callback, your object correctly returned false because it is really a Sketchup::MaterialsObserver object, and it does not have any of the Sketchup::AppObserver callbacks.

Also,… never use global variables. In a shared Ruby environment only the Ruby Core or the Trimble SketchUp Developement Team should be creating globals for everyone to read.

A reference is a reference. If you were attempting to prevent the deletion of your material by holding a reference to it, it doesn’t matter if the reference is a local variable, an instance variable or a class varible.
Any reference will prevent the object from getting garbage collected.

But, preventing garbage collection on your material, will not prevent it from being purged from the model’s Materials collection.

To prevent purging it must be “used”. It must be painted onto some model drawingelement. So you may need to create a hidden layer with a hidden group, with hidden objects painted with your materials.

Or the CleanUp plugin needs to accept some “locked” materials (and componentdefintions would be nice as well,) and skip them when purging.

Now, in a Materials observer, by the time your callback gets called, the material object may have already been removed from the Materials collection.

Always test inside a callback if that second object reference passed in to see if it is valid.

if material.valid?

… and if the collection (usually the first parameter,) stills contains the individual object:

if materials.include?(material)

Yes the 2nd one.

Try adding:

 def expectsStartupModelNotifications()
   return true

into your AppObsever subclass.