Kinematics¶
tesseract_robotics provides forward and inverse kinematics through kinematic groups defined in the SRDF. The high-level Robot wrapper exposes them as robot.fk(...) / robot.ik(...).
Kinematic Groups¶
A kinematic group defines a chain of joints for FK/IK calculations:
<!-- In robot.srdf -->
<group name="manipulator">
<chain base_link="base_link" tip_link="tool0"/>
</group>
Getting a Kinematic Group¶
from tesseract_robotics.planning import Robot
robot = Robot.from_tesseract_support("abb_irb2400")
manip = robot.env.getKinematicGroup("manipulator")
# Group info
print(f"Joint names: {list(manip.getJointNames())}")
print(f"Link names: {list(manip.getLinkNames())}")
print(f"Base link: {manip.getBaseLinkName()}")
print(f"Tip link(s): {list(manip.getTipLinkNames())}")
Forward Kinematics¶
Compute a link's pose from joint values.
import numpy as np
joints = np.array([0.0, -0.5, 0.5, 0.0, 0.5, 0.0])
# group_name is the FIRST positional argument;
# tip_link defaults to the last active link in the chain.
tcp_pose = robot.fk("manipulator", joints)
print(f"Position: ({tcp_pose.x}, {tcp_pose.y}, {tcp_pose.z})")
print(f"Quaternion: ({tcp_pose.qx}, {tcp_pose.qy}, {tcp_pose.qz}, {tcp_pose.qw})")
Quaternion convention — scalar-last project-wide
Project canonical quaternion order is [qx, qy, qz, qw] (scalar-last).
Pose.qx/qy/qz/qw, Pose.quaternion, Quaterniond.from_xyzw,
q.coeffs(), and every factory in tesseract_common follow this.
The Eigen-native Quaterniond(w, x, y, z) 4-double ctor exists for
interop but is the one exception — prefer Quaterniond.from_xyzw.
Pose vs Isometry3d¶
Pose is a subclass of Isometry3d (see planning/transforms.py). Every Pose IS an Isometry3d, so any C++ binding that accepts Isometry3d accepts Pose directly — no conversion step.
from tesseract_robotics.planning import Pose
from tesseract_robotics.tesseract_common import Isometry3d
import numpy as np
pose = Pose.from_xyz_rpy(0.5, 0.0, 0.3, 0, 0, np.pi / 4)
assert isinstance(pose, Isometry3d) # subclass relationship
# Need a plain Isometry3d? Pass the Pose through the Isometry3d ctor.
iso = Isometry3d(pose)
# Wrap an existing Isometry3d as a Pose (gains the ergonomic factories +
# @-operator composition).
pose2 = Pose(iso)
# Composition (Pose uses Python's @ operator)
combined = pose @ pose2
# Low-level composition (Isometry3d uses *)
iso_combined = iso * iso
Inverse Kinematics¶
Find joint values that achieve a target pose.
from tesseract_robotics.planning import Pose
target = Pose.from_xyz_rpy(0.6, 0.0, 0.5, 0.0, np.pi, 0.0)
# Returns a single np.ndarray solution, None, or a list if all_solutions=True
solution = robot.ik("manipulator", target, seed=joints)
if solution is not None:
print(f"Solution: {solution}")
else:
print("No IK solution found")
# Enumerate all solutions (e.g. redundant 7-DOF arms)
solutions = robot.ik("manipulator", target, seed=joints, all_solutions=True)
if solutions:
print(f"Found {len(solutions)} solution(s)")
The full signature is robot.ik(group_name, target_pose, seed=None, tip_link=None, all_solutions=False) — see planning/core.py.
from tesseract_robotics.tesseract_kinematics import KinGroupIKInput
# Pose subclasses Isometry3d, so KinGroupIKInput accepts it directly.
working_frame = manip.getBaseLinkName()
tip_link = list(manip.getActiveLinkNames())[-1]
ik_input = KinGroupIKInput(target, working_frame, tip_link)
seed = np.zeros(6)
solutions = manip.calcInvKin(ik_input, seed) # list[np.ndarray]
IK may return None
robot.ik(...) returns None when:
- the target is outside the workspace,
- no IK solver is configured in the SRDF,
- the solver hit its iteration limit.
IK Solvers¶
| Solver | Type | Robots |
|---|---|---|
| KDL | Numerical | Any serial chain |
| OPW | Analytical | 6-DOF industrial arms (ABB, KUKA, Fanuc) |
| UR | Analytical | Universal Robots |
Configure the solver in the SRDF:
<kinematics_plugin_config>
<group name="manipulator">
<plugin name="KDLInvKin"/>
</group>
</kinematics_plugin_config>
Joint Limits¶
limits = manip.getLimits()
# Position limits: 2-column matrix [lower, upper] per joint
pos_limits = limits.joint_limits
print(f"Joint 1: [{pos_limits[0, 0]}, {pos_limits[0, 1]}]")
# Velocity / acceleration limits (same shape)
vel_limits = limits.velocity_limits
acc_limits = limits.acceleration_limits
Robot.get_joint_limits("manipulator") returns the same information as a {joint_name: {lower, upper, velocity, acceleration}} dict.
Checking Limits¶
def is_within_limits(joints: np.ndarray, manip) -> bool:
limits = manip.getLimits()
lower = limits.joint_limits[:, 0]
upper = limits.joint_limits[:, 1]
return bool(np.all(joints >= lower) and np.all(joints <= upper))
Jacobian¶
The Jacobian relates joint velocities to end-effector velocities:
# 6xN Jacobian matrix at the named link
jacobian = manip.calcJacobian(joints, "tool0")
joint_velocities = np.array([0.1, 0.1, 0.1, 0.1, 0.1, 0.1])
tcp_velocity = jacobian @ joint_velocities # 6D twist [vx, vy, vz, wx, wy, wz]
Singularity Detection¶
def manipulability(jacobian: np.ndarray) -> float:
"""Yoshikawa manipulability index."""
return float(np.sqrt(np.linalg.det(jacobian @ jacobian.T)))
def is_near_singularity(jacobian: np.ndarray, threshold: float = 0.01) -> bool:
return manipulability(jacobian) < threshold
Tool Frames¶
Define Tool Center Point (TCP) offsets relative to the flange:
from tesseract_robotics.planning import Pose
# Gripper extends 15 cm from the flange
tool_offset = Pose.from_xyz(0.0, 0.0, 0.15)
# FK to flange, then apply the tool offset
flange_pose = robot.fk("manipulator", joints)
tcp_pose = flange_pose @ tool_offset
# For IK, transform the target back into the flange frame
target_flange = target_tcp @ tool_offset.inverse()
solution = robot.ik("manipulator", target_flange)
Working with Multiple Groups¶
Some robots have multiple kinematic groups:
# Dual-arm robot
left_arm = robot.env.getKinematicGroup("left_arm")
right_arm = robot.env.getKinematicGroup("right_arm")
# Manipulator + external positioner
full_chain = robot.env.getKinematicGroup("manipulator_with_positioner")
Performance Tips¶
Cache kinematic groups
Creating a kinematic group walks the SRDF. Cache the group if you're looping over many configurations:
Prefer analytical IK when available
OPW is dramatically faster than KDL for supported 6-DOF arms. Configure it in your SRDF for production workloads.
Next Steps¶
- Collision Detection — Check for collisions against the world
- Motion Planning — Plan collision-free trajectories