tinyrobotics
Loading...
Searching...
No Matches
parser.hpp
Go to the documentation of this file.
1#ifndef TR_URDFPARSER_HPP
2#define TR_URDFPARSER_HPP
3
4#include <tinyxml2.h>
5
6#include "math.hpp"
7#include "model.hpp"
8
12namespace tinyrobotics {
13
19 template <typename Scalar>
20 Eigen::Matrix<Scalar, 3, 1> vec_from_string(const std::string& vector_str) {
21 Eigen::Matrix<Scalar, 3, 1> vec;
22 std::vector<Scalar> values;
23 std::istringstream ss(vector_str);
24 std::string vector_element;
25 while (ss >> vector_element) {
26 try {
27 values.push_back(std::stod(vector_element));
28 }
29 catch (std::invalid_argument& e) {
30 throw std::runtime_error("Error not able to parse component (" + vector_element
31 + ") to a Scalar (while parsing a vector value)");
32 }
33 }
34
35 if (values.size() != 3) {
36 std::ostringstream error_msg;
37 error_msg << "Parser found " << values.size() << " elements but 3 expected while parsing vector ["
38 << vector_str << "]";
39 throw std::runtime_error(error_msg.str());
40 }
41
42 vec.x() = values[0];
43 vec.y() = values[1];
44 vec.z() = values[2];
45
46 return vec;
47 }
48
54 template <typename Scalar>
55 Eigen::Matrix<Scalar, 3, 3> rot_from_string(const std::string& rotation_str) {
56 Eigen::Matrix<Scalar, Eigen::Dynamic, 1> rpy = vec_from_string<Scalar>(rotation_str);
57 Eigen::Matrix<Scalar, 3, 3> R =
58 Eigen::AngleAxis<Scalar>(rpy.z(), Eigen::Matrix<Scalar, 3, 1>::UnitZ())
59 * Eigen::AngleAxis<Scalar>(rpy.y(), Eigen::Matrix<Scalar, 3, 1>::UnitY())
60 * Eigen::AngleAxis<Scalar>(rpy.x(), Eigen::Matrix<Scalar, 3, 1>::UnitX()).toRotationMatrix();
61 return R;
62 }
63
69 template <typename Scalar>
70 Eigen::Transform<Scalar, 3, Eigen::Isometry> transform_from_xml(tinyxml2::XMLElement* xml) {
71 Eigen::Transform<Scalar, 3, Eigen::Isometry> t;
72 t.setIdentity();
73 if (xml) {
74 const char* rpy_str = xml->Attribute("rpy");
75 if (rpy_str != nullptr) {
76 t.linear() = rot_from_string<Scalar>(rpy_str);
77 }
78
79 const char* xyz_str = xml->Attribute("xyz");
80 if (xyz_str != nullptr) {
81 t.translation() = vec_from_string<Scalar>(xyz_str);
82 }
83 }
84 return t;
85 }
86
92 template <typename Scalar>
93 Eigen::Matrix<Scalar, 6, 6> spatial_transform_from_xml(tinyxml2::XMLElement* xml) {
94 Eigen::Matrix<Scalar, 6, 6> X = Eigen::Matrix<Scalar, 6, 6>::Identity();
95 Eigen::Matrix<Scalar, 6, 6> R = Eigen::Matrix<Scalar, 6, 6>::Identity();
96 Eigen::Matrix<Scalar, 6, 6> T = Eigen::Matrix<Scalar, 6, 6>::Identity();
97 if (xml) {
98 const char* rpy_str = xml->Attribute("rpy");
99 if (rpy_str != nullptr) {
100 Eigen::Matrix<Scalar, 3, 1> rpy = vec_from_string<Scalar>(rpy_str);
101 R = rpy_to_spatial(rpy);
102 }
103
104 const char* xyz_str = xml->Attribute("xyz");
105 if (xyz_str != nullptr) {
106 Eigen::Matrix<Scalar, 3, 1> translation = vec_from_string<Scalar>(xyz_str);
107 T = translation_to_spatial(translation);
108 }
109 }
110 X = R * T;
111 return X;
112 }
113
119 inline const char* get_parent_link_name(tinyxml2::XMLElement* c) {
120 tinyxml2::XMLElement* e = c->Parent()->ToElement();
121 while (e->Parent() != nullptr) {
122 if (std::strcmp(e->Value(), "link") == 0) {
123 break;
124 }
125 e = e->Parent()->ToElement();
126 }
127 return e->Attribute("name");
128 }
129
135 template <typename Scalar>
136 Link<Scalar> link_from_xml(tinyxml2::XMLElement* xml) {
137 // Create the link object
138 Link<Scalar> link = Link<Scalar>();
139
140 const char* name_char = xml->Attribute("name");
141 if (name_char != nullptr) {
142 link.name = std::string(name_char);
143 }
144 else {
145 throw std::runtime_error("Error! Link without a name attribute detected!");
146 }
147
148 tinyxml2::XMLElement* i = xml->FirstChildElement("inertial");
149 if (i != nullptr) {
150 // Add the center of mass to the link
151 tinyxml2::XMLElement* o = i->FirstChildElement("origin");
152 if (o != nullptr) {
153 link.center_of_mass = transform_from_xml<Scalar>(o);
154 }
155
156 // Add the mass to the link
157 tinyxml2::XMLElement* mass_xml = i->FirstChildElement("mass");
158 if (mass_xml != nullptr) {
159 if (mass_xml->Attribute("value") != nullptr) {
160 try {
161 link.mass = std::stod(mass_xml->Attribute("value"));
162 }
163 catch (std::invalid_argument& e) {
164 throw std::runtime_error("Error while parsing link '" + std::string(get_parent_link_name(i))
165 + "': inertial mass [" + mass_xml->Attribute("value")
166 + "] is not a valid double: " + e.what() + "!");
167 }
168 }
169 else {
170 throw std::runtime_error("Error while parsing link '" + std::string(get_parent_link_name(i))
171 + "' <mass> element must have a value attribute!");
172 }
173 }
174 else {
175 throw std::runtime_error("Error while parsing link '" + std::string(get_parent_link_name(i))
176 + "' inertial element must have a <mass> element!");
177 }
178 }
179
180 // Add the inertia to the link
181 tinyxml2::XMLElement* inertia_xml = i->FirstChildElement("inertia");
182 if (inertia_xml != nullptr) {
183 Eigen::Matrix<Scalar, 3, 3> inertia;
184 if (inertia_xml->Attribute("ixx") && inertia_xml->Attribute("ixy") && inertia_xml->Attribute("ixz")
185 && inertia_xml->Attribute("iyy") && inertia_xml->Attribute("iyz") && inertia_xml->Attribute("izz")) {
186 try {
187 inertia(0, 0) = std::stod(inertia_xml->Attribute("ixx"));
188 inertia(0, 1) = std::stod(inertia_xml->Attribute("ixy"));
189 inertia(0, 2) = std::stod(inertia_xml->Attribute("ixz"));
190 inertia(1, 0) = std::stod(inertia_xml->Attribute("ixy"));
191 inertia(1, 1) = std::stod(inertia_xml->Attribute("iyy"));
192 inertia(1, 2) = std::stod(inertia_xml->Attribute("iyz"));
193 inertia(2, 0) = std::stod(inertia_xml->Attribute("ixz"));
194 inertia(2, 1) = std::stod(inertia_xml->Attribute("iyz"));
195 inertia(2, 2) = std::stod(inertia_xml->Attribute("izz"));
196 link.inertia = inertia;
197 }
198 catch (std::invalid_argument& e) {
199 std::ostringstream error_msg;
200 error_msg << "Error while parsing link '" << get_parent_link_name(i)
201 << "Inertial: one of the inertia elements is not a valid double:"
202 << " ixx [" << inertia_xml->Attribute("ixx") << "]"
203 << " ixy [" << inertia_xml->Attribute("ixy") << "]"
204 << " ixz [" << inertia_xml->Attribute("ixz") << "]"
205 << " iyy [" << inertia_xml->Attribute("iyy") << "]"
206 << " iyz [" << inertia_xml->Attribute("iyz") << "]"
207 << " izz [" << inertia_xml->Attribute("izz") << "]\n\n"
208 << e.what();
209 throw std::runtime_error(error_msg.str());
210 }
211 }
212 else {
213 throw std::runtime_error("Error while parsing link '" + std::string(get_parent_link_name(i))
214 + "' <inertia> element must have ixx,ixy,ixz,iyy,iyz,izz attributes!");
215 }
216 }
217 else {
218 throw std::runtime_error("Error while parsing link '" + std::string(get_parent_link_name(i))
219 + "' inertial element must have a <inertia> element!");
220 }
221
222 // Add the spatial inertia to the link
223 link.I = inertia_to_spatial<Scalar>(link.mass, link.center_of_mass.translation(), link.inertia);
224
225 return link;
226 }
227
233 template <typename Scalar>
234 Joint<Scalar> joint_from_xml(tinyxml2::XMLElement* xml) {
236
237 const char* name = xml->Attribute("name");
238 if (name != nullptr) {
239 joint.name = std::string(name);
240 }
241 else {
242 std::ostringstream error_msg;
243 error_msg << "Error while parsing model: unnamed joint found!";
244 throw std::runtime_error(error_msg.str());
245 }
246
247 tinyxml2::XMLElement* origin_xml = xml->FirstChildElement("origin");
248 if (origin_xml != nullptr) {
249 joint.parent_transform = transform_from_xml<Scalar>(origin_xml);
250 }
251
252 tinyxml2::XMLElement* parent_xml = xml->FirstChildElement("parent");
253 if (parent_xml != nullptr) {
254 const char* pname = parent_xml->Attribute("link");
255 if (pname != nullptr) {
256 joint.parent_link_name = std::string(pname);
257 }
258 // if no parent link name specified. this might be the root node
259 }
260
261 tinyxml2::XMLElement* child_xml = xml->FirstChildElement("child");
262 if (child_xml) {
263 const char* pname = child_xml->Attribute("link");
264 if (pname != nullptr) {
265 joint.child_link_name = std::string(pname);
266 }
267 }
268
269 const char* type_char = xml->Attribute("type");
270 if (type_char == nullptr) {
271 std::ostringstream error_msg;
272 error_msg << "Error! Joint " << joint.name << " has no type, check to see if it's a reference.";
273 throw std::runtime_error(error_msg.str());
274 }
275
276 std::string type_str = type_char;
277 if (type_str == "planar") {
278 joint.type = JointType::PLANAR;
279 }
280 else if (type_str == "floating") {
281 joint.type = JointType::FLOATING;
282 }
283 else if (type_str == "revolute") {
284 joint.type = JointType::REVOLUTE;
285 }
286 else if (type_str == "continuous") {
287 joint.type = JointType::CONTINUOUS;
288 }
289 else if (type_str == "prismatic") {
290 joint.type = JointType::PRISMATIC;
291 }
292 else if (type_str == "fixed") {
293 joint.type = JointType::FIXED;
294 }
295 else {
296 std::ostringstream error_msg;
297 error_msg << "Error! Joint '" << joint.name << "' has unknown type (" << type_str << ")!";
298 throw std::runtime_error(error_msg.str());
299 }
300
301 if (joint.type != JointType::FLOATING && joint.type != JointType::FIXED) {
302 tinyxml2::XMLElement* axis_xml = xml->FirstChildElement("axis");
303 if (axis_xml == nullptr) {
304 joint.axis << 1, 0, 0;
305 joint.S << 1, 0, 0, 0, 0, 0;
306 }
307 else {
308 const char* xyz_char = axis_xml->Attribute("xyz");
309 if (xyz_char != nullptr) {
310 joint.axis = vec_from_string<Scalar>(std::string(xyz_char));
311 }
312 }
313
314 if (joint.type == JointType::REVOLUTE) {
315 joint.S << joint.axis, 0, 0, 0;
316 }
317 else if (joint.type == JointType::PRISMATIC) {
318 joint.S << 0, 0, 0, joint.axis;
319 }
320 }
321 // TODO: Add additional joint properties
322 // tinyxml2::XMLElement *prop_xml = xml->FirstChildElement("dynamics");
323 // if (prop_xml != nullptr) {
324 // joint.dynamics = JointDynamics::fromXml(prop_xml);
325 // }
326
327 // tinyxml2::XMLElement *limit_xml = xml->FirstChildElement("limit");
328 // if (limit_xml != nullptr) {
329 // joint.limits = JointLimits::fromXml(limit_xml);
330 // }
331
332 // tinyxml2::XMLElement *safety_xml = xml->FirstChildElement("safety_controller");
333 // if (safety_xml != nullptr) {
334 // joint.safety = JointSafety::fromXml(safety_xml);
335 // }
336
337 // tinyxml2::XMLElement *calibration_xml = xml->FirstChildElement("calibration");
338 // if (calibration_xml != nullptr) {
339 // joint.calibration = JointCalibration::fromXml(calibration_xml);
340 // }
341
342 // tinyxml2::XMLElement *mimic_xml = xml->FirstChildElement("mimic");
343 // if (mimic_xml != nullptr) {
344 // joint.mimic = JointMimic::fromXml(mimic_xml);
345 // }
346
347 return joint;
348 }
349
356 template <typename Scalar, int nq>
357 void init_link_tree(Model<Scalar, nq>& model, std::vector<Joint<Scalar>>& joints) {
358 // Initialize the joint count to zero
359 model.n_q = 0;
360
361 // Iterate over each joint in the model
362 for (auto joint : joints) {
363 // Check that the joint has a parent link and a child link specified
364 std::string parent_link_name = joint.parent_link_name;
365 if (parent_link_name.empty()) {
366 throw std::runtime_error("Error while constructing model! Joint [" + joint.name
367 + "] is missing a parent link specification.");
368 }
369 std::string child_link_name = joint.child_link_name;
370 if (child_link_name.empty()) {
371 throw std::runtime_error("Error while constructing model! Joint [" + joint.name
372 + "] is missing a child link specification.");
373 }
374
375 // Get references to the child and parent links associated with the joint
376 auto child_link = model.get_link(child_link_name);
377 if (child_link.idx == -1) {
378 throw std::runtime_error("Error while constructing model! Child link [" + child_link_name
379 + "] of joint [" + joint.name + "] not found");
380 }
381
382 auto parent_link = model.get_link(parent_link_name);
383 if (parent_link.idx == -1) {
384 throw std::runtime_error("Error while constructing model! Parent link [" + parent_link_name
385 + "] of joint [" + joint.name + "] not found");
386 }
387
388 child_link.parent = parent_link.idx;
389 parent_link.add_child_link_idx(child_link.idx);
390
391 // Only if the joint is of type REVOLUTE or PRISMATIC, add its index in the configuration vector
392 if (joint.type == JointType::REVOLUTE || joint.type == JointType::PRISMATIC) {
393 joint.idx = model.n_q;
394 model.n_q++;
395 }
396
397 // Associate the joint with the child link and update the child link in the links vector
398 child_link.joint = joint;
399 model.links[child_link.idx] = child_link;
400
401 // Update the parent link in the links vector
402 model.links[parent_link.idx] = parent_link;
403 }
404
405 // Find the base link of the model by finding the link with no parent link
406 for (auto link : model.links) {
407 bool found = false;
408 if (link.parent == -1) {
409 if (found) {
410 throw std::runtime_error(
411 "Error! Multiple base links found. The urdf does not contain a valid link tree.");
412 }
413 model.base_link_idx = link.idx;
414 found = true;
415 }
416 }
417 }
418
426 template <typename Scalar, int nq>
428 for (auto& link : model.links) {
429 // If the link has a fixed joint, update the transforms of the child links and its parents link inertia
430 if (link.joint.type == JointType::FIXED && link.idx != model.base_link_idx) {
431 // Update fixed transforms of the child links
432 for (auto child_link_idx : link.child_links) {
433 auto child_link = model.links[child_link_idx];
434 for (int j = 0; j < model.links.size(); j++) {
435 if (model.links[j].name == child_link.name) {
436 model.links[j].joint.X = model.links[j].joint.X * link.joint.X;
437 break;
438 }
439 }
440 }
441 // Combine spatial inertias
442 auto parent_link = model.links[link.parent];
443 // Add the spatial inertia of the link to its parent link in the link tree
444 for (int j = 0; j < model.links.size(); j++) {
445 if (model.links[j].name == parent_link.name) {
446 Eigen::Matrix<Scalar, 6, 6> X_T = model.links[j].joint.X;
447 model.links[j].I += X_T.transpose() * link.I * X_T;
448 break;
449 }
450 }
451 }
452 else if (link.idx != model.base_link_idx) {
453 // Add the index of this actuatable link to the list of dynamic link indices
454 model.q_map.push_back(link.idx);
455 }
456 }
457 // For each dynamic link, find the index of its parent link in the dynamic link indices
458 for (int i = 0; i < model.q_map.size(); i++) {
459 // Get the dynamic link
460 auto link = model.links[model.q_map[i]];
461 // Get the parent link which isn't fixed
462 auto parent_link = model.links[link.parent];
463 while (parent_link.joint.type == JointType::FIXED) {
464 parent_link = model.links[parent_link.parent];
465 }
466 // First check to see if the parent link is the base link
467 if (parent_link.idx == model.base_link_idx) {
468 // Add the index of the base link to the list of parent indices
469 model.parent.push_back(-1);
470 }
471 else {
472 // Find the parent link in the q_map
473 bool found = false;
474 for (int j = 0; j < model.q_map.size(); j++) {
475 if (model.q_map[j] == parent_link.idx) {
476 model.parent.push_back(j);
477 found = true;
478 break;
479 }
480 }
481 if (!found) {
482 model.parent.push_back(-1);
483 }
484 }
485 }
486
487 // Assign spatial gravity vector in models data
488 model.spatial_gravity.tail(3) = model.gravity;
489
490 // Compute the total mass of the model
491 model.mass = 0;
492 for (auto link : model.links) {
493 model.mass += link.mass;
494 }
495 }
496
502 template <typename Scalar, int nq>
503 Model<Scalar, nq> import_urdf(const std::string& path_to_urdf) {
504 // Open the URDF file
505 std::ifstream input_file(path_to_urdf);
506 if (!input_file.is_open()) {
507 throw std::runtime_error("Could not open the file - '" + path_to_urdf + "'");
508 }
509
510 // Read the URDF file into an XML string
511 std::string urdf_string =
512 std::string(std::istreambuf_iterator<char>(input_file), std::istreambuf_iterator<char>());
513
514 // Parse the XML string using tinyxml2
515 tinyxml2::XMLDocument xml_doc;
516 xml_doc.Parse(urdf_string.c_str());
517 if (xml_doc.Error()) {
518 throw std::runtime_error("Error parsing XML: " + std::string(xml_doc.ErrorStr()));
519 }
520
521 // Get the robot element
522 tinyxml2::XMLElement* robot_xml = xml_doc.RootElement();
523 if (!robot_xml || std::string(robot_xml->Value()) != "robot") {
524 throw std::runtime_error("Error: Could not find the <robot> element in the URDF file");
525 }
526
527 // Create a new model
528 Model<Scalar, nq> model;
529
530 // Set the models name
531 const char* name = robot_xml->Attribute("name");
532 if (!name) {
533 throw std::runtime_error("Error: No name given for the robot");
534 }
535 model.name = name;
536
537 // Parse the links
538 for (tinyxml2::XMLElement* link_xml = robot_xml->FirstChildElement("link"); link_xml;
539 link_xml = link_xml->NextSiblingElement("link")) {
540 Link<Scalar> link = link_from_xml<Scalar>(link_xml);
541 if (model.get_link(link.name).idx != -1) {
542 throw std::runtime_error("Error: Duplicate links '" + link.name + "' found");
543 }
544 link.idx = model.links.size();
545 model.links.push_back(link);
546 }
547 if (model.links.empty()) {
548 throw std::runtime_error("Error: No link elements found in the URDF file");
549 }
550
551 // Parse the joints
552 std::vector<Joint<Scalar>> joints;
553 for (tinyxml2::XMLElement* joint_xml = robot_xml->FirstChildElement("joint"); joint_xml;
554 joint_xml = joint_xml->NextSiblingElement("joint")) {
555 Joint<Scalar> joint = joint_from_xml<Scalar>(joint_xml);
556 for (auto& joint_ : joints) {
557 if (joint.name == joint_.name) {
558 throw std::runtime_error("Error: Duplicate joints '" + joint.name + "' found");
559 }
560 }
561 joints.push_back(joint);
562 }
563
564 // Initialize the link tree and find the base link index (should be -1)
565 init_link_tree(model, joints);
566
567 // Initialize the q_map and parent_map
568 init_dynamics(model);
569
570 return model;
571 }
572} // namespace tinyrobotics
573
574#endif
Contains various math related functions.
Eigen::Matrix< Scalar, 6, 6 > translation_to_spatial(const Eigen::Matrix< Scalar, 3, 1 > &xyz)
Spatial coordinate transform from a xyz translation.
Definition: math.hpp:67
Eigen::Matrix< Scalar, 6, 6 > rpy_to_spatial(const Eigen::Matrix< Scalar, 3, 1 > &rpy)
Spatial coordinate transform from roll-pitch-yaw angles.
Definition: math.hpp:80
Contains struct for representing a tinyrobotics model.
Eigen::Matrix< Scalar, 3, 1 > vec_from_string(const std::string &vector_str)
Get Eigen3 vector from a URDF vector element.
Definition: parser.hpp:20
void init_link_tree(Model< Scalar, nq > &model, std::vector< Joint< Scalar > > &joints)
Initialize the link tree in the model.
Definition: parser.hpp:357
Model< Scalar, nq > import_urdf(const std::string &path_to_urdf)
Construct a new Model object from URDF file description.
Definition: parser.hpp:503
void init_dynamics(Model< Scalar, nq > &model)
Updates dynamic links with any fixed joints associated with them, and updates the indices of the dyna...
Definition: parser.hpp:427
Eigen::Transform< Scalar, 3, Eigen::Isometry > transform_from_xml(tinyxml2::XMLElement *xml)
Get the Eigen3 transform from a URDF xml element.
Definition: parser.hpp:70
const char * get_parent_link_name(tinyxml2::XMLElement *c)
Get the name of the link's parent link.
Definition: parser.hpp:119
Eigen::Matrix< Scalar, 3, 3 > rot_from_string(const std::string &rotation_str)
Get Eigen3 rotation from a URDF rpy string.
Definition: parser.hpp:55
Link< Scalar > link_from_xml(tinyxml2::XMLElement *xml)
Construct a Link from a URDF xml element.
Definition: parser.hpp:136
Joint< Scalar > joint_from_xml(tinyxml2::XMLElement *xml)
Construct a new Joint object from a URDF file description.
Definition: parser.hpp:234
Eigen::Matrix< Scalar, 6, 6 > spatial_transform_from_xml(tinyxml2::XMLElement *xml)
Get the spatial transform from a URDF xml element.
Definition: parser.hpp:93
Represents a joint in a tinyrobotics model which connects two links.
Definition: joint.hpp:29
std::string parent_link_name
Name of the joints parent link.
Definition: joint.hpp:55
Eigen::Matrix< Scalar, 3, 1 > axis
Axis of motion for the joint.
Definition: joint.hpp:44
JointType type
Type of the joint.
Definition: joint.hpp:41
std::string child_link_name
Name of the joints child link.
Definition: joint.hpp:58
std::string name
Name of the joint.
Definition: joint.hpp:38
Eigen::Transform< Scalar, 3, Eigen::Isometry > parent_transform
Homogeneous transform to the parent link.
Definition: joint.hpp:47
Eigen::Matrix< Scalar, 6, 1 > S
Spatial axis.
Definition: joint.hpp:64
A tinyrobotics model.
Definition: model.hpp:25
std::vector< int > parent
Vector of parent link indices of the links which have a non-fixed joints.
Definition: model.hpp:42
std::vector< int > q_map
Map to indices of links in the models link vector that have a non-fixed joints.
Definition: model.hpp:39
Scalar mass
Total mass of the model.
Definition: model.hpp:48
int n_q
Number of configuration coordinates (degrees of freedom) in the model.
Definition: model.hpp:33
Link< Scalar > get_link(const std::string &name) const
Get a link in the model by name.
Definition: model.hpp:144
Eigen::Matrix< Scalar, 6, 1 > spatial_gravity
Gravity vector in spatial coordinates.
Definition: model.hpp:128
Eigen::Matrix< Scalar, 3, 1 > gravity
Gravitational acceleration vector experienced by model.
Definition: model.hpp:45
std::string name
**************** Model Information ****************
Definition: model.hpp:30
int base_link_idx
Index of the base link in the models links vector.
Definition: model.hpp:36
std::vector< Link< Scalar > > links
Vector of links in the model.
Definition: model.hpp:51