Entity change listener not firing


#1

Hi! I am new to SketchUp and ruby all together. I got the following code to work with which displays a menu item called “Dimensions”, which basically shows the measurements of a plane on the ruby console. I also need to show the measurements, in a floating window
I need the output to change when the plane is modified (something like using a change listener). My problem is the onChangeEntity doesn’t seem to fire when I make a change to the entity.

The script dimentCh.rb

    # This is an example of an observer that watches an entity for deletion
     # and shows a messagebox.
     class MyEntityObserver < Sketchup::EntityObserver
       def onChangeEntity(entity)
   #UI.messagebox("onChangeEntity: " + entity.to_s)
         model = Sketchup.active_model
  mname = model.title

   boundingBox = model.selection[0].bounds
   dims = [ boundingBox.height,
    boundingBox.width,
    boundingBox.depth ]
    dims.sort!
    puts "****START****"
    puts "Thickness: " 
    puts dims[0].to_s 
    puts  "\nWidth: " 
    puts  dims[1].to_s 
    puts "\nLength: " 
    puts  dims[2].to_s
    puts "******END****" 
   end
 end

     # Attach the observer. (Assumes there is an entity in the model.)
     Sketchup.active_model.entities[0].add_observer(MyEntityObserver.new)

Once again I have no experience in ruby or SketchUp, Please help, Thanks!


#2

You were attaching the observer to the first entity in the model, which is not necessarily the entity you wanted (most likely it was an edge). Unfortunately some observers don’t trigger reliably and EntityObserver seems not to trigger changes on edges.
For testing/development, use rather model.selection[0]. Also, don’t refer to the selection from inside the observer, because you cannot predict what the user has currently selected.

Little side note: How do you know that the model on which your observer acts is the currently focussed model? Sketchup.active_model just means “give me whatever model is currently focussed”. It just happens that the Windows version of SketchUp only opens one model at a time, but as a programmer one follows instead the API’s “contract”. Try always to avoid assumptions if you can not guarantee it’s 100% of the time the case.

class MyEntityObserver < Sketchup::EntityObserver
  def onChangeEntity(entity)
    # If you need a reference to the model on which the observer was triggered:
    # model = entity.model
    boundingBox = entity.bounds
    dims = [ boundingBox.height,
      boundingBox.width,
      boundingBox.depth ]
    dims.sort!
    puts "****START****"
    puts "Thickness: " 
    puts dims[0].to_s 
    puts  "\nWidth: " 
    puts  dims[1].to_s 
    puts "\nLength: " 
    puts  dims[2].to_s
    puts "******END****"
    # You want to know when an error happens, so you can inspect what's wrong:
  rescue Exception => e
    puts e
    puts e.backtrace
  end
end

# Attach the first currently selected entity.
Sketchup.active_model.selection[0].add_observer(MyEntityObserver.new)

A more reliable alternative is the EntitiesObserver (the entities collection that contains the entity). It triggers for every changed entity in this entity collection, but you can for example tell it for which entity to listen specifically:

module MyNameSpace

  class MyEntitiesObserver < Sketchup::EntitiesObserver

    def initialize(entity)
      @entity = entity
    end

    def onElementModified(entities, entity)
      if entity == @entity
        bounds = entity.bounds
        dimensions = [:height, :width, :depth].map!{ |d| bounds.send(d) }.sort
        puts "entity #{entity} has changed its dimensions #{dimensions}"
      end
    end

  end

  def self.start(entity_to_observe)
    entities = entity_to_observe.parent.entities
    # Pass to the observer information that it needs.
    entities.add_observer(MyEntitiesObserver.new(entity_to_observe))
  end

  self.start(Sketchup.active_model.selection[0])
end

You can use this pattern also for passing results from the observer back to a class of your plugin.

class MyEntitiesObserver < Sketchup::EntitiesObserver

  def initialize(tool)
    @tool = tool
  end

  def onElementModified(entities, entity)
    if entity.is_a?(Sketchup::Edge)
      @tool.handle_further(entity)
    end
  end

end

class MyTool

  def initialize(model)
    @model = model
    # Pass to the observer the instance of this tool, 
    # so that the observer can call methods of this tool
    observer = MyEntitiesObserver.new(self)
    @model.entities.add_observer(observer)
  end

  def handle_further(entity)
    # Do something.
  end

end

#3

Thanks @Aerilius that was very helpful :ok_hand:


#4

The link that @Aerilius gave as “some observers don’t trigger reliably” hasn’t been updated since SketchUp 8, so don’t be surprised if you find inconsistencies in later releases. Also, as discussed in this topic the logic of what fires when can sometimes be different than what you might expect.


#5

It will help if you go to:
http://ruby-doc.org/core-2.0.0/
And read the pages listed under Files section …
that are at the “doc/” root level, and “doc/syntax” level.

(Skip the “rdoc” and “rake” documents, you don’t need to know that for embedded Ruby.)


#6

True - but they are still not good. They just crash less often… :confused:

Yes, as far as change events - these two threads sound similar. Have a look at that thread and see if that answers this one as well.


#7

I’ve been using similar patterns lately. I do minimal code in the observer events directly - mainly delegate into whatever needs to listen. That way the observer doesn’t need to know the logic of the listener.
I’ve also started to define the initialize and forwarder functions in a mix-in module so I can reuse the pattern in multiple observers. It’s on my list of articles to write about.


#8

Looser coupling to the observer class, and higher cohesion in the tool’s class…