52 #include <boost/lexical_cast.hpp>
55 #include <QApplication>
57 #include <QHBoxLayout>
60 #include <QMessageBox>
61 #include <QPushButton>
62 #include <QStackedWidget>
63 #include <QTableWidget>
64 #include <QTreeWidget>
65 #include <QTreeWidgetItem>
66 #include <QVBoxLayout>
73 static const std::string VIS_TOPIC_NAME =
"planning_components_visualization";
81 QVBoxLayout* layout =
new QVBoxLayout();
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.",
92 layout->addWidget(header);
97 groups_tree_widget_ = createContentsWidget();
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>)));
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>)));
115 connect(chain_widget_, SIGNAL(cancelEditing()),
this, SLOT(cancelEditing()));
116 connect(chain_widget_, SIGNAL(doneEditing()),
this, SLOT(saveChainScreen()));
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>)));
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()));
136 stacked_widget_ =
new QStackedWidget(
this);
137 stacked_widget_->addWidget(groups_tree_widget_);
138 stacked_widget_->addWidget(joints_widget_);
139 stacked_widget_->addWidget(links_widget_);
140 stacked_widget_->addWidget(chain_widget_);
141 stacked_widget_->addWidget(subgroups_widget_);
142 stacked_widget_->addWidget(group_edit_widget_);
148 layout->addWidget(stacked_widget_);
152 QApplication::processEvents();
158 QWidget* PlanningGroupsWidget::createContentsWidget()
161 QWidget* content_widget =
new QWidget(
this);
164 QVBoxLayout* layout =
new QVBoxLayout(
this);
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_);
176 QHBoxLayout* controls_layout =
new QHBoxLayout();
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);
185 controls_layout->addItem(
new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Minimum));
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);
196 btn_edit_ =
new QPushButton(
"&Edit Selected",
this);
197 btn_edit_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
198 btn_edit_->setMaximumWidth(300);
200 connect(btn_edit_, SIGNAL(clicked()),
this, SLOT(editSelected()));
201 controls_layout->addWidget(btn_edit_);
202 controls_layout->setAlignment(btn_edit_, Qt::AlignRight);
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);
213 layout->addLayout(controls_layout);
216 content_widget->setLayout(layout);
218 return content_widget;
224 void PlanningGroupsWidget::loadGroupsTree()
227 groups_tree_->setUpdatesEnabled(
false);
228 groups_tree_->setDisabled(
true);
229 groups_tree_->clear();
232 std::vector<srdf::Model::Group>& groups = setup_step_.
getContainer();
233 for (srdf::Model::Group&
group : groups)
235 loadGroupsTreeRecursive(
group,
nullptr);
239 groups_tree_->setUpdatesEnabled(
true);
240 groups_tree_->setDisabled(
false);
260 void PlanningGroupsWidget::loadGroupsTreeRecursive(srdf::Model::Group& group_it, QTreeWidgetItem* parent)
263 const QFont top_level_font(QFont().defaultFamily(), 11, QFont::Bold);
264 const QFont type_font(QFont().defaultFamily(), 11, QFont::Normal, QFont::StyleItalic);
266 QTreeWidgetItem*
group;
269 if (parent ==
nullptr)
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);
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);
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);
294 for (std::vector<std::string>::const_iterator joint_it = group_it.joints_.begin(); joint_it != group_it.joints_.end();
297 QTreeWidgetItem* j =
new QTreeWidgetItem(joints);
298 j->setData(0, Qt::UserRole, QVariant::fromValue(PlanGroupType(&group_it,
JOINT)));
299 std::string joint_name;
302 std::string joint_type = setup_step_.
getJointType(*joint_it);
303 if (!joint_type.empty())
305 joint_name = *joint_it +
" - " + joint_type;
309 joint_name = *joint_it;
313 j->setText(0, joint_name.c_str());
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);
325 for (std::vector<std::string>::const_iterator joint_it = group_it.links_.begin(); joint_it != group_it.links_.end();
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());
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);
342 static bool warn_once =
true;
343 if (group_it.chains_.size() > 1 && warn_once)
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 "
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)
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()));
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);
370 for (std::vector<std::string>::iterator subgroup_it = group_it.subgroups_.begin();
371 subgroup_it != group_it.subgroups_.end(); ++subgroup_it)
374 srdf::Model::Group* searched_group;
378 searched_group = setup_step_.
find(*subgroup_it);
380 catch (
const std::runtime_error& e)
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"));
392 loadGroupsTreeRecursive(*searched_group, subgroups);
399 void PlanningGroupsWidget::previewSelected()
401 QTreeWidgetItem* item = groups_tree_->currentItem();
408 PlanGroupType plan_group = item->data(0, Qt::UserRole).value<PlanGroupType>();
420 void PlanningGroupsWidget::editSelected()
422 QTreeWidgetItem* item = groups_tree_->currentItem();
428 adding_new_group_ =
false;
431 PlanGroupType plan_group = item->data(0, Qt::UserRole).value<PlanGroupType>();
433 switch (plan_group.type_)
436 loadJointsScreen(plan_group.group_);
439 loadLinksScreen(plan_group.group_);
442 loadChainScreen(plan_group.group_);
445 loadSubgroupsScreen(plan_group.group_);
448 loadGroupScreen(plan_group.group_);
451 QMessageBox::critical(
this,
"Error Loading",
"An internal error has occurred while loading.");
461 void PlanningGroupsWidget::loadJointsScreen(srdf::Model::Group* this_group)
464 const std::vector<std::string>& joints = setup_step_.
getJointNames();
468 QMessageBox::critical(
this,
"Error Loading",
"No joints found for robot model");
479 joints_widget_->
title_->setText(
480 QString(
"Edit '").
append(QString::fromUtf8(this_group->name_.c_str())).append(
"' Joint Collection"));
483 current_edit_group_ = this_group->name_;
489 void PlanningGroupsWidget::loadLinksScreen(srdf::Model::Group* this_group)
492 const std::vector<std::string>& links = setup_step_.
getLinkNames();
496 QMessageBox::critical(
this,
"Error Loading",
"No links found for robot model");
507 links_widget_->
title_->setText(
508 QString(
"Edit '").
append(QString::fromUtf8(this_group->name_.c_str())).append(
"' Link Collection"));
511 current_edit_group_ = this_group->name_;
517 void PlanningGroupsWidget::loadChainScreen(srdf::Model::Group* this_group)
522 if (this_group->chains_.size() > 1)
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 "
532 if (!this_group->chains_.empty())
534 chain_widget_->
setSelected(this_group->chains_[0].first, this_group->chains_[0].second);
538 chain_widget_->
title_->setText(
539 QString(
"Edit '").
append(QString::fromUtf8(this_group->name_.c_str())).append(
"' Kinematic Chain"));
542 current_edit_group_ = this_group->name_;
548 void PlanningGroupsWidget::loadSubgroupsScreen(srdf::Model::Group* this_group)
551 std::vector<std::string> subgroups;
554 for (
const std::string& group_name : setup_step_.
getGroupNames())
556 if (group_name != this_group->name_)
559 subgroups.push_back(group_name);
567 subgroups_widget_->
setSelected(this_group->subgroups_);
570 subgroups_widget_->
title_->setText(
571 QString(
"Edit '").
append(QString::fromUtf8(this_group->name_.c_str())).append(
"' Subgroups"));
574 current_edit_group_ = this_group->name_;
580 void PlanningGroupsWidget::loadGroupScreen(srdf::Model::Group* this_group)
585 if (this_group ==
nullptr)
587 current_edit_group_.clear();
588 group_edit_widget_->
title_->setText(
"Create New Planning Group");
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(
"'"));
610 void PlanningGroupsWidget::deleteGroup()
612 std::string group_to_delete = current_edit_group_;
613 if (group_to_delete.empty())
615 QTreeWidgetItem* item = groups_tree_->currentItem();
620 PlanGroupType plan_group = item->data(0, Qt::UserRole).value<PlanGroupType>();
621 if (plan_group.group_)
622 group_to_delete = plan_group.group_->name_;
625 current_edit_group_.clear();
626 if (group_to_delete.empty())
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 "
635 QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel)
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)
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)
675 void PlanningGroupsWidget::addGroup()
677 adding_new_group_ =
true;
680 loadGroupScreen(
nullptr);
689 void PlanningGroupsWidget::saveJointsScreen()
703 void PlanningGroupsWidget::saveLinksScreen()
717 void PlanningGroupsWidget::saveChainScreen()
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();
725 setup_step_.
setChain(current_edit_group_, base, tip);
727 catch (
const std::runtime_error& e)
729 QMessageBox::warning(
this,
"Error Saving", e.what());
743 void PlanningGroupsWidget::saveSubgroupsScreen()
749 catch (
const std::runtime_error& e)
751 QMessageBox::warning(
this,
"Error Saving", e.what());
765 bool PlanningGroupsWidget::saveGroupScreen()
767 const std::string& group_name = group_edit_widget_->
group_name_field_->text().trimmed().toStdString();
769 GroupMetaData meta_data;
773 if (meta_data.default_planner_ ==
"None")
775 meta_data.default_planner_ =
"";
779 if (group_name.empty())
781 QMessageBox::warning(
this,
"Error Saving",
"A name must be given for the group!");
789 meta_data.kinematics_solver_search_resolution_ = boost::lexical_cast<double>(kinematics_resolution);
791 catch (boost::bad_lexical_cast&)
793 QMessageBox::warning(
this,
"Error Saving",
"Unable to convert kinematics resolution to a double number.");
801 meta_data.kinematics_solver_timeout_ = boost::lexical_cast<double>(kinematics_timeout);
803 catch (boost::bad_lexical_cast&)
805 QMessageBox::warning(
this,
"Error Saving",
"Unable to convert kinematics solver timeout to a double number.");
810 if (meta_data.kinematics_solver_search_resolution_ <= 0)
812 QMessageBox::warning(
this,
"Error Saving",
"Kinematics solver search resolution must be greater than 0.");
815 if (meta_data.kinematics_solver_timeout_ <= 0)
817 QMessageBox::warning(
this,
"Error Saving",
"Kinematics solver search timeout must be greater than 0.");
823 adding_new_group_ = current_edit_group_.empty();
824 setup_step_.
get(group_name, current_edit_group_);
826 catch (
const std::runtime_error& e)
828 QMessageBox::warning(
this,
"Error Saving", e.what());
838 current_edit_group_ = group_name;
846 void PlanningGroupsWidget::saveGroupScreenEdit()
849 if (!saveGroupScreen())
859 void PlanningGroupsWidget::saveGroupScreenJoints()
862 if (!saveGroupScreen())
866 loadJointsScreen(setup_step_.
find(current_edit_group_));
867 return_screen_ =
GROUP;
876 void PlanningGroupsWidget::saveGroupScreenLinks()
879 if (!saveGroupScreen())
883 loadLinksScreen(setup_step_.
find(current_edit_group_));
884 return_screen_ =
GROUP;
893 void PlanningGroupsWidget::saveGroupScreenChain()
896 if (!saveGroupScreen())
900 loadChainScreen(setup_step_.
find(current_edit_group_));
901 return_screen_ =
GROUP;
910 void PlanningGroupsWidget::saveGroupScreenSubgroups()
913 if (!saveGroupScreen())
917 loadSubgroupsScreen(setup_step_.
find(current_edit_group_));
918 return_screen_ =
GROUP;
927 void PlanningGroupsWidget::cancelEditing()
935 if (!current_edit_group_.empty() && adding_new_group_)
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())
942 current_edit_group_.clear();
967 void PlanningGroupsWidget::alterTree(
const QString& link)
969 if (link.contains(
"expand"))
970 groups_tree_->expandAll();
972 groups_tree_->collapseAll();
978 void PlanningGroupsWidget::showMainScreen()
980 stacked_widget_->setCurrentIndex(0);
991 stacked_widget_->setCurrentIndex(index);
1000 void PlanningGroupsWidget::previewSelectedLink(
const std::vector<std::string>& links)
1005 for (
const std::string& link : links)
1020 void PlanningGroupsWidget::previewSelectedJoints(
const std::vector<std::string>& joints)
1025 for (
const std::string& joint : joints)
1041 void PlanningGroupsWidget::previewSelectedSubgroup(
const std::vector<std::string>& groups)
1046 for (
const std::string&
group : groups)
1066 #include <pluginlib/class_list_macros.hpp>
PLUGINLIB_EXPORT_CLASS(cached_ik_kinematics_plugin::CachedIKKinematicsPlugin< kdl_kinematics_plugin::KDLKinematicsPlugin >, kinematics::KinematicsBase)
void highlightGroup(const std::string &group_name)
void highlightLink(const std::string &link_name, const QColor &color)
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
LinkNameTree getLinkNameTree() 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.
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 ...
std::string append(const std::string &left, const std::string &right)