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.

pyevspace.X_AXIS#

This value should not be modified!

Value:

0

Type:

int

pyevspace.Y_AXIS#

This value should not be modified!

Value:

1

Type:

int

pyevspace.Z_AXIS#

This value should not be modified!

Value:

2

Type:

int

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 XYZ rotation 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:

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

  • TypeError – if any argument is not an int type

  • 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:

Removed in version 0.16.0: Since the RotationOrder type 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 in RotationOrder.__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 int type 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 list of 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.

Returns:

a tuple containing the type and internal state of self

Return type:

tuple[type, tuple[int, int, int]]

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:

int

Readonly:

this value cannot be changed

property RotationOrder.second#

The second axis of rotation in the rotation order.

Type:

int

Readonly:

this value cannot be changed

property RotationOrder.third#

The third axis of rotation in the rotation order.

Type:

int

Readonly:

this value cannot be changed

Global Variables#

pyevspace.XYZ#

This value should not be modified!

Type:

RotationOrder

Value:

RotationOrder(X_AXIS, Y_AXIS, Z_AXIS)

pyevspace.XZY#

This value should not be modified!

Type:

RotationOrder

Value:

RotationOrder(X_AXIS, Z_AXIS, Y_AXIS)

pyevspace.YXZ#

This value should not be modified!

Type:

RotationOrder

Value:

RotationOrder(Y_AXIS, X_AXIS, Z_AXIS)

pyevspace.YZX#

This value should not be modified!

Type:

RotationOrder

Value:

RotationOrder(Y_AXIS, Z_AXIS, X_AXIS)

pyevspace.ZXY#

This value should not be modified!

Type:

RotationOrder

Value:

RotationOrder(Z_AXIS, X_AXIS, Y_AXIS)

pyevspace.ZYX#

This value should not be modified!

Type:

RotationOrder

Value:

RotationOrder(Z_AXIS, Y_AXIS, X_AXIS)

pyevspace.XYX#

This value should not be modified!

Type:

RotationOrder

Value:

RotationOrder(X_AXIS, Y_AXIS, X_AXIS)

pyevspace.XZX#

This value should not be modified!

Type:

RotationOrder

Value:

RotationOrder(X_AXIS, Z_AXIS, X_AXIS)

pyevspace.YXY#

This value should not be modified!

Type:

RotationOrder

Value:

RotationOrder(Y_AXIS, X_AXIS, Y_AXIS)

pyevspace.YZY#

This value should not be modified!

Type:

RotationOrder

Value:

RotationOrder(Y_AXIS, Z_AXIS, Y_AXIS)

pyevspace.ZXZ#

This value should not be modified!

Type:

RotationOrder

Value:

RotationOrder(Z_AXIS, X_AXIS, Z_AXIS)

pyevspace.ZYZ#

This value should not be modified!

Type:

RotationOrder

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 a RotationOrder.

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.

Parameters:
  • alpha – the first angle of the rotation

  • beta – the second angle of the rotation

  • gamma – the third angle of the rotation

Raises:

TypeError – if alpha, beta, or gamma are not or cannot be converted to a float

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 EulerAngles variable 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.

Parameters:
  • alpha – the first angle of the rotation

  • beta – the second angle of the rotation

  • gamma – the third angle of the rotation

Raises:

TypeError – if alpha, beta, or gamma are not or cannot be converted to a float

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 int type 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 int type and does not provide __index__()

  • TypeError – if value is not or cannot be converted to a float

  • IndexError – 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 list of float s.

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.

Returns:

a tuple containing the type and internal state of self

Return type:

tuple[type, tuple[float, float, float]]

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 Vector object. The second form produces a matrix between a reference frame defined by a set of RotationOrder and EulerAngles. 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 Vector instance.

The second form defines an Euler rotation using a RotationOrder and EulerAngles. 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 Vector instance.

The second form defines an Euler rotation using a RotationOrder and EulerAngles. 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() and rotate_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 a around the x-axis, use any of the RotationOrder objects that start with an X, like XYZ, XZY, etc. and an EulerAngles object 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