A guide on understanding how rotations work in FreeCAD.
Understanding a rotation about one axis is simple to understand.
This guide focuses on rotating about more than one axis.
Install FreeCAD 19.2.
-
Start FreeCAD.
-
Select Part workbench from workbench dropdown.
-
Click "Create a cone solid" button on toolbar.
-
Set the
Radius1property of the Cone to0.00 mm. -
Select View > Toggle axis cross (
A,C).- Red, Green, and Blue represents X, Y, and Z axes respectively.
-
With Cone selected, select Edit > Placement from the top main file menu.
-
Select "Euler Angles (xy'z")" from the dropdown under "Rotation".
- Positive rotations are clockwise when viewed from the Origin along an axis — or counter-clockwise when viewed towards the Origin (see Right-hand rule).
- Also see, explanation of xy'z" notation.
- x-y'-z" (intrinsic rotations) or z-y-x (extrinsic rotations).
-
Enter 90° around x-axis.
-
and 90° around z-axis.
-
Click the OK button.
-
The
Angleis 120°, andAxisis (0.58, 0.58, 0.58).
- But how is this calcuated?
Euler angles combine a series of rotations around X, Y, and Z axes into a single rotation about one axis.
A rotation about Z, Y, and X axes is also know as a rotation about Yaw, Pitch, and Roll axes (from aircraft axes).
| Yaw (Z) |
|---|
| Pitch (Y) |
|---|
| Roll (X) |
|---|
GIF Source: FreeCAD Wiki: Position and Yaw, Pitch and Roll.
The order of multiplication is Yaw, Pitch, Roll.¹
Ensure View > Panels > Python console is checked.
We can calculate the Angle and Axis vector and using FreeCAD.
>>> from FreeCAD import Rotation, Vector
>>> yaw = Rotation(Vector(0, 0, 1), 90)
>>> roll = Rotation(Vector(1, 0, 0), 90)
>>> rotation = yaw.multiply(roll)
>>> rotation.Axis
Vector (0.5773502691896258, 0.5773502691896256, 0.5773502691896258)
>>> from math import degrees
>>> degrees(rotation.Angle)
119.99999999999999How does FreeCAD caculate this though?
The following formula doesn't work if the rotation matrix is symmetric! For example (-90, 0, 180) in (yaw, pitch, roll) or (z, y, x).
Yaw(θ)
┌ cos(θ) -sin(θ) 0 ┐
│ sin(θ) cos(θ) 0 │
└ 0 0 1 ┘
Pitch(θ)
┌ cos(θ) 0 sin(θ) ┐
│ 0 1 0 │
└ -sin(θ) 0 cos(θ) ┘
Roll(θ)
┌ 1 0 0 ┐
│ 0 cos(θ) -sin(θ) │
└ 0 sin(θ) cos(θ) ┘
Using these elemental rotation matrices, we substitute our angle for θ, for each corresponding axis.
Yaw(90)
┌ cos(90) -sin(90) 0 ┐
│ sin(90) cos(90) 0 │
└ 0 0 1 ┘
Roll(90)
┌ 1 0 0 ┐
│ 0 cos(90) -sin(90) │
└ 0 sin(90) cos(90) ┘
Then we evaluate sin(90) and cos(90), which results in 1 and 0 respectively.
Yaw(90)
0 -1 0
1 0 0
0 0 1
Roll(90)
1 0 0
0 0 -1
0 1 0
Finally, we multiply Yaw(90) with Roll(90) using Matrix multiplication and WolframAlpha (individual steps not shown).
0 0 1
1 0 0
0 1 0
We can verify these with a print_matrix function.
from FreeCAD import Matrix
def print_matrix(matrix: Matrix, precision=2, width=5) -> None:
translation_vector = [matrix.A14, matrix.A24, matrix.A34]
has_translation = all(translation_vector)
num_dimensions = 4 if has_translation else 3
for i in range(1, num_dimensions + 1):
for j in range(1, num_dimensions + 1):
attr = 'A' + str(i) + str(j)
# + 0 to format -0 as positive 0.
value = round(getattr(matrix, attr), ndigits=precision) + 0
print("{:>{width}}".format(value, width=width), end='')
print()>>> print_matrix(yaw.toMatrix(), precision=None)
0 -1 0
1 0 0
0 0 1
>>> print_matrix(roll.toMatrix(), precision=None)
1 0 0
0 0 -1
0 1 0
>>> print_matrix(rotation.toMatrix(), precision=None)
0 0 1
1 0 0
0 1 0The Angle, θ, can be calcuated by using the following formula.²
θ = arccos(tr(R) - 1 / 2)
Where R is the rotation matrix above.
tr(R) means calculate the trace of R which is the sum of the elements on the main diagonal.
tr(R) = 0 + 0 + 0 = 0
Substituting 0 for tr(R) results in the following simplified formula.
θ = arccos(-1/2)
We can then use Python to calculate theta for us.
>>> from math import degrees, acos
>>> theta = acos(-1/2)
>>> degrees(theta)
120.00000000000001Where R is the rotation matrix above.
┌ R₁₁ R₁₂ R₁₃ ┐
R = │ R₂₁ R₂₂ R₂₃ │
└ R₃₁ R₃₂ R₃₃ ┘
Substitute our values in.
┌ 0 0 1 ┐
R = │ 1 0 0 │
└ 0 1 0 ┘
A vector u is computed using the following.
┌ R₃₂ - R₂₃ ┐
u = │ R₁₃ - R₃₁ │
└ R₂₁ - R₁₂ ┘
Substitute our values in
┌ 1 - 0 ┐
u = │ 1 - 0 │
└ 1 - 0 ┘
Complete the calculation.
┌ 1 ┐
u = │ 1 │
└ 1 ┘
We then normalize the axis vector u from above to calculate the Axis vector.²
w = (1 / 2 * sin(θ)) * u
In python.
>>> from math import sin, acos
>>> 1 / (2 * sin(theta))
0.5773502691896258
>>> u = Vector(1, 1, 1)
>>> 1 / (2 * sin(theta)) * u
Vector (0.5773502691896258, 0.5773502691896258, 0.5773502691896258)The following matrix product uses the following nomenclature:
1,2,3subscripts represent the anglesα,βandγ(i.e. the angles corresponding to the first, second and third elemental rotations respectively).X,Y,Zare the matrices representing the elemental rotations about the axes x, y, z (e.g.Z₁represents a rotation about z by an angle α).sandcrepresent sine and cosine (e.g.s₁represents the sine of α).
┌ c₁c₂ c₁s₂s₃ - c₃s₁ s₁s₃ + c₁c₃s₂ ┐
Z₁Y₂X₃ = │ c₂s₁ c₁c₃ + s₁s₂s₃ c₃s₁s₂ - c₁s₃ │
└ -s₂ c₂s₃ c₂c₃ ┘
from math import cos, radians, sin
from typing import Tuple
def euler_to_quaternion(yaw: float,
pitch: float,
roll: float) -> Tuple[float, float, float, float]:
"""
Convert Euler angles (in degrees) to quaternion form:
q0 = x, q1 = y, q2 = z and q3 = w
where the quaternion is specified by q = w + xi + yj + zk.
See:
https://github.com/FreeCAD/FreeCAD/blob/0.19.2/src/Base/Rotation.cpp#L632-L658
https://en.wikipedia.org/wiki/Quaternion
"""
y = radians(yaw)
p = radians(pitch)
r = radians(roll)
c1 = cos(y / 2.0)
s1 = sin(y / 2.0)
c2 = cos(p / 2.0)
s2 = sin(p / 2.0)
c3 = cos(r / 2.0)
s3 = sin(r / 2.0)
qx = (c1 * c2 * s3) - (s1 * s2 * c3)
qy = (c1 * s2 * c3) + (s1 * c2 * s3)
qz = (s1 * c2 * c3) - (c1 * s2 * s3)
qw = (c1 * c2 * c3) + (s1 * s2 * s3)
return (qx, qy, qz, qw)>>> euler_to_quaternion(-90, 0, 180)
(0.7071067811865476, -0.7071067811865475, -4.329780281177466e-17, 4.329780281177467e-17)from math import acos, degrees, sqrt
from typing import Tuple
def quaternion_to_axis_angle(quaternion: Tuple[float, float, float, float]) -> Tuple[Tuple[float, float, float], float]:
"""
Convert quaternion to axis-angle form.
Axis-angle is a two-element tuple where
the first element is the axis vector (x, y, z),
and the second element is the angle in degrees.
See:
https://github.com/FreeCAD/FreeCAD/blob/0.19.2/src/Base/Rotation.cpp#L119-L140
https://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm
"""
qx, qy, qz, qw = quaternion
s = sqrt(1 - qw**2)
normalization_factor = 1 if s < 0.001 else s
x = qx / normalization_factor
y = qy / normalization_factor
z = qz / normalization_factor
axis = (x, y, z)
angle = degrees(2 * acos(qw))
return (axis, angle)Source: https://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm