Observer on the placement of a component

Hello
I am making a small plugin to increase my workflow.
I create an order to import a component.
once placed I would like to update the two drop-down lists for its attribute ra_poste and ra_fournisseur?
the component remains selected after its palcement.
and I thought I could use the selection to trigger the update of its meta attribute _option.

Only the time to place the component on the model the script to unfold and the selection is not yet filled
I think it is necessary to monitor with an observer that the placement is effective in order to achieve the following.
Looking at the doc and examples I don’t understand how to use it and what feedback to test to be able to continue the script

Please help me understand how observers work.
Good night

here is the extract of the ruby api sketchup doc

# This is an example of an observer that watches the
# component placement event.
class MyModelObserver < Sketchup::ModelObserver
  def onPlaceComponent(instance)
    puts "onPlaceComponent: #{instance}"
  end
end

# Attach the observer.
Sketchup.active_model.add_observer(MyModelObserver.new)

here is my code


#Commande N°20 IMPORTER COMPOSANT MAIN OEUVRE
def composant_main_oeuvre_cmd
	model = Sketchup.active_model
	#On creer une variable URL qui a pour valeur le chermin le fichier.skp rechercher dans le dossier d'instalation du plugin
	url = Sketchup.find_support_file("MainOeuvre.skp", "Plugins/sj_attribut_rapport/skp/")
	cdef = model.definitions.load(url)
	return unless cdef # good practice
	# could be "next unless cdef" inside a loop
	model.place_component(cdef)
	
	#une fois placé l'objet reste selectionné
		#On récupère le poste du premier élément de la sélection
	sel = model.selection
	sd = sel.first.definition
	
			
	#Mise à jour liste déroulante  attribut POSTE
	
	postes_liste_array = model.get_attribute('AttributRapport', 'postes',"")
	poste_liste_option="&"+postes_liste_array .map {|a|a+"="+a+"&"}.join()
	sd.set_attribute DCDICT,"_ra_poste_options", poste_liste_option
	
	#Mise à jour liste déroulante attribut FOURNISSEUR
	
	fournisseurs_liste_array = model.get_attribute('AttributRapport', 'fournisseurs',"")
	fournisseurs_liste_option="&"+fournisseurs_liste_array .map {|a|a+"="+a+"&"}.join()
	sd.set_attribute DCDICT,"_ra_fournisseur_options", fournisseurs_liste_option

	#on redessinne le composant
	dcs = $dc_observers.get_latest_class
	dcs.redraw_with_undo(entity)
	
	
end#fin de composant_main_oeuvre_cmd

See this reply with links to other topics …
Recreate Move Tool - #8 by DanRathbun


place_component() is a quirky method.
It creates an undo operation so you cannot put a call to it inside another undo operation.
It also is asynchronous. It means that the Ruby code does not stop and wait for the component instance to be placed into the model.

So hopefully reading those old topics will help you know the way to code.

Please learn to search categories using the magnifying glass menu at top right.


You will not use the onPlaceComponent observer callback. Instead you know what the definition is, so you will instead use the DefinitionObserver#onComponentInstanceAdded callback.

As an example, you create a little hybrid observer:

    class PlaceInstanceSpy
      def initialize(definition, parent)
        @definition = definition
        @parent = parent
        # Attach this observer also to watch the tools collection
        # because the active tool changes as the placement proceeds:
        @definition.model.tools.add_observer(self)
      end
      def onComponentInstanceAdded(definition, instance)
        # Set attributes on the definition or the instance here,
        # by calling a method in the parent module:
        @parent.set_attribut(definition, instance)
        # Remove this observer instance. No longer needed.
        definition.remove_observer(self)
      end
      def onActiveToolChanged(tools, tool_name, tool_id)
        if tool_id == 21013 # ComponentTool
          # User has begun the component placement.
          # Status bar text says "Place component."
        elsif tool_id == 21048 # MoveTool
          # User has clicked and placed the instance.
          # Stop watching the tools collection:
          tools.remove_observer(self)
          # User is left with MoveTool active and new instance selected.
        elsif tool_id == 21022 # SelectionTool
          # User has cancelled the placement via ESC key.
          # Stop watching the definition:
          @definition.remove_observer(self)
          # Stop watching the tools collection:
          tools.remove_observer(self)
        end
      end
    end

Later on in the code use it when placing a component:

    # Attach Observer to cdef definition, (self is parent module):
    cdef.add_observer(PlaceInstanceSpy.new(cdef, self))
    # Start the placement:
    model.place_component(cdef)
1 Like

Thanks Dan
at the beginning I struggled a little to understand
but here is the code which works, with the insertion of the called components, the drop-down lists of the attribute post and supplier are well updated!
in the AttributReport dictionary of the model I stored the different values of the position and the supplier in the respective attributes. the different values are stored in a string with “|” as a separator. it works ! but is it regulatory?

thanks again
for the rest I will open a new post directly.

here is the code

class PlaceInstanceSpy
	def initialize(definition, parent)
		@definition = definition
		@parent = parent
		# Attach this observer also to watch the tools collection
		# because the active tool changes as the placement proceeds:
		@definition.model.tools.add_observer(self)
	end
	def onComponentInstanceAdded(definition, instance)
		# Set attributes on the definition or the instance here,
		# by calling a method in the parent module:#@parent.set_attribut(definition, instance)
		model = Sketchup.active_model
		
		#Mise à jour liste déroulante  attribut POSTE
		postes_liste = model.get_attribute('AttributRapport', 'postes',"")
		postes_liste_array = postes_liste.split("|")
		poste_liste_option = "&" + postes_liste_array.map {|a|a+"="+a+"&"}.join()
		definition.set_attribute DCDICT,"_ra_poste_options", poste_liste_option

		#Mise à jour liste déroulante attribut FOURNISSEUR
		fournisseurs_liste = model.get_attribute('AttributRapport', 'fournisseurs',"")
		fournisseurs_liste_array = fournisseurs_liste.split("|")
		fournisseurs_liste_option = "&"+fournisseurs_liste_array.map {|a|a+"="+a+"&"}.join()
		definition.set_attribute DCDICT,"_ra_fournisseur_options", fournisseurs_liste_option

		#on redessinne le composant
		dcs = $dc_observers.get_latest_class
		dcs.redraw_with_undo(instance)
		
		# Remove this observer instance. No longer needed.
		definition.remove_observer(self)
	end
	def onActiveToolChanged(tools, tool_name, tool_id)
		if tool_id == 21013 # ComponentTool
			# User has begun the component placement.
			# Status bar text says "Place component."
		elsif tool_id == 21048 # MoveTool
			# User has clicked and placed the instance
			# Stop watching the tools collection:
			tools.remove_observer(self)
			# User is left with MoveTool active and new instance selected.
		elsif tool_id == 21022 # SelectionTool
			# User has cancelled the placement via ESC key.
			# Stop watching the definition:
			@definition.remove_observer(self)
			# Stop watching the tools collection:
			tools.remove_observer(self)
		end
	end
end

#Commande N°20 IMPORTER COMPOSANT MAIN OEUVRE
def composant_main_oeuvre_cmd

	model = Sketchup.active_model
	#On creer une variable URL qui a pour valeur le chermin le fichier.skp rechercher dans le dossier d'instalation du plugin
	url = Sketchup.find_support_file("MainOeuvre.skp", "Plugins/sj_attribut_rapport/skp/")
	cdef = model.definitions.load(url)
	return unless cdef # good practice
	# could be "next unless cdef" inside a loop
	cdef.add_observer(PlaceInstanceSpy.new(cdef, self))	
	model.place_component(cdef)

end#fin de composant_main_oeuvre_cmd

I meant for you to put that code in a method out in the parent module.
(This is what the @parent reference is passed into the observer for, … so it an be used to call this outer method.)

Using the pipe symbol ("|") is okay.
The API UI::inputbox method also uses this to accept choices for the dropdown pick lists.

It is easy to join an array of values into a pipe separated string …

values = ["blue", "red", "green"]
valstr = values.join('|')

Or a hash …

h = {"c"=>300, "a"=>100, "d"=>400, "b"=>200}
valstr = h.values.join('|') #=> "300|100|400|200"

The values array is returned in the order that the key/value pairs were defined.

If (for example) you wanted the values returned in order of sorted keys, then …

h.sort.to_h.values.join('|') #=> "100|200|300|400"

Hello Dan

the “composant_main_oeuvre_cmd” command creates an observer “onComponentInstanceAdded” which calls a method “ajoutAttributsComposantImporter(definition, instance)” in return to create the attributes for the placed component

If I take out the definition of the attributes in an methode outside the “PlaceInstanceSpy” class Sketchup returns the error "Error: #<NoMethodError: private method `ajoutAttributsComposantImporter_cmd’ called for main:Object> "
I can’t find the correct syntax

here is the code of the class

# component placement event.
class PlaceInstanceSpy
	def initialize(definition, parent)
		@definition = definition
		@parent = parent
		# Attach this observer also to watch the tools collection
		# because the active tool changes as the placement proceeds:
		@definition.model.tools.add_observer(self)
	end
	def onComponentInstanceAdded(definition, instance)
		# Set attributes on the definition or the instance here,
		# # by calling a method in the parent module:
		
		@parent.ajoutAttributsComposantImporter(definition, instance)
				
		# on supprime l'observateur qui devient inutile
		definition.remove_observer(self)
	end
	def onActiveToolChanged(tools, tool_name, tool_id)
		if tool_id == 21013 # ComponentTool
			# User has begun the component placement.
			# Status bar text says "Place component."
		elsif tool_id == 21048 # MoveTool
			# User has clicked and placed the instance
			# Stop watching the tools collection:
			tools.remove_observer(self)
			# User is left with MoveTool active and new instance selected.
		elsif tool_id == 21022 # SelectionTool
			# User has cancelled the placement via ESC key.
			# Stop watching the definition:
			@definition.remove_observer(self)
			# Stop watching the tools collection:
			tools.remove_observer(self)
		end
	end
end #Fin Class PlaceInstanceSpy

here is the code of the methode called

def ajoutAttributsComposantImporter(definition, instance)
		# by calling a method in the parent module:
		definition = @definition 
		instance = @instance
		
		model = Sketchup.active_model
		
		#Creer un attribut de separation RAPPORT
		definition.set_attribute DCDICT,"ra_","**********"
		definition.set_attribute DCDICT,"_ra__access","VIEW"
		definition.set_attribute DCDICT,"_ra__formlabel","***RAPPORT***" 
		definition.set_attribute DCDICT,"_ra__formulaunits","STRING"
		definition.set_attribute DCDICT,"_ra__label","ra_"
		definition.set_attribute DCDICT,"_ra__options","&"
		definition.set_attribute DCDICT,"_ra__units", "STRING"
		
		#Creer un attribut ARTICLE
		# On récupère le nom du composant dans sa définition et on le passe en variable sname et on le netoie du suffixe #suivie des numero d'instance
		sdname = definition.name
		aname = sdname.split(/#/)
		sname = aname[0]
		
		definition.set_attribute DCDICT,"ra_article",sname
		definition.set_attribute DCDICT,"_ra_article_access","TEXTBOX"
		definition.set_attribute DCDICT,"_ra_article_formlabel","Article" 
		definition.set_attribute DCDICT,"_ra_article_formulaunits","STRING"
		definition.set_attribute DCDICT,"_ra_article_label","ra_article"
		definition.set_attribute DCDICT,"_ra_article_options","&"
		definition.set_attribute DCDICT,"_ra_article_units", "STRING"
		
		#Creer un attribut COMMENTAIRE
		definition.set_attribute DCDICT,"ra_commentaire",""
		definition.set_attribute DCDICT,"_ra_commentaire_access","TEXTBOX"
		definition.set_attribute DCDICT,"_ra_commentaire_formlabel","Commentaire" 
		definition.set_attribute DCDICT,"_ra_commentaire_formulaunits","STRING"
		definition.set_attribute DCDICT,"_ra_commentaire_label","ra_commentaire"
		definition.set_attribute DCDICT,"_ra_commentaire_options","&"
		definition.set_attribute DCDICT,"_ra_commentaire_units", "STRING"
		
		#Creer un attribut POSTE
		definition.set_attribute DCDICT,"ra_poste", ""
		definition.set_attribute DCDICT,"_ra_poste_access","LIST"
		definition.set_attribute DCDICT,"_ra_poste_formlabel","Poste" 
		definition.set_attribute DCDICT,"_ra_poste_formulaunits","STRING"
		definition.set_attribute DCDICT,"_ra_poste_label","ra_poste"
		definition.set_attribute DCDICT,"_ra_poste_units", "STRING"
		#Mise à jour liste déroulante  attribut POSTE
		postes_liste = model.get_attribute('AttributRapport', 'postes',"")
		postes_liste_array = postes_liste.split("|")
		poste_liste_option = "&" + postes_liste_array.map {|a|a+"="+a+"&"}.join()
		definition.set_attribute DCDICT,"_ra_poste_options", poste_liste_option

		#Creer un attribut Fournisseur
		definition.set_attribute DCDICT,"ra_fournisseur", ""
		definition.set_attribute DCDICT,"_ra_fournisseur_access","LIST"
		definition.set_attribute DCDICT,"_ra_fournisseur_formlabel","Fournisseur" 
		definition.set_attribute DCDICT,"_ra_fournisseur_formulaunits","STRING"
		definition.set_attribute DCDICT,"_ra_fournisseur_label","ra_fournisseur"
		definition.set_attribute DCDICT,"_ra_fournisseur_units", "STRING"
		#Mise à jour liste déroulante attribut FOURNISSEUR
		fournisseurs_liste = model.get_attribute('AttributRapport', 'fournisseurs',"")
		fournisseurs_liste_array = fournisseurs_liste.split("|")
		fournisseurs_liste_option = "&"+fournisseurs_liste_array.map {|a|a+"="+a+"&"}.join()
		definition.set_attribute DCDICT,"_ra_fournisseur_options", fournisseurs_liste_option
		
		#Creer un attribut PRIX UNITAIRE
		#On récupère l'attribut Price du dictionnaire Su_DefinitionSet et on le passe en variable sprice si acune valeur alors 0
		sprice = definition.get_attribute("SU_DefinitionSet", "Price", 0)
		definition.set_attribute DCDICT,"ra_prix_unitaire",sprice
		definition.set_attribute DCDICT,"_ra_prix_unitaire_access","TEXTBOX"
		definition.set_attribute DCDICT,"_ra_prix_unitaire_formlabel","Prix unitaire" 
		definition.set_attribute DCDICT,"_ra_prix_unitaire_formulaunits","FLOAT"
		definition.set_attribute DCDICT,"_ra_prix_unitaire_label","ra_prix_unitaire"
		definition.set_attribute DCDICT,"_ra_prix_unitaire_options","&"
		definition.set_attribute DCDICT,"_ra_prix_unitaire_units", "EUROS"
		
		#Creer un attribut QTT saisie manuellement
		definition.set_attribute DCDICT,"ra_qtt","1"
		definition.set_attribute DCDICT,"_ra_qtt_access","TEXTBOX"
		definition.set_attribute DCDICT,"_ra_qtt_formlabel","QTT" 
		definition.set_attribute DCDICT,"_ra_qtt_formulaunits","FLOAT"
		definition.set_attribute DCDICT,"_ra_qtt_label","ra_qtt"
		definition.set_attribute DCDICT,"_ra_qtt_options","&"
		definition.set_attribute DCDICT,"_ra_qtt_units", "FLOAT"
		
		#Creer un attribut Unite liste
		
		#valeur par défaut selon le nom de la définition du composant
		case sname
		when "Main d'oeuvre"
		unite = "h"
		when "Livraison"
		unite = "ft"
		when "Mise en place chantier"
		unite = "ft"
		when "Dechets verts"
		unite = "mÂł"
		when "Gravats"
		unite = "T"
		when "Dechets de chantier"
		unite = "T"
		else
		unite = "u"
		end
		
		definition.set_attribute DCDICT,"ra_unite",unite
		definition.set_attribute DCDICT,"_ra_unite_access","LIST"
		definition.set_attribute DCDICT,"_ra_unite_formlabel","Unite" 
		definition.set_attribute DCDICT,"_ra_unite_formulaunits","STRING"
		definition.set_attribute DCDICT,"_ra_unite_label","ra_unite"
		definition.set_attribute DCDICT,"_ra_unite_options","&Unité=u&Forfait=ft&Heure=h&Mètres=m&Surface=m²&Volume%20m3=m³&Volume%20Litres=l&Tonne=T&Kilos=Kg&"
		definition.set_attribute DCDICT,"_ra_unite_units", "STRING"
		

		#Creer un attribut SOUS TOTAL
		definition.set_attribute DCDICT,"ra_sous_total","0"
		definition.set_attribute DCDICT,"_ra_sous_total_access","VIEW"
		definition.set_attribute DCDICT,"_ra_sous_total_formlabel","Sous total" 
		definition.set_attribute DCDICT,"_ra_sous_total_formulaunits","FLOAT"
		definition.set_attribute DCDICT,"_ra_sous_total_formula","ra_qtt*ra_prix_unitaire"
		definition.set_attribute DCDICT,"_ra_sous_total_label","ra_sous_total"
		definition.set_attribute DCDICT,"_ra_sous_total_options","&"
		definition.set_attribute DCDICT,"_ra_sous_total_units", "EUROS"

		#on redessinne le composant
		dcs = $dc_observers.get_latest_class
		dcs.redraw_with_undo(instance)
		
		# On creer un calque Tag ATR_Services
		layer = Sketchup.active_model.layers.add "ATR_Services"
		# On tag l'instance du composant sur le calque ATR_Services
		newlayer = instance.layer = layer
	end

and finally the code of the initial command to place the component

#Commande N°20 IMPORTER COMPOSANT MAIN OEUVRE
def composant_main_oeuvre_cmd

	model = Sketchup.active_model
	#On creer une variable URL qui a pour valeur le chermin le fichier.skp rechercher dans le dossier d'instalation du plugin
	url = Sketchup.find_support_file("MainOeuvre.skp", "Plugins/sj_attribut_rapport/skp/")
	cdef = model.definitions.load(url)
	return unless cdef # good practice
	# could be "next unless cdef" inside a loop
	
	#on ajoutte un observateur en de la classe PlaceInstanceSpy
	#il va mettre à jour les listes déroulantes poste et fournisseur des que le composant sera placé et il sera ensuite redessiné
	cdef.add_observer(PlaceInstanceSpy.new(cdef, self))	
	model.place_component(cdef)
	end#fin de composant_main_oeuvre_cmd

This means that you are defining methods at the top level ObjectSpace which is class Object.
means that your methods would become global and get inherited by everyone’s else’s code objects.


ALL of your code MUST be within a UNIQUE top level namespace module.
Then EACH of your extensions MUST be within a submodule of YOUR namespace module.

If you put extend self at the top of your extension submodule, it’s methods can call each other without qualification.

Hello Dan
It took me a long time to find!
I found your post where you present the blank template to write plugins well and I realized that I had declared the namespace and the module but only in the declaration file of the plugin and in the loader but not in the main.
Calling the command from the observer works
Thanks to you

1 Like