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