Line |
Branch |
Exec |
Source |
1 |
|
|
// |
2 |
|
|
// urdf-parser.cpp |
3 |
|
|
// gepetto-viewer |
4 |
|
|
// |
5 |
|
|
// Created by Anthony Couret, Mathieu Geisert in November 2014. |
6 |
|
|
// Updated by Joseph Mirabel in February 2019. |
7 |
|
|
// Copyright (c) 2014 LAAS-CNRS. All rights reserved. |
8 |
|
|
// |
9 |
|
|
|
10 |
|
|
#include <gepetto/viewer/urdf-parser.h> |
11 |
|
|
|
12 |
|
|
#include <QBuffer> |
13 |
|
|
#include <QDebug> |
14 |
|
|
#include <QDomDocument> |
15 |
|
|
#include <QDomElement> |
16 |
|
|
#include <QFile> |
17 |
|
|
#include <QFileInfo> |
18 |
|
|
#include <QMap> |
19 |
|
|
#include <QString> |
20 |
|
|
#include <QStringList> |
21 |
|
|
#include <QtGlobal> |
22 |
|
|
#include <iostream> |
23 |
|
|
#include <osg/Version> |
24 |
|
|
#include <string> |
25 |
|
|
#include <vector> |
26 |
|
|
#if OSG_VERSION_GREATER_OR_EQUAL(3, 3, 9) && OSG_VERSION_LESS_THAN(3, 5, 7) |
27 |
|
|
#include <osgQt/Version> |
28 |
|
|
#endif |
29 |
|
|
|
30 |
|
|
#include <gepetto/viewer/leaf-node-box.h> |
31 |
|
|
#include <gepetto/viewer/leaf-node-capsule.h> |
32 |
|
|
#include <gepetto/viewer/leaf-node-cylinder.h> |
33 |
|
|
#include <gepetto/viewer/leaf-node-sphere.h> |
34 |
|
|
|
35 |
|
|
#include "log.hh" |
36 |
|
|
|
37 |
|
|
namespace gepetto { |
38 |
|
|
namespace viewer { |
39 |
|
|
namespace urdfParser { |
40 |
|
|
|
41 |
|
|
namespace details { |
42 |
|
|
typedef std::map<std::string, ::osg::NodeRefPtr> Cache_t; |
43 |
|
|
struct Material { |
44 |
|
|
bool hasColor, hasTexture; |
45 |
|
|
osgVector4 color; |
46 |
|
|
std::string texture; |
47 |
|
✗ |
Material() : hasColor(false), hasTexture(false) {} |
48 |
|
|
}; |
49 |
|
|
typedef QMap<QString, Material> MaterialMap_t; |
50 |
|
|
DEF_CLASS_SMART_PTR(LinkNode) |
51 |
|
|
|
52 |
|
|
class LinkNode : public GroupNode { |
53 |
|
|
private: |
54 |
|
|
bool currentIsVisual_; |
55 |
|
|
std::vector<NodePtr_t> visuals_, collisions_; |
56 |
|
|
|
57 |
|
|
public: |
58 |
|
✗ |
virtual ~LinkNode() {} |
59 |
|
|
|
60 |
|
✗ |
LinkNode(const std::string& name) : GroupNode(name) { |
61 |
|
✗ |
addProperty(BoolProperty::create( |
62 |
|
|
"ShowVisual", |
63 |
|
✗ |
BoolProperty::getterFromMemberFunction(this, &LinkNode::showVisual), |
64 |
|
✗ |
BoolProperty::setterFromMemberFunction(this, &LinkNode::showVisual))); |
65 |
|
|
} |
66 |
|
|
|
67 |
|
✗ |
const bool& showVisual() const { return currentIsVisual_; } |
68 |
|
|
|
69 |
|
✗ |
void showVisual(const bool& visual) { |
70 |
|
✗ |
if (currentIsVisual_ == visual) return; |
71 |
|
✗ |
VisibilityMode m = (visual ? VISIBILITY_ON : VISIBILITY_OFF); |
72 |
|
✗ |
for (std::size_t i = 0; i < visuals_.size(); ++i) |
73 |
|
✗ |
visuals_[i]->setVisibilityMode(m); |
74 |
|
✗ |
m = (visual ? VISIBILITY_OFF : VISIBILITY_ON); |
75 |
|
✗ |
for (std::size_t i = 0; i < collisions_.size(); ++i) |
76 |
|
✗ |
collisions_[i]->setVisibilityMode(m); |
77 |
|
✗ |
currentIsVisual_ = visual; |
78 |
|
|
} |
79 |
|
|
|
80 |
|
✗ |
void add(const NodePtr_t& child_ptr, bool vis) { |
81 |
|
✗ |
VisibilityMode m = |
82 |
|
✗ |
(currentIsVisual_ == vis ? VISIBILITY_ON : VISIBILITY_OFF); |
83 |
|
✗ |
addChild(child_ptr); |
84 |
|
✗ |
if (vis) |
85 |
|
✗ |
visuals_.push_back(child_ptr); |
86 |
|
|
else |
87 |
|
✗ |
collisions_.push_back(child_ptr); |
88 |
|
✗ |
child_ptr->setVisibilityMode(m); |
89 |
|
|
} |
90 |
|
|
|
91 |
|
✗ |
static LinkNodePtr_t create(const std::string& name) { |
92 |
|
✗ |
LinkNodePtr_t robot(new LinkNode(name)); |
93 |
|
✗ |
robot->initWeakPtr(robot); |
94 |
|
✗ |
return robot; |
95 |
|
|
} |
96 |
|
|
}; |
97 |
|
|
|
98 |
|
✗ |
bool getShowVisuals(const GroupNode* gn) { |
99 |
|
✗ |
bool value = true; |
100 |
|
✗ |
for (std::size_t i = 0; i < gn->getNumOfChildren(); ++i) { |
101 |
|
✗ |
NodePtr_t n = gn->getChild(i); |
102 |
|
✗ |
if (n->hasProperty("ShowVisual")) { |
103 |
|
|
bool v; |
104 |
|
✗ |
n->getProperty("ShowVisual", v); |
105 |
|
✗ |
value = value && v; |
106 |
|
|
} |
107 |
|
|
} |
108 |
|
✗ |
return value; |
109 |
|
|
} |
110 |
|
✗ |
void setShowVisuals(GroupNode* gn, bool visual) { |
111 |
|
✗ |
for (std::size_t i = 0; i < gn->getNumOfChildren(); ++i) { |
112 |
|
✗ |
NodePtr_t n = gn->getChild(i); |
113 |
|
✗ |
if (n->hasProperty("ShowVisual")) n->setProperty("ShowVisual", visual); |
114 |
|
|
} |
115 |
|
|
} |
116 |
|
|
|
117 |
|
✗ |
QStringList rosPackagePath() { |
118 |
|
✗ |
const QString rosPathVar(qgetenv("ROS_PACKAGE_PATH")); |
119 |
|
✗ |
if (rosPathVar.toStdString() != "") return rosPathVar.split(':'); |
120 |
|
✗ |
const QString amentPrefixPath(qgetenv("AMENT_PREFIX_PATH")); |
121 |
|
✗ |
if (amentPrefixPath.toStdString() != "") { |
122 |
|
✗ |
QStringList paths(amentPrefixPath.split(':')); |
123 |
|
✗ |
QStringList res; |
124 |
|
✗ |
for (const auto& path : paths) { |
125 |
|
✗ |
res.append(path + QString("/share")); |
126 |
|
|
} |
127 |
|
✗ |
return res; |
128 |
|
|
} |
129 |
|
✗ |
throw std::invalid_argument( |
130 |
|
|
"neither ROS_PACKAGE_PATH nor AMENT_PREFIX_PATH environment variables is " |
131 |
|
✗ |
"defined."); |
132 |
|
|
} |
133 |
|
|
|
134 |
|
✗ |
std::string getFilename(const QString& input) { |
135 |
|
✗ |
if (input.startsWith("package://")) { |
136 |
|
✗ |
QStringList rosPaths; |
137 |
|
|
try { |
138 |
|
✗ |
rosPaths = rosPackagePath(); |
139 |
|
✗ |
} catch (const std::invalid_argument& exc) { |
140 |
|
✗ |
throw std::invalid_argument( |
141 |
|
✗ |
std::string("Input path: \"") + input.toStdString() + |
142 |
|
✗ |
std::string("\" starts with \"package://\", but ") + exc.what()); |
143 |
|
|
} |
144 |
|
✗ |
for (int i = 0; i < rosPaths.size(); ++i) { |
145 |
|
✗ |
QFileInfo fileInfo(rosPaths[i] + '/' + input.right(input.size() - 10)); |
146 |
|
✗ |
if (fileInfo.exists() && fileInfo.isFile()) |
147 |
|
✗ |
return fileInfo.filePath().toStdString(); |
148 |
|
|
} |
149 |
|
✗ |
throw std::invalid_argument( |
150 |
|
✗ |
("File not found: " + input + |
151 |
|
|
". Check ROS_PACKAGE_PATH environment variable.") |
152 |
|
✗ |
.toStdString()); |
153 |
|
|
} |
154 |
|
✗ |
return input.toStdString(); |
155 |
|
|
} |
156 |
|
|
|
157 |
|
|
template <typename ReturnType> |
158 |
|
|
void toDoubles(const QString& s, ReturnType& vect) { |
159 |
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) |
160 |
|
|
QStringList nums = s.split(' ', Qt::SkipEmptyParts); |
161 |
|
|
#else |
162 |
|
|
QStringList nums = s.split(' ', QString::SkipEmptyParts); |
163 |
|
|
#endif |
164 |
|
|
if (ReturnType::num_components != nums.size()) |
165 |
|
|
throw std::logic_error("Could not parse " + s.toStdString()); |
166 |
|
|
bool ok; |
167 |
|
|
for (int i = 0; i < nums.size(); ++i) { |
168 |
|
|
double d = nums[i].toDouble(&ok); |
169 |
|
|
if (!ok) throw std::logic_error("Could not parse " + s.toStdString()); |
170 |
|
|
vect[i] = d; |
171 |
|
|
} |
172 |
|
|
} |
173 |
|
|
|
174 |
|
|
template <typename ReturnType> |
175 |
|
✗ |
void toFloats(const QString& s, ReturnType& vect) { |
176 |
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) |
177 |
|
✗ |
QStringList nums = s.split(' ', Qt::SkipEmptyParts); |
178 |
|
|
#else |
179 |
|
|
QStringList nums = s.split(' ', QString::SkipEmptyParts); |
180 |
|
|
#endif |
181 |
|
✗ |
if (ReturnType::num_components != nums.size()) |
182 |
|
✗ |
throw std::logic_error("Could not parse " + s.toStdString()); |
183 |
|
|
bool ok; |
184 |
|
✗ |
for (int i = 0; i < nums.size(); ++i) { |
185 |
|
✗ |
float d = nums[i].toFloat(&ok); |
186 |
|
✗ |
if (!ok) throw std::logic_error("Could not parse " + s.toStdString()); |
187 |
|
✗ |
vect[i] = d; |
188 |
|
|
} |
189 |
|
|
} |
190 |
|
|
|
191 |
|
✗ |
void parseOrigin(const QDomElement element, osgVector3& T, osgQuat& R) { |
192 |
|
✗ |
QDomElement origin = element.firstChildElement("origin"); |
193 |
|
✗ |
if (!origin.nextSiblingElement("origin").isNull()) |
194 |
|
✗ |
throw std::logic_error("URDF contains two origins."); |
195 |
|
|
|
196 |
|
✗ |
T = osgVector3(0, 0, 0); |
197 |
|
✗ |
R = osgQuat(0, 0, 0, 1); |
198 |
|
✗ |
if (origin.isNull()) return; |
199 |
|
✗ |
QString xyz = origin.attribute("xyz"); |
200 |
|
✗ |
if (!xyz.isNull()) toFloats(xyz, T); |
201 |
|
✗ |
QString rpy = origin.attribute("rpy"); |
202 |
|
✗ |
if (!rpy.isNull()) { |
203 |
|
✗ |
osgVector3 rpy2; |
204 |
|
✗ |
toFloats(rpy, rpy2); |
205 |
|
|
|
206 |
|
|
double phi, the, psi; |
207 |
|
✗ |
phi = rpy2[0] / 2.0; |
208 |
|
✗ |
the = rpy2[1] / 2.0; |
209 |
|
✗ |
psi = rpy2[2] / 2.0; |
210 |
|
|
|
211 |
|
✗ |
R.x() = sin(phi) * cos(the) * cos(psi) - cos(phi) * sin(the) * sin(psi); |
212 |
|
✗ |
R.y() = cos(phi) * sin(the) * cos(psi) + sin(phi) * cos(the) * sin(psi); |
213 |
|
✗ |
R.z() = cos(phi) * cos(the) * sin(psi) - sin(phi) * sin(the) * cos(psi); |
214 |
|
✗ |
R.w() = cos(phi) * cos(the) * cos(psi) + sin(phi) * sin(the) * sin(psi); |
215 |
|
|
} |
216 |
|
|
} |
217 |
|
|
|
218 |
|
✗ |
NodePtr_t createMesh(const QString& name, const QDomElement mesh, |
219 |
|
|
Cache_t& cache) { |
220 |
|
✗ |
std::string mesh_path = getFilename(mesh.attribute("filename")); |
221 |
|
|
|
222 |
|
✗ |
Cache_t::const_iterator _cache = cache.find(mesh_path); |
223 |
|
✗ |
LeafNodeColladaPtr_t meshNode; |
224 |
|
✗ |
if (_cache == cache.end()) { |
225 |
|
✗ |
meshNode = LeafNodeCollada::create(name.toStdString(), mesh_path); |
226 |
|
✗ |
cache.insert(std::make_pair(mesh_path, meshNode->getColladaPtr())); |
227 |
|
|
} else |
228 |
|
|
meshNode = |
229 |
|
✗ |
LeafNodeCollada::create(name.toStdString(), _cache->second, mesh_path); |
230 |
|
|
|
231 |
|
✗ |
if (mesh.hasAttribute("scale")) { |
232 |
|
✗ |
osgVector3 scale(1, 1, 1); |
233 |
|
✗ |
toFloats(mesh.attribute("scale"), scale); |
234 |
|
✗ |
if (scale != osgVector3(1, 1, 1)) { |
235 |
|
✗ |
meshNode->setScale(scale); |
236 |
|
✗ |
meshNode->applyScale(); |
237 |
|
|
} |
238 |
|
|
} |
239 |
|
|
|
240 |
|
✗ |
return meshNode; |
241 |
|
|
} |
242 |
|
|
|
243 |
|
|
/// \param collision collision or visual tag |
244 |
|
✗ |
bool isCapsule(const QDomElement& link, const QDomElement& collision) { |
245 |
|
✗ |
QString gname = collision.attribute("name"); |
246 |
|
✗ |
if (gname.isNull()) return false; |
247 |
|
✗ |
QDomElement cc = link.firstChildElement("collision_checking"); |
248 |
|
✗ |
if (cc.isNull()) return false; |
249 |
|
|
|
250 |
|
✗ |
for (QDomElement element = cc.firstChildElement("capsule"); !element.isNull(); |
251 |
|
✗ |
element = element.nextSiblingElement("capsule")) |
252 |
|
✗ |
if (element.attribute("name") == gname) return true; |
253 |
|
|
|
254 |
|
✗ |
return false; |
255 |
|
|
} |
256 |
|
|
|
257 |
|
✗ |
Material parseMaterial(const QDomElement material) { |
258 |
|
✗ |
Material mat; |
259 |
|
|
|
260 |
|
|
// Set color |
261 |
|
✗ |
QDomElement color = material.firstChildElement("color"); |
262 |
|
✗ |
mat.hasColor = !color.isNull(); |
263 |
|
✗ |
if (mat.hasColor) { |
264 |
|
✗ |
toFloats(color.attribute("rgba"), mat.color); |
265 |
|
|
} |
266 |
|
|
|
267 |
|
|
// Set texture |
268 |
|
✗ |
QDomElement texture = material.firstChildElement("texture"); |
269 |
|
✗ |
mat.hasTexture = !texture.isNull(); |
270 |
|
✗ |
if (mat.hasTexture) { |
271 |
|
✗ |
if (!texture.hasAttribute("filename")) |
272 |
|
✗ |
throw std::logic_error("texture tag must have a filename attribute."); |
273 |
|
✗ |
mat.texture = getFilename(texture.attribute("filename")); |
274 |
|
|
} |
275 |
|
✗ |
return mat; |
276 |
|
|
} |
277 |
|
|
|
278 |
|
✗ |
void setMaterial(const QDomElement material, NodePtr_t node, |
279 |
|
|
MaterialMap_t& materials) { |
280 |
|
✗ |
if (material.isNull()) return; |
281 |
|
✗ |
Material mat; |
282 |
|
|
|
283 |
|
✗ |
if (!material.hasAttribute("name")) |
284 |
|
✗ |
throw std::logic_error("material tag must have a name attribute."); |
285 |
|
|
|
286 |
|
✗ |
QString name = material.attribute("name"); |
287 |
|
✗ |
if (materials.contains(name)) { |
288 |
|
✗ |
mat = materials[name]; |
289 |
|
|
} else { |
290 |
|
|
// Parse material |
291 |
|
✗ |
mat = parseMaterial(material); |
292 |
|
✗ |
if (!mat.hasColor && !mat.hasTexture) { |
293 |
|
✗ |
log() << "material tag " << name |
294 |
|
|
<< " must have either a color or a " |
295 |
|
✗ |
"texture.\n"; |
296 |
|
✗ |
mat.color = osgVector4(0.9f, 0.9f, 0.9f, 1.f); |
297 |
|
✗ |
mat.hasColor = true; |
298 |
|
|
} |
299 |
|
✗ |
materials[name] = mat; |
300 |
|
|
} |
301 |
|
|
|
302 |
|
|
// Set color |
303 |
|
✗ |
if (mat.hasColor) node->setColor(mat.color); |
304 |
|
|
|
305 |
|
|
// Set texture |
306 |
|
✗ |
if (mat.hasTexture) { |
307 |
|
✗ |
NodeDrawablePtr_t nd = dynamic_pointer_cast<NodeDrawable>(node); |
308 |
|
✗ |
LeafNodeColladaPtr_t lc = dynamic_pointer_cast<LeafNodeCollada>(node); |
309 |
|
✗ |
assert(nd || lc); |
310 |
|
✗ |
if (nd) |
311 |
|
✗ |
nd->setTexture(mat.texture); |
312 |
|
✗ |
else if (lc) |
313 |
|
✗ |
lc->setTexture(mat.texture); |
314 |
|
|
} |
315 |
|
|
} |
316 |
|
|
|
317 |
|
|
template <bool visual> |
318 |
|
✗ |
void addGeoms(const std::string& robotName, const QString& namePrefix, |
319 |
|
|
const QDomElement& link, LinkNodePtr_t& linkNode, bool linkFrame, |
320 |
|
|
Cache_t& cache, MaterialMap_t& materials) { |
321 |
|
✗ |
static const QString tagName(visual ? "visual" : "collision"); |
322 |
|
✗ |
static const QString nameFormat("%1/%2_%3"); |
323 |
|
✗ |
QString name = nameFormat.arg(robotName.c_str()).arg(namePrefix); |
324 |
|
|
|
325 |
|
✗ |
int index = 0; |
326 |
|
✗ |
for (QDomElement element = link.firstChildElement(tagName); !element.isNull(); |
327 |
|
✗ |
element = element.nextSiblingElement(tagName)) { |
328 |
|
✗ |
NodePtr_t node; |
329 |
|
✗ |
QString name_i = name.arg(index); |
330 |
|
|
|
331 |
|
|
// Parse tag geometry |
332 |
|
✗ |
QDomElement geometry = element.firstChildElement("geometry"); |
333 |
|
✗ |
if (!geometry.nextSiblingElement("geometry").isNull()) |
334 |
|
✗ |
throw std::logic_error( |
335 |
|
|
"Visual or collision tag contains two geometries."); |
336 |
|
✗ |
int N = 0; |
337 |
|
|
bool ok; |
338 |
|
✗ |
for (QDomElement type = geometry.firstChildElement(); !type.isNull(); |
339 |
|
✗ |
type = type.nextSiblingElement()) { |
340 |
|
✗ |
if (type.tagName() == "box") { |
341 |
|
|
// Create box |
342 |
|
✗ |
osgVector3 sideLengths; |
343 |
|
✗ |
toFloats(type.attribute("size"), sideLengths); |
344 |
|
✗ |
node = LeafNodeBox::create(name_i.toStdString(), sideLengths * .5f); |
345 |
|
✗ |
++N; |
346 |
|
✗ |
} else if (type.tagName() == "cylinder") { |
347 |
|
✗ |
float radius = type.attribute("radius").toFloat(&ok); |
348 |
|
✗ |
if (!ok) throw std::logic_error("Could not parse cylinder radius."); |
349 |
|
✗ |
float length = type.attribute("length").toFloat(&ok); |
350 |
|
✗ |
if (!ok) throw std::logic_error("Could not parse cylinder length."); |
351 |
|
✗ |
if (isCapsule(link, element)) |
352 |
|
✗ |
node = LeafNodeCapsule::create(name_i.toStdString(), radius, length); |
353 |
|
|
else |
354 |
|
✗ |
node = LeafNodeCylinder::create(name_i.toStdString(), radius, length); |
355 |
|
✗ |
++N; |
356 |
|
✗ |
} else if (type.tagName() == "sphere") { |
357 |
|
✗ |
float radius = type.attribute("radius").toFloat(&ok); |
358 |
|
✗ |
if (!ok) throw std::logic_error("Could not parse sphere radius."); |
359 |
|
✗ |
node = LeafNodeSphere::create(name_i.toStdString(), radius); |
360 |
|
✗ |
++N; |
361 |
|
✗ |
} else if (type.tagName() == "mesh") { |
362 |
|
✗ |
node = createMesh(name_i, type, cache); |
363 |
|
✗ |
++N; |
364 |
|
|
} |
365 |
|
|
} |
366 |
|
✗ |
if (N != 1) |
367 |
|
✗ |
throw std::logic_error( |
368 |
|
|
"geometry tag must contain only one of box, cylinder, sphere or " |
369 |
|
|
"mesh."); |
370 |
|
|
|
371 |
|
|
// Parse tag origin |
372 |
|
✗ |
if (linkFrame) { |
373 |
|
✗ |
osgVector3 static_pos; |
374 |
|
✗ |
osgQuat static_quat; |
375 |
|
✗ |
parseOrigin(element, static_pos, static_quat); |
376 |
|
✗ |
node->setStaticTransform(static_pos, static_quat); |
377 |
|
|
} |
378 |
|
|
|
379 |
|
|
// Parse tag meterial |
380 |
|
✗ |
setMaterial(element.firstChildElement("material"), node, materials); |
381 |
|
|
|
382 |
|
✗ |
linkNode->add(node, visual); |
383 |
|
✗ |
++index; |
384 |
|
|
} |
385 |
|
|
} |
386 |
|
|
|
387 |
|
✗ |
void parseXML(const std::string& urdf_file, QDomDocument& model) { |
388 |
|
|
bool ok; |
389 |
|
✗ |
QString errorPrefix, errorMsg; |
390 |
|
|
int errorRow, errorCol; |
391 |
|
|
|
392 |
|
✗ |
if (urdf_file.compare(urdf_file.length() - 5, 5, ".urdf") == 0) { |
393 |
|
✗ |
std::string urdf_file2 = getFilename(QString::fromStdString(urdf_file)); |
394 |
|
✗ |
QFile file(urdf_file2.c_str()); |
395 |
|
✗ |
ok = model.setContent(&file, false, &errorMsg, &errorRow, &errorCol); |
396 |
|
✗ |
errorPrefix = "Failed to parse "; |
397 |
|
✗ |
errorPrefix += urdf_file.c_str(); |
398 |
|
✗ |
} else { |
399 |
|
✗ |
QBuffer buffer; |
400 |
|
✗ |
buffer.setData(urdf_file.c_str(), (int)urdf_file.length()); |
401 |
|
✗ |
ok = model.setContent(&buffer, false, &errorMsg, &errorRow, &errorCol); |
402 |
|
✗ |
errorPrefix = "Failed to parse XML string"; |
403 |
|
|
} |
404 |
|
|
|
405 |
|
✗ |
if (!ok) { |
406 |
|
✗ |
throw std::invalid_argument(QString("%1: %2 at %3:%4") |
407 |
|
✗ |
.arg(errorPrefix) |
408 |
|
✗ |
.arg(errorMsg) |
409 |
|
✗ |
.arg(errorRow) |
410 |
|
✗ |
.arg(errorCol) |
411 |
|
✗ |
.toStdString()); |
412 |
|
|
} |
413 |
|
|
} |
414 |
|
|
} // namespace details |
415 |
|
|
|
416 |
|
✗ |
std::string getFilename(const std::string& input) { |
417 |
|
✗ |
return details::getFilename(QString::fromStdString(input)); |
418 |
|
|
} |
419 |
|
|
|
420 |
|
✗ |
GroupNodePtr_t parse(const std::string& robotName, const std::string& urdf_file, |
421 |
|
|
const bool& visual, const bool& linkFrame) { |
422 |
|
✗ |
QDomDocument model; |
423 |
|
|
|
424 |
|
|
// Parse the XML document |
425 |
|
✗ |
details::parseXML(urdf_file, model); |
426 |
|
|
|
427 |
|
|
// Parse the materials |
428 |
|
✗ |
details::MaterialMap_t materials; |
429 |
|
✗ |
for (QDomElement material = |
430 |
|
✗ |
model.firstChildElement("robot").firstChildElement("material"); |
431 |
|
✗ |
!material.isNull(); material = material.nextSiblingElement("material")) { |
432 |
|
✗ |
if (!material.hasAttribute("name")) |
433 |
|
✗ |
throw std::logic_error("material tag must have a name attribute."); |
434 |
|
|
|
435 |
|
✗ |
details::Material mat = details::parseMaterial(material); |
436 |
|
|
|
437 |
|
✗ |
if (!mat.hasColor && !mat.hasTexture) |
438 |
|
✗ |
throw std::logic_error( |
439 |
|
✗ |
"material tag must have either a color or a texture."); |
440 |
|
✗ |
QString name = material.attribute("name"); |
441 |
|
✗ |
materials[name] = mat; |
442 |
|
|
} |
443 |
|
|
|
444 |
|
✗ |
GroupNodePtr_t robot = GroupNode::create(robotName); |
445 |
|
✗ |
QDomNodeList links = model.elementsByTagName("link"); |
446 |
|
|
|
447 |
|
✗ |
robot->addProperty(BoolProperty::create( |
448 |
|
|
"ShowVisual", |
449 |
|
✗ |
BoolProperty::Getter_t(boost::bind(details::getShowVisuals, robot.get())), |
450 |
|
✗ |
BoolProperty::Setter_t( |
451 |
|
|
boost::bind(details::setShowVisuals, robot.get(), _1)))); |
452 |
|
|
|
453 |
|
✗ |
details::Cache_t cache; |
454 |
|
✗ |
for (int i = 0; i < links.size(); i++) { |
455 |
|
✗ |
QDomElement link = links.at(i).toElement(); |
456 |
|
✗ |
if (link.isNull()) throw std::logic_error("link must be a tag."); |
457 |
|
✗ |
QString name = link.attribute("name"); |
458 |
|
✗ |
if (name.isNull()) throw std::logic_error("A link has no name attribute."); |
459 |
|
|
|
460 |
|
✗ |
std::string link_name = name.toStdString(); |
461 |
|
✗ |
log() << name << '\n'; |
462 |
|
|
|
463 |
|
|
details::LinkNodePtr_t linkNode( |
464 |
|
✗ |
details::LinkNode ::create(robotName + "/" + link_name)); |
465 |
|
✗ |
linkNode->showVisual(visual); |
466 |
|
|
// add link to robot node |
467 |
|
✗ |
robot->addChild(linkNode); |
468 |
|
|
|
469 |
|
✗ |
if (visual) { |
470 |
|
✗ |
details::addGeoms<true>(robotName, name, link, linkNode, linkFrame, cache, |
471 |
|
|
materials); |
472 |
|
✗ |
if (linkFrame) { |
473 |
|
|
try { |
474 |
|
✗ |
details::addGeoms<false>(robotName, "collision_" + name, link, |
475 |
|
✗ |
linkNode, linkFrame, cache, materials); |
476 |
|
✗ |
} catch (const std::invalid_argument& e) { |
477 |
|
|
std::cerr << "Could not load collision geometries of " << robotName |
478 |
|
✗ |
<< ":" << e.what() << std::endl; |
479 |
|
|
} |
480 |
|
|
} |
481 |
|
|
} else { |
482 |
|
✗ |
details::addGeoms<false>(robotName, name, link, linkNode, linkFrame, |
483 |
|
|
cache, materials); |
484 |
|
✗ |
if (linkFrame) { |
485 |
|
|
try { |
486 |
|
✗ |
details::addGeoms<true>(robotName, "visual_" + name, link, linkNode, |
487 |
|
✗ |
linkFrame, cache, materials); |
488 |
|
✗ |
} catch (const std::invalid_argument& e) { |
489 |
|
|
std::cerr << "Could not load visual geometries of " << robotName |
490 |
|
✗ |
<< ":" << e.what() << std::endl; |
491 |
|
|
} |
492 |
|
|
} |
493 |
|
|
} |
494 |
|
|
} |
495 |
|
✗ |
return robot; |
496 |
|
|
} |
497 |
|
|
} // namespace urdfParser |
498 |
|
|
} /* namespace viewer */ |
499 |
|
|
|
500 |
|
|
} // namespace gepetto |
501 |
|
|
|