Persistent ID Errors when Copying Groups/Components

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, 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.

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 =,0,0)
        rot_axis_vector =,0,1)
        shift =,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)

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.

1 Like

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"

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

Thanks again for responding to my first post. Now I need to learn how to insert snippets like you did :slight_smile:

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 =
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:

  1. The desired center of rotation;

  2. A second point which defines both the axis of rotation and the amount/direction you want to move each copy;

  3. The third points sets the initial vector for the rotation angle; and

  4. 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.

Desired Effect
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

Thanks for you help :slight_smile:

1 Like

Hi Dan,

Just wanted to thank you again for the help with my first extension, which has now been published:

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

  •                      - Final pick point now inferenced to 'ref center'*

Version 1.0.3 - 2020APR13 - Last copy selected now includes: Components, Groups, Faces, and Edges

  •                      - Can now handle a zero rotation angle (resulting in 'advance' transform only)*

Version 1.0.4 - 2020APR15 - The instance name of Components and Groups now contains ‘SpiralCopy X’

  •                      - Copy count is now accumulated (if 'r' is invoked)*


Steve J. Frawley

1 Like