I am writing my first extension (Ruby based). Extension is run with the current selection which I want to copy and transform multiple times (determined by user). After extension runs, with desired results, I open ‘Model Info’ and run ‘Fix Errors’ and always get a ‘Persistent ID’ errors,
as per upload. For this case I had one component and one group selected. I always end up with one error per copy of the entities being copied.
What is the best way to copy the selection set and copy it?
Post a snippet of your copying method and a test model that shows the errors.
1 Like
RBZ of ‘SpiralTest’ attached. Install into Sketchup, select items and select ‘Spiral Test’ on the Extensions menu, then click the LeftMouseButton. This extension will generate 4 copies, rotated by 10 degrees about origin (so choose something close to origin), and each will be advanced along ‘z’ by 10" from previous. Then open ‘Model Info’ and ‘Fix Problems’.
spiral_test.rbz (3.3 KB)
The attached extension is a small snippet (interface removed) that still demos the problem. The ‘spiral_snippet’ method contains the meat of the copy & rotate.
YES I see the same issue using your convoluted code.
One problem is that you keep creating new groups, then explode them almost immediately. SketchUp will purge a group definition if it has no instances.
Examine this snippet …
def spiral_snippet
num_copies = 4
@model.start_operation('Create Spiral', true)
ents = @model.entities # All entities in model <--- DON'T USE !
sel = @model.selection
rot_origin = Geom::Point3d.new(0,0,0)
rot_axis_vector = Geom::Vector3d.new(0,0,1)
shift = Geom::Vector3d.new(0,0,10)
# Define rotation by axis and angle
tr_rot = Geom::Transformation.rotation(rot_origin, rot_axis_vector, 10.degrees)
# Define translation
tr_mov = Geom::Transformation.translation(shift)
#
###
#
actent = @model.active_entities
grporg = actent.add_group(sel.to_a)
grpdef = grporg.definition
#
transform = tr_rot * tr_mov
t = grporg.transformation
for i in 1..num_copies
t *= transform
actent.add_instance(grpdef, t)
end
grpdef.instances.reverse.each(&:explode)
#
###
#
@model.commit_operation
end
EDITED the above example to initially set the reference t
to the initial groups’s transformation.
(Was previously just starting from an identity transform.)
1
. Always use @model.active_entities
when using a user’s selection set.
2
. Notice how first each new transformed group instance is added, and only afterward do we explode the groups one by one, in reverse order, allowing SketchUp to uniquify the exploded objects. It does this automatically because the originals are still within the group’s definition’s entities collection. Only when the last (which is the first and original) group instance is exploded does SketchUp keep the PIDs of the selected items and then purges the unused group definition because no more instances exist.
3
. So checking the model after using the above there are no more issues with PIDs.
4
. Learn to use the *
method of Geom::Tranformation
class to combine transforms.
3 Likes
Thank you Dan, great advice on points 1 & 2. I am new to Ruby, having worked on Windows UI and embedded FPGA code in C++ and C (respectively) for many years.
Yes, I do know how to combine transforms due to the nature of my real job, I had left them as two consecutive transform to verify that was not the culprit of the persistent ID.
I had based my method on a snippet I found (the dangers of cut & paste…):
def copy_entities(tr_rot, tr_move, entarray)
# Copies and transforms an array of entities
# mod = Sketchup.active_model # Open model
ents = @model.entities # All entities in model
if entarray.length == 0
UI.messagebox "nothing to copy"
return
end
tempgroup = ents.add_group entarray
temptrans = tempgroup.transformation
tempdef = tempgroup.definition
for i in 1...4 do
newgroup = ents.add_instance tempdef, temptrans
ents.transform_entities(tr_rot, newgroup)
ents.transform_entities(tr_move, newgroup)
temptrans = newgroup.transformation
tempdef = newgroup.definition
newgroup.explode
end
Thanks again for responding to my first post. Now I need to learn how to insert snippets like you did 
Unfortunately the new code does not perform the desired effect. The rotation is about the origin of the group, not the model origin. The point ‘rot_origin’, and vectors ‘rot_axis_vector’ & ‘shift’ are in model coordinates.
I posted a topic on this …
I did not solve the rotation axis, only the persistent ID problem.
When the user selects a bunch of entities and group it, the origin (transform) of this new group is a transition between the model origin and the lower left corner of the bounding box surrounding those entities.
EDIT: Nevermind this suggestion, just use the initial group's transformation as the base transform.
So you’ll likely need to make a temporary bounding box, add the bounds of all the selected entities, and get a vector from the origin to the BB’s minimum corner.
bb = Geom::BoundingBox.new
entarray.each {|ent| bb.add(ent.bounds) }
vec = ORIGIN.vector_to(bb.min)
t1 = Geom::Transformation.translation(vec)
… then use this to get a difference between it and where the user wants the rotational axis to be.
Also a quirk of the API is that when inside an editing context (of a group or component instance) the many API methods return or expect coordinates in world values instead of local values.
The rotation axis was not a problem until you solved the persistent ID. Trust me, as a programmer I see this all the time. Fix one bug and another crops up due to the solution.
If you run my original snippet with the selection say 10’ from the origin, all the new objects should be rotated about the origin. Think of creating a spiral staircase where none of the treads actually start at the center of rotation.
Again, I knew it would be something else to solve. But I wasn’t concerned as this thread is about the persistent IDs.
And FYI, I’ve been coding for more than 35 years.
I did and saw this. But your attempt was convoluted and incorrect, resulting in model errors.
Anyway, you see where I created the reference t
which was a starting identity transform ?
To “fix” my example you’d set that instead to the transformation of the original group of selected entities. Ie …
t = grporg.transformation
… (and tested and yes this is the easiest “fix”. Edited the previous example.)
Think of the initial transformation t
as the “base” transform for all the other copies.
IMO, users are not always likely to want their arrays centered on the origin. I think users would prefer to click where the center of rotation is to be. (Similar to how the native Rotate Tool works.)
The full extension I am working takes input for four points:
-
The desired center of rotation;
-
A second point which defines both the axis of rotation and the amount/direction you want to move each copy;
-
The third points sets the initial vector for the rotation angle; and
-
The last point determines the end point of the rotation angle.
Additionally, the user can specify by text the number of copies (using ‘s’), and the rotation angle. Points 1, 2, and 3 must still be entered by mouse click (full inferencing is available as well). Finally, the user can specify (for subsequent runs) only the number of copies (using ‘r’) and the extension will use the last set of points (origin, shift and rotation angle). Great for continuing the spiral. My ultimate goal is to have the last copy already selected so he only has to invoke the extension a second time without actively changing the selection.
For the snippet, I had set the rotation origin to the model origin, and the axis to (0,0,1). My extension allows rotation about any 3d axis, not only along the primary axes. So I agree, the user would prefer and already has the ability, I have simply not passed on the whole source file. If you change ‘rot_origin’, ‘rot_axis’ and ‘shift’ in my example snippet you will see what I mean.
This is the desired effect (left side): The short lines coming out of the blocks point toward the origin (as set in snippet).
With your mod, the apparent center of rotation is the end of the short line (right side): this is not my goal. This in fact uses ‘t = grporg.transformation’, which I had already tried.
FYI, All the native tools use “s” for number of sides, and “x” for number of copies.
Got it, changed as suggested from ‘s’ to ‘x’.
Meanwhile I figured out why my ‘convoluted’ code produced the correct transformations while yours does not…
In your code, you add each new instance while concurrently performing the transform on it, which results in only a transform relative to the local axis of the pre-selected group, rather about ‘rot_origin’ and ‘rot_axis_vector’ points in model space (or worldspace re AutoCad).
So I changed two lines and added one in the ‘for-loop’:
for i in 1..num_copies
tr_iter *= transform # Mod: Keep this iterated transformation separate from the original
tempgroup = actent.add_instance(grpdef, t) # Mod: Only apply original transform of 'grporg'
actent.transform_entities(tr_iter, tempgroup) # New: Now apply accumulated transformation
end
Thanks for you help 
1 Like
Hi Dan,
Just wanted to thank you again for the help with my first extension, which has now been published:
https://extensions.sketchup.com/extension/d071ed65-763d-46c6-b2d7-2d834e01e143/SpiralTool
I am now up to version 1.0.4 which adds some new features. Version 1.0.3 was submitted and is currently awaiting approval. Change log as follows:
Version 1.0.0 - 2020APR08 - Initial release
Version 1.0.1 - 2020APR09 - Added ‘/’ processing to set total ‘advance’ and divide by number of copies
Version 1.0.2 - 2020APR10 - Last copy made is now selected after creating spiral copies
Version 1.0.3 - 2020APR13 - Last copy selected now includes: Components, Groups, Faces, and Edges
Version 1.0.4 - 2020APR15 - The instance name of Components and Groups now contains ‘SpiralCopy X’
~Steve
Steve J. Frawley
sjfrawley1962@gmail.com
2 Likes