Checking if two Placements are parallel and creating one orthogonal.

Need help, or want to share a macro? Post here!
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
keithsloan52
Veteran
Posts: 2764
Joined: Mon Feb 27, 2012 5:31 pm

Re: Checking if two Placements are parallel and creating one orthogonal.

Post by keithsloan52 »

onekk wrote: Sat Apr 03, 2021 6:02 pm
so if the solid is the same, axis and angle are the same if they are parallel, try some sample test and.

Carlo D.
Yes if the axis and the angle are the same they are parallel , but there are also cases where the axis are not equal and the angles are not equal, yet they are still parallel.
edwilliams16
Veteran
Posts: 3180
Joined: Thu Sep 24, 2020 10:31 pm
Location: Hawaii
Contact:

Re: Checking if two Placements are parallel and creating one orthogonal.

Post by edwilliams16 »

keithsloan52 wrote: Sat Apr 03, 2021 7:13 pm Yes if the axis and the angle are the same they are parallel , but there are also cases where the axis are not equal and the angles are not equal, yet they are still parallel.
Like rotations of 30 and -330 or what?

I'm sure I could answer your question if I knew exactly what you are after.
A placement is a transformation between two coordinate systems related by a rigid motion. Objects in the coordinate system do not change shape or dimensions but relocate. The most general such transformation consists of a rotation about an axis through the origin followed by a translation of that origin. What I would mean by parallel would be that the two rotations are equal and that therefore the two coordinate transformations differ only by a translation.

In the attached, I have three objects, a cube, a cone and a cylinder all attached to the XY-plane with differing placements. The cube and the cone have the same rotation, but different translations. The cylinder differs in both.

Code: Select all

cubeR = App.ActiveDocument.Box.Placement.Rotation #rotation as quaternion
coneR = App.ActiveDocument.Cone.Placement.Rotation
cylinderR = App.ActiveDocument.Cylinder.Placement.Rotation
cubeR == coneR # returns True - but can easily fail due to numerical error eg when using -330 instead of 30 for rotation angle
cubeR == cylinderR # returns False - no problem
testIdentity = cubeR.multiply(coneR.inverted()).Angle #  should be 0.0 if rotations equal
abs(testIdentity) <1e-15 #  returns True   despite numerics - Good!
testIdentity = cubeR.multiply(cylinderR.inverted()).Angle #  should be non-zero
abs(testIdentity) <1e-15 #  returns False as desired
As to constructing an orthogonal placement, I'm not sure what you mean in 3D. If you mean constructing a placement rotation such that its local xy plane is orthogonal to the xy plane of the subject, that is easy, any 90 degree rotation about an axis in the local xy plane will do the job. Let us say 90 degrees about the local x-axis for definiteness.
The quaternion for this rotation is (1,0,0,1)/sqrt(2). Post-multiplying quaternions creates successive rotations.

Code: Select all

rot90x = App.Placement(App.Vector(0,0,0),App.Vector(1.,0,0), 90).Rotation
newR = cubeR.multiply(rot90x)
constructs the 90 degree rotation and the new Rotation, orthogonal to that of Box (cubeR)

You can now rotate Box001 to be "orthogonal" to Box with

Code: Select all

App.ActiveDocument.Box001.Placement.Rotation = newR
Is this what you were after?
Attachments
PlacementOrientation.FCStd
(21.63 KiB) Downloaded 14 times
keithsloan52
Veteran
Posts: 2764
Joined: Mon Feb 27, 2012 5:31 pm

Re: Checking if two Placements are parallel and creating one orthogonal.

Post by keithsloan52 »

edwilliams16 wrote: Sun Apr 04, 2021 11:24 pm
keithsloan52 wrote: Sat Apr 03, 2021 7:13 pm Yes if the axis and the angle are the same they are parallel , but there are also cases where the axis are not equal and the angles are not equal, yet they are still parallel.
Like rotations of 30 and -330 or what?
No I am dealing with objects that are from file imports OpenSCAD / GDML etc there Placements could be be any valid Quaternions / 4 x 4 Matrix.
One can create a valid Quaternion with a valid Axis and Angle, but there are a number of valid Axis and Angles that would create the same Quaternion / 4 x 4 Matrix.

I wish to only do something if they are parallel, I don't want to rotate or change their Placements
keithsloan52
Veteran
Posts: 2764
Joined: Mon Feb 27, 2012 5:31 pm

Re: Checking if two Placements are parallel and creating one orthogonal.

Post by keithsloan52 »

My thinking is running on these lines.

A Placements Rotation is a Q (quaternion) a 4 x 4 Matrix to check if two are equivalent one could normalise the 4 x 4 matrix
and check if they are the same.

Now to normalise a Matrix my understanding is that you calculate the determinant of the Matrix, lets call it d,
then take each element of the matrix and divide by the determinant d. Okay one would then loop though all 16 values to check if they are the same.

My intuition rather than any logic has me thinking, could one check if the two normalised matrix were the same by checking if their determinants were the same value and if so could you calc this determinant value in one pass.
edwilliams16
Veteran
Posts: 3180
Joined: Thu Sep 24, 2020 10:31 pm
Location: Hawaii
Contact:

Re: Checking if two Placements are parallel and creating one orthogonal.

Post by edwilliams16 »

No, but at least it seems I have solved the correct problem for you already. That problem, as I understand it, is to compare two rotations to see if they are the same, despite the fact they might have been constructed from different axis-angle pairs.
For instance [(1,1,1), 30] and [(.7,.7,.7), -330] generate the same rotation.

If I have

Code: Select all

rot1 = xxx.Placement.Rotation
rot2 = yyy.Placement.Rotation


Testing to see if they were equal should be as simple as

Code: Select all

rot1 == rot2
, as the

Code: Select all

__eq__
property is inherited from somewhere. Unfortunately, it doesn't work, as I showed above, because somewhere it committed the cardinal numerical sin of testing floating point numbers for exact equality.

Ideally then, I'd like to reduce the difference between two candidate rotations to a single number I can compare to zero within the range of floating point error, so that is what I did.
It is still a one liner, but more complicated:

Code: Select all

abs((rot1.multiply(rot2.inverted())).Angle) < 1e-15 # True if rot1 == rot2
Lets break this down: rot2.inverted() is the inverse rotation to rot2, so rot1.multiply(rot2.inverted()) is the result of doing rot1 then undoing rot2. If rot1 and rot2 were the same, the result will be the identity rotation.

Now all we need is a robust way of checking this. There are a number of ways. The one I chose is to look at the Angle property. An identity rotation consists of a rotation of zero degrees about an arbitrary axis. We can't test to see if the Angle is exactly zero as that commits the cardinal floating point sin, so we test to see it is sufficiently small. A purist would use a precision related to the floating point precision of the platform and an analysis of the degree of compounding of numerical error. 1e-15 should be good enough. 1e-14 should be conservative, it depends on the provenance of the numbers. If I had problems, I would put in a debug line alerting if the Angle were ever very small (say < 1e-7), but not small enough to pass the test.

So changing to Physics professor mode, a few facts:

The 4x4 matrix representation of Placement combines both rotation and translation. If we only want to compare rotations, we would have to look only at the [[0:3],[0:3]} submatrix. I didn't find a convenient method to extract this, but that may be my lack of python skills. The determinant of that matrix, which measures the volume expansion of the linear transformation, is always 1.0, as rotations preserve volume. (Parenthetically -1.0 would also preserve volume but would change the handed-ness of the coordinate system. It would represent a rotation plus a reflection)


The quaternion representation of a rotation is a list of four numbers. They are normalized in that the sum of their squares is always 1.0
They are not quite unique for a given rotation. Changing the signs of all the components gives the same rotation. However, always reducing to a canonical form, such that the scalar component is >=0., removes the ambiguity. I haven't checked to see if FreeCAD does this. It didn't matter for my purposes.

Multiplying quaternions according to very special rules, discovered by William Rowan Hamilton, and scratched on a bridge in Dublin in celebration of his revelation, has the result of creating the quaternion representing the result of the two successive rotations they represent. Because the result of successive rotations can depend on their order, quaternion multiplication is not necessarily commutative.

The multiply() method used above on Rotations implements this non-commutative multiplication, though in this particular instance the order wouldn't matter because successive rotations around the same axis commute.

Last note. I didn't check whether rot1.multiply(rot2) represents first 1 then 2 or vice-versa. Both conventions are possible, and it doesn't matter for these purposes. I suspect I might have the opposite convention to FreeCAD.

EDIT. Ok, I checked this for the sake of future readers.
FreeCAD's quaternion (a, b, c, d) represents the quaternion d + aI +bJ + cK. The product of two quaternions rot1.multiply(rot2) is indeed rot1*rot2 (the order matters), and it represents a rotation rot2 followed by rot1
So I misspoke above, rot1.multiply(rot2.inverted()) is actually undoing rot2 followed by doing rot1. Either way, the result is the identity rotation if rot1 and rot2 are the same. So the test works either way, which is why I didn't take the time to sort it out.
edwilliams16
Veteran
Posts: 3180
Joined: Thu Sep 24, 2020 10:31 pm
Location: Hawaii
Contact:

Re: Checking if two Placements are parallel and creating one orthogonal.

Post by edwilliams16 »

I looked around in the C++ source and found
https://github.com/FreeCAD/FreeCAD/blob ... tation.cpp
which suggested there's a Python binding I failed to find. Indeed there is. We can compare two rotations rot1 and rot2 with

Code: Select all

FreeCAD.Rotation.isSame(rot1, rot2, 1e-15)
returning True if the rot1 and rot2 are equal within the tolerance 1e-15 - which worked for me in a few test cases of constructing the same rotation in different ways. It does fail with the default tolerance of zero, which is apparently what `rot1==rot2` uses.
keithsloan52
Veteran
Posts: 2764
Joined: Mon Feb 27, 2012 5:31 pm

Re: Checking if two Placements are parallel and creating one orthogonal.

Post by keithsloan52 »

edwilliams16 wrote: Tue Apr 06, 2021 3:02 am I looked around in the C++ source and found
https://github.com/FreeCAD/FreeCAD/blob ... tation.cpp
which suggested there's a Python binding I failed to find. Indeed there is. We can compare two rotations rot1 and rot2 with

Code: Select all

FreeCAD.Rotation.isSame(rot1, rot2, 1e-15)
returning True if the rot1 and rot2 are equal within the tolerance 1e-15 - which worked for me in a few test cases of constructing the same rotation in different ways. It does fail with the default tolerance of zero, which is apparently what `rot1==rot2` uses.
I tried your code and it seemed to be working not that I totally understood how, but having a C++ callable version is great.

What I need now is a way to check if a Vector is 90 at degrees to a Placement.Rotation ( i.e Is a Normal ) - Please
edwilliams16
Veteran
Posts: 3180
Joined: Thu Sep 24, 2020 10:31 pm
Location: Hawaii
Contact:

Re: Checking if two Placements are parallel and creating one orthogonal.

Post by edwilliams16 »

keithsloan52 wrote: Tue Apr 06, 2021 7:40 am
What I need now is a way to check if a Vector is 90 at degrees to a Placement.Rotation ( i.e Is a Normal ) - Please
Rather than have me guess, you’ll need to tell me what you mean. Vectors are normal to planes. What plane? A Placement rotation is not a plane. A rotated plane is a plane however. If so, what plane is being rotated? For instance, if the plane you want to see if your vector is normal to is a Datum plane attached to the global XY plane by a Placement, then the plane in question being rotated is the XY plane, and your question has an answer. Without knowing what plane is being rotated by the Placement, asking whether a vector is normal to it is meaningless.
keithsloan52
Veteran
Posts: 2764
Joined: Mon Feb 27, 2012 5:31 pm

Re: Checking if two Placements are parallel and creating one orthogonal.

Post by keithsloan52 »

edwilliams16 wrote: Tue Apr 06, 2021 4:22 pm
keithsloan52 wrote: Tue Apr 06, 2021 7:40 am
What I need now is a way to check if a Vector is 90 at degrees to a Placement.Rotation ( i.e Is a Normal ) - Please
Rather than have me guess, you’ll need to tell me what you mean. Vectors are normal to planes. What plane? A Placement rotation is not a plane. A rotated plane is a plane however. If so, what plane is being rotated? For instance, if the plane you want to see if your vector is normal to is a Datum plane attached to the global XY plane by a Placement, then the plane in question being rotated is the XY plane, and your question has an answer. Without knowing what plane is being rotated by the Placement, asking whether a vector is normal to it is meaningless.
Well I have two objects, I check if they are parallel thanks to your help. I then want to test if a Vector formed from their two Placement Bases i.e. Obj2.Base.Sub(Obj1.Base) would be at right angles to their equal Placement.Rotations.
edwilliams16
Veteran
Posts: 3180
Joined: Thu Sep 24, 2020 10:31 pm
Location: Hawaii
Contact:

Re: Checking if two Placements are parallel and creating one orthogonal.

Post by edwilliams16 »

keithsloan52 wrote: Tue Apr 06, 2021 6:30 pm
Well I have two objects, I check if they are parallel thanks to your help. I then want to test if a Vector formed from their two Placement Bases i.e. Obj2.Base.Sub(Obj1.Base) would be at right angles to their equal Placement.Rotations.
Maybe we are homing in. You have two coordinate systems defined by the placements of Obj1 and Obj2. Because the Rotation components of the two placements are equal, the coordinate axes of each system are parallel. They differ only by a translation. That is your vector Obj2.Base.Sub(Obj1.Base).
You want that vector to be perpendicular to something. What plane or line? The common XY-plane, or the Z-axis for instance. There's no such concept as being normal to a rotation. You could be normal to the rotation Axis perhaps, though I'm not clear why that would be useful.

In 3D, a direction vector can be orthogonal to a plane (no degrees of freedom) or a line (one degree of freedom), or even a point (two degrees of freedom), but you can't be orthogonal to the whole space because there are no dimensions (or degrees of freedom) left.
Post Reply