Attaching more than one observer to a single entities object, just one observer is called

observer

#1

Hi,

I’m attaching different EntitiesObserver instances to the entities object of a ComponentDefinition. One observer instance per component instance. The problem is that when I edit the component, just one of the observers is triggered. I would have expected to get a onElement* call for each of the attached observers.
Am I mis-interpreting the observer mechanism or is this a bug?


#2

What you are doing really doesn’t make sense. All the ComponentInstances of a particular component share the same ComponentDefinition. When you edit the component, the particular ComponentInstance you select is just a gateway to that same, single CD. So you have attached a bunch of observers to the same Entities collection. There will be no way to tell which CI the user happened to open for edit, which is the only reason I can think of that you might want to do this.

That said, I’m not sure why you are getting only one callback. There may be some sort of event compression when the same observer is attached multiple times?


#3

And a Sketchup::InstanceObserver subclass should be used for this purpose.


#4

I know that all ComponentInstances of a component share the same ComponentDefinition. And I know that I attached several observers to the same EntitiesCollection. I did this on purpose, because I have some meta information in my observer class that is different for each of the ComponentInstances. I want to update that meta information when the entities in the definition change, and I want to do this in all of my observer instances, not just one. The overall goal is to react to all changes of the model and send those changes via network to another program. The program at the other end of the connection can’t handle components, just polygons. So I have to send the faces of each component instance per-transformed to their global position separately. I hope that makes sense. Of course I can think of work-arounds, but it would be most convenient if all my observers were triggered.


#5

I would have such meta data either in a AttributeDictionary attached to the instance, or in an EntityObserver or InstanceObeserver instances.

One thing you may not realize is, that basically all the observers were made abstract a few versions ago (even though for historical reasons there are superclasses used to document them.)
And SketchUp does not type check your classes. It uses duck-typing, by asking the objects attached as observers, whether they respond to a particular method name before calling the callback method. (In fact SketchUp does not even care if the attached object is a class instance or a module, only as long as the callback methods are public and accessible.)
So, you can actually create hybrid observers that, for example, have both the callback methods of an InstanceObserver and an EntityObserver. This comes in handy especially for meta data, as you do not need to pass hash(es) of data around as method parameters between separate objects.


#6

I don’t know the details of what you are doing, but I’d be inclined to attach the metadata to the instances (as Dan suggests), not to the observers. After all, it seems like it is info about each instance, not about something watching that instance? Once you get the ComponentDefinition, you can use its #instances method to get all the ComponentInstances, examine their individual metadata, and do whatever you need to relay them to the other application. That way you only need a single observer.


#7

I didn’t know I could create hybrid observers. Thanks for the info! This does sound interesting!


#8

I’ll think about using the attribute dictionaries for meta data. Though I don’t like the fact that changing attributes triggers change observers. This means I’ll have observer callbacks make changes to the model. Something which isn’t encouraged as far as I know.


#9

What version of SketchUp are we talking about there?

Also, do you have a minimal snippet the reproduce this?


#10

The version is

I don’t have a minimal snippet yet. I’ll try to come up with one in the next days.

Okay, I wrote a minimal example and guess what? In the example it works. Sorry to have bothered you with this. I was sure it wasn’t my code, but now it seems I was wrong.

require 'sketchup'

module OricAtmos

class MyEntitiesObserver < Sketchup::EntitiesObserver
    def onElementAdded(su_entities, su_entity)
        puts "MyEntitiesObserver(#{self})::onElementAdded: #{su_entities} #{su_entity} #{su_entity.entityID}"
        nil
    end

    def onElementModified(su_entities, su_entity)
        puts "MyEntitiesObserver(#{self})::onElementModified: #{su_entities} #{su_entity} #{su_entity.entityID}"
        nil
    end

    def onElementRemoved(su_entities, su_entity_id)
        puts "MyEntitiesObserver(#{self})::onElementRemoved: #{su_entities} #{su_entity_id}"
        nil
    end

    def onEraseEntities(su_entities)
        puts "MyEntitiesObserver(#{self})::onEraseEntities: #{su_entities}"
        nil
    end
end

def self.test()
    model = Sketchup.active_model
    comp_def = model.definitions.add("My Definition is this")

    point = Geom::Point3d.new 10,20,30

    transform = Geom::Transformation.new point
    entities = model.entities

    observer1 = OricAtmos::MyEntitiesObserver.new
    instance1 = entities.add_instance(comp_def, transform)
    instance1.definition.entities.add_observer(observer1)

    observer2 = OricAtmos::MyEntitiesObserver.new
    instance2 = entities.add_instance(comp_def, transform)
    instance2.definition.entities.add_observer(observer2)

    depth = 100
    width = 100
    pts = []
    pts[0] = [0, 0, 0]
    pts[1] = [width, 0, 0]
    pts[2] = [width, depth, 0]
    pts[3] = [0, depth, 0]

    face = comp_def.entities.add_face(pts)
end

end