Modifying geometry in freshly copied Groups (not unique)
-
I spent some time exploring the implications of non-unique groups from the Ruby perspective and found that it may be the source of numerous nasty crashes and issues in scripts.
1) Background
When you create a copy of a Group in a model, whether by Copy / Paste or by the Move tool in copy mode, the new created group does not duplicate its geometry. Instead, the new and old groups share the same Definition, a little bit like Components. You can check in the Entity Info panel that the Groups are indicated with “Group (2 in model)”.
In the Sketchup viewport, you will however make a group Unique whenever you double-click on it and edit it.
In Ruby, the situation is different when you access directly the entities of the group. Many scripts just work on geometry at the top level, but there are a few scripts that have to change the geometry within groups. And for these scripts, the situation of duplicated groups may be quite frequent, because, for safety reasons, the user would naturally make a copy of the geometry s/he works on before launching the script modifying this geometry.
2) The Problem for Ruby scripts
From a Ruby standpoint, duplicated groups:
- Have 2 different entities objects:
g1.entities != g2.entities
- However, their list of entities (edges, faces, …) are strictly identical:
g1.entities.to_a == g2.entities.to_a
- which is because they share a common Definition:
g1.entities.parent == g2.entities.parent
As a result, if you modify an element in one of the group by methods such as
transform_by_vector, transform_entities, erase_entities, erase!, material =, …
you may likely get a wrong behavior:- Either the change may apply to the other group (for instance when you transform). These methods seem however to make the group unique
- Or it will apply to both groups (for instance with
erase!, material=
). In addition this does not make the group unique.
The real problem occurs when groups become de-duplicated (i.e. unique), because each group has now its own set of entities. So the references to edges, vertices and faces you previously had will now point to the entities of one of the group. And by chance, it may not be the group you are working on.
This situation is likely to provoke SU crashes and ‘reference to deleted entity’ errors, especially if you do these modifications repeatedly. Not mentioning that when you perform Undo (or
model.abort_operation
) you can get very nasty bugsplats.3) How to detect whether a group <g> is unique or not
The test is
(g.entities.parent.count_instances == 1)
4) How to make a group unique by Ruby
I noticed a few things:
- The method
make_unique
for groups does the job, but display a warning message about its obsolescence - Changing the name of one of the group (new name or same name) does NOT work. If you do this, you will surely see in the Entity Info box that each group is a single instance, BUT under the hood, you still have the commonality of entities (
g1.entities.to_a == g2.entities.to_a
) - Creating a group and deleting it seems to make the groups unique, so a one-liner such as
g1.entities.add_group.erase!
would work. The benefit of this method is that you don’t get any warning message in the Ruby console.
5) How to solve the problem of reference to entities
Once a group is made unique, the references to edges, faces vertices and other elements that you previously computed when exploring the group entities, are NO LONGER valid. Actually, they may be valid SU objects, but belong to another group.
One way to circumvent this problem is to build a mapping of entities for the group before it is made unique in order to retrieve them after. The method is based on two properties that seem to be respected:
- when a group is made unique, the list of entities keeps the same order before and after.
- object_id method is valid for old edges or faces before and after the ‘make unique’ operation
This means that for instance
g.entities[i]
refers to the same edge or face before and after the ‘make unique’ operation, even if the reference it contains is different.A suggested code is therefore as follows:
Let’s say that you explored the group and found a list of edges and faces that you want to operate onto: < selected_edges_and_faces >
First you save the index of each entity in the group
hmapid = {} g.entities.each_with_index { |e, i| hmapid[e.object_id] = i }
Then you make the group unique. At this stage, the list <selected_edges_and_faces> does not contain valid references anymore.
Then to recompute the corresponding valid references , you need to use the mapping:
gent = g.entities selected_edges_and_faces = selected_edges_and_faces.collect { |e| gent[hmapid[e.object_id] }
Note that if you had references to vertices, you need to recomput them from the new edges or faces references.
6) Conclusions
-
The optimization brought by keeping the same definition for freshly copied groups is a real pain for Ruby scripts. Of course it is faster for the user in the viewport and it saves space.
-
The
make_unique
method is not at all to be deprecated, whatever the SU development team thinks. The workaround g.entities.add_group.erase! is not necessarily a good, performing solution. -
It is advised to perform the ‘make unique’ operation on groups as a true Operation, that is, within a start_operation and a commit_operation. This avoids that undo or abort_operation generate crashes, as apparently the undoing is not well managed in those particular situations. Of course, this makes the flow of operations more complex.
-
Personally, I would expect the Sketchup development team to find a built-in solution, either by disabling this optimization feature of duplicated groups or by making sure that any modification on entities are applicable to the referred groups (which is then made unique).
Any comment and feedback welcome.
Fredo
- Have 2 different entities objects:
-
Thank you for this. Very useful information indeed. Will use these methods for sure.
@4) So renaming the group doesent make it uniq, huh? To bad.
-
@jolran said:
@4) So renaming the group doesent make it uniq, huh? To bad.
Because it works just like component instances - you can name each instance differently.
-
@unknownuser said:
Because it works just like component instances - you can name each instance differently
Yeah. So there exist some consistency in the API after all ?
Seriously though, my vote for this post is to be made sticky or something similar.
Otherwise there is a risk it gets lost on page 4 or so 2 weeks. -
@jolran said:
Yeah. So there exist some consistency in the API after all ?
Definitions and Instances in SketchUp
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 …
Procrastinators Revolt! (www.thomthom.net)
@jolran said:
Seriously though, my vote for this post is to be made sticky or something similar.
Otherwise there is a risk it gets lost on page 4 or so 2 weeks.Could add it to the existing sticky with list of link ?
-
@jolran said:
@unknownuser said:
Seriously though, my vote for this post is to be made sticky or something similar.
You can have your OWN set of bookmarked topics.
The "Bookmark topic" link is at the bottom of each topic page.
You access your bookmarks thru the User Control Panel.
-
@unknownuser said:
You can have your OWN set of bookmarked topics.
Dan. I've must admit never ever fiddled with preferences. Thats a good tip.
However I was thinking more as a benefit to others, keeping this info visible.Thomthom I've read your post but was thinking more about that old topic I started:
(which I find higly relative to this topic)http://sketchucation.com/forums/viewtopic.php?f=180&t=51263
Now Fredo seams to have a better solution and that makes me very happy.
-
@jolran said:
However I was thinking more as a benefit to others, keeping this info visible.
I added this topic to my manual list of Snippets.
See Groups: [Code Snippets] by Subject
Advertisement