moveit2
The MoveIt Motion Planning Framework for ROS 2.
Loading...
Searching...
No Matches
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
68namespace moveit_setup
69{
70namespace srdf_setup
71{
72// Name of rviz topic in ROS
73static 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// ******************************************************************************************
158QWidget* 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// ******************************************************************************************
224void 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// ******************************************************************************************
260void 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// ******************************************************************************************
399void 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
411 rviz_panel_->unhighlightAll();
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// ******************************************************************************************
420void 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// ******************************************************************************************
461void 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// ******************************************************************************************
489void 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// ******************************************************************************************
517void 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// ******************************************************************************************
548void 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// ******************************************************************************************
580void 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// ******************************************************************************************
610void 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 {
623 group_to_delete = plan_group.group_->name_;
624 }
625 }
626 else
627 {
628 current_edit_group_.clear();
629 }
630 if (group_to_delete.empty())
631 {
632 return;
633 }
634
635 // Confirm user wants to delete group
636 if (QMessageBox::question(this, "Confirm Group Deletion",
637 QString("Are you sure you want to delete the planning group '")
638 .append(group_to_delete.c_str())
639 .append("'? This will also delete all references in subgroups, robot poses and end "
640 "effectors."),
641 QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel)
642 {
643 return;
644 }
645
646 // Ensure we want to delete the states
647 std::vector<std::string> pose_names = setup_step_.getPosesByGroup(group_to_delete);
648 if (!pose_names.empty() &&
649 QMessageBox::question(
650 this, "Confirm Group State Deletion",
651 QString("The group that is about to be deleted has robot poses (robot states) that depend on this "
652 "group. Are you sure you want to delete this group as well as all dependent robot poses?"),
653 QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel)
654 {
655 return;
656 }
657 // Ensure we want to delete the end_effectors
658 std::vector<std::string> eef_names = setup_step_.getEndEffectorsByGroup(group_to_delete);
659 if (!eef_names.empty() &&
660 QMessageBox::question(
661 this, "Confirm End Effector Deletion",
662 QString("The group that is about to be deleted has end effectors (grippers) that depend on this "
663 "group. Are you sure you want to delete this group as well as all dependent end effectors?"),
664 QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel)
665 {
666 return;
667 }
668
669 setup_step_.deleteGroup(group_to_delete);
670
671 // Switch to main screen
672 showMainScreen();
673
674 // Reload main screen table
675 loadGroupsTree();
676}
677
678// ******************************************************************************************
679// Create a new, empty group
680// ******************************************************************************************
681void PlanningGroupsWidget::addGroup()
682{
683 adding_new_group_ = true;
684
685 // Load the data
686 loadGroupScreen(nullptr); // nullptr indicates this is a new group, not an existing one
687
688 // Switch to screen
690}
691
692// ******************************************************************************************
693// Call when joints edit screen is done and needs to be saved
694// ******************************************************************************************
695void PlanningGroupsWidget::saveJointsScreen()
696{
697 setup_step_.setJoints(current_edit_group_, joints_widget_->getSelectedValues());
698
699 // Switch to main screen
700 showMainScreen();
701
702 // Reload main screen table
703 loadGroupsTree();
704}
705
706// ******************************************************************************************
707// Call when links edit screen is done and needs to be saved
708// ******************************************************************************************
709void PlanningGroupsWidget::saveLinksScreen()
710{
711 setup_step_.setLinks(current_edit_group_, links_widget_->getSelectedValues());
712
713 // Switch to main screen
714 showMainScreen();
715
716 // Reload main screen table
717 loadGroupsTree();
718}
719
720// ******************************************************************************************
721// Call when chains edit screen is done and needs to be saved
722// ******************************************************************************************
723void PlanningGroupsWidget::saveChainScreen()
724{
725 // Get a reference to the supplied strings
726 const std::string& tip = chain_widget_->tip_link_field_->text().trimmed().toStdString();
727 const std::string& base = chain_widget_->base_link_field_->text().trimmed().toStdString();
728
729 try
730 {
731 setup_step_.setChain(current_edit_group_, base, tip);
732 }
733 catch (const std::runtime_error& e)
734 {
735 QMessageBox::warning(this, "Error Saving", e.what());
736 return;
737 }
738
739 // Switch to main screen
740 showMainScreen();
741
742 // Reload main screen table
743 loadGroupsTree();
744}
745
746// ******************************************************************************************
747// Call when subgroups edit screen is done and needs to be saved
748// ******************************************************************************************
749void PlanningGroupsWidget::saveSubgroupsScreen()
750{
751 try
752 {
753 setup_step_.setSubgroups(current_edit_group_, subgroups_widget_->getSelectedValues());
754 }
755 catch (const std::runtime_error& e)
756 {
757 QMessageBox::warning(this, "Error Saving", e.what());
758 return;
759 }
760
761 // Switch to main screen
762 showMainScreen();
763
764 // Reload main screen table
765 loadGroupsTree();
766}
767
768// ******************************************************************************************
769// Call when groups edit screen is done and needs to be saved
770// ******************************************************************************************
771bool PlanningGroupsWidget::saveGroupScreen()
772{
773 const std::string& group_name = group_edit_widget_->group_name_field_->text().trimmed().toStdString();
774
775 GroupMetaData meta_data;
776 meta_data.kinematics_solver_ = group_edit_widget_->kinematics_solver_field_->currentText().toStdString();
777 meta_data.kinematics_parameters_file_ = group_edit_widget_->kinematics_parameters_file_field_->text().toStdString();
778 meta_data.default_planner_ = group_edit_widget_->default_planner_field_->currentText().toStdString();
779 if (meta_data.default_planner_ == "None")
780 {
781 meta_data.default_planner_ = "";
782 }
783
784 // Check that a valid group name has been given
785 if (group_name.empty())
786 {
787 QMessageBox::warning(this, "Error Saving", "A name must be given for the group!");
788 return false;
789 }
790
791 // Check that the resolution is an double number
792 const std::string& kinematics_resolution = group_edit_widget_->kinematics_resolution_field_->text().toStdString();
793 try
794 {
795 meta_data.kinematics_solver_search_resolution_ = boost::lexical_cast<double>(kinematics_resolution);
796 }
797 catch (boost::bad_lexical_cast&)
798 {
799 QMessageBox::warning(this, "Error Saving", "Unable to convert kinematics resolution to a double number.");
800 return false;
801 }
802
803 // Check that the timeout is a double number
804 const std::string& kinematics_timeout = group_edit_widget_->kinematics_timeout_field_->text().toStdString();
805 try
806 {
807 meta_data.kinematics_solver_timeout_ = boost::lexical_cast<double>(kinematics_timeout);
808 }
809 catch (boost::bad_lexical_cast&)
810 {
811 QMessageBox::warning(this, "Error Saving", "Unable to convert kinematics solver timeout to a double number.");
812 return false;
813 }
814
815 // Check that all numbers are >0
816 if (meta_data.kinematics_solver_search_resolution_ <= 0)
817 {
818 QMessageBox::warning(this, "Error Saving", "Kinematics solver search resolution must be greater than 0.");
819 return false;
820 }
821 if (meta_data.kinematics_solver_timeout_ <= 0)
822 {
823 QMessageBox::warning(this, "Error Saving", "Kinematics solver search timeout must be greater than 0.");
824 return false;
825 }
826
827 try
828 {
829 adding_new_group_ = current_edit_group_.empty();
830 setup_step_.get(group_name, current_edit_group_);
831 }
832 catch (const std::runtime_error& e)
833 {
834 QMessageBox::warning(this, "Error Saving", e.what());
835 return false;
836 }
837
838 setup_step_.setMetaData(group_name, meta_data);
839
840 // Reload main screen table
841 loadGroupsTree();
842
843 // Update the current edit group so that we can proceed to the next screen, if user desires
844 current_edit_group_ = group_name;
845
846 return true;
847}
848
849// ******************************************************************************************
850// Call when a new group is created and ready to progress to next screen
851// ******************************************************************************************
852void PlanningGroupsWidget::saveGroupScreenEdit()
853{
854 // Save the group
855 if (!saveGroupScreen())
856 return;
857
858 // Switch to main screen
859 showMainScreen();
860}
861
862// ******************************************************************************************
863// Call when a new group is created and ready to progress to next screen
864// ******************************************************************************************
865void PlanningGroupsWidget::saveGroupScreenJoints()
866{
867 // Save the group
868 if (!saveGroupScreen())
869 return;
870
871 // Find the group we are editing based on the group name string
872 loadJointsScreen(setup_step_.find(current_edit_group_));
873 return_screen_ = GROUP;
874
875 // Switch to screen
877}
878
879// ******************************************************************************************
880// Call when a new group is created and ready to progress to next screen
881// ******************************************************************************************
882void PlanningGroupsWidget::saveGroupScreenLinks()
883{
884 // Save the group
885 if (!saveGroupScreen())
886 return;
887
888 // Find the group we are editing based on the group name string
889 loadLinksScreen(setup_step_.find(current_edit_group_));
890 return_screen_ = GROUP;
891
892 // Switch to screen
894}
895
896// ******************************************************************************************
897// Call when a new group is created and ready to progress to next screen
898// ******************************************************************************************
899void PlanningGroupsWidget::saveGroupScreenChain()
900{
901 // Save the group
902 if (!saveGroupScreen())
903 return;
904
905 // Find the group we are editing based on the group name string
906 loadChainScreen(setup_step_.find(current_edit_group_));
907 return_screen_ = GROUP;
908
909 // Switch to screen
911}
912
913// ******************************************************************************************
914// Call when a new group is created and ready to progress to next screen
915// ******************************************************************************************
916void PlanningGroupsWidget::saveGroupScreenSubgroups()
917{
918 // Save the group
919 if (!saveGroupScreen())
920 return;
921
922 // Find the group we are editing based on the group name string
923 loadSubgroupsScreen(setup_step_.find(current_edit_group_));
924 return_screen_ = GROUP;
925
926 // Switch to screen
928}
929
930// ******************************************************************************************
931// Call when edit screen is canceled
932// ******************************************************************************************
933void PlanningGroupsWidget::cancelEditing()
934{
935 if (return_screen_)
936 {
937 changeScreen(return_screen_);
938 return_screen_ = 0;
939 return;
940 }
941 if (!current_edit_group_.empty() && adding_new_group_)
942 {
943 srdf::Model::Group* editing = setup_step_.find(current_edit_group_);
944 if (editing && editing->joints_.empty() && editing->links_.empty() && editing->chains_.empty() &&
945 editing->subgroups_.empty())
946 {
947 setup_step_.deleteGroup(editing->name_);
948 current_edit_group_.clear();
949 // Load the data to the tree
950 loadGroupsTree();
951 }
952 }
953
954 // Switch to main screen
955 showMainScreen();
956}
957
958// ******************************************************************************************
959// Called when setup assistant navigation switches to this screen
960// ******************************************************************************************
962{
963 // Show the current groups screen
964 showMainScreen();
965
966 // Load the data to the tree
967 loadGroupsTree();
968}
969
970// ******************************************************************************************
971// Expand/Collapse Tree
972// ******************************************************************************************
973void PlanningGroupsWidget::alterTree(const QString& link)
974{
975 if (link.contains("expand"))
976 {
977 groups_tree_->expandAll();
978 }
979 else
980 {
981 groups_tree_->collapseAll();
982 }
983}
984
985// ******************************************************************************************
986// Switch to current groups view
987// ******************************************************************************************
988void PlanningGroupsWidget::showMainScreen()
989{
990 stacked_widget_->setCurrentIndex(0);
991
992 // Announce that this widget is not in modal mode anymore
993 Q_EMIT setModalMode(false);
994}
995
996// ******************************************************************************************
997// Switch which screen is being shown
998// ******************************************************************************************
1000{
1001 stacked_widget_->setCurrentIndex(index);
1002
1003 // Announce this widget's mode
1004 Q_EMIT setModalMode(index != 0);
1005}
1006
1007// ******************************************************************************************
1008// Called from Double List widget to highlight a link
1009// ******************************************************************************************
1010void PlanningGroupsWidget::previewSelectedLink(const std::vector<std::string>& links)
1011{
1012 // Unhighlight all links
1014
1015 for (const std::string& link : links)
1016 {
1017 if (link.empty())
1018 {
1019 continue;
1020 }
1021
1022 // Highlight link
1023 rviz_panel_->highlightLink(link, QColor(255, 0, 0));
1024 }
1025}
1026
1027// ******************************************************************************************
1028// Called from Double List widget to highlight joints
1029// ******************************************************************************************
1030void PlanningGroupsWidget::previewSelectedJoints(const std::vector<std::string>& joints)
1031{
1032 // Unhighlight all links
1033 rviz_panel_->unhighlightAll();
1034
1035 for (const std::string& joint : joints)
1036 {
1037 const std::string link = setup_step_.getChildOfJoint(joint);
1038 if (link.empty())
1039 {
1040 continue;
1041 }
1042
1043 // Highlight link
1044 rviz_panel_->highlightLink(link, QColor(255, 0, 0));
1045 }
1046}
1047
1048// ******************************************************************************************
1049// Called from Double List widget to highlight a subgroup
1050// ******************************************************************************************
1051void PlanningGroupsWidget::previewSelectedSubgroup(const std::vector<std::string>& groups)
1052{
1053 // Unhighlight all links
1054 rviz_panel_->unhighlightAll();
1055
1056 for (const std::string& group : groups)
1057 {
1058 // Highlight group
1059 rviz_panel_->highlightGroup(group);
1060 }
1061}
1062
1063// ******************************************************************************************
1064// ******************************************************************************************
1065// CLASS
1066// ******************************************************************************************
1067// ******************************************************************************************
1068
1069PlanGroupType::PlanGroupType(srdf::Model::Group* group, const GroupType type) : group_(group), type_(type)
1070{
1071}
1072
1073} // namespace srdf_setup
1074} // namespace moveit_setup
1075
1076#include <pluginlib/class_list_macros.hpp> // NOLINT
PLUGINLIB_EXPORT_CLASS(cached_ik_kinematics_plugin::CachedIKKinematicsPlugin< kdl_kinematics_plugin::KDLKinematicsPlugin >, kinematics::KinematicsBase)
void highlightLink(const std::string &link_name, const QColor &color)
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 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.
std::string append(const std::string &left, const std::string &right)