Kinematics · repose-kinematics
Chains & joints
A CuKinematicChain<N> is a list of N joints from the robot base to the flange, plus a fixed transform from the flange to the tool. It follows the URDF conventions, so values from a URDF file carry over one to one.
One joint: CuJointSpec
Each joint is described relative to the frame of the joint before it (the robot base for the first joint):
joint_type | CuJointType::Revolute (rotates), Prismatic (slides) or Fixed (a rigid offset that keeps a slot in the chain). |
origin_translation | [Length; 3]: where the joint sits in the parent frame. URDF origin xyz. |
origin_rpy | [Angle; 3]: roll, pitch, yaw of the joint frame relative to the parent. URDF origin rpy. |
axis | CuJointAxis: PosX, NegX, PosY, NegY, PosZ (default), NegZ, or Arbitrary([x, y, z]), which is normalised for you. |
limits | CuJointLimits: position bounds and maximum velocity, in the joint's own unit. See Joint values & limits. |
How the frames compose
Joint i contributes two transforms: the static origin (translation, then roll-pitch-yaw) and the motion caused by its value qi, which is a rotation about the axis for a revolute joint, a translation along the axis for a prismatic joint, and the identity for a fixed joint.
T_base→tool(q) = Π_{i=0..N-1} [ Origin_i · Motion_i(q_i) ] · ToolOffsetSo the axis is expressed in the joint's own frame, after its origin is applied. That is exactly how URDF defines it, and why a URDF import is a straight copy of numbers. CuJointSpec::origin_transform() and motion_transform(q) expose the two pieces if you need them.
Build a chain in Rust
use cu_spatial_payloads::Transform3D;
use cu29::units::si::{angle::radian, angular_velocity::radian_per_second, length::meter};
use cu29::units::si::f64::{Angle, AngularVelocity, Length};
use repose_kinematics::{CuJointAxis, CuJointLimits, CuJointSpec, CuKinematicChain};
let m = Length::new::<meter>;
let rad = Angle::new::<radian>;
let lim = |lo, hi| CuJointLimits::revolute(rad(lo), rad(hi), AngularVelocity::new::<radian_per_second>(2.0));
let chain = CuKinematicChain::new([
// Shoulder: 0.1 m above the base, rotates about +Z.
CuJointSpec::revolute([m(0.0), m(0.0), m(0.1)], [rad(0.0); 3], CuJointAxis::PosZ, lim(-3.1, 3.1)),
// Elbow: 0.3 m further up, rotates about -Y.
CuJointSpec::revolute([m(0.0), m(0.0), m(0.3)], [rad(0.0); 3], CuJointAxis::NegY, lim(-2.0, 2.0)),
])
// Tool centre point 5 cm beyond the flange.
.with_tool_offset(Transform3D::<f64>::from_translation_euler_xyz([m(0.0), m(0.0), m(0.05)], [rad(0.0); 3]));
chain.validate()?; // finite origins, valid axes, well-formed limits, finite tool offsetCuKinematicChain::new is const, so a chain can be a static. Always call validate() on chains you build yourself: it rejects non-finite values and limits whose unit doesn't match the joint type. Tasks validate their chain on startup for you.
The tool offset
The tool offset is the transform from the last joint (the flange) to the point you care about: a gripper's fingertip centre, a welding tip, a camera. Every pose Repose reports or accepts refers to that point. Change it with with_tool_offset (builder), set_tool_offset, or, in a task, with the optional tool_offset config key, which replaces the chain's own offset. The identity means "the flange itself".
Presets
Repose ships one built-in model, the igus ReBeL 6-axis cobot, derived from its URDF (fixed motor-housing offsets are folded into the revolute joints):
let chain = repose_kinematics::igus_rebel_chain(); // CuKinematicChain<6>In RON the same chain is the string "igus_rebel". CuRobotPreset lists the available presets with name() and dof(); preset.chain::<N>() fails if the preset has a different joint count than N.
Chains in RON: CuChainSpec
Every task that needs a robot has a required chain key. It accepts either a preset name or an inline chain with exactly N joints:
config: { "chain": "igus_rebel" }config: {
"chain": {
"joints": [
{
"joint_type": "revolute",
"origin_translation": [0.0, 0.0, 0.1],
"origin_rpy": [0.0, 0.0, 0.0],
"axis": "pos_z",
"limits": {"revolute": {"lower": -3.0, "upper": 3.0, "max_velocity": 1.0}},
},
],
},
}Numbers are SI: metres, radians, radians per second. Unknown keys are rejected, so a typo is a startup error. No task falls back to a default robot: if chain is missing, the task refuses to start.
For any robot other than the ReBeL, the practical route is a URDF import at startup, or generating the inline spec once from your URDF.
Limits checks on a configuration
chain.is_within_limits(&q) | true if every value has the right unit for its joint and is inside its limits. |
chain.clamp_to_limits(&mut q) | Clamps every value into its limits in place; errors on a unit mismatch. |
chain.joints() / tool_offset() | Read the specs back, e.g. to display limits or export the model. |