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:
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!