DC set_attribute doesn't save with the component

Hello,
I wrote a little plugin to speed up my workflow.
It uses dynamic components.
Here is one of the commands of my report_attribute module


   ###
    #Commande N°10 update name of article
    ###
    cmd10 = UI::Command.new("Mise à jour du nom de l'article") {

      #recover name of article of the first object in selection
      mod = Sketchup.active_model	
      sel = mod.selection
      selfirst = sel.first
      #we define the oldvalue variable which is the old value for item otherwise the name of the component
      oldvalue = selfirst.get_attribute( "dynamic_attributes", "ra_article" ,"")
      if oldvalue == ""
      selfirstdef = selfirst.definition
      oldvalue = selfirstdef.name
      end

      #construction of a dialog box to enter the new value to apply 
      prompts = ["New name : "]
      defaults = [oldvalue]
      input = UI.inputbox(prompts, defaults, "apply new name for the selection.")
      #Test result
      if input == false
    # if user live
      else
    newarticle = input[0]

    # ### Set or update Article attribute
    # mod = Sketchup.active_model	
    # sel = mod.selection
    sel.grep(Sketchup::ComponentInstance).each do |s|
    s.set_attribute "dynamic_attributes","_ra_article_access","TEXTBOX"
    s.set_attribute "dynamic_attributes","_ra_article_formlabel","Article" 
    s.set_attribute "dynamic_attributes","_ra_article_formulaunits","STRING"
    s.set_attribute "dynamic_attributes","_ra_article_label","ra_article"
    s.set_attribute "dynamic_attributes","_ra_article_options","&"
    s.set_attribute "dynamic_attributes","_ra_article_units", "STRING"
    s.set_attribute "dynamic_attributes","ra_article", newarticle # Inputbox value
    $dc_observers.get_latest_class.redraw_with_undo(s)
      end  

    end
    }#end Commande N°10 update name of article

Create or update the “ra_article” attribute.

My problem is that the attribute created is not saved with the component when I save the component.
Looking at the console I have no error.
Looking at the attribute inspector, the attribute and meta attribute are well created but are applied to “drawing element” and not to “component definition”.

if in grep I replace ComponentInstance by ComponentDefinition the console returns an error.

It is the definition that is saved.

Dynamic attributes need to be saved (as the default values) into the definition’s “dynamic_attributes” dictionary.

If an instance needs to have a different dynamic value than the definition, then the DC extension will clone the appropriate entries for the attribute into the instance’s “dynamic_attributes” dictionary.

    sel.grep(Sketchup::ComponentInstance).each do |inst|
      s = inst.definition
      s.set_attribute("dynamic_attributes","_ra_article_access","TEXTBOX")
      # ... etc. ...

Please correct your incorrect indentation of blocks. (Set your editor to replace tabs with 2 spaces.)

Do not use outdented code. Outdents make code hard to read and break indent guidelines.

Define constant strings rather than using multiple literal strings that are the same.

Always use parenthesis around instance method calls arguments (except calls for methods from BasicObject, Object, Kernel, Module and Class. Ie, all those methods that are globally inherited.) Usually all of the API method calls should use parenthesis.

Put spaces between method arguments

Conditionals like if input == false have an unneeded #== method call.
Better to just let the interpreter determine the boolean result of input as in Ruby nil is evaluated as FALSE.
So you can do if !input or unless input.
Within a method, return unless input is a common pattern.

Do yourself a favor and put only a single command method call inside UI::Command blocks.
Then the command’s code will be within a method which can be redefined upon a code reload to update and correct errors during development. Ie, UI::Command blocks cannot be redefined by reloading the code (because they are linked to operating system objects.)

Examples:

At top of extension module define constants …

    extend self

    DCDICT = "dynamic_attributes"

At place in code where command methods are defined …

    def update_article_cmd
      # Recover name of article of the first object in selection
      mod = Sketchup.active_model	
      sel = mod.selection
      selfirst = sel.first
      # We define the oldvalue variable which is the old value
      # for item otherwise the name of the component
      oldvalue = selfirst.get_attribute(DCDICT, "ra_article" , "")
      if oldvalue == ""
        selfirstdef = selfirst.definition
        oldvalue = selfirstdef.name
      end

      # Construction of a dialog box to enter the new value to apply 
      prompts  = ["New name : "]
      defaults = [oldvalue]
      input = UI.inputbox(prompts, defaults, "apply new name for the selection.")
      # Test result
      return unless input
      newarticle = input[0]
      # ### Set or update Article attribute
      sel.grep(Sketchup::ComponentInstance).each do |inst|
        s = inst.definition
        s.set_attribute(DCDICT, "_ra_article_access", "TEXTBOX")
        s.set_attribute(DCDICT, "_ra_article_formlabel", "Article")
        s.set_attribute(DCDICT, "_ra_article_formulaunits", "STRING")
        s.set_attribute(DCDICT, "_ra_article_label", "ra_article")
        s.set_attribute(DCDICT, "_ra_article_options", "&")
        s.set_attribute(DCDICT, "_ra_article_units", "STRING")
        s.set_attribute(DCDICT, "ra_article", newarticle) # Inputbox value
        $dc_observers.get_latest_class.redraw_with_undo(s)
      end
    end ### update_article_cmd()

(Scroll to see all code.)

Later on, at end of the code in the RUN ONCE block where UI objects are defined …

    unless defined?(@loaded)

      # Definitions for command objects 1..9

      cmd10 = UI::Command.new("Mise à jour du nom de l'article") {
        update_article_cmd()
      }

      # Perhaps other commands ...

      @loaded = true
    end

Thanks for your detailed response.
For the rehearsals I did it on purpose for my first draft to clearly identify the steps in my code and fully understand what I was writing. I saw that notepad ++ proposed the indentation but I didn’t know how much.
It will take a little while for me to digest the answer.
I am not sure for the moment to understand everything I will take it step by step.
Good night

Hello
I digested the answers a bit.
when i activate extend self at the start of the script it bug i don’t know if i placed it correctly or really what it is for.

I created according to your advice a definition and I use the definition in the command

on another command that create dynamic attributes for a selected object set

in the command I create an array of attributes which are themselves a hash of key value of the meta attributes

Here is the code of my command

###
#Commande N°1 ATTRIBUT RAPPORT GENERIQUE
###
cmd1 = UI::Command.new("Attributs rapport") {
  UI.messagebox("Creation des tableauattributs rapport pour le composant selectionné") 
  
  # ### DEFINIR LES CENTIMETRES COMME UNITE DE CALCUL DU COMPOSANT
  mod = Sketchup.active_model
  sel = mod.selection
  sel.grep(Sketchup::ComponentInstance).each do |s|
    s.set_attribute "dynamic_attributes","_lengthunits","CENTIMETERS"
    $dc_observers.get_latest_class.redraw_with_undo(s)
  end 
  
  #Initialisation du tableau d'attribut
  dc_attributs = []
  
  # Creer un attribut de separation RAPPORT
  rapport = {
    "aname" => "ra_",
    "alabel" => "ra_",
    "aformulaunits" => "STRING",
    "aaccess" => "VIEW",
    "aformlabel" => "***RAPPORT***",
    "aoptions" => "&",
    "aformula" => "",
    "aunits" => "STRING",
    "avalue" => "**********"
  }
    
  dc_attributs << rapport
   
 
  
  # Creer un attribut Epaisseur saisie manuellement
  epaisseur = {
    "aname" => "ra_epaisseur",
    "alabel" => "ra_Epaisseur",
    "aformulaunits" => "CENTIMETERS",
    "aaccess" => "TEXTBOX",
    "aformlabel" => "Epaisseur",
    "aoptions" => "&",
    "aformula" => "",
    "aunits" => "CENTIMETERS",
    "avalue" => "1"
  }
  
  dc_attributs << epaisseur
 
  # Creer un attribut ARTICLE
  article = {
    "aname" => "ra_article",
    "alabel" => "ra_Article",
    "aformulaunits" => "STRING",
    "aaccess" => "TEXTBOX",
    "aformlabel" => "Article",
    "aoptions" => "&",
    "aformula" => "",
    "aunits" => "STRING",
    "avalue" => "DC_DEF_NAME"
  }
  dc_attributs << article

  # Creer un attribut PID
  pid = {
    "aname" => "ra_pid",
    "alabel" => "ra_pid",
    "aformulaunits" => "STRING",
    "aaccess" => "VIEW",
    "aformlabel" => "ID",
    "aoptions" => "&",
    "aformula" => "",
    "aunits" => "STRING",
    "avalue" => "DC_DEF_PID"
  }
    
  dc_attributs << pid

  # Creer un attribut COMMENTAIRE
  commentaire = {
    "aname" => "ra_commentaire",
    "alabel" => "ra_Commentaire",
    "aformulaunits" => "STRING",
    "aaccess" => "TEXTBOX",
    "aformlabel" => "Commentaire",
    "aoptions" => "&",
    "aformula" => "",
    "aunits" => "STRING",
    "avalue" => ""
  }
    
  dc_attributs << commentaire

  # Creer un attribut PRIX UNITAIRE
  prix_unitaire = {
    "aname" => "ra_prix_unitaire",
    "alabel" => "ra_Prix_unitaire",
    "aformulaunits" => "FLOAT",
    "aaccess" => "TEXTBOX",
    "aformlabel" => "Prix unitaire",
    "aoptions" => "&",
    "aformula" => "",
    "aunits" => "EUROS",
    "avalue" => "DC_DEF_PRICE"
  }
    
  dc_attributs << prix_unitaire

  # Creer un attribut QTT saisie manuellement
  qtt_saisie = {
    "aname" => "ra_qtt_saisie",
    "alabel" => "ra_QTT_Saisie",
    "aformulaunits" => "FLOAT",
    "aaccess" => "TEXTBOX",
    "aformlabel" => "Quantite saisie",
    "aoptions" => "&",
    "aformula" => "",
    "aunits" => "FLOAT",
    "avalue" => "1"
  }
  
  dc_attributs << qtt_saisie

  # Creer un attribut Type Quantite
  type = {
    "aname" => "ra_type",
    "alabel" => "ra_Type",
    "aformulaunits" => "STRING",
    "aaccess" => "LIST",
    "aformlabel" => "Type quantité",
    "aoptions" => "&U=U&H=H&FT=FT&Longueur%20X=LenX&Longueur%20Y=LenY&Longueur%20Z=LenZ&Surface%20XY=Surface%20XY&Surface%20XZ=Surface%20XZ&Surface%20YZ=Surface%20YZ&Surface%20volumique=Surface%20volumique&Volume%20XYZ=Volume%20XYZ&Volume%20m3=Volume%20m3&Volume%20dm3=Volume%20dm3&Volume%20cm3=Volume%20cm3&Volume%20litres=Volume%20litres&Volume%20dl=Volume%20dl&Volume%20cl=Volume%20cl&Volume%20ml=Volume%20ml&",
    "aformula" => "",
    "aunits" => "STRING",
    "avalue" => "U"
  }
  
  dc_attributs << type
  
  # Creer un attribut QTT Calculee
  qtt = {
    "aname" => "ra_qtt",
    "alabel" => "ra_QTT",
    "aformulaunits" => "FLOAT",
    "aaccess" => "VIEW",
    "aformlabel" => "QTT",
    "aoptions" => "&",
    "aformula" => "CHOOSE(OPTIONINDEX(\"ra_Type\"),ra_QTT_Saisie,ra_QTT_Saisie,ra_QTT_Saisie,ROUND(LenX/100,3),ROUND(LenY/100,3),ROUND(LenZ/100,3),ROUND(Volume()/61023.7440947/(LenZ/100),3),ROUND(Volume()/61023.7440947/(LenY/100),3),ROUND(Volume()/61023.7440947/(LenX/100),3),ROUND(VOLUME()/61023.7440947/(ra_Epaisseur/100),3),ROUND(LenX*LenY*LenZ/1000000,3),Round(VOLUME()/61023.7440947,3),Round(VOLUME()/61023.7440947*1000,3),Round(VOLUME()/61023.7440947*1000000,3),Round(VOLUME()/61023.7440947*1000,3),Round(VOLUME()/61023.7440947*10000,3),Round(VOLUME()/61023.7440947*100000,3),Round(VOLUME()/61023.7440947*1000000,3))",
    "aunits" => "FLOAT",
    "avalue" => "1"
  }
  
  dc_attributs << qtt

  # Creer un attribut Unite Calculee
  unite = {
    "aname" => "ra_unite",
    "alabel" => "ra_Unite",
    "aformulaunits" => "STRING",
    "aaccess" => "VIEW",
    "aformlabel" => "Unite",
    "aoptions" => "&",
    "aformula" => "CHOOSE(OPTIONINDEX(\"ra_Type\"),\"u\",\"h\",\"ft\",\"m\",\"m\",\"m\",\"m²\",\"m²\",\"m²\",\"m²\",\"m³\",\"m³\",\"dm³\",\"cm³\",\"l\",\"dl\",\"cl\",\"ml\")",
    "aunits" => "STRING",
    "avalue" => "u"
  }
    
  dc_attributs << unite

  # Creer un attribut SOUS TOTAL
  sous_total = {
    "aname" => "ra_sous_total",
    "alabel" => "ra_Sous_Total",
    "aformulaunits" => "FLOAT",
    "aaccess" => "VIEW",
    "aformlabel" => "Sous total",
    "aoptions" => "&",
    "aformula" => "ra_QTT*ra_Prix_Unitaire",
    "aunits" => "EUROS",
    "avalue" => "0"
  }
    
  dc_attributs << sous_total
    
  creer_attribut_dynamique(dc_attributs)    
     
}
###FIN COMMANDE N°1


In my method definition I don’t know how to retrieve the array passed as parameters in the command
I tried with and without “# {array}”

here is the method code

# First we pull in the standard API hooks.
require 'sketchup.rb'
DCDICT = "dynamic_attributes"
#extend self


# Show the Ruby Console at startup so we can
# see any programming errors we may make.
Sketchup.send_action "showRubyPanel:"

#_____________________________DEFINITIONS METHODES__________________________________________

#Définition de la méthode pour créer un attribut dynamique sur une selection  
def creer_attribut_dynamique(dc_attributs)
  # aname => nom de l'attribut sans espace, sans majuscule, sans "_" ou nombre en premier caractère valeur par défaut "attribut"
  # alabel => label de l'attribut sans espace, sans "_" ou nombre en premier caractère valeur par défaut "Attribut"
  # aformulaunits => unité de l'attribut "STRING"|"FLOAT"|"CENTIMETERS"|"INCHES" par défaut "STRING"
  # aaccess => access mode de visibilité dans le panneau option "TEXTBOX"|"NONE"|"VIEW"|"LIST" par défaut "TEXTBOX"
  # aformlabel => etiquette de l'attribut dans le panneau option par défaut "Attribut"
  # aoptions => liste d'option déroulante si access = "LISTE" . liste de CLE=VALEUR encadrée par de "&" exemple "&CLE1=VALEUR1&CLE2=VALEUR2&" valeur par défaut "&"
  # aformula => Formule ne pas préfixer ave "=", si aucun laissé vide, valeur par défaut ""
  # aunits => unité de l'attribut dans le formulaire "STRING|FLOAT|PERCENT|INTEGER|BOOLEAN|CENTIMETERS|MILLIMETERS|METERS|INCHES|FEET|YARDS|DEGRES|DOLLARS|EUROS|YEN|POUNDS|KILOGRAMS" par défaut "STRING"
  # avalue => valeur de l'attribut
    
  model = Sketchup.active_model
  #On indique le début de l'opération pour une éventuelle annulation
  model.start_operation('créer attribut', true,true,false)
  model.selection.grep(Sketchup::ComponentInstance) { |entity|
    # This will check if there is an dictionary with the given name. The second
    # argument tell SU to not create it if it's missing.
    #next unless entity.definition.attribute_dictionary('dynamic_attributes', false)
    # Now we can be sure the component has the DC dictionary.
    # NOTE: When you do this you end up creating multiple undo operations.
    #       If you plan on releasing this you should make sure you wrap the
    #       code up on model.start/commit_operation such that everything can be
    #       undone in one step. (Requirement for Extension Warehouse)
    
    #on récupère la définition de l'instance, toutes les instances du composants sélectionnées ou pas recevront la création d'attribut
    sd = entity.definition
    
    # Iteration sur chaque valeur du tableau dc_attributs

    dc_attributs.each do |dc_attribut|
    
     # #on definit les avriables pour charque tableauattributs
      aname = dc_attribut["aname"]
      alabel = dc_attribut["alabel"]
      aformulaunits = dc_attribut["aformulaunits"]
      aaccess = dc_attribut["aaccess"]
      aformlabel = dc_attribut["aformlabel"]
      aoptions = dc_attribut["aoptions"]
      aformula = dc_attribut["aformula"]
      aunits = dc_attribut["aunits"]
      avalue = dc_attribut["avalue"]
      
    
      #on test la valeur de avalue passer en parametre pour verifier quelle ne contient pas une commande spéciale
      #commande spéciales : 
      #   DC_DEF_NAME on récupère le nom du composant
      #   DC_DEF_PRICE le prix
      #   DC_DEF_PID l'ID persistant
      
      if avalue == "DC_DEF_NAME"
        avalue = sd.name
      elsif avalue == "DC_DEF_PRICE"
        avalue = sd.get_attribute("SU_DefinitionSet", "Price", 0)
      elsif avalue == "DC_DEF_PID"
        avalue = entity.persistent_id
      end
      
      
      #Définition des noms des méta-tableauattributs
      anameaccess = "_" + aname + "_access"
      anameformlabel = "_" + aname + "_formlabel"
      anameformulaunits = "_" + aname + "_formulaunits"
      anamelabel = "_" + aname + "_label"
      anameoptions = "_" + aname + "_options"
      anameunits = "_" + aname + "_units"
      anameformula = "_" + aname + "_formula"
      
      #On définit l'attribut et les méta-tableauattributs
      sd.set_attribute(DCDICT, anameaccess, aaccess)
      sd.set_attribute(DCDICT, anameformlabel, aformlabel) 
      sd.set_attribute(DCDICT, anameformulaunits, aformulaunits)
      sd.set_attribute(DCDICT, anamelabel, alabel)
      sd.set_attribute(DCDICT, anameoptions, aoptions)
      sd.set_attribute(DCDICT, anameunits, aunits)
      # en cas d'abscence de formule on ne créer pas le meta-attribut formula
      if aformula != ""
        sd.set_attribute(DCDICT, anameformula, aformula)
      end
      sd.set_attribute(DCDICT, aname, avalue)
      
      #on incremente i
      i += 1
    
    
    end #Fin de la boucle sur chaque entite du tableau tableauattributs
    
    #On redessine le composant
    dcs = $dc_observers.get_latest_class
    dcs.redraw_with_undo(entity)
  
  } 
    status = model.commit_operation
    if status == true
      UI.messagebox('Attribut(s) créé(s)',MB_OK)
    end
end
#Fin définition méthode creer_attribut_dynamique

thank you

It must be used within a module. Is all of your code within modules ?

You need to come up with a name for a top level unique namespace module.
Then each of your extensions must be nested within your namespace module.

You must not define other objects (except for your namespace module) at the toplevel ObjectSpace. Doing do makes these objects global.

module Simjoubert # <<---<<< namespace module
  module AttributeReporter # <<---<<< extension submodule

    extend self

    # extension code

  end # extension submodule
end  # top level namespace module

This does not “pull in API hooks”. You do not need to do this as the “sketchup.rb” file is loaded by SketchUp itself just after it loads Ruby.


No you did not understand my advice. The ONLY code for a command should be a SINGLE method call for the command. (But that “command method” can branch off by calling other methods so the code is well organized.)

thank you for your comeback
I did create a module upstream in a namespace

I didn’t understand your last remark

“No, you didn’t understand my advice. The code ONLY for a command must be a SINGLE method call for the command. (But this” command method "can branch off into calling other methods to make the code well organised.) "

The command is called once by the item in the menu
and the command calls the definition
another command will call it with different parameters.
I do not understand what you are trying to explain to me, sorry, but I do want a supplement on this point.

otherwise in my method definition I cannot retrieve the values passed in parameter via an array containing hashes
I don’t understand or is my mistake on this.

thanks

This is an old way of showing the console. Since SU2014 the console is exposed to the API as a singleton object of the Sketchup::Console class. This singleton instance is referenced as the global constant SKETCHUP_CONSOLE.

So prefer …

  SKETCHUP_CONSOLE.show

… or conditionally …

  SKETCHUP_CONSOLE.show if not SKETCHUP_CONSOLE.visible?

I already said it plainly

and I gave you a code example that shows the UI::Command block making only ONE (1) method call to a central method that controls the command workflow.

SO … let us try another example …

      def attributs_rapport_cmd()
        UI.messagebox("Creation des tableauattributs rapport pour le composant selectionné") 
        
        # ### DEFINIR LES CENTIMETRES COMME UNITE DE CALCUL DU COMPOSANT
        mod = Sketchup.active_model
        sel = mod.selection
        sel.grep(Sketchup::ComponentInstance).each do |s|
          s.set_attribute "dynamic_attributes","_lengthunits","CENTIMETERS"
          $dc_observers.get_latest_class.redraw_with_undo(s)
        end 

        #Initialisation du tableau d'attribut
        dc_attributs = []

        # Creer un attribut de separation RAPPORT
        rapport = {
          "aname" => "ra_",
          "alabel" => "ra_",
          "aformulaunits" => "STRING",
          "aaccess" => "VIEW",
          "aformlabel" => "***RAPPORT***",
          "aoptions" => "&",
          "aformula" => "",
          "aunits" => "STRING",
          "avalue" => "**********"
        }

        dc_attributs << rapport
        
        # Creer un attribut Epaisseur saisie manuellement
        epaisseur = {
          "aname" => "ra_epaisseur",
          "alabel" => "ra_Epaisseur",
          "aformulaunits" => "CENTIMETERS",
          "aaccess" => "TEXTBOX",
          "aformlabel" => "Epaisseur",
          "aoptions" => "&",
          "aformula" => "",
          "aunits" => "CENTIMETERS",
          "avalue" => "1"
        }
        
        dc_attributs << epaisseur
       
        # Creer un attribut ARTICLE
        article = {
          "aname" => "ra_article",
          "alabel" => "ra_Article",
          "aformulaunits" => "STRING",
          "aaccess" => "TEXTBOX",
          "aformlabel" => "Article",
          "aoptions" => "&",
          "aformula" => "",
          "aunits" => "STRING",
          "avalue" => "DC_DEF_NAME"
        }
        dc_attributs << article

        # Creer un attribut PID
        pid = {
          "aname" => "ra_pid",
          "alabel" => "ra_pid",
          "aformulaunits" => "STRING",
          "aaccess" => "VIEW",
          "aformlabel" => "ID",
          "aoptions" => "&",
          "aformula" => "",
          "aunits" => "STRING",
          "avalue" => "DC_DEF_PID"
        }

        dc_attributs << pid

        # Creer un attribut COMMENTAIRE
        commentaire = {
          "aname" => "ra_commentaire",
          "alabel" => "ra_Commentaire",
          "aformulaunits" => "STRING",
          "aaccess" => "TEXTBOX",
          "aformlabel" => "Commentaire",
          "aoptions" => "&",
          "aformula" => "",
          "aunits" => "STRING",
          "avalue" => ""
        }

        dc_attributs << commentaire

        # Creer un attribut PRIX UNITAIRE
        prix_unitaire = {
          "aname" => "ra_prix_unitaire",
          "alabel" => "ra_Prix_unitaire",
          "aformulaunits" => "FLOAT",
          "aaccess" => "TEXTBOX",
          "aformlabel" => "Prix unitaire",
          "aoptions" => "&",
          "aformula" => "",
          "aunits" => "EUROS",
          "avalue" => "DC_DEF_PRICE"
        }

        dc_attributs << prix_unitaire

        # Creer un attribut QTT saisie manuellement
        qtt_saisie = {
          "aname" => "ra_qtt_saisie",
          "alabel" => "ra_QTT_Saisie",
          "aformulaunits" => "FLOAT",
          "aaccess" => "TEXTBOX",
          "aformlabel" => "Quantite saisie",
          "aoptions" => "&",
          "aformula" => "",
          "aunits" => "FLOAT",
          "avalue" => "1"
        }
        
        dc_attributs << qtt_saisie

        # Creer un attribut Type Quantite
        type = {
          "aname" => "ra_type",
          "alabel" => "ra_Type",
          "aformulaunits" => "STRING",
          "aaccess" => "LIST",
          "aformlabel" => "Type quantité",
          "aoptions" => "&U=U&H=H&FT=FT&Longueur%20X=LenX&Longueur%20Y=LenY&Longueur%20Z=LenZ&Surface%20XY=Surface%20XY&Surface%20XZ=Surface%20XZ&Surface%20YZ=Surface%20YZ&Surface%20volumique=Surface%20volumique&Volume%20XYZ=Volume%20XYZ&Volume%20m3=Volume%20m3&Volume%20dm3=Volume%20dm3&Volume%20cm3=Volume%20cm3&Volume%20litres=Volume%20litres&Volume%20dl=Volume%20dl&Volume%20cl=Volume%20cl&Volume%20ml=Volume%20ml&",
          "aformula" => "",
          "aunits" => "STRING",
          "avalue" => "U"
        }
        
        dc_attributs << type
        
        # Creer un attribut QTT Calculee
        qtt = {
          "aname" => "ra_qtt",
          "alabel" => "ra_QTT",
          "aformulaunits" => "FLOAT",
          "aaccess" => "VIEW",
          "aformlabel" => "QTT",
          "aoptions" => "&",
          "aformula" => "CHOOSE(OPTIONINDEX(\"ra_Type\"),ra_QTT_Saisie,ra_QTT_Saisie,ra_QTT_Saisie,ROUND(LenX/100,3),ROUND(LenY/100,3),ROUND(LenZ/100,3),ROUND(Volume()/61023.7440947/(LenZ/100),3),ROUND(Volume()/61023.7440947/(LenY/100),3),ROUND(Volume()/61023.7440947/(LenX/100),3),ROUND(VOLUME()/61023.7440947/(ra_Epaisseur/100),3),ROUND(LenX*LenY*LenZ/1000000,3),Round(VOLUME()/61023.7440947,3),Round(VOLUME()/61023.7440947*1000,3),Round(VOLUME()/61023.7440947*1000000,3),Round(VOLUME()/61023.7440947*1000,3),Round(VOLUME()/61023.7440947*10000,3),Round(VOLUME()/61023.7440947*100000,3),Round(VOLUME()/61023.7440947*1000000,3))",
          "aunits" => "FLOAT",
          "avalue" => "1"
        }
        
        dc_attributs << qtt

        # Creer un attribut Unite Calculee
        unite = {
          "aname" => "ra_unite",
          "alabel" => "ra_Unite",
          "aformulaunits" => "STRING",
          "aaccess" => "VIEW",
          "aformlabel" => "Unite",
          "aoptions" => "&",
          "aformula" => "CHOOSE(OPTIONINDEX(\"ra_Type\"),\"u\",\"h\",\"ft\",\"m\",\"m\",\"m\",\"m²\",\"m²\",\"m²\",\"m²\",\"m³\",\"m³\",\"dm³\",\"cm³\",\"l\",\"dl\",\"cl\",\"ml\")",
          "aunits" => "STRING",
          "avalue" => "u"
        }

        dc_attributs << unite

        # Creer un attribut SOUS TOTAL
        sous_total = {
          "aname" => "ra_sous_total",
          "alabel" => "ra_Sous_Total",
          "aformulaunits" => "FLOAT",
          "aaccess" => "VIEW",
          "aformlabel" => "Sous total",
          "aoptions" => "&",
          "aformula" => "ra_QTT*ra_Prix_Unitaire",
          "aunits" => "EUROS",
          "avalue" => "0"
        }

        dc_attributs << sous_total
        
        creer_attribut_dynamique(dc_attributs)    
      end ### attributs_rapport_cmd()

(Scroll to view all code.)
The command method calls the creer_attribut_dynamique() method with the appropriate parameters.

THEN, at the end of your extension code, in the RUN ONCE block …

   unless defined?(@loaded)

      cmd1 = UI::Command.new("Attributs rapport") {
        attributs_rapport_cmd()
      }

      # Definitions for command objects 2..9

      cmd10 = UI::Command.new("Mise à jour du nom de l'article") {
        update_article_cmd()
      }

      # Perhaps other commands ...

      @loaded = true
    end

Ok je comprend un peu mieux
une zone Run Once avec les appel de command UI qui appelle la sous commande correspondante
on a ainsi un bloc claire des commandes interfacables !

Du coup c’est les commandes UI que j’appel pour les menus et les boutons de la barre d’outils.
Et je comprend mieux la possibilité de déboguer sans avoir à redémarer sketchup à chaque fois avec l’encadrement des commandes UI par

unless defined?(@loaded)

et

@loaded = true
end

Merci
Je vais remanier le code et je reviens faire corriger ma copie
merci

The only simple mistake I found was a NoMethodError because + was called upon an i reference which was not defined within the each block.

On line 98 …

          #i += 1 ### !!! There is no "i" increment reference !!!

This file seems to work okay to use the passed dc_attributs array:

Simjoubert_RapportAttributs_main.rb (13.9 KB)


However, there is still one issue.

On line 29, in the creer_attribut_dynamique() method, you start an operation with the next_transparent parameter set TRUE.

Why ? This is deprecated. Code should only do this if absolutely necessary.

Yes because when you reload a Ruby file, these command code blocks have previously been passed to the OS and cannot be redefined. So if you have these code blocks call only 1 Ruby method, then this command control method can be redefined by the reloading.

Oui parce que lorsque vous rechargez un fichier Ruby, ces blocs de code de commande ont déjà été transmis à l’OS et ne peuvent pas être redéfinis. Donc, si vous avez ces blocs de code appeler seulement 1 méthode Ruby, alors cette méthode de contrôle de commande **peut ** être redéfini par le rechargement.