Convert component to group

Is there an efficient way to convert a component instance to a group, keeping transformation and axes, that doesn’t involve exploding the entities to the parent context?
I need this on heavy geometry, and exploding takes ages.

I can do it efficiently in the UI by grouping everything in the component then explode it (only the group moves context, so it’s fast), but how can I replicate that through the API?

3 Likes

Why do you need a Group? If you can live with a Component, the #make_unique method on the ComponentInstance will do it (it creates a clone of the ComponentDefinition and attaches the ComponentInstance to it).

I really need a group, not a component :slight_smile:

Alas, it seems like the API only provides the other direction: to make a component out of a group :disappointed: So one way or another you’d have to cobble it together from smaller steps.

I don’t mind a dirty workaround/hack. It won’t be a released extension, it’s just a script for me. My only concern is for it to be fast.

This topic is covered in “Entities.add_group causes Bugsplat!

It shows an script originally posted by TIG, but modified to avoid a bugsplat when the component included an arc or curve.
Dan R advised that its better to add the component instance to a new group, then explode the instance thats inside the group.

If the instance has just been created, then all of the logic to preserve attributes, layers, shadow characteristic, material, scale masks etc, may not be relevant.
But you still might want to purge the compoennt definition from appearing the in “In Model” component panel.

Since the only difference between a component instance and a group is a hidden flag in accessible to the API, you do need to go through all of this to accomplish the task unfortunately.
Also it would be nice if Model.import which results in a component had an option to import as a group. This would again avoid the need for conversion.

my_instance = my_group.to_component
my_definition = my_instance.definition

???

Was also going to post this link. (Barry beat me to it.)
Pay special attention to the last post by TIG, which explain a non-explode way.
(But you’ll still need to do the property and attribute copying part. Then erase the cinstance, a lastly the definition purge.)

Here’s another related topic:
Move all entities to new group

This is butt-backward to what he wants.

And at one time this method (to_component) was bugged in that it dropped all the attribute dictionaries (perhaps other properties.) Was it ever fixed ?

1 Like

@Dan is right :blush:
I must read the problem more carefully…

However, as @Dan has linked I also produced the fix for component > group situation…

Thanks guys.
I did see those threads, but I can’t get to the result I’m after.

Here’s my snippet so far :

def self.remove_subcomps(definition)
	definition.entities.each{|e|
		if e.is_a?(Sketchup::ComponentInstance)
			self.comp_to_group(e, definition)
		elsif e.is_a?(Sketchup::Group)
			self.remove_subcomps(e.definition)
		end#if
	}
end#def

def self.comp_to_group(inst, parent_group_def)
	entities = inst.definition.entities
	group = parent_group_def.entities.add_group(entities.to_a)
	inst.erase!
end #def

Sketchup.active_model.start_operation("Remove subcomponents", true)
self.remove_subcomps(Sketchup.active_model.selection[0].definition)
Sketchup.active_model.commit_operation

I attached a test model with the groups/comps hierarchy I’m working with. Check the outliner.
comp_to_group.skp (126.4 KB)

I want to select the top-level group (“group_lvl1”) and convert all subcomponents to groups.
When I do select the top-level group and run the above snippet, new groups are created in the root context, not in the context of the subcomponents. (I don’t mind about the transformation at this point, I’ll resolve that later)
That’s why I came here.

Any idea what I’m doing wrong?

PS : Also, I get a bugsplat when I undo this operation.

Well the first issue is that you are violating the cardinal rule of collections. You cannot modify the collection at the same time you are iterating it.

Solution, create an array copy, and iterate the copy, so that the iteration reference will not loose it’s way, ie:

definition.entities.to_a.each{|e|

(2) The way youv’e written this:

… is actaully the same as this (ie, the 2nd parent_group_def argument is not needed):

def self.comp_to_group(inst, parent_group_def)
	entities = inst.definition.entities
	group    = entities.add_group(entities.to_a)
	inst.erase!
end #def

So, look at the "group = " statement:

group = entities.add_group(entities.to_a)

… in which your trying to add the group into the collection that you want to encapsulate IN this group.

Thanks Dan, although that doesn’t seem to fix the issue.

No, “inst” is nested in “parent_group_def”.
What you say would be true if I called
self.comp_to_group(e, e.definition)
But I’m calling
self.comp_to_group(e, definition)
“e” is part of “definition.entities”. It’s not an instance of “definition”.

Ok I see, but as TIG had explained (I thought) the group and the entities need to be in the same context, and they are not. The group and the parent of the entities are in the same context. So then, BugSplat would not be uncommon.

Oh? I must have skipped that.

Then this should work? But it doesn’t, it still creates groups in the root context.

def self.remove_subcomps(definition)
	definition.entities.to_a.each{|e|
		if e.is_a?(Sketchup::ComponentInstance)
			self.comp_to_group(e)
		elsif e.is_a?(Sketchup::Group)
			self.remove_subcomps(e.definition)
		end#if
	}
end#def

def self.comp_to_group(inst)
	entities = inst.definition.entities
	entities_ary = entities.to_a
	entities.add_group(entities_ary)
	group = inst.explode[0]
end #def

Sketchup.active_model.start_operation("Remove subcomponents", true)
	self.remove_subcomps(Sketchup.active_model.selection[0].definition)
Sketchup.active_model.commit_operation

NO !
You are missing a vital point.

group = entities.add_group()

safely makes an empty group in ANY entities context.
You can then add objects into that group.entities context.

entities.add_group(entities_ary)

will only work if the entities reference is pointing to the model.active_entities.
It cannot point at some other entities context - even if the array of entities is in the same entities context as your new group…
You will get BugSplats.


How about this less risky approach ?
entities = inst.parent.entities
group = entities.add_group()
temp = group.entities.add_instance(inst.definition, inst.transformation)
group.layer = inst.layer
group.material = inst.material
group.locked = inst.locked?
group.hidden = inst.hidden?
### perhaps copy over attribute dictionaries too ?
inst.erase!
temp.explode
2 Likes

I see, thanks.

Which involves exploding the component. This is what I wanted to avoid.

Thanks for the help guys, I guess I’ll stick with exploding.

Yes it would be so much simpler if the API had a to_group() instance method of the ComponentDefinition class, that would convert it to a group definition without all this rigmarole and exploding. All the definition’s instances then would become group instances, and there’d be no need to copy attribute dictionaries and native properties.

1 Like

I’m not sure I understand. I do what I think you want all the time. When something is already a group or a component, select it and explode it. Without doing anything else first, now take the highlighted entities, right click and make them into the other, from a component to a group or vice versa. I rarely use components, only when I decide to build something and keep it for further use, like a table or door. If I just need things to stay separate, I use groups almost exclusively. Occasionally I regret having a group if I discover changes I hadn’t thought of, and now must change each group separately. Balusters for example would be drawn once as a group until all details are complete, then copy as many as you need. With components, you’d distribute the copies first, and finish the details later, but I prefer doing it the first way.