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.size() != start_state.joint_state.position.size())
118  {
119  throw SizeMismatchInStartState("Joint state name and position do not match in start state");
120  }
121 
122  sensor_msgs::msg::JointState group_start_state{ filterGroupValues(start_state.joint_state, group) };
123 
124  // verify joint position limits
125  const JointLimitsContainer& limits{ planner_limits_.getJointLimitContainer() };
126  std::string error_msg;
127  for (auto joint : boost::combine(group_start_state.name, group_start_state.position))
128  {
129  if (!limits.verifyPositionLimit(joint.get<0>(), joint.get<1>()))
130  {
131  error_msg.append(error_msg.empty() ? "start state joints outside their position limits: " : ", ");
132  error_msg.append(joint.get<0>());
133  }
134  }
135  if (!error_msg.empty())
136  {
137  throw JointsOfStartStateOutOfRange(error_msg);
138  }
139 
140  // does not allow start velocity
141  if (!std::all_of(group_start_state.velocity.begin(), group_start_state.velocity.end(),
142  [this](double v) { return std::fabs(v) < this->VELOCITY_TOLERANCE; }))
143  {
144  throw NonZeroVelocityInStartState("Trajectory Generator does not allow non-zero start velocity");
145  }
146 }
147 
148 void TrajectoryGenerator::checkJointGoalConstraint(const moveit_msgs::msg::Constraints& constraint,
149  const std::string& group_name) const
150 {
151  for (auto const& joint_constraint : constraint.joint_constraints)
152  {
153  const std::string& curr_joint_name{ joint_constraint.joint_name };
154 
155  if (!robot_model_->getJointModelGroup(group_name)->hasJointModel(curr_joint_name))
156  {
157  std::ostringstream os;
158  os << "Joint \"" << curr_joint_name << "\" does not belong to group \"" << group_name << "\"";
159  throw JointConstraintDoesNotBelongToGroup(os.str());
160  }
161 
162  if (!planner_limits_.getJointLimitContainer().verifyPositionLimit(curr_joint_name, joint_constraint.position))
163  {
164  std::ostringstream os;
165  os << "Joint \"" << curr_joint_name << "\" violates joint limits in goal constraints";
166  throw JointsOfGoalOutOfRange(os.str());
167  }
168  }
169 }
170 
171 void TrajectoryGenerator::checkCartesianGoalConstraint(const moveit_msgs::msg::Constraints& constraint,
172  const moveit::core::RobotState& robot_state,
173  const moveit::core::JointModelGroup* const jmg) const
174 {
175  assert(constraint.position_constraints.size() == 1);
176  assert(constraint.orientation_constraints.size() == 1);
177  const moveit_msgs::msg::PositionConstraint& pos_constraint{ constraint.position_constraints.front() };
178  const moveit_msgs::msg::OrientationConstraint& ori_constraint{ constraint.orientation_constraints.front() };
179 
180  if (pos_constraint.link_name.empty())
181  {
182  throw PositionConstraintNameMissing("Link name of position constraint missing");
183  }
184 
185  if (ori_constraint.link_name.empty())
186  {
187  throw OrientationConstraintNameMissing("Link name of orientation constraint missing");
188  }
189 
190  if (pos_constraint.link_name != ori_constraint.link_name)
191  {
192  std::ostringstream os;
193  os << "Position and orientation constraint name do not match"
194  << "(Position constraint name: \"" << pos_constraint.link_name << "\" | Orientation constraint name: \""
195  << ori_constraint.link_name << "\")";
196  throw PositionOrientationConstraintNameMismatch(os.str());
197  }
198 
199  const auto& lm = robot_state.getRigidlyConnectedParentLinkModel(pos_constraint.link_name);
200  if (!lm || !jmg->canSetStateFromIK(lm->getName()))
201  {
202  std::ostringstream os;
203  os << "No IK solver available for link: \"" << pos_constraint.link_name << "\"";
204  throw NoIKSolverAvailable(os.str());
205  }
206 
207  if (pos_constraint.constraint_region.primitive_poses.empty())
208  {
209  throw NoPrimitivePoseGiven("Primitive pose in position constraints of goal missing");
210  }
211 }
212 
213 void TrajectoryGenerator::checkGoalConstraints(
214  const moveit_msgs::msg::MotionPlanRequest::_goal_constraints_type& goal_constraints, const std::string& group_name,
215  const moveit::core::RobotState& rstate) const
216 {
217  if (goal_constraints.size() != 1)
218  {
219  std::ostringstream os;
220  os << "Exactly one goal constraint required, but " << goal_constraints.size() << " goal constraints given";
221  throw NotExactlyOneGoalConstraintGiven(os.str());
222  }
223 
224  const moveit_msgs::msg::Constraints& goal_con{ goal_constraints.front() };
225  if (!isOnlyOneGoalTypeGiven(goal_con))
226  {
227  throw OnlyOneGoalTypeAllowed("Only cartesian XOR joint goal allowed");
228  }
229 
230  if (isJointGoalGiven(goal_con))
231  {
232  checkJointGoalConstraint(goal_con, group_name);
233  }
234  else
235  {
236  checkCartesianGoalConstraint(goal_con, rstate, robot_model_->getJointModelGroup(group_name));
237  }
238 }
239 
240 void TrajectoryGenerator::validateRequest(const planning_interface::MotionPlanRequest& req,
241  const moveit::core::RobotState& rstate) const
242 {
243  checkVelocityScaling(req.max_velocity_scaling_factor);
244  checkAccelerationScaling(req.max_acceleration_scaling_factor);
245  checkForValidGroupName(req.group_name);
246  checkStartState(req.start_state, req.group_name);
247  checkGoalConstraints(req.goal_constraints, req.group_name, rstate);
248 }
249 
250 void TrajectoryGenerator::setSuccessResponse(const moveit::core::RobotState& start_state, const std::string& group_name,
251  const trajectory_msgs::msg::JointTrajectory& joint_trajectory,
252  const rclcpp::Time& planning_start,
254 {
255  auto rt = std::make_shared<robot_trajectory::RobotTrajectory>(robot_model_, group_name);
256  rt->setRobotTrajectoryMsg(start_state, joint_trajectory);
257 
258  res.trajectory_ = rt;
259  res.error_code_.val = moveit_msgs::msg::MoveItErrorCodes::SUCCESS;
260  res.planning_time_ = (clock_->now() - planning_start).seconds();
261 }
262 
263 void TrajectoryGenerator::setFailureResponse(const rclcpp::Time& planning_start,
265 {
266  if (res.trajectory_)
267  {
268  res.trajectory_->clear();
269  }
270  res.planning_time_ = (clock_->now() - planning_start).seconds();
271 }
272 
273 std::unique_ptr<KDL::VelocityProfile>
274 TrajectoryGenerator::cartesianTrapVelocityProfile(const double& max_velocity_scaling_factor,
275  const double& max_acceleration_scaling_factor,
276  const std::unique_ptr<KDL::Path>& path) const
277 {
278  std::unique_ptr<KDL::VelocityProfile> vp_trans = std::make_unique<KDL::VelocityProfile_Trap>(
279  max_velocity_scaling_factor * planner_limits_.getCartesianLimits().getMaxTranslationalVelocity(),
280  max_acceleration_scaling_factor * planner_limits_.getCartesianLimits().getMaxTranslationalAcceleration());
281 
282  if (path->PathLength() > std::numeric_limits<double>::epsilon()) // avoid division by zero
283  {
284  vp_trans->SetProfile(0, path->PathLength());
285  }
286  else
287  {
288  vp_trans->SetProfile(0, std::numeric_limits<double>::epsilon());
289  }
290  return vp_trans;
291 }
292 
293 bool TrajectoryGenerator::generate(const planning_scene::PlanningSceneConstPtr& scene,
295  planning_interface::MotionPlanResponse& res, double sampling_time)
296 {
297  RCLCPP_INFO_STREAM(LOGGER, "Generating " << req.planner_id << " trajectory...");
298  rclcpp::Time planning_begin = clock_->now();
299 
300  try
301  {
302  validateRequest(req, scene->getCurrentState());
303  }
304  catch (const MoveItErrorCodeException& ex)
305  {
306  RCLCPP_ERROR_STREAM(LOGGER, ex.what());
307  res.error_code_.val = ex.getErrorCode();
308  setFailureResponse(planning_begin, res);
309  return false;
310  }
311 
312  try
313  {
314  cmdSpecificRequestValidation(req);
315  }
316  catch (const MoveItErrorCodeException& ex)
317  {
318  RCLCPP_ERROR_STREAM(LOGGER, ex.what());
319  res.error_code_.val = ex.getErrorCode();
320  setFailureResponse(planning_begin, res);
321  return false;
322  }
323 
324  MotionPlanInfo plan_info(scene, req);
325  try
326  {
327  extractMotionPlanInfo(scene, req, plan_info);
328  }
329  catch (const MoveItErrorCodeException& ex)
330  {
331  RCLCPP_ERROR_STREAM(LOGGER, ex.what());
332  res.error_code_.val = ex.getErrorCode();
333  setFailureResponse(planning_begin, res);
334  return false;
335  }
336 
337  trajectory_msgs::msg::JointTrajectory joint_trajectory;
338  try
339  {
340  plan(plan_info.start_scene, req, plan_info, sampling_time, joint_trajectory);
341  }
342  catch (const MoveItErrorCodeException& ex)
343  {
344  RCLCPP_ERROR_STREAM(LOGGER, ex.what());
345  res.error_code_.val = ex.getErrorCode();
346  setFailureResponse(planning_begin, res);
347  return false;
348  }
349 
350  setSuccessResponse(plan_info.start_scene->getCurrentState(), req.group_name, joint_trajectory, planning_begin, res);
351  return true;
352 }
353 
354 TrajectoryGenerator::MotionPlanInfo::MotionPlanInfo(const planning_scene::PlanningSceneConstPtr& scene,
356 {
357  auto ps = scene->diff();
358  auto& start_state = ps->getCurrentStateNonConst();
359  // update start state from req
360  moveit::core::robotStateMsgToRobotState(scene->getTransforms(), req.start_state, start_state);
361  start_state.update();
362  start_scene = std::move(ps);
363 
364  // initialize info.start_joint_position with active joint values from start_state
365  const double* positions = start_state.getVariablePositions();
366  for (const auto* jm : start_state.getRobotModel()->getJointModelGroup(req.group_name)->getActiveJointModels())
367  {
368  const auto& names = jm->getVariableNames();
369  for (std::size_t i = 0, j = jm->getFirstVariableIndex(); i < jm->getVariableCount(); ++i, ++j)
370  {
371  start_joint_position[names[i]] = positions[j];
372  }
373  }
374 }
375 
376 } // namespace pilz_industrial_motion_planner
bool canSetStateFromIK(const std::string &tip) const
Representation of a robot's state. This includes position, velocity, acceleration and effort.
Definition: robot_state.h:90
const RobotModelConstPtr & getRobotModel() const
Get the robot model this state is constructed for.
Definition: robot_state.h:104
double * getVariablePositions()
Get a raw pointer to the positions of the variables stored in this state. Use carefully....
Definition: robot_state.h:147
void update(bool force=false)
Update all transforms.
const moveit::core::LinkModel * getRigidlyConnectedParentLinkModel(const std::string &frame) const
Get the latest link upwards the kinematic tree which is only connected via fixed joints.
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.
MotionPlanInfo(const planning_scene::PlanningSceneConstPtr &scene, const planning_interface::MotionPlanRequest &req)
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_