moveit2
The MoveIt Motion Planning Framework for ROS 2.
motion_planning_frame_objects.cpp
Go to the documentation of this file.
1 /*********************************************************************
2  * Software License Agreement (BSD License)
3  *
4  * Copyright (c) 2012, Willow Garage, Inc.
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 Willow Garage 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 
35 /* Author: Ioan Sucan, Mario Prats */
37 
42 
43 #include <interactive_markers/tools.hpp>
44 
45 #include <rviz_common/display_context.hpp>
46 #include <rviz_common/frame_manager_iface.hpp>
47 #include <rviz_common/window_manager_interface.hpp>
48 
49 #include <tf2_eigen/tf2_eigen.hpp>
50 #include <geometric_shapes/shape_operations.h>
51 
52 #include <QMessageBox>
53 #include <QInputDialog>
54 #include <QFileDialog>
55 
56 #include "ui_motion_planning_rviz_plugin_frame.h"
57 
58 namespace
59 {
60 QString subframePosesToQstring(const moveit::core::FixedTransformsMap& subframes)
61 {
62  QString status_text = "\nIt has the subframes '";
63  for (const auto& subframe : subframes)
64  {
65  status_text += QString::fromStdString(subframe.first) + "', '";
66  }
67  status_text.chop(3);
68  status_text += ".";
69  return status_text;
70 }
71 } // namespace
72 
73 namespace moveit_rviz_plugin
74 {
75 
76 void MotionPlanningFrame::shapesComboBoxChanged(const QString& /*text*/)
77 {
78  switch (ui_->shapes_combo_box->currentData().toInt()) // fetch shape ID from current combobox item
79  {
80  case shapes::BOX:
81  ui_->shape_size_x_spin_box->setEnabled(true);
82  ui_->shape_size_y_spin_box->setEnabled(true);
83  ui_->shape_size_z_spin_box->setEnabled(true);
84  break;
85  case shapes::SPHERE:
86  ui_->shape_size_x_spin_box->setEnabled(true);
87  ui_->shape_size_y_spin_box->setEnabled(false);
88  ui_->shape_size_z_spin_box->setEnabled(false);
89  break;
90  case shapes::CYLINDER:
91  case shapes::CONE:
92  ui_->shape_size_x_spin_box->setEnabled(true);
93  ui_->shape_size_y_spin_box->setEnabled(false);
94  ui_->shape_size_z_spin_box->setEnabled(true);
95  break;
96  case shapes::MESH:
97  ui_->shape_size_x_spin_box->setEnabled(false);
98  ui_->shape_size_y_spin_box->setEnabled(false);
99  ui_->shape_size_z_spin_box->setEnabled(false);
100  break;
101  default:
102  break;
103  }
104 }
105 
106 void MotionPlanningFrame::setLocalSceneEdited(bool dirty)
107 {
108  ui_->publish_current_scene_button->setEnabled(dirty);
109 }
110 
111 bool MotionPlanningFrame::isLocalSceneDirty() const
112 {
113  return ui_->publish_current_scene_button->isEnabled();
114 }
115 
116 void MotionPlanningFrame::publishScene()
117 {
119  if (ps)
120  {
121  moveit_msgs::msg::PlanningScene msg;
122  ps->getPlanningSceneMsg(msg);
123  planning_scene_publisher_->publish(msg);
124  setLocalSceneEdited(false);
125  }
126 }
127 
128 void MotionPlanningFrame::publishSceneIfNeeded()
129 {
130  if (isLocalSceneDirty() &&
131  QMessageBox::question(this, "Update PlanningScene",
132  "You have local changes to your planning scene.\n"
133  "Publish them to the move_group node?",
134  QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes)
135  publishScene();
136 }
137 
138 void MotionPlanningFrame::clearScene()
139 {
141  if (ps)
142  {
143  ps->getWorldNonConst()->clearObjects();
144  ps->getCurrentStateNonConst().clearAttachedBodies();
145  moveit_msgs::msg::PlanningScene msg;
146  ps->getPlanningSceneMsg(msg);
147  planning_scene_publisher_->publish(msg);
148  setLocalSceneEdited(false);
149  planning_display_->addMainLoopJob([this] { populateCollisionObjectsList(); });
151  }
152 }
153 
154 void MotionPlanningFrame::sceneScaleChanged(int value)
155 {
156  const double scaling_factor = static_cast<double>(value) / 100.0; // The GUI slider gives percent values
157  if (scaled_object_)
158  {
160  if (ps)
161  {
162  if (ps->getWorld()->hasObject(scaled_object_->id_))
163  {
164  ps->getWorldNonConst()->removeObject(scaled_object_->id_);
165  for (std::size_t i = 0; i < scaled_object_->shapes_.size(); ++i)
166  {
167  shapes::Shape* s = scaled_object_->shapes_[i]->clone();
168  s->scale(scaling_factor);
169 
170  Eigen::Isometry3d scaled_shape_pose = scaled_object_->shape_poses_[i];
171  scaled_shape_pose.translation() *= scaling_factor;
172 
173  ps->getWorldNonConst()->addToObject(scaled_object_->id_, scaled_object_->pose_, shapes::ShapeConstPtr(s),
174  scaled_shape_pose);
175  }
176  moveit::core::FixedTransformsMap scaled_subframes = scaled_object_->subframe_poses_;
177  for (auto& subframe_pair : scaled_subframes)
178  subframe_pair.second.translation() *= scaling_factor;
179 
180  ps->getWorldNonConst()->setSubframesOfObject(scaled_object_->id_, scaled_subframes);
181  setLocalSceneEdited();
182  scene_marker_->processMessage(createObjectMarkerMsg(ps->getWorld()->getObject(scaled_object_->id_)));
184  }
185  else
186  scaled_object_.reset();
187  }
188  else
189  scaled_object_.reset();
190  }
191 }
192 
193 void MotionPlanningFrame::sceneScaleStartChange()
194 {
195  QList<QListWidgetItem*> sel = ui_->collision_objects_list->selectedItems();
196  if (sel.empty())
197  return;
198  if (planning_display_->getPlanningSceneMonitor() && sel[0]->checkState() == Qt::Unchecked)
199  {
201  if (ps)
202  {
203  scaled_object_ = ps->getWorld()->getObject(sel[0]->text().toStdString());
204  scaled_object_subframes_ = scaled_object_->subframe_poses_;
205  scaled_object_shape_poses_ = scaled_object_->shape_poses_;
206  }
207  }
208 }
209 
210 void MotionPlanningFrame::sceneScaleEndChange()
211 {
212  scaled_object_.reset();
213  ui_->scene_scale->setSliderPosition(100);
214 }
215 
216 void MotionPlanningFrame::removeSceneObject()
217 {
218  QList<QListWidgetItem*> sel = ui_->collision_objects_list->selectedItems();
219  if (sel.empty())
220  return;
222  if (ps)
223  {
224  for (int i = 0; i < sel.count(); ++i)
225  {
226  if (sel[i]->checkState() == Qt::Unchecked)
227  {
228  ps->getWorldNonConst()->removeObject(sel[i]->text().toStdString());
229  }
230  else
231  {
232  ps->getCurrentStateNonConst().clearAttachedBody(sel[i]->text().toStdString());
233  }
234  }
235  scene_marker_.reset();
236  setLocalSceneEdited();
237  planning_display_->addMainLoopJob([this] { populateCollisionObjectsList(); });
239  }
240 }
241 
242 static QString decideStatusText(const collision_detection::CollisionEnv::ObjectConstPtr& obj)
243 {
244  QString status_text = "'" + QString::fromStdString(obj->id_) + "' is a collision object with ";
245  if (obj->shapes_.empty())
246  {
247  status_text += "no geometry";
248  }
249  else
250  {
251  std::vector<QString> shape_names;
252  for (const shapes::ShapeConstPtr& shape : obj->shapes_)
253  shape_names.push_back(QString::fromStdString(shapes::shapeStringName(shape.get())));
254  if (shape_names.size() == 1)
255  {
256  status_text += "one " + shape_names[0];
257  }
258  else
259  {
260  status_text += QString::fromStdString(std::to_string(shape_names.size())) + " shapes:";
261  for (const QString& shape_name : shape_names)
262  status_text += " " + shape_name;
263  }
264  status_text += ".";
265  }
266  if (!obj->subframe_poses_.empty())
267  {
268  status_text += subframePosesToQstring(obj->subframe_poses_);
269  }
270  return status_text;
271 }
272 
273 static QString decideStatusText(const moveit::core::AttachedBody* attached_body)
274 {
275  QString status_text = "'" + QString::fromStdString(attached_body->getName()) + "' is attached to '" +
276  QString::fromStdString(attached_body->getAttachedLinkName()) + "'.";
277  if (!attached_body->getSubframes().empty())
278  {
279  status_text += subframePosesToQstring(attached_body->getSubframes());
280  }
281  return status_text;
282 }
283 
284 void MotionPlanningFrame::selectedCollisionObjectChanged()
285 {
286  QList<QListWidgetItem*> sel = ui_->collision_objects_list->selectedItems();
287  if (sel.empty())
288  {
289  bool old_state = ui_->object_x->blockSignals(true);
290  ui_->object_x->setValue(0.0);
291  ui_->object_x->blockSignals(old_state);
292 
293  old_state = ui_->object_y->blockSignals(true);
294  ui_->object_y->setValue(0.0);
295  ui_->object_y->blockSignals(old_state);
296 
297  old_state = ui_->object_z->blockSignals(true);
298  ui_->object_z->setValue(0.0);
299  ui_->object_z->blockSignals(old_state);
300 
301  old_state = ui_->object_rx->blockSignals(true);
302  ui_->object_rx->setValue(0.0);
303  ui_->object_rx->blockSignals(old_state);
304 
305  old_state = ui_->object_ry->blockSignals(true);
306  ui_->object_ry->setValue(0.0);
307  ui_->object_ry->blockSignals(old_state);
308 
309  old_state = ui_->object_rz->blockSignals(true);
310  ui_->object_rz->setValue(0.0);
311  ui_->object_rz->blockSignals(old_state);
312 
313  ui_->object_status->setText("");
314  scene_marker_.reset();
315  ui_->pose_scale_group_box->setEnabled(false);
316  }
318  {
319  // if this is a CollisionWorld element
320  if (sel[0]->checkState() == Qt::Unchecked)
321  {
322  ui_->pose_scale_group_box->setEnabled(true);
323  bool update_scene_marker = false;
324  Eigen::Isometry3d obj_pose;
325  {
328  ps->getWorld()->getObject(sel[0]->text().toStdString());
329  if (obj)
330  {
331  ui_->object_status->setText(decideStatusText(obj));
332 
333  if (obj->shapes_.size() == 1)
334  {
335  obj_pose = obj->pose_; // valid isometry by contract
336  Eigen::Vector3d xyz = obj_pose.linear().eulerAngles(0, 1, 2);
337  update_scene_marker = true; // do the marker update outside locked scope to avoid deadlock
338 
339  bool old_state = ui_->object_x->blockSignals(true);
340  ui_->object_x->setValue(obj_pose.translation()[0]);
341  ui_->object_x->blockSignals(old_state);
342 
343  old_state = ui_->object_y->blockSignals(true);
344  ui_->object_y->setValue(obj_pose.translation()[1]);
345  ui_->object_y->blockSignals(old_state);
346 
347  old_state = ui_->object_z->blockSignals(true);
348  ui_->object_z->setValue(obj_pose.translation()[2]);
349  ui_->object_z->blockSignals(old_state);
350 
351  old_state = ui_->object_rx->blockSignals(true);
352  ui_->object_rx->setValue(xyz[0]);
353  ui_->object_rx->blockSignals(old_state);
354 
355  old_state = ui_->object_ry->blockSignals(true);
356  ui_->object_ry->setValue(xyz[1]);
357  ui_->object_ry->blockSignals(old_state);
358 
359  old_state = ui_->object_rz->blockSignals(true);
360  ui_->object_rz->setValue(xyz[2]);
361  ui_->object_rz->blockSignals(old_state);
362  }
363  }
364  else
365  ui_->object_status->setText("ERROR: '" + sel[0]->text() + "' should be a collision object but it is not");
366  }
367  if (update_scene_marker && ui_->tabWidget->tabText(ui_->tabWidget->currentIndex()).toStdString() == TAB_OBJECTS)
368  {
369  createSceneInteractiveMarker();
370  }
371  }
372  else
373  {
374  ui_->pose_scale_group_box->setEnabled(false);
375  // if it is an attached object
376  scene_marker_.reset();
378  const moveit::core::AttachedBody* attached_body =
379  ps->getCurrentState().getAttachedBody(sel[0]->text().toStdString());
380  if (attached_body)
381  {
382  ui_->object_status->setText(decideStatusText(attached_body));
383  }
384  else
385  {
386  ui_->object_status->setText("ERROR: '" + sel[0]->text() + "' should be an attached object but it is not");
387  }
388  }
389  }
390 }
391 
392 void MotionPlanningFrame::objectPoseValueChanged(double /* value */)
393 {
394  updateCollisionObjectPose(true);
395 }
396 
397 void MotionPlanningFrame::updateCollisionObjectPose(bool update_marker_position)
398 {
399  QList<QListWidgetItem*> sel = ui_->collision_objects_list->selectedItems();
400  if (sel.empty())
401  return;
403  if (ps)
404  {
405  collision_detection::CollisionEnv::ObjectConstPtr obj = ps->getWorld()->getObject(sel[0]->text().toStdString());
406  if (obj)
407  {
408  Eigen::Isometry3d p;
409  p.translation()[0] = ui_->object_x->value();
410  p.translation()[1] = ui_->object_y->value();
411  p.translation()[2] = ui_->object_z->value();
412 
413  p = Eigen::Translation3d(p.translation()) *
414  (Eigen::AngleAxisd(ui_->object_rx->value(), Eigen::Vector3d::UnitX()) *
415  Eigen::AngleAxisd(ui_->object_ry->value(), Eigen::Vector3d::UnitY()) *
416  Eigen::AngleAxisd(ui_->object_rz->value(), Eigen::Vector3d::UnitZ()));
417 
418  ps->getWorldNonConst()->setObjectPose(obj->id_, p);
420  setLocalSceneEdited();
421 
422  // Update the interactive marker pose to match the manually introduced one
423  if (update_marker_position && scene_marker_)
424  {
425  // p is isometry by construction
426  Eigen::Quaterniond eq(p.linear());
427  scene_marker_->setPose(Ogre::Vector3(ui_->object_x->value(), ui_->object_y->value(), ui_->object_z->value()),
428  Ogre::Quaternion(eq.w(), eq.x(), eq.y(), eq.z()), "");
429  }
430  }
431  }
432 }
433 
434 void MotionPlanningFrame::collisionObjectChanged(QListWidgetItem* item)
435 {
436  if (item->type() < static_cast<int>(known_collision_objects_.size()) && planning_display_->getPlanningSceneMonitor())
437  {
438  // if we have a name change
439  if (known_collision_objects_[item->type()].first != item->text().toStdString())
440  {
441  renameCollisionObject(item);
442  }
443  else
444  {
445  bool checked = item->checkState() == Qt::Checked;
446  if (known_collision_objects_[item->type()].second != checked)
447  attachDetachCollisionObject(item);
448  }
449  }
450 }
451 
452 /* Receives feedback from the interactive marker and updates the shape pose in the world accordingly */
453 void MotionPlanningFrame::imProcessFeedback(visualization_msgs::msg::InteractiveMarkerFeedback& feedback)
454 {
455  if (!planning_display_->getPlanningSceneRO()->knowsFrameTransform(feedback.header.frame_id))
456  {
457  RCLCPP_ERROR_STREAM(logger_,
458  "Frame `" << feedback.header.frame_id << "` unknown doesn't exists in the planning scene");
459  }
460  Eigen::Isometry3d fixed_frame_t_scene_marker;
461  tf2::fromMsg(feedback.pose, fixed_frame_t_scene_marker);
462  Eigen::Isometry3d model_frame_t_scene_marker =
463  planning_display_->getPlanningSceneRO()->getFrameTransform(feedback.header.frame_id) * fixed_frame_t_scene_marker;
464 
465  bool old_state = ui_->object_x->blockSignals(true);
466  ui_->object_x->setValue(model_frame_t_scene_marker.translation().x());
467  ui_->object_x->blockSignals(old_state);
468 
469  old_state = ui_->object_y->blockSignals(true);
470  ui_->object_y->setValue(model_frame_t_scene_marker.translation().y());
471  ui_->object_y->blockSignals(old_state);
472 
473  old_state = ui_->object_z->blockSignals(true);
474  ui_->object_z->setValue(model_frame_t_scene_marker.translation().z());
475  ui_->object_z->blockSignals(old_state);
476 
477  Eigen::Vector3d xyz = model_frame_t_scene_marker.linear().eulerAngles(0, 1, 2);
478 
479  old_state = ui_->object_rx->blockSignals(true);
480  ui_->object_rx->setValue(xyz[0]);
481  ui_->object_rx->blockSignals(old_state);
482 
483  old_state = ui_->object_ry->blockSignals(true);
484  ui_->object_ry->setValue(xyz[1]);
485  ui_->object_ry->blockSignals(old_state);
486 
487  old_state = ui_->object_rz->blockSignals(true);
488  ui_->object_rz->setValue(xyz[2]);
489  ui_->object_rz->blockSignals(old_state);
490 
491  updateCollisionObjectPose(false);
492 }
493 
494 void MotionPlanningFrame::copySelectedCollisionObject()
495 {
496  QList<QListWidgetItem*> sel = ui_->collision_objects_list->selectedItems();
497  if (sel.empty())
498  return;
499 
501  if (!ps)
502  return;
503 
504  for (const QListWidgetItem* item : sel)
505  {
506  std::string name = item->text().toStdString();
507  collision_detection::CollisionEnv::ObjectConstPtr obj = ps->getWorld()->getObject(name);
508  if (!obj)
509  continue;
510 
511  // find a name for the copy
512  name.insert(0, "Copy of ");
513  if (ps->getWorld()->hasObject(name))
514  {
515  name += " ";
516  unsigned int n = 1;
517  while (ps->getWorld()->hasObject(name + std::to_string(n)))
518  n++;
519  name += std::to_string(n);
520  }
521  ps->getWorldNonConst()->addToObject(name, obj->shapes_, obj->shape_poses_);
522  RCLCPP_DEBUG(logger_, "Copied collision object to '%s'", name.c_str());
523  }
524  setLocalSceneEdited();
525  planning_display_->addMainLoopJob([this] { populateCollisionObjectsList(); });
526 }
527 
528 void MotionPlanningFrame::computeSaveSceneButtonClicked()
529 {
531  {
532  moveit_msgs::msg::PlanningScene msg;
533  planning_display_->getPlanningSceneRO()->getPlanningSceneMsg(msg);
534  try
535  {
536  planning_scene_storage_->removePlanningScene(msg.name);
537  planning_scene_storage_->addPlanningScene(msg);
538  }
539  catch (std::exception& ex)
540  {
541  RCLCPP_ERROR(logger_, "%s", ex.what());
542  }
543 
544  planning_display_->addMainLoopJob([this] { populatePlanningSceneTreeView(); });
545  }
546 }
547 
548 void MotionPlanningFrame::computeSaveQueryButtonClicked(const std::string& scene, const std::string& query_name)
549 {
553  {
554  try
555  {
556  if (!query_name.empty())
557  planning_scene_storage_->removePlanningQuery(scene, query_name);
558  planning_scene_storage_->addPlanningQuery(mreq, scene, query_name);
559  }
560  catch (std::exception& ex)
561  {
562  RCLCPP_ERROR(logger_, "%s", ex.what());
563  }
564 
565  planning_display_->addMainLoopJob([this] { populatePlanningSceneTreeView(); });
566  }
567 }
568 
569 void MotionPlanningFrame::computeDeleteSceneButtonClicked()
570 {
572  {
573  QList<QTreeWidgetItem*> sel = ui_->planning_scene_tree->selectedItems();
574  if (!sel.empty())
575  {
576  QTreeWidgetItem* s = sel.front();
577  if (s->type() == ITEM_TYPE_SCENE)
578  {
579  std::string scene = s->text(0).toStdString();
580  try
581  {
582  planning_scene_storage_->removePlanningScene(scene);
583  }
584  catch (std::exception& ex)
585  {
586  RCLCPP_ERROR(logger_, "%s", ex.what());
587  }
588  }
589  else
590  {
591  // if we selected a query name, then we overwrite that query
592  std::string scene = s->parent()->text(0).toStdString();
593  try
594  {
595  planning_scene_storage_->removePlanningScene(scene);
596  }
597  catch (std::exception& ex)
598  {
599  RCLCPP_ERROR(logger_, "%s", ex.what());
600  }
601  }
602  planning_display_->addMainLoopJob([this] { populatePlanningSceneTreeView(); });
603  }
604  }
605 }
606 
607 void MotionPlanningFrame::computeDeleteQueryButtonClicked()
608 {
610  {
611  QList<QTreeWidgetItem*> sel = ui_->planning_scene_tree->selectedItems();
612  if (!sel.empty())
613  {
614  QTreeWidgetItem* s = sel.front();
615  if (s->type() == ITEM_TYPE_QUERY)
616  {
617  std::string scene = s->parent()->text(0).toStdString();
618  std::string query_name = s->text(0).toStdString();
619  try
620  {
621  planning_scene_storage_->removePlanningQuery(scene, query_name);
622  }
623  catch (std::exception& ex)
624  {
625  RCLCPP_ERROR(logger_, "%s", ex.what());
626  }
627  planning_display_->addMainLoopJob([this, s] { computeDeleteQueryButtonClickedHelper(s); });
628  }
629  }
630  }
631 }
632 
633 void MotionPlanningFrame::computeDeleteQueryButtonClickedHelper(QTreeWidgetItem* s)
634 {
635  ui_->planning_scene_tree->setUpdatesEnabled(false);
636  s->parent()->removeChild(s);
637  ui_->planning_scene_tree->setUpdatesEnabled(true);
638 }
639 
640 void MotionPlanningFrame::checkPlanningSceneTreeEnabledButtons()
641 {
642  QList<QTreeWidgetItem*> sel = ui_->planning_scene_tree->selectedItems();
643  if (sel.empty())
644  {
645  ui_->load_scene_button->setEnabled(false);
646  ui_->load_query_button->setEnabled(false);
647  ui_->save_query_button->setEnabled(false);
648  ui_->delete_scene_button->setEnabled(false);
649  }
650  else
651  {
652  ui_->save_query_button->setEnabled(true);
653 
654  QTreeWidgetItem* s = sel.front();
655 
656  // if the item is a PlanningScene
657  if (s->type() == ITEM_TYPE_SCENE)
658  {
659  ui_->load_scene_button->setEnabled(true);
660  ui_->load_query_button->setEnabled(false);
661  ui_->delete_scene_button->setEnabled(true);
662  ui_->delete_query_button->setEnabled(false);
663  ui_->save_query_button->setEnabled(true);
664  }
665  else
666  {
667  // if the item is a query
668  ui_->load_scene_button->setEnabled(false);
669  ui_->load_query_button->setEnabled(true);
670  ui_->delete_scene_button->setEnabled(false);
671  ui_->delete_query_button->setEnabled(true);
672  }
673  }
674 }
675 
676 void MotionPlanningFrame::computeLoadSceneButtonClicked()
677 {
679  {
680  QList<QTreeWidgetItem*> sel = ui_->planning_scene_tree->selectedItems();
681  if (!sel.empty())
682  {
683  QTreeWidgetItem* s = sel.front();
684  if (s->type() == ITEM_TYPE_SCENE)
685  {
686  std::string scene = s->text(0).toStdString();
687  RCLCPP_DEBUG(logger_, "Attempting to load scene '%s'", scene.c_str());
688 
690  bool got_ps = false;
691  try
692  {
693  got_ps = planning_scene_storage_->getPlanningScene(scene_m, scene);
694  }
695  catch (std::exception& ex)
696  {
697  RCLCPP_ERROR(logger_, "%s", ex.what());
698  }
699 
700  if (got_ps)
701  {
702  RCLCPP_INFO(logger_, "Loaded scene '%s'", scene.c_str());
704  {
705  if (scene_m->robot_model_name != planning_display_->getRobotModel()->getName())
706  {
707  RCLCPP_INFO(logger_,
708  "Scene '%s' was saved for robot '%s' but we are using robot '%s'. Using scene geometry only",
709  scene.c_str(), scene_m->robot_model_name.c_str(),
710  planning_display_->getRobotModel()->getName().c_str());
711  planning_scene_world_publisher_->publish(scene_m->world);
712  // publish the parts that are not in the world
713  moveit_msgs::msg::PlanningScene diff;
714  diff.is_diff = true;
715  diff.name = scene_m->name;
716  planning_scene_publisher_->publish(diff);
717  }
718  else
719  planning_scene_publisher_->publish(static_cast<const moveit_msgs::msg::PlanningScene&>(*scene_m));
720  }
721  else
722  planning_scene_publisher_->publish(static_cast<const moveit_msgs::msg::PlanningScene&>(*scene_m));
723  }
724  else
725  {
726  RCLCPP_WARN(logger_, "Failed to load scene '%s'. Has the message format changed since the scene was saved?",
727  scene.c_str());
728  }
729  }
730  }
731  }
732 }
733 
734 void MotionPlanningFrame::computeLoadQueryButtonClicked()
735 {
737  {
738  QList<QTreeWidgetItem*> sel = ui_->planning_scene_tree->selectedItems();
739  if (!sel.empty())
740  {
741  QTreeWidgetItem* s = sel.front();
742  if (s->type() == ITEM_TYPE_QUERY)
743  {
744  std::string scene = s->parent()->text(0).toStdString();
745  std::string query_name = s->text(0).toStdString();
746 
748  bool got_q = false;
749  try
750  {
751  got_q = planning_scene_storage_->getPlanningQuery(mp, scene, query_name);
752  }
753  catch (std::exception& ex)
754  {
755  RCLCPP_ERROR(logger_, "%s", ex.what());
756  }
757 
758  if (got_q)
759  {
760  moveit::core::RobotStatePtr start_state(
763  mp->start_state, *start_state);
764  planning_display_->setQueryStartState(*start_state);
765 
766  auto goal_state = std::make_shared<moveit::core::RobotState>(*planning_display_->getQueryGoalState());
767  for (const moveit_msgs::msg::Constraints& goal_constraint : mp->goal_constraints)
768  {
769  if (!goal_constraint.joint_constraints.empty())
770  {
771  std::map<std::string, double> vals;
772  for (const moveit_msgs::msg::JointConstraint& joint_constraint : goal_constraint.joint_constraints)
773  vals[joint_constraint.joint_name] = joint_constraint.position;
774  goal_state->setVariablePositions(vals);
775  break;
776  }
777  }
778  planning_display_->setQueryGoalState(*goal_state);
779  }
780  else
781  {
782  RCLCPP_ERROR(logger_,
783  "Failed to load planning query '%s'. Has the message format changed since the query was saved?",
784  query_name.c_str());
785  }
786  }
787  }
788  }
789 }
790 
791 visualization_msgs::msg::InteractiveMarker
792 MotionPlanningFrame::createObjectMarkerMsg(const collision_detection::CollisionEnv::ObjectConstPtr& obj)
793 {
794  Eigen::Vector3d center;
795  double scale;
796  shapes::computeShapeBoundingSphere(obj->shapes_[0].get(), center, scale);
797  geometry_msgs::msg::PoseStamped shape_pose = tf2::toMsg(tf2::Stamped<Eigen::Isometry3d>(
798  obj->pose_, std::chrono::system_clock::now(), planning_display_->getRobotModel()->getModelFrame()));
799  // TODO(felixvd): Consider where to place the object marker.
800  // obj->pose*obj->shape_poses_[0] is backwards compatible, sits on the visible part of
801  // the object, and is more difficult to implement now.
802  // obj->pose is easier to implement and makes more sense.
803  scale = (scale + center.cwiseAbs().maxCoeff()) * 2.0 * 1.2; // add padding of 20% size
804 
805  // create an interactive marker msg for the given shape
806  visualization_msgs::msg::InteractiveMarker imarker =
807  robot_interaction::make6DOFMarker("marker_scene_object", shape_pose, scale);
808  imarker.description = obj->id_;
809  interactive_markers::autoComplete(imarker);
810  return imarker;
811 }
812 
813 void MotionPlanningFrame::createSceneInteractiveMarker()
814 {
815  QList<QListWidgetItem*> sel = ui_->collision_objects_list->selectedItems();
816  if (sel.empty())
817  return;
818 
820  if (!ps)
821  return;
822 
824  ps->getWorld()->getObject(sel[0]->text().toStdString());
825  if (obj && obj->shapes_.size() == 1)
826  {
827  scene_marker_ = std::make_shared<rviz_default_plugins::displays::InteractiveMarker>(
828  planning_display_->getSceneNode(), context_);
829  scene_marker_->processMessage(createObjectMarkerMsg(obj));
830  scene_marker_->setShowAxes(false);
831 
832  // Connect signals
833  connect(scene_marker_.get(), SIGNAL(userFeedback(visualization_msgs::msg::InteractiveMarkerFeedback&)), this,
834  SLOT(imProcessFeedback(visualization_msgs::msg::InteractiveMarkerFeedback&)));
835  }
836  else
837  {
838  scene_marker_.reset();
839  }
840 }
841 
842 void MotionPlanningFrame::renameCollisionObject(QListWidgetItem* item)
843 {
844  long unsigned int version = known_collision_objects_version_;
845  if (item->text().isEmpty())
846  {
847  QMessageBox::warning(this, "Invalid object name", "Cannot set an empty object name.");
848  if (version == known_collision_objects_version_)
849  item->setText(QString::fromStdString(known_collision_objects_[item->type()].first));
850  return;
851  }
852 
853  std::string item_text = item->text().toStdString();
854  bool already_exists = planning_display_->getPlanningSceneRO()->getWorld()->hasObject(item_text);
855  if (!already_exists)
856  already_exists = planning_display_->getPlanningSceneRO()->getCurrentState().hasAttachedBody(item_text);
857  if (already_exists)
858  {
859  QMessageBox::warning(this, "Duplicate object name",
860  QString("The name '")
861  .append(item->text())
862  .append("' already exists. Not renaming object ")
863  .append((known_collision_objects_[item->type()].first.c_str())));
864  if (version == known_collision_objects_version_)
865  item->setText(QString::fromStdString(known_collision_objects_[item->type()].first));
866  return;
867  }
868 
869  if (item->checkState() == Qt::Unchecked)
870  {
873  ps->getWorld()->getObject(known_collision_objects_[item->type()].first);
874  if (obj)
875  {
876  known_collision_objects_[item->type()].first = item_text;
877  ps->getWorldNonConst()->removeObject(obj->id_);
878  ps->getWorldNonConst()->addToObject(known_collision_objects_[item->type()].first, obj->pose_, obj->shapes_,
879  obj->shape_poses_);
880  ps->getWorldNonConst()->setSubframesOfObject(obj->id_, obj->subframe_poses_);
881  if (scene_marker_)
882  {
883  scene_marker_.reset();
884  planning_display_->addMainLoopJob([this] { createSceneInteractiveMarker(); });
885  }
886  }
887  }
888  else
889  {
890  // rename attached body
892  moveit::core::RobotState& cs = ps->getCurrentStateNonConst();
893  const moveit::core::AttachedBody* ab = cs.getAttachedBody(known_collision_objects_[item->type()].first);
894  if (ab)
895  {
896  known_collision_objects_[item->type()].first = item_text;
897  auto new_ab = std::make_unique<moveit::core::AttachedBody>(
898  ab->getAttachedLink(), known_collision_objects_[item->type()].first, ab->getPose(), ab->getShapes(),
899  ab->getShapePoses(), ab->getTouchLinks(), ab->getDetachPosture(), ab->getSubframes());
900  cs.clearAttachedBody(ab->getName());
901  cs.attachBody(std::move(new_ab));
902  }
903  }
904  setLocalSceneEdited();
905 }
906 
907 void MotionPlanningFrame::attachDetachCollisionObject(QListWidgetItem* item)
908 {
909  long unsigned int version = known_collision_objects_version_;
910  bool checked = item->checkState() == Qt::Checked;
911  std::pair<std::string, bool> data = known_collision_objects_[item->type()];
912  moveit_msgs::msg::AttachedCollisionObject aco;
913 
914  if (checked) // we need to attach a known collision object
915  {
916  QStringList links;
917  const std::vector<std::string>& links_std = planning_display_->getRobotModel()->getLinkModelNames();
918  for (const std::string& link : links_std)
919  links.append(QString::fromStdString(link));
920  bool ok = false;
921  QString response =
922  QInputDialog::getItem(this, tr("Select Link Name"), tr("Choose the link to attach to:"), links, 0, false, &ok);
923  if (!ok)
924  {
925  if (version == known_collision_objects_version_)
926  item->setCheckState(Qt::Unchecked);
927  return;
928  }
929  aco.link_name = response.toStdString();
930  aco.object.id = data.first;
931  aco.object.operation = moveit_msgs::msg::CollisionObject::ADD;
932  }
933  else // we need to detach an attached object
934  {
936  const moveit::core::AttachedBody* attached_body = ps->getCurrentState().getAttachedBody(data.first);
937  if (attached_body)
938  {
939  aco.link_name = attached_body->getAttachedLinkName();
940  aco.object.id = attached_body->getName();
941  aco.object.operation = moveit_msgs::msg::CollisionObject::REMOVE;
942  }
943  }
944 
946  {
948  // we loop through the list in case updates were received since the start of the function
949  for (std::pair<std::string, bool>& known_collision_object : known_collision_objects_)
950  {
951  if (known_collision_object.first == data.first)
952  {
953  known_collision_object.second = checked;
954  break;
955  }
956  }
957  ps->processAttachedCollisionObjectMsg(aco);
958  rs = ps->getCurrentState();
959  }
960 
961  selectedCollisionObjectChanged();
962  setLocalSceneEdited();
965 }
966 
967 void MotionPlanningFrame::populateCollisionObjectsList()
968 {
969  ui_->collision_objects_list->setUpdatesEnabled(false);
970  bool old_state = ui_->collision_objects_list->blockSignals(true);
971  bool octomap_in_scene = false;
972 
973  {
974  QList<QListWidgetItem*> sel = ui_->collision_objects_list->selectedItems();
975  std::set<std::string> to_select;
976  for (QListWidgetItem* item : sel)
977  to_select.insert(item->text().toStdString());
978  ui_->collision_objects_list->clear();
979  known_collision_objects_.clear();
980  known_collision_objects_version_++;
981 
983  if (ps)
984  {
985  const std::vector<std::string>& collision_object_names = ps->getWorld()->getObjectIds();
986  for (std::size_t i = 0; i < collision_object_names.size(); ++i)
987  {
988  if (collision_object_names[i] == planning_scene::PlanningScene::OCTOMAP_NS)
989  {
990  octomap_in_scene = true;
991  continue;
992  }
993 
994  QListWidgetItem* item = new QListWidgetItem(QString::fromStdString(collision_object_names[i]),
995  ui_->collision_objects_list, static_cast<int>(i));
996  item->setFlags(item->flags() | Qt::ItemIsEditable);
997  item->setToolTip(item->text());
998  item->setCheckState(Qt::Unchecked);
999  if (to_select.find(collision_object_names[i]) != to_select.end())
1000  item->setSelected(true);
1001  ui_->collision_objects_list->addItem(item);
1002  known_collision_objects_.push_back(std::make_pair(collision_object_names[i], false));
1003  }
1004 
1005  const moveit::core::RobotState& cs = ps->getCurrentState();
1006  std::vector<const moveit::core::AttachedBody*> attached_bodies;
1007  cs.getAttachedBodies(attached_bodies);
1008  for (std::size_t i = 0; i < attached_bodies.size(); ++i)
1009  {
1010  QListWidgetItem* item =
1011  new QListWidgetItem(QString::fromStdString(attached_bodies[i]->getName()), ui_->collision_objects_list,
1012  static_cast<int>(i + collision_object_names.size()));
1013  item->setFlags(item->flags() | Qt::ItemIsEditable);
1014  item->setToolTip(item->text());
1015  item->setCheckState(Qt::Checked);
1016  if (to_select.find(attached_bodies[i]->getName()) != to_select.end())
1017  item->setSelected(true);
1018  ui_->collision_objects_list->addItem(item);
1019  known_collision_objects_.push_back(std::make_pair(attached_bodies[i]->getName(), true));
1020  }
1021  }
1022  }
1023 
1024  ui_->clear_octomap_button->setEnabled(octomap_in_scene);
1025  ui_->collision_objects_list->blockSignals(old_state);
1026  ui_->collision_objects_list->setUpdatesEnabled(true);
1027  selectedCollisionObjectChanged();
1028 }
1029 
1030 void MotionPlanningFrame::exportGeometryAsTextButtonClicked()
1031 {
1032  QString path =
1033  QFileDialog::getSaveFileName(this, tr("Export Scene Geometry"), tr(""), tr("Scene Geometry (*.scene)"));
1034  if (!path.isEmpty())
1035  {
1036  planning_display_->addBackgroundJob([this, path = path.toStdString()] { computeExportGeometryAsText(path); },
1037  "export as text");
1038  }
1039 }
1040 
1041 void MotionPlanningFrame::computeExportGeometryAsText(const std::string& path)
1042 {
1044  if (ps)
1045  {
1046  std::string p = (path.length() < 7 || path.substr(path.length() - 6) != ".scene") ? path + ".scene" : path;
1047  std::ofstream fout(p.c_str());
1048  if (fout.good())
1049  {
1050  ps->saveGeometryToStream(fout);
1051  fout.close();
1052  RCLCPP_INFO(logger_, "Saved current scene geometry to '%s'", p.c_str());
1053  }
1054  else
1055  RCLCPP_WARN(logger_, "Unable to save current scene geometry to '%s'", p.c_str());
1056  }
1057 }
1058 
1059 void MotionPlanningFrame::computeImportGeometryFromText(const std::string& path)
1060 {
1062  if (ps)
1063  {
1064  std::ifstream fin(path.c_str());
1065  if (ps->loadGeometryFromStream(fin))
1066  {
1067  RCLCPP_INFO(logger_, "Loaded scene geometry from '%s'", path.c_str());
1068  planning_display_->addMainLoopJob([this] { populateCollisionObjectsList(); });
1070  setLocalSceneEdited();
1071  }
1072  else
1073  {
1074  QMessageBox::warning(nullptr, "Loading scene geometry",
1075  "Failed to load scene geometry.\n"
1076  "See console output for more details.");
1077  }
1078  }
1079 }
1080 
1081 void MotionPlanningFrame::importGeometryFromTextButtonClicked()
1082 {
1083  QString path =
1084  QFileDialog::getOpenFileName(this, tr("Import Scene Geometry"), tr(""), tr("Scene Geometry (*.scene)"));
1085  if (!path.isEmpty())
1086  {
1087  planning_display_->addBackgroundJob([this, path = path.toStdString()] { computeImportGeometryFromText(path); },
1088  "import from text");
1089  }
1090 }
1091 } // namespace moveit_rviz_plugin
World::ObjectConstPtr ObjectConstPtr
Object defining bodies that can be attached to robot links.
Definition: attached_body.h:58
const std::string & getAttachedLinkName() const
Get the name of the link this body is attached to.
Definition: attached_body.h:92
const std::set< std::string > & getTouchLinks() const
Get the links that the attached body is allowed to touch.
const trajectory_msgs::msg::JointTrajectory & getDetachPosture() const
Return the posture that is necessary for the object to be released, (if any). This is useful for exam...
const moveit::core::FixedTransformsMap & getSubframes() const
Get subframes of this object (relative to the object pose). The returned transforms are guaranteed to...
const std::string & getName() const
Get the name of the attached body.
Definition: attached_body.h:74
const std::vector< shapes::ShapeConstPtr > & getShapes() const
Get the shapes that make up this attached body.
const LinkModel * getAttachedLink() const
Get the model of the link this body is attached to.
Definition: attached_body.h:98
const Eigen::Isometry3d & getPose() const
Get the pose of the attached body relative to the parent link.
Definition: attached_body.h:80
const EigenSTL::vector_Isometry3d & getShapePoses() const
Get the shape poses (the transforms to the shapes of this body, relative to the pose)....
Representation of a robot's state. This includes position, velocity, acceleration and effort.
Definition: robot_state.h:90
void attachBody(std::unique_ptr< AttachedBody > attached_body)
Add an attached body to this state.
void getAttachedBodies(std::vector< const AttachedBody * > &attached_bodies) const
Get all bodies attached to the model corresponding to this state.
bool clearAttachedBody(const std::string &id)
Remove the attached body named id. Return false if the object was not found (and thus not removed)....
const AttachedBody * getAttachedBody(const std::string &name) const
Get the attached body named name. Return nullptr if not found.
void setQueryGoalState(const moveit::core::RobotState &goal)
void setQueryStartState(const moveit::core::RobotState &start)
void updateQueryStates(const moveit::core::RobotState &current_state)
moveit::core::RobotStateConstPtr getQueryStartState() const
moveit::core::RobotStateConstPtr getQueryGoalState() const
moveit_warehouse::PlanningSceneStoragePtr planning_scene_storage_
std::shared_ptr< rviz_default_plugins::displays::InteractiveMarker > scene_marker_
void constructPlanningRequest(moveit_msgs::msg::MotionPlanRequest &mreq)
planning_scene_monitor::LockedPlanningSceneRO getPlanningSceneRO() const
get read-only access to planning scene
planning_scene_monitor::LockedPlanningSceneRW getPlanningSceneRW()
get write access to planning scene
void addMainLoopJob(const std::function< void()> &job)
queue the execution of this function for the next time the main update() loop gets called
const moveit::core::RobotModelConstPtr & getRobotModel() const
void addBackgroundJob(const std::function< void()> &job, const std::string &name)
const planning_scene_monitor::PlanningSceneMonitorPtr & getPlanningSceneMonitor()
static const std::string OCTOMAP_NS
This is a convenience class for obtaining access to an instance of a locked PlanningScene.
This is a convenience class for obtaining access to an instance of a locked PlanningScene.
Vec3fX< details::Vec3Data< double > > Vector3d
Definition: fcl_compat.h:89
std::map< std::string, Eigen::Isometry3d, std::less< std::string >, Eigen::aligned_allocator< std::pair< const std::string, Eigen::Isometry3d > > > FixedTransformsMap
Map frame names to the transformation matrix that can transform objects from the frame name to the pl...
Definition: transforms.h:53
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.
warehouse_ros::MessageWithMetadata< moveit_msgs::msg::PlanningScene >::ConstPtr PlanningSceneWithMetadata
warehouse_ros::MessageWithMetadata< moveit_msgs::msg::MotionPlanRequest >::ConstPtr MotionPlanRequestWithMetadata
moveit_msgs::msg::MotionPlanRequest MotionPlanRequest
std::string append(const std::string &left, const std::string &right)
visualization_msgs::msg::InteractiveMarker make6DOFMarker(const std::string &name, const geometry_msgs::msg::PoseStamped &stamped, double scale, bool orientation_fixed=false)
version
Definition: setup.py:8
name
Definition: setup.py:7