moveit2
The MoveIt Motion Planning Framework for ROS 2.
Loading...
Searching...
No Matches
unittest_trajectory_blender_transition_window.cpp
Go to the documentation of this file.
1/*********************************************************************
2 * Software License Agreement (BSD License)
3 *
4 * Copyright (c) 2018 Pilz GmbH & Co. KG
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * * Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * * Redistributions in binary form must reproduce the above
14 * copyright notice, this list of conditions and the following
15 * disclaimer in the documentation and/or other materials provided
16 * with the distribution.
17 * * Neither the name of Pilz GmbH & Co. KG nor the names of its
18 * contributors may be used to endorse or promote products derived
19 * from this software without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 * POSSIBILITY OF SUCH DAMAGE.
33 *********************************************************************/
34
35#include <memory>
36
37#include <gtest/gtest.h>
38
43#include <tf2_eigen/tf2_eigen.hpp>
44#include <tf2_geometry_msgs/tf2_geometry_msgs.hpp>
45
49
55#include "test_utils.h"
56
57#include <rclcpp/rclcpp.hpp>
58
59using namespace pilz_industrial_motion_planner;
61
62static const std::string PARAM_NAMESPACE_LIMITS = "robot_description_planning";
63class TrajectoryBlenderTransitionWindowTest : public testing::Test
64{
65protected:
70 void SetUp() override
71 {
72 rclcpp::NodeOptions node_options;
73 node_options.automatically_declare_parameters_from_overrides(true);
74 node_ = rclcpp::Node::make_shared("unittest_trajectory_blender_transition_window", node_options);
75
76 // load robot model
77 rm_loader_ = std::make_unique<robot_model_loader::RobotModelLoader>(node_);
78 robot_model_ = rm_loader_->getModel();
79 ASSERT_TRUE(bool(robot_model_)) << "Failed to load robot model";
80 planning_scene_ = std::make_shared<planning_scene::PlanningScene>(robot_model_);
81
82 // get parameters
83 ASSERT_TRUE(node_->has_parameter("planning_group"));
84 node_->get_parameter<std::string>("planning_group", planning_group_);
85 ASSERT_TRUE(node_->has_parameter("target_link"));
86 node_->get_parameter<std::string>("target_link", target_link_);
87 ASSERT_TRUE(node_->has_parameter("cartesian_velocity_tolerance"));
88 node_->get_parameter<double>("cartesian_velocity_tolerance", cartesian_velocity_tolerance_);
89 ASSERT_TRUE(node_->has_parameter("cartesian_angular_velocity_tolerance"));
90 node_->get_parameter<double>("cartesian_angular_velocity_tolerance", cartesian_angular_velocity_tolerance_);
91 ASSERT_TRUE(node_->has_parameter("joint_velocity_tolerance"));
92 node_->get_parameter<double>("joint_velocity_tolerance", joint_velocity_tolerance_);
93 ASSERT_TRUE(node_->has_parameter("joint_acceleration_tolerance"));
94 node_->get_parameter<double>("joint_acceleration_tolerance", joint_acceleration_tolerance_);
95 ASSERT_TRUE(node_->has_parameter("sampling_time"));
96 node_->get_parameter<double>("sampling_time", sampling_time_);
97 ASSERT_TRUE(node_->has_parameter("testdata_file_name"));
98 node_->get_parameter<std::string>("testdata_file_name", test_data_file_name_);
99
100 // load the test data provider
101 data_loader_ = std::make_unique<XmlTestdataLoader>(test_data_file_name_, robot_model_);
102 ASSERT_NE(nullptr, data_loader_) << "Failed to load test data by provider.";
103
104 // check robot model
106
107 // create the limits container
110 node_, PARAM_NAMESPACE_LIMITS, robot_model_->getActiveJointModels());
111
112 cartesian_limits::Params cart_limits;
113 cart_limits.max_trans_vel = 1 * M_PI;
114 cart_limits.max_trans_acc = 2;
115 cart_limits.max_trans_dec = 2;
116 cart_limits.max_rot_vel = 1;
117
120
121 // initialize trajectory generators and blender
122 lin_generator_ = std::make_unique<TrajectoryGeneratorLIN>(robot_model_, planner_limits_, planning_group_);
123 ASSERT_NE(nullptr, lin_generator_) << "failed to create LIN trajectory generator";
124 blender_ = std::make_unique<TrajectoryBlenderTransitionWindow>(planner_limits_);
125 ASSERT_NE(nullptr, blender_) << "failed to create trajectory blender";
126 }
127
128 void TearDown() override
129 {
130 robot_model_.reset();
131 }
132
136 std::vector<planning_interface::MotionPlanResponse> generateLinTrajs(const Sequence& seq, size_t num_cmds)
137 {
138 std::vector<planning_interface::MotionPlanResponse> responses(num_cmds);
139 for (size_t index = 0; index < num_cmds; ++index)
140 {
141 planning_interface::MotionPlanRequest req{ seq.getCmd<LinCart>(index).toRequest() };
142 // Set start state of request to end state of previous trajectory (except
143 // for first)
144 if (index > 0)
145 {
146 moveit::core::robotStateToRobotStateMsg(responses[index - 1].trajectory->getLastWayPoint(), req.start_state);
147 }
148 // generate trajectory
150 lin_generator_->generate(planning_scene_, req, resp, sampling_time_);
151 if (resp.error_code.val != moveit_msgs::msg::MoveItErrorCodes::SUCCESS)
152 {
153 std::runtime_error("Failed to generate trajectory.");
154 }
155 responses.at(index) = resp;
156 }
157 return responses;
158 }
159
160protected:
161 // ros stuff
162 rclcpp::Node::SharedPtr node_;
163 moveit::core::RobotModelConstPtr robot_model_;
164 std::unique_ptr<robot_model_loader::RobotModelLoader> rm_loader_;
165 planning_scene::PlanningSceneConstPtr planning_scene_;
166
167 std::unique_ptr<TrajectoryGenerator> lin_generator_;
168 std::unique_ptr<TrajectoryBlenderTransitionWindow> blender_;
169
170 // test parameters from parameter server
175
178};
179
192{
193 Sequence seq{ data_loader_->getSequence("SimpleSequence") };
194
195 std::vector<planning_interface::MotionPlanResponse> res{ generateLinTrajs(seq, 2) };
196
199
200 blend_req.group_name = "invalid_group_name";
201 blend_req.link_name = target_link_;
202 blend_req.first_trajectory = res.at(0).trajectory;
203 blend_req.second_trajectory = res.at(1).trajectory;
204
205 blend_req.blend_radius = seq.getBlendRadius(0);
206 EXPECT_FALSE(blender_->blend(planning_scene_, blend_req, blend_res));
207}
208
221{
222 Sequence seq{ data_loader_->getSequence("SimpleSequence") };
223
224 std::vector<planning_interface::MotionPlanResponse> res{ generateLinTrajs(seq, 2) };
225
228
229 blend_req.group_name = planning_group_;
230 blend_req.link_name = "invalid_target_link";
231 blend_req.first_trajectory = res.at(0).trajectory;
232 blend_req.second_trajectory = res.at(1).trajectory;
233
234 blend_req.blend_radius = seq.getBlendRadius(0);
235 EXPECT_FALSE(blender_->blend(planning_scene_, blend_req, blend_res));
236}
237
251{
252 Sequence seq{ data_loader_->getSequence("SimpleSequence") };
253
254 std::vector<planning_interface::MotionPlanResponse> res{ generateLinTrajs(seq, 2) };
255
258
259 blend_req.group_name = planning_group_;
260 blend_req.link_name = target_link_;
261 blend_req.first_trajectory = res.at(0).trajectory;
262 blend_req.second_trajectory = res.at(1).trajectory;
263
264 blend_req.blend_radius = -0.1;
265 EXPECT_FALSE(blender_->blend(planning_scene_, blend_req, blend_res));
266}
267
280{
281 Sequence seq{ data_loader_->getSequence("SimpleSequence") };
282
283 std::vector<planning_interface::MotionPlanResponse> res{ generateLinTrajs(seq, 2) };
284
287
288 blend_req.group_name = planning_group_;
289 blend_req.link_name = target_link_;
290 blend_req.first_trajectory = res.at(0).trajectory;
291 blend_req.second_trajectory = res.at(1).trajectory;
292
293 blend_req.blend_radius = 0.;
294 EXPECT_FALSE(blender_->blend(planning_scene_, blend_req, blend_res));
295}
296
310{
311 Sequence seq{ data_loader_->getSequence("SimpleSequence") };
312
313 // perform lin trajectory generation and modify sampling time
314 std::size_t num_cmds{ 2 };
315 std::vector<planning_interface::MotionPlanResponse> responses(num_cmds);
316
317 for (size_t index = 0; index < num_cmds; ++index)
318 {
319 planning_interface::MotionPlanRequest req{ seq.getCmd<LinCart>(index).toRequest() };
320 // Set start state of request to end state of previous trajectory (except
321 // for first)
322 if (index > 0)
323 {
324 moveit::core::robotStateToRobotStateMsg(responses[index - 1].trajectory->getLastWayPoint(), req.start_state);
325 sampling_time_ *= 2;
326 }
327 // generate trajectory
329 lin_generator_->generate(planning_scene_, req, resp, sampling_time_);
330 if (resp.error_code.val != moveit_msgs::msg::MoveItErrorCodes::SUCCESS)
331 {
332 std::runtime_error("Failed to generate trajectory.");
333 }
334 responses.at(index) = resp;
335 }
336
339
340 blend_req.group_name = planning_group_;
341 blend_req.link_name = target_link_;
342 blend_req.first_trajectory = responses[0].trajectory;
343 blend_req.second_trajectory = responses[1].trajectory;
344 blend_req.blend_radius = seq.getBlendRadius(0);
345 EXPECT_FALSE(blender_->blend(planning_scene_, blend_req, blend_res));
346}
347
363{
364 Sequence seq{ data_loader_->getSequence("SimpleSequence") };
365
366 std::vector<planning_interface::MotionPlanResponse> res{ generateLinTrajs(seq, 2) };
367
368 // Modify first time interval
369 EXPECT_GT(res[0].trajectory->getWayPointCount(), 2u);
370 res[0].trajectory->setWayPointDurationFromPrevious(1, 2 * sampling_time_);
371
374
375 blend_req.group_name = planning_group_;
376 blend_req.link_name = target_link_;
377 blend_req.first_trajectory = res.at(0).trajectory;
378 blend_req.second_trajectory = res.at(1).trajectory;
379 blend_req.blend_radius = seq.getBlendRadius(0);
380 EXPECT_FALSE(blender_->blend(planning_scene_, blend_req, blend_res));
381}
382
396TEST_F(TrajectoryBlenderTransitionWindowTest, testNotIntersectingTrajectories)
397{
398 Sequence seq{ data_loader_->getSequence("SimpleSequence") };
399
400 std::vector<planning_interface::MotionPlanResponse> res{ generateLinTrajs(seq, 1) };
401
404
405 blend_req.group_name = planning_group_;
406 blend_req.link_name = target_link_;
407 blend_req.first_trajectory = res.at(0).trajectory;
408 // replace the second trajectory to make the two trajectories timely not
409 // intersect
410 blend_req.second_trajectory = res.at(0).trajectory;
411 blend_req.blend_radius = seq.getBlendRadius(0);
412 EXPECT_FALSE(blender_->blend(planning_scene_, blend_req, blend_res));
413}
414
428{
429 Sequence seq{ data_loader_->getSequence("SimpleSequence") };
430
431 std::vector<planning_interface::MotionPlanResponse> res{ generateLinTrajs(seq, 2) };
432
435
436 blend_req.group_name = planning_group_;
437 blend_req.link_name = target_link_;
438 blend_req.blend_radius = seq.getBlendRadius(0);
439
440 blend_req.first_trajectory = res.at(0).trajectory;
441 blend_req.second_trajectory = res.at(1).trajectory;
442
443 // Modify last waypoint of first trajectory and first point of second
444 // trajectory
445 blend_req.first_trajectory->getLastWayPointPtr()->setVariableVelocity(0, 1.0);
446 blend_req.second_trajectory->getFirstWayPointPtr()->setVariableVelocity(0, 1.0);
447
448 EXPECT_FALSE(blender_->blend(planning_scene_, blend_req, blend_res));
449}
450
465{
466 Sequence seq{ data_loader_->getSequence("SimpleSequence") };
467
468 std::vector<planning_interface::MotionPlanResponse> res{ generateLinTrajs(seq, 2) };
469
470 double lin1_distance;
471 lin1_distance = (res[0].trajectory->getFirstWayPoint().getFrameTransform(target_link_).translation() -
472 res[0].trajectory->getLastWayPoint().getFrameTransform(target_link_).translation())
473 .norm();
474
477
478 blend_req.group_name = planning_group_;
479 blend_req.link_name = target_link_;
480 blend_req.blend_radius = 1.1 * lin1_distance;
481
482 blend_req.first_trajectory = res.at(0).trajectory;
483 blend_req.second_trajectory = res.at(1).trajectory;
484
485 EXPECT_FALSE(blender_->blend(planning_scene_, blend_req, blend_res));
486}
487
502{
503 Sequence seq{ data_loader_->getSequence("NoIntersectionTraj2") };
504
505 std::vector<planning_interface::MotionPlanResponse> res{ generateLinTrajs(seq, 2) };
506
509
510 blend_req.group_name = planning_group_;
511 blend_req.link_name = target_link_;
512 blend_req.blend_radius = seq.getBlendRadius(0);
513
514 blend_req.first_trajectory = res.at(0).trajectory;
515 blend_req.second_trajectory = res.at(1).trajectory;
516
517 EXPECT_FALSE(blender_->blend(planning_scene_, blend_req, blend_res));
518}
519
539{
540 Sequence seq{ data_loader_->getSequence("SimpleSequence") };
541
542 std::vector<planning_interface::MotionPlanResponse> res{ generateLinTrajs(seq, 2) };
543
546
547 blend_req.group_name = planning_group_;
548 blend_req.link_name = target_link_;
549 blend_req.blend_radius = seq.getBlendRadius(0);
550
551 blend_req.first_trajectory = res.at(0).trajectory;
552 blend_req.second_trajectory = res.at(1).trajectory;
553
554 EXPECT_TRUE(blender_->blend(planning_scene_, blend_req, blend_res));
555
556 EXPECT_TRUE(testutils::checkBlendResult(blend_req, blend_res, planner_limits_, joint_velocity_tolerance_,
557 joint_acceleration_tolerance_, cartesian_velocity_tolerance_,
558 cartesian_angular_velocity_tolerance_));
559}
560
581TEST_F(TrajectoryBlenderTransitionWindowTest, testOverlappingBlendTrajectories)
582{
583 Sequence seq{ data_loader_->getSequence("SimpleSequence") };
584 // Set goal of second traj to start of first traj.
585 seq.getCmd<LinCart>(1).setGoalConfiguration(seq.getCmd<LinCart>(0).getStartConfiguration());
586
587 std::vector<planning_interface::MotionPlanResponse> res{ generateLinTrajs(seq, 2) };
588
591
592 blend_req.group_name = planning_group_;
593 blend_req.link_name = target_link_;
594 blend_req.first_trajectory = res.at(0).trajectory;
595 blend_req.second_trajectory = res.at(1).trajectory;
596 blend_req.blend_radius = seq.getBlendRadius(0);
597 EXPECT_TRUE(blender_->blend(planning_scene_, blend_req, blend_res));
598
599 EXPECT_TRUE(testutils::checkBlendResult(blend_req, blend_res, planner_limits_, joint_velocity_tolerance_,
600 joint_acceleration_tolerance_, cartesian_velocity_tolerance_,
601 cartesian_angular_velocity_tolerance_));
602}
603
628{
629 const double sine_scaling_factor{ 0.01 };
630 const double time_scaling_factor{ 10 };
631
632 Sequence seq{ data_loader_->getSequence("SimpleSequence") };
633
634 std::vector<planning_interface::MotionPlanResponse> res{ generateLinTrajs(seq, 2) };
635
636 // prepare looping over trajectories
637 std::vector<robot_trajectory::RobotTrajectoryPtr> sine_trajs(2);
638
639 for (size_t traj_index = 0; traj_index < 2; ++traj_index)
640 {
641 auto lin_traj{ res.at(traj_index).trajectory };
642
643 CartesianTrajectory cart_traj;
644 trajectory_msgs::msg::JointTrajectory joint_traj;
645 const double duration{ lin_traj->getWayPointDurationFromStart(lin_traj->getWayPointCount()) };
646 // time from start zero does not work
647 const double time_from_start_offset{ time_scaling_factor * lin_traj->getWayPointDurations().back() };
648
649 // generate modified cartesian trajectory
650 for (size_t i = 0; i < lin_traj->getWayPointCount(); ++i)
651 {
652 // transform time to interval [0, 4*pi]
653 const double sine_arg{ 4 * M_PI * lin_traj->getWayPointDurationFromStart(i) / duration };
654
655 // get pose
657 const Eigen::Isometry3d eigen_pose{ lin_traj->getWayPointPtr(i)->getFrameTransform(target_link_) };
658 geometry_msgs::msg::Pose waypoint_pose = tf2::toMsg(eigen_pose);
659
660 // add scaled sine function
661 waypoint_pose.position.x += sine_scaling_factor * sin(sine_arg);
662 waypoint_pose.position.y += sine_scaling_factor * sin(sine_arg);
663 waypoint_pose.position.z += sine_scaling_factor * sin(sine_arg);
664
665 // add to trajectory
666 waypoint.pose = waypoint_pose;
667 waypoint.time_from_start = rclcpp::Duration::from_seconds(
668 time_from_start_offset + time_scaling_factor * lin_traj->getWayPointDurationFromStart(i));
669 cart_traj.points.push_back(waypoint);
670 }
671
672 // prepare ik
673 std::map<std::string, double> initial_joint_position, initial_joint_velocity;
674 for (const std::string& joint_name :
675 lin_traj->getFirstWayPointPtr()->getJointModelGroup(planning_group_)->getActiveJointModelNames())
676 {
677 if (traj_index == 0)
678 {
679 initial_joint_position[joint_name] = lin_traj->getFirstWayPoint().getVariablePosition(joint_name);
680 initial_joint_velocity[joint_name] = lin_traj->getFirstWayPoint().getVariableVelocity(joint_name);
681 }
682 else
683 {
684 initial_joint_position[joint_name] =
685 sine_trajs[traj_index - 1]->getLastWayPoint().getVariablePosition(joint_name);
686 initial_joint_velocity[joint_name] =
687 sine_trajs[traj_index - 1]->getLastWayPoint().getVariableVelocity(joint_name);
688 }
689 }
690
691 moveit_msgs::msg::MoveItErrorCodes error_code;
692 if (!generateJointTrajectory(planning_scene_, planner_limits_.getJointLimitContainer(), cart_traj, planning_group_,
693 target_link_, initial_joint_position, initial_joint_velocity, joint_traj, error_code,
694 true))
695 {
696 std::runtime_error("Failed to generate trajectory.");
697 }
698
699 joint_traj.points.back().velocities.assign(joint_traj.points.back().velocities.size(), 0.0);
700 joint_traj.points.back().accelerations.assign(joint_traj.points.back().accelerations.size(), 0.0);
701
702 // convert trajectory_msgs::JointTrajectory to robot_trajectory::RobotTrajectory
703 sine_trajs[traj_index] = std::make_shared<robot_trajectory::RobotTrajectory>(robot_model_, planning_group_);
704 sine_trajs.at(traj_index)->setRobotTrajectoryMsg(lin_traj->getFirstWayPoint(), joint_traj);
705 }
706
707 TrajectoryBlendRequest blend_req;
708 TrajectoryBlendResponse blend_res;
709
710 blend_req.group_name = planning_group_;
711 blend_req.link_name = target_link_;
712 blend_req.blend_radius = seq.getBlendRadius(0);
713
714 blend_req.first_trajectory = sine_trajs.at(0);
715 blend_req.second_trajectory = sine_trajs.at(1);
716
717 EXPECT_TRUE(blender_->blend(planning_scene_, blend_req, blend_res));
718
719 EXPECT_TRUE(testutils::checkBlendResult(blend_req, blend_res, planner_limits_, joint_velocity_tolerance_,
720 joint_acceleration_tolerance_, cartesian_velocity_tolerance_,
721 cartesian_angular_velocity_tolerance_));
722}
723
724int main(int argc, char** argv)
725{
726 rclcpp::init(argc, argv);
727 testing::InitGoogleTest(&argc, argv);
728 return RUN_ALL_TESTS();
729}
void SetUp() override
Create test scenario for trajectory blender.
std::vector< planning_interface::MotionPlanResponse > generateLinTrajs(const Sequence &seq, size_t num_cmds)
Generate lin trajectories for blend sequences.
std::unique_ptr< TrajectoryBlenderTransitionWindow > blender_
std::unique_ptr< robot_model_loader::RobotModelLoader > rm_loader_
static JointLimitsContainer getAggregatedLimits(const rclcpp::Node::SharedPtr &node, const std::string &param_namespace, const std::vector< const moveit::core::JointModel * > &joint_models)
Aggregates(combines) the joint limits from joint model and node parameters. The rules for the combina...
Container for JointLimits, essentially a map with convenience functions. Adds the ability to as for l...
This class combines CartesianLimit and JointLimits into on single class.
void setCartesianLimits(cartesian_limits::Params &cartesian_limit)
Set cartesian limits.
void setJointLimits(JointLimitsContainer &joint_limits)
Set joint limits.
Data class storing all information regarding a linear command.
Definition lin.h:48
Data class storing all information regarding a Sequence command.
Definition sequence.h:54
void robotStateToRobotStateMsg(const RobotState &state, moveit_msgs::msg::RobotState &robot_state, bool copy_attached_bodies=true)
Convert a MoveIt robot state to a robot state message.
std::unique_ptr< TestdataLoader > XmlTestDataLoaderUPtr
bool generateJointTrajectory(const planning_scene::PlanningSceneConstPtr &scene, const JointLimitsContainer &joint_limits, const KDL::Trajectory &trajectory, const std::string &group_name, const std::string &link_name, const std::map< std::string, double > &initial_joint_position, double sampling_time, trajectory_msgs::msg::JointTrajectory &joint_trajectory, moveit_msgs::msg::MoveItErrorCodes &error_code, bool check_self_collision=false)
Generate joint trajectory from a KDL Cartesian trajectory.
TEST_F(GetSolverTipFrameIntegrationTest, TestHasSolverManipulator)
Check if hasSolver() can be called successfully for the manipulator group.
moveit_msgs::msg::MotionPlanRequest MotionPlanRequest
bool checkBlendResult(const pilz_industrial_motion_planner::TrajectoryBlendRequest &blend_req, const pilz_industrial_motion_planner::TrajectoryBlendResponse &blend_res, const pilz_industrial_motion_planner::LimitsContainer &limits, double joint_velocity_tolerance, double joint_acceleration_tolerance, double cartesian_velocity_tolerance, double cartesian_angular_velocity_tolerance)
check the blending result of lin-lin
void checkRobotModel(const moveit::core::RobotModelConstPtr &robot_model, const std::string &group_name, const std::string &link_name)
std::vector< CartesianTrajectoryPoint > points
Response to a planning query.
moveit::core::MoveItErrorCode error_code
int main(int argc, char **argv)