urdf_parser.cpp
Go to the documentation of this file.
1 
29 #include <fstream>
30 #include <stdexcept>
31 
32 #include <boost/filesystem.hpp>
33 #include <string_view>
34 #include <tesseract_common/utils.h>
35 #include <tinyxml2.h>
37 
42 
43 #include <tesseract_urdf/joint.h>
44 #include <tesseract_urdf/link.h>
47 #include <tesseract_urdf/utils.h>
48 
49 static constexpr std::string_view ROBOT_ELEMENT_NAME = "robot";
50 
51 namespace tesseract_urdf
52 {
53 std::unique_ptr<tesseract_scene_graph::SceneGraph> parseURDFString(const std::string& urdf_xml_string,
54  const tesseract_common::ResourceLocator& locator)
55 {
56  tinyxml2::XMLDocument xml_doc;
57  if (xml_doc.Parse(urdf_xml_string.c_str()) != tinyxml2::XML_SUCCESS)
58  std::throw_with_nested(std::runtime_error("URDF: Failed to parse urdf string!"));
59 
60  tinyxml2::XMLElement* robot = xml_doc.FirstChildElement(ROBOT_ELEMENT_NAME.data());
61  if (robot == nullptr)
62  std::throw_with_nested(std::runtime_error("URDF: Missing element 'robot'!"));
63 
64  std::string robot_name;
65  if (tesseract_common::QueryStringAttribute(robot, "name", robot_name) != tinyxml2::XML_SUCCESS)
66  std::throw_with_nested(std::runtime_error("URDF: Missing or failed parsing attribute 'name'!"));
67 
68  int urdf_version = 1;
69  auto version_status = robot->QueryIntAttribute("version", &urdf_version);
70  if (version_status != tinyxml2::XML_NO_ATTRIBUTE && version_status != tinyxml2::XML_SUCCESS)
71  std::throw_with_nested(
72  std::runtime_error("URDF: Failed parsing attribute 'version' for robot '" + robot_name + "'!"));
73 
74  if (urdf_version != 1)
75  std::throw_with_nested(std::runtime_error("URDF: 'version' for robot '" + robot_name + "' is set to `" +
76  std::to_string(urdf_version) +
77  "', this is not supported, please set it to 1.0."));
78 
79  // Check for global attribute for converting meshes to convex hulls
80  bool make_convex = false;
81  auto make_convex_status = robot->QueryBoolAttribute("tesseract:make_convex", &make_convex);
82  switch (make_convex_status)
83  {
84  case tinyxml2::XML_SUCCESS:
85  break;
86  case tinyxml2::XML_NO_ATTRIBUTE:
87  {
88  const std::string message = "URDF: missing boolean attribute 'tesseract:make_convex'. This attribute indicates "
89  "whether Tesseract should globally convert all collision mesh geometries into convex "
90  "hulls. Previous versions of Tesseract performed this conversion automatically "
91  "(i.e., 'tesseract:make_convex=\"true\"'. If you want to perform collision checking "
92  "with detailed meshes instead of convex hulls, set "
93  "'tesseract:make_convex=\"false\"'. This global attribute can be overriden on a "
94  "per-mesh basis by specifying the 'tesseract:make_convex' attribute in the 'mesh' "
95  "element (e.g., <mesh filename=\"...\" tesseract:make_convex=\"true/false\"> .";
96  std::throw_with_nested(std::runtime_error(message));
97  }
98  default:
99  std::throw_with_nested(std::runtime_error("URDF: Failed to parse boolean attribute 'tesseract:make_convex' for "
100  "robot '" +
101  robot_name + "'"));
102  }
103 
104  auto sg = std::make_unique<tesseract_scene_graph::SceneGraph>();
105  sg->setName(robot_name);
106 
107  std::unordered_map<std::string, tesseract_scene_graph::Material::Ptr> available_materials;
108  for (tinyxml2::XMLElement* material = robot->FirstChildElement(MATERIAL_ELEMENT_NAME.data()); material != nullptr;
109  material = material->NextSiblingElement(MATERIAL_ELEMENT_NAME.data()))
110  {
112  std::unordered_map<std::string, tesseract_scene_graph::Material::Ptr> empty_material;
113  try
114  {
115  m = parseMaterial(material, empty_material, true);
116  }
117  catch (...)
118  {
119  std::throw_with_nested(
120  std::runtime_error("URDF: Error parsing urdf global 'material' element for robot '" + robot_name + "'!"));
121  }
122 
123  available_materials[m->getName()] = m;
124  }
125 
126  for (tinyxml2::XMLElement* link = robot->FirstChildElement(LINK_ELEMENT_NAME.data()); link != nullptr;
127  link = link->NextSiblingElement(LINK_ELEMENT_NAME.data()))
128  {
130  try
131  {
132  l = parseLink(link, locator, make_convex, available_materials);
133  }
134  catch (...)
135  {
136  std::throw_with_nested(std::runtime_error("URDF: Error parsing 'link' element for robot '" + robot_name + "'!"));
137  }
138 
139  // Check if link name is unique
140  if (sg->getLink(l->getName()) != nullptr)
141  std::throw_with_nested(std::runtime_error("URDF: Error link name '" + l->getName() +
142  "' is not unique for robot '" + robot_name + "'!"));
143 
144  // Add link to scene graph
145  if (!sg->addLink(*l))
146  std::throw_with_nested(std::runtime_error("URDF: Error adding link '" + l->getName() +
147  "' to scene graph for robot '" + robot_name + "'!"));
148  }
149 
150  if (sg->getLinks().empty())
151  std::throw_with_nested(std::runtime_error("URDF: Error no links were found for robot '" + robot_name + "'!"));
152 
153  for (tinyxml2::XMLElement* joint = robot->FirstChildElement(JOINT_ELEMENT_NAME.data()); joint != nullptr;
154  joint = joint->NextSiblingElement(JOINT_ELEMENT_NAME.data()))
155  {
157  try
158  {
159  j = parseJoint(joint);
160  }
161  catch (...)
162  {
163  std::throw_with_nested(std::runtime_error("URDF: Error parsing 'joint' element for robot '" + robot_name + "'!"));
164  }
165 
166  // Check if joint name is unique
167  if (sg->getJoint(j->getName()) != nullptr)
168  std::throw_with_nested(std::runtime_error("URDF: Error joint name '" + j->getName() +
169  "' is not unique for robot '" + robot_name + "'!"));
170 
171  // Add joint to scene graph
172  if (!sg->addJoint(*j))
173  std::throw_with_nested(std::runtime_error("URDF: Error adding joint '" + j->getName() +
174  "' to scene graph for robot '" + robot_name + "'!"));
175  }
176 
177  if (sg->getJoints().empty())
178  std::throw_with_nested(std::runtime_error("URDF: Error no joints were found for robot '" + robot_name + "'!"));
179 
180  if (!sg->isTree())
181  {
182  if (!sg->isAcyclic())
183  std::throw_with_nested(std::runtime_error("URDF: Error, is not a tree structure and contains cycles for robot '" +
184  robot_name + "'!"));
185 
186  std::throw_with_nested(std::runtime_error("URDF: Error, is not a tree structure for robot '" + robot_name + "'!"));
187  }
188 
189  // Find root link
190  for (const auto& l : sg->getLinks())
191  if (sg->getInboundJoints(l->getName()).empty())
192  sg->setRoot(l->getName());
193 
194  return sg;
195 }
196 
197 std::unique_ptr<tesseract_scene_graph::SceneGraph> parseURDFFile(const std::string& path,
198  const tesseract_common::ResourceLocator& locator)
199 {
200  std::ifstream ifs(path);
201  if (!ifs)
202  std::throw_with_nested(std::runtime_error("URDF: Error opening file '" + path + "'!"));
203 
204  std::string urdf_xml_string((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
206  try
207  {
208  sg = parseURDFString(urdf_xml_string, locator);
209  }
210  catch (...)
211  {
212  std::throw_with_nested(std::runtime_error("URDF: Error parsing file '" + path + "'!"));
213  }
214 
215  return sg;
216 }
217 
218 void writeURDFFile(const std::shared_ptr<const tesseract_scene_graph::SceneGraph>& sg,
219  const std::string& package_path,
220  const std::string& urdf_name)
221 {
222  // Check for null input
223  if (sg == nullptr)
224  std::throw_with_nested(std::runtime_error("Scene Graph is nullptr and cannot be converted to URDF"));
225 
226  // If the directory does not exist, make it
227  if (package_path.empty())
228  std::throw_with_nested(std::runtime_error("Package path cannot be empty"));
229  std::filesystem::create_directory(std::filesystem::path(package_path));
230 
231  // // If the collision and visual subdirectories do not exist, make them
232  // std::filesystem::create_directory(std::filesystem::path(directory + "collision"));
233  // std::filesystem::create_directory(std::filesystem::path(directory + "visual"));
234 
235  // Create XML Document
236  tinyxml2::XMLDocument doc;
237 
238  // Add XML Declaration
239  tinyxml2::XMLDeclaration* xml_declaration = doc.NewDeclaration(R"(xml version="1.0")");
240  doc.InsertFirstChild(xml_declaration);
241 
242  // Assign Robot Name
243  tinyxml2::XMLElement* xml_robot = doc.NewElement(ROBOT_ELEMENT_NAME.data());
244  xml_robot->SetAttribute("name", sg->getName().c_str());
245  xml_robot->SetAttribute("tesseract:make_convex", false);
246  // version?
247  doc.InsertEndChild(xml_robot);
248 
249  // Materials were not saved anywhere at load
250 
251  // Get Links
252  std::vector<std::string> link_names;
253  for (const tesseract_scene_graph::Link::ConstPtr& l : sg->getLinks())
254  {
255  if (l == nullptr)
256  std::throw_with_nested(std::runtime_error("Link is nullptr, cannot get name"));
257  link_names.push_back(l->getName());
258  }
259 
260  // Sort the link names using a lamda-defined comparator function.
261  // The lamda takes in two strings, applies the default < operator, and returns a bool.
262  std::sort(
263  link_names.begin(), link_names.end(), [](const std::string& a, const std::string& b) -> bool { return a < b; });
264 
265  // Iterate through the sorted names and write the corresponding links to XML
266  for (const std::string& s : link_names)
267  {
268  const tesseract_scene_graph::Link::ConstPtr& l = sg->getLink(s);
269  try
270  {
271  tinyxml2::XMLElement* xml_link = writeLink(l, doc, package_path);
272  xml_robot->InsertEndChild(xml_link);
273  }
274  catch (...)
275  {
276  std::throw_with_nested(std::runtime_error("Could not write out urdf link"));
277  }
278  }
279 
280  // Get joints
281  std::vector<std::string> joint_names;
282  for (const tesseract_scene_graph::Joint::ConstPtr& j : sg->getJoints())
283  {
284  if (j == nullptr)
285  std::throw_with_nested(std::runtime_error("Joint is nullptr, cannot get name!"));
286  joint_names.push_back(j->getName());
287  }
288 
289  // Sort the joint names using a lamda-defined comparator function.
290  // The lamda takes in two strings, applies the default < operator, and returns a bool.
291  std::sort(
292  joint_names.begin(), joint_names.end(), [](const std::string& a, const std::string& b) -> bool { return a < b; });
293 
294  // Iterate through the sorted joint names and write the corresponding joints to xml
295  for (const std::string& s : joint_names)
296  {
297  const tesseract_scene_graph::Joint::ConstPtr& j = sg->getJoint(s);
298  try
299  {
300  tinyxml2::XMLElement* xml_joint = writeJoint(j, doc);
301  xml_robot->InsertEndChild(xml_joint);
302  }
303  catch (...)
304  {
305  std::throw_with_nested(std::runtime_error("Could not write out urdf joint"));
306  }
307  }
308 
309  // Check for acyclic?
310 
311  // Prepare the urdf directory
312  std::filesystem::create_directory(std::filesystem::path(trailingSlash(package_path) + "urdf/"));
313 
314  // Write the URDF XML to a file
315  std::string full_filepath;
316  if (!urdf_name.empty())
317  full_filepath = trailingSlash(package_path) + "urdf/" + noLeadingSlash(urdf_name) + ".urdf";
318  else
319  full_filepath = trailingSlash(package_path) + "urdf/" + sg->getName() + ".urdf";
320  doc.SaveFile(full_filepath.c_str());
321 }
322 
323 } // namespace tesseract_urdf
graph.h
tesseract_urdf::JOINT_ELEMENT_NAME
static constexpr std::string_view JOINT_ELEMENT_NAME
Definition: joint.h:45
utils.h
resource_locator.h
tesseract_urdf::MATERIAL_ELEMENT_NAME
static constexpr std::string_view MATERIAL_ELEMENT_NAME
Definition: material.h:46
tesseract_urdf::trailingSlash
std::string trailingSlash(const std::string &path)
Definition: utils.cpp:20
joint.h
ROBOT_ELEMENT_NAME
TESSERACT_COMMON_IGNORE_WARNINGS_PUSH static constexpr TESSERACT_COMMON_IGNORE_WARNINGS_POP std::string_view ROBOT_ELEMENT_NAME
Definition: urdf_parser.cpp:49
TESSERACT_COMMON_IGNORE_WARNINGS_PUSH
#define TESSERACT_COMMON_IGNORE_WARNINGS_PUSH
tesseract_scene_graph::Joint::ConstPtr
std::shared_ptr< const Joint > ConstPtr
tesseract_scene_graph::SceneGraph::UPtr
std::unique_ptr< SceneGraph > UPtr
joint.h
Parse joint from xml string.
utils.h
tesseract_urdf::writeLink
tinyxml2::XMLElement * writeLink(const std::shared_ptr< const tesseract_scene_graph::Link > &link, tinyxml2::XMLDocument &doc, const std::string &package_path)
writeLink Write a link to URDF XML
Definition: link.cpp:111
tesseract_urdf::parseURDFFile
std::unique_ptr< tesseract_scene_graph::SceneGraph > parseURDFFile(const std::string &path, const tesseract_common::ResourceLocator &locator)
Parse a URDF file into a Tesseract Scene Graph.
Definition: urdf_parser.cpp:197
material.h
Parse material from xml string.
urdf_parser.h
A urdf parser for tesseract.
tesseract_common::ResourceLocator
tesseract_common::QueryStringAttribute
int QueryStringAttribute(const tinyxml2::XMLElement *xml_element, const char *name, std::string &value)
TESSERACT_COMMON_IGNORE_WARNINGS_POP
tesseract_urdf::parseLink
std::shared_ptr< tesseract_scene_graph::Link > parseLink(const tinyxml2::XMLElement *xml_element, const tesseract_common::ResourceLocator &locator, bool make_convex_meshes, std::unordered_map< std::string, std::shared_ptr< tesseract_scene_graph::Material >> &available_materials)
Parse xml element link.
tesseract_urdf::writeURDFFile
void writeURDFFile(const std::shared_ptr< const tesseract_scene_graph::SceneGraph > &sg, const std::string &package_path, const std::string &urdf_name="")
Definition: urdf_parser.cpp:218
tesseract_urdf::parseJoint
std::shared_ptr< tesseract_scene_graph::Joint > parseJoint(const tinyxml2::XMLElement *xml_element)
Parse xml element joint.
Definition: joint.cpp:48
tesseract_scene_graph::Joint::Ptr
std::shared_ptr< Joint > Ptr
tesseract_scene_graph::Material::Ptr
std::shared_ptr< Material > Ptr
tesseract_urdf::writeJoint
tinyxml2::XMLElement * writeJoint(const std::shared_ptr< const tesseract_scene_graph::Joint > &joint, tinyxml2::XMLDocument &doc)
Definition: joint.cpp:233
tesseract_urdf::LINK_ELEMENT_NAME
static constexpr std::string_view LINK_ELEMENT_NAME
Definition: link.h:47
macros.h
tesseract_urdf
Definition: box.h:43
tesseract_urdf::noLeadingSlash
std::string noLeadingSlash(const std::string &filename)
Definition: utils.cpp:45
tesseract_urdf::parseURDFString
std::unique_ptr< tesseract_scene_graph::SceneGraph > parseURDFString(const std::string &urdf_xml_string, const tesseract_common::ResourceLocator &locator)
Parse a URDF string into a Tesseract Scene Graph.
Definition: urdf_parser.cpp:53
tesseract_urdf::parseMaterial
std::shared_ptr< tesseract_scene_graph::Material > parseMaterial(const tinyxml2::XMLElement *xml_element, std::unordered_map< std::string, std::shared_ptr< tesseract_scene_graph::Material >> &available_materials, bool allow_anonymous)
Parse xml element material.


tesseract_urdf
Author(s): Levi Armstrong
autogenerated on Thu Apr 24 2025 03:10:44