Edge Change Observer


#1

Both the EntitiesObserver and EntityObserver don’t seem to trigger onElementModified or onChangeEntity for edges in the scene. I would expect these to trigger if the edge is modified in some way (either split or moved), similar to how it triggers for faces. Is this the desired behavior or a known bug?

Does anyone know of any other way to observe modifications to edges in a general way as a workaround?


#2

The current observer API has many issues. When it was implemented originally it was done rather low level and “as-is”.

In terms of splitting an edge, then a change might be reasonable to expect. Though it could be that remove and add is currently sent.

There could also be differences in what change-notifications are sent depending on whether or not you are using model.start/commit_operation - meaning that if you do simple tests in the console without you might see different results than in real world where you’d use them.

I’m currently working on mapping out the various issues. It would help if you could produce some example snippets that reproduce the scenarios - along with description of expected behaviour.

EntitiesObserver is the general way to observe, though if you could describe the higher level concept of what you are using observer for I might be able to provide some more specific advice.


#3

The way I’ve seen it behave, is that add / remove work very reliably for edges. If you split an edge, “add” will get called exactly one time for a single new edge that is created, the original edge is truncated but is not re-created, so add is not called for it. I would expect “change” to fire in this case since it’s neither being deleted or added again.

The splitting is almost even more complex than what I am talking about though, if you draw a single line, and move it via the move tool, a change event is never fired for an EntityObserver or EntitiesObserver. It seems like this should be the bare minimum for an edge observer to implement. In fact, I don’t think in all my testing I have ever seen “onElementModified” called where the entity object is a Edge class.

What I was hoping to do was keep tabs on all the bounding boxes for every single entity the scene so I could build my own spatial acceleration data structure. Just to enteratin the idea if it was possible or not. I think I could do this reliably for faces, groups, and components using the observer interface, but edges seems more problematic given that I can only observe when they are added or deleted.


#4

I concur. I looked into detecting move events for Edges to implement “association” for angular dimensions, and I could never get any events to fire when an Edge is moved.


#5

I agree that I would expect the change events to kick off when entities are moved or transformed.
However, the current observer system was implemented very close to the metal many years ago.

Lets look at what happens with this sample script:

module Example


  class TestEntitiesObserver < Sketchup::EntitiesObserver

    def onElementAdded(entities, entity)
      puts "EntitiesObserver.onElementAdded(#{entities}, #{entity})"
    end

    def onElementRemoved(entities, entity)
      puts "EntitiesObserver.onElementRemoved(#{entities}, #{entity})"
    end

    def onElementModified(entities, entity)
      puts "EntitiesObserver.onElementModified(#{entities}, #{entity})"
    end

  end # class TestEntitiesObserver


  class TestEntityObserver < Sketchup::EntityObserver

    def onChangeEntity(entity)
      puts "EntityObserver.onChangeEntity(#{entity})"
    end

    def onEraseEntity(entity)
      puts "EntityObserver.onEraseEntity(#{entity})"
    end

  end # class TestEntityObserver


  # Example.start_observing
  def self.start_observing
    model = Sketchup.active_model
    model.entities.clear!

    model.entities.remove_observer(@entities_observer) if @entities_observer
    @entities_observer = TestEntitiesObserver.new
    model.entities.add_observer(@entities_observer)

    @edge = model.entities.add_line([0,0,0], [9,9,9])
    
    @entity_observer = TestEntityObserver.new
    @edge.add_observer(@entity_observer)
    @edge.start.add_observer(@entity_observer)
    @edge.end.add_observer(@entity_observer)
  end

end # module
# Here I use the Move tool to move the Edge. Observe that the onChangeEntity for
# the Vertex trigger. This is because when you move something you change the
# position property of the vertices.
# (The AttributeDictionary is an odd one. Looks like noise that should not be
# there.)
EntityObserver.onChangeEntity(#<Sketchup::Vertex:0x0000000cf1bdd0>)
EntityObserver.onChangeEntity(#<Sketchup::Vertex:0x0000000cf1bda8>)
EntitiesObserver.onElementModified(#<Sketchup::Entities:0x0000000ceb0918>, #<Sketchup::AttributeDictionary:0x0000000cfc9bd8>)
EntitiesObserver.onElementModified(#<Sketchup::Entities:0x0000000ceb0918>, #<Sketchup::AttributeDictionaries:0x0000000cfc9c00>)


# Here I toggle the various Soft, Smooth, Hidden properties of the edge.
# The edge itself triggers here because the property is actually on the edge.
EntitiesObserver.onElementModified(#<Sketchup::Entities:0x0000000ceb0918>, #<Sketchup::Edge:0x0000000cf1bfb0>)
EntityObserver.onChangeEntity(#<Sketchup::Edge:0x0000000cf1bfb0>)

EntitiesObserver.onElementModified(#<Sketchup::Entities:0x0000000ceb0918>, #<Sketchup::Edge:0x0000000cf1bfb0>)
EntityObserver.onChangeEntity(#<Sketchup::Edge:0x0000000cf1bfb0>)

EntitiesObserver.onElementModified(#<Sketchup::Entities:0x0000000ceb0918>, #<Sketchup::Edge:0x0000000cf1bfb0>)
EntityObserver.onChangeEntity(#<Sketchup::Edge:0x0000000cf1bfb0>)

EntitiesObserver.onElementModified(#<Sketchup::Entities:0x0000000ceb0918>, #<Sketchup::Edge:0x0000000cf1bfb0>)
EntityObserver.onChangeEntity(#<Sketchup::Edge:0x0000000cf1bfb0>)


# Another odd one - creating a layer sends out a change event. I see this with
# any other entities as well. I think it's the core setting up the relationships
# which in turn trigger a change. Noise that I don't think is needed.
EntitiesObserver.onElementModified(#<Sketchup::Entities:0x0000000ceb0918>, #<Sketchup::Layer:0x0000000d1320d8>)

# Here I change the layer property.
EntitiesObserver.onElementModified(#<Sketchup::Entities:0x0000000ceb0918>, #<Sketchup::Edge:0x0000000cf1bfb0>)
EntityObserver.onChangeEntity(#<Sketchup::Edge:0x0000000cf1bfb0>)

So while it might not be intuitive, there is a logic. You move something you change the vertices - not the properties of the edges or faces that share the vertices. While I agree that it would be convenient in many case for the notifications to cascade through to the “owner(s)” - this is the current situation.

The reason that the EntitiesObserver doesn’t trigger on the vertex changes is that Entities doesn’t really own the vertices. They have shared ownership by the edges and faces that use it - their lifespan is implicit. Delete a face and you don’t delete its edges, but delete an edge and you delete the vertices because they have no standalone representation in SketchUp.

I hope this sheds some light on things.

“Desired” isn’t the correct word here. As I mentioned briefly, the observers where originally implemented close to the metal. Too close i my opinion and they are a source of pain. We are aware of that. We are looking into ways to ease this pain.


#6

Thanks for the details TT! I never would have suspected that logic. As you say, it makes a sort of sense, just not what most people would expect.


#7

It makes sense when you have access to see the underlying data structure. This is where the current documentation fails. We are working towards addressing this.

To be honest - I have puzzled this myself and it was only now that I set to see what happened that I got a hunch to what was going on. Another piece in the puzzle.


Entity change listener not firing
#8

I see, that helps a lot. I didn’t even think to attach an observer to the vertices directly. I think I’ll be able to implement what I am trying with this info.