moveit2
The MoveIt Motion Planning Framework for ROS 2.
trajectory_cache.cpp
Go to the documentation of this file.
1 // Copyright 2024 Intrinsic Innovation LLC.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
20 #include <memory>
21 #include <vector>
22 
26 #include <warehouse_ros/message_collection.h>
27 
28 #include <rclcpp/rclcpp.hpp>
29 #include <rclcpp/logging.hpp>
31 
32 namespace moveit_ros
33 {
34 namespace trajectory_cache
35 {
36 
37 using warehouse_ros::MessageWithMetadata;
38 using warehouse_ros::Metadata;
39 using warehouse_ros::Query;
40 
41 // Utils =======================================================================
42 
43 // Ensure we always have a workspace frame ID.
44 //
45 // It makes sense to use getModelFrame() in the absence of a workspace parameter frame ID because the same method is
46 // used to populate the workspace header frame ID in the MoveGroupInterface's setWorkspace() method, which this function
47 // is associated with.
49  const moveit_msgs::msg::WorkspaceParameters& workspace_parameters)
50 {
51  if (workspace_parameters.header.frame_id.empty())
52  {
53  return move_group.getRobotModel()->getModelFrame();
54  }
55  else
56  {
57  return workspace_parameters.header.frame_id;
58  }
59 }
60 
61 // Ensure we always have a cartesian path request frame ID.
62 //
63 // It makes sense to use getPoseReferenceFrame() in the absence of a frame ID in the request because the same method is
64 // used to populate the header frame ID in the MoveGroupInterface's computeCartesianPath() method, which this function is associated with.
66  const moveit_msgs::srv::GetCartesianPath::Request& path_request)
67 {
68  if (path_request.header.frame_id.empty())
69  {
70  return move_group.getPoseReferenceFrame();
71  }
72  else
73  {
74  return path_request.header.frame_id;
75  }
76 }
77 
78 // Append a range inclusive query with some tolerance about some center value.
79 void queryAppendRangeInclusiveWithTolerance(Query& query, const std::string& name, double center, double tolerance)
80 {
81  query.appendRangeInclusive(name, center - tolerance / 2, center + tolerance / 2);
82 }
83 
84 // Sort constraint components by joint or link name.
85 void sortConstraints(std::vector<moveit_msgs::msg::JointConstraint>& joint_constraints,
86  std::vector<moveit_msgs::msg::PositionConstraint>& position_constraints,
87  std::vector<moveit_msgs::msg::OrientationConstraint>& orientation_constraints)
88 {
89  std::sort(joint_constraints.begin(), joint_constraints.end(),
90  [](const moveit_msgs::msg::JointConstraint& l, const moveit_msgs::msg::JointConstraint& r) {
91  return l.joint_name < r.joint_name;
92  });
93 
94  std::sort(position_constraints.begin(), position_constraints.end(),
95  [](const moveit_msgs::msg::PositionConstraint& l, const moveit_msgs::msg::PositionConstraint& r) {
96  return l.link_name < r.link_name;
97  });
98 
99  std::sort(orientation_constraints.begin(), orientation_constraints.end(),
100  [](const moveit_msgs::msg::OrientationConstraint& l, const moveit_msgs::msg::OrientationConstraint& r) {
101  return l.link_name < r.link_name;
102  });
103 }
104 
105 // Trajectory Cache ============================================================
106 
107 TrajectoryCache::TrajectoryCache(const rclcpp::Node::SharedPtr& node)
108  : node_(node), logger_(moveit::getLogger("moveit.ros.trajectory_cache"))
109 {
110  tf_buffer_ = std::make_unique<tf2_ros::Buffer>(node_->get_clock());
111  tf_listener_ = std::make_shared<tf2_ros::TransformListener>(*tf_buffer_);
112 }
113 
115 {
116  RCLCPP_DEBUG(logger_, "Opening trajectory cache database at: %s (Port: %d, Precision: %f)", options.db_path.c_str(),
117  options.db_port, options.exact_match_precision);
118 
119  // If the `warehouse_plugin` parameter isn't set, defaults to warehouse_ros'
120  // default.
121  db_ = moveit_warehouse::loadDatabase(node_);
122  options_ = options;
123 
124  db_->setParams(options.db_path, options.db_port);
125  return db_->connect();
126 }
127 
128 unsigned TrajectoryCache::countTrajectories(const std::string& cache_namespace)
129 {
130  auto coll = db_->openCollection<moveit_msgs::msg::RobotTrajectory>("move_group_trajectory_cache", cache_namespace);
131  return coll.count();
132 }
133 
134 unsigned TrajectoryCache::countCartesianTrajectories(const std::string& cache_namespace)
135 {
136  auto coll =
137  db_->openCollection<moveit_msgs::msg::RobotTrajectory>("move_group_cartesian_trajectory_cache", cache_namespace);
138  return coll.count();
139 }
140 
141 // =============================================================================
142 // GETTERS AND SETTERS
143 // =============================================================================
144 
145 std::string TrajectoryCache::getDbPath() const
146 {
147  return options_.db_path;
148 }
149 
151 {
152  return options_.db_port;
153 }
154 
156 {
157  return options_.exact_match_precision;
158 }
159 
160 void TrajectoryCache::setExactMatchPrecision(double exact_match_precision)
161 {
162  options_.exact_match_precision = exact_match_precision;
163 }
164 
166 {
168 }
169 
171  size_t num_additional_trajectories_to_preserve_when_deleting_worse)
172 {
174  num_additional_trajectories_to_preserve_when_deleting_worse;
175 }
176 
177 // =============================================================================
178 // MOTION PLAN TRAJECTORY CACHING
179 // =============================================================================
180 
181 std::vector<MessageWithMetadata<moveit_msgs::msg::RobotTrajectory>::ConstPtr>
183  const std::string& cache_namespace,
184  const moveit_msgs::msg::MotionPlanRequest& plan_request,
185  double start_tolerance, double goal_tolerance, bool metadata_only,
186  const std::string& sort_by, bool ascending) const
187 {
188  auto coll = db_->openCollection<moveit_msgs::msg::RobotTrajectory>("move_group_trajectory_cache", cache_namespace);
189 
190  Query::Ptr query = coll.createQuery();
191 
192  bool start_ok = this->extractAndAppendTrajectoryStartToQuery(*query, move_group, plan_request, start_tolerance);
193  bool goal_ok = this->extractAndAppendTrajectoryGoalToQuery(*query, move_group, plan_request, goal_tolerance);
194 
195  if (!start_ok || !goal_ok)
196  {
197  RCLCPP_ERROR(logger_, "Could not construct trajectory query.");
198  return {};
199  }
200 
201  return coll.queryList(query, metadata_only, sort_by, ascending);
202 }
203 
204 MessageWithMetadata<moveit_msgs::msg::RobotTrajectory>::ConstPtr TrajectoryCache::fetchBestMatchingTrajectory(
205  const moveit::planning_interface::MoveGroupInterface& move_group, const std::string& cache_namespace,
206  const moveit_msgs::msg::MotionPlanRequest& plan_request, double start_tolerance, double goal_tolerance,
207  bool metadata_only, const std::string& sort_by, bool ascending) const
208 {
209  // First find all matching, but metadata only.
210  // Then use the ID metadata of the best plan to pull the actual message.
211  auto matching_trajectories = this->fetchAllMatchingTrajectories(
212  move_group, cache_namespace, plan_request, start_tolerance, goal_tolerance, true, sort_by, ascending);
213 
214  if (matching_trajectories.empty())
215  {
216  RCLCPP_DEBUG(logger_, "No matching trajectories found.");
217  return nullptr;
218  }
219 
220  auto coll = db_->openCollection<moveit_msgs::msg::RobotTrajectory>("move_group_trajectory_cache", cache_namespace);
221 
222  // Best plan is at first index, since the lookup query was sorted by
223  // execution_time.
224  int best_trajectory_id = matching_trajectories.at(0)->lookupInt("id");
225  Query::Ptr best_query = coll.createQuery();
226  best_query->append("id", best_trajectory_id);
227 
228  return coll.findOne(best_query, metadata_only);
229 }
230 
232  const std::string& cache_namespace,
233  const moveit_msgs::msg::MotionPlanRequest& plan_request,
234  const moveit_msgs::msg::RobotTrajectory& trajectory, double execution_time_s,
235  double planning_time_s, bool prune_worse_trajectories)
236 {
237  std::string workspace_frame_id = getWorkspaceFrameId(move_group, plan_request.workspace_parameters);
238 
239  // Check pre-conditions
240  if (!trajectory.multi_dof_joint_trajectory.points.empty())
241  {
242  RCLCPP_ERROR(logger_, "Skipping plan insert: Multi-DOF trajectory plans are not supported.");
243  return false;
244  }
245  if (workspace_frame_id.empty())
246  {
247  RCLCPP_ERROR(logger_, "Skipping plan insert: Workspace frame ID cannot be empty.");
248  return false;
249  }
250  if (trajectory.joint_trajectory.header.frame_id.empty())
251  {
252  RCLCPP_ERROR(logger_, "Skipping plan insert: Trajectory frame ID cannot be empty.");
253  return false;
254  }
255  if (workspace_frame_id != trajectory.joint_trajectory.header.frame_id)
256  {
257  RCLCPP_ERROR(logger_,
258  "Skipping plan insert: "
259  "Plan request frame (%s) does not match plan frame (%s).",
260  workspace_frame_id.c_str(), trajectory.joint_trajectory.header.frame_id.c_str());
261  return false;
262  }
263 
264  auto coll = db_->openCollection<moveit_msgs::msg::RobotTrajectory>("move_group_trajectory_cache", cache_namespace);
265 
266  // Pull out trajectories "exactly" keyed by request in cache.
267  Query::Ptr exact_query = coll.createQuery();
268 
269  bool start_query_ok = this->extractAndAppendTrajectoryStartToQuery(*exact_query, move_group, plan_request, 0);
270  bool goal_query_ok = this->extractAndAppendTrajectoryGoalToQuery(*exact_query, move_group, plan_request, 0);
271 
272  if (!start_query_ok || !goal_query_ok)
273  {
274  RCLCPP_ERROR(logger_, "Skipping plan insert: Could not construct lookup query.");
275  return false;
276  }
277 
278  auto exact_matches =
279  coll.queryList(exact_query, /*metadata_only=*/true, /*sort_by=*/"execution_time_s", /*ascending=*/true);
280 
281  double best_execution_time = std::numeric_limits<double>::infinity();
282  if (!exact_matches.empty())
283  {
284  best_execution_time = exact_matches.at(0)->lookupDouble("execution_time_s");
285 
286  if (prune_worse_trajectories)
287  {
288  size_t preserved_count = 0;
289  for (auto& match : exact_matches)
290  {
291  double match_execution_time_s = match->lookupDouble("execution_time_s");
292  if (++preserved_count > options_.num_additional_trajectories_to_preserve_when_deleting_worse &&
293  execution_time_s < match_execution_time_s)
294  {
295  int delete_id = match->lookupInt("id");
296  RCLCPP_DEBUG(logger_,
297  "Overwriting plan (id: %d): "
298  "execution_time (%es) > new trajectory's execution_time (%es)",
299  delete_id, match_execution_time_s, execution_time_s);
300 
301  Query::Ptr delete_query = coll.createQuery();
302  delete_query->append("id", delete_id);
303  coll.removeMessages(delete_query);
304  }
305  }
306  }
307  }
308 
309  // Insert if candidate is best seen.
310  if (execution_time_s < best_execution_time)
311  {
312  Metadata::Ptr insert_metadata = coll.createMetadata();
313 
314  bool start_meta_ok = this->extractAndAppendTrajectoryStartToMetadata(*insert_metadata, move_group, plan_request);
315  bool goal_meta_ok = this->extractAndAppendTrajectoryGoalToMetadata(*insert_metadata, move_group, plan_request);
316  insert_metadata->append("execution_time_s", execution_time_s);
317  insert_metadata->append("planning_time_s", planning_time_s);
318 
319  if (!start_meta_ok || !goal_meta_ok)
320  {
321  RCLCPP_ERROR(logger_, "Skipping plan insert: Could not construct insert metadata.");
322  return false;
323  }
324 
325  RCLCPP_DEBUG(logger_,
326  "Inserting trajectory: New trajectory execution_time (%es) "
327  "is better than best trajectory's execution_time (%es)",
328  execution_time_s, best_execution_time);
329 
330  coll.insert(trajectory, insert_metadata);
331  return true;
332  }
333 
334  RCLCPP_DEBUG(logger_,
335  "Skipping plan insert: New trajectory execution_time (%es) "
336  "is worse than best trajectory's execution_time (%es)",
337  execution_time_s, best_execution_time);
338  return false;
339 }
340 
341 // =============================================================================
342 // CARTESIAN TRAJECTORY CACHING
343 // =============================================================================
344 
345 moveit_msgs::srv::GetCartesianPath::Request
347  const std::vector<geometry_msgs::msg::Pose>& waypoints,
348  double max_step, double jump_threshold, bool avoid_collisions)
349 {
350  moveit_msgs::srv::GetCartesianPath::Request out;
351 
352  move_group.constructRobotState(out.start_state);
353 
354  out.group_name = move_group.getName();
355  out.max_velocity_scaling_factor = move_group.getMaxVelocityScalingFactor();
356  out.max_acceleration_scaling_factor = move_group.getMaxVelocityScalingFactor();
357 
358  out.header.frame_id = move_group.getPoseReferenceFrame();
359  out.waypoints = waypoints;
360  out.max_step = max_step;
361  out.jump_threshold = jump_threshold;
362  out.path_constraints = moveit_msgs::msg::Constraints();
363  out.avoid_collisions = avoid_collisions;
364  out.link_name = move_group.getEndEffectorLink();
365  out.header.stamp = move_group.getNode()->now();
366 
367  return out;
368 }
369 
370 std::vector<MessageWithMetadata<moveit_msgs::msg::RobotTrajectory>::ConstPtr>
372  const std::string& cache_namespace,
373  const moveit_msgs::srv::GetCartesianPath::Request& plan_request,
374  double min_fraction, double start_tolerance,
375  double goal_tolerance, bool metadata_only,
376  const std::string& sort_by, bool ascending) const
377 {
378  auto coll =
379  db_->openCollection<moveit_msgs::msg::RobotTrajectory>("move_group_cartesian_trajectory_cache", cache_namespace);
380 
381  Query::Ptr query = coll.createQuery();
382 
383  bool start_ok =
384  this->extractAndAppendCartesianTrajectoryStartToQuery(*query, move_group, plan_request, start_tolerance);
385  bool goal_ok = this->extractAndAppendCartesianTrajectoryGoalToQuery(*query, move_group, plan_request, goal_tolerance);
386 
387  if (!start_ok || !goal_ok)
388  {
389  RCLCPP_ERROR(logger_, "Could not construct cartesian trajectory query.");
390  return {};
391  }
392 
393  query->appendGTE("fraction", min_fraction);
394  return coll.queryList(query, metadata_only, sort_by, ascending);
395 }
396 
397 MessageWithMetadata<moveit_msgs::msg::RobotTrajectory>::ConstPtr TrajectoryCache::fetchBestMatchingCartesianTrajectory(
398  const moveit::planning_interface::MoveGroupInterface& move_group, const std::string& cache_namespace,
399  const moveit_msgs::srv::GetCartesianPath::Request& plan_request, double min_fraction, double start_tolerance,
400  double goal_tolerance, bool metadata_only, const std::string& sort_by, bool ascending) const
401 {
402  // First find all matching, but metadata only.
403  // Then use the ID metadata of the best plan to pull the actual message.
404  auto matching_trajectories =
405  this->fetchAllMatchingCartesianTrajectories(move_group, cache_namespace, plan_request, min_fraction,
406  start_tolerance, goal_tolerance, true, sort_by, ascending);
407 
408  if (matching_trajectories.empty())
409  {
410  RCLCPP_DEBUG(logger_, "No matching cartesian trajectories found.");
411  return nullptr;
412  }
413 
414  auto coll =
415  db_->openCollection<moveit_msgs::msg::RobotTrajectory>("move_group_cartesian_trajectory_cache", cache_namespace);
416 
417  // Best plan is at first index, since the lookup query was sorted by
418  // execution_time.
419  int best_trajectory_id = matching_trajectories.at(0)->lookupInt("id");
420  Query::Ptr best_query = coll.createQuery();
421  best_query->append("id", best_trajectory_id);
422 
423  return coll.findOne(best_query, metadata_only);
424 }
425 
427  const std::string& cache_namespace,
428  const moveit_msgs::srv::GetCartesianPath::Request& plan_request,
429  const moveit_msgs::msg::RobotTrajectory& trajectory,
430  double execution_time_s, double planning_time_s, double fraction,
431  bool prune_worse_trajectories)
432 {
433  std::string path_request_frame_id = getCartesianPathRequestFrameId(move_group, plan_request);
434 
435  // Check pre-conditions
436  if (!trajectory.multi_dof_joint_trajectory.points.empty())
437  {
438  RCLCPP_ERROR(logger_, "Skipping cartesian trajectory insert: "
439  "Multi-DOF trajectory plans are not supported.");
440  return false;
441  }
442  if (path_request_frame_id.empty())
443  {
444  RCLCPP_ERROR(logger_, "Skipping cartesian trajectory insert: Path request frame ID cannot be empty.");
445  return false;
446  }
447  if (trajectory.joint_trajectory.header.frame_id.empty())
448  {
449  RCLCPP_ERROR(logger_, "Skipping cartesian trajectory insert: Trajectory frame ID cannot be empty.");
450  return false;
451  }
452 
453  auto coll =
454  db_->openCollection<moveit_msgs::msg::RobotTrajectory>("move_group_cartesian_trajectory_cache", cache_namespace);
455 
456  // Pull out trajectories "exactly" keyed by request in cache.
457  Query::Ptr exact_query = coll.createQuery();
458 
459  bool start_query_ok =
460  this->extractAndAppendCartesianTrajectoryStartToQuery(*exact_query, move_group, plan_request, 0);
461  bool goal_query_ok = this->extractAndAppendCartesianTrajectoryGoalToQuery(*exact_query, move_group, plan_request, 0);
462  exact_query->append("fraction", fraction);
463 
464  if (!start_query_ok || !goal_query_ok)
465  {
466  RCLCPP_ERROR(logger_, "Skipping cartesian trajectory insert: Could not construct lookup query.");
467  return false;
468  }
469 
470  auto exact_matches =
471  coll.queryList(exact_query, /*metadata_only=*/true, /*sort_by=*/"execution_time_s", /*ascending=*/true);
472  double best_execution_time = std::numeric_limits<double>::infinity();
473  if (!exact_matches.empty())
474  {
475  best_execution_time = exact_matches.at(0)->lookupDouble("execution_time_s");
476 
477  if (prune_worse_trajectories)
478  {
479  size_t preserved_count = 0;
480  for (auto& match : exact_matches)
481  {
482  double match_execution_time_s = match->lookupDouble("execution_time_s");
483  if (++preserved_count > options_.num_additional_trajectories_to_preserve_when_deleting_worse &&
484  execution_time_s < match_execution_time_s)
485  {
486  int delete_id = match->lookupInt("id");
487  RCLCPP_DEBUG(logger_,
488  "Overwriting cartesian trajectory (id: %d): "
489  "execution_time (%es) > new trajectory's execution_time (%es)",
490  delete_id, match_execution_time_s, execution_time_s);
491 
492  Query::Ptr delete_query = coll.createQuery();
493  delete_query->append("id", delete_id);
494  coll.removeMessages(delete_query);
495  }
496  }
497  }
498  }
499 
500  // Insert if candidate is best seen.
501  if (execution_time_s < best_execution_time)
502  {
503  Metadata::Ptr insert_metadata = coll.createMetadata();
504 
505  bool start_meta_ok =
506  this->extractAndAppendCartesianTrajectoryStartToMetadata(*insert_metadata, move_group, plan_request);
507  bool goal_meta_ok =
508  this->extractAndAppendCartesianTrajectoryGoalToMetadata(*insert_metadata, move_group, plan_request);
509  insert_metadata->append("execution_time_s", execution_time_s);
510  insert_metadata->append("planning_time_s", planning_time_s);
511  insert_metadata->append("fraction", fraction);
512 
513  if (!start_meta_ok || !goal_meta_ok)
514  {
515  RCLCPP_ERROR(logger_, "Skipping cartesian trajectory insert: "
516  "Could not construct insert metadata.");
517  return false;
518  }
519 
520  RCLCPP_DEBUG(logger_,
521  "Inserting cartesian trajectory: New trajectory execution_time (%es) "
522  "is better than best trajectory's execution_time (%es) at fraction (%es)",
523  execution_time_s, best_execution_time, fraction);
524 
525  coll.insert(trajectory, insert_metadata);
526  return true;
527  }
528 
529  RCLCPP_DEBUG(logger_,
530  "Skipping cartesian trajectory insert: New trajectory execution_time (%es) "
531  "is worse than best trajectory's execution_time (%es) at fraction (%es)",
532  execution_time_s, best_execution_time, fraction);
533  return false;
534 }
535 
536 // =============================================================================
537 // MOTION PLAN TRAJECTORY QUERY AND METADATA CONSTRUCTION
538 // =============================================================================
539 
540 // MOTION PLAN TRAJECTORY CACHING: QUERY CONSTRUCTION ==========================
541 
542 bool TrajectoryCache::extractAndAppendTrajectoryStartToQuery(
544  const moveit_msgs::msg::MotionPlanRequest& plan_request, double match_tolerance) const
545 {
546  std::string workspace_frame_id = getWorkspaceFrameId(move_group, plan_request.workspace_parameters);
547  match_tolerance += options_.exact_match_precision;
548 
549  // Make ignored members explicit
550  if (!plan_request.start_state.multi_dof_joint_state.joint_names.empty())
551  {
552  RCLCPP_WARN(logger_, "Ignoring start_state.multi_dof_joint_states: Not supported.");
553  }
554  if (!plan_request.start_state.attached_collision_objects.empty())
555  {
556  RCLCPP_WARN(logger_, "Ignoring start_state.attached_collision_objects: Not supported.");
557  }
558 
559  query.append("group_name", plan_request.group_name);
560 
561  // Workspace params
562  // Match anything within our specified workspace limits.
563  query.append("workspace_parameters.header.frame_id", workspace_frame_id);
564  query.appendGTE("workspace_parameters.min_corner.x", plan_request.workspace_parameters.min_corner.x);
565  query.appendGTE("workspace_parameters.min_corner.y", plan_request.workspace_parameters.min_corner.y);
566  query.appendGTE("workspace_parameters.min_corner.z", plan_request.workspace_parameters.min_corner.z);
567  query.appendLTE("workspace_parameters.max_corner.x", plan_request.workspace_parameters.max_corner.x);
568  query.appendLTE("workspace_parameters.max_corner.y", plan_request.workspace_parameters.max_corner.y);
569  query.appendLTE("workspace_parameters.max_corner.z", plan_request.workspace_parameters.max_corner.z);
570 
571  // Joint state
572  // Only accounts for joint_state position. Ignores velocity and effort.
573  if (plan_request.start_state.is_diff)
574  {
575  // If plan request states that the start_state is_diff, then we need to get
576  // the current state from the move_group.
577 
578  // NOTE: methyldragon -
579  // I think if is_diff is on, the joint states will not be populated in all
580  // of our motion plan requests? If this isn't the case we might need to
581  // apply the joint states as offsets as well.
582  //
583  // TODO: Since MoveIt also potentially does another getCurrentState() call
584  // when planning, there is a chance that the current state in the cache
585  // differs from the state used in MoveIt's plan.
586  //
587  // When upstreaming this class to MoveIt, this issue should go away once
588  // the class is used within the move group's Plan call.
589  moveit::core::RobotStatePtr current_state = move_group.getCurrentState();
590  if (!current_state)
591  {
592  RCLCPP_WARN(logger_, "Skipping start query append: Could not get robot state.");
593  // NOTE: methyldragon -
594  // Ideally we would restore the original state here and undo our changes, however copy of the query is not
595  // supported.
596  return false;
597  }
598 
599  moveit_msgs::msg::RobotState current_state_msg;
600  robotStateToRobotStateMsg(*current_state, current_state_msg);
601 
602  for (size_t i = 0; i < current_state_msg.joint_state.name.size(); i++)
603  {
604  query.append("start_state.joint_state.name_" + std::to_string(i), current_state_msg.joint_state.name.at(i));
605  queryAppendRangeInclusiveWithTolerance(query, "start_state.joint_state.position_" + std::to_string(i),
606  current_state_msg.joint_state.position.at(i), match_tolerance);
607  }
608  }
609  else
610  {
611  for (size_t i = 0; i < plan_request.start_state.joint_state.name.size(); i++)
612  {
613  query.append("start_state.joint_state.name_" + std::to_string(i), plan_request.start_state.joint_state.name.at(i));
614  queryAppendRangeInclusiveWithTolerance(query, "start_state.joint_state.position_" + std::to_string(i),
615  plan_request.start_state.joint_state.position.at(i), match_tolerance);
616  }
617  }
618  return true;
619 }
620 
621 bool TrajectoryCache::extractAndAppendTrajectoryGoalToQuery(
623  const moveit_msgs::msg::MotionPlanRequest& plan_request, double match_tolerance) const
624 {
625  std::string workspace_frame_id = getWorkspaceFrameId(move_group, plan_request.workspace_parameters);
626  match_tolerance += options_.exact_match_precision;
627 
628  // Make ignored members explicit
629  bool emit_position_constraint_warning = false;
630  for (auto& constraint : plan_request.goal_constraints)
631  {
632  for (auto& position_constraint : constraint.position_constraints)
633  {
634  if (!position_constraint.constraint_region.primitives.empty())
635  {
636  emit_position_constraint_warning = true;
637  break;
638  }
639  }
640  if (emit_position_constraint_warning)
641  {
642  break;
643  }
644  }
645  if (emit_position_constraint_warning)
646  {
647  RCLCPP_WARN(logger_, "Ignoring goal_constraints.position_constraints.constraint_region: "
648  "Not supported.");
649  }
650 
651  queryAppendRangeInclusiveWithTolerance(query, "max_velocity_scaling_factor", plan_request.max_velocity_scaling_factor,
652  match_tolerance);
653  queryAppendRangeInclusiveWithTolerance(query, "max_acceleration_scaling_factor",
654  plan_request.max_acceleration_scaling_factor, match_tolerance);
655  queryAppendRangeInclusiveWithTolerance(query, "max_cartesian_speed", plan_request.max_cartesian_speed,
656  match_tolerance);
657 
658  // Extract constraints (so we don't have cardinality on goal_constraint idx.)
659  std::vector<moveit_msgs::msg::JointConstraint> joint_constraints;
660  std::vector<moveit_msgs::msg::PositionConstraint> position_constraints;
661  std::vector<moveit_msgs::msg::OrientationConstraint> orientation_constraints;
662  for (auto& constraint : plan_request.goal_constraints)
663  {
664  for (auto& joint_constraint : constraint.joint_constraints)
665  {
666  joint_constraints.push_back(joint_constraint);
667  }
668  for (auto& position_constraint : constraint.position_constraints)
669  {
670  position_constraints.push_back(position_constraint);
671  }
672  for (auto& orientation_constraint : constraint.orientation_constraints)
673  {
674  orientation_constraints.push_back(orientation_constraint);
675  }
676 
677  // Also sort for even less cardinality.
678  sortConstraints(joint_constraints, position_constraints, orientation_constraints);
679  }
680 
681  // Joint constraints
682  size_t joint_idx = 0;
683  for (auto& constraint : joint_constraints)
684  {
685  std::string meta_name = "goal_constraints.joint_constraints_" + std::to_string(joint_idx++);
686 
687  query.append(meta_name + ".joint_name", constraint.joint_name);
688  queryAppendRangeInclusiveWithTolerance(query, meta_name + ".position", constraint.position, match_tolerance);
689  query.appendGTE(meta_name + ".tolerance_above", constraint.tolerance_above);
690  query.appendLTE(meta_name + ".tolerance_below", constraint.tolerance_below);
691  }
692 
693  // Position constraints
694  // All offsets will be "frozen" and computed wrt. the workspace frame
695  // instead.
696  if (!position_constraints.empty())
697  {
698  query.append("goal_constraints.position_constraints.header.frame_id", workspace_frame_id);
699 
700  size_t pos_idx = 0;
701  for (auto& constraint : position_constraints)
702  {
703  std::string meta_name = "goal_constraints.position_constraints_" + std::to_string(pos_idx++);
704 
705  // Compute offsets wrt. to workspace frame.
706  double x_offset = 0;
707  double y_offset = 0;
708  double z_offset = 0;
709 
710  if (workspace_frame_id != constraint.header.frame_id)
711  {
712  try
713  {
714  auto transform =
715  tf_buffer_->lookupTransform(constraint.header.frame_id, workspace_frame_id, tf2::TimePointZero);
716 
717  x_offset = transform.transform.translation.x;
718  y_offset = transform.transform.translation.y;
719  z_offset = transform.transform.translation.z;
720  }
721  catch (tf2::TransformException& ex)
722  {
723  RCLCPP_WARN(logger_,
724  "Skipping goal query append: "
725  "Could not get goal transform for translation %s to %s: %s",
726  workspace_frame_id.c_str(), constraint.header.frame_id.c_str(), ex.what());
727 
728  // NOTE: methyldragon -
729  // Ideally we would restore the original state here and undo our changes, however copy of the query is not
730  // supported.
731  return false;
732  }
733  }
734 
735  query.append(meta_name + ".link_name", constraint.link_name);
736 
737  queryAppendRangeInclusiveWithTolerance(query, meta_name + ".target_point_offset.x",
738  x_offset + constraint.target_point_offset.x, match_tolerance);
739  queryAppendRangeInclusiveWithTolerance(query, meta_name + ".target_point_offset.y",
740  y_offset + constraint.target_point_offset.y, match_tolerance);
741  queryAppendRangeInclusiveWithTolerance(query, meta_name + ".target_point_offset.z",
742  z_offset + constraint.target_point_offset.z, match_tolerance);
743  }
744  }
745 
746  // Orientation constraints
747  // All offsets will be "frozen" and computed wrt. the workspace frame
748  // instead.
749  if (!orientation_constraints.empty())
750  {
751  query.append("goal_constraints.orientation_constraints.header.frame_id", workspace_frame_id);
752 
753  size_t ori_idx = 0;
754  for (auto& constraint : orientation_constraints)
755  {
756  std::string meta_name = "goal_constraints.orientation_constraints_" + std::to_string(ori_idx++);
757 
758  // Compute offsets wrt. to workspace frame.
759  geometry_msgs::msg::Quaternion quat_offset;
760  quat_offset.x = 0;
761  quat_offset.y = 0;
762  quat_offset.z = 0;
763  quat_offset.w = 1;
764 
765  if (workspace_frame_id != constraint.header.frame_id)
766  {
767  try
768  {
769  auto transform =
770  tf_buffer_->lookupTransform(constraint.header.frame_id, workspace_frame_id, tf2::TimePointZero);
771 
772  quat_offset = transform.transform.rotation;
773  }
774  catch (tf2::TransformException& ex)
775  {
776  RCLCPP_WARN(logger_,
777  "Skipping goal query append: "
778  "Could not get goal transform for orientation %s to %s: %s",
779  workspace_frame_id.c_str(), constraint.header.frame_id.c_str(), ex.what());
780 
781  // NOTE: methyldragon -
782  // Ideally we would restore the original state here and undo our changes, however copy of the query is not
783  // supported.
784  return false;
785  }
786  }
787 
788  query.append(meta_name + ".link_name", constraint.link_name);
789 
790  // Orientation of constraint frame wrt. workspace frame
791  tf2::Quaternion tf2_quat_frame_offset(quat_offset.x, quat_offset.y, quat_offset.z, quat_offset.w);
792 
793  // Added offset on top of the constraint frame's orientation stated in
794  // the constraint.
795  tf2::Quaternion tf2_quat_goal_offset(constraint.orientation.x, constraint.orientation.y, constraint.orientation.z,
796  constraint.orientation.w);
797 
798  tf2_quat_frame_offset.normalize();
799  tf2_quat_goal_offset.normalize();
800 
801  auto final_quat = tf2_quat_goal_offset * tf2_quat_frame_offset;
802  final_quat.normalize();
803 
804  queryAppendRangeInclusiveWithTolerance(query, meta_name + ".target_point_offset.x", final_quat.getX(),
805  match_tolerance);
806  queryAppendRangeInclusiveWithTolerance(query, meta_name + ".target_point_offset.y", final_quat.getY(),
807  match_tolerance);
808  queryAppendRangeInclusiveWithTolerance(query, meta_name + ".target_point_offset.z", final_quat.getZ(),
809  match_tolerance);
810  queryAppendRangeInclusiveWithTolerance(query, meta_name + ".target_point_offset.w", final_quat.getW(),
811  match_tolerance);
812  }
813  }
814 
815  return true;
816 }
817 
818 // MOTION PLAN TRAJECTORY CACHING: METADATA CONSTRUCTION =======================
819 
820 bool TrajectoryCache::extractAndAppendTrajectoryStartToMetadata(
822  const moveit_msgs::msg::MotionPlanRequest& plan_request) const
823 {
824  std::string workspace_frame_id = getWorkspaceFrameId(move_group, plan_request.workspace_parameters);
825 
826  // Make ignored members explicit
827  if (!plan_request.start_state.multi_dof_joint_state.joint_names.empty())
828  {
829  RCLCPP_WARN(logger_, "Ignoring start_state.multi_dof_joint_states: Not supported.");
830  }
831  if (!plan_request.start_state.attached_collision_objects.empty())
832  {
833  RCLCPP_WARN(logger_, "Ignoring start_state.attached_collision_objects: Not supported.");
834  }
835 
836  metadata.append("group_name", plan_request.group_name);
837 
838  // Workspace params
839  metadata.append("workspace_parameters.header.frame_id", workspace_frame_id);
840  metadata.append("workspace_parameters.min_corner.x", plan_request.workspace_parameters.min_corner.x);
841  metadata.append("workspace_parameters.min_corner.y", plan_request.workspace_parameters.min_corner.y);
842  metadata.append("workspace_parameters.min_corner.z", plan_request.workspace_parameters.min_corner.z);
843  metadata.append("workspace_parameters.max_corner.x", plan_request.workspace_parameters.max_corner.x);
844  metadata.append("workspace_parameters.max_corner.y", plan_request.workspace_parameters.max_corner.y);
845  metadata.append("workspace_parameters.max_corner.z", plan_request.workspace_parameters.max_corner.z);
846 
847  // Joint state
848  // Only accounts for joint_state position. Ignores velocity and effort.
849  if (plan_request.start_state.is_diff)
850  {
851  // If plan request states that the start_state is_diff, then we need to get
852  // the current state from the move_group.
853 
854  // NOTE: methyldragon -
855  // I think if is_diff is on, the joint states will not be populated in all
856  // of our motion plan requests? If this isn't the case we might need to
857  // apply the joint states as offsets as well.
858  //
859  // TODO: Since MoveIt also potentially does another getCurrentState() call
860  // when planning, there is a chance that the current state in the cache
861  // differs from the state used in MoveIt's plan.
862  //
863  // When upstreaming this class to MoveIt, this issue should go away once
864  // the class is used within the move group's Plan call.
865  moveit::core::RobotStatePtr current_state = move_group.getCurrentState();
866  if (!current_state)
867  {
868  RCLCPP_WARN(logger_, "Skipping start metadata append: Could not get robot state.");
869  // NOTE: methyldragon -
870  // Ideally we would restore the original state here and undo our changes, however copy of the query is not
871  // supported.
872  return false;
873  }
874 
875  moveit_msgs::msg::RobotState current_state_msg;
876  robotStateToRobotStateMsg(*current_state, current_state_msg);
877 
878  for (size_t i = 0; i < current_state_msg.joint_state.name.size(); i++)
879  {
880  metadata.append("start_state.joint_state.name_" + std::to_string(i), current_state_msg.joint_state.name.at(i));
881  metadata.append("start_state.joint_state.position_" + std::to_string(i),
882  current_state_msg.joint_state.position.at(i));
883  }
884  }
885  else
886  {
887  for (size_t i = 0; i < plan_request.start_state.joint_state.name.size(); i++)
888  {
889  metadata.append("start_state.joint_state.name_" + std::to_string(i),
890  plan_request.start_state.joint_state.name.at(i));
891  metadata.append("start_state.joint_state.position_" + std::to_string(i),
892  plan_request.start_state.joint_state.position.at(i));
893  }
894  }
895 
896  return true;
897 }
898 
899 bool TrajectoryCache::extractAndAppendTrajectoryGoalToMetadata(
901  const moveit_msgs::msg::MotionPlanRequest& plan_request) const
902 {
903  std::string workspace_frame_id = getWorkspaceFrameId(move_group, plan_request.workspace_parameters);
904 
905  // Make ignored members explicit
906  bool emit_position_constraint_warning = false;
907  for (auto& constraint : plan_request.goal_constraints)
908  {
909  for (auto& position_constraint : constraint.position_constraints)
910  {
911  if (!position_constraint.constraint_region.primitives.empty())
912  {
913  emit_position_constraint_warning = true;
914  break;
915  }
916  }
917  if (emit_position_constraint_warning)
918  {
919  break;
920  }
921  }
922  if (emit_position_constraint_warning)
923  {
924  RCLCPP_WARN(logger_, "Ignoring goal_constraints.position_constraints.constraint_region: "
925  "Not supported.");
926  }
927 
928  metadata.append("max_velocity_scaling_factor", plan_request.max_velocity_scaling_factor);
929  metadata.append("max_acceleration_scaling_factor", plan_request.max_acceleration_scaling_factor);
930  metadata.append("max_cartesian_speed", plan_request.max_cartesian_speed);
931 
932  // Extract constraints (so we don't have cardinality on goal_constraint idx.)
933  std::vector<moveit_msgs::msg::JointConstraint> joint_constraints;
934  std::vector<moveit_msgs::msg::PositionConstraint> position_constraints;
935  std::vector<moveit_msgs::msg::OrientationConstraint> orientation_constraints;
936  for (auto& constraint : plan_request.goal_constraints)
937  {
938  for (auto& joint_constraint : constraint.joint_constraints)
939  {
940  joint_constraints.push_back(joint_constraint);
941  }
942  for (auto& position_constraint : constraint.position_constraints)
943  {
944  position_constraints.push_back(position_constraint);
945  }
946  for (auto& orientation_constraint : constraint.orientation_constraints)
947  {
948  orientation_constraints.push_back(orientation_constraint);
949  }
950 
951  // Also sort for even less cardinality.
952  sortConstraints(joint_constraints, position_constraints, orientation_constraints);
953  }
954 
955  // Joint constraints
956  size_t joint_idx = 0;
957  for (auto& constraint : joint_constraints)
958  {
959  std::string meta_name = "goal_constraints.joint_constraints_" + std::to_string(joint_idx++);
960 
961  metadata.append(meta_name + ".joint_name", constraint.joint_name);
962  metadata.append(meta_name + ".position", constraint.position);
963  metadata.append(meta_name + ".tolerance_above", constraint.tolerance_above);
964  metadata.append(meta_name + ".tolerance_below", constraint.tolerance_below);
965  }
966 
967  // Position constraints
968  if (!position_constraints.empty())
969  {
970  // All offsets will be "frozen" and computed wrt. the workspace frame
971  // instead.
972  metadata.append("goal_constraints.position_constraints.header.frame_id", workspace_frame_id);
973 
974  size_t position_idx = 0;
975  for (auto& constraint : position_constraints)
976  {
977  std::string meta_name = "goal_constraints.position_constraints_" + std::to_string(position_idx++);
978 
979  // Compute offsets wrt. to workspace frame.
980  double x_offset = 0;
981  double y_offset = 0;
982  double z_offset = 0;
983 
984  if (workspace_frame_id != constraint.header.frame_id)
985  {
986  try
987  {
988  auto transform =
989  tf_buffer_->lookupTransform(constraint.header.frame_id, workspace_frame_id, tf2::TimePointZero);
990 
991  x_offset = transform.transform.translation.x;
992  y_offset = transform.transform.translation.y;
993  z_offset = transform.transform.translation.z;
994  }
995  catch (tf2::TransformException& ex)
996  {
997  RCLCPP_WARN(logger_,
998  "Skipping goal metadata append: "
999  "Could not get goal transform for translation %s to %s: %s",
1000  workspace_frame_id.c_str(), constraint.header.frame_id.c_str(), ex.what());
1001 
1002  // NOTE: methyldragon -
1003  // Ideally we would restore the original state here and undo our changes, however copy of the query is not
1004  // supported.
1005  return false;
1006  }
1007  }
1008 
1009  metadata.append(meta_name + ".link_name", constraint.link_name);
1010 
1011  metadata.append(meta_name + ".target_point_offset.x", x_offset + constraint.target_point_offset.x);
1012  metadata.append(meta_name + ".target_point_offset.y", y_offset + constraint.target_point_offset.y);
1013  metadata.append(meta_name + ".target_point_offset.z", z_offset + constraint.target_point_offset.z);
1014  }
1015  }
1016 
1017  // Orientation constraints
1018  if (!orientation_constraints.empty())
1019  {
1020  // All offsets will be "frozen" and computed wrt. the workspace frame
1021  // instead.
1022  metadata.append("goal_constraints.orientation_constraints.header.frame_id", workspace_frame_id);
1023 
1024  size_t ori_idx = 0;
1025  for (auto& constraint : orientation_constraints)
1026  {
1027  std::string meta_name = "goal_constraints.orientation_constraints_" + std::to_string(ori_idx++);
1028 
1029  // Compute offsets wrt. to workspace frame.
1030  geometry_msgs::msg::Quaternion quat_offset;
1031  quat_offset.x = 0;
1032  quat_offset.y = 0;
1033  quat_offset.z = 0;
1034  quat_offset.w = 1;
1035 
1036  if (workspace_frame_id != constraint.header.frame_id)
1037  {
1038  try
1039  {
1040  auto transform =
1041  tf_buffer_->lookupTransform(constraint.header.frame_id, workspace_frame_id, tf2::TimePointZero);
1042 
1043  quat_offset = transform.transform.rotation;
1044  }
1045  catch (tf2::TransformException& ex)
1046  {
1047  RCLCPP_WARN(logger_,
1048  "Skipping goal metadata append: "
1049  "Could not get goal transform for orientation %s to %s: %s",
1050  workspace_frame_id.c_str(), constraint.header.frame_id.c_str(), ex.what());
1051 
1052  // NOTE: methyldragon -
1053  // Ideally we would restore the original state here and undo our changes, however copy of the query is not
1054  // supported.
1055  return false;
1056  }
1057  }
1058 
1059  metadata.append(meta_name + ".link_name", constraint.link_name);
1060 
1061  // Orientation of constraint frame wrt. workspace frame
1062  tf2::Quaternion tf2_quat_frame_offset(quat_offset.x, quat_offset.y, quat_offset.z, quat_offset.w);
1063 
1064  // Added offset on top of the constraint frame's orientation stated in
1065  // the constraint.
1066  tf2::Quaternion tf2_quat_goal_offset(constraint.orientation.x, constraint.orientation.y, constraint.orientation.z,
1067  constraint.orientation.w);
1068 
1069  tf2_quat_frame_offset.normalize();
1070  tf2_quat_goal_offset.normalize();
1071 
1072  auto final_quat = tf2_quat_goal_offset * tf2_quat_frame_offset;
1073  final_quat.normalize();
1074 
1075  metadata.append(meta_name + ".target_point_offset.x", final_quat.getX());
1076  metadata.append(meta_name + ".target_point_offset.y", final_quat.getY());
1077  metadata.append(meta_name + ".target_point_offset.z", final_quat.getZ());
1078  metadata.append(meta_name + ".target_point_offset.w", final_quat.getW());
1079  }
1080  }
1081 
1082  return true;
1083 }
1084 
1085 // =============================================================================
1086 // CARTESIAN TRAJECTORY QUERY AND METADATA CONSTRUCTION
1087 // =============================================================================
1088 
1089 // CARTESIAN TRAJECTORY CACHING: QUERY CONSTRUCTION ============================
1090 
1091 bool TrajectoryCache::extractAndAppendCartesianTrajectoryStartToQuery(
1093  const moveit_msgs::srv::GetCartesianPath::Request& plan_request, double match_tolerance) const
1094 {
1095  std::string path_request_frame_id = getCartesianPathRequestFrameId(move_group, plan_request);
1096  match_tolerance += options_.exact_match_precision;
1097 
1098  // Make ignored members explicit
1099  if (!plan_request.start_state.multi_dof_joint_state.joint_names.empty())
1100  {
1101  RCLCPP_WARN(logger_, "Ignoring start_state.multi_dof_joint_states: Not supported.");
1102  }
1103  if (!plan_request.start_state.attached_collision_objects.empty())
1104  {
1105  RCLCPP_WARN(logger_, "Ignoring start_state.attached_collision_objects: Not supported.");
1106  }
1107 
1108  query.append("group_name", plan_request.group_name);
1109  query.append("path_request.header.frame_id", path_request_frame_id);
1110 
1111  // Joint state
1112  // Only accounts for joint_state position. Ignores velocity and effort.
1113  if (plan_request.start_state.is_diff)
1114  {
1115  // If plan request states that the start_state is_diff, then we need to get
1116  // the current state from the move_group.
1117 
1118  // NOTE: methyldragon -
1119  // I think if is_diff is on, the joint states will not be populated in all
1120  // of our motion plan requests? If this isn't the case we might need to
1121  // apply the joint states as offsets as well.
1122  //
1123  // TODO: Since MoveIt also potentially does another getCurrentState() call
1124  // when planning, there is a chance that the current state in the cache
1125  // differs from the state used in MoveIt's plan.
1126  //
1127  // When upstreaming this class to MoveIt, this issue should go away once
1128  // the class is used within the move group's Plan call.
1129  moveit::core::RobotStatePtr current_state = move_group.getCurrentState();
1130  if (!current_state)
1131  {
1132  RCLCPP_WARN(logger_, "Skipping start metadata append: Could not get robot state.");
1133  // NOTE: methyldragon -
1134  // Ideally we would restore the original state here and undo our changes, however copy of the query is not
1135  // supported.
1136  return false;
1137  }
1138 
1139  moveit_msgs::msg::RobotState current_state_msg;
1140  robotStateToRobotStateMsg(*current_state, current_state_msg);
1141 
1142  for (size_t i = 0; i < current_state_msg.joint_state.name.size(); i++)
1143  {
1144  query.append("start_state.joint_state.name_" + std::to_string(i), current_state_msg.joint_state.name.at(i));
1145  queryAppendRangeInclusiveWithTolerance(query, "start_state.joint_state.position_" + std::to_string(i),
1146  current_state_msg.joint_state.position.at(i), match_tolerance);
1147  }
1148  }
1149  else
1150  {
1151  for (size_t i = 0; i < plan_request.start_state.joint_state.name.size(); i++)
1152  {
1153  query.append("start_state.joint_state.name_" + std::to_string(i), plan_request.start_state.joint_state.name.at(i));
1154  queryAppendRangeInclusiveWithTolerance(query, "start_state.joint_state.position_" + std::to_string(i),
1155  plan_request.start_state.joint_state.position.at(i), match_tolerance);
1156  }
1157  }
1158 
1159  return true;
1160 }
1161 
1162 bool TrajectoryCache::extractAndAppendCartesianTrajectoryGoalToQuery(
1164  const moveit_msgs::srv::GetCartesianPath::Request& plan_request, double match_tolerance) const
1165 {
1166  std::string path_request_frame_id = getCartesianPathRequestFrameId(move_group, plan_request);
1167  match_tolerance += options_.exact_match_precision;
1168 
1169  // Make ignored members explicit
1170  if (!plan_request.path_constraints.joint_constraints.empty() ||
1171  !plan_request.path_constraints.position_constraints.empty() ||
1172  !plan_request.path_constraints.orientation_constraints.empty() ||
1173  !plan_request.path_constraints.visibility_constraints.empty())
1174  {
1175  RCLCPP_WARN(logger_, "Ignoring path_constraints: Not supported.");
1176  }
1177  if (plan_request.avoid_collisions)
1178  {
1179  RCLCPP_WARN(logger_, "Ignoring avoid_collisions: Not supported.");
1180  }
1181 
1182  queryAppendRangeInclusiveWithTolerance(query, "max_velocity_scaling_factor", plan_request.max_velocity_scaling_factor,
1183  match_tolerance);
1184  queryAppendRangeInclusiveWithTolerance(query, "max_acceleration_scaling_factor",
1185  plan_request.max_acceleration_scaling_factor, match_tolerance);
1186  queryAppendRangeInclusiveWithTolerance(query, "max_step", plan_request.max_step, match_tolerance);
1187  queryAppendRangeInclusiveWithTolerance(query, "jump_threshold", plan_request.jump_threshold, match_tolerance);
1188 
1189  // Waypoints
1190  // Restating them in terms of the robot model frame (usually base_link)
1191  std::string base_frame = move_group.getRobotModel()->getModelFrame();
1192 
1193  // Compute offsets.
1194  double x_offset = 0;
1195  double y_offset = 0;
1196  double z_offset = 0;
1197 
1198  geometry_msgs::msg::Quaternion quat_offset;
1199  quat_offset.x = 0;
1200  quat_offset.y = 0;
1201  quat_offset.z = 0;
1202  quat_offset.w = 1;
1203 
1204  if (base_frame != path_request_frame_id)
1205  {
1206  try
1207  {
1208  auto transform = tf_buffer_->lookupTransform(path_request_frame_id, base_frame, tf2::TimePointZero);
1209 
1210  x_offset = transform.transform.translation.x;
1211  y_offset = transform.transform.translation.y;
1212  z_offset = transform.transform.translation.z;
1213  quat_offset = transform.transform.rotation;
1214  }
1215  catch (tf2::TransformException& ex)
1216  {
1217  RCLCPP_WARN(logger_,
1218  "Skipping goal metadata append: "
1219  "Could not get goal transform for %s to %s: %s",
1220  base_frame.c_str(), path_request_frame_id.c_str(), ex.what());
1221 
1222  // NOTE: methyldragon -
1223  // Ideally we would restore the original state here and undo our changes, however copy of the query is not
1224  // supported.
1225  return false;
1226  }
1227  }
1228 
1229  tf2::Quaternion tf2_quat_frame_offset(quat_offset.x, quat_offset.y, quat_offset.z, quat_offset.w);
1230  tf2_quat_frame_offset.normalize();
1231 
1232  size_t waypoint_idx = 0;
1233  for (auto& waypoint : plan_request.waypoints)
1234  {
1235  std::string meta_name = "waypoints_" + std::to_string(waypoint_idx++);
1236 
1237  // Apply offsets
1238  // Position
1239  queryAppendRangeInclusiveWithTolerance(query, meta_name + ".position.x", x_offset + waypoint.position.x,
1240  match_tolerance);
1241  queryAppendRangeInclusiveWithTolerance(query, meta_name + ".position.y", y_offset + waypoint.position.y,
1242  match_tolerance);
1243  queryAppendRangeInclusiveWithTolerance(query, meta_name + ".position.z", z_offset + waypoint.position.z,
1244  match_tolerance);
1245 
1246  // Orientation
1247  tf2::Quaternion tf2_quat_goal_offset(waypoint.orientation.x, waypoint.orientation.y, waypoint.orientation.z,
1248  waypoint.orientation.w);
1249  tf2_quat_goal_offset.normalize();
1250 
1251  auto final_quat = tf2_quat_goal_offset * tf2_quat_frame_offset;
1252  final_quat.normalize();
1253 
1254  queryAppendRangeInclusiveWithTolerance(query, meta_name + ".orientation.x", final_quat.getX(), match_tolerance);
1255  queryAppendRangeInclusiveWithTolerance(query, meta_name + ".orientation.y", final_quat.getY(), match_tolerance);
1256  queryAppendRangeInclusiveWithTolerance(query, meta_name + ".orientation.z", final_quat.getZ(), match_tolerance);
1257  queryAppendRangeInclusiveWithTolerance(query, meta_name + ".orientation.w", final_quat.getW(), match_tolerance);
1258  }
1259 
1260  query.append("link_name", plan_request.link_name);
1261  query.append("header.frame_id", base_frame);
1262 
1263  return true;
1264 }
1265 
1266 // CARTESIAN TRAJECTORY CACHING: METADATA CONSTRUCTION =========================
1267 
1268 bool TrajectoryCache::extractAndAppendCartesianTrajectoryStartToMetadata(
1270  const moveit_msgs::srv::GetCartesianPath::Request& plan_request) const
1271 {
1272  std::string path_request_frame_id = getCartesianPathRequestFrameId(move_group, plan_request);
1273 
1274  // Make ignored members explicit
1275  if (!plan_request.start_state.multi_dof_joint_state.joint_names.empty())
1276  {
1277  RCLCPP_WARN(logger_, "Ignoring start_state.multi_dof_joint_states: Not supported.");
1278  }
1279  if (!plan_request.start_state.attached_collision_objects.empty())
1280  {
1281  RCLCPP_WARN(logger_, "Ignoring start_state.attached_collision_objects: Not supported.");
1282  }
1283 
1284  metadata.append("group_name", plan_request.group_name);
1285  metadata.append("path_request.header.frame_id", path_request_frame_id);
1286 
1287  // Joint state
1288  // Only accounts for joint_state position. Ignores velocity and effort.
1289  if (plan_request.start_state.is_diff)
1290  {
1291  // If plan request states that the start_state is_diff, then we need to get
1292  // the current state from the move_group.
1293 
1294  // NOTE: methyldragon -
1295  // I think if is_diff is on, the joint states will not be populated in all
1296  // of our motion plan requests? If this isn't the case we might need to
1297  // apply the joint states as offsets as well.
1298  //
1299  // TODO: Since MoveIt also potentially does another getCurrentState() call
1300  // when planning, there is a chance that the current state in the cache
1301  // differs from the state used in MoveIt's plan.
1302  //
1303  // When upstreaming this class to MoveIt, this issue should go away once
1304  // the class is used within the move group's Plan call.
1305  moveit::core::RobotStatePtr current_state = move_group.getCurrentState();
1306  if (!current_state)
1307  {
1308  RCLCPP_WARN(logger_, "Skipping start metadata append: Could not get robot state.");
1309  // NOTE: methyldragon -
1310  // Ideally we would restore the original state here and undo our changes, however copy of the query is not
1311  // supported.
1312  return false;
1313  }
1314 
1315  moveit_msgs::msg::RobotState current_state_msg;
1316  robotStateToRobotStateMsg(*current_state, current_state_msg);
1317 
1318  for (size_t i = 0; i < current_state_msg.joint_state.name.size(); i++)
1319  {
1320  metadata.append("start_state.joint_state.name_" + std::to_string(i), current_state_msg.joint_state.name.at(i));
1321  metadata.append("start_state.joint_state.position_" + std::to_string(i),
1322  current_state_msg.joint_state.position.at(i));
1323  }
1324  }
1325  else
1326  {
1327  for (size_t i = 0; i < plan_request.start_state.joint_state.name.size(); i++)
1328  {
1329  metadata.append("start_state.joint_state.name_" + std::to_string(i),
1330  plan_request.start_state.joint_state.name.at(i));
1331  metadata.append("start_state.joint_state.position_" + std::to_string(i),
1332  plan_request.start_state.joint_state.position.at(i));
1333  }
1334  }
1335 
1336  return true;
1337 }
1338 
1339 bool TrajectoryCache::extractAndAppendCartesianTrajectoryGoalToMetadata(
1341  const moveit_msgs::srv::GetCartesianPath::Request& plan_request) const
1342 {
1343  std::string path_request_frame_id = getCartesianPathRequestFrameId(move_group, plan_request);
1344 
1345  // Make ignored members explicit
1346  if (!plan_request.path_constraints.joint_constraints.empty() ||
1347  !plan_request.path_constraints.position_constraints.empty() ||
1348  !plan_request.path_constraints.orientation_constraints.empty() ||
1349  !plan_request.path_constraints.visibility_constraints.empty())
1350  {
1351  RCLCPP_WARN(logger_, "Ignoring path_constraints: Not supported.");
1352  }
1353  if (plan_request.avoid_collisions)
1354  {
1355  RCLCPP_WARN(logger_, "Ignoring avoid_collisions: Not supported.");
1356  }
1357 
1358  metadata.append("max_velocity_scaling_factor", plan_request.max_velocity_scaling_factor);
1359  metadata.append("max_acceleration_scaling_factor", plan_request.max_acceleration_scaling_factor);
1360  metadata.append("max_step", plan_request.max_step);
1361  metadata.append("jump_threshold", plan_request.jump_threshold);
1362 
1363  // Waypoints
1364  // Restating them in terms of the robot model frame (usually base_link)
1365  std::string base_frame = move_group.getRobotModel()->getModelFrame();
1366 
1367  // Compute offsets.
1368  double x_offset = 0;
1369  double y_offset = 0;
1370  double z_offset = 0;
1371 
1372  geometry_msgs::msg::Quaternion quat_offset;
1373  quat_offset.x = 0;
1374  quat_offset.y = 0;
1375  quat_offset.z = 0;
1376  quat_offset.w = 1;
1377 
1378  if (base_frame != path_request_frame_id)
1379  {
1380  try
1381  {
1382  auto transform = tf_buffer_->lookupTransform(path_request_frame_id, base_frame, tf2::TimePointZero);
1383 
1384  x_offset = transform.transform.translation.x;
1385  y_offset = transform.transform.translation.y;
1386  z_offset = transform.transform.translation.z;
1387  quat_offset = transform.transform.rotation;
1388  }
1389  catch (tf2::TransformException& ex)
1390  {
1391  RCLCPP_WARN(logger_,
1392  "Skipping goal metadata append: "
1393  "Could not get goal transform for %s to %s: %s",
1394  base_frame.c_str(), path_request_frame_id.c_str(), ex.what());
1395 
1396  // NOTE: methyldragon -
1397  // Ideally we would restore the original state here and undo our changes, however copy of the query is not
1398  // supported.
1399  return false;
1400  }
1401  }
1402 
1403  tf2::Quaternion tf2_quat_frame_offset(quat_offset.x, quat_offset.y, quat_offset.z, quat_offset.w);
1404  tf2_quat_frame_offset.normalize();
1405 
1406  size_t waypoint_idx = 0;
1407  for (auto& waypoint : plan_request.waypoints)
1408  {
1409  std::string meta_name = "waypoints_" + std::to_string(waypoint_idx++);
1410 
1411  // Apply offsets
1412  // Position
1413  metadata.append(meta_name + ".position.x", x_offset + waypoint.position.x);
1414  metadata.append(meta_name + ".position.y", y_offset + waypoint.position.y);
1415  metadata.append(meta_name + ".position.z", z_offset + waypoint.position.z);
1416 
1417  // Orientation
1418  tf2::Quaternion tf2_quat_goal_offset(waypoint.orientation.x, waypoint.orientation.y, waypoint.orientation.z,
1419  waypoint.orientation.w);
1420  tf2_quat_goal_offset.normalize();
1421 
1422  auto final_quat = tf2_quat_goal_offset * tf2_quat_frame_offset;
1423  final_quat.normalize();
1424 
1425  metadata.append(meta_name + ".orientation.x", final_quat.getX());
1426  metadata.append(meta_name + ".orientation.y", final_quat.getY());
1427  metadata.append(meta_name + ".orientation.z", final_quat.getZ());
1428  metadata.append(meta_name + ".orientation.w", final_quat.getW());
1429  }
1430 
1431  metadata.append("link_name", plan_request.link_name);
1432  metadata.append("header.frame_id", base_frame);
1433 
1434  return true;
1435 }
1436 
1437 } // namespace trajectory_cache
1438 } // namespace moveit_ros
Client class to conveniently use the ROS interfaces provided by the move_group node.
bool insertTrajectory(const moveit::planning_interface::MoveGroupInterface &move_group, const std::string &cache_namespace, const moveit_msgs::msg::MotionPlanRequest &plan_request, const moveit_msgs::msg::RobotTrajectory &trajectory, double execution_time_s, double planning_time_s, bool prune_worse_trajectories=true)
Inserts a trajectory into the database if it is the best matching trajectory seen so far.
unsigned countCartesianTrajectories(const std::string &cache_namespace)
Count the number of cartesian trajectories for a particular cache namespace.
unsigned countTrajectories(const std::string &cache_namespace)
Count the number of non-cartesian trajectories for a particular cache namespace.
void setNumAdditionalTrajectoriesToPreserveWhenDeletingWorse(size_t num_additional_trajectories_to_preserve_when_deleting_worse)
Set the number of additional trajectories to preserve when deleting worse trajectories.
TrajectoryCache(const rclcpp::Node::SharedPtr &node)
Constructs a TrajectoryCache.
double getExactMatchPrecision() const
Gets the exact match precision.
size_t getNumAdditionalTrajectoriesToPreserveWhenDeletingWorse() const
Get the number of trajectories to preserve when deleting worse trajectories.
void setExactMatchPrecision(double exact_match_precision)
Sets the exact match precision.
warehouse_ros::MessageWithMetadata< moveit_msgs::msg::RobotTrajectory >::ConstPtr fetchBestMatchingCartesianTrajectory(const moveit::planning_interface::MoveGroupInterface &move_group, const std::string &cache_namespace, const moveit_msgs::srv::GetCartesianPath::Request &plan_request, double min_fraction, double start_tolerance, double goal_tolerance, bool metadata_only=false, const std::string &sort_by="execution_time_s", bool ascending=true) const
Fetches the best cartesian trajectory that fits within the requested tolerances for start and goal co...
std::vector< warehouse_ros::MessageWithMetadata< moveit_msgs::msg::RobotTrajectory >::ConstPtr > fetchAllMatchingTrajectories(const moveit::planning_interface::MoveGroupInterface &move_group, const std::string &cache_namespace, const moveit_msgs::msg::MotionPlanRequest &plan_request, double start_tolerance, double goal_tolerance, bool metadata_only=false, const std::string &sort_by="execution_time_s", bool ascending=true) const
Fetches all plans that fit within the requested tolerances for start and goal conditions,...
warehouse_ros::MessageWithMetadata< moveit_msgs::msg::RobotTrajectory >::ConstPtr fetchBestMatchingTrajectory(const moveit::planning_interface::MoveGroupInterface &move_group, const std::string &cache_namespace, const moveit_msgs::msg::MotionPlanRequest &plan_request, double start_tolerance, double goal_tolerance, bool metadata_only=false, const std::string &sort_by="execution_time_s", bool ascending=true) const
Fetches the best trajectory that fits within the requested tolerances for start and goal conditions,...
moveit_msgs::srv::GetCartesianPath::Request constructGetCartesianPathRequest(moveit::planning_interface::MoveGroupInterface &move_group, const std::vector< geometry_msgs::msg::Pose > &waypoints, double max_step, double jump_threshold, bool avoid_collisions=true)
Constructs a GetCartesianPath request.
std::string getDbPath() const
Gets the database path.
std::vector< warehouse_ros::MessageWithMetadata< moveit_msgs::msg::RobotTrajectory >::ConstPtr > fetchAllMatchingCartesianTrajectories(const moveit::planning_interface::MoveGroupInterface &move_group, const std::string &cache_namespace, const moveit_msgs::srv::GetCartesianPath::Request &plan_request, double min_fraction, double start_tolerance, double goal_tolerance, bool metadata_only=false, const std::string &sort_by="execution_time_s", bool ascending=true) const
Fetches all cartesian trajectories that fit within the requested tolerances for start and goal condit...
bool init(const Options &options)
Initializes the TrajectoryCache.
bool insertCartesianTrajectory(const moveit::planning_interface::MoveGroupInterface &move_group, const std::string &cache_namespace, const moveit_msgs::srv::GetCartesianPath::Request &plan_request, const moveit_msgs::msg::RobotTrajectory &trajectory, double execution_time_s, double planning_time_s, double fraction, bool prune_worse_trajectories=true)
Inserts a cartesian trajectory into the database if it is the best matching cartesian trajectory seen...
uint32_t getDbPort() const
Gets the database port.
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.
void queryAppendRangeInclusiveWithTolerance(Query &query, const std::string &name, double center, double tolerance)
std::string getWorkspaceFrameId(const moveit::planning_interface::MoveGroupInterface &move_group, const moveit_msgs::msg::WorkspaceParameters &workspace_parameters)
std::string getCartesianPathRequestFrameId(const moveit::planning_interface::MoveGroupInterface &move_group, const moveit_msgs::srv::GetCartesianPath::Request &path_request)
void sortConstraints(std::vector< moveit_msgs::msg::JointConstraint > &joint_constraints, std::vector< moveit_msgs::msg::PositionConstraint > &position_constraints, std::vector< moveit_msgs::msg::OrientationConstraint > &orientation_constraints)
warehouse_ros::DatabaseConnection::Ptr loadDatabase(const rclcpp::Node::SharedPtr &node)
Load a database connection.
Main namespace for MoveIt.
Definition: exceptions.h:43
moveit_msgs::msg::MotionPlanRequest MotionPlanRequest
name
Definition: setup.py:7
Fuzzy-Matching Trajectory Cache.