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 <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
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(), 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 {
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().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().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)
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.
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)