Dynamic Components conflicting with modified versions when imported - code workaround

I had a problem with importing Dynamic Components and looks like quite a few others have hit the same problem. @dougward articulated it really clearly here:

I was drawing kitchen cabinets and was trying to re-use quite a few of the subcomponents (e.g. panel door) in the dynamic components I built. So I have one DC for wall cabinets, another for regular base cabinets, and another for corner base cabinets. All worked fine on their own in their own files. But then when trying to use them together in a model, they go haywire. It appears the cause is that the shared subcomponents have a unique identifier that causes a clash deep in the bowels of Sketchup. It looks fine on the surface because Sketchup automatically makes the names unique, but the formulae get messed up.

I wrote some (ugly) code which is working for me. Feel free to use it but make sure you save a backup or two before using it because I’m no software engineer and the code is doubtless bug ridden. The method it uses is to make copies of each component, then make them unique, then relink them back to each other as they were before. I couldn’t find a good way of making copies in Ruby (tried marshal but it didn’t work) so ended up with lots of ugly copying-across of attributedictionaries and layer and material references.

#---------------------------------------------------------------------------------------------
#Method to reinitiate component
def reinitiate_component(original_defn, entity_list_str)
  mod = Sketchup.active_model # Open model
  defs = mod.definitions #All component definition objects
  ents = mod.entities # All entities in model
  original_instances = []
  new_instances = []
  inst_names = []
  dict_names = []
  dict_keys_array = []
  dict_values_array = []
  
  #populate the original list of instances
  inst_count = 0
  original_defn.instances.each{ |inst|
    original_instances << inst
    inst_count = inst_count + 1
  }

  if !original_instances[0].get_attribute('dynamic_attributes', 'copies').nil?
    #if the component has DC formula driven instances, then use the DC copies attribute 
		#to cut back to zero, copy, the original instance, and then reinstate the copies
		#(that way they stay linked to each other properly):
    for i in 0...inst_count
      inst = original_instances[i]
      if inst.get_attribute('dynamic_attributes', 'copy').nil?
        break
      end
    end

    original_def = inst.definition
    original_def_name = original_def.name
    original_copies = inst.get_attribute('dynamic_attributes', 'copies')
    original_copies_formula = inst.get_attribute('dynamic_attributes', '_copies_formula')

    #Make a copy of the dictionaries
    dict_names.clear
    dict_keys_array.clear
    dict_values_array.clear
    inst.attribute_dictionaries.each{ |dict|
      dict_names << dict.name
      dict_keys = []
      dict_values = []
      dict.each{ |key, value|
        dict_keys << key
        dict_values << value
      }
      dict_keys_array << dict_keys
      dict_values_array << dict_values
    }

    #Take a reference of the material and tags (layers)
    material_buffer = nil
    layer_buffer = nil
    if !inst.material.nil?
      material_buffer = mod.materials[inst.material.name]
    end
    if !inst.layer.nil?
      layer_buffer = mod.layers[inst.layer.name]
    end

    #cut back to zero copies
    inst.set_attribute 'dynamic_attributes', '_copies_formula', '=0'
    inst.set_attribute 'dynamic_attributes', 'copies', 0
 
    #copy the original definition, make unique, delete the old one
    new_ent = inst.parent.entities.add_instance(original_def, inst.transformation)
    new_ent.make_unique
    defs.remove(original_def)
    defs.purge_unused
   
    #Refill the material and tags (layers)
    if !material_buffer.nil?
      new_ent.material = material_buffer
    end
    if !layer_buffer.nil?
      new_ent.layer = layer_buffer
    end
    
    #Refill the dictionaries into the new component
    for j in 0...dict_names.count
      for k in 0...dict_keys_array[j].count
        new_ent.set_attribute dict_names[j], dict_keys_array[j][k], dict_values_array[j][k]
      end
    end
      
    #Reinstate the original name and copies formula and value
    new_ent.definition.name = original_def_name
    new_ent.set_attribute 'dynamic_attributes', '_copies_formula', original_copies_formula
    new_ent.set_attribute 'dynamic_attributes', 'copies', original_copies
 
    #Update the entity list string
    if new_ent.definition.entities.nil?
     entity_list_str = entity_list_str + " | " + new_ent.definition.name + 
		", " + new_ent.name + ", " + new_ent.entityID.to_s + " |"
    else
      new_ent.definition.entities.each{|ent|
        entity_list_str = entity_list_str + " | " + ent.definition.name + 
			", " + ent.name + ", " + ent.entityID.to_s + " |"
      }
    end
    

  
  #=====================================================================================================================================
  else
    #if the component has non-dynamic instances, then copy each individually 
		#and store into a new array, saving a reference to the original definition
    inst_names.clear
    for i in 0...inst_count
      inst = original_instances[i]
      inst_names << inst.name
      if i==0
        master_def_name = inst.definition.name
        original_def = defs[master_def_name]
      end
      
      #Make a copy of the dictionaries
      dict_names.clear
      dict_keys_array.clear
      dict_values_array.clear
      inst.attribute_dictionaries.each{ |dict|
        dict_names << dict.name
        dict_keys = []
        dict_values = []
        dict.each{|key, value|
          dict_keys << key.to_s
          dict_values << value.to_s
        }
        dict_keys_array << dict_keys
        dict_values_array << dict_values
      }
     
      #Take a reference of the material and tags (layers)
      material_buffer = nil
      layer_buffer = nil
      if !inst.material.nil?
        material_buffer = mod.materials[inst.material.name]
      end
      if !inst.layer.nil?
        layer_buffer = mod.layers[inst.layer.name]
      end
      
      #Copy the component instance
      new_ent = inst.parent.entities.add_instance(inst.definition, inst.transformation)
      new_instances << new_ent
      new_ent.make_unique

      #Refill the dictionaries into the new component
      for j in 0...dict_names.count
        for k in 0...dict_keys_array[j].count
          new_ent.set_attribute dict_names[j], dict_keys_array[j][k], dict_values_array[j][k]
        end
      end
      
      #Refill the material and tags (layers)
      if !material_buffer.nil?
        new_ent.material = material_buffer
      end
      if !layer_buffer.nil?
        new_ent.layer = layer_buffer
      end
      
      #Keep a record of the master instance's definition
      if i==0
        masterdef = defs[new_ent.definition.name]
      end
    end #for

    #Relink the new copies
    for i in 1...inst_count
      inst = new_instances[i]
      inst.definition = masterdef
    end

    #Delete the old definition and purge it
    defs.remove(original_def)
    defs.purge_unused

    #Put the name back to what it was
    masterdef.name = master_def_name
    
    #Replace names and append the entity list string
    for i in 0...inst_count
      new_instances[i].name = inst_names[i]
      entity_list_str = entity_list_str + " | " + new_instances[i].definition.name + 
		", " + new_instances[i].name + ", " + new_instances[i].entityID.to_s + " |"
    end

  end #if
entity_list_str
end
#end of copy definition method-----------------------------------------------------
#---------------------------------------------------------------------------------------------



#---------------------------------------------------------------------------------------------
#Program launch
mod = Sketchup.active_model # Open model
ents = mod.entities # All entities in model
sel = mod.selection # Current selection
defs = mod.definitions #All component definition objects
entity_list_str = "."
def_list = []

#Purge unused definitions
defs.purge_unused

defs.each{|defn|
  def_list << defn
}

def_list.each{ |defn|
  entity_list_str = reinitiate_component(defn, entity_list_str)
}

Hmm, that didn’t paste well. If anyone wants to use the code and can advise on how to post it so it’s readable please let me know!

another failed attempt to copy. can’t delete so editing

place three ` before and after the code

1 Like

Perfect! Worked a treat. Thanks.

Please read and bookmark this …


Tim your example violates Ruby language conventions and good practices.

def ReinitiateComponent (originaldefn, entityliststr)

a. Do not put a space between the method name and it’s argument list.
This causes warnings to be spit out to STDOUT.

b. Ruby uses CamelCase (aka SnakeCase) constant identifiers for class and module idnetifiers.
Method names and variables (note that parameter names are actually local variables to the method) should be all lower case with words separated using underscore characters.
SHOUTED_NAMES are used for constants.

c. all operators should have space before and after.

(Note if you wish to break Ruby convention in your own code that only you need to read, then this is fine but you should not post such code for others … especially new coders.)

Find (or build) a concise (and workable) coding style guide. See: https://ruby.style/
(Some of these guides are way too subjective and have too many rules that do not explain reasonings.)

There are numerous good free books available for Ruby. See the lists I created:

Thanks for the tips. I’ve edited the code to fix as much as I could see of the issues you flagged (and also bookmarked the links!).
This is my first time using Ruby and I’m not an experienced programmer so I still would expect the code is far from perfect. But might be useful if anyone’s having the same issue as me with components clashing with each other.

1 Like

Thank you. Yes you have more to learn (but isn’t it fun?)

Two more major things. Any and all code you write needs to be wrapped within your unique namespace module at the top level. Inside this you would separate each of your extensions within a submodule.

There is never any reason for extension code to evaluate outside your namespace.

In addition to this your method is way toooo loooonnnnngggg. :wink: Long methods like this are hard to understand. It is better to break the code up into logical pieces that do a task, and then have a command method that calls these little task methods.

1 Like