simulation_widget.cpp
Go to the documentation of this file.
1 /*********************************************************************
2  * Software License Agreement (BSD License)
3  *
4  * Copyright (c) 2018, Mohamad Ayman.
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  * * The name of Mohamad Ayman may not be used to endorse or promote products derived
18  * from this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31  * POSSIBILITY OF SUCH DAMAGE.
32  *********************************************************************/
33 
34 /* Author: Mohamad Ayman */
35 
36 // SA
37 #include "simulation_widget.h"
38 #include "header_widget.h"
39 #include "../tools/xml_syntax_highlighter.h"
41 
42 // Qt
43 #include <QColor>
44 #include <QLabel>
45 #include <QMessageBox>
46 #include <QPushButton>
47 #include <QTextEdit>
48 #include <QVBoxLayout>
49 #include <QProcess>
50 
52 #include <moveit_msgs/DisplayRobotState.h>
53 
54 #include <regex>
55 
56 namespace moveit_setup_assistant
57 {
58 // ******************************************************************************************
59 // Constructor
60 // ******************************************************************************************
61 SimulationWidget::SimulationWidget(QWidget* parent, const MoveItConfigDataPtr& config_data)
62  : SetupScreenWidget(parent), config_data_(config_data)
63 {
64  // Basic widget container
65  QVBoxLayout* layout = new QVBoxLayout();
66  layout->setAlignment(Qt::AlignTop);
67 
68  // Top Header Area ------------------------------------------------
69 
70  HeaderWidget* header = new HeaderWidget(
71  "Gazebo Simulation",
72  QString("For use in the Gazebo physics simulation, the URDF needs to define inertial properties "
73  "for all links as well as control interfaces for all joints. "
74  "The required changes to your URDF are <b>highlighted below in "
75  "<font color=\"darkgreen\">green</font></b>.<br>"
76  "You can accept these suggestions and overwrite your existing URDF, or manually "
77  "adapt your URDF opening your preferred editor. "
78  "By default, a new file comprising those changes will be written to <tt>config/gazebo_%1.urdf</tt>")
79  .arg(config_data_->urdf_model_->getName().c_str())
80  .toStdString(),
81  this);
82  layout->addWidget(header);
83  layout->addSpacerItem(new QSpacerItem(1, 8, QSizePolicy::Fixed, QSizePolicy::Fixed));
84 
85  // Top Buttons --------------------------------------------------
86  QHBoxLayout* controls_layout = new QHBoxLayout();
87 
88  // Used to overwrite the original URDF
89  btn_overwrite_ = new QPushButton("Over&write original URDF", this);
90  btn_overwrite_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
91  connect(btn_overwrite_, SIGNAL(clicked()), this, SLOT(overwriteURDF()));
92  controls_layout->addWidget(btn_overwrite_);
93 
94  btn_open_ = new QPushButton("&Open original URDF", this);
95  btn_open_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
96  btn_open_->setToolTip("Open original URDF file in editor");
97  connect(btn_open_, SIGNAL(clicked()), this, SLOT(openURDF()));
98  controls_layout->addWidget(btn_open_);
99 
100  // Align buttons to the left
101  controls_layout->addItem(new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Fixed));
102 
103  // Add layout
104  layout->addLayout(controls_layout);
105 
106  // When there are no changes to be made
107  no_changes_label_ = new QLabel(this);
108  no_changes_label_->setText("URDF is ready for Gazebo. No changes required.");
109  no_changes_label_->setFont(QFont(QFont().defaultFamily(), 18));
110  no_changes_label_->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
111  no_changes_label_->setAlignment(Qt::AlignTop);
112  layout->addWidget(no_changes_label_);
113 
114  // URDF text
115  simulation_text_ = new QTextEdit(this);
116  simulation_text_->setLineWrapMode(QTextEdit::NoWrap);
117  connect(simulation_text_, &QTextEdit::textChanged, this, [this]() { setDirty(); });
118  layout->addWidget(simulation_text_);
119  // Configure highlighter
120  auto highlighter = new XmlSyntaxHighlighter(simulation_text_->document());
121  QTextCharFormat format;
122  format.setForeground(Qt::darkGreen);
123  highlighter->addTag("inertial", format);
124  highlighter->addTag("transmission", format);
125  highlighter->addTag("gazebo", format);
126 
127  // Copy URDF link, hidden initially
128  copy_urdf_ = new QLabel(this);
129  copy_urdf_->setText("<a href='contract'>Copy to Clipboard</a>");
130  connect(copy_urdf_, &QLabel::linkActivated, this, &SimulationWidget::copyURDF);
131  layout->addWidget(copy_urdf_);
132 
133  // Finish Layout --------------------------------------------------
134  this->setLayout(layout);
135 }
136 
137 void SimulationWidget::setDirty(bool dirty)
138 {
139  if (dirty)
140  config_data_->changes |= MoveItConfigData::SIMULATION;
141  else
142  config_data_->changes &= ~MoveItConfigData::SIMULATION;
143  btn_overwrite_->setEnabled(dirty && !config_data_->urdf_from_xacro_);
144 }
145 
146 void SimulationWidget::focusGiven()
147 {
148  if (!simulation_text_->document()->isEmpty())
149  return; // don't change existing content
150 
151  simulation_text_->setVisible(true);
152  std::string text = generateGazeboCompatibleURDF();
153  config_data_->gazebo_urdf_string_ = text;
154 
155  simulation_text_->document()->setPlainText(QString::fromStdString(text));
156 
157  // Add generated Gazebo URDF to config file if not empty
158  bool have_changes = !text.empty();
159 
160  // GUI elements are visible only if there are URDF changes to display/edit
161  simulation_text_->setVisible(have_changes);
162  btn_overwrite_->setVisible(have_changes);
163  btn_open_->setVisible(have_changes);
164  copy_urdf_->setVisible(have_changes);
165  no_changes_label_->setVisible(!have_changes);
166 
167  // Explain why overwrite button is disabled
168  QString tooltip;
169  if (config_data_->urdf_from_xacro_)
170  tooltip = tr("Cannot overwrite original, <i>xacro-based</i> URDF");
171  else
172  tooltip = tr("Overwrite URDF in original location:<br><tt>%1</tt>").arg(config_data_->urdf_path_.c_str());
173  btn_overwrite_->setToolTip(tooltip);
174 
175  setDirty(have_changes);
176 }
177 
178 bool SimulationWidget::focusLost()
179 {
180  if (!(config_data_->changes & MoveItConfigData::SIMULATION))
181  return true; // saving is disabled anyway
182 
183  // validate XML
184  TiXmlDocument doc;
185  auto urdf = simulation_text_->document()->toPlainText().toStdString();
186  doc.Parse(urdf.c_str(), nullptr, TIXML_ENCODING_UTF8);
187  if (!urdf.empty() && doc.Error())
188  {
189  QTextCursor cursor = simulation_text_->textCursor();
190  cursor.movePosition(QTextCursor::Start);
191  cursor.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, doc.ErrorRow());
192  cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, doc.ErrorCol());
193  simulation_text_->setTextCursor(cursor);
194  QMessageBox::warning(this, tr("Gazebo URDF"), tr("Error parsing XML:\n").append(doc.ErrorDesc()));
195  simulation_text_->setFocus(Qt::OtherFocusReason);
196  return false; // reject switching
197  }
198  else
199  config_data_->gazebo_urdf_string_ = std::move(urdf);
200  return true;
201 }
202 
203 // ******************************************************************************************
204 // Called when save URDF button is clicked
205 // ******************************************************************************************
206 void SimulationWidget::overwriteURDF()
207 {
208  if (!focusLost()) // validate XML
209  return;
210 
211  if (!config_data_->outputGazeboURDFFile(config_data_->urdf_path_))
212  QMessageBox::warning(this, "Gazebo URDF", tr("Failed to save to ").append(config_data_->urdf_path_.c_str()));
213  else
214  {
215  // Remove Gazebo URDF file from list of to-be-written config files
216  setDirty(false);
217  config_data_->gazebo_urdf_string_.clear();
218  }
219 }
220 
221 void SimulationWidget::openURDF()
222 {
223  QString editor = qgetenv("EDITOR");
224  if (editor.isEmpty())
225  editor = "xdg-open";
226  QStringList args{ QString::fromStdString(config_data_->urdf_path_) };
227  if (!QProcess::startDetached(editor, args))
228  QMessageBox::warning(this, "URDF Editor", tr("Failed to open editor: <pre>%1</pre>").arg(editor));
229 }
230 
231 // ******************************************************************************************
232 // Called the copy to clipboard button is clicked
233 // ******************************************************************************************
234 void SimulationWidget::copyURDF()
235 {
236  simulation_text_->selectAll();
237  simulation_text_->copy();
238 }
239 
240 // Generate a Gazebo-compatible robot URDF
241 std::string SimulationWidget::generateGazeboCompatibleURDF() const
242 {
243  TiXmlDocument doc;
244  doc.Parse(config_data_->urdf_string_.c_str(), nullptr, TIXML_ENCODING_UTF8);
245  auto root = doc.RootElement();
246 
247  // Normalize original urdf_string_
248  TiXmlPrinter orig_urdf;
249  doc.Accept(&orig_urdf);
250 
251  // Map existing SimpleTransmission elements to their joint name
252  std::map<std::string, TiXmlElement*> transmission_elements;
253  for (TiXmlElement* element = root->FirstChildElement("transmission"); element != nullptr;
254  element = element->NextSiblingElement(element->Value()))
255  {
256  auto type_tag = element->FirstChildElement("type");
257  auto joint_tag = element->FirstChildElement("joint");
258  if (!type_tag || !type_tag->GetText() || !joint_tag || !joint_tag->Attribute("name"))
259  continue; // ignore invalid tags
260  if (std::string(type_tag->GetText()) == "transmission_interface/SimpleTransmission")
261  transmission_elements[element->FirstChildElement("joint")->Attribute("name")] = element;
262  }
263 
264  // Loop through Link and Joint elements and add Gazebo tags if not present
265  for (TiXmlElement* element = root->FirstChildElement(); element != nullptr; element = element->NextSiblingElement())
266  {
267  const std::string tag_name(element->Value());
268  if (tag_name == "link" && element->FirstChildElement("collision"))
269  {
270  TiXmlElement* inertial = uniqueInsert(*element, "inertial");
271  uniqueInsert(*inertial, "mass", { { "value", "0.1" } });
272  uniqueInsert(*inertial, "origin", { { "xyz", "0 0 0" }, { "rpy", "0 0 0" } });
273  uniqueInsert(*inertial, "inertia",
274  { { "ixx", "0.03" },
275  { "iyy", "0.03" },
276  { "izz", "0.03" },
277  { "ixy", "0.0" },
278  { "ixz", "0.0" },
279  { "iyz", "0.0" } });
280  }
281  else if (tag_name == "joint")
282  {
283  const char* joint_type = element->Attribute("type");
284  const char* joint_name = element->Attribute("name");
285  if (!joint_type || !joint_name || strcmp(joint_type, "fixed") == 0)
286  continue; // skip invalid or fixed joints
287 
288  // find existing or create new transmission element for this joint
289  TiXmlElement* transmission;
290  auto it = transmission_elements.find(joint_name);
291  if (it != transmission_elements.end())
292  transmission = it->second;
293  else
294  {
295  transmission = root->InsertEndChild(TiXmlElement("transmission"))->ToElement();
296  transmission->SetAttribute("name", std::string("trans_") + joint_name);
297  }
298 
299  uniqueInsert(*transmission, "type", {}, "transmission_interface/SimpleTransmission");
300 
301  std::string hw_interface = config_data_->getJointHardwareInterface(joint_name);
302  auto* joint = uniqueInsert(*transmission, "joint", { { "name", joint_name } });
303  uniqueInsert(*joint, "hardwareInterface", {}, hw_interface.c_str());
304 
305  auto actuator_name = joint_name + std::string("_motor");
306  auto* actuator = uniqueInsert(*transmission, "actuator", { { "name", actuator_name.c_str() } });
307  uniqueInsert(*actuator, "hardwareInterface", {}, hw_interface.c_str());
308  uniqueInsert(*actuator, "mechanicalReduction", {}, "1");
309  }
310  }
311 
312  // Add gazebo_ros_control plugin which reads the transmission tags
313  TiXmlElement* gazebo = uniqueInsert(*root, "gazebo");
314  TiXmlElement* plugin = uniqueInsert(
315  *gazebo, "plugin", { { "name", "gazebo_ros_control", true }, { "filename", "libgazebo_ros_control.so", true } });
316  uniqueInsert(*plugin, "robotNamespace", {}, "/");
317 
318  // generate new URDF
319  TiXmlPrinter new_urdf;
320  doc.Accept(&new_urdf);
321  // and return it when there are changes
322  return orig_urdf.Str() == new_urdf.Str() ? std::string() : new_urdf.Str();
323 }
324 
325 } // namespace moveit_setup_assistant
MoveItConfigData
Definition: moveit_config_data_test.cpp:50
moveit_setup_assistant::SimulationWidget::SimulationWidget
SimulationWidget(QWidget *parent, const MoveItConfigDataPtr &config_data)
Definition: simulation_widget.cpp:92
xml_manipulation.h
moveit_setup_assistant::uniqueInsert
TiXmlElement * uniqueInsert(TiXmlElement &element, const char *tag, const std::vector< Attribute > &attributes={}, const char *text=nullptr)
Definition: xml_manipulation.cpp:91
SetupScreenWidget
Definition: setup_screen_widget.h:44
simulation_widget.h
header_widget.h
moveit_setup_assistant
Definition: compute_default_collisions.h:46
text
text
append
ROSCPP_DECL std::string append(const std::string &left, const std::string &right)
urdf
XmlSyntaxHighlighter
Definition: xml_syntax_highlighter.h:44
conversions.h
args
root
root
header
const std::string header


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