The SketchUp Ruby API documentation says the following about the DefinitionList class, exposed via model.definitions
:
A DefinitionList object holds a list of all of the ComponentDefinition objects in a model. This class contains methods for adding and retrieving definitions from the list.
When you then look up the ComponentDefinition class it explains:
The ComponentDefinition class is used to define the contents for a Sketchup component. Components are a collection of entities that can be instanced and reused multiple times throughout a model. For example, you could draw a chair once, turn it into a component, and then use 6 instances of it to surround a table. Edits to the original “definition” will then propogate across all of its instances.
The ComponentDefinition class contains the global entities and settings for each definition. See the ComponentInstance class for how each copy is defined.
This explanation is not telling the whole story and misleads the reader to think that model.definitions
returns a collection of definitions which only relates to ComponentInstance
.
Many Types of Instances
The fact is that
Group
and Image
entities are instances. They all derive from a ComponentDefinition
and when you copy then around your model you’re making new instances of the same definition. This is why ComponentDefinition
has two methods group?
and image?
. These methods are the only clues the API documentation gives.
So if you want to iterate the content of all components in a model – “component” in terms of a user, which is the elements you see in the Component Browser – the correct method is:
1 2 3 4 5 | for definition in model.definitions next if definition.image? next if definition.group? self.cast_magic_spell( definition ) # ...or something... end |
for definition in model.definitions next if definition.image? next if definition.group? self.cast_magic_spell( definition ) # ...or something... end
Without these checks you might inadvertently modify an Image
entity, say if you wanted to remove all back-side materials in the model. The definition of an Image
looks like any other geometry, it has a textured face bound by four edges. I made use of that when I wrote Image Opacity which allows the user to adjust the transparency of the selected Image
entity – a feature SketchUp doesn’t expose.
Subtle Differences
The are few differences between Group
and ComponentInstance
. When the user makes a copy of a Group
SketchUp internally makes a new instance of the Group
‘s definition. It is only when you modify the one of the groups that SketchUp makes it unique and creates a new definition.
On the API side there are some other subtle differences. You can access a group’s entities by group.entities
while for ComponentInstances
you must access the definition: instance.definition.entities
. Despite this, when you edit the entities of one Group
instance it affects all copies – SketchUp doesn’t make it unique like when you modify it via the UI.
A caveat with Image
objects is that they do not have a .transformation
method like Group
and ComponentInstance
. You get info about it via other methods, but there a a flaw in the API design which makes it impossible to determine if an Image
is mirrored.
Finding the ComponentDefinition
The Group
and Image
class doesn’t have a #definition
method, but you can get a group’s definition by group.entities.parent
. Note that a bug sometimes cause an incorrect definition to be returned. The definition of Image
elements can only be obtained by searching the model.definitions
list.
Here is a method I use to obtain the definition of any instance, whether it is a ComponentInstance
, Group
or Image
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | # Returns the definition for a +Group+, +ComponentInstance+ or +Image+ # # @param [Sketchup::ComponentInstance, Sketchup::Group, Sketchup::Image] instance # # @return [Sketchup::ComponentDefinition] def self.definition(instance) if instance.is_a?(Sketchup::ComponentInstance) return instance.definition elsif instance.is_a?(Sketchup::Group) # (i) group.entities.parent should return the definition of a group. # But because of a SketchUp bug we must verify that group.entities.parent # returns the correct definition. If the returned definition doesn't # include our group instance then we must search through all the # definitions to locate it. if instance.entities.parent.instances.include?(instance) return instance.entities.parent else Sketchup.active_model.definitions.each { |definition| return definition if definition.instances.include?(instance) } end elsif instance.is_a?(Sketchup::Image) Sketchup.active_model.definitions.each { |definition| if definition.image? && definition.instances.include?(instance) return definition end } end return nil end |
# Returns the definition for a +Group+, +ComponentInstance+ or +Image+ # # @param [Sketchup::ComponentInstance, Sketchup::Group, Sketchup::Image] instance # # @return [Sketchup::ComponentDefinition] def self.definition(instance) if instance.is_a?(Sketchup::ComponentInstance) return instance.definition elsif instance.is_a?(Sketchup::Group) # (i) group.entities.parent should return the definition of a group. # But because of a SketchUp bug we must verify that group.entities.parent # returns the correct definition. If the returned definition doesn't # include our group instance then we must search through all the # definitions to locate it. if instance.entities.parent.instances.include?(instance) return instance.entities.parent else Sketchup.active_model.definitions.each { |definition| return definition if definition.instances.include?(instance) } end elsif instance.is_a?(Sketchup::Image) Sketchup.active_model.definitions.each { |definition| if definition.image? && definition.instances.include?(instance) return definition end } end return nil end
“Internal” Components
ComponentDefinition
has a method #internal?
where the documentation cryptically explains:
The
internal? method is used to determine if the component definition is internal to the Component Browser
What this means is that the component will not be visible in the Component Browser unless the user “Expand” the list. Unfortunately there is no way to set this flag. It appear to be set to all sub-component of imported models.
The Name Might not be Unique!
A bug in SketchUp can cause a model to have multiple ComponentDefinition
objects with the same #name
. I came across it for the first time when I found that a model would render the wrong component in place of another. At turned out that it relied on the name to be unique which then cause problems when given a bugged model. An alternative, depending on the application, can be ComponentDefinition.guid
– which can be used directly to access a definition in the same way as #name
. But note that #guid
might change during a session.
[…] Given a model that contains two materials and an Image element you would think that model.materials.length returns 2. But instead it returns 3 – this reveals that Image entities contribute to the internal material list in a model. Not so surprising when you discover that Image entities are instances of ComponentDefinition. […]
[…] Remember not all definitions are components […]