00001
00002
00003
00004 import string
00005 from naoqi_tools.gazeboUrdf import *
00006 from xml.dom.minidom import Document
00007 from xml.dom import minidom
00008 import sys
00009 from numpy import array,pi
00010 import re, copy
00011
00012 HUBO_JOINT_SUFFIX_MASK=r'([HKASEWF][RPY12345])'
00013
00014 class Collision(object):
00015 """Collision node stores collision geometry for a link."""
00016 def __init__(self, geometry=None, origin=None):
00017 self.geometry = geometry
00018 self.origin = origin
00019
00020 @staticmethod
00021 def parse(node, verbose=True):
00022 c = Collision()
00023 for child in children(node):
00024 if child.localName == 'geometry':
00025 c.geometry = Geometry.parse(child, verbose)
00026 elif child.localName == 'origin':
00027 c.origin = Pose.parse(child)
00028 else:
00029 print("Unknown collision element '%s'"%child.localName)
00030 return c
00031
00032 def to_xml(self, doc):
00033 xml = doc.createElement("collision")
00034 add(doc, xml, self.geometry)
00035 add(doc, xml, self.origin)
00036 return xml
00037
00038 def to_openrave_xml(self, doc):
00039
00040 xml = self.geometry.to_openrave_xml(doc)
00041 add_openrave(doc,xml, self.origin)
00042 return xml
00043
00044 def __str__(self):
00045 s = "Origin:\n{0}\n".format(reindent(str(self.origin), 1))
00046 s += "Geometry:\n{0}\n".format(reindent(str(self.geometry), 1))
00047 return s
00048
00049 class Color(object):
00050 """Color node stores color definition for a material or a visual."""
00051 def __init__(self, r=0.0, g=0.0, b=0.0, a=0.0):
00052 self.rgba=(r,g,b,a)
00053
00054 @staticmethod
00055 def parse(node):
00056 rgba = node.getAttribute("rgba").split()
00057 (r,g,b,a) = [ float(x) for x in rgba ]
00058 return Color(r,g,b,a)
00059
00060 def to_xml(self, doc):
00061 xml = doc.createElement("color")
00062 set_attribute(xml, "rgba", self.rgba)
00063 return xml
00064
00065 def to_openrave_xml(self,doc):
00066 return None
00067
00068 def __str__(self):
00069 return "r: {0}, g: {1}, b: {2}, a: {3},".format(
00070 self.rgba[0], self.rgba[1], self.rgba[2], self.rgba[3])
00071
00072 class Dynamics(object):
00073 """Dynamics node stores coefficients that define the dynamics of a joint"""
00074 def __init__(self, damping=None, friction=None):
00075 self.damping = damping
00076 self.friction = friction
00077
00078 @staticmethod
00079 def parse(node):
00080 d = Dynamics()
00081 if node.hasAttribute('damping'):
00082 d.damping = node.getAttribute('damping')
00083 if node.hasAttribute('friction'):
00084 d.friction = node.getAttribute('friction')
00085 return d
00086
00087 def to_xml(self, doc):
00088 xml = doc.createElement('dynamics')
00089 set_attribute(xml, 'damping', self.damping)
00090 set_attribute(xml, 'friction', self.friction)
00091 return xml
00092
00093 def to_openrave_xml(self,doc):
00094 return None
00095
00096 def __str__(self):
00097 return "Damping: {0}\nFriction: {1}\n".format(self.damping,
00098 self.friction)
00099
00100 class Geometry(object):
00101 """Geometry abstract class define the type of geomerical shape for a visual or collision element"""
00102 def __init__(self):
00103 None
00104
00105 @staticmethod
00106 def parse(node, verbose=True):
00107 shape = children(node)[0]
00108 if shape.localName=='box':
00109 return Box.parse(shape)
00110 elif shape.localName=='cylinder':
00111 return Cylinder.parse(shape)
00112 elif shape.localName=='sphere':
00113 return Sphere.parse(shape)
00114 elif shape.localName=='mesh':
00115 return Mesh.parse(shape)
00116 else:
00117 if verbose:
00118 print("Unknown shape %s"%shape.localName)
00119
00120 def __str__(self):
00121 return "Geometry abstract class"
00122
00123 class Box(Geometry):
00124 """Box node stores the dimensions for a rectangular geometry element"""
00125 def __init__(self, dims=None):
00126 if dims is None:
00127 self.dims = None
00128 else:
00129 self.dims = (dims[0], dims[1], dims[2])
00130
00131 @staticmethod
00132 def parse(node):
00133 dims = node.getAttribute('size').split()
00134 return Box([float(a) for a in dims])
00135
00136 def to_xml(self, doc):
00137 xml = doc.createElement("box")
00138 set_attribute(xml, "size", self.dims)
00139 geom = doc.createElement('geometry')
00140 geom.appendChild(xml)
00141 return geom
00142
00143 def to_openrave_xml(self, doc):
00144 xml = short(doc, "geometry","type","box")
00145 xml.appendChild(create_element(xml, "extents", self.dims))
00146 return xml
00147
00148 def __str__(self):
00149 return "Dimension: {0}".format(self.dims)
00150
00151
00152 class Cylinder(Geometry):
00153 """Cylinder node stores the dimensions for a z-rotation invariant geometry element"""
00154 def __init__(self, radius=0.0, length=0.0):
00155 self.radius = radius
00156 self.length = length
00157
00158 @staticmethod
00159 def parse(node):
00160 r = node.getAttribute('radius')
00161 l = node.getAttribute('length')
00162 return Cylinder(float(r), float(l))
00163
00164 def to_xml(self, doc):
00165 xml = doc.createElement("cylinder")
00166 set_attribute(xml, "radius", self.radius)
00167 set_attribute(xml, "length", self.length)
00168 geom = doc.createElement('geometry')
00169 geom.appendChild(xml)
00170 return geom
00171
00172 def to_openrave_xml(self, doc):
00173 xml = short(doc, "geometry","type","cylinder")
00174 xml.appendChild(create_element(doc, "height", self.length))
00175 xml.appendChild(create_element(doc, "radius", self.radius))
00176 return xml
00177
00178 def __str__(self):
00179 return "Radius: {0}\nLength: {1}".format(self.radius,
00180 self.length)
00181
00182 class Sphere(Geometry):
00183 """Sphere node stores the dimensions for a rotation invariant geometry element"""
00184 def __init__(self, radius=0.0):
00185 self.radius = radius
00186
00187 @staticmethod
00188 def parse(node):
00189 r = node.getAttribute('radius')
00190 return Sphere(float(r))
00191
00192 def to_xml(self, doc):
00193 xml = doc.createElement("sphere")
00194 set_attribute(xml, "radius", self.radius)
00195 geom = doc.createElement('geometry')
00196 geom.appendChild(xml)
00197 return geom
00198
00199 def to_openrave_xml(self, doc):
00200 xml = short(doc, "geometry","type","sphere")
00201 xml.create_child(xml, "radius", self.radius)
00202 return xml
00203
00204 def __str__(self):
00205 return "Radius: {0}".format(self.radius)
00206
00207
00208 class Mesh(Geometry):
00209 """Mesh node stores the path of the mesh file to be displayed"""
00210 def __init__(self, filename=None, scale=1):
00211 self.filename = filename
00212 self.scale = scale
00213
00214 @staticmethod
00215 def parse(node):
00216 fn = node.getAttribute('filename')
00217 s = node.getAttribute('scale')
00218 if s == "":
00219 s = 1
00220 else:
00221 xyz = node.getAttribute('scale').split()
00222 s = map(float, xyz)
00223 return Mesh(fn, s)
00224
00225 def to_xml(self, doc):
00226 xml = doc.createElement("mesh")
00227 set_attribute(xml, "filename", self.filename)
00228 if self.scale != 1:
00229 set_attribute(xml, "scale", self.scale)
00230 geom = doc.createElement('geometry')
00231 geom.appendChild(xml)
00232 return geom
00233
00234 def to_openrave_xml(self, doc):
00235 xml = short(doc, "geometry","type","trimesh")
00236 f='../meshes/'+self.filename.split('/')[-1]
00237 fhull=re.sub(r"Body","convhull",f)
00238
00239 xml.appendChild(create_element(doc, "data", [fhull, self.scale]))
00240
00241 return xml
00242
00243 def __str__(self):
00244 return "Filename: {0}\nScale: {1}".format(self.filename, self.scale)
00245
00246
00247 class Inertial(object):
00248 """Inertial node stores mecanical information of a Link : mass, inertia matrix and origin"""
00249 def __init__(self, ixx=0.0, ixy=0.0, ixz=0.0, iyy=0.0, iyz=0.0, izz=0.0,
00250 mass=0.0, origin=None):
00251 self.matrix = {}
00252 self.matrix['ixx'] = ixx
00253 self.matrix['ixy'] = ixy
00254 self.matrix['ixz'] = ixz
00255 self.matrix['iyy'] = iyy
00256 self.matrix['iyz'] = iyz
00257 self.matrix['izz'] = izz
00258 self.mass = mass
00259 self.origin = origin
00260
00261 @staticmethod
00262 def parse(node):
00263 inert = Inertial()
00264 for child in children(node):
00265 if child.localName=='inertia':
00266 for v in ['ixx', 'ixy', 'ixz', 'iyy', 'iyz', 'izz']:
00267 inert.matrix[v] = float(child.getAttribute(v))
00268 elif child.localName=='mass':
00269 inert.mass = float(child.getAttribute('value'))
00270 elif child.localName == 'origin':
00271 inert.origin = Pose.parse(child)
00272 return inert
00273
00274
00275 def to_xml(self, doc):
00276 xml = doc.createElement("inertial")
00277
00278 xml.appendChild(short(doc, "mass", "value", self.mass))
00279
00280 inertia = doc.createElement("inertia")
00281 for (n,v) in self.matrix.items():
00282 set_attribute(inertia, n, v)
00283 xml.appendChild(inertia)
00284
00285 add(doc, xml, self.origin)
00286 return xml
00287
00288 def to_openrave_xml(self, doc):
00289 xml = doc.createElement("mass")
00290 set_attribute(xml,"type","custom")
00291 xml.appendChild(create_element(doc, "total", self.mass))
00292 text='{ixx} {ixy} {ixz}\n{ixy} {iyy} {iyz}\n{ixz} {iyz} {izz}'.format(**self.matrix)
00293 xml.appendChild(create_element(doc,"inertia",text))
00294 add_openrave(doc, xml, self.origin)
00295 xml.getElementsByTagName('translation')[0].tagName="com"
00296 return xml
00297
00298 def __str__(self):
00299 s = "Origin:\n{0}\n".format(reindent(str(self.origin), 1))
00300 s += "Mass: {0}\n".format(self.mass)
00301 s += "ixx: {0}\n".format(self.matrix['ixx'])
00302 s += "ixy: {0}\n".format(self.matrix['ixy'])
00303 s += "ixz: {0}\n".format(self.matrix['ixz'])
00304 s += "iyy: {0}\n".format(self.matrix['iyy'])
00305 s += "iyz: {0}\n".format(self.matrix['iyz'])
00306 s += "izz: {0}\n".format(self.matrix['izz'])
00307 return s
00308
00309
00310 class Joint(object):
00311 """Joint node stores articulation information coupling two links"""
00312 UNKNOWN = 'unknown'
00313 REVOLUTE = 'revolute'
00314 CONTINUOUS = 'continuous'
00315 PRISMATIC = 'prismatic'
00316 FLOATING = 'floating'
00317 PLANAR = 'planar'
00318 FIXED = 'fixed'
00319
00320
00321 def __init__(self, name, parent, child, joint_type, axis=None, origin=None,
00322 limits=None, dynamics=None, safety=None, calibration=None,
00323 mimic=None):
00324 self.name = name
00325 self.parent = parent
00326 self.child = child
00327 self.joint_type = joint_type
00328 self.axis = axis
00329 self.origin = origin
00330 self.limits = limits
00331 self.dynamics = dynamics
00332 self.safety = safety
00333 self.calibration = calibration
00334 self.mimic = mimic
00335
00336 @staticmethod
00337 def parse(node, verbose=True):
00338 joint = Joint(node.getAttribute('name'), None, None,
00339 node.getAttribute('type'))
00340 for child in children(node):
00341 if child.localName == 'parent':
00342 joint.parent = child.getAttribute('link')
00343 elif child.localName == 'child':
00344 joint.child = child.getAttribute('link')
00345 elif child.localName == 'axis':
00346 joint.axis = array([float(x) for x in child.getAttribute('xyz').split(' ')])
00347 elif child.localName == 'origin':
00348 joint.origin = Pose.parse(child)
00349 elif child.localName == 'limit':
00350 joint.limits = JointLimit.parse(child)
00351 elif child.localName == 'dynamics':
00352 joint.dynamics = Dynamics.parse(child)
00353 elif child.localName == 'safety_controller':
00354 joint.safety = SafetyController.parse(child)
00355 elif child.localName == 'calibration':
00356 joint.calibration = JointCalibration.parse(child)
00357 elif child.localName == 'mimic':
00358 joint.mimic = JointMimic.parse(child)
00359 else:
00360 if verbose:
00361 print("Unknown joint element '%s'"%child.localName)
00362 return joint
00363
00364 def to_xml(self, doc):
00365 xml = doc.createElement("joint")
00366 set_attribute(xml, "name", self.name)
00367 set_attribute(xml, "type", self.joint_type)
00368 xml.appendChild( short(doc, "parent", "link", self.parent) )
00369 xml.appendChild( short(doc, "child" , "link", self.child ) )
00370 add(doc, xml, self.origin)
00371 if self.axis is not None:
00372 xml.appendChild( short(doc, "axis", "xyz", to_string(self.axis) ) )
00373 add(doc, xml, self.limits)
00374 add(doc, xml, self.dynamics)
00375 add(doc, xml, self.safety)
00376 add(doc, xml, self.calibration)
00377 add(doc, xml, self.mimic)
00378
00379 return xml
00380
00381 def to_openrave_xml(self, doc):
00382 xml = doc.createElement("joint")
00383 set_attribute(xml, "name", self.name)
00384 s=""
00385 if self.joint_type == Joint.UNKNOWN:
00386 s = "unknown"
00387 elif self.joint_type == Joint.REVOLUTE:
00388 s = "hinge"
00389 elif self.joint_type == Joint.CONTINUOUS:
00390 s = "hinge"
00391 set_attribute(xml, "circular", "true")
00392
00393 elif self.joint_type == Joint.PRISMATIC:
00394 s = "slider"
00395 elif self.joint_type == Joint.FIXED:
00396 s = "hinge"
00397 set_attribute(xml, "enable", "false")
00398 xml.appendChild( create_element(doc, "limits", "0 0") )
00399
00400 set_attribute(xml, "type", s)
00401 if self.mimic is not None:
00402 multiplier=self.mimic.multiplier if self.mimic.multiplier is not None else 1.0
00403 offset=self.mimic.offset if self.mimic.offset is not None else 0.0
00404
00405 set_attribute(xml,"enable","false")
00406
00407 set_attribute(xml,"mimic_pos","{0} * {1} + {2}".format(self.mimic.joint_name,multiplier,offset))
00408 set_attribute(xml,"mimic_vel","|{0} {1}".format(self.mimic.joint_name,multiplier))
00409
00410 xml.appendChild( create_element(doc, "body", self.parent) )
00411 xml.appendChild( create_element(doc, "body" , self.child ) )
00412 xml.appendChild( create_element(doc, "offsetfrom" , self.parent) )
00413 add_openrave(doc, xml, self.origin)
00414 xml.getElementsByTagName('translation')[0].tagName="anchor"
00415 if self.axis is not None:
00416 xml.appendChild( create_element(doc, "axis", self.axis) )
00417 add_openrave(doc, xml, self.limits)
00418 return xml
00419
00420
00421 def __str__(self):
00422 s = "Name: {0}\n".format(self.name)
00423
00424 s += "Child link name: {0}\n".format(self.child)
00425 s += "Parent link name: {0}\n".format(self.parent)
00426
00427 if self.joint_type == Joint.UNKNOWN:
00428 s += "Type: unknown\n"
00429 elif self.joint_type == Joint.REVOLUTE:
00430 s += "Type: revolute\n"
00431 elif self.joint_type == Joint.CONTINUOUS:
00432 s += "Type: continuous\n"
00433 elif self.joint_type == Joint.PRISMATIC:
00434 s += "Type: prismatic\n"
00435 elif self.joint_type == Joint.FLOATING:
00436 s += "Type: floating\n"
00437 elif self.joint_type == Joint.PLANAR:
00438 s += "Type: planar\n"
00439 elif self.joint_type == Joint.FIXED:
00440 s += "Type: fixed\n"
00441 else:
00442 print("unknown joint type")
00443
00444 s += "Axis: {0}\n".format(self.axis)
00445 s += "Origin:\n{0}\n".format(reindent(str(self.origin), 1))
00446 s += "Limits:\n"
00447 s += reindent(str(self.limits), 1) + "\n"
00448 s += "Dynamics:\n"
00449 s += reindent(str(self.dynamics), 1) + "\n"
00450 s += "Safety:\n"
00451 s += reindent(str(self.safety), 1) + "\n"
00452 s += "Calibration:\n"
00453 s += reindent(str(self.calibration), 1) + "\n"
00454 s += "Mimic:\n"
00455 s += reindent(str(self.mimic), 1) + "\n"
00456 return s
00457
00458
00459
00460 class JointCalibration(object):
00461 def __init__(self, rising=None, falling=None):
00462 self.rising = rising
00463 self.falling = falling
00464
00465 @staticmethod
00466 def parse(node):
00467 jc = JointCalibration()
00468 if node.hasAttribute('rising'):
00469 jc.rising = float( node.getAttribute('rising') )
00470 if node.hasAttribute('falling'):
00471 jc.falling = float( node.getAttribute('falling') )
00472 return jc
00473
00474 def to_xml(self, doc):
00475 xml = doc.createElement('calibration')
00476 set_attribute(xml, 'rising', self.rising)
00477 set_attribute(xml, 'falling', self.falling)
00478 return xml
00479
00480 def to_openrave_xml(self,doc):
00481
00482 return None
00483
00484 def __str__(self):
00485 s = "Raising: {0}\n".format(self.rising)
00486 s += "Falling: {0}\n".format(self.falling)
00487 return s
00488
00489 class JointLimit(object):
00490 """JointLimit node stores mechanical limits of a given joint"""
00491 def __init__(self, effort, velocity, lower=None, upper=None):
00492 self.effort = effort
00493 self.velocity = velocity
00494 self.lower = lower
00495 self.upper = upper
00496
00497 @staticmethod
00498 def parse(node):
00499 jl = JointLimit( float( node.getAttribute('effort') ) ,
00500 float( node.getAttribute('velocity')))
00501 if node.hasAttribute('lower'):
00502 jl.lower = float( node.getAttribute('lower') )
00503 if node.hasAttribute('upper'):
00504 jl.upper = float( node.getAttribute('upper') )
00505 return jl
00506
00507 def get_from_table(self,node):
00508 jl = JointLimit( float( node.getAttribute('effort') ) ,
00509 float( node.getAttribute('velocity')))
00510 if node.hasAttribute('lower'):
00511 jl.lower = float( node.getAttribute('lower') )
00512 if node.hasAttribute('upper'):
00513 jl.upper = float( node.getAttribute('upper') )
00514 return jl
00515
00516 def to_xml(self, doc):
00517 xml = doc.createElement('limit')
00518 set_attribute(xml, 'effort', self.effort)
00519 set_attribute(xml, 'velocity', self.velocity)
00520 set_attribute(xml, 'lower', self.lower)
00521 set_attribute(xml, 'upper', self.upper)
00522 return xml
00523
00524 def to_openrave_xml(self,doc):
00525 limit = create_element(doc,'limitsdeg',[round(self.lower*180/pi,1),round(self.upper*180/pi,1)])
00526 maxvel = create_element(doc,'maxvel',self.velocity)
00527 maxtrq = create_element(doc,'maxtorque',self.effort)
00528 return [limit,maxvel,maxtrq]
00529
00530 def __str__(self):
00531 s = "Effort: {0}\n".format(self.effort)
00532 s += "Lower: {0}\n".format(self.lower)
00533 s += "Upper: {0}\n".format(self.upper)
00534 s += "Velocity: {0}\n".format(self.velocity)
00535 return s
00536
00537 class JointMimic(object):
00538 """JointMimic node stores information coupling an actuated joint to a not controllable joint"""
00539 def __init__(self, joint_name, multiplier=None, offset=None):
00540 self.joint_name = joint_name
00541 self.multiplier = multiplier
00542 self.offset = offset
00543
00544 @staticmethod
00545 def parse(node):
00546 mimic = JointMimic( node.getAttribute('joint') )
00547 if node.hasAttribute('multiplier'):
00548 mimic.multiplier = float( node.getAttribute('multiplier') )
00549 if node.hasAttribute('offset'):
00550 mimic.offset = float( node.getAttribute('offset') )
00551 return mimic
00552
00553 def to_xml(self, doc):
00554 xml = doc.createElement('mimic')
00555 set_attribute(xml, 'joint', self.joint_name)
00556 set_attribute(xml, 'multiplier', self.multiplier)
00557 set_attribute(xml, 'offset', self.offset)
00558 return xml
00559
00560 def __str__(self):
00561 s = "Joint: {0}\n".format(self.joint_name)
00562 s += "Multiplier: {0}\n".format(self.multiplier)
00563 s += "Offset: {0}\n".format(self.offset)
00564 return s
00565
00566
00567 class Link(object):
00568 """Link node stores information defining the mechanics and the visualization of a link"""
00569 def __init__(self, name, visual=None, inertial=None, collision=None, xacro=None):
00570 self.name = name
00571 self.visual = visual
00572 self.inertial = inertial
00573 self.collision = collision
00574 self.xacro = xacro
00575
00576 @staticmethod
00577 def parse(node, verbose=True):
00578 link = Link(node.getAttribute('name'))
00579 for child in children(node):
00580 if child.localName == 'visual':
00581 link.visual = Visual.parse(child, verbose)
00582 elif child.localName == 'collision':
00583 link.collision = Collision.parse(child, verbose)
00584 elif child.localName == 'inertial':
00585 link.inertial = Inertial.parse(child)
00586 elif child.localName.startswith('xacro'):
00587 link.xacro = xacro
00588 else:
00589 if verbose:
00590 print("Unknown link element '%s'"%child.localName)
00591 return link
00592
00593 def to_xml(self, doc):
00594 xml = doc.createElement("link")
00595 xml.setAttribute("name", self.name)
00596 add( doc, xml, self.visual)
00597 add( doc, xml, self.collision)
00598 add( doc, xml, self.inertial)
00599 if self.xacro is not None:
00600 text = doc.createElement(self.xacro)
00601 xml.appendChild(text)
00602 return xml
00603
00604 def to_openrave_xml(self, doc):
00605 xml = doc.createElement("body")
00606 xml.setAttribute("name", self.name)
00607 add_openrave( doc, xml, self.collision)
00608 add_openrave( doc, xml, self.inertial)
00609 return xml
00610
00611 def __str__(self):
00612 s = "Name: {0}\n".format(self.name)
00613 s += "Inertial:\n"
00614 s += reindent(str(self.inertial), 1) + "\n"
00615 s += "Visual:\n"
00616 s += reindent(str(self.visual), 1) + "\n"
00617 s += "Collision:\n"
00618 s += reindent(str(self.collision), 1) + "\n"
00619 s += "Xacro:\n"
00620 s += reindent(str(self.xacro),1) + "\n"
00621 return s
00622
00623 class Material(object):
00624 """Material node stores visual information of a mechanical material : color, texture"""
00625 def __init__(self, name=None, color=None, texture=None):
00626 self.name = name
00627 self.color = color
00628 self.texture = texture
00629
00630 @staticmethod
00631 def parse(node, verbose=True):
00632 material = Material()
00633 if node.hasAttribute('name'):
00634 material.name = node.getAttribute('name')
00635 for child in children(node):
00636 if child.localName == 'color':
00637 material.color = Color.parse(child)
00638 elif child.localName == 'texture':
00639 material.texture = child.getAttribute('filename')
00640 else:
00641 if verbose:
00642 print("Unknown material element '%s'"%child.localName)
00643
00644 return material
00645
00646 def to_xml(self, doc):
00647 xml = doc.createElement("material")
00648 set_attribute(xml, "name", self.name)
00649 add( doc, xml, self.color )
00650
00651 if self.texture is not None:
00652 text = doc.createElement("texture")
00653 text.setAttribute('filename', self.texture)
00654 xml.appendChild(text)
00655 return xml
00656
00657 def __str__(self):
00658 s = "Name: {0}\n".format(self.name)
00659 s += "Color: {0}\n".format(self.color)
00660 s += "Texture:\n"
00661 s += reindent(str(self.texture), 1)
00662 return s
00663
00664
00665 class Pose(object):
00666 """Pose node stores a cartesian 6-D pose :
00667 xyz : array
00668 rpy : array
00669 """
00670 def __init__(self, position=None, rotation=None):
00671 self.position = array(position)
00672 self.rotation = array(rotation)
00673
00674 @staticmethod
00675 def parse(node):
00676 pose = Pose()
00677 if node.hasAttribute("xyz"):
00678 xyz = node.getAttribute('xyz').split()
00679 pose.position = array(map(float, xyz))
00680 if node.hasAttribute("rpy"):
00681 rpy = node.getAttribute('rpy').split()
00682 pose.rotation = array(map(float, rpy))
00683 return pose
00684
00685 def to_xml(self, doc):
00686 xml = doc.createElement("origin")
00687 set_attribute(xml, 'xyz', self.position)
00688 set_attribute(xml, 'rpy', self.rotation)
00689 return xml
00690
00691 def to_openrave_xml(self, doc):
00692 xml = doc.createElement("translation")
00693 set_content(doc,xml,self.position)
00694 elements=[xml]
00695 if not self.rotation[0] == 0:
00696 rotr = doc.createElement("rotationaxis")
00697 set_content(doc,rotr,[1,0,0,self.rotation[0]*180.0/pi])
00698 elements.append(rotr)
00699 if not self.rotation[1] == 0:
00700 rotp = doc.createElement("rotationaxis")
00701 set_content(doc,rotp,[0,1,0,self.rotation[1]*180.0/pi])
00702 elements.append(rotp)
00703 if not self.rotation[2] == 0:
00704 roty = doc.createElement("rotationaxis")
00705 set_content(doc,roty,[0,0,1,self.rotation[2]*180.0/pi])
00706 elements.append(roty)
00707
00708 return elements
00709
00710 def __str__(self):
00711 return "Position: {0}\nRotation: {1}".format(self.position,
00712 self.rotation)
00713
00714
00715 class SafetyController(object):
00716 def __init__(self, velocity, position=None, lower=None, upper=None):
00717 self.velocity = velocity
00718 self.position = position
00719 self.lower = lower
00720 self.upper = upper
00721
00722 @staticmethod
00723 def parse(node):
00724 sc = SafetyController( float(node.getAttribute('k_velocity')) )
00725 if node.hasAttribute('soft_lower_limit'):
00726 sc.lower = float( node.getAttribute('soft_lower_limit') )
00727 if node.hasAttribute('soft_upper_limit'):
00728 sc.upper = float( node.getAttribute('soft_upper_limit') )
00729 if node.hasAttribute('k_position'):
00730 sc.position = float( node.getAttribute('k_position') )
00731 return sc
00732
00733 def to_xml(self, doc):
00734 xml = doc.createElement('safety_controller')
00735 set_attribute(xml, 'k_velocity', self.velocity)
00736 set_attribute(xml, 'k_position', self.position)
00737 set_attribute(xml, 'soft_upper_limit', self.upper)
00738 set_attribute(xml, 'soft_lower_limit', self.lower)
00739 return xml
00740
00741 def __str__(self):
00742 s = "Safe lower limit: {0}\n".format(self.lower)
00743 s += "Safe upper limit: {0}\n".format(self.upper)
00744 s += "K position: {0}\n".format(self.position)
00745 s += "K velocity: {0}\n".format(self.velocity)
00746 return s
00747
00748
00749 class Visual(object):
00750 """Visual node stores information defining shape and material of the link to be displayed"""
00751 def __init__(self, geometry=None, material=None, origin=None):
00752 self.geometry = geometry
00753 self.material = material
00754 self.origin = origin
00755
00756 @staticmethod
00757 def parse(node, verbose=True):
00758 v = Visual()
00759 for child in children(node):
00760 if child.localName == 'geometry':
00761 v.geometry = Geometry.parse(child, verbose)
00762 elif child.localName == 'origin':
00763 v.origin = Pose.parse(child)
00764 elif child.localName == 'material':
00765 v.material = Material.parse(child, verbose)
00766 else:
00767 if verbose:
00768 print("Unknown visual element '%s'"%child.localName)
00769 return v
00770
00771 def to_xml(self, doc):
00772 xml = doc.createElement("visual")
00773 add( doc, xml, self.geometry )
00774 add( doc, xml, self.origin )
00775 add( doc, xml, self.material )
00776 return xml
00777
00778 def __str__(self):
00779 s = "Origin:\n{0}\n".format(reindent(str(self.origin), 1))
00780 s += "Geometry:\n"
00781 s += reindent(str(self.geometry), 1) + "\n"
00782 s += "Material:\n"
00783 s += reindent(str(self.material), 1) + "\n"
00784 return s
00785
00786
00787 class Actuator(object):
00788 """Actuator node stores information about how a motor controls a joint : used in transmission tags"""
00789 def __init__(self,name=None,hardwareInterface=None,mechanicalReduction=1):
00790 self.name = name
00791 self.hardwareInterface = hardwareInterface
00792 self.mechanicalReduction = mechanicalReduction
00793
00794 @staticmethod
00795 def parse(node, verbose=True):
00796 actuator = Actuator()
00797 if node.hasAttribute('name'):
00798 actuator.name = node.getAttribute('name')
00799 for child in children(node):
00800 if child.localName == 'hardwareInterface':
00801 actuator.hardwareInterface = str(child.childNodes[0].nodeValue)
00802 if child.localName == 'mechanicalReduction':
00803 actuator.mechanicalReduction = str(child.childNodes[0].nodeValue)
00804 else:
00805 print'Unknown actuator element ' + str(child.localName)
00806 return actuator
00807
00808 def to_xml(self, doc):
00809 xml = doc.createElement('actuator')
00810 set_attribute(xml, 'name', self.name)
00811 xml.appendChild(create_element(doc, 'hardwareInterface', self.hardwareInterface))
00812 xml.appendChild(create_element(doc,"mechanicalReduction",str(self.mechanicalReduction)))
00813 return xml
00814
00815 def __str__(self):
00816 s = "Name: {0}\n".format(self.name)
00817 s += "HardwareInterface : {0}\n".format(self.hardwareInterface)
00818 s += "Mechanical Reduction: {0}\n".format(self.mechanicalReduction)
00819 return s
00820
00821
00822 class Transmission(object):
00823 """Transmission node stores information linking a joint to an actuator"""
00824 def __init__(self,name=None,type=None,joint=None,actuator=None):
00825 self.name = name
00826 self.joint = joint
00827 self.actuator = actuator
00828 self.type = type
00829 @staticmethod
00830 def parse(node, verbose=True):
00831 trans = Transmission()
00832 if node.hasAttribute('name'):
00833 trans.name = node.getAttribute('name')
00834 for child in children(node):
00835 if child.localName == 'joint':
00836 trans.joint = child.getAttribute('name')
00837 if child.localName == 'actuator':
00838 trans.actuator = Actuator.parse(child,verbose)
00839 if child.localName == 'type':
00840 trans.type = str(child.childNodes[0].nodeValue)
00841 return trans
00842
00843 def to_xml(self, doc):
00844 xml = doc.createElement('transmission')
00845 set_attribute(xml, 'name', self.name)
00846 xml.appendChild( short(doc, "joint", "name", self.joint) )
00847 xml.appendChild(create_element(doc, 'type', self.type))
00848 add(doc, xml, self.actuator)
00849
00850 return xml
00851
00852 def __str__(self):
00853 s = "Name: {0}\n".format(self.name)
00854 s += "Type: {0}\n".format(self.type)
00855 s += "Joint: {0}\n".format(self.joint)
00856 s += "Actuator:\n"
00857 s += reindent(str(self.actuator), 1) + "\n"
00858
00859 return s
00860
00861
00862
00863
00864 class URDF(object):
00865 """URDF node stores every information about a robot's model"""
00866 ZERO_THRESHOLD=0.000000001
00867 def __init__(self, name=""):
00868 self.name = name
00869 self.elements = []
00870 self.gazebos = {}
00871 self.links = {}
00872 self.joints = {}
00873 self.materials = {}
00874 self.parent_map = {}
00875 self.child_map = {}
00876
00877 @staticmethod
00878 def parse_xml_string(xml_string, verbose=True):
00879 """Parse a string to create a URDF robot structure."""
00880 urdf = URDF()
00881 base = minidom.parseString(xml_string)
00882 robot = children(base)[0]
00883 urdf.name = robot.getAttribute('name')
00884
00885 for node in children(robot):
00886 if node.nodeType is node.TEXT_NODE:
00887 continue
00888 if node.localName == 'joint':
00889 urdf.add_joint( Joint.parse(node, verbose) )
00890 elif node.localName == 'link':
00891 urdf.add_link( Link.parse(node, verbose) )
00892 elif node.localName == 'material':
00893 urdf.add_material(Material.parse(node,verbose))
00894 elif node.localName == 'gazebo':
00895 urdf.add_gazebo(Gazebo.parse(node,verbose))
00896 elif node.localName == 'transmission':
00897 urdf.elements.append( Transmission.parse(node,verbose) )
00898 else:
00899 if verbose:
00900 print("Unknown robot element '%s'"%node.localName)
00901 return urdf
00902
00903 @staticmethod
00904 def load_xml_file(filename, verbose=True):
00905 """Parse a file to create a URDF robot structure."""
00906 f = open(filename, 'r')
00907 return URDF.parse_xml_string(f.read(), verbose)
00908
00909 @staticmethod
00910 def load_from_parameter_server(key = 'robot_description', verbose=True):
00911 """
00912 Retrieve the robot model on the parameter server
00913 and parse it to create a URDF robot structure.
00914
00915 Warning: this requires roscore to be running.
00916 """
00917 import rospy
00918 return URDF.parse_xml_string(rospy.get_param(key), verbose)
00919
00920 def add_link(self, link):
00921 self.elements.append(link)
00922 self.links[link.name] = link
00923
00924 def add_joint(self, joint):
00925 self.elements.append(joint)
00926 self.joints[joint.name] = joint
00927
00928 self.parent_map[ joint.child ] = (joint.name, joint.parent)
00929 if joint.parent in self.child_map:
00930 self.child_map[joint.parent].append( (joint.name, joint.child) )
00931 else:
00932 self.child_map[joint.parent] = [ (joint.name, joint.child) ]
00933
00934 def add_material(self, material):
00935 """
00936 Add a material to the URDF.elements list
00937 """
00938 self.elements.append(material)
00939 self.materials[material.name] = material
00940
00941 def add_gazebo(self, gazebo):
00942 """
00943 Add gazebo data to the URDF.elements list
00944 """
00945
00946 self.elements.append(gazebo)
00947 self.gazebos[gazebo.reference] = gazebo
00948
00949 def get_chain(self, root, tip, joints=True, links=True, fixed=True):
00950 """Based on given link names root and tip, return either a joint or link chain as a list of names."""
00951 chain = []
00952 if links:
00953 chain.append(tip)
00954 link = tip
00955 while link != root:
00956 (joint, parent) = self.parent_map[link]
00957 if joints:
00958 if fixed or self.joints[joint].joint_type != 'fixed':
00959 chain.append(joint)
00960 if links:
00961 chain.append(parent)
00962 link = parent
00963 chain.reverse()
00964 return chain
00965
00966 def get_root(self):
00967 root = None
00968 for link in self.links:
00969 if link not in self.parent_map:
00970 assert root is None, "Multiple roots detected, invalid URDF."
00971 root = link
00972 assert root is not None, "No roots detected, invalid URDF."
00973 return root
00974
00975 def to_xml(self,orderbytree=False,orderbytype=False):
00976 doc = Document()
00977 root = doc.createElement("robot")
00978 doc.appendChild(root)
00979 root.setAttribute("name", self.name)
00980
00981 baselink=self.parent_map
00982 if orderbytree:
00983
00984 pass
00985 if orderbytype:
00986 pass
00987
00988 for element in self.elements:
00989 root.appendChild(element.to_xml(doc))
00990
00991 return doc.toprettyxml()
00992
00993 def write_xml(self,outfile=None):
00994 if outfile is None:
00995 outfile=self.name+'.urdf'
00996
00997 self.write_reformatted(self.to_xml(),outfile)
00998
00999 def make_openrave_kinbody(self,doc):
01000 kinbody = doc.createElement("kinbody")
01001 doc.appendChild(kinbody)
01002 kinbody.setAttribute("name", self.name)
01003
01004 for element in self.elements:
01005 kinbody.appendChild(element.to_openrave_xml(doc))
01006
01007
01008
01009 for j in self.joints.keys():
01010 for l in kinbody.getElementsByTagName('body'):
01011 if l.getAttribute('name')== self.joints[j].child:
01012
01013 l.appendChild( create_element(doc, "offsetfrom" , self.joints[j].parent) )
01014 add_openrave(doc,l,self.joints[j].origin)
01015 break
01016
01017
01018 for j in self.joints.values():
01019 kinbody.appendChild(create_element(doc,"adjacent",[j.parent, j.child]))
01020
01021
01022 badjoints={'LAR':'LKP','LWR':'LWY','LSY':'LSP','LHY':'LHP',
01023 'RAR':'RKP','RWR':'RWY','RSY':'RSP','RHY':'RHP',
01024 'NK1':'Torso'}
01025 for k,v in badjoints.items():
01026
01027 b1=None
01028 b2=None
01029 for b in kinbody.getElementsByTagName('body'):
01030 if re.search(k,b.getAttribute('name')):
01031 b1=b.getAttribute('name')
01032 if re.search(v,b.getAttribute('name')):
01033 b2=b.getAttribute('name')
01034 if b1 and b2:
01035 kinbody.appendChild(create_element(doc,"adjacent",[b1, b2]))
01036
01037 return kinbody
01038
01039 def to_openrave_xml(self):
01040 doc = Document()
01041 root = doc.createElement("robot")
01042 doc.appendChild(root)
01043 root.setAttribute("name", self.name)
01044 root.appendChild(self.make_openrave_kinbody(doc))
01045
01046 return doc.toprettyxml()
01047
01048 def write_reformatted(self,data,name):
01049 if name is None:
01050 name=self.name
01051 outdata=re.sub(r'\t',' ',data)
01052 with open(name,'w+') as f:
01053 f.write(outdata)
01054
01055 def write_openrave_files(self,outname=None,writerobot=False):
01056 if outname is None:
01057 outname=self.name
01058
01059 kinfile=outname+'.kinbody.xml'
01060
01061 if writerobot:
01062 robotfile=outname+'.robot.xml'
01063 doc = Document()
01064 root = doc.createElement("robot")
01065 doc.appendChild(root)
01066 root.setAttribute("name", outname)
01067 kinbody = doc.createElement("kinbody")
01068 root.appendChild(kinbody)
01069 kinbody.setAttribute("name", outname)
01070 kinbody.setAttribute("file", kinfile)
01071 self.write_reformatted(doc.toprettyxml(),robotfile)
01072
01073 doc2 = Document()
01074 doc2.appendChild(self.make_openrave_kinbody(doc2))
01075
01076 self.write_reformatted(doc2.toprettyxml(),kinfile)
01077
01078
01079 def __str__(self):
01080 s = "Name: {0}\n".format(self.name)
01081 s += "\n"
01082 s += "Links:\n"
01083 if len(self.links) == 0:
01084 s += "None\n"
01085 else:
01086 for k, v in self.links.iteritems():
01087 s += "- Link '{0}':\n{1}\n".format(k, reindent(str(v), 1))
01088 s += "\n"
01089 s += "Joints:\n"
01090 if len(self.joints) == 0:
01091 s += "None\n"
01092 else:
01093 for k, v in self.joints.iteritems():
01094 s += "- Joint '{0}':\n{1}\n".format(k, reindent(str(v), 1))
01095 s += "\n"
01096 s += "Materials:\n"
01097 if len(self.materials) == 0:
01098 s += "None\n"
01099 else:
01100 for k, v in self.materials.iteritems():
01101 s += "- Material '{0}':\n{1}\n".format(k, reindent(str(v), 1))
01102
01103 return s
01104
01105 def walk_chain(self,link,branchorder=None):
01106 """Walk along the first branch of urdf tree to find last link. Optionally specify which fork to take globally)."""
01107 child=link
01108 if branchorder is None:
01109 branchorder=0
01110
01111 while self.child_map.has_key(child):
01112 children=self.child_map[link]
01113 l=len(children)
01114 child=children[min(branchorder,l-1)][1]
01115
01116 return child
01117
01118 def rename_link(self,link,newlink):
01119 """Rename a link, updating all internal references to the name, and
01120 removing the old name from the links dict."""
01121 self.links[link].name=newlink
01122 self.links[newlink]=self.links[link]
01123 self.links.pop(link)
01124 for k,v in self.parent_map.items():
01125 if k==link:
01126 self.parent_map[newlink]=v
01127 self.parent_map.pop(k)
01128 k=newlink
01129 if v[0]==link:
01130 new0=newlink
01131 v=(new0,v[1])
01132 if v[1]==link:
01133 new1=newlink
01134 v=(v[0],new1)
01135 self.parent_map[k]=v
01136 for k,v in self.child_map.items():
01137 if k==link:
01138 self.child_map[newlink]=v
01139 self.child_map.pop(k)
01140 k=newlink
01141 vnew=[]
01142 for el in v:
01143 if el[1]==link:
01144 el=(el[0],newlink)
01145 vnew.append(el)
01146
01147 self.child_map[k]=vnew
01148 for n,j in self.joints.items():
01149 if j.parent==link:
01150 j.parent=newlink
01151 if j.child==link:
01152 j.child=newlink
01153
01154
01155 def rename_joint(self,joint,newjoint):
01156 """Find a joint and rename it to newjoint, updating all internal
01157 structures and mimic joints with the new name. Removes the old joint
01158 reference from the joints list."""
01159 self.joints[joint].name=newjoint
01160 self.joints[newjoint]=self.joints[joint]
01161 self.joints.pop(joint)
01162 for k,v in self.child_map.items():
01163 vnew=[]
01164 for el in v:
01165 if el[0]==joint:
01166 el=(newjoint,el[1])
01167 print el
01168 vnew.append(el)
01169 self.child_map[k]=vnew
01170 for k,v in self.parent_map.items():
01171 if v[0]==joint:
01172 v=(newjoint,v[1])
01173 print el
01174 self.parent_map[k]=v
01175 for n,j in self.joints.items():
01176 if j.mimic is not None:
01177 j.mimic.joint_name=newjoint if j.mimic.joint_name==joint else j.mimic.joint_name
01178
01179 def copy_joint(self,joint,f,r):
01180 """Copy and rename a joint and it's parent/child by the f / r strings. Assumes links exist.
01181 Note that it renames the parent and child, so use this with copy_link"""
01182 newjoint=copy.deepcopy(self.joints[joint])
01183 newjoint.name=re.sub(f,r,newjoint.name)
01184 newjoint.parent=re.sub(f,r,newjoint.parent)
01185 newjoint.child=re.sub(f,r,newjoint.child)
01186
01187 self.add_joint(newjoint)
01188 return newjoint
01189
01190 def move_chain_with_rottrans(self,root,tip,rpy,xyz,f,r):
01191 """Find and rename a kinematic chain based on the given root and top
01192 names. Renames all links and joints according to find and replace
01193 terms.
01194 """
01195 linkchain=self.get_chain(root,tip,links=True,joints=False)
01196 jointchain=self.get_chain(root,tip,joints=True,links=False)
01197 print linkchain
01198 print jointchain
01199
01200 for l in linkchain[1:]:
01201 newlink=re.sub(f,r,l)
01202 self.rename_link(l,newlink)
01203 for j in jointchain:
01204 newname=re.sub(f,r,j)
01205 self.rename_joint(j,newname)
01206 self.joints[newname].origin.position+=array(xyz)
01207 self.joints[newname].origin.rotation+=array(rpy)
01208 if self.joints[newname].mimic is not None:
01209 self.joints[newname].mimic.joint_name=re.sub(f,r,self.joints[newname].mimic.joint_name)
01210
01211 def copy_chain_with_rottrans(self,root,tip,rpy,xyz,f,r,mir_ax=None):
01212 """Copy a kinematic chain, renaming joints and links according to a regular expression.
01213
01214 Note that this is designed to work with the Hubo joint / body
01215 convention, which has an easy pattern for joint and body names. If your
01216 model has joints and links that are not systematically named, this
01217 function won't be much use.
01218 """
01219
01220 linkchain=self.get_chain(root,tip,links=True,joints=False)
01221 jointchain=self.get_chain(root,tip,joints=True,links=False)
01222 print linkchain
01223 print jointchain
01224 newjoints=[]
01225 newlinks=[]
01226 for l in linkchain[1:]:
01227 newlink=copy.deepcopy(self.links[l])
01228 newlink.name=re.sub(f,r,newlink.name)
01229 if newlink.collision is not None:
01230 newlink.collision.geometry.filename=re.sub(f,r,newlink.collision.geometry.filename)
01231 newlink.visual.geometry.filename=re.sub(f,r,newlink.visual.geometry.filename)
01232 self.add_link(newlink)
01233 newlinks.append(newlink)
01234 if mir_ax == 'x':
01235 newlink.inertial.matrix['ixy']*=-1.
01236 newlink.inertial.matrix['ixz']*=-1.
01237 newlink.inertial.origin.position[0]*=-1.
01238 if mir_ax == 'y':
01239 newlink.inertial.matrix['ixy']*=-1.
01240 newlink.inertial.matrix['iyz']*=-1.
01241 newlink.inertial.origin.position[1]*=-1.
01242 if mir_ax == 'z':
01243 newlink.inertial.matrix['ixz']*=-1.
01244 newlink.inertial.matrix['iyz']*=-1.
01245 newlink.inertial.origin.position[2]*=-1.
01246
01247 for j in jointchain:
01248 newjoints.append(self.copy_joint(j,f,r))
01249 if mir_ax == 'x':
01250 newjoints[-1].origin.position[0]*=-1.0
01251 newjoints[-1].origin.rotation[1]*=-1.0
01252 newjoints[-1].origin.rotation[2]*=-1.0
01253 if mir_ax == 'y':
01254 newjoints[-1].origin.position[1]*=-1.0
01255 newjoints[-1].origin.rotation[0]*=-1.0
01256 newjoints[-1].origin.rotation[2]*=-1.0
01257 if mir_ax == 'z':
01258 newjoints[-1].origin.position[2]*=-1.0
01259 newjoints[-1].origin.rotation[0]*=-1.0
01260 newjoints[-1].origin.rotation[1]*=-1.0
01261
01262 if mir_ax =='rotx':
01263 newjoints[0].origin.position[1]*=-1.0
01264
01265 for j in newjoints:
01266 if j.mimic is not None:
01267 j.mimic.joint_name=re.sub(f,r,j.mimic.joint_name)
01268
01269 newjoints[0].origin.position+=array(xyz)
01270 newjoints[0].origin.rotation+=array(rpy)
01271
01272 def fix_mesh_case(self):
01273 for l in self.links:
01274 fname=l.collision.geometry.filename
01275 l.collision.geometry.filename=re.sub(r'\.[Ss][Tt][Ll]','.stl',fname)
01276
01277
01278 def update_mesh_paths(self,package_name):
01279 """Search and replace package paths in urdf with chosen package name"""
01280 for n,l in self.links.items():
01281
01282 for g in [l.collision.geometry,l.visual.geometry]:
01283
01284 meshfile=g.filename.split('/')[-1]
01285 newpath=[package_name,'meshes',meshfile]
01286 cleanpath=re.sub('/+','/','/'.join(newpath))
01287 g.filename='package://'+cleanpath
01288 l.collision.geometry.filename=re.sub('Body','convhull',l.collision.geometry.filename)
01289
01290 def apply_default_limits(self,effort,vel,lower,upper,mask=None):
01291 """Apply default limits to all joints and convert continous joints to revolute. Ignores fixed and other joint types."""
01292 for n,j in self.joints.items():
01293 if mask is None or re.search(mask,n):
01294 if j.joint_type==Joint.CONTINUOUS or j.joint_type==Joint.REVOLUTE:
01295 j.limits=JointLimit(effort,vel,lower,upper)
01296 j.joint_type=Joint.REVOLUTE
01297
01298 if __name__ == '__main__':
01299
01300
01301
01302
01303
01304 try:
01305 filename=sys.argv[1]
01306 except IndexError:
01307 print "Please supply a URDF filename to convert!"
01308
01309 try:
01310 outname=sys.argv[2]
01311 except IndexError:
01312 outname=None
01313
01314 model=URDF.load_xml_file(filename)
01315
01316 model.write_openrave_files(outname)
01317