moveit2
The MoveIt Motion Planning Framework for ROS 2.
Loading...
Searching...
No Matches
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>
43
44namespace
45{
46rclcpp::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
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, true))
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
145bool 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
210void 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
268bool 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
298void 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