Skip to main content

deke_linear/
constraints.rs

1use std::time::Duration;
2
3use deke_types::SRobotQ;
4
5/// Per-axis joint velocity/acceleration/jerk ceilings.
6#[derive(Clone, Debug)]
7pub struct JointLimits<const N: usize> {
8    pub v_max: SRobotQ<N, f64>,
9    pub a_max: SRobotQ<N, f64>,
10    pub j_max: SRobotQ<N, f64>,
11}
12
13impl<const N: usize> JointLimits<N> {
14    /// The same v/a/j ceiling on every axis.
15    pub fn symmetric(v: f64, a: f64, j: f64) -> Self {
16        Self {
17            v_max: SRobotQ::splat(v),
18            a_max: SRobotQ::splat(a),
19            j_max: SRobotQ::splat(j),
20        }
21    }
22}
23
24/// How the raw Cartesian polyline is conditioned into smooth, arc-length runs.
25#[derive(Clone, Debug)]
26pub struct PathConditioning {
27    /// Turn angle (radians) above which a vertex is treated as a *sharp* corner —
28    /// the path is split there into separate runs that start/stop at rest.
29    pub sharp_corner_angle: f64,
30}
31
32impl Default for PathConditioning {
33    fn default() -> Self {
34        Self {
35            sharp_corner_angle: 30.0_f64.to_radians(),
36        }
37    }
38}
39
40/// Knobs for the branch-tracking planner (Stage B).
41#[derive(Clone, Debug)]
42pub struct PlannerOptions<const N: usize> {
43    /// Arc-length spacing (metres) at which the run is sampled and IK'd.
44    pub sample_ds: f64,
45    /// Weight on the manipulability (singularity-avoidance) node cost.
46    pub manip_weight: f64,
47    /// Absolute per-sample joint continuity guard (radians): an edge whose worst
48    /// per-axis joint jump exceeds this is a reconfiguration. Always active.
49    pub max_branch_jump: f64,
50    /// TCP speed (m/s) used by the velocity-based reconfiguration test. When
51    /// `> 0` and [`Self::joint_v_max`] is finite, an edge that would drive **any**
52    /// joint past [`Self::reconfig_vel_fraction`] of its velocity limit at this
53    /// speed is treated as a reconfiguration/discontinuity. At weld speeds this is
54    /// the signature of a singularity or wrist flip. Set `0.0` to disable.
55    pub max_velocity: f64,
56    /// Per-joint velocity ceilings for the velocity-based reconfiguration test.
57    /// `INFINITY` (the default) disables the test on that axis.
58    pub joint_v_max: SRobotQ<N, f64>,
59    /// Fraction of `joint_v_max` an edge may demand before it counts as a
60    /// reconfiguration (e.g. `0.9` = 90%).
61    pub reconfig_vel_fraction: f64,
62}
63
64impl<const N: usize> Default for PlannerOptions<N> {
65    fn default() -> Self {
66        Self {
67            sample_ds: 2e-3,
68            manip_weight: 1.0,
69            max_branch_jump: 0.6,
70            max_velocity: 0.0,
71            joint_v_max: SRobotQ::splat(f64::INFINITY),
72            reconfig_vel_fraction: 0.9,
73        }
74    }
75}
76
77/// Kinematic ceilings + the commanded constant TCP speed (Stage C).
78#[derive(Clone, Debug)]
79pub struct LinearConstraints<const N: usize> {
80    pub joint: JointLimits<N>,
81    /// Commanded constant TCP linear speed (m/s), held wherever feasible.
82    pub tcp_speed: f64,
83    /// Output trajectory sample period.
84    pub output_dt: Duration,
85    /// When `true`, the speed may only fall below `tcp_speed` during the rest
86    /// ramp at the start and end of each run. If the joint v/a/j geometry would
87    /// force a dip anywhere in a run's interior (a shallow corner or a
88    /// near-singular patch), the retime fails with [`crate::LinearError::SpeedDipRequired`]
89    /// instead of slowing down. Sharp corners are unaffected — they are already
90    /// split into separate runs whose endpoints are legitimate stops.
91    pub forbid_interior_dips: bool,
92}
93
94/// Everything the [`crate::LinearFollower`] needs in one bundle.
95#[derive(Clone, Debug)]
96pub struct FollowConfig<const N: usize> {
97    pub conditioning: PathConditioning,
98    pub planner: PlannerOptions<N>,
99    /// When set, the tool's symmetry axis yaw is treated as a free DOF and
100    /// resolved globally to avoid singularities (see [`crate::RedundantOptions`]).
101    /// When `None`, the TCP orientation is fully constrained.
102    pub redundant: Option<crate::redundant::RedundantOptions>,
103    pub constraints: LinearConstraints<N>,
104}
105
106impl<const N: usize> FollowConfig<N> {
107    /// Preset tuned for arc-welding travel speeds, quoted in **inches per minute**
108    /// (typical procedures run 20–50 IPM). Enables the velocity-based
109    /// reconfiguration test against `joint`, with fine geometric sampling.
110    pub fn weld(ipm: f64, joint: JointLimits<N>, output_dt: Duration) -> Self {
111        let tcp_speed = ipm * 0.0254 / 60.0;
112        let planner = PlannerOptions {
113            sample_ds: 5e-4,
114            manip_weight: 1.0,
115            max_branch_jump: 0.6,
116            max_velocity: tcp_speed,
117            joint_v_max: joint.v_max,
118            reconfig_vel_fraction: 0.9,
119        };
120        Self {
121            conditioning: PathConditioning {
122                sharp_corner_angle: 30.0_f64.to_radians(),
123            },
124            planner,
125            redundant: None,
126            constraints: LinearConstraints {
127                joint,
128                tcp_speed,
129                output_dt,
130                forbid_interior_dips: false,
131            },
132        }
133    }
134
135    /// Declare the tool symmetry axis free (functional redundancy) for this config.
136    pub fn with_redundancy(mut self, options: crate::redundant::RedundantOptions) -> Self {
137        self.redundant = Some(options);
138        self
139    }
140}