Source code for trip_kinematics.Transformation
from typing import Dict, List
from copy import deepcopy
from trip_kinematics.Utility import hom_translation_matrix, x_axis_rotation_matrix
from trip_kinematics.Utility import y_axis_rotation_matrix, z_axis_rotation_matrix
from trip_kinematics.Utility import quat_rotation_matrix
import trip_kinematics.KinematicGroup
[docs]def array_find(arr, obj) -> int:
"""A helpher function which finds the index of an object in an array.
Instead of throwing an error when no index can be found it returns -1.
Args:
arr : the array to be searched
obj : the object whose index is to be found.
Returns:
int: The index of obj in the array. -1 if the object is not in the array
"""
index = -1
try:
index = arr.index(obj)
return index
except:
return -1
[docs]class Transformation():
"""Initializes the :py:class:`Transformation` class.
Args:
name (str): The unique name identifying the Transformation.
No two :py:class:`Transformation` objects of a :py:class`Robot`
should have the same name
values (Dict[str, float]): A parametric description of the transformation.
state_variables (List[str], optional): This list describes which state variables are
dynamically changable.
This is the case if the :py:class:`Transformation`
represents a joint. Defaults to [].
Raises:
ValueError: A dynamic state was declared that does not
correspond to a parameter declared in ``values``.
"""
[docs] @staticmethod
def get_convention(state: Dict[str, float]):
"""Returns the connvention which describes how the matrix
of a :py:class:`Transformation` is build from its state.
Args:
state (Dict[str, float]): :py:attr:'state'
Raises:
ValueError: "Invalid key." If the dictionary kontains keys that dont
correspond to a parameter of the transformation.
ValueError: "State can't have euler angles and quaternions!"
If the dictionary contains keys correspondig
to multiple mutually exclusive conventions.
Returns:
[type]: A string describing the convention
"""
valid_keys = ['tx', 'ty', 'tz', 'qw', 'qx',
'qy', 'qz', 'rx', 'rz', 'ry']
quaternion_keys = valid_keys[3:7]
euler_keys = valid_keys[7:]
got_quaternion = False
got_euler = False
for key in state:
if array_find(valid_keys, key) == -1:
raise ValueError(
"Invalid key, acceptable keys are: "+str(valid_keys))
if array_find(quaternion_keys, key) >= 0:
got_quaternion = True
if array_find(euler_keys, key) >= 0:
got_euler = True
if got_euler and got_quaternion:
raise ValueError("State can't have euler angles and quaternions!")
if got_euler:
return "euler"
return "quaternion"
def __init__(self, name: str, values: Dict[str, float],
state_variables: List[str] = None, parent=None):
self._name = name
self.children = []
if parent is None:
self.parent = name
elif isinstance(parent, (trip_kinematics.KinematicGroup, Transformation)):
self.parent = str(parent)
parent.add_children(name)
else:
raise TypeError(
"The parent of a Transformation must be either " +
"a KinematicGroup or a Transformation")
if state_variables is None:
state_variables = []
if not set(state_variables) <= set(values.keys()):
raise ValueError(
"Element of state_variables do not correspond to key(s) of values dictionary." +
" This can happen if both follow different conventions")
constants = {}
state = {}
self.convention = Transformation.get_convention(values)
for key in values:
if array_find(state_variables, key) != -1:
state.setdefault(key, values.get(key))
else:
constants.setdefault(key, values.get(key))
self._state = state
self._constants = constants
def __str__(self):
return self._name
[docs] def get_state(self):
"""Returns a copy of the :py:attr:`_state`
attribute of the :py:class:`Transformation` object.
Returns:
Dict[str,float]: a copy of the :py:attr:`_state`
"""
return deepcopy(self._state)
[docs] def set_state(self, state: Dict[str, float]):
"""Sets the state of the :py:class:`Transformation` object.
Args:
state (Dict[str, float]): Dictionary with states that should be set.
Does not have to be the full state.
Raises:
KeyError: If a key in the argument is not valid state parameter name.
"""
for key in state:
if not key in self._state:
raise KeyError(
"The specified keys is not part of the Transformations state." +
"Maybe the Transofmration uses a different convention?")
self._state[key] = state[key]
[docs] def get_name(self):
"""Returns the :py:attr:`_name` of the :py:class:`Transformation`
Returns:
str: a copy the :py:attr:`_name` attribute
"""
return deepcopy(self._name)
[docs] def get_transformation_matrix(self):
"""Returns a homogeneous transformation matrix build
from the :py:attr:`state` and :py:attr:`constants`
Raises:
RuntimeError: If the convention used in :py:attr:`state` is not supported.
Should normally be catched during initialization.
Returns:
[type]: A transformation matrix build using the parameters
of the :py:class:`Transformation` :py:attr:`state`
"""
# collect transformation parameters from state and constants respectively
t_x = self._constants.get(
'tx') if 'tx' in self._constants else self._state.get('tx', 0)
t_y = self._constants.get(
'ty') if 'ty' in self._constants else self._state.get('ty', 0)
t_z = self._constants.get(
'tz') if 'tz' in self._constants else self._state.get('tz', 0)
matrix = hom_translation_matrix(t_x, t_y, t_z)
if self.convention == 'euler':
r_x = self._constants.get(
'rx') if 'rx' in self._constants else self._state.get('rx', 0)
r_y = self._constants.get(
'ry') if 'ry' in self._constants else self._state.get('ry', 0)
r_z = self._constants.get(
'rz') if 'rz' in self._constants else self._state.get('rz', 0)
matrix[:3, :3] = x_axis_rotation_matrix(
r_x) @ y_axis_rotation_matrix(r_y) @ z_axis_rotation_matrix(r_z)
elif self.convention == 'quaternion':
q_w = self._constants.get(
'qw') if 'qw' in self._constants else self._state.get('qw', 0)
q_x = self._constants.get(
'qx') if 'qx' in self._constants else self._state.get('qx', 0)
q_y = self._constants.get(
'qy') if 'qy' in self._constants else self._state.get('qy', 0)
q_z = self._constants.get(
'qz') if 'qz' in self._constants else self._state.get('qz', 0)
matrix[:3, :3] = quat_rotation_matrix(q_w, q_x, q_y, q_z)
else:
raise RuntimeError("No Convention. " +
"This should normally be catched during initialization. " +
"Did you retroactively change the keys of the Transformation state?")
return matrix
[docs] def add_children(self, child: str):
"""Adds the name of a :py:class:`KinematicGroup` or :py:class:`Transformation`
as a child.
Args:
child (str): the name of a :py:class:`KinematicGroup` or :py:class:`Transformation`
"""
self.children.append(child)