The API documentation warns:
“NOTE: calling add_group with entities in its parameters has been known to crash SketchUp before version 8.0. It is preferable to create an empty group and then add things to its Entities collection.”
But there’s more to the problem. The entity array you pass can contain ONLY edges, faces, text, groups, and/or component instances (EFTGC). If you array contains other entities, add_group should gracefully ignore them. But instead you usually get a bugsplat.
The problem arises if, for example, you want to convert a component instance to a group. So you need to first explode the instance and add the resulting array to a new group. But the array returned from componentInstance.explode returns EFTGC plus Edgeuse, Loop, Vertex, Curve and ArcCurve entities. Add_group doesn’t need them in the array but derives them from the AFTCG entities to put them in the new group.
A while back, TIG posted a script to do this but it left out some of the above entity tests. Here’s a revised version:
def self.comptogroup(selcomp)
mod = Sketchup.active_model # Open model
ent = mod.active_entities # All entities in model
sel = mod.selection # Current selection
#converts component instance to a group
tr = selcomp.transformation
dad = selcomp.parent
defn=selcomp.definition
lok=selcomp.locked?
hid=selcomp.hidden?
shc=selcomp.casts_shadows?
shr=selcomp.receives_shadows?
nam=selcomp.name
mat=selcomp.material
lay=selcomp.layer
nsm =selcomp.definition.behavior.no_scale_mask?
# attributes
ads=selcomp.definition.attribute_dictionaries
ent.transform_entities(tr.inverse, selcomp)
entarr = Array.new(0)
#puts "next is explode"
entarr = selcomp.explode
#puts "explode done " + entarr.length.to_s
# now remove entities that will confuse add_group.
grouparr = Array.new(0)
for item in entarr
if item.class.name == "Sketchup::EdgeUse"
elsif item.class.name == "Sketchup::Loop"
elsif item.class.name == "Sketchup::Vertex"
elsif item.class.name == "Sketchup::Curve"
elsif item.class.name == "Sketchup::ArcCurve"
else
#puts item.to_s
grouparr << item
end
end
#puts "number of entities for group = " + grouparr.length.to_s
group = ent.add_group
group = ent.add_group(grouparr)
#puts "group added"
ent.transform_entities(tr, group)
group.locked=lok
group.hidden=hid
group.casts_shadows=shc
group.receives_shadows=shr
# group not allowed to have face_me, gluing, cutting behaviors
# but you can limit scaling
group.definition.behavior.no_scale_mask = nsm
group.name=nam
group.material=mat
group.layer=lay
# attributes
ads.each{|ad|
na=ad.name
ad.each_pair{|k,v|
group.set_attribute(na, k, v)
}
} if ads
return group
end #def
#=begin # to test insert # before =
mod = Sketchup.active_model # Open model
ent = mod.active_entities # All entities in model
sel = mod.selection # Current selection
selcomp = sel[0]
group = self.comptogroup(selcomp)
sel.clear
sel.add(group)
#=end # to test insert # before =
The method above ignores those attached to the component instance, and copies only the first level attached to the component definition, to the new group instance. (Groups are also components that just have their definition hidden in the component browser.)
There are some components (Trimble extensions) that use multi-level dictionaries.
I like the last idea although my understanding is that geometry interaction won’t occur until at a commit (which in my case would occur in the method that calls this one).
Your other style comments apply to the original code supplied by TIG. I just added Curve and ArcCurve to the filter.
Since I’m dealing only with a single small instance each time a user clicks on the command, (maybe a dozen entities), speed is not important so I left it that way.
To reiterate…
If what is to be added into the new group is NOT already in the model.active_entities, then use group = some_entities.add_group()
then use add entities to group.entities afterwards…
If the entity or entities are in the active_entities context then you can use: group = some_active_entities.add_group(some_active_entity)
or group = some_active_entities.add_group(model.selection.to_a)
or group = some_active_entities.add_group(an_array_of_some_active_entities)
etc
Obviously, you can’t add an entity from one context directly into another [a BugSlat awaits!] - however, if the entity is a group or component_instance, then you can get the definition of that and use add_instance(), copying the original’s transformation, making allowances for the context’s transformation etc.
Similarly you could ‘clone’ faces and edges etc - but not easily…
If you merely want to copy it, then there will be two instances [for a group you might want to consider using make_unique]
If you want to move it from one context to another, then after the add_instance() you will need to use .erase! on the original item…
is_a? will match if its argument is item’s class or any superclass. If you are interested in whether item can be used the same way as an instance of a particular superclass, that is the test to use.
The equality test will match only if Sketchup::EdgeUse is the exact class of item.
Unless your code has created a subclass of Sketchup::EdgeUse the difference is unimportant. But creating subclasses of SketchUp Ruby API classes is risky because they tie to behind-the-scenes C structures and not all of them were implemented with a mind to allowing Ruby subclasses.
I would not explode an existing component instance, use the returned entities array, and create a group, to convert it to a group. Doing so risks having the geometry merged with existing geometry in the same drawing contexts, applies layer and materials from the instance if not reset to nil, and loses the axes placement. Instead you can create a new empty group, add a component instance with the unity transformation to it, and explode this component instance.
Yes there is. Method calls eat time. The first (.is_a?) is one method call. The second … item.class.==(Sketchup::EdgeUse) … is 2 method calls.
(The Ruby interpreter allows us mere humans to omit the dot notation for the == method and the parenthesis around it’s argument list. It is known as having “keyword status” because it is basically a global method, being defined within BasicObject, Object or Kernel.)
And this holds true as long as classes do not override the == method inherited by BasicObject.
Equality — At the Object level, == returns true only if obj and other are the same object. Typically, this method is overridden in descendant classes to provide class-specific meaning.
A check on Sketchup::EdgeUse and it’s superclass Sketchup::Entity reveals that this method has not been overridden.
This is not the case throughout the API. Many Geom module classes, and the Length class override the == method for class specific purposes.