1use std::time::Duration;
5
6use deke_types::glam::DAffine3;
7use deke_types::{
8 ContinuousFKChain, DekeError, DekeResult, IkSolver, Planner, Retimer, SRobotPath, SRobotQ,
9 SRobotQLike, SRobotTraj, Validator,
10};
11
12use crate::constraints::{FollowConfig, LinearConstraints, PlannerOptions};
13use crate::diagnostic::{LinearFollowDiagnostic, LinearPlannerDiagnostic, LinearRetimerDiagnostic};
14use crate::error::LinearError;
15use crate::path::{CartesianRun, condition};
16use crate::planner::CartesianLinearPlanner;
17use crate::retimer::ConstantSpeedRetimer;
18
19#[derive(Clone, Debug)]
21pub struct LinearFollower<'a, const N: usize, FK> {
22 fk: &'a FK,
23}
24
25impl<'a, const N: usize, FK> LinearFollower<'a, N, FK>
26where
27 FK: ContinuousFKChain<N, f64> + IkSolver<N, f64>,
28{
29 pub fn new(fk: &'a FK) -> Self {
30 Self { fk }
31 }
32
33 pub fn follow<V: Validator<N, (), f64>>(
43 &self,
44 poses: &[DAffine3],
45 cfg: &FollowConfig<N>,
46 validator: &V,
47 ctx: &V::Context<'_>,
48 ) -> Result<(SRobotTraj<N, f64>, LinearFollowDiagnostic), LinearError> {
49 let runs = condition(poses, &cfg.conditioning)?;
50 let planner = CartesianLinearPlanner::new(self.fk);
51 let retimer = ConstantSpeedRetimer::new(self.fk);
52
53 let mut all: Vec<SRobotQ<N, f64>> = Vec::new();
54 let mut diag = LinearFollowDiagnostic {
55 runs: runs.len(),
56 ..Default::default()
57 };
58
59 let redundant = cfg
60 .redundant
61 .as_ref()
62 .map(|_| crate::redundant::RedundantLinearPlanner::new(self.fk));
63
64 let mut seed: Option<SRobotQ<N, f64>> = None;
65 for (i, run) in runs.iter().enumerate() {
66 let jpath = match (&redundant, &cfg.redundant) {
67 (Some(rp), Some(ropts)) => {
68 let (path, rdiag) =
69 rp.plan_run(run, &cfg.planner, ropts, validator, ctx, seed.as_ref(), i)?;
70 diag.redundant.push(rdiag);
71 path
72 }
73 _ => {
74 let (path, pdiag) =
75 planner.plan_run(run, &cfg.planner, validator, ctx, seed.as_ref(), i)?;
76 diag.planner.push(pdiag);
77 path
78 }
79 };
80 seed = Some(*jpath.last());
81 let (traj, rdiag) = retimer.retime_path(&cfg.constraints, &jpath, i)?;
82 let samples = traj.path().iter().copied();
83 if all.is_empty() {
84 all.extend(samples);
85 } else {
86 all.extend(samples.skip(1));
87 }
88 diag.retimer.push(rdiag);
89 }
90
91 validator
94 .validate_motion(&all, ctx)
95 .map_err(LinearError::from)?;
96
97 let dt = cfg.constraints.output_dt;
98 diag.total_samples = all.len();
99 diag.total_duration =
100 Duration::from_secs_f64(all.len().saturating_sub(1) as f64 * dt.as_secs_f64());
101 let path = SRobotPath::try_new(all).map_err(LinearError::from)?;
102 Ok((SRobotTraj::new(dt, path), diag))
103 }
104}
105
106impl<'a, const N: usize, FK> Planner<N, f64> for CartesianLinearPlanner<'a, N, FK>
107where
108 FK: ContinuousFKChain<N, f64> + IkSolver<N, f64>,
109{
110 type Diagnostic = LinearPlannerDiagnostic;
111 type Config = PlannerOptions<N>;
112 type Waypoints = CartesianRun;
113
114 fn plan<E: Into<DekeError>, V: Validator<N, (), f64>>(
115 &self,
116 config: &Self::Config,
117 waypoints: &Self::Waypoints,
118 validator: &V,
119 ctx: &V::Context<'_>,
120 ) -> (DekeResult<SRobotPath<N, f64>>, Self::Diagnostic) {
121 match self.plan_run(waypoints, config, validator, ctx, None, 0) {
122 Ok((path, diag)) => (Ok(path), diag),
123 Err(e) => (
124 Err(e.into()),
125 LinearPlannerDiagnostic {
126 samples: 0,
127 min_manipulability: 0.0,
128 total_cost: f64::INFINITY,
129 },
130 ),
131 }
132 }
133}
134
135impl<'a, const N: usize, FK> Retimer<N, f64> for ConstantSpeedRetimer<'a, N, FK>
136where
137 FK: ContinuousFKChain<N, f64>,
138{
139 type Diagnostic = LinearRetimerDiagnostic;
140 type Constraints = LinearConstraints<N>;
141
142 fn retime<V: Validator<N, (), f64>>(
143 &self,
144 constraints: &Self::Constraints,
145 path: &SRobotPath<N, f64>,
146 validator: &V,
147 ctx: &V::Context<'_>,
148 ) -> (DekeResult<SRobotTraj<N, f64>>, Self::Diagnostic) {
149 match self.retime_path(constraints, path, 0) {
150 Ok((traj, diag)) => {
151 let samples: Vec<SRobotQ<N, f64>> = traj.path().iter().copied().collect();
152 if let Err(e) = validator.validate_motion(&samples, ctx) {
153 return (Err(e), diag);
154 }
155 (Ok(traj), diag)
156 }
157 Err(e) => (
158 Err(e.into()),
159 LinearRetimerDiagnostic {
160 output_samples: 0,
161 duration: Duration::ZERO,
162 arc_length: 0.0,
163 commanded_speed: constraints.tcp_speed,
164 peak_speed: 0.0,
165 },
166 ),
167 }
168 }
169}
170
171#[derive(Debug, Clone, Default)]
174pub struct NoopValidator<const N: usize>;
175
176impl<const N: usize> Validator<N, (), f64> for NoopValidator<N> {
177 type Context<'ctx> = ();
178
179 fn validate<'ctx, E: Into<DekeError>, A: SRobotQLike<N, E, f64>>(
180 &self,
181 _q: A,
182 _ctx: &Self::Context<'ctx>,
183 ) -> DekeResult<()> {
184 Ok(())
185 }
186
187 fn validate_motion<'ctx>(
188 &self,
189 _qs: &[SRobotQ<N, f64>],
190 _ctx: &Self::Context<'ctx>,
191 ) -> DekeResult<()> {
192 Ok(())
193 }
194}