I would like to implement an observer that classifies all the entities that have been modified in a array.
class Myclass < Sketchup::EntitiesObserver
def onElementModified(entities, entity)
entities.remove_observer(self)
end
end
Sketchup.active_model.entities.add_observer(Myclass.new())
#=> "[Entity01, Entity02]"
It’s not very serious, I wanted to know if it was a shortcoming on my part in understanding the class.
Extracting an array seems impossible in the case of a true class that observes entities.
I don’t have the necessary experience in ruby to determine the reasons.
Thanks for your help
In addition, the Sketchup::EntitiesObserver#onElementRemoved callback may have variant return when deleting selected entities with the DEL key. (The 2nd object passed to the callback can be an Integer id of the object and not an actual entity reference since the entity has already been deleted.)
You cannot use an array. You must use a Hash with entityID keys because the onElementRemoved callback does not return the already removed entity reference. It returns the integer ID of the deleted entity, so you need to be able to look up the items in your hash by integer ID keys.
I posted a test observer in this API Issue, that you can use to see what editing scenarios that fail to note modifications.
With the method below, if several “ComponentDefinition” are modified, the method will be executed as many times as there are "ComponentDefinition" modified!
class MyClass < Sketchup::EntitiesObserver
def onElementModified(entities, entity)
if entity.is_a?(Sketchup::ComponentDefinition)
p "method"
end
entities.remove_observer(self)
end
end
mod = Sketchup.active_model
mod.entities.add_observer(MyClass.new)
Is there a way to stop the execution of the observer when the first “ComponentDefinition” has been modified in order to execute the method only once?
Therefore, your example does not fire the onElementModified callback when a ComponentDefinition is modified.
I will answer this question generally for any object “watched” by an observer. And also assume that …
… is an attempt to cause a “temporary” observer to execute only once.
If you do a search through the API issues that are still open, you’ll see that there are several observer classes where callbacks are firing more than once for a given event.
This means that the problem happens with SketchUp’s internal observer queue(s), which are likely to already be “queued up” to call observers multiple times before any of your observer’s callback methods get the first call.
So, attempting to “fix” a core bug by detaching the observer (ie, a “short circuit” pattern) is probably bound to fail.
The only thing you can do is control your own code object (the observer) with a “short circuit” or “bailout” pattern. In this coding pattern your observer keeps an internal state variable for whether a certain callback has yet been called.
This also means that that the object being watched (to which the observer is attached) must have a unique instance of the observer all to itself so that the state instance variable only applies to that one instance.
p “method” is simply a way of saying that a method is going to be activated by the observer.
So I represented the method that does not exist as a string.
Do you have an example of a problem encountered with the “p” instead of the “put”
I can’t follow you!
Component entity are attached to definition as are Component instance.
So if an instance is modified, the observer will activate!
I agree with you!
I can make the “ComponentDefinition” have a unique attribute so that the observer only activates if it encounters this attribute!
Thus the observer will only be activated once.
I found a solution to work around the error which seems to work.
class MyClass < Sketchup::EntitiesObserver
def onElementModified(entities, entity)
mod = Sketchup.active_model
val = mod.get_attribute("observer", "statut")
unless val == true
p "#{entity}"
mod.set_attribute("observer", "statut", true )
end
entities.remove_observer(self)
end
end
mod = Sketchup.active_model
mod.set_attribute("observer", "statut", false)
mod.entities.add_observer(MyClass.new)
I will do more tests to be sure that this solution does not cause any other problems.
Yes. Simply, a ComponentInstancecan be a member of any Entities collection, so therefore it should be expected that some EntitiesObserver callbacks should fire in this case.
Ordinarily, an extension would likely try to trap this copy scenario with the:
Do not confound instance objects with definition objects.
A definition can only be a member of the DefinitionList collection.
I don’t know (exactly) why internally EntitiesObserver#onElementModified() fires in this Move+Copy tool scenario. Perhaps (guessing) the extra instance is first placed at the location of the existing sibling instance and then transformed along the transition of the mouse drag? (Similar to my DragTool example.)
What part of the above, or anywhere else in the API documentation, does it say that a Sketchup::ComponentDefinition object can be or will be a member of a Sketchup::Entities collection, either the model proper or that of any other component definition ?
As I said above …
Again, … A definition can only be a member of the model’s DefinitionList collection.
But an instance can be a member of ANY OTHERSketchup::Entities collection, either the model proper or that of any OTHER component definition. (Ie, it cannot be a member of it’s own parent definition’s Sketchup::Entities collection, as that is what it’s made up of. This would be a circular reference.)
If you wish to track the changes to any definition’s entities, then you must separately attach your observer to that definition’s entities collection. Just attaching the observer to the model’s top level entities will not work.
Except … you did not attach the attribute to the component instance nor definition.
You attached it to the model object. And your snippet only watches the model’s entities collection.
In my opinion, an instance variable inside the observer class attached to the instance object (or it’s definition) would be faster, and not bloat the model file with unneeded attribute dictionaries.
It’s is just like setting an attribute, but you set an instance variable instead.
I showed you how to define an instance variable within the initialize method of a class in the example at GitHub. Just make the variable name something like @first_time (or whatever,) …
Then attach the observer to the instances you want to “watch”. In the appropriate callback set the @first_time variable to false if it’s true, like …
def initialize
@first_time = true
end
def onSomeEvent(some_collection, some_object)
return unless @first_time # <--<<< short-circuit / bailout test
# Otherwise do something here ...
@first_time = false
end
Dan I am trying to apply your tips in below method:
class MyEntityObserver < Sketchup::EntityObserver
def initialize
@value = true
end
def onChangeEntity(entity)
return unless @value
if entity.is_a?(Sketchup::ComponentInstance)
puts "Instance Definition Name : #{entity.definition.name}"
end
@value = false
end
end
@change_observer ||= MyEntityObserver.new
mod = Sketchup.active_model
sel = mod.selection
sel.grep(Sketchup::ComponentInstance) do |i|
i.add_observer(@change_observer)
end
With this method the “puts” method executes only once as I had requested.
However the watcher never runs again even if I attach the watcher to other instances.
Should the value of the instance variable @value be changed to true in any way other than the initialize method?
I may have misunderstood you and that is why I need your help.