Rotation of a part using two vectors

Need help, or want to share a macro? Post here!
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
Post Reply
User avatar
cblt2l
Posts: 155
Joined: Sat May 15, 2010 3:59 am

Rotation of a part using two vectors

Post by cblt2l »

I'm working on a macro that does the following:

1. User selects face of part
2. User runs macro
3. Part is rotated so that the selected face is coincident to the XY plane. The normal vector for the face should be (0,0,-1) so that the Z vertices of the part are all >= 0.

The method I tried is to create a rotation object using the normal vector of the selected face and the vector (0,0,-1). The problem with this method is that the rotation always returns the values relative to the nearest axis ( i.e. all values are < 180) but the part placement is based on the absolute or total rotation of the part. If the part has a rotation of 0,0,0 then it works fine, otherwise the rotation is wrong.

Here's a small example that demonstrates the problem. Any ideas? Maybe this should be done with a rotation matrix :?

Code: Select all

# Make a box
obj = App.ActiveDocument.addObject("Part::Box","Box")
App.ActiveDocument.recompute()

# Create a vector facing down (-Z)
vec1 = App.Vector(0,0,-1)
FreeCAD.Console.PrintMessage("Vector 1: " + str(vec1) + '\n')

# The goal is to rotate the part so that the top face is on the bottom
# Basically the box must rotate 180 degrees
# Face 5 is the top and should have a normal vector equal to: (0,0,1)
vec2 = obj.Shape.Faces[5].normalAt(0,0)
FreeCAD.Console.PrintMessage("Vector 2: " + str(vec2) + '\n')

# Create a rotation object from the two vectors.
rot1 = App.Rotation(vec1,vec2)
FreeCAD.Console.PrintMessage("Rotation 1: " + str(rot1.toEuler()) + '\n')

# Get the part origin
org1 = obj.Placement.Base
FreeCAD.Console.PrintMessage("Base: " + str(org1) + '\n')

# Create the placement object
pla1 = App.Placement(org1,rot1)
FreeCAD.Console.PrintMessage("Placement 1: " + str(pla1) + '\n')

# Set the placement of the box
obj.Placement = pla1

## The box rotates sucessfully i.e. the normal of face 5 is now (0,0,-1)
FreeCAD.Console.PrintMessage("\n")

# Now rotate the box back to the original using the same methodology
vec3 = App.Vector(0,0,1)
FreeCAD.Console.PrintMessage("Vector 3: " + str(vec3) + '\n')
vec4 = obj.Shape.Faces[5].normalAt(0,0)
FreeCAD.Console.PrintMessage("Vector 4: " + str(vec4) + '\n')
rot2 = App.Rotation(vec3,vec4)
FreeCAD.Console.PrintMessage("Rotation 2: " + str(rot2.toEuler()) + '\n')
pla2 = App.Placement(org1,rot2)
FreeCAD.Console.PrintMessage("Placement 2: " + str(pla2) + '\n')
obj.Placement = pla2

## The box does not rotate because the rotation is the same as the placement???
OS: Debian GNU/Linux 7.4 (wheezy)
Platform: 64-bit
Version: 0.14.3426 (Git)
Branch: master
Hash: 380dfcff0954c1c6e683b64fe82817f0812c12c7
Python version: 2.7.3
Qt version: 4.8.2
Coin version: 3.1.3
SoQt version: 1.5.0
OCC version: 6.7.0
ickby
Veteran
Posts: 3116
Joined: Wed Oct 05, 2011 7:36 am

Re: Rotation of a part using two vectors

Post by ickby »

When you do

Code: Select all

rot1 = App.Rotation(vec1,vec2)
you get the rotation needed to get vec1 to vec2, and as you noted this is a relative rotation between the two parts as the surface vectors value depend on the objects curent rotation. So what you need to do is to add the calculated relative rotation to the current rotation of the part which can be done with

Code: Select all

#pla1 = App.Placement(org1,rot1) <-- wrong as you set the relative rotation as absolute one
pla1 = obj.Placement * App.Placement(App.Vector(0,0,0),rot1) <-- correct as you add the relative rotation to the current Placement
Alternativly you can first set the object rotation to zero and then calculate the surface vector. Then the relative rotation would be the absolute one too. But this is way less intuitive.

Note also that the relative rotation should not add an extra translation as you only want to rotate the part, not move it again. If you want to move it to make the faces realy coincident with a standart plane you can replace App.Vector(0,0,0) with the wanted translation vector.
User avatar
cblt2l
Posts: 155
Joined: Sat May 15, 2010 3:59 am

Re: Rotation of a part using two vectors

Post by cblt2l »

Thanks for the help ickby (and thanks for not suggesting quaternions :lol:)

One problem with your method though is when I change the macro per your suggestion I get:

Code: Select all

Traceback (most recent call last):
  File "/home/user/Programs/python/FreeCAD_Macros/testlog.py", line 25, in <module>
    pla1 = obj.Placement * App.Placement(App.Vector(0,0,0),rot1)
<type 'exceptions.TypeError'>: unsupported operand type(s) for *: 'Base.Placement' and 'Base.Placement'
I get this when running on my Linux machine using Freecad revision 3426 but strangely, earlier in the day I ran it on my Win 7 work PC with no problems. I don't remember the Freecad version I'm running there but I believe its somewhere in between 2k - 3k revisions. I'm running python 2.7 on both machines so don't think that's an issue.

I'm guessing something must have changed between versions.....

This is the modified test macro

Code: Select all

# Make a box
obj = App.ActiveDocument.addObject("Part::Box","Box")
App.ActiveDocument.recompute()

# Create a vector facing down (-Z)
vec1 = App.Vector(0,0,-1)
FreeCAD.Console.PrintMessage("Vector 1: " + str(vec1) + '\n')

# The goal is to rotate the part so that the top face is on the bottom
# Basically the box must rotate 180 degrees
# Face 5 is the top and should have a normal vector equal to: (0,0,1)
vec2 = obj.Shape.Faces[5].normalAt(0,0)
FreeCAD.Console.PrintMessage("Vector 2: " + str(vec2) + '\n')

# Create a rotation object from the two vectors.
rot1 = App.Rotation(vec1,vec2)
FreeCAD.Console.PrintMessage("Rotation 1: " + str(rot1.toEuler()) + '\n')

# Get the part origin
org1 = obj.Placement.Base
FreeCAD.Console.PrintMessage("Base: " + str(org1) + '\n')

# Create the placement object
#pla1 = App.Placement(org1,rot1)
pla1 = obj.Placement * App.Placement(App.Vector(0,0,0),rot1)
FreeCAD.Console.PrintMessage("Placement 1: " + str(pla1) + '\n')

# Set the placement of the box
obj.Placement = pla1

## The box rotates sucessfully i.e. the normal of face 5 is now (0,0,-1)
FreeCAD.Console.PrintMessage("\n")

# Now rotate the box back to the original using the same methodology
vec3 = App.Vector(0,0,1)
FreeCAD.Console.PrintMessage("Vector 3: " + str(vec3) + '\n')
vec4 = obj.Shape.Faces[5].normalAt(0,0)
FreeCAD.Console.PrintMessage("Vector 4: " + str(vec4) + '\n')
rot2 = App.Rotation(vec3,vec4)
FreeCAD.Console.PrintMessage("Rotation 2: " + str(rot2.toEuler()) + '\n')
#pla2 = App.Placement(org1,rot2)
pla2 = obj.Placement * App.Placement(App.Vector(0,0,0),rot2)
FreeCAD.Console.PrintMessage("Placement 2: " + str(pla2) + '\n')
obj.Placement = pla2

## The box does not rotate because the rotation is the same as the placement???
ickby
Veteran
Posts: 3116
Joined: Wed Oct 05, 2011 7:36 am

Re: Rotation of a part using two vectors

Post by ickby »

Now that is very strange. I can reproduce this on

OS: Windows 7
Platform: 32-bit
Version: 0.14.3252
Python version: 2.6.2
Qt version: 4.5.2
Coin version: 3.1.0
SoQt version: 1.4.1
OCC version: 6.5.1

There must have been a chenge in the Placement behaviour, I'm sure I have done this before. However, instead of the operator you can use the multiply function:

Code: Select all

pla1 = obj.Placement.multiply(App.Placement(App.Vector(0,0,0),rot1))
wmayer
Founder
Posts: 20245
Joined: Thu Feb 19, 2009 10:32 am
Contact:

Re: Rotation of a part using two vectors

Post by wmayer »

I don't think that we have ever implemented the number protocol for placements and rotations (i.e. quaternions :D)

Code: Select all

p=App.Placement()
p*p # error
r=App.Rotation()
r*r # error
m=App.Matrix()
m*m # ok
v=App.Vector()
v*v # ok
Tested with 0.13.1828.
ickby
Veteran
Posts: 3116
Joined: Wed Oct 05, 2011 7:36 am

Re: Rotation of a part using two vectors

Post by ickby »

ok, than I may have mixed up the diffrent languages and API's :)
User avatar
cblt2l
Posts: 155
Joined: Sat May 15, 2010 3:59 am

Re: Rotation of a part using two vectors

Post by cblt2l »

Ok, I changed it to Placement.multiply and everything worked fine in the test macro. I then changed my original macro that I described in the first post and tested it but noticed that there are a few instances where the rotation is off by 180 or 90 degrees. Here is the final macro:

Code: Select all

import FreeCAD, FreeCADGui
from FreeCAD import Vector, Rotation, Placement, Console

# Get the selected object
obj = FreeCADGui.Selection.getSelection()[0]
Console.PrintMessage("Part: " + str(obj.Label) + '\n')

# Get the selected face
selface = FreeCADGui.Selection.getSelectionEx()[0].SubObjects[0]

# Get normal vector of face
nv = selface.normalAt(0,0)

# Get the number of the selected face
faces = obj.Shape.Faces
for num,face in enumerate(faces):
	nv2 = face.normalAt(0,0)
	if abs(nv2.x - nv.x) < .00001 and abs(nv2.y - nv.y) < .00001 and abs(nv2.z - nv.z) < .00001:
		Console.PrintMessage("Face number " + str(num) + " is selected \n")
		Console.PrintMessage("Normal Vector: " + str(nv) + '\n')

# Align selected face normal to Z
zv = Vector(0,0,-1)
Console.PrintMessage("Z Vector: " + str(zv) + '\n')

# Create a rotation object using the normal vector of the selected face & -Z vector
rot1 = Rotation(nv, zv)
Console.PrintMessage("Rotation: " + str(rot1.toEuler()) + '\n')

# Base point of object
pos = obj.Placement.Base
Console.PrintMessage("Base: " + str(pos) + '\n')

# Get the placement of the selected object
objpl = obj.Placement
Console.PrintMessage("Selected Object Placement: " + str(objpl) + '\n')

# Calculate placement object and add it to the original placement of the part
#pla = Placement(pos,rot)
pla = objpl.multiply(App.Placement(pos,rot1))
Console.PrintMessage("Calculated Placement: " + str(pla) + '\n')

# Set the placement of the part equal to the calculated placement
obj.Placement = pla

Console.PrintMessage('\n')
Here is a case where I ran the macro on a test cube and the rotation was off 90 degrees (i.e. the normal vector of the the selected face was (0,-1,0) and it was rotated to (1,0,0) instead of (0,0,-1)). I believe it was the 5th or 6th time I had rotated the part using the macro.

Code: Select all

Part: Cut001
Face number 1 is selected 
Normal Vector: Vector (-0.0, -1.0, -0.0)
Z Vector: Vector (0.0, 0.0, -1.0)
Rotation: (0.0, -0.0, 89.99999999999999)
Base: Vector (0.0, 0.0, 0.0)
Selected Object Placement: Placement [Pos=(0,0,0), Yaw-Pitch-Roll=(180,-90,180)]
Calculated Placement: Placement [Pos=(0,0,0), Yaw-Pitch-Roll=(180,-90,-90)]
Before rotation
bad-rotation-1.png
bad-rotation-1.png (16.49 KiB) Viewed 5367 times
After rotation
bad-rotation-2.png
bad-rotation-2.png (21.36 KiB) Viewed 5367 times
If my understaning of 'Roll' is correct, the rotation object says that the part should rotate 90 degrees around the X axis which would correctly align my selected face with the XY plane. However this does not happen. The part instead rotates 90 degrees around the Z axis.

Another example:

Code: Select all

# Make a box
>>>obj = App.ActiveDocument.addObject("Part::Box","Box")
>>>App.ActiveDocument.recompute()

# Set the placement of the box
>>> pla = App.Placement(obj.Placement.Base, App.Rotation(180,-90,180))
>>> obj.Placement = pla

# Try to rotate the part around X axis 
>>> pla = obj.Placement.multiply(App.Placement(obj.Placement.Base, App.Rotation(0,0,90)))
>>> obj.Placement = pla
>>> print pla
Placement [Pos=(0,0,0), Yaw-Pitch-Roll=(180,-90,-90)] # <--  Part is incorrectly rotated around Z 

# Try again using 270 instead of 90
>>> pla = App.Placement(obj.Placement.Base, App.Rotation(180,-90,180))
>>> obj.Placement = pla
>>> pla = App.Placement(obj.Placement.Base, App.Rotation(180,-90,270))
>>> obj.Placement = pla
>>> print pla
Placement [Pos=(0,0,0), Yaw-Pitch-Roll=(180,-90,-90)] # <--  Part is incorrectly rotated around Z 
I guess I am stumped as to why this happens. It's obviously not a rounding issue but rather something in Placement that rotates the part the wrong way. :?
DavidCAD
Posts: 10
Joined: Fri Feb 06, 2015 10:34 pm

Re: Rotation of a part using two vectors

Post by DavidCAD »

This is my first post and although the topic is almost a year old, I thought I might chime in on this because I have been struggling with rotation myself and figured out some things. It might be helpful to others. If you think of Yaw, Pitch and Roll in terms of an airplane sitting on the ground pointing down the x axis with the Z axis up, Yaw rotates around the Z axis, Pitch rotates around the Y axis and Roll rotates around the X axis. In the air, however it changes. From the interior of the plane, Yaw, Pitch and Roll seem the same. But relative to the XY plane of the ground, their effect changes. If you think of a plane flying straight up, Roll rotates around the Z axis because the plane's orientation to the ground is different. If X axis points North and Y axis points West and the plane is flying West, Roll rotates around the Y axis. So the three planes of rotation affect each other relative to the X,Y,Z axis. In your script your first rotation was

pla = App.Placement(obj.Placement.Base, App.Rotation(180,-90,180))

If you think of the plane sitting on the ground pointing down the X axis, Yaw rotates it 180 so it is facing backwards, Pitch rotates it -90 so it is pointing down into the ground and Roll rotates it 180 around the Z axis. The result is the same as if you had just done Pitch of -90. The airplane is pointing straight down. If you then rotate it 90 degrees using Roll, it does correctly rotate around the Z axis relative to the ground.

# Try to rotate the part around X axis
>>> pla = obj.Placement.multiply(App.Placement(obj.Placement.Base, App.Rotation(0,0,90)))

As shown, rotation in 3D space can be complex. You can only think of which axis Yaw, Pitch and Roll rotate around in terms of the interior of the plane (or other object) or the plane on the ground in its original orientation. Once you rotate something, the relative effect of Yaw, Pitch and Roll change relative to the ground ( and X,Y,Z axis). Two vectors can be combined to point the airplane (or other object) any direction in 3D space. I decided to simplify my rotation by specifying only Yaw and Pitch. However, I found that when I used the GUI and adjusted only Yaw and Pitch, after I accepted it and I went back to check, the program had stored different combinations of Yaw, Pitch and Roll. The effect was the same but the program changed the proportions. I also found the program did the math and combined the transform and center into a single transform offset with no numbers in the centering area. Again the effect was the same but the numbers were different.

There are a couple of tutorials that I learned from, but most of what I learned I learned from creating an airplane like object and playing with it. In the GUI you can use the up down arrow keys to change the settings in real time and see how the object is affected.

http://www.freecadweb.org/wiki/index.ph ... =Aeroplane
http://www.freecadweb.org/wiki/index.ph ... =Placement

FreeCAD is a great and very powerful program with a steep learning curve. It always seems to take me much more time than I think it will but I keep learning. :D
Post Reply