moveit2
The MoveIt Motion Planning Framework for ROS 2.
planning_groups_widget.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: Dave Coleman */
36 
37 // ******************************************************************************************
38 /* DEVELOPER NOTES
39 
40  This widget has 6 subscreens, located in somewhat different places
41  - Main screen, the tree view of all groups & subgroups - embedded in this file as a function
42  - Add/Edit Group screen - located in group_edit_widget.cpp
43  - Joint Collection Screen - implements the double_list_widget.cpp widget
44  - Link Collection Screen - implements the double_list_widget.cpp widget
45  - Kinematic Chain Screen - uses it own custom widget - kinematic_chain_widget.cpp
46  - Subgroup Screen - implements the double_list_widget.cpp widget
47 */
48 // ******************************************************************************************
49 
52 #include <boost/lexical_cast.hpp>
53 
54 // Qt
55 #include <QApplication>
56 #include <QComboBox>
57 #include <QHBoxLayout>
58 #include <QLabel>
59 #include <QLineEdit>
60 #include <QMessageBox>
61 #include <QPushButton>
62 #include <QStackedWidget>
63 #include <QTableWidget>
64 #include <QTreeWidget>
65 #include <QTreeWidgetItem>
66 #include <QVBoxLayout>
67 
68 namespace moveit_setup
69 {
70 namespace srdf_setup
71 {
72 // Name of rviz topic in ROS
73 static const std::string VIS_TOPIC_NAME = "planning_components_visualization";
74 
75 // ******************************************************************************************
76 // Constructor
77 // ******************************************************************************************
79 {
80  // Basic widget container
81  QVBoxLayout* layout = new QVBoxLayout();
82 
83  // Top Label Area ------------------------------------------------
84  auto header = new HeaderWidget(
85  "Define Planning Groups",
86  "Create and edit 'joint model' groups for your robot based on joint collections, "
87  "link collections, kinematic chains or subgroups. "
88  "A planning group defines the set of (joint, link) pairs considered for planning "
89  "and collision checking. Define individual groups for each subset of the robot you want to plan for.\n"
90  "Note: when adding a link to the group, its parent joint is added too and vice versa.",
91  this);
92  layout->addWidget(header);
93 
94  // Left Side ---------------------------------------------
95 
96  // Create left side widgets
97  groups_tree_widget_ = createContentsWidget(); // included in this file
98 
99  // Joints edit widget
100  joints_widget_ = new DoubleListWidget(this, "Joint Collection", "Joint");
101  connect(joints_widget_, SIGNAL(cancelEditing()), this, SLOT(cancelEditing()));
102  connect(joints_widget_, SIGNAL(doneEditing()), this, SLOT(saveJointsScreen()));
103  connect(joints_widget_, SIGNAL(previewSelected(std::vector<std::string>)), this,
104  SLOT(previewSelectedJoints(std::vector<std::string>)));
105 
106  // Links edit widget
107  links_widget_ = new DoubleListWidget(this, "Link Collection", "Link");
108  connect(links_widget_, SIGNAL(cancelEditing()), this, SLOT(cancelEditing()));
109  connect(links_widget_, SIGNAL(doneEditing()), this, SLOT(saveLinksScreen()));
110  connect(links_widget_, SIGNAL(previewSelected(std::vector<std::string>)), this,
111  SLOT(previewSelectedLink(std::vector<std::string>)));
112 
113  // Chain Widget
114  chain_widget_ = new KinematicChainWidget(this, rviz_panel_);
115  connect(chain_widget_, SIGNAL(cancelEditing()), this, SLOT(cancelEditing()));
116  connect(chain_widget_, SIGNAL(doneEditing()), this, SLOT(saveChainScreen()));
117 
118  // Subgroups Widget
119  subgroups_widget_ = new DoubleListWidget(this, "Subgroup", "Subgroup");
120  connect(subgroups_widget_, SIGNAL(cancelEditing()), this, SLOT(cancelEditing()));
121  connect(subgroups_widget_, SIGNAL(doneEditing()), this, SLOT(saveSubgroupsScreen()));
122  connect(subgroups_widget_, SIGNAL(previewSelected(std::vector<std::string>)), this,
123  SLOT(previewSelectedSubgroup(std::vector<std::string>)));
124 
125  // Group Edit Widget
126  group_edit_widget_ = new GroupEditWidget(this, setup_step_);
127  connect(group_edit_widget_, SIGNAL(cancelEditing()), this, SLOT(cancelEditing()));
128  connect(group_edit_widget_, SIGNAL(deleteGroup()), this, SLOT(deleteGroup()));
129  connect(group_edit_widget_, SIGNAL(save()), this, SLOT(saveGroupScreenEdit()));
130  connect(group_edit_widget_, SIGNAL(saveJoints()), this, SLOT(saveGroupScreenJoints()));
131  connect(group_edit_widget_, SIGNAL(saveLinks()), this, SLOT(saveGroupScreenLinks()));
132  connect(group_edit_widget_, SIGNAL(saveChain()), this, SLOT(saveGroupScreenChain()));
133  connect(group_edit_widget_, SIGNAL(saveSubgroups()), this, SLOT(saveGroupScreenSubgroups()));
134 
135  // Combine into stack: Note, order is same as GroupType!
136  stacked_widget_ = new QStackedWidget(this);
137  stacked_widget_->addWidget(groups_tree_widget_); // screen index 0
138  stacked_widget_->addWidget(joints_widget_); // screen index 1
139  stacked_widget_->addWidget(links_widget_); // screen index 2
140  stacked_widget_->addWidget(chain_widget_); // screen index 3
141  stacked_widget_->addWidget(subgroups_widget_); // screen index 4
142  stacked_widget_->addWidget(group_edit_widget_); // screen index 5
143 
144  showMainScreen();
145 
146  // Finish GUI -----------------------------------------------------------
147 
148  layout->addWidget(stacked_widget_);
149  setLayout(layout);
150 
151  // process the gui
152  QApplication::processEvents();
153 }
154 
155 // ******************************************************************************************
156 // Create the main tree view widget
157 // ******************************************************************************************
158 QWidget* PlanningGroupsWidget::createContentsWidget()
159 {
160  // Main widget
161  QWidget* content_widget = new QWidget(this);
162 
163  // Basic widget container
164  QVBoxLayout* layout = new QVBoxLayout(this);
165 
166  // Tree Box ----------------------------------------------------------------------
167 
168  groups_tree_ = new QTreeWidget(this);
169  groups_tree_->setHeaderLabel("Current Groups");
170  connect(groups_tree_, SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)), this, SLOT(editSelected()));
171  connect(groups_tree_, SIGNAL(itemClicked(QTreeWidgetItem*, int)), this, SLOT(previewSelected()));
172  layout->addWidget(groups_tree_);
173 
174  // Bottom Controls -------------------------------------------------------------
175 
176  QHBoxLayout* controls_layout = new QHBoxLayout();
177 
178  // Expand/Contract controls
179  QLabel* expand_controls = new QLabel(this);
180  expand_controls->setText("<a href='expand'>Expand All</a> <a href='contract'>Collapse All</a>");
181  connect(expand_controls, SIGNAL(linkActivated(const QString)), this, SLOT(alterTree(const QString)));
182  controls_layout->addWidget(expand_controls);
183 
184  // Spacer
185  controls_layout->addItem(new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Minimum));
186 
187  // Delete Selected Button
188  btn_delete_ = new QPushButton("&Delete Selected", this);
189  btn_delete_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
190  btn_delete_->setMaximumWidth(300);
191  connect(btn_delete_, SIGNAL(clicked()), this, SLOT(deleteGroup()));
192  controls_layout->addWidget(btn_delete_);
193  controls_layout->setAlignment(btn_delete_, Qt::AlignRight);
194 
195  // Edit Selected Button
196  btn_edit_ = new QPushButton("&Edit Selected", this);
197  btn_edit_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
198  btn_edit_->setMaximumWidth(300);
199  btn_edit_->hide(); // show once we know if there are existing groups
200  connect(btn_edit_, SIGNAL(clicked()), this, SLOT(editSelected()));
201  controls_layout->addWidget(btn_edit_);
202  controls_layout->setAlignment(btn_edit_, Qt::AlignRight);
203 
204  // Add Group Button
205  QPushButton* btn_add = new QPushButton("&Add Group", this);
206  btn_add->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
207  btn_add->setMaximumWidth(300);
208  connect(btn_add, SIGNAL(clicked()), this, SLOT(addGroup()));
209  controls_layout->addWidget(btn_add);
210  controls_layout->setAlignment(btn_add, Qt::AlignRight);
211 
212  // Add Controls to layout
213  layout->addLayout(controls_layout);
214 
215  // Set layout
216  content_widget->setLayout(layout);
217 
218  return content_widget;
219 }
220 
221 // ******************************************************************************************
222 // Displays data in the link_pairs_ data structure into a QtTableWidget
223 // ******************************************************************************************
224 void PlanningGroupsWidget::loadGroupsTree()
225 {
226  // Disable Tree
227  groups_tree_->setUpdatesEnabled(false); // prevent table from updating until we are completely done
228  groups_tree_->setDisabled(true); // make sure we disable it so that the cellChanged event is not called
229  groups_tree_->clear(); // reset the tree
230 
231  // Display all groups by looping through them
232  std::vector<srdf::Model::Group>& groups = setup_step_.getContainer();
233  for (srdf::Model::Group& group : groups)
234  {
235  loadGroupsTreeRecursive(group, nullptr);
236  }
237 
238  // Re-enable Tree
239  groups_tree_->setUpdatesEnabled(true); // prevent table from updating until we are completely done
240  groups_tree_->setDisabled(false); // make sure we disable it so that the cellChanged event is not called
241 
242  // Show Edit button if there are things to edit
243  if (!groups.empty())
244  {
245  btn_edit_->show();
246  btn_delete_->show();
247  }
248  else
249  {
250  btn_edit_->hide();
251  btn_delete_->hide();
252  }
253 
254  alterTree("expand");
255 }
256 
257 // ******************************************************************************************
258 // Recursively Adds Groups, and subgroups to groups...
259 // ******************************************************************************************
260 void PlanningGroupsWidget::loadGroupsTreeRecursive(srdf::Model::Group& group_it, QTreeWidgetItem* parent)
261 {
262  // Fonts for tree
263  const QFont top_level_font(QFont().defaultFamily(), 11, QFont::Bold);
264  const QFont type_font(QFont().defaultFamily(), 11, QFont::Normal, QFont::StyleItalic);
265 
266  QTreeWidgetItem* group;
267 
268  // Allow a subgroup to open into a whole new group
269  if (parent == nullptr)
270  {
271  group = new QTreeWidgetItem(groups_tree_);
272  group->setText(0, group_it.name_.c_str());
273  group->setFont(0, top_level_font);
274  group->setData(0, Qt::UserRole, QVariant::fromValue(PlanGroupType(&group_it, GROUP)));
275  groups_tree_->addTopLevelItem(group);
276  }
277  else
278  {
279  group = new QTreeWidgetItem(parent);
280  group->setText(0, group_it.name_.c_str());
281  group->setFont(0, top_level_font);
282  group->setData(0, Qt::UserRole, QVariant::fromValue(PlanGroupType(&group_it, GROUP)));
283  parent->addChild(group);
284  }
285 
286  // Joints --------------------------------------------------------------
287  QTreeWidgetItem* joints = new QTreeWidgetItem(group);
288  joints->setText(0, "Joints");
289  joints->setFont(0, type_font);
290  joints->setData(0, Qt::UserRole, QVariant::fromValue(PlanGroupType(&group_it, JOINT)));
291  group->addChild(joints);
292 
293  // Loop through all aval. joints
294  for (std::vector<std::string>::const_iterator joint_it = group_it.joints_.begin(); joint_it != group_it.joints_.end();
295  ++joint_it)
296  {
297  QTreeWidgetItem* j = new QTreeWidgetItem(joints);
298  j->setData(0, Qt::UserRole, QVariant::fromValue(PlanGroupType(&group_it, JOINT)));
299  std::string joint_name;
300 
301  // Get the type of joint this is
302  std::string joint_type = setup_step_.getJointType(*joint_it);
303  if (!joint_type.empty())
304  {
305  joint_name = *joint_it + " - " + joint_type;
306  }
307  else
308  {
309  joint_name = *joint_it;
310  }
311 
312  // Add to tree
313  j->setText(0, joint_name.c_str());
314  joints->addChild(j);
315  }
316 
317  // Links -------------------------------------------------------------
318  QTreeWidgetItem* links = new QTreeWidgetItem(group);
319  links->setText(0, "Links");
320  links->setFont(0, type_font);
321  links->setData(0, Qt::UserRole, QVariant::fromValue(PlanGroupType(&group_it, LINK)));
322  group->addChild(links);
323 
324  // Loop through all aval. links
325  for (std::vector<std::string>::const_iterator joint_it = group_it.links_.begin(); joint_it != group_it.links_.end();
326  ++joint_it)
327  {
328  QTreeWidgetItem* j = new QTreeWidgetItem(links);
329  j->setData(0, Qt::UserRole, QVariant::fromValue(PlanGroupType(&group_it, LINK)));
330  j->setText(0, joint_it->c_str());
331  links->addChild(j);
332  }
333 
334  // Chains -------------------------------------------------------------
335  QTreeWidgetItem* chains = new QTreeWidgetItem(group);
336  chains->setText(0, "Chain");
337  chains->setFont(0, type_font);
338  chains->setData(0, Qt::UserRole, QVariant::fromValue(PlanGroupType(&group_it, CHAIN)));
339  group->addChild(chains);
340 
341  // Warn if there is more than 1 chain per group
342  static bool warn_once = true;
343  if (group_it.chains_.size() > 1 && warn_once)
344  {
345  warn_once = false;
346  QMessageBox::warning(this, "Group with Multiple Kinematic Chains",
347  "Warning: this MoveIt Setup Assistant is only designed to handle one kinematic chain per "
348  "group. The loaded SRDF has more than one kinematic chain for a group. A possible loss of "
349  "data may occur.");
350  }
351 
352  // Loop through all aval. chains
353  for (std::vector<std::pair<std::string, std::string> >::const_iterator chain_it = group_it.chains_.begin();
354  chain_it != group_it.chains_.end(); ++chain_it)
355  {
356  QTreeWidgetItem* j = new QTreeWidgetItem(chains);
357  j->setData(0, Qt::UserRole, QVariant::fromValue(PlanGroupType(&group_it, CHAIN)));
358  j->setText(0, QString(chain_it->first.c_str()).append(" -> ").append(chain_it->second.c_str()));
359  chains->addChild(j);
360  }
361 
362  // Subgroups -------------------------------------------------------------
363  QTreeWidgetItem* subgroups = new QTreeWidgetItem(group);
364  subgroups->setText(0, "Subgroups");
365  subgroups->setFont(0, type_font);
366  subgroups->setData(0, Qt::UserRole, QVariant::fromValue(PlanGroupType(&group_it, SUBGROUP)));
367  group->addChild(subgroups);
368 
369  // Loop through all aval. subgroups
370  for (std::vector<std::string>::iterator subgroup_it = group_it.subgroups_.begin();
371  subgroup_it != group_it.subgroups_.end(); ++subgroup_it)
372  {
373  // Find group with this subgroups' name
374  srdf::Model::Group* searched_group;
375 
376  try
377  {
378  searched_group = setup_step_.find(*subgroup_it);
379  }
380  catch (const std::runtime_error& e)
381  {
382  QMessageBox::critical(this, "Error Loading SRDF",
383  QString("Subgroup '")
384  .append(subgroup_it->c_str())
385  .append("' of group '")
386  .append(group_it.name_.c_str())
387  .append("' not found. Your SRDF is invalid"));
388  return; // TODO: something better for error handling?
389  }
390 
391  // Recurse this function for each new group
392  loadGroupsTreeRecursive(*searched_group, subgroups);
393  }
394 }
395 
396 // ******************************************************************************************
397 // Highlight the group of whatever element is selected in the tree view
398 // ******************************************************************************************
399 void PlanningGroupsWidget::previewSelected()
400 {
401  QTreeWidgetItem* item = groups_tree_->currentItem();
402 
403  // Check that something was actually selected
404  if (item == nullptr)
405  return;
406 
407  // Get the user custom properties of the currently selected row
408  PlanGroupType plan_group = item->data(0, Qt::UserRole).value<PlanGroupType>();
409 
410  // Unhighlight all links
412 
413  // Highlight the group
414  rviz_panel_->highlightGroup(plan_group.group_->name_);
415 }
416 
417 // ******************************************************************************************
418 // Edit whatever element is selected in the tree view
419 // ******************************************************************************************
420 void PlanningGroupsWidget::editSelected()
421 {
422  QTreeWidgetItem* item = groups_tree_->currentItem();
423 
424  // Check that something was actually selected
425  if (item == nullptr)
426  return;
427 
428  adding_new_group_ = false;
429 
430  // Get the user custom properties of the currently selected row
431  PlanGroupType plan_group = item->data(0, Qt::UserRole).value<PlanGroupType>();
432 
433  switch (plan_group.type_)
434  {
435  case JOINT:
436  loadJointsScreen(plan_group.group_);
437  break;
438  case LINK:
439  loadLinksScreen(plan_group.group_);
440  break;
441  case CHAIN:
442  loadChainScreen(plan_group.group_);
443  break;
444  case SUBGROUP:
445  loadSubgroupsScreen(plan_group.group_);
446  break;
447  case GROUP:
448  loadGroupScreen(plan_group.group_);
449  break;
450  default:
451  QMessageBox::critical(this, "Error Loading", "An internal error has occurred while loading.");
452  return;
453  }
454  return_screen_ = 0; // return to main screen directly
455  changeScreen(plan_group.type_);
456 }
457 
458 // ******************************************************************************************
459 // Load the popup screen with correct data for joints
460 // ******************************************************************************************
461 void PlanningGroupsWidget::loadJointsScreen(srdf::Model::Group* this_group)
462 {
463  // Get the names of the all joints
464  const std::vector<std::string>& joints = setup_step_.getJointNames();
465 
466  if (joints.empty())
467  {
468  QMessageBox::critical(this, "Error Loading", "No joints found for robot model");
469  return;
470  }
471 
472  // Set the available joints (left box)
473  joints_widget_->setAvailable(joints);
474 
475  // Set the selected joints (right box)
476  joints_widget_->setSelected(this_group->joints_);
477 
478  // Set the title
479  joints_widget_->title_->setText(
480  QString("Edit '").append(QString::fromUtf8(this_group->name_.c_str())).append("' Joint Collection"));
481 
482  // Remember what is currently being edited so we can later save changes
483  current_edit_group_ = this_group->name_;
484 }
485 
486 // ******************************************************************************************
487 // Load the popup screen with correct data for links
488 // ******************************************************************************************
489 void PlanningGroupsWidget::loadLinksScreen(srdf::Model::Group* this_group)
490 {
491  // Get the names of the all links
492  const std::vector<std::string>& links = setup_step_.getLinkNames();
493 
494  if (links.empty())
495  {
496  QMessageBox::critical(this, "Error Loading", "No links found for robot model");
497  return;
498  }
499 
500  // Set the available links (left box)
501  links_widget_->setAvailable(links);
502 
503  // Set the selected links (right box)
504  links_widget_->setSelected(this_group->links_);
505 
506  // Set the title
507  links_widget_->title_->setText(
508  QString("Edit '").append(QString::fromUtf8(this_group->name_.c_str())).append("' Link Collection"));
509 
510  // Remember what is currently being edited so we can later save changes
511  current_edit_group_ = this_group->name_;
512 }
513 
514 // ******************************************************************************************
515 // Load the popup screen with correct data for chains
516 // ******************************************************************************************
517 void PlanningGroupsWidget::loadChainScreen(srdf::Model::Group* this_group)
518 {
519  chain_widget_->setAvailable(setup_step_.getLinkNameTree());
520 
521  // Make sure there isn't more than 1 chain pair
522  if (this_group->chains_.size() > 1)
523  {
524  QMessageBox::warning(this, "Multiple Kinematic Chains",
525  "Warning: This setup assistant is only designed to handle "
526  "one kinematic chain per group. The loaded SRDF has more "
527  "than one kinematic chain for a group. A possible loss of "
528  "data may occur.");
529  }
530 
531  // Set the selected tip and base of chain if one exists
532  if (!this_group->chains_.empty())
533  {
534  chain_widget_->setSelected(this_group->chains_[0].first, this_group->chains_[0].second);
535  }
536 
537  // Set the title
538  chain_widget_->title_->setText(
539  QString("Edit '").append(QString::fromUtf8(this_group->name_.c_str())).append("' Kinematic Chain"));
540 
541  // Remember what is currently being edited so we can later save changes
542  current_edit_group_ = this_group->name_;
543 }
544 
545 // ******************************************************************************************
546 // Load the popup screen with correct data for subgroups
547 // ******************************************************************************************
548 void PlanningGroupsWidget::loadSubgroupsScreen(srdf::Model::Group* this_group)
549 {
550  // Load all groups into the subgroup screen except the current group
551  std::vector<std::string> subgroups;
552 
553  // Display all groups by looping through them
554  for (const std::string& group_name : setup_step_.getGroupNames())
555  {
556  if (group_name != this_group->name_) // do not include current group
557  {
558  // add to available subgroups list
559  subgroups.push_back(group_name);
560  }
561  }
562 
563  // Set the available subgroups (left box)
564  subgroups_widget_->setAvailable(subgroups);
565 
566  // Set the selected subgroups (right box)
567  subgroups_widget_->setSelected(this_group->subgroups_);
568 
569  // Set the title
570  subgroups_widget_->title_->setText(
571  QString("Edit '").append(QString::fromUtf8(this_group->name_.c_str())).append("' Subgroups"));
572 
573  // Remember what is currently being edited so we can later save changes
574  current_edit_group_ = this_group->name_;
575 }
576 
577 // ******************************************************************************************
578 // Load the popup screen with correct data for groups
579 // ******************************************************************************************
580 void PlanningGroupsWidget::loadGroupScreen(srdf::Model::Group* this_group)
581 {
582  // Load the avail kin solvers. This function only runs once
583  group_edit_widget_->loadKinematicPlannersComboBox();
584 
585  if (this_group == nullptr) // this is a new screen
586  {
587  current_edit_group_.clear(); // provide a blank group name
588  group_edit_widget_->title_->setText("Create New Planning Group");
589  group_edit_widget_->btn_delete_->hide();
590  group_edit_widget_->new_buttons_widget_->show(); // helps user choose next step
591  group_edit_widget_->btn_save_->hide(); // this is only for edit mode
592  }
593  else // load the group name into the widget
594  {
595  current_edit_group_ = this_group->name_;
596  group_edit_widget_->title_->setText(
597  QString("Edit Planning Group '").append(current_edit_group_.c_str()).append("'"));
598  group_edit_widget_->btn_delete_->show();
599  group_edit_widget_->new_buttons_widget_->hide(); // not necessary for existing groups
600  group_edit_widget_->btn_save_->show(); // this is only for edit mode
601  }
602 
603  // Set the data in the edit box
604  group_edit_widget_->setSelected(current_edit_group_, setup_step_.getMetaData(current_edit_group_));
605 }
606 
607 // ******************************************************************************************
608 // Delete a group
609 // ******************************************************************************************
610 void PlanningGroupsWidget::deleteGroup()
611 {
612  std::string group_to_delete = current_edit_group_;
613  if (group_to_delete.empty())
614  {
615  QTreeWidgetItem* item = groups_tree_->currentItem();
616  // Check that something was actually selected
617  if (item == nullptr)
618  return;
619  // Get the user custom properties of the currently selected row
620  PlanGroupType plan_group = item->data(0, Qt::UserRole).value<PlanGroupType>();
621  if (plan_group.group_)
622  group_to_delete = plan_group.group_->name_;
623  }
624  else
625  current_edit_group_.clear();
626  if (group_to_delete.empty())
627  return;
628 
629  // Confirm user wants to delete group
630  if (QMessageBox::question(this, "Confirm Group Deletion",
631  QString("Are you sure you want to delete the planning group '")
632  .append(group_to_delete.c_str())
633  .append("'? This will also delete all references in subgroups, robot poses and end "
634  "effectors."),
635  QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel)
636  {
637  return;
638  }
639 
640  // Ensure we want to delete the states
641  std::vector<std::string> pose_names = setup_step_.getPosesByGroup(group_to_delete);
642  if (!pose_names.empty() &&
643  QMessageBox::question(
644  this, "Confirm Group State Deletion",
645  QString("The group that is about to be deleted has robot poses (robot states) that depend on this "
646  "group. Are you sure you want to delete this group as well as all dependent robot poses?"),
647  QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel)
648  {
649  return;
650  }
651  // Ensure we want to delete the end_effectors
652  std::vector<std::string> eef_names = setup_step_.getEndEffectorsByGroup(group_to_delete);
653  if (!eef_names.empty() &&
654  QMessageBox::question(
655  this, "Confirm End Effector Deletion",
656  QString("The group that is about to be deleted has end effectors (grippers) that depend on this "
657  "group. Are you sure you want to delete this group as well as all dependent end effectors?"),
658  QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel)
659  {
660  return;
661  }
662 
663  setup_step_.deleteGroup(group_to_delete);
664 
665  // Switch to main screen
666  showMainScreen();
667 
668  // Reload main screen table
669  loadGroupsTree();
670 }
671 
672 // ******************************************************************************************
673 // Create a new, empty group
674 // ******************************************************************************************
675 void PlanningGroupsWidget::addGroup()
676 {
677  adding_new_group_ = true;
678 
679  // Load the data
680  loadGroupScreen(nullptr); // nullptr indicates this is a new group, not an existing one
681 
682  // Switch to screen
684 }
685 
686 // ******************************************************************************************
687 // Call when joints edit screen is done and needs to be saved
688 // ******************************************************************************************
689 void PlanningGroupsWidget::saveJointsScreen()
690 {
691  setup_step_.setJoints(current_edit_group_, joints_widget_->getSelectedValues());
692 
693  // Switch to main screen
694  showMainScreen();
695 
696  // Reload main screen table
697  loadGroupsTree();
698 }
699 
700 // ******************************************************************************************
701 // Call when links edit screen is done and needs to be saved
702 // ******************************************************************************************
703 void PlanningGroupsWidget::saveLinksScreen()
704 {
705  setup_step_.setLinks(current_edit_group_, links_widget_->getSelectedValues());
706 
707  // Switch to main screen
708  showMainScreen();
709 
710  // Reload main screen table
711  loadGroupsTree();
712 }
713 
714 // ******************************************************************************************
715 // Call when chains edit screen is done and needs to be saved
716 // ******************************************************************************************
717 void PlanningGroupsWidget::saveChainScreen()
718 {
719  // Get a reference to the supplied strings
720  const std::string& tip = chain_widget_->tip_link_field_->text().trimmed().toStdString();
721  const std::string& base = chain_widget_->base_link_field_->text().trimmed().toStdString();
722 
723  try
724  {
725  setup_step_.setChain(current_edit_group_, base, tip);
726  }
727  catch (const std::runtime_error& e)
728  {
729  QMessageBox::warning(this, "Error Saving", e.what());
730  return;
731  }
732 
733  // Switch to main screen
734  showMainScreen();
735 
736  // Reload main screen table
737  loadGroupsTree();
738 }
739 
740 // ******************************************************************************************
741 // Call when subgroups edit screen is done and needs to be saved
742 // ******************************************************************************************
743 void PlanningGroupsWidget::saveSubgroupsScreen()
744 {
745  try
746  {
747  setup_step_.setSubgroups(current_edit_group_, subgroups_widget_->getSelectedValues());
748  }
749  catch (const std::runtime_error& e)
750  {
751  QMessageBox::warning(this, "Error Saving", e.what());
752  return;
753  }
754 
755  // Switch to main screen
756  showMainScreen();
757 
758  // Reload main screen table
759  loadGroupsTree();
760 }
761 
762 // ******************************************************************************************
763 // Call when groups edit screen is done and needs to be saved
764 // ******************************************************************************************
765 bool PlanningGroupsWidget::saveGroupScreen()
766 {
767  const std::string& group_name = group_edit_widget_->group_name_field_->text().trimmed().toStdString();
768 
769  GroupMetaData meta_data;
770  meta_data.kinematics_solver_ = group_edit_widget_->kinematics_solver_field_->currentText().toStdString();
771  meta_data.kinematics_parameters_file_ = group_edit_widget_->kinematics_parameters_file_field_->text().toStdString();
772  meta_data.default_planner_ = group_edit_widget_->default_planner_field_->currentText().toStdString();
773  if (meta_data.default_planner_ == "None")
774  {
775  meta_data.default_planner_ = "";
776  }
777 
778  // Check that a valid group name has been given
779  if (group_name.empty())
780  {
781  QMessageBox::warning(this, "Error Saving", "A name must be given for the group!");
782  return false;
783  }
784 
785  // Check that the resolution is an double number
786  const std::string& kinematics_resolution = group_edit_widget_->kinematics_resolution_field_->text().toStdString();
787  try
788  {
789  meta_data.kinematics_solver_search_resolution_ = boost::lexical_cast<double>(kinematics_resolution);
790  }
791  catch (boost::bad_lexical_cast&)
792  {
793  QMessageBox::warning(this, "Error Saving", "Unable to convert kinematics resolution to a double number.");
794  return false;
795  }
796 
797  // Check that the timeout is a double number
798  const std::string& kinematics_timeout = group_edit_widget_->kinematics_timeout_field_->text().toStdString();
799  try
800  {
801  meta_data.kinematics_solver_timeout_ = boost::lexical_cast<double>(kinematics_timeout);
802  }
803  catch (boost::bad_lexical_cast&)
804  {
805  QMessageBox::warning(this, "Error Saving", "Unable to convert kinematics solver timeout to a double number.");
806  return false;
807  }
808 
809  // Check that all numbers are >0
810  if (meta_data.kinematics_solver_search_resolution_ <= 0)
811  {
812  QMessageBox::warning(this, "Error Saving", "Kinematics solver search resolution must be greater than 0.");
813  return false;
814  }
815  if (meta_data.kinematics_solver_timeout_ <= 0)
816  {
817  QMessageBox::warning(this, "Error Saving", "Kinematics solver search timeout must be greater than 0.");
818  return false;
819  }
820 
821  try
822  {
823  adding_new_group_ = current_edit_group_.empty();
824  setup_step_.get(group_name, current_edit_group_);
825  }
826  catch (const std::runtime_error& e)
827  {
828  QMessageBox::warning(this, "Error Saving", e.what());
829  return false;
830  }
831 
832  setup_step_.setMetaData(group_name, meta_data);
833 
834  // Reload main screen table
835  loadGroupsTree();
836 
837  // Update the current edit group so that we can proceed to the next screen, if user desires
838  current_edit_group_ = group_name;
839 
840  return true;
841 }
842 
843 // ******************************************************************************************
844 // Call when a new group is created and ready to progress to next screen
845 // ******************************************************************************************
846 void PlanningGroupsWidget::saveGroupScreenEdit()
847 {
848  // Save the group
849  if (!saveGroupScreen())
850  return;
851 
852  // Switch to main screen
853  showMainScreen();
854 }
855 
856 // ******************************************************************************************
857 // Call when a new group is created and ready to progress to next screen
858 // ******************************************************************************************
859 void PlanningGroupsWidget::saveGroupScreenJoints()
860 {
861  // Save the group
862  if (!saveGroupScreen())
863  return;
864 
865  // Find the group we are editing based on the group name string
866  loadJointsScreen(setup_step_.find(current_edit_group_));
867  return_screen_ = GROUP;
868 
869  // Switch to screen
871 }
872 
873 // ******************************************************************************************
874 // Call when a new group is created and ready to progress to next screen
875 // ******************************************************************************************
876 void PlanningGroupsWidget::saveGroupScreenLinks()
877 {
878  // Save the group
879  if (!saveGroupScreen())
880  return;
881 
882  // Find the group we are editing based on the group name string
883  loadLinksScreen(setup_step_.find(current_edit_group_));
884  return_screen_ = GROUP;
885 
886  // Switch to screen
888 }
889 
890 // ******************************************************************************************
891 // Call when a new group is created and ready to progress to next screen
892 // ******************************************************************************************
893 void PlanningGroupsWidget::saveGroupScreenChain()
894 {
895  // Save the group
896  if (!saveGroupScreen())
897  return;
898 
899  // Find the group we are editing based on the group name string
900  loadChainScreen(setup_step_.find(current_edit_group_));
901  return_screen_ = GROUP;
902 
903  // Switch to screen
905 }
906 
907 // ******************************************************************************************
908 // Call when a new group is created and ready to progress to next screen
909 // ******************************************************************************************
910 void PlanningGroupsWidget::saveGroupScreenSubgroups()
911 {
912  // Save the group
913  if (!saveGroupScreen())
914  return;
915 
916  // Find the group we are editing based on the group name string
917  loadSubgroupsScreen(setup_step_.find(current_edit_group_));
918  return_screen_ = GROUP;
919 
920  // Switch to screen
922 }
923 
924 // ******************************************************************************************
925 // Call when edit screen is canceled
926 // ******************************************************************************************
927 void PlanningGroupsWidget::cancelEditing()
928 {
929  if (return_screen_)
930  {
931  changeScreen(return_screen_);
932  return_screen_ = 0;
933  return;
934  }
935  if (!current_edit_group_.empty() && adding_new_group_)
936  {
937  srdf::Model::Group* editing = setup_step_.find(current_edit_group_);
938  if (editing && editing->joints_.empty() && editing->links_.empty() && editing->chains_.empty() &&
939  editing->subgroups_.empty())
940  {
941  setup_step_.deleteGroup(editing->name_);
942  current_edit_group_.clear();
943  // Load the data to the tree
944  loadGroupsTree();
945  }
946  }
947 
948  // Switch to main screen
949  showMainScreen();
950 }
951 
952 // ******************************************************************************************
953 // Called when setup assistant navigation switches to this screen
954 // ******************************************************************************************
956 {
957  // Show the current groups screen
958  showMainScreen();
959 
960  // Load the data to the tree
961  loadGroupsTree();
962 }
963 
964 // ******************************************************************************************
965 // Expand/Collapse Tree
966 // ******************************************************************************************
967 void PlanningGroupsWidget::alterTree(const QString& link)
968 {
969  if (link.contains("expand"))
970  groups_tree_->expandAll();
971  else
972  groups_tree_->collapseAll();
973 }
974 
975 // ******************************************************************************************
976 // Switch to current groups view
977 // ******************************************************************************************
978 void PlanningGroupsWidget::showMainScreen()
979 {
980  stacked_widget_->setCurrentIndex(0);
981 
982  // Announce that this widget is not in modal mode anymore
983  Q_EMIT setModalMode(false);
984 }
985 
986 // ******************************************************************************************
987 // Switch which screen is being shown
988 // ******************************************************************************************
990 {
991  stacked_widget_->setCurrentIndex(index);
992 
993  // Announce this widget's mode
994  Q_EMIT setModalMode(index != 0);
995 }
996 
997 // ******************************************************************************************
998 // Called from Double List widget to highlight a link
999 // ******************************************************************************************
1000 void PlanningGroupsWidget::previewSelectedLink(const std::vector<std::string>& links)
1001 {
1002  // Unhighlight all links
1004 
1005  for (const std::string& link : links)
1006  {
1007  if (link.empty())
1008  {
1009  continue;
1010  }
1011 
1012  // Highlight link
1013  rviz_panel_->highlightLink(link, QColor(255, 0, 0));
1014  }
1015 }
1016 
1017 // ******************************************************************************************
1018 // Called from Double List widget to highlight joints
1019 // ******************************************************************************************
1020 void PlanningGroupsWidget::previewSelectedJoints(const std::vector<std::string>& joints)
1021 {
1022  // Unhighlight all links
1024 
1025  for (const std::string& joint : joints)
1026  {
1027  const std::string link = setup_step_.getChildOfJoint(joint);
1028  if (link.empty())
1029  {
1030  continue;
1031  }
1032 
1033  // Highlight link
1034  rviz_panel_->highlightLink(link, QColor(255, 0, 0));
1035  }
1036 }
1037 
1038 // ******************************************************************************************
1039 // Called from Double List widget to highlight a subgroup
1040 // ******************************************************************************************
1041 void PlanningGroupsWidget::previewSelectedSubgroup(const std::vector<std::string>& groups)
1042 {
1043  // Unhighlight all links
1045 
1046  for (const std::string& group : groups)
1047  {
1048  // Highlight group
1050  }
1051 }
1052 
1053 // ******************************************************************************************
1054 // ******************************************************************************************
1055 // CLASS
1056 // ******************************************************************************************
1057 // ******************************************************************************************
1058 
1059 PlanGroupType::PlanGroupType(srdf::Model::Group* group, const GroupType type) : group_(group), type_(type)
1060 {
1061 }
1062 
1063 } // namespace srdf_setup
1064 } // namespace moveit_setup
1065 
1066 #include <pluginlib/class_list_macros.hpp> // NOLINT
PLUGINLIB_EXPORT_CLASS(cached_ik_kinematics_plugin::CachedIKKinematicsPlugin< kdl_kinematics_plugin::KDLKinematicsPlugin >, kinematics::KinematicsBase)
std::vector< std::string > getSelectedValues() const
Return all the values that are in the "selected" subset.
void setAvailable(const std::vector< std::string > &items)
Loads the available data list.
void setSelected(const std::vector< std::string > &items)
Set the right box.
void highlightGroup(const std::string &group_name)
Definition: rviz_panel.hpp:104
void highlightLink(const std::string &link_name, const QColor &color)
Definition: rviz_panel.hpp:96
The GUI code for one SetupStep.
void setModalMode(bool isModal)
Event for when the current screen is in modal view. Disables the left navigation.
void loadKinematicPlannersComboBox()
Populate the combo dropdown box with kinematic planners.
void setSelected(const std::string &group_name, const GroupMetaData &meta_data)
Set the previous data.
void setAvailable(const LinkNameTree &link_name_tree)
Loads the available data list.
void setSelected(const std::string &base_link, const std::string &tip_link)
Set the link field with previous value.
void focusGiven() override
Received when this widget is chosen from the navigation menu.
std::vector< srdf::Model::Group > & getContainer() override
Returns the reference to the vector in the SRDF.
void setChain(const std::string &group_name, const std::string &base, const std::string &tip)
Set the specified group's kinematic chain.
const std::vector< std::string > & getLinkNames() const
void setLinks(const std::string &group_name, const std::vector< std::string > &link_names)
Set the specified group's link names.
void setSubgroups(const std::string &selected_group_name, const std::vector< std::string > &subgroups)
Set the specified group's subgroups.
const std::vector< std::string > & getJointNames() const
std::string getChildOfJoint(const std::string &joint_name) const
void deleteGroup(const std::string &group_name)
std::vector< std::string > getPosesByGroup(const std::string &group_name) const
std::string getJointType(const std::string &joint_name) const
std::vector< std::string > getGroupNames() const
std::vector< std::string > getEndEffectorsByGroup(const std::string &group_name) const
void setJoints(const std::string &group_name, const std::vector< std::string > &joint_names)
Set the specified group's joint names.
const GroupMetaData & getMetaData(const std::string &group_name) const
void setMetaData(const std::string &group_name, const GroupMetaData &meta_data)
T * find(const std::string &name)
Return a pointer to an item with the given name if it exists, otherwise null.
Definition: srdf_step.hpp:98
T * get(const std::string &name, const std::string &old_name="")
Get a pointer to an item with the given name, creating if necessary. If old_name is provided (and is ...
Definition: srdf_step.hpp:164
std::string append(const std::string &left, const std::string &right)