Kinematics · repose-kinematics

Joint values & limits

A joint value in Repose always carries its unit. A revolute joint has an angle, a prismatic joint has a length, and the type system keeps them apart so a value can never be read in the wrong unit.

Positions: CuJointPosition

rust
pub enum CuJointPosition {
    Revolute(Angle),
    Prismatic(Length),
    Fixed,
}

Build one with .into() from an Angle or Length. Read it back with angle() or displacement(), which return an error when the variant does not match, or with si_value() for the raw radians or metres (0.0 for Fixed).

rust
let q: CuJointPosition = Angle::new::<radian>(0.5).into();
let a = q.angle()?;                       // Ok(0.5 rad)
let d = q.displacement();                 // Err: expected prismatic coordinate
let mid = q.interpolate(other, 0.5)?;     // same-kind linear interpolation

A configuration of the whole arm is [CuJointPosition; N]. Every chain method checks that the variant in slot i matches joint i's type before doing any maths.

Velocity, acceleration, jerk

The derivatives follow the same pattern:

CuJointVelocityRevolute(AngularVelocity) (rad/s) or Prismatic(Velocity) (m/s).
CuJointAccelerationrad/s² or m/s².
CuJointJerkrad/s³ or m/s³.

Each has from_coordinate(position, value), which builds the matching variant for a joint from a raw SI number, and si_value(). Trajectory limits and the controller use these types, so per-joint limits on a mixed arm stay correct.

In RON

Enums are snake_case, keyed by variant:

ron
"goal": [{"revolute": 0.0}, {"revolute": 0.4}, {"prismatic": 0.12}],
"max_velocities": [{"revolute": 0.8}, {"revolute": 0.8}, {"prismatic": 0.2}],

Templates are the one deliberate exception: a robot program's Step values are plain degrees and metres for readability, and the runner converts them.

Limits: CuJointLimits

Revolute { lower, upper, max_velocity }Bounded rotation in radians.
Prismatic { lower, upper, max_velocity }Bounded travel in metres.
Continuous { max_velocity }Unbounded rotation. lower()/upper() return ±π as the sampling interval for planners.
FixedNo motion.

Helpers: CuJointLimits::revolute(..)/prismatic(..) constructors, contains(q), clamp(q) and validate(q). Lower must not exceed upper and velocities must be positive and finite; chain.validate() checks all of it.

ron
"limits": {"revolute": {"lower": -3.12, "upper": 3.12, "max_velocity": 2.16}}
"limits": {"continuous": {"max_velocity": 3.0}}

Comparing radians with metres: CuJointMetric

Planners need one number for "how far apart are two configurations", but a mixed arm has radians and metres. CuJointMetric defines how much of each counts as one unit of distance:

rust
pub struct CuJointMetric { pub angular_scale: Angle, pub linear_scale: Length }
// default: 1 rad and 1 m both count as 1.0

metric.normalized(q) divides a value by its scale. Planner parameters like step_size, connection_radius or resolution are measured in these normalised units, and path cost is the L2 length in them. For an all-revolute arm with the default metric, normalised units are simply radians.

If your arm has a long prismatic axis, raise linear_scale (for example to 0.1 m) so that 10 cm of travel weighs as much as 1 rad of rotation.

Collision sampling: CuJointResolution

Collision checks between two configurations are discrete samples. CuJointResolution sets the largest step between samples per joint kind:

ron
"collision_check_step": {"angular": 0.08, "linear": 0.01}   // default: 0.08 rad, 1 cm

Smaller is safer and slower. Thin obstacles or long links (where a small joint step moves the tool a lot) need a finer resolution. resolution.metric() turns it into a CuJointMetric.

API reference