Purge my sketchup definitions

Congratulations for your detailed explanations dezmo!

Indeed I can provide better explanations.

For the moment I have not done so to avoid dispersing ourselves on other themes that are not related to my difficulties.

I also wanted to avoid discouraging readers because the problem is relatively long to explain.
Alright, I’ll go into the details. :wink:

Origin of the problem:

  • Some of my dynamic components generate “ComponentDefinition” when the “copy” attribute is used.

  • This happens because each copy is automatically made unique by the dynamic component script or SketchUp?

  • The number of copies can evolve with the “OnClick” method which is the little hand that interacts with the dynamic components.

  • If the number of copies reaches 0, the “ComponentInstance” no longer exist but the “ComponentDefinition” still exist.

Result:

  • This unnecessarily bloats the file and drastically reduces the response time of my dynamic components!

  • For this reason I wrote a method “purgeMyComponent” which purges the definitions of my components which do not have an instance in the model.

  • I now want my method to activate each time a definition without an instance is generated by one of my dynamic components.

Objective:
Found the lightest and most efficient solution possible!

Option 1: Use Sketchup::EntityObserver

  • EntityObservers only last for the duration of the SketchUp session.

  • Consequently each time a model is opened, the “onOpenModel” observer must be activated to attach all instances of the components with “onChangeEntity”!

  • To attach the instances I want to monitor, I need to loop through all instances of the model!

The questions:

  1. If a project has thousands of instances, won’t the observer slow down SketchUp too much?

  2. If thousands of instances are modified at once, how to run the cleanup method only once at the end?

This is the challenge I must take up while deepening my knowledge of Ruby.

Here is a small example of code that I am writing:

  # Should run the cleanup method only once after all entities are changed.
  class MyEntityObserver < Sketchup::EntityObserver
    def initialize
      @value = true
    end   
    def onChangeEntity(entity)
	  entity.remove_observer(self)
	  return unless @value
      p "purgeMyComponent"
	  @value = false
    end
  end
  
  # Attach Sketchup::EntityObserver to all instances to watch when the "OnClick" 
  # tool is activated.
  class MyToolsObserver < Sketchup::ToolsObserver
    def onActiveToolChanged(tools, tool_name, tool_id)
	  if tool_name == "RubyTool"
	    mod = Sketchup.active_model
	    defs = mod.definitions
	    defs.each do |d|
	      d.instances.each do |i|
		    if i.attribute_dictionary("My_attributes")
		      ref = i.get_attribute "My_attributes","reference","0"
	          if ref == "component_to_monitor"
			    p "Observer Attach to: #{i.definition.name}"
	            i.add_observer(MyEntityObserver.new)
		      end
		    end
          end
	    end
      end  
    end
  end
  
  # Attach "Sketchup::ToolsObserver" when opening a project or a new model.
  class MyAppObserver < Sketchup::AppObserver
    def onNewModel(model)
	  Sketchup.active_model.tools.add_observer(MyToolsObserver.new)
    end
    def onOpenModel(model)
	  Sketchup.active_model.tools.add_observer(MyToolsObserver.new)
    end	
  end
  Sketchup.add_observer(MyAppObserver.new)

For the moment I don’t know yet how to execute the cleaning method only once after the change of all the entities.

Ideally for me would be to be able to execute an “end” method which is the opposite of the initialize method.

This end method could run the end cleanup method after “EntityObserver” finished working.

The problem is that I don’t know if this is possible?

There is no need to call Sketchup.active_model within these callbacks because SketchUp passes the model object in the model argument, so …

  # Attach "Sketchup::ToolsObserver" when opening a project or a new model.
  class MyAppObserver < Sketchup::AppObserver
    def onNewModel(model)
      model.tools.add_observer(MyToolsObserver.new)
    end
    def onOpenModel(model)
      model.tools.add_observer(MyToolsObserver.new)
    end	
  end

Thanks for your example and explanations Dezmo:

In reality these are existing definitions that I must delete.

So the “onComponentAdded” watcher doesn’t work in my case.

And since instances of these definitions are nested in parent components, the “onComponentPropertiesChanged” watcher doesn’t react either.

Sorry to tell you this now, but my situation is very complex and that’s why I didn’t go into the details from the start.

I also want to avoid having an “Observer” who works constantly.

I prefer that it activates after a user action and that it is close automatically.

So the timer with a purge_unused every 5 seconds is not an alternative that suits me.

  • I would really like to find a solution to execute an end method when an observer has finished working.

Thank you anyway for your examples where there are good tips to take.

Sorry Dan! This is a careless mistake on my part.

How about using a ModelObserver and purge before the save ?

To my surprise the method below works perfectly even for my dynamic components:

# AUTOMATICALLY PURGE ALL DEFINITIONS WITHOUT INSTANCES
class MyEntitiesObserver < Sketchup::EntitiesObserver
  def onElementModified(entities, entity)
	return unless entity.is_a?(Sketchup::ComponentDefinition) 
    return if entity.count_instances > 0 
	return unless entity.valid?
	entities.model.start_operation("Purge Definition", true)
    puts "DEFINITIONS PURGED : #{entity.name}"
    entity.entities.clear!		
	entities.model.commit_operation
  end
end
mod = Sketchup.active_model
@myentitiesobserver ||= MyEntitiesObserver.new  
mod.entities.add_observer(@myentitiesobserver) 
# TO DELETE THE OBSERVER
mod.entities.reference(@myentitiesobserver)

I posted the code without the attribute conditions so that using the method is much easier for those who want to try. :wink:

Basic Error:
Dynamic components create definitions without instances in the model.
SketchUp_mrXVZvJsWo

DEFINITIONS ERROR BOX.skp (88.0 KB)

Solution:
SketchUp_vxDUiwjiID

I still have to find a better alternative than “ToolsObserver” to start or stop the observer when my dynamic components are used!

The observer must take the minimum possible resources because it must be active all the session to monitor my components.

EntityObserver:
Can be a good alternative provided that I don’t have to attach all my instances each time the model is opened.
This would be much too cumbersome, especially since my components have a lot of entities.

I think I finally found a good alternative!

The first thing is to create an attribute library on all the definitions offered by my extension.

For example if all my definitions are present in an open model, I can use this method:

# Attach an attribute dictionary to all definitions of a model
mod = Sketchup.active_model
defs = mod.definitions
defs.each{|d| d.set_attribute "my_dictionary","my_attribute","my_value"}

Then I have to attach Entities Observer to each opening of a model:

# Automatically purge definitions without instances with a defined attribute.
class MyEntitiesObserver < Sketchup::EntitiesObserver
  def onElementModified(entities, entity)
	return unless entity.is_a?(Sketchup::ComponentDefinition)
    return if entity.count_instances > 0 || entity.attribute_dictionaries.nil?
	val = entity.get_attribute "my_dictionary","my_attribute"
	return unless val == "my_value"
	return unless entity.valid?
	entities.model.start_operation("Purge Definition", true)
    entity.entities.clear!	
	entities.model.commit_operation
  end
end
# Attach "ToolsObserver" when opening a project or a new model
class MyAppObserver < Sketchup::AppObserver
  def activate_observer_entities( model )
    @change_entities ||= MyEntitiesObserver.new
    model.entities.add_observer(@change_entities)
  end
  def onNewModel(model)
    activate_observer_entities( model )
  end
  def onOpenModel(model)
    activate_observer_entities( model )
  end
end
Sketchup.add_observer(MyAppObserver.new)

If you have a better alternative, I’m always open to progress. :wink:

The method of the previous post is not finally suitable.

If I add a new instance to the model, Sketchup::EntitiesObserver.onElementModified will fire!

The result is that I find myself in a loop where the observer will want to purge the instance that has just been added.

I need to find a way for my watcher to activate only when one of my dynamic components is changed by the user.
This same observer must be deleted after the modification of the component.

When I think I have found a solution, I realize that is far from the case.
It’s not a big deal because it’s with this kind of disappointment that I’m going to end up progressing.