moveit2
The MoveIt Motion Planning Framework for ROS 2.
configuration_files_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 // Qt
38 #include <QAction>
39 #include <QApplication>
40 #include <QLabel>
41 #include <QList>
42 #include <QListWidget>
43 #include <QMessageBox>
44 #include <QProgressBar>
45 #include <QPushButton>
46 #include <QPushButton>
47 #include <QRegExp>
48 #include <QSplitter>
49 #include <QVBoxLayout>
50 
52 
53 // Boost
54 #include <boost/algorithm/string.hpp> // string trim
55 // Read write files
56 #include <iostream> // For writing yaml and launch files
57 #include <fstream>
58 
59 namespace moveit_setup
60 {
61 namespace core
62 {
63 // ******************************************************************************************
64 // Outer User Interface for MoveIt Configuration Assistant
65 // ******************************************************************************************
67 {
68  has_generated_pkg_ = false;
69 
70  // Basic widget container
71  QVBoxLayout* layout = new QVBoxLayout();
72 
73  // Top Header Area ------------------------------------------------
74 
75  auto header =
76  new HeaderWidget("Generate Configuration Files",
77  "Create or update the configuration files package needed to run your robot with MoveIt. Uncheck "
78  "files to disable them from being generated - this is useful if you have made custom changes to "
79  "them. Files in orange have been automatically detected as changed.",
80  this);
81  layout->addWidget(header);
82 
83  // Path Widget ----------------------------------------------------
84 
85  // Stack Path Dialog
86  stack_path_ = new LoadPathWidget("Configuration Package Save Path",
87  "Specify the desired directory for the MoveIt configuration package to be "
88  "generated. Overwriting an existing configuration package directory is acceptable. "
89  "Example: <i>/u/robot/ros/panda_moveit_config</i>",
90  this, true); // is directory
91  layout->addWidget(stack_path_);
92  connect(stack_path_, SIGNAL(pathChanged(QString)), this, SLOT(onPackagePathChanged(QString)));
93 
94  // Generated Files List -------------------------------------------
95  QLabel* generated_list = new QLabel("Check files you want to be generated:", this);
96  layout->addWidget(generated_list);
97 
98  QSplitter* splitter = new QSplitter(Qt::Horizontal, this);
99  splitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
100 
101  // List Box
102  action_list_ = new QListWidget(this);
103  action_list_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
104  action_list_->setSelectionMode(QAbstractItemView::ExtendedSelection);
105  connect(action_list_, SIGNAL(currentRowChanged(int)), this, SLOT(changeActionDesc(int)));
106  // Allow checking / unchecking of multiple items
107  action_list_->setContextMenuPolicy(Qt::ActionsContextMenu);
108  QAction* action = new QAction("Check all selected files", this);
109  connect(action, &QAction::triggered, [this]() { setCheckSelected(true); });
110  action_list_->addAction(action);
111  action = new QAction("Uncheck all selected files", this);
112  connect(action, &QAction::triggered, [this]() { setCheckSelected(false); });
113  action_list_->addAction(action);
114 
115  // Description
116  action_label_ = new QLabel(this);
117  action_label_->setFrameShape(QFrame::StyledPanel);
118  action_label_->setFrameShadow(QFrame::Raised);
119  action_label_->setLineWidth(1);
120  action_label_->setMidLineWidth(0);
121  action_label_->setWordWrap(true);
122  action_label_->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
123  action_label_->setMinimumWidth(100);
124  action_label_->setAlignment(Qt::AlignTop);
125  action_label_->setOpenExternalLinks(true); // open with web browser
126 
127  // Add to splitter
128  splitter->addWidget(action_list_);
129  splitter->addWidget(action_label_);
130 
131  // Add Layout
132  layout->addWidget(splitter);
133 
134  // Progress bar and generate buttons ---------------------------------------------------
135  QHBoxLayout* hlayout1 = new QHBoxLayout();
136 
137  // Progress Bar
138  progress_bar_ = new QProgressBar(this);
139  progress_bar_->setMaximum(100);
140  progress_bar_->setMinimum(0);
141  hlayout1->addWidget(progress_bar_);
142  // hlayout1->setContentsMargins( 20, 30, 20, 30 );
143 
144  // Generate Package Button
145  btn_save_ = new QPushButton("&Generate Package", this);
146  // btn_save_->setMinimumWidth(180);
147  btn_save_->setMinimumHeight(40);
148  connect(btn_save_, SIGNAL(clicked()), this, SLOT(savePackage()));
149  hlayout1->addWidget(btn_save_);
150 
151  // Add Layout
152  layout->addLayout(hlayout1);
153 
154  // Bottom row --------------------------------------------------
155 
156  QHBoxLayout* hlayout3 = new QHBoxLayout();
157 
158  // Success label
159  success_label_ = new QLabel(this);
160  QFont success_label_font(QFont().defaultFamily(), 12, QFont::Bold);
161  success_label_->setFont(success_label_font);
162  success_label_->hide(); // only show once the files have been generated
163  success_label_->setText("Configuration package generated successfully!");
164  hlayout3->addWidget(success_label_);
165  hlayout3->setAlignment(success_label_, Qt::AlignRight);
166 
167  // Exit button
168  QPushButton* btn_exit = new QPushButton("E&xit Setup Assistant", this);
169  btn_exit->setMinimumWidth(180);
170  connect(btn_exit, SIGNAL(clicked()), this, SLOT(exitSetupAssistant()));
171  hlayout3->addWidget(btn_exit);
172  hlayout3->setAlignment(btn_exit, Qt::AlignRight);
173 
174  layout->addLayout(hlayout3);
175 
176  // Finish Layout --------------------------------------------------
177  this->setLayout(layout);
178 }
179 
180 void ConfigurationFilesWidget::setCheckSelected(bool checked)
181 {
182  for (const QModelIndex& row : action_list_->selectionModel()->selectedRows())
183  action_list_->model()->setData(row, checked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole);
184 }
185 
186 void ConfigurationFilesWidget::onPackagePathChanged(const QString& path)
187 {
188  std::filesystem::path package_path = path.toStdString();
189 
190  if (package_path == setup_step_.getPackagePath())
191  {
192  return;
193  }
194  setup_step_.setPackagePath(package_path);
195  setup_step_.setPackageName(package_path.filename().string());
196 
197  focusGiven();
198 }
199 
200 // ******************************************************************************************
201 // Verify with user if certain screens have not been completed
202 // ******************************************************************************************
203 bool ConfigurationFilesWidget::checkDependencies()
204 {
205  std::vector<std::string> dependencies = setup_step_.getIncompleteWarnings();
206  bool required_actions = false;
207 
208  // Note that MSA 1.0 required that you have valid author information before proceedings, and if not, would
209  // set required_actions to true here. We ignore this for now.
210 
211  // Display all accumumlated errors:
212  if (!dependencies.empty())
213  {
214  // Create a dependency message
215  QString dep_message;
216  if (!required_actions)
217  {
218  dep_message = "Some setup steps have not been completed. None of the steps are required, but here is a reminder "
219  "of what was not filled in, just in case something was forgotten:<br /><ul>";
220  }
221  else
222  {
223  dep_message = "Some setup steps have not been completed. Please fix the required steps (printed in bold), "
224  "otherwise the setup cannot be completed:<br /><ul>";
225  }
226 
227  for (const auto& dependency : dependencies)
228  {
229  dep_message.append("<li>").append(QString::fromStdString(dependency)).append("</li>");
230  }
231 
232  if (!required_actions)
233  {
234  dep_message.append("</ul><br/>Press Ok to continue generating files.");
235  if (QMessageBox::question(this, "Incomplete MoveIt Setup Assistant Steps", dep_message,
236  QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel)
237  {
238  return false; // abort
239  }
240  }
241  else
242  {
243  QMessageBox::warning(this, "Incomplete MoveIt Setup Assistant Steps", dep_message);
244  return false;
245  }
246  }
247 
248  return true;
249 }
250 
251 // ******************************************************************************************
252 // A function for showing progress and user feedback about what happened
253 // ******************************************************************************************
254 void ConfigurationFilesWidget::updateProgress()
255 {
256  action_num_++;
257 
258  // Calc percentage
259  progress_bar_->setValue(double(action_num_) / setup_step_.getNumFiles() * 100);
260 
261  // allow the progress bar to be shown
262  QApplication::processEvents();
263 }
264 
265 // ******************************************************************************************
266 // Display the selected action in the desc box
267 // ******************************************************************************************
268 void ConfigurationFilesWidget::changeActionDesc(int id)
269 {
270  // Only allow event if list is not empty
271  if (id >= 0)
272  {
273  // Show the selected text
274  action_label_->setText(action_desc_.at(id));
275  }
276 }
277 
278 // ******************************************************************************************
279 // Disable or enable item in gen_files_ array
280 // ******************************************************************************************
281 void ConfigurationFilesWidget::changeCheckedState(QListWidgetItem* item)
282 {
283  std::size_t index = item->data(Qt::UserRole).toUInt();
284 
285  auto gen_file = setup_step_.getGeneratedFiles()[index];
286 
287  bool generate = (item->checkState() == Qt::Checked);
288 
289  if (!generate && gen_file->hasChanges())
290  {
291  QMessageBox::warning(this, "Package Generation",
292  "You should generate this file to ensure your changes will take "
293  "effect.");
294  }
295 
296  // Enable/disable file
297  setup_step_.setShouldGenerate(gen_file->getRelativePath(), generate);
298 }
299 
300 // ******************************************************************************************
301 // Called when setup assistant navigation switches to this screen
302 // ******************************************************************************************
304 {
305  // Pass the package path from start screen to configuration files screen
306  stack_path_->setPath(setup_step_.getPackagePath());
307 
308  setup_step_.loadFiles();
309 
310  // disable reaction to checkbox changes
311  disconnect(action_list_, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(changeCheckedState(QListWidgetItem*)));
312 
313  // Show files in GUI
314  showGenFiles();
315 
316  // react to manual changes only (not programmatic ones)
317  connect(action_list_, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(changeCheckedState(QListWidgetItem*)));
318 
319  // Allow list box to populate
320  QApplication::processEvents();
321 
322  // Which files have been modified outside the Setup Assistant?
323  if (setup_step_.hasModifiedFiles())
324  {
325  // Some were found to be modified
326  QString msg("Some files have been modified outside of the Setup Assistant (according to timestamp). "
327  "The Setup Assistant will not overwrite these changes by default because often changing configuration "
328  "files manually is necessary, "
329  "but we recommend you check the list and enable the checkbox next to files you would like to "
330  "overwrite. ");
331  if (setup_step_.hasConflictingFiles())
332  msg += "<br/><font color='red'>Attention:</font> Some files (<font color='red'>marked red</font>) are changed "
333  "both, externally and in Setup Assistant.";
334  QMessageBox::information(this, "Files Modified", msg);
335  }
336 }
337 
338 // ******************************************************************************************
339 // Show the list of files to be generated
340 // ******************************************************************************************
341 void ConfigurationFilesWidget::showGenFiles()
342 {
343  action_list_->clear();
344 
345  auto gen_files = setup_step_.getGeneratedFiles();
346 
347  // Display this list in the GUI
348  for (std::size_t i = 0; i < gen_files.size(); ++i)
349  {
350  auto gen_file = gen_files[i];
351 
352  // Create a formatted row
353  QListWidgetItem* item = new QListWidgetItem(QString(gen_file->getRelativePath().c_str()), action_list_, 0);
354 
355  // Checkbox
356  item->setCheckState(setup_step_.shouldGenerate(gen_file) ? Qt::Checked : Qt::Unchecked);
357 
358  auto status = gen_file->getStatus();
359  if (status == FileStatus::CONFLICTED)
360  {
361  item->setForeground(QBrush(QColor(255, 0, 0)));
362  }
363  else if (status == FileStatus::EXTERNALLY_MODIFIED)
364  {
365  item->setForeground(QBrush(QColor(255, 135, 0)));
366  }
367 
368  // Link the gen_files_ index to this item
369  item->setData(Qt::UserRole, QVariant(static_cast<qulonglong>(i)));
370 
371  // Add actions to list
372  action_list_->addItem(item);
373  action_desc_.append(QString(gen_file->getDescription().c_str()));
374  }
375 
376  // Select the first item in the list so that a description is visible
377  action_list_->setCurrentRow(0);
378 }
379 
380 // ******************************************************************************************
381 // Save configuration click event
382 // ******************************************************************************************
383 void ConfigurationFilesWidget::savePackage()
384 {
385  // Feedback
386  success_label_->hide();
387 
388  // Reset the progress bar counter and GUI stuff
389  action_num_ = 0;
390  progress_bar_->setValue(0);
391 
392  if (!generatePackage())
393  {
394  RCLCPP_ERROR_STREAM(setup_step_.getLogger(), "Failed to generate entire configuration package");
395  return;
396  }
397 
398  // Alert user it completed successfully --------------------------------------------------
399  progress_bar_->setValue(100);
400  success_label_->show();
401  has_generated_pkg_ = true;
402 }
403 
404 // ******************************************************************************************
405 // Save package using default path
406 // ******************************************************************************************
407 bool ConfigurationFilesWidget::generatePackage()
408 {
409  // Get path name
410  std::string package_path_s = stack_path_->getPath();
411  // Trim whitespace from user input
412  boost::trim(package_path_s);
413 
414  // Check that a valid stack package name has been given
415  if (package_path_s.empty())
416  {
417  QMessageBox::warning(this, "Error Generating",
418  "No package path provided. Please choose a directory location to "
419  "generate the MoveIt configuration files.");
420  return false;
421  }
422 
423  // Check setup assist deps
424  if (!checkDependencies())
425  return false; // canceled
426 
427  // Check that all groups have components
428  if (!noGroupsEmpty())
429  return false; // not ready
430 
431  // Make sure old package is correct package type and verify over write
432  if (setup_step_.isExistingConfig())
433  {
434  // Check if the old package is a setup assistant package. If it is not, quit
435  if (!setup_step_.hasSetupAssistantFile())
436  {
437  QMessageBox::warning(
438  this, "Incorrect Folder/Package",
439  QString("The chosen package location already exists but was not previously created using this MoveIt Setup "
440  "Assistant. "
441  "If this is a mistake, add the missing file: ")
442  .append(SETUP_ASSISTANT_FILE.c_str()));
443  return false;
444  }
445 
446  // Confirm overwrite
447  if (QMessageBox::question(this, "Confirm Package Update",
448  QString("Are you sure you want to overwrite this existing package with updated "
449  "configurations?<br /><i>")
450  .append(package_path_s.c_str())
451  .append("</i>"),
452  QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel)
453  {
454  return false; // abort
455  }
456  }
457 
458  setup_step_.setGenerationTime();
459 
460  // Begin to create files and folders ----------------------------------------------------------------------
461  std::filesystem::path absolute_path;
462 
463  for (auto& gen_file : setup_step_.getGeneratedFiles())
464  {
465  // Check if we should skip this file
466  if (!setup_step_.shouldGenerate(gen_file))
467  {
468  continue;
469  }
470  absolute_path = gen_file->getPath();
471 
472  // Create the absolute path
473  RCLCPP_DEBUG_STREAM(setup_step_.getLogger(), "Creating file " << absolute_path.string());
474 
475  // Run the generate function
476  if (!gen_file->write())
477  {
478  // Error occurred
479  QMessageBox::critical(this, "Error Generating File",
480  QString("Failed to generate folder or file: '")
481  .append(gen_file->getRelativePath().c_str())
482  .append("' at location:\n")
483  .append(absolute_path.c_str()));
484  return false;
485  }
486  updateProgress(); // Increment and update GUI
487  }
488 
489  return true;
490 }
491 
492 // ******************************************************************************************
493 // Quit the program because we are done
494 // ******************************************************************************************
495 void ConfigurationFilesWidget::exitSetupAssistant()
496 {
497  if (has_generated_pkg_ || QMessageBox::question(this, "Exit Setup Assistant",
498  QString("Are you sure you want to exit the MoveIt Setup Assistant?"),
499  QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok)
500  {
501  QApplication::quit();
502  }
503 }
504 
505 // ******************************************************************************************
506 // Check that no group is empty (without links/joints/etc)
507 // ******************************************************************************************
508 bool ConfigurationFilesWidget::noGroupsEmpty()
509 {
510  // Loop through all groups
511  std::string invalid_group = setup_step_.getInvalidGroupName();
512  if (!invalid_group.empty())
513  {
514  // This group has no contents, bad
515  QMessageBox::warning(
516  this, "Empty Group",
517  QString("The planning group '")
518  .append(invalid_group.c_str())
519  .append("' is empty and has no subcomponents associated with it (joints/links/chains/subgroups). You must "
520  "edit or remove this planning group before this configuration package can be saved."));
521  return false;
522  }
523 
524  return true; // good
525 }
526 } // namespace core
527 } // namespace moveit_setup
528 
529 #include <pluginlib/class_list_macros.hpp> // NOLINT
PLUGINLIB_EXPORT_CLASS(cached_ik_kinematics_plugin::CachedIKKinematicsPlugin< kdl_kinematics_plugin::KDLKinematicsPlugin >, kinematics::KinematicsBase)
void setPath(const QString &path)
Set the path with QString.
std::string getPath() const
Returns the file path in std::string format.
The GUI code for one SetupStep.
const rclcpp::Logger & getLogger() const
Makes a namespaced logger for this step available to the widget.
Definition: setup_step.hpp:91
void focusGiven() override
Received when this widget is chosen from the navigation menu.
void loadFiles()
Populate the 'Files to be generated' list.
void setShouldGenerate(const std::string &rel_path, bool should_generate)
const std::filesystem::path & getPackagePath()
std::vector< std::string > getIncompleteWarnings() const
void setPackagePath(const std::filesystem::path &package_path)
bool shouldGenerate(const GeneratedFilePtr &file) const
void setPackageName(const std::string &package_name)
const std::vector< GeneratedFilePtr > getGeneratedFiles() const
std::string append(const std::string &left, const std::string &right)