Group transformation lag

Hi Guys,

I try to pushpull faces of a building then transform them, so to create certain scaffolding spaces. My scripts works well so far to find the walls to be scaffolded and create the basic space properly in the UI. However, it seem that that in the code there is some kind of “lag” and my entities are not the ones which were transformed.

The purple spaces are the correct ones which were made by the below code:

          group = entities.add_group
 = "Scaffolding_space:#{i.persistent_id}"
          face = group.entities.add_face i.vertices

          face.pushpull(1.0.m, true)
          dist = 0.3.m
          move_by = i.normal.to_a.collect { |n| n * dist }
          p move_by
          moving_trans = Geom::Transformation.translation(move_by)

          group.transformation = moving_trans
          group.material ="Plum")

          all_scaffolding_spaces << group

The problem comes from the last row. Later in my codes I want to use those entities, so the group which were transformed. I try to find the the end of the scaffolding spaces to do further operation on them. (i.e managing the corners, etc.) But as you may see on the screenshot my other scripts finds a strange face. For me it seems that it finds the face of the “un-transformed” face of that group. Does it make sense? …Can it be a certain lag which might be due to the time of the operation of the transformation? So I am actually pushing the un-transformed group entities into the array of all_scaffolding_spaces ?

Thank you in advance.

Assuming that i is a face, since it responds_to?(:vertices)

How about …

move_vec = i.normal.clone.length= dist
move_by = Geom::Transformation.translation(move_vec)
group.transformation= move_by

Hi @DanRathbun,

Thanks for your very prompt comment. Yes, correct, i is a face, more precisely one of the face of the building. (the white geometry) …I tried your suggestion now and it throws an error:

"Move vec: 300 mm"
Error: #<ArgumentError: Cannot convert argument to Geom::Vector3d>

I guess that’s because of the Transformation expects a Geom::Vector3d rather than a distance.

But actually the transformation part works fine in my code. A new group is created, then the face is added to the group, then the face is pushpull-ed and then transformed (or simply say moved) 300mm away from the building. The results are the purple (Plum) objects, as the code iterates through several faces.

My issue is that when I store the group (the groups are the Plum coloured objects, the scaffolding spaces) in the array, it stores them as they were not moved (transformed). You may see in my code snippet above that first I pull the faces and then transform them. It seems like the group is pushed to the array after the pushpull operation but before the translation. Does that make sense?

BTW, what an observer in Sketchup is meant to be? For instance the EntityObserver? Should I maybe use an observer to watch for the transformation when it’s done? What do you think?

After an all day long searching, I am still not get the point what happens with my transformation. In the SU UI it looks perfect, my ScaffoldingSpaces as groups are created and transformed from the wall by 30cm away. See below:

But with my script I can only find the red or blue faces which is not what I want. I would need the transformed purple faces around the corners.

I understood from here that the vertices of a face are relative to their parents. But once I transform the whole group (including those faces) why the vertices shouldn’t be transformed as well, as in the UI?

What is "300 mm" ?

No kidding!

I gave you a statement that produces a vector and sets it’s length to whatever you set dist to …

move_vec = i.normal.clone.length= dist

EDIT: No, actually I didn’t, see below

Your code had been setting the dist variable previously. What happened ?

I expected you to remove the these old statements from your iteration block …

         move_by = i.normal.to_a.collect { |n| n * dist }
          p move_by
          moving_trans = Geom::Transformation.translation(move_by)

          group.transformation = moving_trans

… and insert the 3 statements I gave you. It’s not rocket science.

I do not understand why you would down convert a vector to an array and then mutiply all three (x, y and z) by the translation distance ? You only need to move along one axis in the direction that the original face’s normal vector is pointing.

Start another topic about observers ...

Start another topic about observers. It’s off-topic for this one.

We don’t have your script to test, nor do we have the test model. (Hint hint.)

When you add a group into an entities object, it will be added at the origin. We don’t know what entities object you are adding the group into (because you only gave us a part of your code.)

Vertices are objects that belong to a face or edge. Vertices have a method that returns a position (a Geom::Point3d) that is relative to the origin of the entities context (you call it “parent”) in which the face or edge is a member.

However, your plum group has faces that are in the group’s entities context, not the same as the original face.

Actually the error message is correct, because the statement move_vec = i.normal.clone.length= dist returns only the length rather than a cloned, new Vector3d.

> move_vec =,0,0)
(1, 0, 0)
> move_vec.clone.length= 10
> move_vec.length= 1.23.m
1230 mm
> move_vec
(48.4252, 0, 0)

However, it worked when I saved the new cloned vector into a new variable like this:

move_vec = i.normal.clone
move_vec.length= dist  
#then use

However, this had no impact on the result I was hoping for but with your last sentence you gave me the direction to look further, and eventually solved my problem.

My script was working in the context of the model where the original group was added to. I had entities = Sketchup.active_model.entities in a method where I should associate other iterations on group.entities instead. Now it works as expected and I can go further :wink: Thanks

Now yur talkin’ ! I tried to chain too much together and “got ahead of myself”. :blush:

The learning is better when you figure it out yourself.

1 Like

Hi @DanRathbun,

I’m sorry, it seems my concern is not finished yet. Graphically speaking I was happy and believed what I saw in the UI. But unfortunately my group coordinates are not correct if I compute them. So, what is the best practise in SU API to create objects add them to a group and transform? I thought the best way is to

  • create an empty group in the model context
  • create some entities (faces, edges, etc.)
  • add these entities to the group
  • make the transformation to the group

Isn’t it?
If you see my below screenshot, you may see that the X-coordinates of my faces are different than the dimension lines show them. (mm and inches, sorry for that) The coordinates printed to the Ruby console shows unfortunately the original location of those faces before the transformation.
The blue and red colors were added to the faces, for my own reference to understand which face I am manipulating or working further with. But even it shows a face which is transformed, but that is only “illusion”.
So I still don’t understand clearly what’s going on with the transformation. …I attach now my code as well.

create_scaffolding_spaces.rb (9.1 KB)

You can use Sketchup::format_length to output coordinates (to the console) in the model’s units, rather than the internal inches of the API.

You should seek to get in the habit of creating things in the model’s active_entities context.
This is the context in which the user will be working.

Yes this is good practice.

But then you need to adjust the coordinates so that the groups are not as big as the model.

  • Note, I’m “thinking out loud” here “on the fly” … (none of this is tested.)

Collect all the points from the original face into an array (pts.)

pts =

… choose the min position (3D point) from the original face’s vertices …

bot_pts = pts.find_all { |pt| pt.z == 0 }
bot_pts.sort! {|a,b| a.x <=> b.x }
min_y = { |pt| pt.y }.min
# Now we go through the points sorted by x, choosing by the y minimum
min = bot_pts.find { |pt| pt.y == min_y }

(It would likely work with any of the bottom points.)

Then create a vector from the origin to the min point …

tvec = ORIGIN.vector_to(min)

Transform the points in the array with the reverse of tvec.

pts.each { |pt| pt.offset!(tvec.reverse) }

Now you have the points for the internal group scaffolding face.

ents = model.active_entities
grp = ents.add_group
face = grp.entities.add_face(pts)

Then create a transitional transform from tvec, and
transform the group back to the original face’s location …

t = Geom::Transformation.translation(tvec)

Now transform the group spaced off of the wall as you did before,
in the direction of the faces normal vector.

And then pushpull the group internal face in that same direction.

Now the groups should be only as big as their bounds.

Hi @DanRathbun, thanks for the workaround.

This didn’t work and the result is something like the below.

Here you meant face = grp.entities.add_face(pts)?

…but let see the problem in a simple geometry. A triangle created at the origin and translated by a vector.
The code looks like this:

model = Sketchup.active_model
entities = model.active_entities

group = entities.add_group

p1 = [0,0,0]
p2 = [0,100,0]
p3 = [100,100,0]
face = group.entities.add_face(p1,p2,p3)
vect = [50,50,150]
tr = Geom::Transformation.translation(vect)
group.transform! tr

group.entities.each {|ent|
  p ent.start.position unless ent.typename != "Edge"

the result is this in the UI
Screen Shot 2020-08-31 at 13.20.26
…this is the output in the console

Point3d(0, 0, 0)
Point3d(0, 100, 0)
Point3d(100, 100, 0)
Point3d(0, 0, 50)
Point3d(0, 100, 50)
Point3d(100, 100, 50)
Point3d(0, 100, 0)
Point3d(0, 0, 0)
Point3d(100, 100, 0)

As you see here, all of the coordinates of the start point of the edges are around the origin, not in the translated position. What is the reason for this?

What do you mean exactly by this? What does that mean “not as big as the model” And what is the reason that I have to adjust coordinates?:

I attached also my file, so you may see if I misunderstood something. (I made a small change compared to you suggestion, for occasions when geometries are not placed on the z == 0 plane.
create_scaffolding_spaces.rb (10.7 KB)

@DanRathbun, pls see another workaround. If I apply the same transformation to points, in the triangle case, to the its vertices, it works fine. …ora at least seems to be.

model = Sketchup.active_model
entities = model.active_entities

group = entities.add_group

p1 = [0,0,0]
p2 = [0,100,0]
p3 = [100,100,0]
points = [p1,p2,p3]

vect = [50,50,150]
tr = Geom::Transformation.translation(vect)
#group.transform! tr

new_points = []
points.each {|p| 
  new_points << p.transform!(tr)

face =group.entities.add_face(new_points)
#group.entities.transform_entities(tr, face)

group.entities.each {|ent|
  p ent.start.position unless ent.typename != "Edge"

And the the coordinates:

Point3d(50, 50, 150)
Point3d(50, 150, 150)
Point3d(150, 150, 150)
Point3d(50, 50, 200)
Point3d(50, 150, 200)
Point3d(150, 150, 200)
Point3d(50, 150, 150)
Point3d(50, 50, 150)
Point3d(150, 150, 150)

And I also have a nice bounding box:
Screen Shot 2020-08-31 at 14.14.39
In this was I got nicely the transformed group coordinates. For me this would be ok, but what is the reason the users are not encouraged to transform entities, other than groups?

I may not be able to describe it as precisely as Dan would do, but I will try my best… :wink:

The coordinates is around the group local origin since you are iterating “inside” of group entities.

If you want to know the parent coordinates - in this case the ‘global’ or model coordinates. you have to apply a transformation of the group.

tr_g = group.transformation 
group.entities.each {|ent|
  unless ent.typename != "Edge"
    loc_cord = ent.start.position 
    mod_cord = ent.start.position.transform(tr_g) 
    p loc_cord
    p mod_cord

Notice that the tr_g will be the same as tr you applied to the group before.
Notice 2: unless ent.typename != "Edge" is slow better to use if ent.is_a?(Sketchup::Edge)

If you are creating a group “inside” the model entities the origin of the group will be placed on the origin of the model.
If you are adding the face to the group.entities the coordinates will be relative to group coordinate system.
In the second case when you applied the transformation to create new points, and adding to the group it will be still relative to group coordinate system. The “good” result is just because the group origin is a same position as the model origin.
The “nice bounding box” does not means that the group origin is on the corner of the bounding box, like in the first case when you applied the transformation on the whole group after.
Let’s try to "simulate manually " both cases:

Hi @dezmo, thank you for your reply and I appreciate the screen record.
I think I start to get the point :wink: …In the meantime I refactored my code and I got a good result by transforming the Vertices of the building faces and then add them into a fresh group + pushpull. …is there any drawback if I apply translation to Point3ds instead of groups? …it is also a listed method in the API doc.

yepp, seems I started with a wrong practise, …I’ll optimise my code accordingly :wink:

PS.: could it be a good practise to first always “simulate” our code in the UI, as you did and then lay down our code based on what we achieved in the UI? Is the UI and API are consistent enough to believe that what we see is what we code or contrary?

No. Sure not. The only thing is to apply it when it is appropriate… and check why the result is really good and not a ‘fake’ good.
Developers are free to choice how to do the programming. There are always several good or better ways.

I’m not a programmer just a hobbist, but I guess the API itself is consistent enough with some bugs in it, and there are some mistakes in the docu as well.

Therefore I’m always “simulating” and comparing what is written in a docu and what result I see on the UI.
There are quite a few good examples in the doc that you can try. Some are good, some are too simple, or just not complete …
My method is always: try, check and see!

@dezmo Sure, I’m checking the API docs continuously along the coding. I’ll post it in the forum if I see any discrepancy.

Yes. My bad. (I corrected the error.)

Why do you use a vector that has a z of 150 ? T

Yes. The reason is so that these coordinates are local to the group entities context.

So that the group’s origin is near the wall face, not all at the model’s origin. And …

This is exactly what I mean. So that each group’s bounding box is only as big as the group.

I don’t know where this statement comes from.

The reason SketchUp users use groups and components is to separate geometry into “parts”.
We must do this because SketchUp geometry interacts. It has “sticky” edges.

It looks as though you did not transform the groups back to the walls as I said …

Geom::Point3d objects are virtual objects, not model entity objects. They are geometric “helper” objects that encapsulate coordinates and are used to create or manipulate entity objects.

The example I showed above uses the wall face’s vertices, to get points and transform them using #offset! to “convert” them to group local coordinates, so that the group’s scafolding faces can be created at the group’s local origin.

Afterward, you need to transform the whole group back to it’s mating wall face.