deke-linear 2.0.0

Constant-TCP-speed Cartesian polyline following for serial manipulators.
Documentation

deke-linear

Constant-TCP-speed Cartesian polyline following for serial manipulators.

Where the deke-topp* retimers are time-optimal (maximise speed under v/a/j caps), deke-linear holds a constant TCP travel speed — the requirement for welding and similar process motions — and degrades gracefully near singularities rather than failing. It is a CNC-style constant-feedrate interpolator in three stages, the latter two of which implement the deke_types Planner / Retimer traits so they compose with the rest of the ecosystem.

Pipeline

&[DAffine3] poses + FollowConfig
   │
 [A] path::condition   → Vec<CartesianRun>      split at sharp corners; shallow
   │                                            corners smoothed (squiggle Catmull–Rom),
   │                                            arc-length parameterised
 [B] CartesianLinearPlanner : Planner           dense IK → DP branch track
   │                                            (analytic IK, manipulability-weighted,
   │                                            no Jacobian inversion → singularity-safe)
 [C] ConstantSpeedRetimer  : Retimer            constant feedrate held where the joint
   │                                            v/a/j MVC allows; smooth dips elsewhere
 LinearFollower::follow → SRobotTraj            per-run trajectories stitched at rest
  • Corners (hybrid). A vertex whose turn angle exceeds PathConditioning::sharp_corner_angle is sharp: the path splits there into runs that start and stop at rest, keeping the vertex exact. Shallower corners are smoothed by the Catmull–Rom spline and traversed without stopping.
  • forbid_interior_dips. By default the speed dips smoothly where the joint v/a/j geometry forces it (a shallow corner, a near-singular patch). Set this flag to require the commanded speed to hold flat through a run's interior — the only sub-commanded speed allowed is the rest ramp at each run's start and end. If a dip would be forced anywhere in the interior, the retime fails with SpeedDipRequired { run, s, feasible_speed, commanded } instead of slowing down.
  • Singularity tolerance. The planner inverts each pose with the chain's analytic IK (every branch, limit-filtered, no Jacobian inversion) and routes a continuous joint track that maximises manipulability √det(J·Jᵀ). The retimer's feasible-speed ceiling is min_j v_max,j / |q'_j(s)|, so as a singularity is approached the commanded speed dips smoothly to zero instead of demanding infinite joint speed.

Example

use deke_linear::{LinearFollower, FollowConfig};
use deke_types::glam::DAffine3;

let follower = LinearFollower::new(&chain);            // any ContinuousFKChain + IkSolver
let (traj, diag) = follower.follow(&poses, &cfg)?;     // poses: &[DAffine3], cfg: FollowConfig<N>

Free tool-axis yaw (functional redundancy)

A tool that is rotationally symmetric about one of its axes — a welding torch, spray head — leaves the rotation about that axis free (a 5-DOF task on a 6-DOF arm). Declaring it lets the planner spend that DOF to steer around singularities.

use deke_linear::{FollowConfig, JointLimits, RedundantAxis, RedundantOptions};
use std::time::Duration;

let cfg = FollowConfig::weld(35.0, joint_limits, Duration::from_millis(8))  // 35 in/min
    .with_redundancy(RedundantOptions {
        axis: RedundantAxis::PosZ,                 // ±X/±Y/±Z or Custom(unit vec), tool frame
        yaw_window: (-45f64.to_radians(), 45f64.to_radians()),
        ..RedundantOptions::default()
    });

Yaw is a smooth scalar, so it is gridded coarsely and resolved by a single global DP over (station) × (yaw × branch) — exact, so it finds the globally optimal yaw track in one pass. A manipulability node cost steers off singularities, a yaw-rate edge penalty keeps the spin smooth, and the velocity reconfiguration test rejects discontinuous edges; the yaw may sweep freely across the window (bounded only by per-step max_yaw_step). That coarse ψ(s) schedule is then refined at fine arc-length spacing, with analytic IK placing the arm exactly each step (predictor–corrector). The free axis is configurable because tool frames differ (this project is Z-forward; others are X-forward).

Reconfiguration by joint velocity

The planner takes a max_velocity and per-joint velocity ceilings: any edge that would drive any joint past a configurable fraction (default 90%) of its limit at that speed is treated as a reconfiguration/discontinuity and rejected. At weld speeds (20–50 IPM) a joint approaching its velocity limit is the signature of a singularity or wrist flip, so this cleanly separates "needs to slow down" from "can't be done continuously." FollowConfig::weld() wires it up; otherwise set PlannerOptions::{max_velocity, joint_v_max, reconfig_vel_fraction}.

Scope / notes

  • Orientation is a full TCP pose per vertex (slerped along the path). For a symmetric tool, declare the free axis via with_redundancy (see above) to let the planner resolve the yaw.
  • Joint velocity is enforced exactly; acceleration/jerk are enforced by the path-tangent projection plus a jerk-limited integrator. The q''(s)·ṡ² curvature cross-term is a deliberate first-pass approximation (negligible at process speeds; see the corner tests).
  • Geometry runs in f32 (squiggle); kinematics and timing in f64.

License

Apache-2.0.