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 #include "header_widget.h"
53 
54 // Boost
55 #include <boost/algorithm/string.hpp> // for string find and replace in templates
56 #include <boost/filesystem/path.hpp> // for creating folders/files
57 #include <boost/filesystem/operations.hpp> // is_regular_file, is_directory, etc.
58 // Read write files
59 #include <iostream> // For writing yaml and launch files
60 #include <fstream>
61 
62 namespace moveit_setup_assistant
63 {
64 // Boost file system
65 namespace fs = boost::filesystem;
66 
67 static const std::string SETUP_ASSISTANT_FILE = ".setup_assistant";
68 static const std::string CONFIG_PATH = "config";
69 
70 // ******************************************************************************************
71 // Outer User Interface for MoveIt Configuration Assistant
72 // ******************************************************************************************
73 ConfigurationFilesWidget::ConfigurationFilesWidget(QWidget* parent, const MoveItConfigDataPtr& config_data)
74  : SetupScreenWidget(parent), config_data_(config_data), has_generated_pkg_(false)
75 {
76  // Basic widget container
77  QVBoxLayout* layout = new QVBoxLayout();
78 
79  // Top Header Area ------------------------------------------------
80 
81  HeaderWidget* header =
82  new HeaderWidget("Generate Configuration Files",
83  "Create or update the configuration files package needed to run your robot with MoveIt. Uncheck "
84  "files to disable them from being generated - this is useful if you have made custom changes to "
85  "them. Files in orange have been automatically detected as changed.",
86  this);
87  layout->addWidget(header);
88 
89  // Path Widget ----------------------------------------------------
90 
91  // Stack Path Dialog
92  stack_path_ = new LoadPathWidget("Configuration Package Save Path",
93  "Specify the desired directory for the MoveIt configuration package to be "
94  "generated. Overwriting an existing configuration package directory is acceptable. "
95  "Example: <i>/u/robot/ros/panda_moveit_config</i>",
96  this, true); // is directory
97  layout->addWidget(stack_path_);
98 
99  // Pass the package path from start screen to configuration files screen
100  stack_path_->setPath(config_data_->config_pkg_path_);
101 
102  // Generated Files List -------------------------------------------
103  QLabel* generated_list = new QLabel("Check files you want to be generated:", this);
104  layout->addWidget(generated_list);
105 
106  QSplitter* splitter = new QSplitter(Qt::Horizontal, this);
107  splitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
108 
109  // List Box
110  action_list_ = new QListWidget(this);
111  action_list_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
112  action_list_->setSelectionMode(QAbstractItemView::ExtendedSelection);
113  connect(action_list_, SIGNAL(currentRowChanged(int)), this, SLOT(changeActionDesc(int)));
114  // Allow checking / unchecking of multiple items
115  action_list_->setContextMenuPolicy(Qt::ActionsContextMenu);
116  QAction* action = new QAction("Check all selected files", this);
117  connect(action, &QAction::triggered, [this]() { setCheckSelected(true); });
118  action_list_->addAction(action);
119  action = new QAction("Uncheck all selected files", this);
120  connect(action, &QAction::triggered, [this]() { setCheckSelected(false); });
121  action_list_->addAction(action);
122 
123  // Description
124  action_label_ = new QLabel(this);
125  action_label_->setFrameShape(QFrame::StyledPanel);
126  action_label_->setFrameShadow(QFrame::Raised);
127  action_label_->setLineWidth(1);
128  action_label_->setMidLineWidth(0);
129  action_label_->setWordWrap(true);
130  action_label_->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
131  action_label_->setMinimumWidth(100);
132  action_label_->setAlignment(Qt::AlignTop);
133  action_label_->setOpenExternalLinks(true); // open with web browser
134 
135  // Add to splitter
136  splitter->addWidget(action_list_);
137  splitter->addWidget(action_label_);
138 
139  // Add Layout
140  layout->addWidget(splitter);
141 
142  // Progress bar and generate buttons ---------------------------------------------------
143  QHBoxLayout* hlayout1 = new QHBoxLayout();
144 
145  // Progress Bar
146  progress_bar_ = new QProgressBar(this);
147  progress_bar_->setMaximum(100);
148  progress_bar_->setMinimum(0);
149  hlayout1->addWidget(progress_bar_);
150  // hlayout1->setContentsMargins( 20, 30, 20, 30 );
151 
152  // Generate Package Button
153  btn_save_ = new QPushButton("&Generate Package", this);
154  // btn_save_->setMinimumWidth(180);
155  btn_save_->setMinimumHeight(40);
156  connect(btn_save_, SIGNAL(clicked()), this, SLOT(savePackage()));
157  hlayout1->addWidget(btn_save_);
158 
159  // Add Layout
160  layout->addLayout(hlayout1);
161 
162  // Bottom row --------------------------------------------------
163 
164  QHBoxLayout* hlayout3 = new QHBoxLayout();
165 
166  // Success label
167  success_label_ = new QLabel(this);
168  QFont success_label_font(QFont().defaultFamily(), 12, QFont::Bold);
169  success_label_->setFont(success_label_font);
170  success_label_->hide(); // only show once the files have been generated
171  success_label_->setText("Configuration package generated successfully!");
172  hlayout3->addWidget(success_label_);
173  hlayout3->setAlignment(success_label_, Qt::AlignRight);
174 
175  // Exit button
176  QPushButton* btn_exit = new QPushButton("E&xit Setup Assistant", this);
177  btn_exit->setMinimumWidth(180);
178  connect(btn_exit, SIGNAL(clicked()), this, SLOT(exitSetupAssistant()));
179  hlayout3->addWidget(btn_exit);
180  hlayout3->setAlignment(btn_exit, Qt::AlignRight);
181 
182  layout->addLayout(hlayout3);
183 
184  // Finish Layout --------------------------------------------------
185  this->setLayout(layout);
186 }
187 
188 void ConfigurationFilesWidget::setCheckSelected(bool checked)
189 {
190  for (const QModelIndex& row : action_list_->selectionModel()->selectedRows())
191  action_list_->model()->setData(row, checked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole);
192 }
193 
194 // ******************************************************************************************
195 // Populate the 'Files to be generated' list
196 // ******************************************************************************************
197 bool ConfigurationFilesWidget::loadGenFiles()
198 {
199  GenerateFile file; // re-used
200  std::string template_path; // re-used
201  const std::string robot_name = config_data_->srdf_->robot_name_;
202 
203  gen_files_.clear(); // reset vector
204 
205  // Get template package location ----------------------------------------------------------------------
206  fs::path template_package_path = config_data_->setup_assistant_path_;
207  template_package_path /= "templates";
208  template_package_path /= "moveit_config_pkg_template";
209  config_data_->template_package_path_ = template_package_path.make_preferred().string();
210 
211  if (!fs::is_directory(config_data_->template_package_path_))
212  {
213  QMessageBox::critical(
214  this, "Error Generating",
215  QString("Unable to find package template directory: ").append(config_data_->template_package_path_.c_str()));
216  return false;
217  }
218 
219  // -------------------------------------------------------------------------------------------------------------------
220  // ROS PACKAGE FILES AND FOLDERS ----------------------------------------------------------------------------
221  // -------------------------------------------------------------------------------------------------------------------
222 
223  // package.xml --------------------------------------------------------------------------------------
224  // Note: we call the file package.xml.template so that it isn't automatically indexed by rosprofile
225  // in the scenario where we want to disabled the setup_assistant by renaming its root package.xml
226  file.file_name_ = "package.xml";
227  file.rel_path_ = file.file_name_;
228  template_path = config_data_->appendPaths(config_data_->template_package_path_, "package.xml.template");
229  file.description_ = "Defines a ROS package";
230  file.gen_func_ = [this, template_path](const std::string& output_path) {
231  return copyTemplate(template_path, output_path);
232  };
233  file.write_on_changes = MoveItConfigData::AUTHOR_INFO;
234  gen_files_.push_back(file);
235 
236  // CMakeLists.txt --------------------------------------------------------------------------------------
237  file.file_name_ = "CMakeLists.txt";
238  file.rel_path_ = file.file_name_;
239  template_path = config_data_->appendPaths(config_data_->template_package_path_, file.file_name_);
240  file.description_ = "CMake build system configuration file";
241  file.gen_func_ = [this, template_path](const std::string& output_path) {
242  return copyTemplate(template_path, output_path);
243  };
244  file.write_on_changes = 0;
245  gen_files_.push_back(file);
246 
247  // -------------------------------------------------------------------------------------------------------------------
248  // CONIG FILES -------------------------------------------------------------------------------------------------------
249  // -------------------------------------------------------------------------------------------------------------------
250 
251  // config/ --------------------------------------------------------------------------------------
252  file.file_name_ = "config/";
253  file.rel_path_ = file.file_name_;
254  file.description_ = "Folder containing all MoveIt configuration files for your robot. This folder is required and "
255  "cannot be disabled.";
256  file.gen_func_ = [this](const std::string& output_path) { return createFolder(output_path); };
257  file.write_on_changes = 0;
258  gen_files_.push_back(file);
259 
260  // robot.srdf ----------------------------------------------------------------------------------------------
261  file.file_name_ = config_data_->srdf_pkg_relative_path_.empty() ? config_data_->urdf_model_->getName() + ".srdf" :
262  config_data_->srdf_pkg_relative_path_;
263  file.rel_path_ = config_data_->srdf_pkg_relative_path_.empty() ?
264  config_data_->appendPaths(CONFIG_PATH, file.file_name_) :
265  config_data_->srdf_pkg_relative_path_;
266  file.description_ = "SRDF (<a href='http://www.ros.org/wiki/srdf'>Semantic Robot Description Format</a>) is a "
267  "representation of semantic information about robots. This format is intended to represent "
268  "information about the robot that is not in the URDF file, but it is useful for a variety of "
269  "applications. The intention is to include information that has a semantic aspect to it.";
270  file.gen_func_ = [&writer = *config_data_->srdf_](const std::string& output_path) {
271  return writer.writeSRDF(output_path);
272  };
273  file.write_on_changes = MoveItConfigData::SRDF;
274  gen_files_.push_back(file);
275  // special step required so the generated .setup_assistant yaml has this value
276  config_data_->srdf_pkg_relative_path_ = file.rel_path_;
277 
278  // gazebo_<ROBOT>.urdf ---------------------------------------------------------------------------------------
279  if (!config_data_->gazebo_urdf_string_.empty())
280  {
281  file.file_name_ = "gazebo_" + config_data_->urdf_model_->getName() + ".urdf";
282  file.rel_path_ = config_data_->appendPaths(CONFIG_PATH, file.file_name_);
283  file.description_ =
284  "This <a href='https://wiki.ros.org/urdf'>URDF</a> file comprises your original robot description "
285  "augmented with tags required for use with Gazebo, i.e. defining inertia and transmission properties. "
286  "Checkout the <a href='http://gazebosim.org/tutorials/?tut=ros_urdf'>URDF Gazebo documentation</a> "
287  "for more infos.";
288  file.gen_func_ = [this](const std::string& output_path) { return config_data_->outputGazeboURDFFile(output_path); };
289  file.write_on_changes = MoveItConfigData::SIMULATION;
290  gen_files_.push_back(file);
291  }
292 
293  // ompl_planning.yaml --------------------------------------------------------------------------------------
294  file.file_name_ = "ompl_planning.yaml";
295  file.rel_path_ = config_data_->appendPaths(CONFIG_PATH, file.file_name_);
296  file.description_ = "Configures the OMPL (<a href='http://ompl.kavrakilab.org/'>Open Motion Planning Library</a>) "
297  "planning plugin. For every planning group defined in the SRDF, a number of planning "
298  "configurations are specified (under planner_configs). Additionally, default settings for the "
299  "state space to plan in for a particular group can be specified, such as the collision checking "
300  "resolution. Each planning configuration specified for a group must be defined under the "
301  "planner_configs tag. While defining a planner configuration, the only mandatory parameter is "
302  "'type', which is the name of the motion planner to be used. Any other planner-specific "
303  "parameters can be defined but are optional.";
304  file.gen_func_ = [this](const std::string& output_path) { return config_data_->outputOMPLPlanningYAML(output_path); };
305  file.write_on_changes = MoveItConfigData::GROUPS;
306  gen_files_.push_back(file);
307 
308  // chomp_planning.yaml --------------------------------------------------------------------------------------
309  file.file_name_ = "chomp_planning.yaml";
310  file.rel_path_ = config_data_->appendPaths(CONFIG_PATH, file.file_name_);
311  template_path = config_data_->appendPaths(config_data_->template_package_path_, file.rel_path_);
312  file.description_ = "Specifies which chomp planning plugin parameters to be used for the CHOMP planner";
313  file.gen_func_ = [this, template_path](const std::string& output_path) {
314  return copyTemplate(template_path, output_path);
315  };
316  gen_files_.push_back(file);
317 
318  // stomp_planning.yaml --------------------------------------------------------------------------------------
319  file.file_name_ = "stomp_planning.yaml";
320  file.rel_path_ = config_data_->appendPaths(CONFIG_PATH, file.file_name_);
321  file.description_ = "Specifies which stomp planning plugin parameters to be used for the STOMP planner";
322  file.gen_func_ = [this](const std::string& output_path) { return config_data_->outputSTOMPPlanningYAML(output_path); };
323  file.write_on_changes = MoveItConfigData::GROUPS; // need to double check if this is actually correct!
324  gen_files_.push_back(file);
325 
326  // kinematics.yaml --------------------------------------------------------------------------------------
327  file.file_name_ = "kinematics.yaml";
328  file.rel_path_ = config_data_->appendPaths(CONFIG_PATH, file.file_name_);
329  file.description_ = "Specifies which kinematic solver plugin to use for each planning group in the SRDF, as well as "
330  "the kinematic solver search resolution.";
331  file.gen_func_ = [this](const std::string& output_path) { return config_data_->outputKinematicsYAML(output_path); };
332  file.write_on_changes = MoveItConfigData::GROUPS | MoveItConfigData::GROUP_KINEMATICS;
333  gen_files_.push_back(file);
334 
335  // joint_limits.yaml --------------------------------------------------------------------------------------
336  file.file_name_ = "joint_limits.yaml";
337  file.rel_path_ = config_data_->appendPaths(CONFIG_PATH, file.file_name_);
338  file.description_ = "Contains additional information about joints that appear in your planning groups that is not "
339  "contained in the URDF, as well as allowing you to set maximum and minimum limits for velocity "
340  "and acceleration than those contained in your URDF. This information is used by our trajectory "
341  "filtering system to assign reasonable velocities and timing for the trajectory before it is "
342  "passed to the robots controllers.";
343  file.gen_func_ = [this](const std::string& output_path) { return config_data_->outputJointLimitsYAML(output_path); };
344  file.write_on_changes = 0; // Can they be changed?
345  gen_files_.push_back(file);
346 
347  // cartesian_limits.yaml --------------------------------------------------------------------------------------
348  file.file_name_ = "cartesian_limits.yaml";
349  file.rel_path_ = config_data_->appendPaths(CONFIG_PATH, file.file_name_);
350  template_path = config_data_->appendPaths(config_data_->template_package_path_, file.rel_path_);
351  file.description_ = "Cartesian velocity for planning in the workspace."
352  "The velocity is used by pilz industrial motion planner as maximum velocity for cartesian "
353  "planning requests scaled by the velocity scaling factor of an individual planning request.";
354  file.gen_func_ = [this, template_path](const std::string& output_path) {
355  return copyTemplate(template_path, output_path);
356  };
357  file.write_on_changes = 0; // Can they be changed?
358  gen_files_.push_back(file);
359 
360  // fake_controllers.yaml --------------------------------------------------------------------------------------
361  file.file_name_ = "fake_controllers.yaml";
362  file.rel_path_ = config_data_->appendPaths(CONFIG_PATH, file.file_name_);
363  file.description_ = "Creates dummy configurations for controllers that correspond to defined groups. This is mostly "
364  "useful for testing.";
365  file.gen_func_ = [this](const std::string& output_path) {
366  return config_data_->outputFakeControllersYAML(output_path);
367  };
368  file.write_on_changes = MoveItConfigData::GROUPS;
369  gen_files_.push_back(file);
370 
371  // simple_moveit_controllers.yaml -------------------------------------------------------------------------------
372  file.file_name_ = "simple_moveit_controllers.yaml";
373  file.rel_path_ = config_data_->appendPaths(CONFIG_PATH, file.file_name_);
374  file.description_ = "Creates controller configuration for SimpleMoveItControllerManager";
375  file.gen_func_ = [this](const std::string& output_path) {
376  return config_data_->outputSimpleControllersYAML(output_path);
377  };
378  file.write_on_changes = MoveItConfigData::GROUPS;
379  gen_files_.push_back(file);
380 
381  // gazebo_controllers.yaml ------------------------------------------------------------------
382  file.file_name_ = "gazebo_controllers.yaml";
383  file.rel_path_ = config_data_->appendPaths(CONFIG_PATH, file.file_name_);
384  template_path = config_data_->appendPaths(config_data_->template_package_path_, file.rel_path_);
385  file.description_ = "Configuration of Gazebo controllers";
386  file.gen_func_ = [this, template_path](const std::string& output_path) {
387  return copyTemplate(template_path, output_path);
388  };
389  gen_files_.push_back(file);
390 
391  // ros_controllers.yaml --------------------------------------------------------------------------------------
392  file.file_name_ = "ros_controllers.yaml";
393  file.rel_path_ = config_data_->appendPaths(CONFIG_PATH, file.file_name_);
394  file.description_ = "Creates controller configurations for ros_control.";
395  file.gen_func_ = [this](const std::string& output_path) {
396  return config_data_->outputROSControllersYAML(output_path);
397  };
398  file.write_on_changes = MoveItConfigData::GROUPS;
399  gen_files_.push_back(file);
400 
401  // sensors_3d.yaml --------------------------------------------------------------------------------------
402  file.file_name_ = "sensors_3d.yaml";
403  file.rel_path_ = config_data_->appendPaths(CONFIG_PATH, file.file_name_);
404  file.description_ = "Creates configurations 3d sensors.";
405  file.gen_func_ = [this](const std::string& output_path) {
406  return config_data_->output3DSensorPluginYAML(output_path);
407  };
408  file.write_on_changes = MoveItConfigData::SENSORS_CONFIG;
409  gen_files_.push_back(file);
410 
411  // -------------------------------------------------------------------------------------------------------------------
412  // LAUNCH FILES ------------------------------------------------------------------------------------------------------
413  // -------------------------------------------------------------------------------------------------------------------
414  std::string launch_path = "launch";
415  const std::string template_launch_path = config_data_->appendPaths(config_data_->template_package_path_, launch_path);
416 
417  // launch/ --------------------------------------------------------------------------------------
418  file.file_name_ = "launch/";
419  file.rel_path_ = file.file_name_;
420  file.description_ = "Folder containing all MoveIt launch files for your robot. "
421  "This folder is required and cannot be disabled.";
422  file.gen_func_ = [this](const std::string& output_path) { return createFolder(output_path); };
423  file.write_on_changes = 0;
424  gen_files_.push_back(file);
425 
426  // move_group.launch --------------------------------------------------------------------------------------
427  file.file_name_ = "move_group.launch";
428  file.rel_path_ = config_data_->appendPaths(launch_path, file.file_name_);
429  template_path = config_data_->appendPaths(template_launch_path, file.file_name_);
430  file.description_ = "Launches the move_group node that provides the MoveGroup action and other parameters <a "
431  "href='http://moveit.ros.org/doxygen/"
432  "classmoveit_1_1planning__interface_1_1MoveGroup.html#details'>MoveGroup action</a>";
433  file.gen_func_ = [this, template_path](const std::string& output_path) {
434  return copyTemplate(template_path, output_path);
435  };
436  file.write_on_changes = 0;
437  gen_files_.push_back(file);
438 
439  // planning_context.launch --------------------------------------------------------------------------------------
440  file.file_name_ = "planning_context.launch";
441  file.rel_path_ = config_data_->appendPaths(launch_path, file.file_name_);
442  template_path = config_data_->appendPaths(template_launch_path, file.file_name_);
443  file.description_ = "Loads settings for the ROS parameter server, required for running MoveIt. This includes the "
444  "SRDF, joints_limits.yaml file, ompl_planning.yaml file, optionally the URDF, etc";
445  file.gen_func_ = [this, template_path](const std::string& output_path) {
446  return copyTemplate(template_path, output_path);
447  };
448  file.write_on_changes = 0;
449  gen_files_.push_back(file);
450 
451  // moveit_rviz.launch --------------------------------------------------------------------------------------
452  file.file_name_ = "moveit_rviz.launch";
453  file.rel_path_ = config_data_->appendPaths(launch_path, file.file_name_);
454  template_path = config_data_->appendPaths(template_launch_path, file.file_name_);
455  file.description_ = "Visualize in Rviz the robot's planning groups running with interactive markers that allow goal "
456  "states to be set.";
457  file.gen_func_ = [this, template_path](const std::string& output_path) {
458  return copyTemplate(template_path, output_path);
459  };
460  file.write_on_changes = 0;
461  gen_files_.push_back(file);
462 
463  // ompl_planning_pipeline.launch
464  // --------------------------------------------------------------------------------------
465  file.file_name_ = "ompl_planning_pipeline.launch.xml";
466  file.rel_path_ = config_data_->appendPaths(launch_path, file.file_name_);
467  template_path = config_data_->appendPaths(template_launch_path, file.file_name_);
468  file.description_ = "Intended to be included in other launch files that require the OMPL planning plugin. Defines "
469  "the proper plugin name on the parameter server and a default selection of planning request "
470  "adapters.";
471  file.gen_func_ = [this, template_path](const std::string& output_path) {
472  return copyTemplate(template_path, output_path);
473  };
474  file.write_on_changes = 0;
475  gen_files_.push_back(file);
476 
477  // pilz_industrial_motion_planner_planning_pipeline.launch
478  // --------------------------------------------------------------------------------------
479  file.file_name_ = "pilz_industrial_motion_planner_planning_pipeline.launch.xml";
480  file.rel_path_ = config_data_->appendPaths(launch_path, file.file_name_);
481  template_path = config_data_->appendPaths(template_launch_path, file.file_name_);
482  file.description_ = "Intended to be included in other launch files that require the Pilz industrial motion planner "
483  "planning plugin. Defines the proper plugin name on the parameter server and a default selection "
484  "of planning request adapters.";
485  file.gen_func_ = [this, template_path](const std::string& output_path) {
486  return copyTemplate(template_path, output_path);
487  };
488  file.write_on_changes = 0;
489  gen_files_.push_back(file);
490 
491  // chomp_planning_pipeline.launch
492  // --------------------------------------------------------------------------------------
493  file.file_name_ = "chomp_planning_pipeline.launch.xml";
494  file.rel_path_ = config_data_->appendPaths(launch_path, file.file_name_);
495  template_path = config_data_->appendPaths(template_launch_path, file.file_name_);
496  file.description_ = "Intended to be included in other launch files that require the CHOMP planning plugin. Defines "
497  "the proper plugin name on the parameter server and a default selection of planning request "
498  "adapters.";
499  file.gen_func_ = [this, template_path](const std::string& output_path) {
500  return copyTemplate(template_path, output_path);
501  };
502  file.write_on_changes = 0;
503  gen_files_.push_back(file);
504 
505  // stomp_planning_pipeline.launch
506  // --------------------------------------------------------------------------------------
507  file.file_name_ = "stomp_planning_pipeline.launch.xml";
508  file.rel_path_ = config_data_->appendPaths(launch_path, file.file_name_);
509  template_path = config_data_->appendPaths(template_launch_path, file.file_name_);
510  file.description_ = "Intended to be included in other launch files that require the STOMP planning plugin. Defines "
511  "the proper plugin name on the parameter server and a default selection of planning request "
512  "adapters.";
513  file.gen_func_ = [this, template_path](const std::string& output_path) {
514  return copyTemplate(template_path, output_path);
515  };
516  file.write_on_changes = 0;
517  gen_files_.push_back(file);
518 
519  // ompl-chomp_planning_pipeline.launch.xml
520  // --------------------------------------------------------------------------------------
521  file.file_name_ = "ompl-chomp_planning_pipeline.launch.xml";
522  file.rel_path_ = config_data_->appendPaths(launch_path, file.file_name_);
523  template_path = config_data_->appendPaths(template_launch_path, file.file_name_);
524  file.description_ =
525  "Intended to be included in other launch files that require the OMPL-CHOMP planning plugins. Defines "
526  "the proper plugin name on the parameter server and a default selection of planning request "
527  "adapters.";
528  file.gen_func_ = [this, template_path](const std::string& output_path) {
529  return copyTemplate(template_path, output_path);
530  };
531  file.write_on_changes = 0;
532  gen_files_.push_back(file);
533 
534  // planning_pipeline.launch --------------------------------------------------------------------------------------
535  file.file_name_ = "planning_pipeline.launch.xml";
536  file.rel_path_ = config_data_->appendPaths(launch_path, file.file_name_);
537  template_path = config_data_->appendPaths(template_launch_path, file.file_name_);
538  file.description_ = "Helper launch file that can choose between different planning pipelines to be loaded.";
539  file.gen_func_ = [this, template_path](const std::string& output_path) {
540  return copyTemplate(template_path, output_path);
541  };
542  file.write_on_changes = 0;
543  gen_files_.push_back(file);
544 
545  // warehouse_settings.launch --------------------------------------------------------------------------------------
546  file.file_name_ = "warehouse_settings.launch.xml";
547  file.rel_path_ = config_data_->appendPaths(launch_path, file.file_name_);
548  template_path = config_data_->appendPaths(template_launch_path, file.file_name_);
549  file.description_ = "Helper launch file that specifies default settings for MongoDB.";
550  file.gen_func_ = [this, template_path](const std::string& output_path) {
551  return copyTemplate(template_path, output_path);
552  };
553  file.write_on_changes = 0;
554  gen_files_.push_back(file);
555 
556  // warehouse.launch --------------------------------------------------------------------------------------
557  file.file_name_ = "warehouse.launch";
558  file.rel_path_ = config_data_->appendPaths(launch_path, file.file_name_);
559  template_path = config_data_->appendPaths(template_launch_path, file.file_name_);
560  file.description_ = "Launch file for starting MongoDB.";
561  file.gen_func_ = [this, template_path](const std::string& output_path) {
562  return copyTemplate(template_path, output_path);
563  };
564  file.write_on_changes = 0;
565  gen_files_.push_back(file);
566 
567  // default_warehouse_db.launch --------------------------------------------------------------------------------------
568  file.file_name_ = "default_warehouse_db.launch";
569  file.rel_path_ = config_data_->appendPaths(launch_path, file.file_name_);
570  template_path = config_data_->appendPaths(template_launch_path, file.file_name_);
571  file.description_ = "Launch file for starting the warehouse with a default MongoDB.";
572  file.gen_func_ = [this, template_path](const std::string& output_path) {
573  return copyTemplate(template_path, output_path);
574  };
575  file.write_on_changes = 0;
576  gen_files_.push_back(file);
577 
578  // run_benchmark_ompl.launch --------------------------------------------------------------------------------------
579  file.file_name_ = "run_benchmark_ompl.launch";
580  file.rel_path_ = config_data_->appendPaths(launch_path, file.file_name_);
581  template_path = config_data_->appendPaths(template_launch_path, file.file_name_);
582  file.description_ = "Launch file for benchmarking OMPL planners";
583  file.gen_func_ = [this, template_path](const std::string& output_path) {
584  return copyTemplate(template_path, output_path);
585  };
586  file.write_on_changes = 0;
587  gen_files_.push_back(file);
588 
589  // sensor_manager.launch --------------------------------------------------------------------------------------
590  file.file_name_ = "sensor_manager.launch.xml";
591  file.rel_path_ = config_data_->appendPaths(launch_path, file.file_name_);
592  template_path = config_data_->appendPaths(template_launch_path, file.file_name_);
593  file.description_ = "Helper launch file that can choose between different sensor managers to be loaded.";
594  file.gen_func_ = [this, template_path](const std::string& output_path) {
595  return copyTemplate(template_path, output_path);
596  };
597  file.write_on_changes = 0;
598  gen_files_.push_back(file);
599 
600  // robot_moveit_sensor_manager.launch ------------------------------------------------------------------
601  file.file_name_ = robot_name + "_moveit_sensor_manager.launch.xml";
602  file.rel_path_ = config_data_->appendPaths(launch_path, file.file_name_);
603  template_path = config_data_->appendPaths(template_launch_path, "moveit_sensor_manager.launch.xml");
604  file.description_ = "Placeholder for settings specific to the MoveIt sensor manager implemented for you robot.";
605  file.gen_func_ = [this, template_path](const std::string& output_path) {
606  return copyTemplate(template_path, output_path);
607  };
608  file.write_on_changes = 0;
609  gen_files_.push_back(file);
610 
611  // trajectory_execution.launch ------------------------------------------------------------------
612  file.file_name_ = "trajectory_execution.launch.xml";
613  file.rel_path_ = config_data_->appendPaths(launch_path, file.file_name_);
614  template_path = config_data_->appendPaths(template_launch_path, file.file_name_);
615  file.description_ = "Loads settings for the ROS parameter server required for executing trajectories using the "
616  "trajectory_execution_manager::TrajectoryExecutionManager.";
617  file.gen_func_ = [this, template_path](const std::string& output_path) {
618  return copyTemplate(template_path, output_path);
619  };
620  file.write_on_changes = 0;
621  gen_files_.push_back(file);
622 
623  file.file_name_ = "fake_moveit_controller_manager.launch.xml";
624  file.rel_path_ = config_data_->appendPaths(launch_path, file.file_name_);
625  template_path = config_data_->appendPaths(template_launch_path, file.file_name_);
626  file.description_ = "Loads the fake controller plugin.";
627  file.gen_func_ = [this, template_path](const std::string& output_path) {
628  return copyTemplate(template_path, output_path);
629  };
630  file.write_on_changes = 0;
631  gen_files_.push_back(file);
632 
633  file.file_name_ = "simple_moveit_controller_manager.launch.xml";
634  file.rel_path_ = config_data_->appendPaths(launch_path, file.file_name_);
635  template_path = config_data_->appendPaths(template_launch_path, file.file_name_);
636  file.description_ = "Loads the default controller plugin.";
637  file.gen_func_ = [this, template_path](const std::string& output_path) {
638  return copyTemplate(template_path, output_path);
639  };
640  file.write_on_changes = 0;
641  gen_files_.push_back(file);
642 
643  file.file_name_ = "ros_control_moveit_controller_manager.launch.xml";
644  file.rel_path_ = config_data_->appendPaths(launch_path, file.file_name_);
645  template_path = config_data_->appendPaths(template_launch_path, file.file_name_);
646  file.description_ = "Loads the ros_control controller plugin.";
647  file.gen_func_ = [this, template_path](const std::string& output_path) {
648  return copyTemplate(template_path, output_path);
649  };
650  file.write_on_changes = 0;
651  gen_files_.push_back(file);
652 
653  // demo.launch ------------------------------------------------------------------
654  file.file_name_ = "demo.launch";
655  file.rel_path_ = config_data_->appendPaths(launch_path, file.file_name_);
656  template_path = config_data_->appendPaths(template_launch_path, file.file_name_);
657  file.description_ = "Run a demo of MoveIt.";
658  file.gen_func_ = [this, template_path](const std::string& output_path) {
659  return copyTemplate(template_path, output_path);
660  };
661  file.write_on_changes = 0;
662  gen_files_.push_back(file);
663 
664  // demo_gazebo.launch ------------------------------------------------------------------
665  file.file_name_ = "demo_gazebo.launch";
666  file.rel_path_ = config_data_->appendPaths(launch_path, file.file_name_);
667  template_path = config_data_->appendPaths(template_launch_path, file.file_name_);
668  file.description_ = "Run a demo of MoveIt with Gazebo and Rviz";
669  file.gen_func_ = [this, template_path](const std::string& output_path) {
670  return copyTemplate(template_path, output_path);
671  };
672  file.write_on_changes = 0;
673  gen_files_.push_back(file);
674 
675  // gazebo.launch ------------------------------------------------------------------
676  file.file_name_ = "gazebo.launch";
677  file.rel_path_ = config_data_->appendPaths(launch_path, file.file_name_);
678  template_path = config_data_->appendPaths(template_launch_path, "gazebo.launch");
679  file.description_ = "Gazebo launch file which also launches ros_controllers and sends robot urdf to param server, "
680  "then using gazebo_ros pkg the robot is spawned to Gazebo";
681  file.gen_func_ = [this, template_path](const std::string& output_path) {
682  return copyTemplate(template_path, output_path);
683  };
684  file.write_on_changes = 0;
685  gen_files_.push_back(file);
686 
687  // joystick_control.launch ------------------------------------------------------------------
688  file.file_name_ = "joystick_control.launch";
689  file.rel_path_ = config_data_->appendPaths(launch_path, file.file_name_);
690  template_path = config_data_->appendPaths(template_launch_path, file.file_name_);
691  file.description_ = "Control the Rviz Motion Planning Plugin with a joystick";
692  file.gen_func_ = [this, template_path](const std::string& output_path) {
693  return copyTemplate(template_path, output_path);
694  };
695  file.write_on_changes = 0;
696  gen_files_.push_back(file);
697 
698  // setup_assistant.launch ------------------------------------------------------------------
699  file.file_name_ = "setup_assistant.launch";
700  file.rel_path_ = config_data_->appendPaths(launch_path, file.file_name_);
701  template_path = config_data_->appendPaths(
702  template_launch_path, "edit_configuration_package.launch"); // named this so that this launch file is not mixed
703  // up with the SA's real launch file
704  file.description_ = "Launch file for easily re-starting the MoveIt Setup Assistant to edit this robot's generated "
705  "configuration package.";
706  file.gen_func_ = [this, template_path](const std::string& output_path) {
707  return copyTemplate(template_path, output_path);
708  };
709  file.write_on_changes = 0;
710  gen_files_.push_back(file);
711 
712  // ros_controllers.launch ------------------------------------------------------------------
713  file.file_name_ = "ros_controllers.launch";
714  file.rel_path_ = config_data_->appendPaths(launch_path, file.file_name_);
715  template_path = config_data_->appendPaths(template_launch_path, "ros_controllers.launch");
716  file.description_ = "ros_controllers launch file";
717  file.gen_func_ = [this, template_path](const std::string& output_path) {
718  return copyTemplate(template_path, output_path);
719  };
720  file.write_on_changes = MoveItConfigData::GROUPS;
721  gen_files_.push_back(file);
722 
723  // moveit.rviz ------------------------------------------------------------------
724  file.file_name_ = "moveit.rviz";
725  file.rel_path_ = config_data_->appendPaths(launch_path, file.file_name_);
726  template_path = config_data_->appendPaths(template_launch_path, "moveit.rviz");
727  file.description_ = "Configuration file for Rviz with the Motion Planning Plugin already setup. Used by passing "
728  "roslaunch moveit_rviz.launch config:=true";
729  file.gen_func_ = [this, template_path](const std::string& output_path) {
730  return copyTemplate(template_path, output_path);
731  };
732  file.write_on_changes = 0;
733  gen_files_.push_back(file);
734 
735  // -------------------------------------------------------------------------------------------------------------------
736  // OTHER FILES -------------------------------------------------------------------------------------------------------
737  // -------------------------------------------------------------------------------------------------------------------
738 
739  // .setup_assistant ------------------------------------------------------------------
740  file.file_name_ = SETUP_ASSISTANT_FILE;
741  file.rel_path_ = file.file_name_;
742  file.description_ = "MoveIt Setup Assistant's hidden settings file. You should not need to edit this file.";
743  file.gen_func_ = [this](const std::string& output_path) {
744  return config_data_->outputSetupAssistantFile(output_path);
745  };
746  file.write_on_changes = -1; // write on any changes
747  gen_files_.push_back(file);
748 
749  return true;
750 }
751 
752 // ******************************************************************************************
753 // Verify with user if certain screens have not been completed
754 // ******************************************************************************************
755 bool ConfigurationFilesWidget::checkDependencies()
756 {
757  QStringList dependencies;
758  bool required_actions = false;
759 
760  // Check that at least 1 planning group exists
761  if (config_data_->srdf_->groups_.empty())
762  {
763  dependencies << "No robot model planning groups have been created";
764  }
765 
766  // Check that at least 1 link pair is disabled from collision checking
767  if (config_data_->srdf_->disabled_collision_pairs_.empty())
768  {
769  dependencies << "No self-collisions have been disabled";
770  }
771 
772  // Check that there is at least 1 end effector added
773  if (config_data_->srdf_->end_effectors_.empty())
774  {
775  dependencies << "No end effectors have been added";
776  }
777 
778  // Check that there is at least 1 virtual joint added
779  if (config_data_->srdf_->virtual_joints_.empty())
780  {
781  dependencies << "No virtual joints have been added";
782  }
783 
784  // Check that there is a author name
785  if (config_data_->author_name_.find_first_not_of(' ') == std::string::npos)
786  {
787  // There is no name or it consists of whitespaces only
788  dependencies << "<b>No author name added</b>";
789  required_actions = true;
790  }
791 
792  // Check that email information is filled
793  QRegularExpression mail_regex("\\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}\\b");
794  mail_regex.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
795  QString test_email = QString::fromStdString(config_data_->author_email_);
796  if (!mail_regex.match(test_email).hasMatch())
797  {
798  dependencies << "<b>No valid email address added</b>";
799  required_actions = true;
800  }
801 
802  // Display all accumumlated errors:
803  if (!dependencies.empty())
804  {
805  // Create a dependency message
806  QString dep_message;
807  if (!required_actions)
808  {
809  dep_message = "Some setup steps have not been completed. None of the steps are required, but here is a reminder "
810  "of what was not filled in, just in case something was forgotten:<br /><ul>";
811  }
812  else
813  {
814  dep_message = "Some setup steps have not been completed. Please fix the required steps (printed in bold), "
815  "otherwise the setup cannot be completed:<br /><ul>";
816  }
817 
818  for (int i = 0; i < dependencies.size(); ++i)
819  {
820  dep_message.append("<li>").append(dependencies.at(i)).append("</li>");
821  }
822 
823  if (!required_actions)
824  {
825  dep_message.append("</ul><br/>Press Ok to continue generating files.");
826  if (QMessageBox::question(this, "Incomplete MoveIt Setup Assistant Steps", dep_message,
827  QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel)
828  {
829  return false; // abort
830  }
831  }
832  else
833  {
834  QMessageBox::warning(this, "Incomplete MoveIt Setup Assistant Steps", dep_message);
835  return false;
836  }
837  }
838 
839  return true;
840 }
841 
842 // ******************************************************************************************
843 // A function for showing progress and user feedback about what happened
844 // ******************************************************************************************
845 void ConfigurationFilesWidget::updateProgress()
846 {
847  action_num_++;
848 
849  // Calc percentage
850  progress_bar_->setValue(double(action_num_) / gen_files_.size() * 100);
851 
852  // allow the progress bar to be shown
853  QApplication::processEvents();
854 }
855 
856 // ******************************************************************************************
857 // Display the selected action in the desc box
858 // ******************************************************************************************
859 void ConfigurationFilesWidget::changeActionDesc(int id)
860 {
861  // Only allow event if list is not empty
862  if (id >= 0)
863  {
864  // Show the selected text
865  action_label_->setText(action_desc_.at(id));
866  }
867 }
868 
869 // ******************************************************************************************
870 // Disable or enable item in gen_files_ array
871 // ******************************************************************************************
872 void ConfigurationFilesWidget::changeCheckedState(QListWidgetItem* item)
873 {
874  std::size_t index = item->data(Qt::UserRole).toUInt();
875  bool generate = (item->checkState() == Qt::Checked);
876 
877  if (!generate && (gen_files_[index].write_on_changes & config_data_->changes))
878  {
879  QMessageBox::warning(this, "Package Generation",
880  "You should generate this file to ensure your changes will take "
881  "effect.");
882  }
883 
884  // Enable/disable file
885  gen_files_[index].generate_ = generate;
886 }
887 
888 // ******************************************************************************************
889 // Called when setup assistant navigation switches to this screen
890 // ******************************************************************************************
891 void ConfigurationFilesWidget::focusGiven()
892 {
893  loadGenFiles();
894 
895  // Which files have been modified outside the Setup Assistant?
896  bool files_already_modified = checkGenFiles();
897 
898  // disable reaction to checkbox changes
899  disconnect(action_list_, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(changeCheckedState(QListWidgetItem*)));
900 
901  // Show files in GUI
902  bool have_conflicting_changes = showGenFiles();
903 
904  // react to manual changes only (not programmatic ones)
905  connect(action_list_, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(changeCheckedState(QListWidgetItem*)));
906 
907  // Allow list box to populate
908  QApplication::processEvents();
909 
910  if (files_already_modified)
911  {
912  // Some were found to be modified
913  QString msg("Some files have been modified outside of the Setup Assistant (according to timestamp). "
914  "The Setup Assistant will not overwrite these changes by default because often changing configuration "
915  "files manually is necessary, "
916  "but we recommend you check the list and enable the checkbox next to files you would like to "
917  "overwrite. ");
918  if (have_conflicting_changes)
919  msg += "<br/><font color='red'>Attention:</font> Some files (<font color='red'>marked red</font>) are changed "
920  "both, externally and in Setup Assistant.";
921  QMessageBox::information(this, "Files Modified", msg);
922  }
923 }
924 
925 // ******************************************************************************************
926 // Check the list of files to be generated for modification
927 // Returns true if files were detected as modified
928 // ******************************************************************************************
929 bool ConfigurationFilesWidget::checkGenFiles()
930 {
931  // Check if we are 'editing' a prev generated config pkg
932  if (config_data_->config_pkg_path_.empty())
933  return false; // this is a new package
934 
935  // Check if we have the previous modification timestamp to compare agains
936  if (config_data_->config_pkg_generated_timestamp_ == 0)
937  return false; // this package has not been generated with a timestamp, backwards compatible.
938 
939  static const std::time_t TIME_MOD_TOLERANCE = 10;
940 
941  // Check all old file's modification time
942  bool found_modified = false;
943  for (GenerateFile& gen_file : gen_files_)
944  {
945  GenerateFile* file = &gen_file;
946 
947  fs::path file_path = config_data_->appendPaths(config_data_->config_pkg_path_, file->rel_path_);
948 
949  // Don't disable folders from being generated
950  if (fs::is_directory(file_path))
951  continue;
952 
953  if (fs::is_regular_file(file_path))
954  {
955  std::time_t mod_time = fs::last_write_time(file_path);
956 
957  // ROS_DEBUG_STREAM("File " << file->file_name_ << " file modified " << mod_time << " pkg modified " <<
958  // config_data_->config_pkg_generated_timestamp_);
959 
960  if (mod_time > config_data_->config_pkg_generated_timestamp_ + TIME_MOD_TOLERANCE ||
961  mod_time < config_data_->config_pkg_generated_timestamp_ - TIME_MOD_TOLERANCE)
962  {
963  ROS_INFO_STREAM("Manual editing detected: not over-writing by default file " << file->file_name_);
964 
965  if (file->write_on_changes & config_data_->changes)
966  ROS_WARN_STREAM("Editing in Setup Assistant conflicts with external editing of file " << file->file_name_);
967 
968  file->generate_ = false; // do not overwrite by default
969  file->modified_ = true;
970  found_modified = true;
971  }
972  else
973  {
974  file->modified_ = false;
975  }
976  }
977  }
978 
979  // Warn user if files have been modified outside Setup Assistant
980  return found_modified;
981 }
982 
983 // ******************************************************************************************
984 // Show the list of files to be generated
985 // ******************************************************************************************
986 bool ConfigurationFilesWidget::showGenFiles()
987 {
988  bool have_conflicting_changes = false;
989  action_list_->clear();
990 
991  // Display this list in the GUI
992  for (std::size_t i = 0; i < gen_files_.size(); ++i)
993  {
994  GenerateFile* file = &gen_files_[i];
995 
996  // Create a formatted row
997  QListWidgetItem* item = new QListWidgetItem(QString(file->rel_path_.c_str()), action_list_, 0);
998 
999  fs::path file_path = config_data_->appendPaths(config_data_->config_pkg_path_, file->rel_path_);
1000 
1001  // Checkbox
1002  item->setCheckState(file->generate_ ? Qt::Checked : Qt::Unchecked);
1003  // externally modified?
1004  if (file->modified_)
1005  {
1006  if (file->write_on_changes & config_data_->changes)
1007  {
1008  have_conflicting_changes = true;
1009  item->setForeground(QBrush(QColor(255, 0, 0)));
1010  }
1011  else
1012  item->setForeground(QBrush(QColor(255, 135, 0)));
1013  }
1014 
1015  // Don't allow folders to be disabled
1016  if (fs::is_directory(file_path))
1017  {
1018  item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
1019  }
1020 
1021  // Link the gen_files_ index to this item
1022  item->setData(Qt::UserRole, QVariant(static_cast<qulonglong>(i)));
1023 
1024  // Add actions to list
1025  action_list_->addItem(item);
1026  action_desc_.append(QString(file->description_.c_str()));
1027  }
1028 
1029  // Select the first item in the list so that a description is visible
1030  action_list_->setCurrentRow(0);
1031  return have_conflicting_changes;
1032 }
1033 
1034 // ******************************************************************************************
1035 // Save configuration click event
1036 // ******************************************************************************************
1037 void ConfigurationFilesWidget::savePackage()
1038 {
1039  // Feedback
1040  success_label_->hide();
1041 
1042  // Reset the progress bar counter and GUI stuff
1043  action_num_ = 0;
1044  progress_bar_->setValue(0);
1045 
1046  if (!generatePackage())
1047  {
1048  ROS_ERROR_STREAM("Failed to generate entire configuration package");
1049  return;
1050  }
1051 
1052  // Alert user it completed successfully --------------------------------------------------
1053  progress_bar_->setValue(100);
1054  success_label_->show();
1055  has_generated_pkg_ = true;
1056 }
1057 
1058 // ******************************************************************************************
1059 // Save package using default path
1060 // ******************************************************************************************
1061 bool ConfigurationFilesWidget::generatePackage()
1062 {
1063  // Get path name
1064  std::string new_package_path = stack_path_->getPath();
1065 
1066  // Check that a valid stack package name has been given
1067  if (new_package_path.empty())
1068  {
1069  QMessageBox::warning(this, "Error Generating",
1070  "No package path provided. Please choose a directory location to "
1071  "generate the MoveIt configuration files.");
1072  return false;
1073  }
1074 
1075  // Check setup assist deps
1076  if (!checkDependencies())
1077  return false; // canceled
1078 
1079  // Check that all groups have components
1080  if (!noGroupsEmpty())
1081  return false; // not ready
1082 
1083  // Trim whitespace from user input
1084  boost::trim(new_package_path);
1085 
1086  // Get the package name ---------------------------------------------------------------------------------
1087  new_package_name_ = getPackageName(new_package_path);
1088 
1089  const std::string setup_assistant_file = config_data_->appendPaths(new_package_path, SETUP_ASSISTANT_FILE);
1090 
1091  // Make sure old package is correct package type and verify over write
1092  if (fs::is_directory(new_package_path) && !fs::is_empty(new_package_path))
1093  {
1094  // Check if the old package is a setup assistant package. If it is not, quit
1095  if (!fs::is_regular_file(setup_assistant_file))
1096  {
1097  QMessageBox::warning(
1098  this, "Incorrect Folder/Package",
1099  QString("The chosen package location already exists but was not previously created using this MoveIt Setup "
1100  "Assistant. "
1101  "If this is a mistake, add the missing file: ")
1102  .append(setup_assistant_file.c_str()));
1103  return false;
1104  }
1105 
1106  // Confirm overwrite
1107  if (QMessageBox::question(this, "Confirm Package Update",
1108  QString("Are you sure you want to overwrite this existing package with updated "
1109  "configurations?<br /><i>")
1110  .append(new_package_path.c_str())
1111  .append("</i>"),
1112  QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel)
1113  {
1114  return false; // abort
1115  }
1116  }
1117  else // this is a new package (but maybe the folder already exists)
1118  {
1119  // Create new directory ------------------------------------------------------------------
1120  try
1121  {
1122  fs::create_directory(new_package_path) && !fs::is_directory(new_package_path);
1123  }
1124  catch (...)
1125  {
1126  QMessageBox::critical(this, "Error Generating Files",
1127  QString("Unable to create directory ").append(new_package_path.c_str()));
1128  return false;
1129  }
1130  }
1131 
1132  loadTemplateStrings();
1133 
1134  // Begin to create files and folders ----------------------------------------------------------------------
1135  std::string absolute_path;
1136 
1137  for (GenerateFile& gen_file : gen_files_)
1138  {
1139  GenerateFile* file = &gen_file;
1140 
1141  // Check if we should skip this file
1142  if (!file->generate_)
1143  {
1144  continue;
1145  }
1146 
1147  // Create the absolute path
1148  absolute_path = config_data_->appendPaths(new_package_path, file->rel_path_);
1149  ROS_DEBUG_STREAM("Creating file " << absolute_path);
1150 
1151  // Run the generate function
1152  if (!file->gen_func_(absolute_path))
1153  {
1154  // Error occured
1155  QMessageBox::critical(this, "Error Generating File",
1156  QString("Failed to generate folder or file: '")
1157  .append(file->rel_path_.c_str())
1158  .append("' at location:\n")
1159  .append(absolute_path.c_str()));
1160  return false;
1161  }
1162  updateProgress(); // Increment and update GUI
1163  }
1164 
1165  return true;
1166 }
1167 
1168 // ******************************************************************************************
1169 // Quit the program because we are done
1170 // ******************************************************************************************
1171 void ConfigurationFilesWidget::exitSetupAssistant()
1172 {
1173  if (has_generated_pkg_ || QMessageBox::question(this, "Exit Setup Assistant",
1174  QString("Are you sure you want to exit the MoveIt Setup Assistant?"),
1175  QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok)
1176  {
1177  QApplication::quit();
1178  }
1179 }
1180 
1181 // ******************************************************************************************
1182 // Get the last folder name in a directory path
1183 // ******************************************************************************************
1184 const std::string ConfigurationFilesWidget::getPackageName(std::string package_path)
1185 {
1186  // Remove end slash if there is one
1187  if (!package_path.compare(package_path.size() - 1, 1, "/"))
1188  {
1189  package_path = package_path.substr(0, package_path.size() - 1);
1190  }
1191 
1192  // Get the last directory name
1193  std::string package_name;
1194  fs::path fs_package_path = package_path;
1195 
1196  package_name = fs_package_path.filename().string();
1197 
1198  // check for empty
1199  if (package_name.empty())
1200  package_name = "unknown";
1201 
1202  return package_name;
1204 
1205 // ******************************************************************************************
1206 // Check that no group is empty (without links/joints/etc)
1207 // ******************************************************************************************
1208 bool ConfigurationFilesWidget::noGroupsEmpty()
1209 {
1210  // Loop through all groups
1211  for (const auto& group : config_data_->srdf_->groups_)
1212  {
1213  // Whenever 1 of the 4 component types are found, stop checking this group
1214  if (!group.joints_.empty())
1215  continue;
1216  if (!group.links_.empty())
1217  continue;
1218  if (!group.chains_.empty())
1219  continue;
1220  if (!group.subgroups_.empty())
1221  continue;
1222 
1223  // This group has no contents, bad
1224  QMessageBox::warning(
1225  this, "Empty Group",
1226  QString("The planning group '")
1227  .append(group.name_.c_str())
1228  .append("' is empty and has no subcomponents associated with it (joints/links/chains/subgroups). You must "
1229  "edit or remove this planning group before this configuration package can be saved."));
1230  return false;
1231  }
1232 
1233  return true; // good
1234 }
1235 
1236 // ******************************************************************************************
1237 // Load the strings that will be replaced in all templates
1238 // ******************************************************************************************
1239 void ConfigurationFilesWidget::loadTemplateStrings()
1241  // Clear strings (in case export is run multiple times)
1242  template_strings_.clear();
1243 
1244  // Pair 1
1245  addTemplateString("[GENERATED_PACKAGE_NAME]", new_package_name_);
1246 
1247  // Pair 2
1248  std::string urdf_location = config_data_->urdf_pkg_name_.empty() ? config_data_->urdf_path_ :
1249  "$(find " + config_data_->urdf_pkg_name_ + ")/" +
1250  config_data_->urdf_pkg_relative_path_;
1251  addTemplateString("[URDF_LOCATION]", urdf_location);
1252 
1253  // Pair 3
1254  if (config_data_->urdf_from_xacro_)
1255  addTemplateString("[URDF_LOAD_ATTRIBUTE]",
1256  "command=\"xacro " + config_data_->xacro_args_ + " '" + urdf_location + "'\"");
1257  else
1258  addTemplateString("[URDF_LOAD_ATTRIBUTE]", "textfile=\"" + urdf_location + "\"");
1259 
1260  // Pair 4
1261  if (config_data_->changes & MoveItConfigData::SIMULATION && !config_data_->gazebo_urdf_string_.empty())
1262  {
1263  std::string file_name = "gazebo_" + config_data_->urdf_model_->getName() + ".urdf";
1264  std::string rel_path = config_data_->appendPaths(CONFIG_PATH, file_name);
1265  addTemplateString("[GAZEBO_URDF_LOAD_ATTRIBUTE]", "textfile=\"$(find " + new_package_name_ + ")/" + rel_path + "\"");
1266  }
1267  else // reuse [URDF_LOAD_ATTRIBUTE] template
1268  addTemplateString("[GAZEBO_URDF_LOAD_ATTRIBUTE]", template_strings_.back().second);
1269 
1270  // Pair 5
1271  addTemplateString("[ROBOT_NAME]", config_data_->srdf_->robot_name_);
1272 
1273  // Pair 6
1274  addTemplateString("[ROBOT_ROOT_LINK]", config_data_->getRobotModel()->getRootLinkName());
1275 
1276  // Pair 7
1277  addTemplateString("[PLANNING_FRAME]", config_data_->getRobotModel()->getModelFrame());
1278 
1279  // Pair 8
1280  std::stringstream vjb;
1281  for (std::size_t i = 0; i < config_data_->srdf_->virtual_joints_.size(); ++i)
1282  {
1283  const srdf::Model::VirtualJoint& vj = config_data_->srdf_->virtual_joints_[i];
1284  vjb << " <node pkg=\"tf2_ros\" type=\"static_transform_publisher\" name=\"virtual_joint_broadcaster_" << i
1285  << "\" args=\"0 0 0 0 0 0 " << vj.parent_frame_ << " " << vj.child_link_ << "\" />" << std::endl;
1286  }
1287  addTemplateString("[VIRTUAL_JOINT_BROADCASTER]", vjb.str());
1288 
1289  // Pair 9 - Add dependencies to package.xml if the robot.urdf file is relative to a ROS package
1290  if (config_data_->urdf_pkg_name_.empty())
1291  {
1292  addTemplateString("[OTHER_DEPENDENCIES", ""); // not relative to a ROS package
1293  }
1294  else
1295  {
1296  std::stringstream deps;
1297  deps << " <run_depend>" << config_data_->urdf_pkg_name_ << "</run_depend>\n";
1298  addTemplateString("[OTHER_DEPENDENCIES]", deps.str()); // not relative to a ROS package
1299  }
1300 
1301  // Pair 10 - List of ROS Controllers to load in ros_controllers.launch file
1302  if (config_data_->getControllers().empty())
1303  {
1304  addTemplateString("[ROS_CONTROLLERS]", "");
1305  }
1306  else
1307  {
1308  std::stringstream controllers;
1309  for (ControllerConfig& controller : config_data_->getControllers())
1310  {
1311  // Check if the controller belongs to controller_list namespace
1312  if (controller.type_ != "FollowJointTrajectory")
1313  controllers << controller.name_ << " ";
1314  }
1315  addTemplateString("[ROS_CONTROLLERS]", controllers.str());
1316  }
1317 
1318  // Pair 11 - Add parameter files for the kinematics solvers that should be loaded
1319  // in addition to kinematics.yaml by planning_context.launch
1320  std::string kinematics_parameters_files_block;
1321  for (const auto& groups : config_data_->group_meta_data_)
1322  {
1323  if (groups.second.kinematics_parameters_file_.empty())
1324  continue;
1325 
1326  // add a linebreak if we have more than one entry
1327  if (!kinematics_parameters_files_block.empty())
1328  kinematics_parameters_files_block += "\n";
1329 
1330  std::string line = " <rosparam command=\"load\" ns=\"" + groups.first + "\" file=\"" +
1331  groups.second.kinematics_parameters_file_ + "\"/>";
1332  kinematics_parameters_files_block += line;
1333  }
1334  addTemplateString("[KINEMATICS_PARAMETERS_FILE_NAMES_BLOCK]", kinematics_parameters_files_block);
1335 
1336  addTemplateString("[AUTHOR_NAME]", config_data_->author_name_);
1337  addTemplateString("[AUTHOR_EMAIL]", config_data_->author_email_);
1338 
1339  {
1340  std::stringstream joints;
1341  for (const auto& pair : config_data_->getInitialJoints())
1342  joints << " -J " << pair.first << " " << pair.second;
1343  addTemplateString("[GAZEBO_INITIAL_JOINTS]", joints.str());
1344  }
1345 }
1346 
1347 // ******************************************************************************************
1348 // Insert a string pair into the template_strings_ datastructure
1349 // ******************************************************************************************
1350 bool ConfigurationFilesWidget::addTemplateString(const std::string& key, const std::string& value)
1351 {
1352  template_strings_.push_back(std::pair<std::string, std::string>(key, value));
1353 
1354  return true;
1355 }
1356 
1357 // ******************************************************************************************
1358 // Copy a template from location <template_path> to location <output_path> and replace package name
1359 // ******************************************************************************************
1360 bool ConfigurationFilesWidget::copyTemplate(const std::string& template_path, const std::string& output_path)
1361 {
1362  // Error check file
1363  if (!fs::is_regular_file(template_path))
1364  {
1365  ROS_ERROR_STREAM("Unable to find template file " << template_path);
1366  return false;
1367  }
1368 
1369  // Load file
1370  std::ifstream template_stream(template_path.c_str());
1371  if (!template_stream.good()) // File not found
1372  {
1373  ROS_ERROR_STREAM("Unable to load file " << template_path);
1374  return false;
1375  }
1376 
1377  // Load the file to a string using an efficient memory allocation technique
1378  std::string template_string;
1379  template_stream.seekg(0, std::ios::end);
1380  template_string.reserve(template_stream.tellg());
1381  template_stream.seekg(0, std::ios::beg);
1382  template_string.assign((std::istreambuf_iterator<char>(template_stream)), std::istreambuf_iterator<char>());
1383  template_stream.close();
1384 
1385  // Replace keywords in string ------------------------------------------------------------
1386  for (std::pair<std::string, std::string>& it : template_strings_)
1387  {
1388  boost::replace_all(template_string, it.first, it.second);
1389  }
1390 
1391  // Save string to new location -----------------------------------------------------------
1392  std::ofstream output_stream(output_path.c_str(), std::ios_base::trunc);
1393  if (!output_stream.good())
1394  {
1395  ROS_ERROR_STREAM("Unable to open file for writing " << output_path);
1396  return false;
1397  }
1398 
1399  output_stream << template_string.c_str();
1400  output_stream.close();
1401 
1402  return true; // file created successfully
1403 }
1404 
1405 // ******************************************************************************************
1406 // Create a folder
1407 // ******************************************************************************************
1408 bool ConfigurationFilesWidget::createFolder(const std::string& output_path)
1409 {
1410  if (!fs::is_directory(output_path))
1411  {
1412  if (!fs::create_directory(output_path))
1413  {
1414  QMessageBox::critical(this, "Error Generating Files",
1415  QString("Unable to create directory ").append(output_path.c_str()));
1416  return false;
1417  }
1418  }
1419  return true;
1420 }
1421 
1422 } // namespace moveit_setup_assistant
moveit_setup_assistant::GenerateFile::modified_
bool modified_
Definition: configuration_files_widget.h:67
ROS_ERROR_STREAM
#define ROS_ERROR_STREAM(args)
moveit_setup_assistant::GenerateFile::rel_path_
std::string rel_path_
Definition: configuration_files_widget.h:63
moveit_setup_assistant::GenerateFile
Definition: configuration_files_widget.h:57
ROS_DEBUG_STREAM
#define ROS_DEBUG_STREAM(args)
srdf::Model::VirtualJoint::parent_frame_
std::string parent_frame_
moveit_setup_assistant::CONFIG_PATH
static const std::string CONFIG_PATH
Definition: configuration_files_widget.cpp:100
ROS_WARN_STREAM
#define ROS_WARN_STREAM(args)
moveit_setup_assistant::ConfigurationFilesWidget::ConfigurationFilesWidget
ConfigurationFilesWidget(QWidget *parent, const MoveItConfigDataPtr &config_data)
Definition: configuration_files_widget.cpp:105
moveit_setup_assistant::GenerateFile::file_name_
std::string file_name_
Definition: configuration_files_widget.h:62
moveit_setup_assistant::GenerateFile::generate_
bool generate_
Definition: configuration_files_widget.h:66
generate
def generate(srv_path)
SetupScreenWidget
Definition: setup_screen_widget.h:44
ROS_INFO_STREAM
#define ROS_INFO_STREAM(args)
header_widget.h
moveit_setup_assistant
Definition: compute_default_collisions.h:46
moveit_setup_assistant::SETUP_ASSISTANT_FILE
static const std::string SETUP_ASSISTANT_FILE
Definition: configuration_files_widget.cpp:99
srdf::Model::VirtualJoint
append
ROSCPP_DECL std::string append(const std::string &left, const std::string &right)
srdf::Model::VirtualJoint::child_link_
std::string child_link_
index
unsigned int index
moveit_setup_assistant::GenerateFile::write_on_changes
unsigned long write_on_changes
Definition: configuration_files_widget.h:65
header
const std::string header
configuration_files_widget.h
group
group


moveit_setup_assistant
Author(s): Dave Coleman
autogenerated on Sat May 3 2025 02:28:04