moveit2
The MoveIt Motion Planning Framework for ROS 2.
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 
36 
37 #include <algorithm>
38 #include <memory>
39 #include <math.h>
40 #include <tf2_eigen/tf2_eigen.hpp>
42 #include <moveit/utils/logger.hpp>
43 
44 namespace
45 {
46 rclcpp::Logger getLogger()
47 {
48  return moveit::getLogger("moveit.planners.pilz.trajectory_blender_transition_window");
49 }
50 } // namespace
51 
53  const planning_scene::PlanningSceneConstPtr& planning_scene,
56 {
57  RCLCPP_INFO(getLogger(), "Start trajectory blending using transition window.");
58 
59  double sampling_time = 0.;
60  if (!validateRequest(req, sampling_time, res.error_code))
61  {
62  RCLCPP_ERROR(getLogger(), "Trajectory blend request is not valid.");
63  return false;
64  }
65 
66  // search for intersection points of the two trajectories with the blending
67  // sphere
68  // intersection points belongs to blend trajectory after blending
69  std::size_t first_intersection_index;
70  std::size_t second_intersection_index;
71  if (!searchIntersectionPoints(req, first_intersection_index, second_intersection_index))
72  {
73  RCLCPP_ERROR(getLogger(), "Blend radius too large.");
74  res.error_code.val = moveit_msgs::msg::MoveItErrorCodes::INVALID_MOTION_PLAN;
75  return false;
76  }
77 
78  // Select blending period and adjust the start and end point of the blend
79  // phase
80  std::size_t blend_align_index;
81  determineTrajectoryAlignment(req, first_intersection_index, second_intersection_index, blend_align_index);
82 
83  // blend the trajectories in Cartesian space
84  pilz_industrial_motion_planner::CartesianTrajectory blend_trajectory_cartesian;
85  blendTrajectoryCartesian(req, first_intersection_index, second_intersection_index, blend_align_index, sampling_time,
86  blend_trajectory_cartesian);
87 
88  // generate the blending trajectory in joint space
89  std::map<std::string, double> initial_joint_position, initial_joint_velocity;
90  for (const std::string& joint_name :
91  req.first_trajectory->getFirstWayPointPtr()->getJointModelGroup(req.group_name)->getActiveJointModelNames())
92  {
93  initial_joint_position[joint_name] =
94  req.first_trajectory->getWayPoint(first_intersection_index - 1).getVariablePosition(joint_name);
95  initial_joint_velocity[joint_name] =
96  req.first_trajectory->getWayPoint(first_intersection_index - 1).getVariableVelocity(joint_name);
97  }
98  trajectory_msgs::msg::JointTrajectory blend_joint_trajectory;
99  moveit_msgs::msg::MoveItErrorCodes error_code;
100 
101  if (!generateJointTrajectory(planning_scene, limits_.getJointLimitContainer(), blend_trajectory_cartesian,
102  req.group_name, req.link_name, initial_joint_position, initial_joint_velocity,
103  blend_joint_trajectory, error_code))
104  {
105  // LCOV_EXCL_START
106  RCLCPP_INFO(getLogger(), "Failed to generate joint trajectory for blending trajectory.");
107  res.error_code.val = error_code.val;
108  return false;
109  // LCOV_EXCL_STOP
110  }
111 
112  res.first_trajectory = std::make_shared<robot_trajectory::RobotTrajectory>(req.first_trajectory->getRobotModel(),
113  req.first_trajectory->getGroup());
114  res.blend_trajectory = std::make_shared<robot_trajectory::RobotTrajectory>(req.first_trajectory->getRobotModel(),
115  req.first_trajectory->getGroup());
116  res.second_trajectory = std::make_shared<robot_trajectory::RobotTrajectory>(req.first_trajectory->getRobotModel(),
117  req.first_trajectory->getGroup());
118 
119  // set the three trajectories after blending in response
120  // erase the points [first_intersection_index, back()] from the first
121  // trajectory
122  for (size_t i = 0; i < first_intersection_index; ++i)
123  {
124  res.first_trajectory->insertWayPoint(i, req.first_trajectory->getWayPoint(i),
125  req.first_trajectory->getWayPointDurationFromPrevious(i));
126  }
127 
128  // append the blend trajectory
129  res.blend_trajectory->setRobotTrajectoryMsg(req.first_trajectory->getFirstWayPoint(), blend_joint_trajectory);
130 
131  // copy the points [second_intersection_index, len] from the second trajectory
132  for (size_t i = second_intersection_index + 1; i < req.second_trajectory->getWayPointCount(); ++i)
133  {
134  res.second_trajectory->insertWayPoint(i - (second_intersection_index + 1), req.second_trajectory->getWayPoint(i),
135  req.second_trajectory->getWayPointDurationFromPrevious(i));
136  }
137 
138  // adjust the time from start
139  res.second_trajectory->setWayPointDurationFromPrevious(0, sampling_time);
140 
141  res.error_code.val = moveit_msgs::msg::MoveItErrorCodes::SUCCESS;
142  return true;
143 }
144 
145 bool pilz_industrial_motion_planner::TrajectoryBlenderTransitionWindow::validateRequest(
146  const pilz_industrial_motion_planner::TrajectoryBlendRequest& req, double& sampling_time,
147  moveit_msgs::msg::MoveItErrorCodes& error_code) const
148 {
149  RCLCPP_DEBUG(getLogger(), "Validate the trajectory blend request.");
150 
151  // check planning group
152  if (!req.first_trajectory->getRobotModel()->hasJointModelGroup(req.group_name))
153  {
154  RCLCPP_ERROR_STREAM(getLogger(), "Unknown planning group: " << req.group_name);
155  error_code.val = moveit_msgs::msg::MoveItErrorCodes::INVALID_GROUP_NAME;
156  return false;
157  }
158 
159  // check link exists
160  if (!req.first_trajectory->getRobotModel()->hasLinkModel(req.link_name) &&
161  !req.first_trajectory->getLastWayPoint().hasAttachedBody(req.link_name))
162  {
163  RCLCPP_ERROR_STREAM(getLogger(), "Unknown link name: " << req.link_name);
164  error_code.val = moveit_msgs::msg::MoveItErrorCodes::INVALID_LINK_NAME;
165  return false;
166  }
167 
168  if (req.blend_radius <= 0)
169  {
170  RCLCPP_ERROR(getLogger(), "Blending radius must be positive");
171  error_code.val = moveit_msgs::msg::MoveItErrorCodes::INVALID_MOTION_PLAN;
172  return false;
173  }
174 
175  // end position of the first trajectory and start position of second
176  // trajectory must be the same
178  req.first_trajectory->getLastWayPoint(), req.second_trajectory->getFirstWayPoint(), req.group_name, EPSILON))
179  {
180  RCLCPP_ERROR_STREAM(getLogger(), "During blending the last point of the preceding and the first point of the "
181  "succeeding trajectory");
182  error_code.val = moveit_msgs::msg::MoveItErrorCodes::INVALID_MOTION_PLAN;
183  return false;
184  }
185 
186  // same uniform sampling time
188  EPSILON, sampling_time))
189  {
190  error_code.val = moveit_msgs::msg::MoveItErrorCodes::INVALID_MOTION_PLAN;
191  return false;
192  }
193 
194  // end position of the first trajectory and start position of second
195  // trajectory must have zero
196  // velocities/accelerations
198  EPSILON) ||
200  EPSILON))
201  {
202  RCLCPP_ERROR(getLogger(), "Intersection point of the blending trajectories has non-zero velocities/accelerations.");
203  error_code.val = moveit_msgs::msg::MoveItErrorCodes::INVALID_MOTION_PLAN;
204  return false;
205  }
206 
207  return true;
208 }
209 
210 void pilz_industrial_motion_planner::TrajectoryBlenderTransitionWindow::blendTrajectoryCartesian(
211  const pilz_industrial_motion_planner::TrajectoryBlendRequest& req, const std::size_t first_interse_index,
212  const std::size_t second_interse_index, const std::size_t blend_align_index, double sampling_time,
214 {
215  // other fields of the trajectory
216  trajectory.group_name = req.group_name;
217  trajectory.link_name = req.link_name;
218 
219  // Pose on first trajectory
220  Eigen::Isometry3d blend_sample_pose1 =
221  req.first_trajectory->getWayPoint(first_interse_index).getFrameTransform(req.link_name);
222 
223  // Pose on second trajectory
224  Eigen::Isometry3d blend_sample_pose2 =
225  req.second_trajectory->getWayPoint(second_interse_index).getFrameTransform(req.link_name);
226 
227  // blend the trajectory
228  double blend_sample_num = second_interse_index + blend_align_index - first_interse_index + 1;
230  blend_sample_pose2 = req.second_trajectory->getFirstWayPoint().getFrameTransform(req.link_name);
231 
232  // Pose on blending trajectory
233  Eigen::Isometry3d blend_sample_pose;
234  for (std::size_t i = 0; i < blend_sample_num; ++i)
235  {
236  // if the first trajectory does not reach the last sample, update
237  if ((first_interse_index + i) < req.first_trajectory->getWayPointCount())
238  {
239  blend_sample_pose1 = req.first_trajectory->getWayPoint(first_interse_index + i).getFrameTransform(req.link_name);
240  }
241 
242  // if after the alignment, the second trajectory starts, update
243  if ((first_interse_index + i) > blend_align_index)
244  {
245  blend_sample_pose2 = req.second_trajectory->getWayPoint(first_interse_index + i - blend_align_index)
246  .getFrameTransform(req.link_name);
247  }
248 
249  double s = (i + 1) / blend_sample_num;
250  double alpha = 6 * std::pow(s, 5) - 15 * std::pow(s, 4) + 10 * std::pow(s, 3);
251 
252  // blend the translation
253  blend_sample_pose.translation() = blend_sample_pose1.translation() +
254  alpha * (blend_sample_pose2.translation() - blend_sample_pose1.translation());
255 
256  // blend the orientation
257  Eigen::Quaterniond start_quat(blend_sample_pose1.rotation());
258  Eigen::Quaterniond end_quat(blend_sample_pose2.rotation());
259  blend_sample_pose.linear() = start_quat.slerp(alpha, end_quat).toRotationMatrix();
260 
261  // push to the trajectory
262  waypoint.pose = tf2::toMsg(blend_sample_pose);
263  waypoint.time_from_start = rclcpp::Duration::from_seconds((i + 1.0) * sampling_time);
264  trajectory.points.push_back(waypoint);
265  }
266 }
267 
268 bool pilz_industrial_motion_planner::TrajectoryBlenderTransitionWindow::searchIntersectionPoints(
269  const pilz_industrial_motion_planner::TrajectoryBlendRequest& req, std::size_t& first_interse_index,
270  std::size_t& second_interse_index) const
271 {
272  RCLCPP_INFO(getLogger(), "Search for start and end point of blending trajectory.");
273 
274  // compute the position of the center of the blend sphere
275  // (last point of the first trajectory, first point of the second trajectory)
276  Eigen::Isometry3d circ_pose = req.first_trajectory->getLastWayPoint().getFrameTransform(req.link_name);
277 
278  // Search for intersection points according to distance
279  if (!linearSearchIntersectionPoint(req.link_name, circ_pose.translation(), req.blend_radius, req.first_trajectory,
280  true, first_interse_index))
281  {
282  RCLCPP_ERROR_STREAM(getLogger(), "Intersection point of first trajectory not found.");
283  return false;
284  }
285  RCLCPP_INFO_STREAM(getLogger(), "Intersection point of first trajectory found, index: " << first_interse_index);
286 
287  if (!linearSearchIntersectionPoint(req.link_name, circ_pose.translation(), req.blend_radius, req.second_trajectory,
288  false, second_interse_index))
289  {
290  RCLCPP_ERROR_STREAM(getLogger(), "Intersection point of second trajectory not found.");
291  return false;
292  }
293 
294  RCLCPP_INFO_STREAM(getLogger(), "Intersection point of second trajectory found, index: " << second_interse_index);
295  return true;
296 }
297 
298 void pilz_industrial_motion_planner::TrajectoryBlenderTransitionWindow::determineTrajectoryAlignment(
299  const pilz_industrial_motion_planner::TrajectoryBlendRequest& req, std::size_t first_interse_index,
300  std::size_t second_interse_index, std::size_t& blend_align_index) const
301 {
302  size_t way_point_count_1 = req.first_trajectory->getWayPointCount() - first_interse_index;
303  size_t way_point_count_2 = second_interse_index + 1;
304 
305  if (way_point_count_1 > way_point_count_2)
306  {
307  blend_align_index = req.first_trajectory->getWayPointCount() - second_interse_index - 1;
308  }
309  else
310  {
311  blend_align_index = first_interse_index;
312  }
313 }
const JointLimitsContainer & getJointLimitContainer() const
Obtain the Joint Limits from the container.
bool blend(const planning_scene::PlanningSceneConstPtr &planning_scene, const pilz_industrial_motion_planner::TrajectoryBlendRequest &req, pilz_industrial_motion_planner::TrajectoryBlendResponse &res) override
Blend two trajectories using transition window. The trajectories have to be equally and uniformly dis...
const pilz_industrial_motion_planner::LimitsContainer limits_
rclcpp::Logger getLogger(const std::string &name)
Creates a namespaced logger.
Definition: logger.cpp:79
bool linearSearchIntersectionPoint(const std::string &link_name, const Eigen::Vector3d &center_position, const double r, const robot_trajectory::RobotTrajectoryPtr &traj, bool inverseOrder, std::size_t &index)
Performs a linear search for the intersection point of the trajectory with the blending radius.
bool isRobotStateStationary(const moveit::core::RobotState &state, const std::string &group, double EPSILON)
check if the robot state have zero velocity/acceleration
bool determineAndCheckSamplingTime(const robot_trajectory::RobotTrajectoryPtr &first_trajectory, const robot_trajectory::RobotTrajectoryPtr &second_trajectory, double EPSILON, double &sampling_time)
Determines the sampling time and checks that both trajectroies use the same sampling time.
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.
bool isRobotStateEqual(const moveit::core::RobotState &state1, const moveit::core::RobotState &state2, const std::string &joint_group_name, double epsilon)
Check if the two robot states have the same joint position/velocity/acceleration.
This namespace includes the central class for representing planning contexts.
std::vector< CartesianTrajectoryPoint > points