moveit2
The MoveIt Motion Planning Framework for ROS 2.
command_list_manager.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 <rclcpp/logger.hpp>
38 #include <rclcpp/logging.hpp>
39 #include <cassert>
40 #include <functional>
41 #include <sstream>
42 
45 #include <moveit/utils/logger.hpp>
46 
47 #include "cartesian_limits_parameters.hpp"
52 
54 {
55 namespace
56 {
57 const std::string PARAM_NAMESPACE_LIMITS = "robot_description_planning";
58 rclcpp::Logger getLogger()
59 {
60  return moveit::getLogger("pilz_command_list_manager");
61 }
62 } // namespace
63 
64 CommandListManager::CommandListManager(const rclcpp::Node::SharedPtr& node,
65  const moveit::core::RobotModelConstPtr& model)
66  : node_(node), model_(model)
67 {
68  // Obtain the aggregated joint limits
69  pilz_industrial_motion_planner::JointLimitsContainer aggregated_limit_active_joints;
70 
72  node_, PARAM_NAMESPACE_LIMITS, model_->getActiveJointModels());
73 
74  param_listener_ =
75  std::make_shared<cartesian_limits::ParamListener>(node, PARAM_NAMESPACE_LIMITS + ".cartesian_limits");
76  params_ = param_listener_->get_params();
77 
79  limits.setJointLimits(aggregated_limit_active_joints);
80  limits.setCartesianLimits(params_);
81 
82  plan_comp_builder_.setModel(model);
83  plan_comp_builder_.setBlender(std::unique_ptr<pilz_industrial_motion_planner::TrajectoryBlender>(
85 }
86 
87 RobotTrajCont CommandListManager::solve(const planning_scene::PlanningSceneConstPtr& planning_scene,
88  const planning_pipeline::PlanningPipelinePtr& planning_pipeline,
89  const moveit_msgs::msg::MotionSequenceRequest& req_list)
90 {
91  if (req_list.items.empty())
92  {
93  return RobotTrajCont();
94  }
95 
96  checkForNegativeRadii(req_list);
97  checkLastBlendRadiusZero(req_list);
98  checkStartStates(req_list);
99 
100  MotionResponseCont resp_cont{ solveSequenceItems(planning_scene, planning_pipeline, req_list) };
101 
102  assert(model_);
103  RadiiCont radii{ extractBlendRadii(*model_, req_list) };
104  checkForOverlappingRadii(resp_cont, radii);
105 
106  plan_comp_builder_.reset();
107  for (MotionResponseCont::size_type i = 0; i < resp_cont.size(); ++i)
108  {
109  plan_comp_builder_.append(planning_scene, resp_cont.at(i).trajectory,
110  // The blend radii has to be "attached" to
111  // the second part of a blend trajectory,
112  // therefore: "i-1".
113  (i > 0 ? radii.at(i - 1) : 0.));
114  }
115  return plan_comp_builder_.build();
116 }
117 
118 bool CommandListManager::checkRadiiForOverlap(const robot_trajectory::RobotTrajectory& traj_A, const double radii_A,
119  const robot_trajectory::RobotTrajectory& traj_B,
120  const double radii_B) const
121 {
122  // No blending between trajectories from different groups
123  if (traj_A.getGroupName() != traj_B.getGroupName())
124  {
125  return false;
126  }
127 
128  auto sum_radii{ radii_A + radii_B };
129  if (sum_radii == 0.)
130  {
131  return false;
132  }
133 
134  const std::string& blend_frame{ getSolverTipFrame(model_->getJointModelGroup(traj_A.getGroupName())) };
135  auto distance_endpoints = (traj_A.getLastWayPoint().getFrameTransform(blend_frame).translation() -
136  traj_B.getLastWayPoint().getFrameTransform(blend_frame).translation())
137  .norm();
138  return distance_endpoints <= sum_radii;
139 }
140 
141 void CommandListManager::checkForOverlappingRadii(const MotionResponseCont& resp_cont, const RadiiCont& radii) const
142 {
143  if (resp_cont.empty())
144  {
145  return;
146  }
147  if (resp_cont.size() < 3)
148  {
149  return;
150  }
151 
152  for (MotionResponseCont::size_type i = 0; i < resp_cont.size() - 2; ++i)
153  {
154  if (checkRadiiForOverlap(*(resp_cont.at(i).trajectory), radii.at(i), *(resp_cont.at(i + 1).trajectory),
155  radii.at(i + 1)))
156  {
157  std::ostringstream os;
158  os << "Overlapping blend radii between command [" << i << "] and [" << i + 1 << "].";
159  throw OverlappingBlendRadiiException(os.str());
160  }
161  }
162 }
163 
164 CommandListManager::RobotState_OptRef
165 CommandListManager::getPreviousEndState(const MotionResponseCont& motion_plan_responses, const std::string& group_name)
166 {
167  for (MotionResponseCont::const_reverse_iterator it = motion_plan_responses.crbegin();
168  it != motion_plan_responses.crend(); ++it)
169  {
170  if (it->trajectory->getGroupName() == group_name)
171  {
172  return std::reference_wrapper(it->trajectory->getLastWayPoint());
173  }
174  }
175  return {};
176 }
177 
178 void CommandListManager::setStartState(const MotionResponseCont& motion_plan_responses, const std::string& group_name,
179  moveit_msgs::msg::RobotState& start_state)
180 {
181  RobotState_OptRef rob_state_op{ getPreviousEndState(motion_plan_responses, group_name) };
182  if (rob_state_op)
183  {
184  moveit::core::robotStateToRobotStateMsg(rob_state_op.value(), start_state);
185  }
186 }
187 
188 bool CommandListManager::isInvalidBlendRadii(const moveit::core::RobotModel& model,
189  const moveit_msgs::msg::MotionSequenceItem& item_A,
190  const moveit_msgs::msg::MotionSequenceItem& item_B)
191 {
192  // Zero blend radius is always valid
193  if (item_A.blend_radius == 0.)
194  {
195  return false;
196  }
197 
198  // No blending between different groups
199  if (item_A.req.group_name != item_B.req.group_name)
200  {
201  RCLCPP_WARN_STREAM(getLogger(), "Blending between different groups (in this case: \""
202  << item_A.req.group_name << "\" and \"" << item_B.req.group_name
203  << "\") not allowed");
204  return true;
205  }
206 
207  // No blending for groups without solver
208  if (!hasSolver(model.getJointModelGroup(item_A.req.group_name)))
209  {
210  RCLCPP_WARN_STREAM(getLogger(), "Blending for groups without solver not allowed");
211  return true;
212  }
213 
214  return false;
215 }
216 
217 CommandListManager::RadiiCont
218 CommandListManager::extractBlendRadii(const moveit::core::RobotModel& model,
219  const moveit_msgs::msg::MotionSequenceRequest& req_list)
220 {
221  RadiiCont radii(req_list.items.size(), 0.);
222  for (RadiiCont::size_type i = 0; i < (radii.size() - 1); ++i)
223  {
224  if (isInvalidBlendRadii(model, req_list.items.at(i), req_list.items.at(i + 1)))
225  {
226  RCLCPP_WARN_STREAM(getLogger(), "Invalid blend radii between commands: [" << i << "] and [" << i + 1
227  << "] => Blend radii set to zero");
228  continue;
229  }
230  radii.at(i) = req_list.items.at(i).blend_radius;
231  }
232  return radii;
233 }
234 
235 CommandListManager::MotionResponseCont
236 CommandListManager::solveSequenceItems(const planning_scene::PlanningSceneConstPtr& planning_scene,
237  const planning_pipeline::PlanningPipelinePtr& planning_pipeline,
238  const moveit_msgs::msg::MotionSequenceRequest& req_list) const
239 {
240  MotionResponseCont motion_plan_responses;
241  size_t curr_req_index{ 0 };
242  const size_t num_req{ req_list.items.size() };
243  for (const auto& seq_item : req_list.items)
244  {
245  planning_interface::MotionPlanRequest req{ seq_item.req };
246  setStartState(motion_plan_responses, req.group_name, req.start_state);
247 
249  if (!planning_pipeline->generatePlan(planning_scene, req, res))
250  {
251  RCLCPP_ERROR(getLogger(), "Generating a plan with planning pipeline failed.");
252  res.error_code.val = moveit_msgs::msg::MoveItErrorCodes::FAILURE;
253  }
254  if (res.error_code.val != res.error_code.SUCCESS)
255  {
256  std::ostringstream os;
257  os << "Could not solve request\n"; // TODO(henning): re-enable "---\n" << req << "\n---\n";
258  throw PlanningPipelineException(os.str(), res.error_code.val);
259  }
260  motion_plan_responses.emplace_back(res);
261  RCLCPP_DEBUG_STREAM(getLogger(), "Solved [" << ++curr_req_index << '/' << num_req << ']');
262  }
263  return motion_plan_responses;
264 }
265 
266 void CommandListManager::checkForNegativeRadii(const moveit_msgs::msg::MotionSequenceRequest& req_list)
267 {
268  if (!std::all_of(req_list.items.begin(), req_list.items.end(),
269  [](const moveit_msgs::msg::MotionSequenceItem& req) { return (req.blend_radius >= 0.); }))
270  {
271  throw NegativeBlendRadiusException("All blending radii MUST be non negative");
272  }
273 }
274 
275 void CommandListManager::checkStartStatesOfGroup(const moveit_msgs::msg::MotionSequenceRequest& req_list,
276  const std::string& group_name)
277 {
278  bool first_elem{ true };
279  for (const moveit_msgs::msg::MotionSequenceItem& item : req_list.items)
280  {
281  if (item.req.group_name != group_name)
282  {
283  continue;
284  }
285 
286  if (first_elem)
287  {
288  first_elem = false;
289  continue;
290  }
291 
292  if (!(item.req.start_state.joint_state.position.empty() && item.req.start_state.joint_state.velocity.empty() &&
293  item.req.start_state.joint_state.effort.empty() && item.req.start_state.joint_state.name.empty()))
294  {
295  std::ostringstream os;
296  os << "Only the first request is allowed to have a start state, but"
297  << " the requests for group: \"" << group_name << "\" violate the rule";
298  throw StartStateSetException(os.str());
299  }
300  }
301 }
302 
303 void CommandListManager::checkStartStates(const moveit_msgs::msg::MotionSequenceRequest& req_list)
304 {
305  if (req_list.items.size() <= 1)
306  {
307  return;
308  }
309 
310  GroupNamesCont group_names{ getGroupNames(req_list) };
311  for (const auto& curr_group_name : group_names)
312  {
313  checkStartStatesOfGroup(req_list, curr_group_name);
314  }
315 }
316 
317 CommandListManager::GroupNamesCont
318 CommandListManager::getGroupNames(const moveit_msgs::msg::MotionSequenceRequest& req_list)
319 {
320  GroupNamesCont group_names;
321  std::for_each(req_list.items.cbegin(), req_list.items.cend(),
322  [&group_names](const moveit_msgs::msg::MotionSequenceItem& item) {
323  if (std::find(group_names.cbegin(), group_names.cend(), item.req.group_name) == group_names.cend())
324  {
325  group_names.emplace_back(item.req.group_name);
326  }
327  });
328  return group_names;
329 }
330 
331 } // namespace pilz_industrial_motion_planner
Definition of a kinematic model. This class is not thread safe, however multiple instances can be cre...
Definition: robot_model.h:76
const JointModelGroup * getJointModelGroup(const std::string &name) const
Get a joint group from this model (by name)
const Eigen::Isometry3d & getFrameTransform(const std::string &frame_id, bool *frame_found=nullptr)
Get the transformation matrix from the model frame (root of model) to the frame identified by frame_i...
CommandListManager(const rclcpp::Node::SharedPtr &node, const moveit::core::RobotModelConstPtr &model)
RobotTrajCont solve(const planning_scene::PlanningSceneConstPtr &planning_scene, const planning_pipeline::PlanningPipelinePtr &planning_pipeline, const moveit_msgs::msg::MotionSequenceRequest &req_list)
Generates trajectories for the specified list of motion commands.
static JointLimitsContainer getAggregatedLimits(const rclcpp::Node::SharedPtr &node, const std::string &param_namespace, const std::vector< const moveit::core::JointModel * > &joint_models)
Aggregates(combines) the joint limits from joint model and node parameters. The rules for the combina...
Container for JointLimits, essentially a map with convenience functions. Adds the ability to as for l...
This class combines CartesianLimit and JointLimits into on single class.
void setCartesianLimits(cartesian_limits::Params &cartesian_limit)
Set cartesian limits.
void setJointLimits(JointLimitsContainer &joint_limits)
Set joint limits.
std::vector< robot_trajectory::RobotTrajectoryPtr > build() const
void reset()
Clears the trajectory container under construction.
void setModel(const moveit::core::RobotModelConstPtr &model)
Sets the robot model needed to create new trajectory elements.
void setBlender(std::unique_ptr< pilz_industrial_motion_planner::TrajectoryBlender > blender)
Sets the blender used to blend two trajectories.
void append(const planning_scene::PlanningSceneConstPtr &planning_scene, const robot_trajectory::RobotTrajectoryPtr &other, const double blend_radius)
Appends the specified trajectory to the trajectory container under construction.
Trajectory blender implementing transition window algorithm.
Maintain a sequence of waypoints and the time durations between these waypoints.
const std::string & getGroupName() const
const moveit::core::RobotState & getLastWayPoint() const
void robotStateToRobotStateMsg(const RobotState &state, moveit_msgs::msg::RobotState &robot_state, bool copy_attached_bodies=true)
Convert a MoveIt robot state to a robot state message.
rclcpp::Logger getLogger(const std::string &name)
Creates a namespaced logger.
Definition: logger.cpp:79
std::vector< robot_trajectory::RobotTrajectoryPtr > RobotTrajCont
moveit_msgs::msg::MotionPlanRequest MotionPlanRequest
This namespace includes the central class for representing planning contexts.
Response to a planning query.
moveit::core::MoveItErrorCode error_code
const std::string PARAM_NAMESPACE_LIMITS