moveit2
The MoveIt Motion Planning Framework for ROS 2.
trajectory_generator.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 <cassert>
38 
39 #include <tf2_eigen/tf2_eigen.hpp>
40 #include <tf2_kdl/tf2_kdl.hpp>
41 #include <boost/range/combine.hpp>
42 
43 #include <kdl/velocityprofile_trap.hpp>
45 
47 
49 {
50 static const rclcpp::Logger LOGGER = rclcpp::get_logger("moveit.pilz_industrial_motion_planner.trajectory_generator");
51 
52 sensor_msgs::msg::JointState TrajectoryGenerator::filterGroupValues(const sensor_msgs::msg::JointState& robot_state,
53  const std::string& group) const
54 {
55  const std::vector<std::string>& group_joints{ robot_model_->getJointModelGroup(group)->getActiveJointModelNames() };
56  sensor_msgs::msg::JointState group_state;
57  group_state.name.reserve(group_joints.size());
58  group_state.position.reserve(group_joints.size());
59  group_state.velocity.reserve(group_joints.size());
60 
61  for (size_t i = 0; i < robot_state.name.size(); ++i)
62  {
63  if (std::find(group_joints.begin(), group_joints.end(), robot_state.name.at(i)) != group_joints.end())
64  {
65  group_state.name.push_back(robot_state.name.at(i));
66  group_state.position.push_back(robot_state.position.at(i));
67  if (i < robot_state.velocity.size())
68  {
69  group_state.velocity.push_back(robot_state.velocity.at(i));
70  }
71  }
72  }
73  return group_state;
74 }
75 
76 void TrajectoryGenerator::cmdSpecificRequestValidation(const planning_interface::MotionPlanRequest& /*req*/) const
77 {
78  // Empty implementation, in case the derived class does not want
79  // to provide a command specific request validation.
80 }
81 
82 void TrajectoryGenerator::checkVelocityScaling(const double& scaling_factor)
83 {
84  if (!isScalingFactorValid(scaling_factor))
85  {
86  std::ostringstream os;
87  os << "Velocity scaling not in range [" << MIN_SCALING_FACTOR << ", " << MAX_SCALING_FACTOR << "], "
88  << "actual value is: " << scaling_factor;
89  throw VelocityScalingIncorrect(os.str());
90  }
91 }
92 
93 void TrajectoryGenerator::checkAccelerationScaling(const double& scaling_factor)
94 {
95  if (!isScalingFactorValid(scaling_factor))
96  {
97  std::ostringstream os;
98  os << "Acceleration scaling not in range [" << MIN_SCALING_FACTOR << ", " << MAX_SCALING_FACTOR << "], "
99  << "actual value is: " << scaling_factor;
100  throw AccelerationScalingIncorrect(os.str());
101  }
102 }
103 
104 void TrajectoryGenerator::checkForValidGroupName(const std::string& group_name) const
105 {
106  if (!robot_model_->hasJointModelGroup(group_name))
107  {
108  std::ostringstream os;
109  os << "Unknown planning group: " << group_name;
110  throw UnknownPlanningGroup(os.str());
111  }
112 }
113 
114 void TrajectoryGenerator::checkStartState(const moveit_msgs::msg::RobotState& start_state,
115  const std::string& group) const
116 {
117  if (start_state.joint_state.name.empty())
118  {
119  throw NoJointNamesInStartState("No joint names for state state given");
120  }
121 
122  if (start_state.joint_state.name.size() != start_state.joint_state.position.size())
123  {
124  throw SizeMismatchInStartState("Joint state name and position do not match in start state");
125  }
126 
127  sensor_msgs::msg::JointState group_start_state{ filterGroupValues(start_state.joint_state, group) };
128 
129  // verify joint position limits
130  const JointLimitsContainer& limits{ planner_limits_.getJointLimitContainer() };
131  std::string error_msg;
132  for (auto joint : boost::combine(group_start_state.name, group_start_state.position))
133  {
134  if (!limits.verifyPositionLimit(joint.get<0>(), joint.get<1>()))
135  {
136  error_msg.append(error_msg.empty() ? "start state joints outside their position limits: " : ", ");
137  error_msg.append(joint.get<0>());
138  }
139  }
140  if (!error_msg.empty())
141  {
142  throw JointsOfStartStateOutOfRange(error_msg);
143  }
144 
145  // does not allow start velocity
146  if (!std::all_of(group_start_state.velocity.begin(), group_start_state.velocity.end(),
147  [this](double v) { return std::fabs(v) < this->VELOCITY_TOLERANCE; }))
148  {
149  throw NonZeroVelocityInStartState("Trajectory Generator does not allow non-zero start velocity");
150  }
151 }
152 
153 void TrajectoryGenerator::checkJointGoalConstraint(const moveit_msgs::msg::Constraints& constraint,
154  const std::vector<std::string>& expected_joint_names,
155  const std::string& group_name) const
156 {
157  for (auto const& joint_constraint : constraint.joint_constraints)
158  {
159  const std::string& curr_joint_name{ joint_constraint.joint_name };
160  if (std::find(expected_joint_names.cbegin(), expected_joint_names.cend(), curr_joint_name) ==
161  expected_joint_names.cend())
162  {
163  std::ostringstream os;
164  os << "Cannot find joint \"" << curr_joint_name << "\" from start state in goal constraint";
165  throw StartStateGoalStateMismatch(os.str());
166  }
167 
168  if (!robot_model_->getJointModelGroup(group_name)->hasJointModel(curr_joint_name))
169  {
170  std::ostringstream os;
171  os << "Joint \"" << curr_joint_name << "\" does not belong to group \"" << group_name << "\"";
172  throw JointConstraintDoesNotBelongToGroup(os.str());
173  }
174 
175  if (!planner_limits_.getJointLimitContainer().verifyPositionLimit(curr_joint_name, joint_constraint.position))
176  {
177  std::ostringstream os;
178  os << "Joint \"" << curr_joint_name << "\" violates joint limits in goal constraints";
179  throw JointsOfGoalOutOfRange(os.str());
180  }
181  }
182 }
183 
184 void TrajectoryGenerator::checkCartesianGoalConstraint(const moveit_msgs::msg::Constraints& constraint,
185  const std::string& group_name) const
186 {
187  assert(constraint.position_constraints.size() == 1);
188  assert(constraint.orientation_constraints.size() == 1);
189  const moveit_msgs::msg::PositionConstraint& pos_constraint{ constraint.position_constraints.front() };
190  const moveit_msgs::msg::OrientationConstraint& ori_constraint{ constraint.orientation_constraints.front() };
191 
192  if (pos_constraint.link_name.empty())
193  {
194  throw PositionConstraintNameMissing("Link name of position constraint missing");
195  }
196 
197  if (ori_constraint.link_name.empty())
198  {
199  throw OrientationConstraintNameMissing("Link name of orientation constraint missing");
200  }
201 
202  if (pos_constraint.link_name != ori_constraint.link_name)
203  {
204  std::ostringstream os;
205  os << "Position and orientation constraint name do not match"
206  << "(Position constraint name: \"" << pos_constraint.link_name << "\" | Orientation constraint name: \""
207  << ori_constraint.link_name << "\")";
208  throw PositionOrientationConstraintNameMismatch(os.str());
209  }
210 
211  if (!robot_model_->getJointModelGroup(group_name)->canSetStateFromIK(pos_constraint.link_name))
212  {
213  std::ostringstream os;
214  os << "No IK solver available for link: \"" << pos_constraint.link_name << "\"";
215  throw NoIKSolverAvailable(os.str());
216  }
217 
218  if (pos_constraint.constraint_region.primitive_poses.empty())
219  {
220  throw NoPrimitivePoseGiven("Primitive pose in position constraints of goal missing");
221  }
222 }
223 
224 void TrajectoryGenerator::checkGoalConstraints(
225  const moveit_msgs::msg::MotionPlanRequest::_goal_constraints_type& goal_constraints,
226  const std::vector<std::string>& expected_joint_names, const std::string& group_name) const
227 {
228  if (goal_constraints.size() != 1)
229  {
230  std::ostringstream os;
231  os << "Exactly one goal constraint required, but " << goal_constraints.size() << " goal constraints given";
232  throw NotExactlyOneGoalConstraintGiven(os.str());
233  }
234 
235  const moveit_msgs::msg::Constraints& goal_con{ goal_constraints.front() };
236  if (!isOnlyOneGoalTypeGiven(goal_con))
237  {
238  throw OnlyOneGoalTypeAllowed("Only cartesian XOR joint goal allowed");
239  }
240 
241  if (isJointGoalGiven(goal_con))
242  {
243  checkJointGoalConstraint(goal_con, expected_joint_names, group_name);
244  }
245  else
246  {
247  checkCartesianGoalConstraint(goal_con, group_name);
248  }
249 }
250 
251 void TrajectoryGenerator::validateRequest(const planning_interface::MotionPlanRequest& req) const
252 {
253  checkVelocityScaling(req.max_velocity_scaling_factor);
254  checkAccelerationScaling(req.max_acceleration_scaling_factor);
255  checkForValidGroupName(req.group_name);
256  checkStartState(req.start_state, req.group_name);
257  checkGoalConstraints(req.goal_constraints, req.start_state.joint_state.name, req.group_name);
258 }
259 
260 void TrajectoryGenerator::setSuccessResponse(const moveit::core::RobotState& start_state, const std::string& group_name,
261  const trajectory_msgs::msg::JointTrajectory& joint_trajectory,
262  const rclcpp::Time& planning_start,
264 {
265  auto rt = std::make_shared<robot_trajectory::RobotTrajectory>(robot_model_, group_name);
266  rt->setRobotTrajectoryMsg(start_state, joint_trajectory);
267 
268  res.trajectory_ = rt;
269  res.error_code_.val = moveit_msgs::msg::MoveItErrorCodes::SUCCESS;
270  res.planning_time_ = (clock_->now() - planning_start).seconds();
271 }
272 
273 void TrajectoryGenerator::setFailureResponse(const rclcpp::Time& planning_start,
275 {
276  if (res.trajectory_)
277  {
278  res.trajectory_->clear();
279  }
280  res.planning_time_ = (clock_->now() - planning_start).seconds();
281 }
282 
283 std::unique_ptr<KDL::VelocityProfile>
284 TrajectoryGenerator::cartesianTrapVelocityProfile(const double& max_velocity_scaling_factor,
285  const double& max_acceleration_scaling_factor,
286  const std::unique_ptr<KDL::Path>& path) const
287 {
288  std::unique_ptr<KDL::VelocityProfile> vp_trans = std::make_unique<KDL::VelocityProfile_Trap>(
289  max_velocity_scaling_factor * planner_limits_.getCartesianLimits().getMaxTranslationalVelocity(),
290  max_acceleration_scaling_factor * planner_limits_.getCartesianLimits().getMaxTranslationalAcceleration());
291 
292  if (path->PathLength() > std::numeric_limits<double>::epsilon()) // avoid division by zero
293  {
294  vp_trans->SetProfile(0, path->PathLength());
295  }
296  else
297  {
298  vp_trans->SetProfile(0, std::numeric_limits<double>::epsilon());
299  }
300  return vp_trans;
301 }
302 
303 bool TrajectoryGenerator::generate(const planning_scene::PlanningSceneConstPtr& scene,
305  planning_interface::MotionPlanResponse& res, double sampling_time)
306 {
307  RCLCPP_INFO_STREAM(LOGGER, "Generating " << req.planner_id << " trajectory...");
308  rclcpp::Time planning_begin = clock_->now();
309 
310  try
311  {
312  validateRequest(req);
313  }
314  catch (const MoveItErrorCodeException& ex)
315  {
316  RCLCPP_ERROR_STREAM(LOGGER, ex.what());
317  res.error_code_.val = ex.getErrorCode();
318  setFailureResponse(planning_begin, res);
319  return false;
320  }
321 
322  try
323  {
324  cmdSpecificRequestValidation(req);
325  }
326  catch (const MoveItErrorCodeException& ex)
327  {
328  RCLCPP_ERROR_STREAM(LOGGER, ex.what());
329  res.error_code_.val = ex.getErrorCode();
330  setFailureResponse(planning_begin, res);
331  return false;
332  }
333 
334  MotionPlanInfo plan_info;
335  try
336  {
337  extractMotionPlanInfo(scene, req, plan_info);
338  }
339  catch (const MoveItErrorCodeException& ex)
340  {
341  RCLCPP_ERROR_STREAM(LOGGER, ex.what());
342  res.error_code_.val = ex.getErrorCode();
343  setFailureResponse(planning_begin, res);
344  return false;
345  }
346 
347  trajectory_msgs::msg::JointTrajectory joint_trajectory;
348  try
349  {
350  plan(scene, req, plan_info, sampling_time, joint_trajectory);
351  }
352  catch (const MoveItErrorCodeException& ex)
353  {
354  RCLCPP_ERROR_STREAM(LOGGER, ex.what());
355  res.error_code_.val = ex.getErrorCode();
356  setFailureResponse(planning_begin, res);
357  return false;
358  }
359 
360  moveit::core::RobotState start_state(scene->getCurrentState());
361  moveit::core::robotStateMsgToRobotState(req.start_state, start_state, true);
362  setSuccessResponse(start_state, req.group_name, joint_trajectory, planning_begin, res);
363  return true;
364 }
365 
366 } // namespace pilz_industrial_motion_planner
Representation of a robot's state. This includes position, velocity, acceleration and effort.
Definition: robot_state.h:90
double getMaxTranslationalAcceleration() const
Return the maximal translational acceleration [m/s^2], 0 if nothing was set.
double getMaxTranslationalVelocity() const
Return the maximal translational velocity [m/s], 0 if nothing was set.
bool verifyPositionLimit(const std::string &joint_name, const double &joint_position) const
verify position limit of single joint
const JointLimitsContainer & getJointLimitContainer() const
Obtain the Joint Limits from the container.
const CartesianLimit & getCartesianLimits() const
Return the cartesian limits.
Exception storing an moveit_msgs::msg::MoveItErrorCodes value.
virtual const moveit_msgs::msg::MoveItErrorCodes::_val_type & getErrorCode() const =0
This class is used to extract needed information from motion plan request.
const moveit::core::RobotModelConstPtr robot_model_
bool generate(const planning_scene::PlanningSceneConstPtr &scene, const planning_interface::MotionPlanRequest &req, planning_interface::MotionPlanResponse &res, double sampling_time=0.1)
generate robot trajectory with given sampling time
std::unique_ptr< KDL::VelocityProfile > cartesianTrapVelocityProfile(const double &max_velocity_scaling_factor, const double &max_acceleration_scaling_factor, const std::unique_ptr< KDL::Path > &path) const
build cartesian velocity profile for the path
const pilz_industrial_motion_planner::LimitsContainer planner_limits_
const std::unique_ptr< rclcpp::Clock > clock_
bool robotStateMsgToRobotState(const Transforms &tf, const moveit_msgs::msg::RobotState &robot_state, RobotState &state, bool copy_attached_bodies=true)
Convert a robot state msg (with accompanying extra transforms) to a MoveIt robot state.
scene
Definition: pick.py:52
Definition: plan.py:1
moveit_msgs::msg::MotionPlanRequest MotionPlanRequest
robot_trajectory::RobotTrajectoryPtr trajectory_
moveit_msgs::msg::MoveItErrorCodes error_code_