moveit2
The MoveIt Motion Planning Framework for ROS 2.
Loading...
Searching...
No Matches
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 <QRegularExpression>
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
59namespace moveit_setup
60{
61namespace 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 setLayout(layout);
178}
179
180void 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
186void 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// ******************************************************************************************
203bool 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// ******************************************************************************************
254void 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// ******************************************************************************************
268void 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// ******************************************************************************************
281void 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().string(), 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().string());
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 {
333 msg += "<br/><font color='red'>Attention:</font> Some files (<font color='red'>marked red</font>) are changed "
334 "both, externally and in Setup Assistant.";
335 }
336 QMessageBox::information(this, "Files Modified", msg);
337 }
338}
339
340// ******************************************************************************************
341// Show the list of files to be generated
342// ******************************************************************************************
343void ConfigurationFilesWidget::showGenFiles()
344{
345 action_list_->clear();
346
347 auto gen_files = setup_step_.getGeneratedFiles();
348
349 // Display this list in the GUI
350 for (std::size_t i = 0; i < gen_files.size(); ++i)
351 {
352 auto gen_file = gen_files[i];
353
354 // Create a formatted row
355 QListWidgetItem* item = new QListWidgetItem(QString(gen_file->getRelativePath().string().c_str()), action_list_, 0);
356
357 // Checkbox
358 item->setCheckState(setup_step_.shouldGenerate(gen_file) ? Qt::Checked : Qt::Unchecked);
359
360 auto status = gen_file->getStatus();
361 if (status == FileStatus::CONFLICTED)
362 {
363 item->setForeground(QBrush(QColor(255, 0, 0)));
364 }
365 else if (status == FileStatus::EXTERNALLY_MODIFIED)
366 {
367 item->setForeground(QBrush(QColor(255, 135, 0)));
368 }
369
370 // Link the gen_files_ index to this item
371 item->setData(Qt::UserRole, QVariant(static_cast<qulonglong>(i)));
372
373 // Add actions to list
374 action_list_->addItem(item);
375 action_desc_.append(QString(gen_file->getDescription().c_str()));
376 }
377
378 // Select the first item in the list so that a description is visible
379 action_list_->setCurrentRow(0);
380}
381
382// ******************************************************************************************
383// Save configuration click event
384// ******************************************************************************************
385void ConfigurationFilesWidget::savePackage()
386{
387 // Feedback
388 success_label_->hide();
389
390 // Reset the progress bar counter and GUI stuff
391 action_num_ = 0;
392 progress_bar_->setValue(0);
393
394 if (!generatePackage())
395 {
396 RCLCPP_ERROR_STREAM(setup_step_.getLogger(), "Failed to generate entire configuration package");
397 return;
398 }
399
400 // Alert user it completed successfully --------------------------------------------------
401 progress_bar_->setValue(100);
402 success_label_->show();
403 has_generated_pkg_ = true;
404}
405
406// ******************************************************************************************
407// Save package using default path
408// ******************************************************************************************
409bool ConfigurationFilesWidget::generatePackage()
410{
411 // Get path name
412 std::string package_path_s = stack_path_->getPath();
413 // Trim whitespace from user input
414 boost::trim(package_path_s);
415
416 // Check that a valid stack package name has been given
417 if (package_path_s.empty())
418 {
419 QMessageBox::warning(this, "Error Generating",
420 "No package path provided. Please choose a directory location to "
421 "generate the MoveIt configuration files.");
422 return false;
423 }
424
425 // Check setup assist deps
426 if (!checkDependencies())
427 return false; // canceled
428
429 // Check that all groups have components
430 if (!noGroupsEmpty())
431 return false; // not ready
432
433 // Make sure old package is correct package type and verify over write
434 if (setup_step_.isExistingConfig())
435 {
436 // Check if the old package is a setup assistant package. If it is not, quit
437 if (!setup_step_.hasSetupAssistantFile())
438 {
439 QMessageBox::warning(
440 this, "Incorrect Folder/Package",
441 QString("The chosen package location already exists but was not previously created using this MoveIt Setup "
442 "Assistant. "
443 "If this is a mistake, add the missing file: ")
444 .append(SETUP_ASSISTANT_FILE.c_str()));
445 return false;
446 }
447
448 // Confirm overwrite
449 if (QMessageBox::question(this, "Confirm Package Update",
450 QString("Are you sure you want to overwrite this existing package with updated "
451 "configurations?<br /><i>")
452 .append(package_path_s.c_str())
453 .append("</i>"),
454 QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel)
455 {
456 return false; // abort
457 }
458 }
459
460 setup_step_.setGenerationTime();
461
462 // Begin to create files and folders ----------------------------------------------------------------------
463 std::filesystem::path absolute_path;
464
465 for (auto& gen_file : setup_step_.getGeneratedFiles())
466 {
467 // Check if we should skip this file
468 if (!setup_step_.shouldGenerate(gen_file))
469 {
470 continue;
471 }
472 absolute_path = gen_file->getPath();
473
474 // Create the absolute path
475 RCLCPP_DEBUG_STREAM(setup_step_.getLogger(), "Creating file " << absolute_path.string());
476
477 // Run the generate function
478 if (!gen_file->write())
479 {
480 // Error occurred
481 QMessageBox::critical(this, "Error Generating File",
482 QString("Failed to generate folder or file: '")
483 .append(gen_file->getRelativePath().string().c_str())
484 .append("' at location:\n")
485 .append(absolute_path.c_str()));
486 return false;
487 }
488 updateProgress(); // Increment and update GUI
489 }
490
491 return true;
492}
493
494// ******************************************************************************************
495// Quit the program because we are done
496// ******************************************************************************************
497void ConfigurationFilesWidget::exitSetupAssistant()
498{
499 if (has_generated_pkg_ || QMessageBox::question(this, "Exit Setup Assistant",
500 QString("Are you sure you want to exit the MoveIt Setup Assistant?"),
501 QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok)
502 {
503 QApplication::quit();
504 }
505}
506
507// ******************************************************************************************
508// Check that no group is empty (without links/joints/etc)
509// ******************************************************************************************
510bool ConfigurationFilesWidget::noGroupsEmpty()
511{
512 // Loop through all groups
513 std::string invalid_group = setup_step_.getInvalidGroupName();
514 if (!invalid_group.empty())
515 {
516 // This group has no contents, bad
517 QMessageBox::warning(
518 this, "Empty Group",
519 QString("The planning group '")
520 .append(invalid_group.c_str())
521 .append("' is empty and has no subcomponents associated with it (joints/links/chains/subgroups). You must "
522 "edit or remove this planning group before this configuration package can be saved."));
523 return false;
524 }
525
526 return true; // good
527}
528} // namespace core
529} // namespace moveit_setup
530
531#include <pluginlib/class_list_macros.hpp> // NOLINT
PLUGINLIB_EXPORT_CLASS(cached_ik_kinematics_plugin::CachedIKKinematicsPlugin< kdl_kinematics_plugin::KDLKinematicsPlugin >, kinematics::KinematicsBase)
The GUI code for one SetupStep.
void focusGiven() override
Received when this widget is chosen from the navigation menu.
const std::filesystem::path & getPackagePath()
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)