Clone one of the Attribute Dictionaries from a component to another

Is there a quick way to copy from a component’s definition AttributeDictionaries a hole AttributeDictionary with all nested AttributeDictionares within it and all their pair of keys and values?

Maybe one idea to iterate over d0 dictionary tree ?

# d0 = entity.attribute_dictionaries['IFC 4']
(proc = Proc.new { |d|
  puts d.name;
  d.each { |k, v| puts "#{k} -> #{v}" };
  d.attribute_dictionaries.each(&proc) unless d.attribute_dictionaries.nil?
}).call(d0)

And replace “puts” by object creation on second entity ?

Thanks Boris,
Yes, iterate over each dictionary is what I’m trying to avoid. It would be ideal to save the dictionary inside a variable and attaching its value to the other component.

We cannot directly do this with AttributeDictionary objects because they can only be assigned using a factory method. Ie, the usual ::new constructor is undefined for this class.

So you must iterate or use the #to_h method mixed in from Enumerable to make a snapshot of the data.

The gateway to attributes in the Ruby API is through the Entity class, and it provides only limited support - probably to avoid creating severe errors in the underlying C code.

Every Entity in SketchUp already has an AttributeDictionaries collection, to which a reference can be obtained using the #attribute_dictionaries method. If no AttributeDictionary has yet been created for that Entity, the collection will be empty, but it exists. So far as I know, there is no direct way to clone or create an AttributeDictionaries collection, as it lacks the required Ruby allocator to create a new instance Object. For the same reason, there is no method to assign an AttributeDictionaries object to an Entity, as that would require a way to clone the original.

An AttributeDictionary has similar limitations: it also has no exposed Ruby allocator and can’t be directly created or cloned. It is created when you invoke #attribute_dictionary on an Entity with the create flag set to true, or when you invoke #set_attribute using a name of an AttributeDictionary that does not already exist on that Entity. And, there is no way to assign an entire AttributeDictionary to an Entity in one method.

So, bottom line: whereas you can fetch all of an Entity’s AttributeDictionaries in one call, you must iterate down through the whole thing to set the same attributes on a different Entity.

I don’t know as I can agree with this. If an Entity has no attribute dictionaries, then the getter method returns nil. This implies that the collection is not instantiated when it is not needed.


I do have a snippet for a refinement to merge dictionaries using recursion.
(Keep in mind I wrote this in 2019 and haven`t messed with it since.)

module AttributeDictionaryMerge

  refine Sketchup::AttributeDictionary do

    # A merging method patterned after Hash#merge!
    # NOTE: The Sketchup::AttributeDictionary class setters will
    # automatically convert symbol keys to strings.
    #
    # @param source [:each_pair] An object that responds to an :each_pair
    #   method call. (AttributeDictionary, Hash, OpenStruct or Struct)
    # @return [Sketchup::AttributeDictionary] - returns itself.
    def merge_from(source)
      #
      unless source.respond_to?(:each_pair)
        fail(TypeError,"argument must respond to :each_pair().",caller)
      end
      #
      # Convert OpenStruct or Struct to a Hash
      source = source.to_h unless source.respond_to?(:keys)
      #
      keys = self.keys
      source.each_pair { |key,val|
        next if key == :attribute_dictionaries
        if block_given? && keys.include?(key.to_s)
        # The dictionary has a matching key, so call block if given:
          self[key]= yield(key,self[key],val) # key, old_val, new_val
        else
          self[key]= val # copy new key/value to self
        end
      }
      #
      if source.is_a?(Sketchup::AttributeDictionary)
        dictionaries = source.attribute_dictionaries
        return self if dictionaries.nil?
        # a Sketchup::AttributeDictionaries collection object.
        dictionaries.each { |dict| # recursive method call
          self.attribute_dictionary(dict.name,true).merge_from(dict)
        }
      elsif keys.include?(:attribute_dictionaries)
        dictionaries = source[:attribute_dictionaries]
        # a hash (whose keys are dictionary names) of hashes.
        dictionaries.each { |name,dict| # recursive method call
          self.attribute_dictionary(name,true).merge_from(dict) 
        }
      end
      #
      return self
      #
    end

  end # Sketchup::AttributeDictionary class refinement

end # using module for refinement

This requires that you first create the target dictionary upon the “copy to” entity, then call the merge_from(source_dictionary) method upon it.

:nerd_face:

Refer to:

refinements - Documentation for Ruby 3.2

Module#using

It is also expected that you will wrap the AttributeDictionaryMerge module inside your namespace module or extension submodule.

You are right. I quick tested on several components before I wrote that, and they always had an existing AttributeDictionaries collection. On further inspection, they contain an AttributeDictionary named SU_InstanceSet, which seems to be for advanced attributes. I just tried again on a plain Face, and indeed the return is nil.

So, the back end must create one only when needed.