openrr_planner/planner/
joint_path_planner.rs

1/*
2Copyright 2017 Takashi Ogura
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16use std::{path::Path, sync::Arc};
17
18use k::nalgebra as na;
19use na::RealField;
20use ncollide3d::shape::Compound;
21use schemars::JsonSchema;
22use serde::{Deserialize, Serialize};
23use tracing::*;
24
25use crate::{
26    collision::{CollisionDetector, RobotCollisionDetector},
27    errors::*,
28    funcs::*,
29};
30
31/// Collision Avoidance Path Planner
32pub struct JointPathPlanner<N>
33where
34    N: RealField + Copy + k::SubsetOf<f64>,
35{
36    /// Robot for reference (read only and assumed to hold the latest full states)
37    reference_robot: Arc<k::Chain<N>>,
38    /// Robot collision detector
39    robot_collision_detector: RobotCollisionDetector<N>,
40    /// Unit length for searching
41    ///
42    /// If the value is large, the path become sparse.
43    pub step_length: N,
44    /// Max num of RRT search loop
45    pub max_try: usize,
46    /// Num of path smoothing trials
47    pub num_smoothing: usize,
48}
49
50impl<N> JointPathPlanner<N>
51where
52    N: RealField + num_traits::Float + k::SubsetOf<f64>,
53{
54    /// Create `JointPathPlanner`
55    pub fn new(
56        reference_robot: Arc<k::Chain<N>>,
57        robot_collision_detector: RobotCollisionDetector<N>,
58        step_length: N,
59        max_try: usize,
60        num_smoothing: usize,
61    ) -> Self {
62        JointPathPlanner {
63            reference_robot,
64            robot_collision_detector,
65            step_length,
66            max_try,
67            num_smoothing,
68        }
69    }
70
71    /// Check if the joint_positions are OK
72    fn is_feasible(
73        &self,
74        using_joints: &k::Chain<N>,
75        joint_positions: &[N],
76        objects: &Compound<N>,
77    ) -> bool {
78        match using_joints.set_joint_positions(joint_positions) {
79            Ok(()) => !self.robot_collision_detector.is_collision_detected(objects),
80            Err(err) => {
81                debug!("is_feasible: {err}");
82                false
83            }
84        }
85    }
86
87    /// Check if the joint_positions are OK
88    fn is_feasible_with_self(&self, using_joints: &k::Chain<N>, joint_positions: &[N]) -> bool {
89        match using_joints.set_joint_positions(joint_positions) {
90            Ok(()) => !self.robot_collision_detector.is_self_collision_detected(),
91            Err(err) => {
92                debug!("is_feasible: {err}");
93                false
94            }
95        }
96    }
97
98    /// Plan the sequence of joint angles of `using_joints`
99    ///
100    /// # Arguments
101    ///
102    /// - `using_joints`: part of collision_check_robot. the dof of the following angles must be same as this model.
103    /// - `start_angles`: initial joint angles of `using_joints`.
104    /// - `goal_angles`: goal joint angles of `using_joints`.
105    /// - `objects`: The collision between `self.collision_check_robot` and `objects` will be checked.
106    pub fn plan(
107        &self,
108        using_joint_names: &[String],
109        start_angles: &[N],
110        goal_angles: &[N],
111        objects: &Compound<N>,
112    ) -> Result<Vec<Vec<N>>> {
113        self.sync_joint_positions_with_reference();
114
115        let using_joints =
116            create_chain_from_joint_names(self.collision_check_robot(), using_joint_names)?;
117        let limits = using_joints.iter_joints().map(|j| j.limits).collect();
118        let step_length = self.step_length;
119        let max_try = self.max_try;
120        let current_angles = using_joints.joint_positions();
121
122        if !self.is_feasible(&using_joints, start_angles, objects) {
123            let collision_link_names = self.env_collision_link_names(objects);
124            using_joints.set_joint_positions(&current_angles)?;
125            return Err(Error::Collision {
126                point: UnfeasibleTrajectoryPoint::Start,
127                collision_link_names,
128            });
129        } else if !self.is_feasible(&using_joints, goal_angles, objects) {
130            let collision_link_names = self.env_collision_link_names(objects);
131            using_joints.set_joint_positions(&current_angles)?;
132            return Err(Error::Collision {
133                point: UnfeasibleTrajectoryPoint::Goal,
134                collision_link_names,
135            });
136        }
137
138        let mut path = match rrt::dual_rrt_connect(
139            start_angles,
140            goal_angles,
141            |angles: &[N]| self.is_feasible(&using_joints, angles, objects),
142            || generate_random_joint_positions_from_limits(&limits),
143            step_length,
144            max_try,
145        ) {
146            Ok(p) => p,
147            Err(error) => {
148                using_joints.set_joint_positions(&current_angles)?;
149                return Err(Error::PathPlanFail(error));
150            }
151        };
152        let num_smoothing = self.num_smoothing;
153        rrt::smooth_path(
154            &mut path,
155            |angles: &[N]| self.is_feasible(&using_joints, angles, objects),
156            step_length,
157            num_smoothing,
158        );
159
160        // The joint positions of using_joint can be changed in the smoothing,
161        // so we need to surely set the goal at the end.
162        using_joints.set_joint_positions(goal_angles)?;
163        Ok(path)
164    }
165
166    /// Plan the sequence of joint angles of `using_joints` to avoid self collision.
167    ///
168    /// # Arguments
169    ///
170    /// - `using_joints`: part of collision_check_robot. the dof of the following angles must be same as this model.
171    /// - `start_angles`: initial joint angles of `using_joints`.
172    /// - `goal_angles`: goal joint angles of `using_joints`.
173    pub fn plan_avoid_self_collision(
174        &self,
175        using_joint_names: &[String],
176        start_angles: &[N],
177        goal_angles: &[N],
178    ) -> Result<Vec<Vec<N>>> {
179        self.sync_joint_positions_with_reference();
180
181        let using_joints =
182            create_chain_from_joint_names(self.collision_check_robot(), using_joint_names)?;
183        let limits = using_joints.iter_joints().map(|j| j.limits).collect();
184        let step_length = self.step_length;
185        let max_try = self.max_try;
186        let current_angles = using_joints.joint_positions();
187
188        if !self.is_feasible_with_self(&using_joints, start_angles) {
189            let collision_link_names = self.self_collision_link_pairs();
190            using_joints.set_joint_positions(&current_angles)?;
191            return Err(Error::SelfCollision {
192                point: UnfeasibleTrajectoryPoint::Start,
193                collision_link_names,
194            });
195        } else if !self.is_feasible_with_self(&using_joints, goal_angles) {
196            let collision_link_names = self.self_collision_link_pairs();
197            using_joints.set_joint_positions(&current_angles)?;
198            return Err(Error::SelfCollision {
199                point: UnfeasibleTrajectoryPoint::Goal,
200                collision_link_names,
201            });
202        }
203
204        let mut path = match rrt::dual_rrt_connect(
205            start_angles,
206            goal_angles,
207            |angles: &[N]| self.is_feasible_with_self(&using_joints, angles),
208            || generate_random_joint_positions_from_limits(&limits),
209            step_length,
210            max_try,
211        ) {
212            Ok(p) => p,
213            Err(error) => {
214                using_joints.set_joint_positions(&current_angles)?;
215                return Err(Error::PathPlanFail(error));
216            }
217        };
218        let num_smoothing = self.num_smoothing;
219        rrt::smooth_path(
220            &mut path,
221            |angles: &[N]| self.is_feasible_with_self(&using_joints, angles),
222            step_length,
223            num_smoothing,
224        );
225        Ok(path)
226    }
227
228    /// Synchronize joint positions of the planning robot model with the reference robot
229    pub fn sync_joint_positions_with_reference(&self) {
230        self.collision_check_robot()
231            .set_joint_positions_clamped(self.reference_robot.joint_positions().as_slice());
232    }
233
234    /// Calculate the transforms of all of the links
235    pub fn update_transforms(&self) -> Vec<na::Isometry3<N>> {
236        self.collision_check_robot().update_transforms()
237    }
238
239    /// Get the names of the links
240    pub fn joint_names(&self) -> Vec<String> {
241        self.collision_check_robot()
242            .iter_joints()
243            .map(|j| j.name.clone())
244            .collect()
245    }
246
247    /// Get the robot model used for collision checking
248    pub fn collision_check_robot(&self) -> &k::Chain<N> {
249        &self.robot_collision_detector.robot
250    }
251
252    /// Get names of links colliding with environmental objects
253    /// objects: environmental objects
254    pub fn env_collision_link_names(&self, objects: &Compound<N>) -> Vec<String> {
255        self.robot_collision_detector
256            .env_collision_link_names(objects)
257    }
258
259    /// Get names of self-colliding links
260    pub fn self_collision_link_pairs(&self) -> Vec<(String, String)> {
261        self.robot_collision_detector.self_collision_link_pairs()
262    }
263}
264
265/// Builder pattern to create `JointPathPlanner`
266pub struct JointPathPlannerBuilder<N>
267where
268    N: RealField + Copy + k::SubsetOf<f64>,
269{
270    reference_robot: Option<Arc<k::Chain<N>>>,
271    robot_collision_detector: RobotCollisionDetector<N>,
272    step_length: N,
273    max_try: usize,
274    num_smoothing: usize,
275    collision_check_margin: Option<N>,
276    self_collision_pairs: Vec<(String, String)>,
277}
278
279impl<N> JointPathPlannerBuilder<N>
280where
281    N: RealField + num_traits::Float + k::SubsetOf<f64>,
282{
283    /// Create from components
284    ///
285    /// There are also some utility functions to create from urdf
286    pub fn new(robot_collision_detector: RobotCollisionDetector<N>) -> Self {
287        JointPathPlannerBuilder {
288            reference_robot: None,
289            robot_collision_detector,
290            step_length: na::convert(0.1),
291            max_try: 5000,
292            num_smoothing: 100,
293            collision_check_margin: None,
294            self_collision_pairs: vec![],
295        }
296    }
297
298    pub fn reference_robot(mut self, robot: Arc<k::Chain<N>>) -> Self {
299        self.reference_robot = Some(robot);
300        self
301    }
302
303    pub fn collision_check_margin(mut self, length: N) -> Self {
304        self.collision_check_margin = Some(length);
305        self
306    }
307
308    pub fn step_length(mut self, step_length: N) -> Self {
309        self.step_length = step_length;
310        self
311    }
312
313    pub fn max_try(mut self, max_try: usize) -> Self {
314        self.max_try = max_try;
315        self
316    }
317
318    pub fn num_smoothing(mut self, num_smoothing: usize) -> Self {
319        self.num_smoothing = num_smoothing;
320        self
321    }
322
323    pub fn self_collision_pairs(mut self, self_collision_pairs: Vec<(String, String)>) -> Self {
324        self.self_collision_pairs = self_collision_pairs;
325        self
326    }
327
328    pub fn finalize(mut self) -> Result<JointPathPlanner<N>> {
329        if let Some(margin) = self.collision_check_margin {
330            self.robot_collision_detector.collision_detector.prediction = margin;
331        }
332
333        match self.reference_robot {
334            Some(robot) => {
335                let mut planner = JointPathPlanner::new(
336                    robot,
337                    self.robot_collision_detector,
338                    self.step_length,
339                    self.max_try,
340                    self.num_smoothing,
341                );
342                planner.robot_collision_detector.self_collision_pairs = self.self_collision_pairs;
343                Ok(planner)
344            }
345            None => Err(Error::ReferenceRobot("JointPathBuilder".to_owned())),
346        }
347    }
348
349    /// Try to create `JointPathPlannerBuilder` instance from URDF file and end link name
350    pub fn from_urdf_file<P>(file: P) -> Result<JointPathPlannerBuilder<N>>
351    where
352        P: AsRef<Path>,
353    {
354        let urdf_robot = urdf_rs::utils::read_urdf_or_xacro(file.as_ref())?;
355        Ok(JointPathPlannerBuilder::from_urdf_robot(urdf_robot))
356    }
357
358    /// Try to create `JointPathPlannerBuilder` instance from `urdf_rs::Robot` instance
359    pub fn from_urdf_robot(urdf_robot: urdf_rs::Robot) -> JointPathPlannerBuilder<N> {
360        let robot = k::Chain::from(&urdf_robot);
361        let default_margin = na::convert(0.0);
362        let collision_detector = CollisionDetector::from_urdf_robot(&urdf_robot, default_margin);
363        let robot_collision_detector =
364            RobotCollisionDetector::new(robot, collision_detector, vec![]);
365        JointPathPlannerBuilder::new(robot_collision_detector)
366    }
367}
368
369#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
370#[serde(deny_unknown_fields)]
371pub struct JointPathPlannerConfig {
372    #[serde(default = "default_step_length")]
373    step_length: f64,
374    #[serde(default = "default_max_try")]
375    max_try: usize,
376    #[serde(default = "default_num_smoothing")]
377    num_smoothing: usize,
378    #[serde(default = "default_margin")]
379    margin: f64,
380}
381
382fn default_step_length() -> f64 {
383    0.1
384}
385
386fn default_max_try() -> usize {
387    5000
388}
389
390fn default_num_smoothing() -> usize {
391    100
392}
393
394fn default_margin() -> f64 {
395    0.001
396}
397
398impl Default for JointPathPlannerConfig {
399    fn default() -> Self {
400        Self {
401            step_length: default_step_length(),
402            max_try: default_max_try(),
403            num_smoothing: default_num_smoothing(),
404            margin: default_margin(),
405        }
406    }
407}
408
409pub fn create_joint_path_planner<P: AsRef<Path>>(
410    urdf_path: P,
411    self_collision_check_pairs: Vec<(String, String)>,
412    config: &JointPathPlannerConfig,
413    robot: Arc<k::Chain<f64>>,
414) -> JointPathPlanner<f64> {
415    JointPathPlannerBuilder::from_urdf_file(urdf_path)
416        .unwrap()
417        .step_length(config.step_length)
418        .max_try(config.max_try)
419        .num_smoothing(config.num_smoothing)
420        .collision_check_margin(config.margin)
421        .self_collision_pairs(self_collision_check_pairs)
422        .reference_robot(robot)
423        .finalize()
424        .unwrap()
425}
426
427#[cfg(test)]
428mod tests {
429    use super::*;
430    use crate::collision::create_all_collision_pairs;
431
432    #[test]
433    fn from_urdf() {
434        let urdf_path = Path::new("sample.urdf");
435        let _planner = JointPathPlannerBuilder::from_urdf_file(urdf_path)
436            .unwrap()
437            .collision_check_margin(0.01)
438            .finalize();
439    }
440
441    #[test]
442    fn test_create_joint_path_planner() {
443        let urdf_path = Path::new("sample.urdf");
444        let robot = k::Chain::from_urdf_file(urdf_path).unwrap();
445        let planner = create_joint_path_planner(
446            urdf_path,
447            create_all_collision_pairs(&robot),
448            &JointPathPlannerConfig::default(),
449            Arc::new(robot),
450        );
451
452        let l_shoulder_yaw_node = planner
453            .collision_check_robot()
454            .find("l_shoulder_yaw")
455            .unwrap();
456        let using_joints = k::SerialChain::from_end(l_shoulder_yaw_node);
457        let using_joint_names = using_joints
458            .iter_joints()
459            .map(|j| j.name.to_owned())
460            .collect::<Vec<String>>();
461
462        assert!(planner
463            .plan_avoid_self_collision(using_joint_names.as_slice(), &[0.0], &[0.0],)
464            .is_ok());
465        // an error occurs in the start
466        assert!(planner
467            .plan_avoid_self_collision(using_joint_names.as_slice(), &[1.57], &[0.0],)
468            .is_err());
469        // an error occurs in the goal
470        assert!(planner
471            .plan_avoid_self_collision(using_joint_names.as_slice(), &[0.0], &[1.57],)
472            .is_err());
473    }
474
475    // This test potentially fails because of RRT-based planning,
476    // i.e. the success rate is not 100%.
477    // Therefore, this test is treated as a `flaky test`.
478    #[flaky_test::flaky_test]
479    fn test_plan_avoid_self_collision() {
480        let urdf_path = Path::new("sample.urdf");
481        let robot = k::Chain::from_urdf_file(urdf_path).unwrap();
482
483        // cross the arms only by moving the right arm
484        k::SerialChain::from_end(robot.find("r_tool_fixed").unwrap())
485            .set_joint_positions_clamped(&[0.9, 0.0, 0.0, 0.0, 0.67, 0.0]);
486
487        let planner = create_joint_path_planner(
488            urdf_path,
489            create_all_collision_pairs(&robot),
490            &JointPathPlannerConfig::default(),
491            Arc::new(robot),
492        );
493
494        let l_tool_node = planner
495            .collision_check_robot()
496            .find("l_tool_fixed")
497            .unwrap();
498        let using_joints = k::SerialChain::from_end(l_tool_node);
499        let using_joint_names = using_joints
500            .iter_joints()
501            .map(|j| j.name.to_owned())
502            .collect::<Vec<String>>();
503
504        // an error occurs since the arms collide
505        assert!(planner
506            .plan_avoid_self_collision(using_joint_names.as_slice(), &[0.0; 6], &[0.0; 6],)
507            .is_err());
508        // the planner PROBABLY generates a trajectory avoiding self-collisions
509        assert!(planner
510            .plan_avoid_self_collision(
511                using_joint_names.as_slice(),
512                &[0.0, -0.3, 0.0, 0.0, 0.0, 0.0],
513                &[0.0, 0.3, 0.0, 0.0, 0.0, 0.0],
514            )
515            .is_ok());
516    }
517}