Purge my sketchup definitions

Hello, :smiley:

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]"	

How can I do this?

Thank you in advance for your help.

1 Like

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

It is not this. In many cases geometry editing causes edges or faces to be deleted and replaced by entirely new edges (or faces as the case may be.)

Also, the Sketchup::EntitiesObserver#onElementModified callback is bugged and does not fire correctly for some tools such as modifying the length of an edge with the MoveTool.

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.)

1 Like

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.

2 Likes

Thanks Dan for taking your time to write your example!

Your way of coding is extremely advanced which often confuses me during the first reading.
Then deepen I realize that there are many things to learn.

Thank you for that!

2 Likes

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?

Does not print the callback method’s name. You need to call the global __method__() method. Ie…

      puts __method__

or using string interpolation:

      puts "#{self.class.name}, in callback: #{__method__}, entity is:\n#{entity}"

Also (in my opinion) puts() is less problematic than p().

ComponentDefinition objects are not members of any Entities collection.

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.

I’m afraid your solution doesn’t allow removing the watcher after the first modified definition:

The proof is that your message "The modified entity is a " is activated 2 times in the ruby console.

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)

Exemple

I will do more tests to be sure that this solution does not cause any other problems.

Yes. Simply, a ComponentInstance can 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:

Probably not.

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.)

1 Like

Your remark is contradictory to the API or I misunderstood something?

No it is not !

Yes you do.

You are showing the docstring for the Sketchup::Definition#entities method, which clearly says (ignoring your misdrawn emphasis,) …

The entities method retrieves a collection of all the entities in the component definition

Now the docstring is actually a bit incorrect. The method retrieves a reference to the definition’s Sketchup::Entities collection object.

Look at the “Returns:” section for the method.

Returns:


Okay ?

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 OTHER Sketchup::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.

Offftopic: ... example of a problem encountered with the p() instead of the puts()

It seems p() does not output embedded newlines in strings arguments.

But this is not necessary to this discussion. Use what works for you if you don’t have this issue.

But … in the preceding post you said …

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.

Yes that was my original intention but I found an easier alternative.

You are right but I have not found the solution to do what you say!

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

Thanks for your help and tips!

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.

Thanks

Yes you missed something I said …

This means you should use:

sel.grep(Sketchup::ComponentInstance) do |i|
  i.add_observer(MyEntityObserver.new)
end

… so that each instance gets it’s own observer,