Attribute dictionary values do not match those in the Component Attributes window

I’m trying to iterate through a drawing’s components to record length attributes. It’s worked fine up to this point, but this particular drawing I have has outdated values when I try to read them from the API. You can easily see this using Aerilius’ Attribute Inspector. When entity type is changed from component definitions to drawing elements, meter look fine:

What’s going on here? Is the drawing file corrupted? Or should I be grabbing “drawing elements” in my plugin to begin with and how would I do that?

You may also need to look at the “dynamic_attributes” dictionary attached to the component definition.

That’s what I’m doing. It doesn’t match the actual value. I expect

entity.definition.attribute_dictionaries["dynamic_attributes"]["meter"]

to return 0.374233, as is shown in the Component Attributes windows, but instead I get 1.03471.

I figured it out. entity.attribute_dictionaries will ONLY contain dynamically updated attributes while definition.attribute_dictionaries will contain static attributes.

This is what I ended up doing to iterate through all dynamic attributes:

    Sketchup.active_model.entities.each_with_index do |entity|
		processEntity(entity)
    end
    def processEntity(entity)
		if entity.typename == "Group"
			entity.entities.each{ |e|
				processEntity(e)
			}
		elsif entity.typename == "ComponentInstance" && entity.definition
			if entity.definition.attribute_dictionary("dynamic_attributes")
				attributes = entity.definition.attribute_dictionary("dynamic_attributes")
			        **Do stuff with your static attributes**
			if(entity.attribute_dictionary("dynamic_attributes"))
				attributes = entity.attribute_dictionary("dynamic_attributes")
				**Do stuff with your dynamic attributes**
			end
			end
			entity.definition.entities.each{ |e|
				if e.typename == "Group"
					processEntity(e)
				end
			}
		end
	end

have a look at this link typename

john

Firstly, I would call the defintion’s attributes default, not static.

There are a number of issues with your example. Listed in order of importance:

CRITICAL:

  • Does not actually process the instance’s attributes, in preference to the component’s attributes.

  • Does not handle the oft situation where dynamic components have nested dynamic components.

  • Using slow String compare of the Sketchup::Entity#typename() method.

    • Instead, use: Object#is_a?(ClassIndentifier)

IMPORTANT:

  • Using recursive methods on very large models can cause out of memory errors or crash SketchUp.

  • Creating multiple String objects that have the same value.

    • Declaring literal strings, causes the Ruby interpreter to call String::new(literal_string),
      creating [unnecessary] separate String instance objects.
    • Instead, declare one referenced String instance object, and reuse the reference:
      dict = "dynamic_attributes" or even better a constant or a module variable.
  • Tab indentation characters paste into forum as 8 space indents.

    • Making hard to read code, … often causing unwanted horizontal scrolling.
    • Ruby uses 2 space indents.

NOTABLE:

  • Ruby does not need boolean expressions wrapped in parenthesis (like Javascript.)
    • Doing this is a violation of most published Ruby style guides.
    • When unneeded, it just clutters the code.
    • Ie, there should always be a space between the if or unless and it’s boolean expression.
    • But they can be used for clarity, or forcing particular evaluation order,
      when the boolean expressions are compound or complex.

TRIVIAL:

  • Use #each instead of #each_with_index if no access to the collection’s index is needed.

  • Unneeded checking if a component instance object has a definition. It always will.

Here is a similar example (that still uses a recursive method call):

# encoding: UTF-8

module Author
  module SomePlugin

    DICT = "dynamic_attributes"

    @@loaded = false unless defined?(@@loaded)

    class << self # this module's singleton proxy class instance

      def do_stuff()
        #
        # ** Do stuff with your dynamic attributes **
        #
        if @inst_atts # nil if empty
          puts "Component Instance has #{@inst_atts.size} dynamic attributes."
        else
          puts "Component Instance has no dynamic attributes.\n"<<
               "Using only the definition's atttributes."
        end
        #
        if get_value("LenX") > 30.3
          puts "Length is too long !"
        end
        #
      end ###

      def get_value(attr)
        # Check the instance's dictionary first:
        # It will be nil if empty, or the attribute will be nil if unset.
        if !@inst_atts.nil? && !@inst_atts[attr].nil?
          return @inst_atts[attr]
        else
          return @defn_atts[attr]
        end
      end ###

      def process_entity(entity) # <-----<<< ### ! Recursive Method ! ###
        if entity.is_a?(Sketchup::Group)
          entity.entities.each {|e|
            process_entity(e)
          }
        elsif entity.is_a?(Sketchup::ComponentInstance)
          if entity.definition.attribute_dictionary(DICT)
            # ... then it is a dynamic component, so:
            @defn_atts = entity.definition.attribute_dictionary(DICT)
            @inst_atts = entity.attribute_dictionary(DICT) # nil if empty
            # **Do stuff with your dynamic attributes**
            do_stuff()
            @defn_atts = @inst_atts = nil # release the references
          end
          entity.definition.entities.each {|e|
            if e.is_a?(Sketchup::Group) ||
            entity.is_a?(Sketchup::ComponentInstance)
              process_entity(e)
            end
          }
        end
      end ###

    end

    if not @@loaded

      cmd = UI::Command::new("Process Components") {
        Sketchup.active_model.entities.each do |entity|
          begin
            process_entity(entity)
          rescue Exception => e
            puts e.inspect
          end
        end
      }

      ## add cmd to top menu, toolbar or context menu here.

    end # run once block

  end
end
2 Likes

Very informative post. Thank you very much, this helps a lot as a .NET dev trying to string together a plugin!