Entities.add_group causes Bugsplat!


#1

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 =

To_group Method?
Convert component to group
#2
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

… is positively ugly (and slow string comparison.)

Class comparison is fast!

grouparr = entarr.reject {|item|
  item.is_a?(Sketchup::EdgeUse) ||
  item.is_a?(Sketchup::Loop)    ||
  item.is_a?(Sketchup::Vertex)  ||
  item.is_a?(Sketchup::Curve)   ||
  item.is_a?(Sketchup::ArcCurve)
} 

OR

bad = [
  Sketchup::EdgeUse,
  Sketchup::Loop,
  Sketchup::Vertex,
  Sketchup::Curve,
  Sketchup::ArcCurve
]

grouparr = entarr.reject {|item| bad.include?(item.class) }

#3

Re. attribute dictionaries.

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.


#4
group = ent.add_group
group = ent.add_group(grouparr)

… the first line seems frivolous ?

Okay,… now what happens if you first move the component instance into the new group, then explode it afterward ?

Exploding it in the model first may cause it’s geometry to interact with unprotected geomtery.


#5

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.


#6

I agree SketchUp shouldn’t BugSplat, but all you can do is file a bug report and work around the problem.

You could first create a Group from the Instance:

grp = entities.add_group(instance)

Then set all the Group’s attributes/properties, then explode the Instance afterwards.

Alternatively if you want to keep your existing code, it is better to explicitly include what you want instead of excluding what you do not:

entarr = entarr.grep(Sketchup::Drawingelement)


#7

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 anothe, then after the add_instance() you will need to use .erase! on the original item…