Rotation API Reference#
About Rotations#
Rotations are ways of moving between two reference frames. A reference frame is a set of coordinate axes, which in a vector space define the vector values for specific positions. Different systems require different reference frames for describing things inside them, and it is quite common to want to know how the same direction “looks”, or what the coordinates of the same point in space are in another reference frame.
PyEVSpace provides the means for defining reference frames relative to one another, and a set of functions for applying the rotations to a vector using these definitions.
Rotation Types#
Here we describe several variables and types that allows us to define rotations.
Axis Variables#
Axes are designated by enumerated values. These are implemented as integer constants at the module level, and represent the x, y, and z axes of any reference frame.
It is valid to use integer literals in place of one of these variables, which may be useful for dynamically selecting axes within a program. It is still recommended to use the above variables when possible so that intent of code is more explicit.
RotationOrder Type#
- class pyevspace.RotationOrder(first: int, second: int, third: int)#
For muli-axis rotations, namely Euler rotations, the RotationOrder type is used to declare which, and in what order individual rotations are applied. For example, a rotation which should first revolve around the x-axis, then the y-axis, and finally the z-axis would use the
XYZrotation order variable. PyEVSpace defines the twelve common rotation orders at the module level, which are all RotationOrder instances.While new RotationOrder variables can be instantiated, the global order instances should be prefered.
Changed in version 0.16.0: RotationOrder is now subclassable, and is no longer an immutable type, meaning class members can be directly modified or added.
- Parameters:
first – the first axis of the rotation
second – the second axis of the rotation
third – the third axis of the rotation
- Raises:
ValueError – if any argument is not in the range [0-2]
Other Constructors#
- RotationOrder.__new__(cls: type, first: int, second: int, third: int) Self#
Creates a new instance of a cls object if cls is a subclass of
RotationOrder. Initialize the axes to the remaining parameters.- Parameters:
cls – the type of the variable to create
first – the first axis of the rotation
second – the second axis of the rotation
third – the third axis of the rotation
- Raises:
TypeError – if cls is not a subtype of RotationOrder
ValueError – if any argument is not in the range [0-2]
Changed in version 0.16.0: type can be a subtype of
RotationOrder, and an object of that type will be created and returned.Changed in version 0.16.0: All initialization takes place here instead of
RotationOrder.__init__().
- RotationOrder.__init__(first: int, second: int, third: int)#
Initialize a RotationOrder to the axes parameters.
- Parameters:
first – the first axis of the rotation
second – the second axis of the rotation
third – the third axis of the rotation
- Returns:
a new RotationOrder object
- Raises:
ValueError – if any argument is not in the range [0-2]
Removed in version 0.16.0: Since the
RotationOrdertype is immutable, and this method can be called any number of times, in order to avoid manipulating the state of the object, all initialization of this type is done inRotationOrder.__new__()and this method is removed.
Subclassing RotationOrder#
As of version 0.16.0 the RotationOrder class supports subclassing. As the
RotationOrder class is implemented as a C struct, it has a specific memory
layout, and as a consequence of that it is a @distjoint-base. This
means RotationOrder cannot be combined with any other types whose layout
is defined as a C struct when inheriting, which most Python built-in types
such as list or tuple are. For example combining
RotationOrder and list as base classes will raise a
TypeError:
>>> class Foo(RotationOrder, list):
... pass
...
TypeError: multiple bases have instance lay-out conflict
This limitation does not apply when combining a @distjoin-base with a
pure python class, and of course is not an issue if RotationOrder is the
only base class for a derived type.
Instance Methods#
- RotationOrder.__len__() int#
Returns the length of the container, which is always 3.
- Returns:
this method always returns 3
- RotationOrder.__getitem__(index: int) int#
Returns the axis of the order depending on index.
- Parameters:
index – the index of the axis to retrieve
- Returns:
the axis at index
- Raises:
IndexError – if index is not an
inttype or doesn’t provide__index__()ValueError – if index is not in the range [0-2] (after adjusting for negative indexing)
- RotationOrder.__repr__() str#
Returns a string representation of the order representative of a constructor call.
- Returns:
a string representation of self
Changed in version 0.16.0: The method now prepends the output with the module name for better scope resolution support when using the output with
exec().
- RotationOrder.__str__() str#
Returns a string representation of the order that looks similar to a
listof axis values.- Returns:
a string representation of self
- RotationOrder.__hash__() int#
Because RotationOrder isntances are immutable, they can be hashed to support use in situations that require hashable types.
- Returns:
a hash of the rotation order
- RotationOrder.__reduce__()#
Provides support for pickling and copying. This function returns a 2-tuple containing a reference to the type constructor and a tuple of arguments to initialize an equivalent instance. For types inheriting from
RotationOrder, this function may need to be overloaded and modified to return your own type.
Iterability#
While the RotationOrder class does not define an __iter__()
method directly, the class does support iterability via the sequence protocol.
Attempting to access RotationOrder.__iter__() will raise an
AttributeError, however a RotationOrder can be used
anywhere an iterable is expected.
>>> order = RotationOrder(X_AXIS, Y_AXIS, Z_AXIS)
>>> itr = iter(order)
>>> print(list(itr))
[0, 1, 2]
>>> for i in order:
... print(i)
...
0
1
2
Logical Operators#
- RotationOrder.__eq__(other: RotationOrder) bool#
Compares the axis values of two orders for equality.
- Returns:
True if each respective axis in self and other are equal, False otherwise.
- RotationOrder.__ne__(other: RotationOrder) bool#
Compares the axis values of two orders for inequality.
- Returns:
True if any respective axis in self and other are different, False otherwise
Properties#
- property RotationOrder.first#
The first axis of rotation in the rotation order.
- Type:
- Readonly:
this value cannot be changed
- property RotationOrder.second#
The second axis of rotation in the rotation order.
- Type:
- Readonly:
this value cannot be changed
Global Variables#
- pyevspace.XYZ#
This value should not be modified!
- Type:
- Value:
RotationOrder(X_AXIS, Y_AXIS, Z_AXIS)
- pyevspace.XZY#
This value should not be modified!
- Type:
- Value:
RotationOrder(X_AXIS, Z_AXIS, Y_AXIS)
- pyevspace.YXZ#
This value should not be modified!
- Type:
- Value:
RotationOrder(Y_AXIS, X_AXIS, Z_AXIS)
- pyevspace.YZX#
This value should not be modified!
- Type:
- Value:
RotationOrder(Y_AXIS, Z_AXIS, X_AXIS)
- pyevspace.ZXY#
This value should not be modified!
- Type:
- Value:
RotationOrder(Z_AXIS, X_AXIS, Y_AXIS)
- pyevspace.ZYX#
This value should not be modified!
- Type:
- Value:
RotationOrder(Z_AXIS, Y_AXIS, X_AXIS)
- pyevspace.XYX#
This value should not be modified!
- Type:
- Value:
RotationOrder(X_AXIS, Y_AXIS, X_AXIS)
- pyevspace.XZX#
This value should not be modified!
- Type:
- Value:
RotationOrder(X_AXIS, Z_AXIS, X_AXIS)
- pyevspace.YXY#
This value should not be modified!
- Type:
- Value:
RotationOrder(Y_AXIS, X_AXIS, Y_AXIS)
- pyevspace.YZY#
This value should not be modified!
- Type:
- Value:
RotationOrder(Y_AXIS, Z_AXIS, Y_AXIS)
- pyevspace.ZXZ#
This value should not be modified!
- Type:
- Value:
RotationOrder(Z_AXIS, X_AXIS, Z_AXIS)
- pyevspace.ZYZ#
This value should not be modified!
- Type:
- Value:
RotationOrder(Z_AXIS, Y_AXIS, Z_AXIS)
EulerAngles Type#
- class pyevspace.EulerAngles#
- class pyevspace.EulerAngles(alpha: SupportsFloat, beta: SupportsFloat, gamma: SupportsFloat)
An EulerAngles object is a container class for angles, used in conjunction with the
RotationOrder`type. Each angle (alpha, beta, and gamma) describes the angle of rotation for each respective axis in aRotationOrder.The EulerAngles type is a mutable type, as it’s common for rotations to need to be continually updated for rotating reference frames.
If no parameters are provided to the constructor all values are initialized to 0.0. The constructor only accepts three or zero parameters, all unneeded angles should explicitly set to 0.0.
Changed in version 0.16.0: EulerAngles is now subclassable, and is no longer an immutable type, meaning class members can be directly modified or added.
Other Constructors#
- EulerAngles.__new__(cls: type) Self#
Creates an uninitialized cls object if cls is a subclass of
EulerAngles. Each angle defaules to 0.0.- Parameters:
cls – type of the instance to create
- Returns:
a new EulerAngles object
- Raises:
TypeError – if cls is not a subtype of
EulerAngles
Changed in version 0.16.0: type can be a subtype of
EulerAngles, and an object of that type will be created and returned.
- EulerAngles.__init__()#
- EulerAngles.__init__(alpha: SupportsFloat, beta: SupportsFloat, gamma: SupportsFloat)
Initializes an
EulerAnglesvariable to the specified angles. If no angles are provided they default to 0.0. This method only accepts three or zero parameters, all unneeded angles shoulld explicitly set to 0.0.
Subclassing EulerAngles#
As of version 0.16.0 the EulerAngles class supports subclassing. As the
EulerAngles class is implemented as a C struct, it has a specific memory
layout, and as a consequence of that it is a @distjoint-base. This
means EulerAngles cannot be combined with any other types whose layout
is defined as a C struct when inheriting, which most Python built-in types
such as list or tuple are. For example combining
EulerAngles and list as base classes will raise a
TypeError:
>>> class Foo(EulerAngles, list):
... pass
...
TypeError: multiple bases have instance lay-out conflict
This limitation does not apply when combining a @distjoin-base with a
pure python class, and of course is not an issue if EulerAngles is the
only base class for a derived type.
Instance Methods#
- EulerAngles.__len__() int#
Returns the length of the container, which is always 3.
- Returns:
this method always returns 3
- EulerAngles.__getitem__(index: int) float#
Returns the indexed angle of the container.
- Parameters:
index – the index of the angle to retrieve
- Returns:
the indexed angle
- Raises:
TypeError – if index is not an
inttype and does not provided__index__()IndexError – if index is not in the range [0-2] (after adjusting for negative indexing)
- EulerAngles.__setitem__(index: int, value: SupportsFloat) float#
Sets the angle at index to value.
- Parameters:
index – the index of the angle to set
value – the value to set the indexed angle
- Returns:
value
- Raises:
TypeError – if index is not an
inttype and does not provide__index__()TypeError – if value is not or cannot be converted to a
floatIndexError – if index is not in the range [0-2] (after adjusting for negative indexing)
- EulerAngles.__repr__() str#
Returns a string representation of self, representative of a contructor call.
- Returns:
a string representation of self
Changed in version 0.16.0: The method now prepends the output with the module name for better scope resolution support when using the output with
exec().
- EulerAngles.__str__() str#
Returns a string representation of self, similar to a
listoffloats.- Returns:
a string representation of self
- EulerAngles.__reduce__()#
- Provides support for pickling and copying. This function returns a
2-tuple containing a reference to the type constructor and a tuple of arguments to initialize an equivalent instance. For types inheriting from
EulerAngles, this function may need to be overloaded and modified to return your own type.
Iterability#
While the EulerAngles class does not define an __iter__()
method directly, the class does support iterability via the sequence protocol.
Attempting to access EulerAngles.__iter__() will raise an
AttributeError, however a EulerAngles can be used anywhere
an iterable is expected.
>>> angles = EulerAngles(1, 2, 3)
>>> itr = iter(angles)
>>> print(list(itr))
[1.0, 2.0, 3.0]
>>> for i in angles:
... print(i)
...
1.0
2.0
3.0
Explaining Relative Reference Frames#
In PyEVSpace, we need to ability to define a reference frame relative to another. In the strictest definition of the word, an inertial reference frame is a non-accelerating reference frame, either at rest or moving at a constant velocity. In this documentation we will use inertial to mean a base reference frame. Now, a reference frame that fits this description for your project may be an entirely different reference frame in another project, but they both fit this definition! Every single reference frame will be different relative to another, but as long as you’re consistent within the scope of your project, this will work.
We have one final, hopefully not too confusing point to make. The same rotation between two non-inertial reference frames, and a rotated reference frame relative to an inertial frame produces the same rotation matrix. The key takeaway here is that as we define reference frames, they are always relative to another, consistent reference frame. A reference reference frame, if you’d like.
>>> # Define reference frame A as a 90 degree rotation around
>>> # an inertial frame's x-axis:
>>> frame_a = compute_rotation_matrix(math.pi / 2, X_AXIS)
>>> # define refrence frame B as two 90 degree rotations around
>>> # the x-axis, then y-axis relative to an inertial frame:
>>> angles = EulerAngles(math.pi / 2, math.pi / 2, 0)
>>> frame_b = compute_rotation_matrix(XYZ, angles)
>>>
>>> # here, frame_b only differs by the final y-axis rotation,
>>> # therefore to define a rotation matrix moving from
>>> # frame_a -> frame_b would just be a 90 degree rotation
>>> # around the y-axis of frame_a. Knowing how these frames
>>> # differ relative to a truely inertial frame is not needed:
>>> relative_matrix = compute_rotation_matrix(math.pi / 2, Y_AXIS)
The above code snippet demonstrates that even though a reference frame may not truely be inertial, in the context of this documentation, we can call it inertial in order to define our second frame.
Generating Rotation Matrices#
Generating rotation matricies is as simple as passing the correct
arguments to compute_rotation_matrix(). Rotation matricies
can be derived from:
single axis rotations, defined by a coordinate axis or a vector
multi-axis Euler rotations, involving three successive rotations
Defining A Reference Frame#
Each reference frame is defined by the parameters passed to the
compute_rotation_matrix() (similar to the rotate_*() methods).
The reference frame is defined relative to another frame. As explained
above, you only need to define the relative rotation between the two
frames. The possible ways to define a rotation are
angle, axis (enumerated value)
angle, axis (an axis defined by a vector)
euler order, euler angles (can be intrinsic or extrinsic)
between two non-inertial reference frames using euler orders and euler angles
Any time a rotation involves only two axis rotations (instead of three), a
RotationOrder instance should be chosen where the third axis can
be any axis, and will become redundant by setting the cooresponding
EulerAngles third angle to 0.0.
Intrinsic Vs. Extrinsic#
Intrinsic and extrinsic rotations mostly apply to Euler rotations. An intrinsic rotation is a rotation whose subsequent rotation axes are applied to the rotated frame. For example, in an XYZ intrinsic rotation, the coordinate axes are rotated around the x-axis, then next rotation is applied to where the y-axis ends up due to the first rotation. Again, the final z-axis rotation is applied where the z-axis ends up after the first two rotations.
Extrinsic rotations are rotations applied to all axes in the original
inertial reference frame, not where the axes end up after preceeding
rotations. While intrinsic rotations are generally more easy to define
in real world systems, extrinsic matrices can still be computed, but
compute_rotation_matrix() defaults to intrinsic rotations.
Computing Matricies#
- pyevspace.compute_rotation_matrix(angle: SupportsFloat, axis: int | Vector) Matrix#
- pyevspace.compute_rotation_matrix(order: RotationOrder, angles: EulerAngles, *, intrinsic: bool = True) Matrix
- pyevspace.compute_rotation_matrix(order_from: RotationOrder, angles_from: EulerAngles, order_to: RotationOrder, angles_to: EulerAngles, *, intrinsic_from: bool = True, intrinsic_to: bool = True) Matrix
Computes a rotation matrix based on the parameter types and values. The first form rotates a coordinate system around an enumerated axis or a
Vectorobject. The second form produces a matrix between a reference frame defined by a set ofRotationOrderandEulerAngles. This form defaults to an intrinsic rotation, and can be controlled by the intrinsic keyword.Rotations only need to be defined from one reference frame relative to another, however in practice it may be very difficult to simplify the relative rotations. For moving from one reference frame to another, a matrix can be produced to “undo” the rotation of the from frame to an inertial one, and another matrix to move from the inertial frame to the to reference frame. These matrices can be combined into a single matrix, and is what is returned by the third form of this function.
- Parameters:
angle – angle for a single axis rotation in radians
axis – single axis rotation axis as an enumerated value or a Vector type
order – the rotation order for a simple Euler rotation
angles – the Euler angles for a simple Euler rotation
order_from – the rotation order moving from in complex Euler rotations
angles_from – the Euler angles moving from in complex Euler rotations
order_to – the rotation order moving to in complex Euler rotations
angles_from – the Euler angles moving to in complex Euler rotations
intrinsic – True (default) for an intrinsic Euler rotation, False for an extrinsic Euler rotation
intrinsic_from – True (default) for intrinsic Euler rotation moving from in complex Euler rotations, False for an extrinsic rotation
intrinsic_to – True (default) for intrinsic Euler rotation moving to in complex Euler rotations, False for an extrinsic rotation
- Returns:
the rotation matrix
- Raises:
TypeError – if any parameters are not their explicit type
ValueError – if axis is provided as an integer but is not in the range of [0-2]
Rotation Functions#
The following rotation functions abstract away the logic for applying
rotation matrices to vectors. The nomenclature is designed to simplify
the thinking of how matrix-vector multiplication needs to be applied.
The function rotate_from() rotates a vector from a rotated
reference frame to an inertial frame. The function rotate_to()
rotates a vector from an inertial reference frame to a rotated
reference frame. The key note here is the suffix describes the rotated
frame: rotate_from is from the rotated frame, rotate_to is to
the rotated frame.
For reference frames that are difficult to define relative to another,
but easier to define relative to an inertial frame, the rotate_between()
function allows you to provide both reference frame definitions, and
a single matrix is produced that will move vectors between these
reference frames.
The origin of two reference frames may also have offset origins. When
using rotate_to() and rotate_from() the offset
keyword can be used to define the offset vector, which points from
the inertial frame to the rotated frame. In the rotate_between()
function, use the offset_from or offset_to keyword
arguments to achieve the same thing for the respective reference frames.
Note
The offset keywords do not follow the to, from
nomenclature. The offset vector ALWAYS points from the inertial
reference frame’s origin to the rotated reference frame’s origin.
- pyevspace.rotate_from(matrix: Matrix, vector: Vector, *, offset: Vector | None = None) Vector#
- pyevspace.rotate_from(angle: SupportsFloat, axis: int | Vector, vector: Vector, *, offset: Vector | None = None) Vector
- pyevspace.rotate_from(order: RotationOrder, angles: EulerAngles, vector: Vector, *, intrinsic: bool = True, offset: Vector | None = None) Vector
Rotates a vector from a rotated reference frame, to an inertial reference frame. The first form takes an already produced rotation matrix and applies it to vector, accounting for any difference in the origin defined by offset. This version of the function is useful for applying the same rotation to multiple vectors, as the rotation matrix isn’t generated multiple times when using the other versions of this function.
The second form defines a rotation by a single angle rotation around an axis, which can be either an enumerated axis value or by an axis defined by a
Vectorinstance.The second form defines an Euler rotation using a
RotationOrderandEulerAngles. In this case the intrinsic keyword is used to define an intrinsic or extrinsic rotation.- Parameters:
matrix – a pre-computed rotation matrix
angle – the angle of a single axis rotation
axis – the axis of a single axis rotation
order – the order for an Euler rotation
angles – the angles for an Euler rotation
vector – the vector to rotate
offset – an optional vector that points from an inertial reference frame’s origin to the rotated reference frame’s origin
intrinsic – defines an intrinsic (default) or extrinsic Euler rotation
- Returns:
the rotated vector
- Raises:
TypeError – if any parameters are not their explicit type
ValueError – if axis is provided as an integer but is not in the range of [0-2]
- pyevspace.rotate_to(matrix: Matrix, vector: Vector, *, offset: Vector | None = None) Vector#
- pyevspace.rotate_to(angle: SupportsFloat, axis: int | Vector, vector: Vector, *, offset: Vector | None = None) Vector
- pyevspace.rotate_to(order: RotationOrder, angles: EulerAngles, vector: Vector, *, intrinsic: bool = True, offset: Vector | None = None) Vector
Rotates a vector from an inertial reference frame, to a rotated reference frame. The first form takes an already produced rotation matrix and applies it to vector, accounting for any difference in the origin defined by offset. This version of the function is useful for applying the same rotation to multiple vectors, as the rotation matrix isn’t generated multiple times when using the other versions of this function.
The second form defines a rotation by a single angle rotation around an axis, which can be either an enumerated axis value or by an axis defined by a
Vectorinstance.The second form defines an Euler rotation using a
RotationOrderandEulerAngles. In this case the intrinsic keyword is used to define an intrinsic or extrinsic rotation.- Parameters:
matrix – a pre-computed rotation matrix
angle – the angle of a single axis rotation
axis – the axis of a single axis rotation
order – the order for an Euler rotation
angles – the angles for an Euler rotation
vector – the vector to rotate
offset – an optional vector that points from an inertial reference frame’s origin to the rotated reference frame’s origin
intrinsic – defines an intrinsic (default) or extrinsic Euler rotation
- Returns:
the rotated vector
- Raises:
TypeError – if any parameters are not their explicit type
ValueError – if axis is provided as an integer but is not in the range of [0-2]
- pyevspace.rotate_between(order_from: RotationOrder, angles_from: EulerAngles, order_to: RotationOrder, angles_to: EulerAngles, vector: Vector, *, intrinsic_from: bool = True, intrinsic_to: bool = True, offset_from: Vector | None = None, offset_to: Vector | None = None) Vector#
Rotates a vector between two reference frames. Each reference frame is defined relative to an intermediate inertial frame, which may be easier to define. All parameters behave the same as the related
rotate_from()androtate_to()functions.If one of the reference frames may be a single axis rotation, it still must be defined using Euler rotation semantics. For example, for a frame defined by an angle
aaround the x-axis, use any of theRotationOrderobjects that start with an X, likeXYZ,XZY, etc. and anEulerAnglesobject with only the first angle being non-zero:EulerAngles(a, 0.0, 0.0).- Parameters:
order_from – the rotation order of the Euler rotation moving from
angles_from – the angles of the Euler rotation moving from
order_to – the rotation order of the Euler rotation moving to
angles_to – the angles of the Euler rotation moving to
vector – the vector to rotate
intrinsic_from – determines if the rotation from is intrinsic (default) or extrinsic
intrinsic_to – determines if the rotation to is intrinsic (default) or extrinsic
offset_from – offset of the from reference frame origin relative to the inertial frame (default is
None)offset_to – offset of the to reference frame origin relative to the inertial frame (default is
None)
- Returns:
the rotated vector
- Raises:
TypeError – if any parameters are not their explicit type