# Observer on the placement of a component

**URL:** <https://forums.sketchup.com/t/observer-on-the-placement-of-a-component/162126>\
**Category:** Ruby API\
**Tags:** observer\
**Created:** [April 26, 2021, 5:34pm UTC](https://forums.sketchup.com/t/observer-on-the-placement-of-a-component/162126 "2021-04-26T17:34:11Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![simjoubert](https://sea2.discourse-cdn.com/sketchup/user_avatar/forums.sketchup.com/simjoubert/32/224620_2.png) [@simjoubert](https://forums.sketchup.com/u/simjoubert)\
**Post date:** [April 26, 2021, 5:34pm UTC](https://forums.sketchup.com/t/observer-on-the-placement-of-a-component/162126/1 "2021-04-26T17:34:11Z")

</div>

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**

```ruby
# 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**

```ruby

#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

```

---

<div class="post-metadata">

**Author:** ![DanRathbun](https://sea2.discourse-cdn.com/sketchup/user_avatar/forums.sketchup.com/danrathbun/32/500_2.png) [@DanRathbun](https://forums.sketchup.com/u/DanRathbun)\
**Post date:** [April 26, 2021, 7:39pm UTC](https://forums.sketchup.com/t/observer-on-the-placement-of-a-component/162126/2 "2021-04-26T19:39:07Z")

</div>

See this reply with links to other topics …  
[Recreate Move Tool - #8 by DanRathbun](https://forums.sketchup.com/t/recreate-move-tool/26659/8)

* * *

`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`](https://ruby.sketchup.com/Sketchup/DefinitionObserver.html#onComponentInstanceAdded-instance_method) callback.

As an example, you create a little **hybrid** observer:

```ruby
    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:

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

```

---

<div class="post-metadata">

**Author:** ![simjoubert](https://sea2.discourse-cdn.com/sketchup/user_avatar/forums.sketchup.com/simjoubert/32/224620_2.png) [@simjoubert](https://forums.sketchup.com/u/simjoubert)\
**Post date:** [April 27, 2021, 7:51am UTC](https://forums.sketchup.com/t/observer-on-the-placement-of-a-component/162126/3 "2021-04-27T07:51:07Z")

</div>

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

```ruby
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

```

---

<div class="post-metadata">

**Author:** ![DanRathbun](https://sea2.discourse-cdn.com/sketchup/user_avatar/forums.sketchup.com/danrathbun/32/500_2.png) [@DanRathbun](https://forums.sketchup.com/u/DanRathbun)\
**Post date:** [April 27, 2021, 6:54pm UTC](https://forums.sketchup.com/t/observer-on-the-placement-of-a-component/162126/4 "2021-04-27T18:54:41Z")

</div>

> [@simjoubert](#):
>
> 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!

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

> [@simjoubert](#):
>
> 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?

Using the pipe symbol (`"|"`) is okay.  
The API [`UI::inputbox`](https://ruby.sketchup.com/UI.html#inputbox-class_method) 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 …

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

```

Or a hash …

```ruby
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 …

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

```

---

<div class="post-metadata">

**Author:** ![simjoubert](https://sea2.discourse-cdn.com/sketchup/user_avatar/forums.sketchup.com/simjoubert/32/224620_2.png) [@simjoubert](https://forums.sketchup.com/u/simjoubert)\
**Post date:** [April 28, 2021, 8:17am UTC](https://forums.sketchup.com/t/observer-on-the-placement-of-a-component/162126/5 "2021-04-28T08:17:42Z")

</div>

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**

```ruby
# 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**

```ruby
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

```ruby
#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

```

---

<div class="post-metadata">

**Author:** ![DanRathbun](https://sea2.discourse-cdn.com/sketchup/user_avatar/forums.sketchup.com/danrathbun/32/500_2.png) [@DanRathbun](https://forums.sketchup.com/u/DanRathbun)\
**Post date:** [April 28, 2021, 6:14pm UTC](https://forums.sketchup.com/t/observer-on-the-placement-of-a-component/162126/6 "2021-04-28T18:14:46Z")

</div>

> [@simjoubert](#):
>
> 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\> "

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.

---

<div class="post-metadata">

**Author:** ![simjoubert](https://sea2.discourse-cdn.com/sketchup/user_avatar/forums.sketchup.com/simjoubert/32/224620_2.png) [@simjoubert](https://forums.sketchup.com/u/simjoubert)\
**Post date:** [May 5, 2021, 10:53am UTC](https://forums.sketchup.com/t/observer-on-the-placement-of-a-component/162126/7 "2021-05-05T10:53:38Z")

</div>

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
