Align Group based on vectors

Dear community,

I’m trying to move a 3d text (images below) paralle to an edge transforming the 3d text group in order to have the x-axis (of the 3d text group) parallel to the vector representig the edge.

Actually I can moves the group containing 3d text to the middle point of the edge:

        grouptext.transform!(
            Geom::Transformation.new([textx, texty, textz])
        )

but I cannot reorient the group in order to have x-axis of the 3d text group oriented as the edge vector and y-axis oriented to an edge perpendicular to the edge vector (already calculated):

Initial condition

Expected result
image image

I tried to use the below lines of code, but it moves the 3d text group away in the space (because I think this is a traslation and I’m not aligning the axes of the group to the vectors mentioned)

        transf = Geom::Transformation.new([textx, texty, textz],@vettore[0], @v_off[0])
        grouptext.entities.transform_entities( transf.inverse, grouptext.entities.to_a )

@vettore[0] is the vector parallel to the edge, to be use as x-axis of the 3d text group, and @v_off[0] is the vector perpendicular to the edge, to be used as y-axis of the 3d text group.

Do I need to apply a rotation (maybe multiple rotations) or is there a way to orient a group according to a specific position?

Thank you all for your attention and cooperation, it is very appreciated!

Best regards

I create 3d text and orient it to other geometry in several of my extensions. As I’m sure you know, the text is created in the x,y quadrant of the model coordinates with its lower left corner at the origin. My preferred method to create the needed transformation is to build a 4x4 matrix from the target x,y, and z vectors and position and use it to initialize the transformation. I get hopelessly tangled up if I try to concatenate rotations and translations to accomplish the same end!

Thank you @slbaumgartner for your suggestion! I have no experience using matrices in Ruby Sketchup. Could you please share some code example? Again, thank you for your time!!

Tomorrow…I have prior commitments tonight.

Here is a simple example. I assume you have at least some familiarity with vector algebra, else my explanation may not make much sense to you…but then you are probably in over your head already! But please ask more questions if you can’t figure this out.

You would use the code below to create a Transformation and apply that to your 3DText. Typically the new x axis would point along a desired edge. However, without additional information such as an adjacent face or some reference axis it is not possible to determine which way the new y or z should go, as a single edge defines only a single direction in 3D space. I leave it to you to figure out the required additional directions and the appropriate new origin. Typically I get them either from the normal vector of a face adjacent to the edge or a fixed axis such as the Z_AXIS or the camera’s view direction, usually using vector cross products to calculate perpendicular vectors.

The construction here is based on the way that SketchUp and most other graphics internally represent Points and Transformations: A Point is a 4-element Array with the x,y,and z values in the first three elements and a 1 in the fourth. A Transformation is a 4x4 matrix unrolled column-major order into a 16-element Array. The first three elements of the unrolled Array are the target x-axis vector, followed by a 0. These would be the first column of the 4x4 matrix. The second column is the y axis and the third is the z axis (also padded with a 0 each). The fourth column is the offset vector that moves the origin, padded with a 1. This interpretation corresponds to transforming by multiplying a position vector on the left by the 4x4 transformation matrix.

make_transformation.rb (1.5 KB)

1 Like

Hello @slbaumgartner, thank you very much for your effort!

Yes, I’m quite familiar with vector algebra!

I tested your script (manipulating it a bit) but I’m probably doing something wrong!

I used as input:

  • new_x: the vector paralle to the edge;
  • new_y: a vector perpendicular to new_x;
  • new_z: the cross product between new_x and new_y;

Honestly, the fourth vector of the matrix is not clear to me. What are you referring to when you talk about “offset vector that moves the origin”?

I tried to figure out your example semplifing it as a sequence of simple lines instruction (just for semplicity and for better understand what is happening):

        new_x = @vettore[0]
        new_y = @v_off[0]
        new_z = @vettore[0]*@v_off[0]

        new_x.normalize!
        new_y.normalize!
        new_z.normalize!

        trans_array = []
        trans_array[0] = new_x.axes[0]
        trans_array[1] = new_x.axes[1]
        trans_array[2] = new_x.axes[2]
        trans_array[3] = 0.0
        trans_array[4] = new_y.axes[0]
        trans_array[5] = new_y.axes[1]
        trans_array[6] = new_y.axes[2]
        trans_array[7] = 0.0
        trans_array[8] = new_z.axes[0]
        trans_array[9] = new_z.axes[1]
        trans_array[10] = new_z.axes[2]
        trans_array[11] = 0.0
        trans_array[12] = textx
        trans_array[13] = texty
        trans_array[14] = txpoint[0].z
        trans_array[15] = 1.0

        tr_ax = Geom::Transformation.new(trans_array)
        grouptext.entities.transform_entities(tr_ax.inverse, grouptext.entities.to_a)

In this case I used as fourh vector of the transformation matrix the target point (the new origin: textx, texty, textx).

In the two final lines above I’m applying the transformation to the 3d text group, but Sketchup is telling me “Error: #<TypeError: can’t convert Geom::Vector3d into Float>” in:

tr_ax = Geom::Transformation.new(trans_array)

I need your help again!

Thank you very much indeed!

Your mistake is in using the axis method on the new axis vectors instead of just the components of the Vector3d. Check the documentation for the Vector3d#axis method. It returns an Array of three Vector3d’s that are supposed to make an orthogonal set using the given vector as the z axis. So, when you write

trans_array[0] = new_x.axes[0]

you are setting the first element of trans_array to be the first of the three Vector3d’s returned by Vector3d#axes. The Transformation constructor is expecting to see a scalar Float, which is what you will get if you write

trans_array[0] = new_x[0]

as I did in my code example. The [ ] operator on a Vector3d returns a component of the vector as a Length: [0] = x component, [1] = y component, [2] = z component. It would probably have been clearer if instead I had used the #x, #y, and #z methods on the Vector3d, as they don’t require you to understand which index goes with which component.

The fourth argument to my method is the new location of whatever point was originally at the model origin. By calling that “offset” I evidently confused you. When the original location is the origin, there is no difference between coordinates of the destination Point3d and a Vector3d offset from the origin to that Point3d. SketchUp is often pretty flexible about taking a 3-element Array, a Point3d, or a Vector3d as an argument and silently converting between them. They have been gradually tightening up the Ruby API to be more precise about these things, as mathematically they are different concepts. An Array may be used to hold the components of a Point3d or a Vector3d, but technically they are not the same thing and you really should convert explicitly to avoid mistakes.

Edit: On reviewing my code, I see an error: I called the fourth argument “new_origin” but put “symbol_point” in the code where that argument should have been used. Sorry! That was a copy-paste error from the extension where I grabbed the sample code.

@slbaumgartner thank you for the explanation, I misinterpreted the meaning of Vector3d#axis method.

Again, thank you so much for your help, you were essential! I still have some doubts to clarify, if you want to help me.

I managed to place all the texts around the edges using your suggestion to adopt a matrix technique, but as you can see from the images below, if the edges make a loops, the text is flipped according to the position of the new target axes X, Y and Z of the 3d text group, especially the Y axis.

image

If the Y axis (of the 3d text group) is pointing to the intern loop, texts are positioned like the picture on the left, on the opposite if I set the reverse, texts are positioned like the picture on the right.

This happens more generally not only if the edges make a loops but for all the edges found in the north and south quadrant of a hypothetical system of axes.

image

I think that is a matter of setting the new axes according to a rule to be defined. Do you have experience with these cases? How do you solve it?

Thank you again for your support

1 Like

Yes, I have dealt with similar issues. But I know of no simple set of rules that always work - there are too many possible factors entangling. I have usually struggled long enough that I think important situations are covered and then thrown in the towel on the rest. I have found it helpful to print out the calculated values of the new x, y, and z to understand what is going on.

The definition of “important situations” depends on the nature of the models you plan to annotate, so I won’t share code snippets that would risk mucking up a case that you consider to be important but I didn’t. For example, my Slope Markers extension expects that Edges of interest will be sloped and possibly not in any cardinal plane of the model, so it decides what to do based on the direction from which the camera is looking at the model. The logic got pretty complex and ad-hoc.

Note that these factors don’t depend on whether you use my vector technique to build the Transformation or build it by concatenating translate and rotate Transformations.

Among the factors are questions about which way the texts should read correctly. They are static objects once placed in 3D space, but you can orbit around the model to view them from different directions. For example, in your first image, the texts on the left face will be ok if viewed from the back side. The upside-down text at the bottom of the right face will be ok if you rotate the view so the model’s y axis points down on your screen, but then the one originally at the top will be upside-down. Et cetera.

Another confusing factor is that a Face may “use” an edge in the reverse direction from what its end - start would indicate. This happens because the outer loop of a Face always traces in counter-clockwise order around the Face’s normal (“right-hand rule”). But when two Faces share an Edge, they must use the Edge in opposite directions for this right-hand rule to be true for both of them. That’s why SketchUp has EdgeUse objects between Loops and Edges.

From basic vector algebra, if you determine two orthogonal axis vectors, you can always find the third using a cross product.

z_axis = x_axis.cross(y_axis)
x_axis = y_axis.cross(z_axis)
y_axis = z_axis.cross(x_axis)

So, they key is to determine which two will best constrain the orientation in a particular case.

For example, it is usually safe to assume that the new z axis should be parallel to the normal vector of the plane in which the labels are to be placed. This will make sure the front Faces of the 3DText will face upward when viewed looking toward the front of the Face.

It is usually also the case that the new x axis should be parallel to the associated Edge, as that axis is parallel to the baseline of the text. But that’s where it starts to get tricky because a parallel axis could be pointed in either of two directions. An Edge has a start point and an end point. You might assume that end - start would define a vector suitable for use as new_x. But depending on how the Edge was drawn and how it is being used by an adjacent Face, that might be backward from what you want! When the chosen new x axis is revesed, so will the new y axis calculated from it and z! That will cause the backward effect in some of your images.

When you are dealing with the outer loop of a Face, you can usually test whether y is how you want it by creating a test point offset in y by a small amount from the Edge midpoint and using Face#classify_point to check whether the test point is inside or outside the Face. If it is inside, you can conclude that you have the reversed case on the left in your first image. Once you have one Edge’s vector oriented correctly, the others will go around the Loop in the same direction.

For an inner loop, you need to check whether the Edge is also used by another Face. If so, ignore it on this Face and add the 3dText when you process the other Face’s outer loop. If the Edge isn’t used by any other Face, its loop will

The lower image is more of a problem because there is no Loop involved so you have to resort to some arbitrary notion of what is “right”. Cases such as the one on the lower right are hard to analyze, as there is no inherent notion of “outside” or “inside”.

@slbaumgartner sorry for the absence these days, I’ve been busy with big family Christmas lunches!! I take this opportunity to wish you a happy 2024!!
Thanks for the help and suggestions. I’m looking for a rule that covers the main cases that interest me. The problem in my case is that there are no faces involved, I’m always dealing with open polylines. I’ll create a new topic for a concept I have in mind that might work.
Thanks again for the vital help with the transformation matrix, and again: Happy New Year!

1 Like

Just thinking off the top of my head, no time today to try any code…

You could search for an edge with only one other connected edge, then build a polyline by following through successive connected edges until you find either an edge with no more new connections or an edge with more than one new connection. Then analyze the polyline to see which end you can start from to follow all of them in anti-clockwise order around z (remembering that some of them may be reversed in the polyline). Then you can process them the same as the outer loop of a face.

Edit: Nah…that assumes the polyline always turned the same way at each vertex. Failing that there is no consistency of clockwise or anti-clockwise.