urdf.py
Go to the documentation of this file.
00001 import xml.dom.minidom
00002 from xml.dom.minidom import Document
00003 
00004 def add(doc, base, element):
00005     if element is not None:
00006         base.appendChild( element.to_xml(doc) )
00007 
00008 def pfloat(x):
00009     return ("%.12f"%x).rstrip('0').rstrip('.')
00010 
00011 def set_attribute(node, name, value):
00012     if value is None:
00013         return
00014     if type([]) == type(value) or type(()) == type(value):
00015         value = " ".join( [pfloat(a) for a in value] )
00016     elif type(value) == type(0.0):
00017         value = pfloat(value)
00018     elif type(value) != type(''):
00019         value = str(value)
00020     node.setAttribute(name, value)
00021 
00022 def short(doc, name, key, value):
00023     element = doc.createElement(name)
00024     set_attribute(element, key, value)
00025     return element
00026 
00027 def children(node):
00028     children = []
00029     for child in node.childNodes:
00030         if child.nodeType is node.TEXT_NODE or child.nodeType is node.COMMENT_NODE:
00031             continue
00032         else:
00033             children.append(child)
00034     return children
00035 
00036 class Text:
00037     def __init__(self, name, value=""):
00038         self.name = name
00039         self.value = value
00040 
00041     @staticmethod
00042     def parse(node):
00043         t = Text(node.localName, node.childNodes[0].nodeValue)
00044         return t
00045 
00046     def to_xml(self, doc):
00047         xml = doc.createElement(self.name)
00048         xml.appendChild( doc.createTextNode(str(self.value)) )
00049 
00050 class Collision:
00051     def __init__(self, geometry=None, origin=None):
00052         self.geometry = geometry
00053         self.origin = origin
00054 
00055     @staticmethod
00056     def parse(node):
00057         c = Collision()
00058         for child in children(node):
00059             if child.localName == 'geometry':
00060                 c.geometry = Geometry.parse(child)
00061             elif child.localName == 'origin':
00062                 c.origin = Pose.parse(child)
00063             elif child.localName == 'material':
00064                 c.material = Material.parse(child)
00065             elif child.localName == 'verbose':
00066                 pass
00067             else:
00068                 raise Exception("Unknown collision element '%s'"%child.localName)
00069         return c
00070 
00071     def to_xml(self, doc):
00072         xml = doc.createElement("collision")
00073         add(doc, xml, self.origin)
00074         add(doc, xml, self.geometry)
00075         return xml
00076 
00077 class Color:
00078     def __init__(self, r=0.0, g=0.0, b=0.0, a=0.0):
00079         self.rgba=(r,g,b,a)
00080 
00081     @staticmethod
00082     def parse(node):
00083         rgba = node.getAttribute("rgba").split()
00084         (r,g,b,a) = [ float(x) for x in rgba ] 
00085         return Color(r,g,b,a)
00086 
00087     def to_xml(self, doc):
00088         xml = doc.createElement("color")
00089         set_attribute(xml, "rgba", self.rgba)
00090         return xml
00091 
00092 class Dynamics:
00093     def __init__(self, damping=None, friction=None):
00094         self.damping = damping
00095         self.friction = friction
00096 
00097     @staticmethod
00098     def parse(node):
00099         d = Dynamics()
00100         if node.hasAttribute('damping'):
00101             d.damping = node.getAttribute('damping')
00102         if node.hasAttribute('friction'):
00103             d.friction = node.getAttribute('friction')
00104         return d
00105 
00106     def to_xml(self, doc):
00107         xml = doc.createElement('dynamics')
00108         set_attribute(xml, 'damping', self.damping)
00109         set_attribute(xml, 'friction', self.friction)
00110         return xml
00111 
00112 class Geometry:
00113     def __init__(self):
00114         None
00115 
00116     @staticmethod
00117     def parse(node):
00118         shape = children(node)[0]
00119         if shape.localName=='box':
00120             return Box.parse(shape)
00121         elif shape.localName=='cylinder':
00122             return Cylinder.parse(shape)
00123         elif shape.localName=='sphere':
00124             return Sphere.parse(shape)
00125         elif shape.localName=='mesh':
00126             return Mesh.parse(shape)
00127         else:
00128             raise Exception("Unknown shape %s"%child.localName)
00129 
00130 class Box(Geometry):
00131     def __init__(self, dims=None):
00132         if dims is None:
00133             self.dims = None
00134         else:
00135             self.dims = (dims[0], dims[1], dims[2])
00136 
00137     @staticmethod
00138     def parse(node):
00139         dims = node.getAttribute('size').split()
00140         return Box([float(a) for a in dims])
00141 
00142     def to_xml(self, doc):
00143         xml = doc.createElement("box")
00144         set_attribute(xml, "size", self.dims)
00145         geom = doc.createElement('geometry')
00146         geom.appendChild(xml)
00147         return geom
00148 
00149 class Cylinder(Geometry):
00150     def __init__(self, radius=0.0, length=0.0):
00151         self.radius = radius
00152         self.length = length
00153 
00154     @staticmethod
00155     def parse(node):
00156         r = node.getAttribute('radius')
00157         l = node.getAttribute('length')
00158         return Cylinder(float(r), float(l))
00159 
00160     def to_xml(self, doc):
00161         xml = doc.createElement("cylinder")
00162         set_attribute(xml, "radius", self.radius)
00163         set_attribute(xml, "length", self.length)
00164         geom = doc.createElement('geometry')
00165         geom.appendChild(xml)
00166         return geom
00167 
00168 class Sphere(Geometry):
00169     def __init__(self, radius=0.0):
00170         self.radius = radius
00171 
00172     @staticmethod
00173     def parse(node):
00174         r = node.getAttribute('radius')
00175         return Sphere(float(r))
00176 
00177     def to_xml(self, doc):
00178         xml = doc.createElement("sphere")
00179         set_attribute(xml, "radius", self.radius)
00180         geom = doc.createElement('geometry')
00181         geom.appendChild(xml)
00182         return geom
00183 
00184 class Mesh(Geometry):
00185     def __init__(self, filename=None, scale=None):
00186         self.filename = filename
00187         self.scale = scale
00188 
00189     @staticmethod
00190     def parse(node):
00191         fn = node.getAttribute('filename')
00192         s = node.getAttribute('scale')
00193         if s == "":
00194             s = None
00195         else:
00196             xyz = node.getAttribute('scale').split()
00197             scale = map(float, xyz)
00198         return Mesh(fn, s)
00199 
00200     def to_xml(self, doc):
00201         xml = doc.createElement("mesh")
00202         set_attribute(xml, "filename", self.filename)
00203         set_attribute(xml, "scale", self.scale)
00204         geom = doc.createElement('geometry')
00205         geom.appendChild(xml)
00206         return geom
00207 
00208 class Inertial:
00209     def __init__(self, ixx=0.0, ixy=0.0, ixz=0.0, iyy=0.0, iyz=0.0, izz=0.0, mass=0.0, origin=None):
00210         self.matrix = {}
00211         self.matrix['ixx'] = ixx
00212         self.matrix['ixy'] = iyy
00213         self.matrix['ixz'] = ixz
00214         self.matrix['iyy'] = iyy
00215         self.matrix['iyz'] = iyz
00216         self.matrix['izz'] = izz
00217         self.mass = mass
00218         self.origin = origin
00219 
00220     @staticmethod
00221     def parse(node):
00222         inert = Inertial()
00223         for child in children(node):
00224             if child.localName=='inertia':
00225                 for v in ['ixx', 'ixy', 'ixz', 'iyy', 'iyz', 'izz']:
00226                     inert.matrix[v] = float(child.getAttribute(v))
00227             elif child.localName=='mass':
00228                 inert.mass = float(child.getAttribute('value'))
00229             elif child.localName == 'origin':
00230                 inert.origin = Pose.parse(child)
00231         return inert
00232 
00233     def to_xml(self, doc):
00234         xml = doc.createElement("inertial")
00235 
00236         xml.appendChild(short(doc, "mass", "value", self.mass))
00237         add(doc, xml, self.origin)
00238 
00239         inertia = doc.createElement("inertia")
00240         for (n,v) in self.matrix.items():
00241             set_attribute(inertia, n, v)
00242         xml.appendChild(inertia)
00243         return xml
00244 
00245 class Joint:
00246     def __init__(self, name, parent, child, joint_type, axis=None, origin=None, limits=None,
00247                     dynamics=None, safety=None, calibration=None, mimic=None):
00248         self.name = name
00249         self.parent = parent
00250         self.child = child
00251         self.joint_type = joint_type
00252         self.axis = axis
00253         self.origin = origin
00254         self.limits = limits
00255         self.dynamics = dynamics
00256         self.safety = safety
00257         self.calibration = calibration
00258         self.mimic = mimic
00259 
00260     @staticmethod
00261     def parse(node):
00262         joint = Joint(node.getAttribute('name'), None, None, node.getAttribute('type'))
00263         for child in children(node):
00264             if child.localName == 'parent':
00265                 joint.parent = child.getAttribute('link')
00266             elif child.localName == 'child':
00267                 joint.child = child.getAttribute('link')
00268             elif child.localName == 'axis':
00269                 joint.axis = child.getAttribute('xyz')
00270             elif child.localName == 'origin':
00271                 joint.origin = Pose.parse(child)
00272             elif child.localName == 'limit':
00273                 joint.limits = JointLimit.parse(child)
00274             elif child.localName == 'dynamics':
00275                 joint.dynamics = Dynamics.parse(child)
00276             elif child.localName == 'safety_controller':
00277                 joint.safety = SafetyController.parse(child)
00278             elif child.localName == 'calibration':
00279                 joint.calibration = JointCalibration.parse(child)
00280             elif child.localName == 'mimic':
00281                 joint.mimic = JointMimic.parse(child)
00282             else:
00283                 raise Exception("Unknown joint element '%s'"%child.localName)
00284         return joint
00285 
00286     def to_xml(self, doc):
00287         xml = doc.createElement("joint")
00288         set_attribute(xml, "name", self.name)
00289         set_attribute(xml, "type", self.joint_type)
00290         xml.appendChild( short(doc, "parent", "link", self.parent) )
00291         xml.appendChild( short(doc, "child" , "link", self.child ) )
00292         add(doc, xml, self.origin)
00293         if self.axis is not None:
00294             xml.appendChild( short(doc, "axis", "xyz", self.axis) )
00295         add(doc, xml, self.limits)
00296         add(doc, xml, self.dynamics)
00297         add(doc, xml, self.safety)
00298         add(doc, xml, self.calibration)
00299         return xml
00300 
00301 class JointCalibration:
00302     def __init__(self, rising=None, falling=None):
00303         self.rising = rising
00304         self.falling = falling
00305 
00306     @staticmethod
00307     def parse(node):
00308         jc = JointCalibration()
00309         if node.hasAttribute('rising'):
00310             jc.rising = float( node.getAttribute('rising') )
00311         if node.hasAttribute('falling'):
00312             jc.falling = float( node.getAttribute('falling') )
00313         return jc
00314 
00315     def to_xml(self, doc):
00316         xml = doc.createElement('calibration')
00317         set_attribute(xml, 'rising', self.rising)
00318         set_attribute(xml, 'falling', self.falling)
00319         return xml
00320 
00321 class JointLimit:
00322     def __init__(self, effort, velocity, lower=None, upper=None):
00323         self.effort = effort
00324         self.velocity = velocity
00325         self.lower = lower
00326         self.upper = upper
00327 
00328     @staticmethod
00329     def parse(node):
00330         jl = JointLimit( float( node.getAttribute('effort') ) ,
00331                          float( node.getAttribute('velocity')))
00332         if node.hasAttribute('lower'):
00333             jl.lower = float( node.getAttribute('lower') )
00334         if node.hasAttribute('upper'):
00335             jl.upper = float( node.getAttribute('upper') )
00336         return jl
00337 
00338     def to_xml(self, doc):
00339         xml = doc.createElement('limit')
00340         set_attribute(xml, 'effort', self.effort) 
00341         set_attribute(xml, 'velocity', self.velocity) 
00342         set_attribute(xml, 'lower', self.lower) 
00343         set_attribute(xml, 'upper', self.upper) 
00344         return xml
00345 
00346     def __str__(self):
00347         if self.lower is not None:
00348             return "[%f, %f]"%(self.lower, self.upper)
00349         else:
00350             return "limit"
00351         
00352 class JointMimic:
00353     def __init__(self, joint_name, multiplier=None, offset=None):
00354         self.joint_name = joint_name
00355         self.multiplier = multiplier
00356         self.offset = offset
00357 
00358     @staticmethod
00359     def parse(node):
00360         mimic = JointMimic( node.getAttribute('joint_name') ) 
00361         if node.hasAttribute('multiplier'):
00362             mimic.multiplier = float( node.getAttribute('multiplier') )
00363         if node.hasAttribute('offset'):
00364             mimic.offset = float( node.getAttribute('offset') )
00365         return mimic
00366 
00367     def to_xml(self, doc):
00368         xml = doc.createElement('mimic')
00369         set_attribute(xml, 'joint_name', self.joint_name) 
00370         set_attribute(xml, 'multiplier', self.multiplier) 
00371         set_attribute(xml, 'offset', self.offset) 
00372         return xml
00373 
00374 class Link:
00375     def __init__(self, name, visual=None, inertial=None, collision=None):
00376         self.name = name
00377         self.type = None
00378         self.visual = visual
00379         self.inertial=inertial
00380         self.collision=collision
00381 
00382     @staticmethod
00383     def parse(node):
00384         link = Link(node.getAttribute('name'))
00385         link.type = node.getAttribute('type')
00386         for child in children(node):
00387             if child.localName == 'visual':
00388                 link.visual = Visual.parse(child)
00389             elif child.localName == 'collision':
00390                 link.collision = Collision.parse(child)
00391             elif child.localName == 'inertial':
00392                 link.inertial = Inertial.parse(child)
00393             else:
00394                 raise Exception("Unknown link element '%s'"%child.localName)
00395         return link
00396 
00397     def to_xml(self, doc):
00398         xml = doc.createElement("link")
00399         xml.setAttribute("name", self.name)
00400         if self.type != "":
00401             xml.setAttribute("type", self.type)
00402         add( doc, xml, self.inertial)
00403         add( doc, xml, self.visual)
00404         add( doc, xml, self.collision)
00405         return xml
00406 
00407 class Material:
00408     def __init__(self, name=None, color=None, texture=None):
00409         self.name = name
00410         self.color = color
00411         self.texture = texture
00412 
00413     @staticmethod
00414     def parse(node):
00415         material = Material()
00416         if node.hasAttribute('name'):
00417             material.name = node.getAttribute('name')
00418         for child in children(node):
00419             if child.localName == 'color':
00420                 material.color = Color.parse(child)
00421             elif child.localName == 'texture':
00422                 material.texture = child.getAttribute('filename')
00423             else:
00424                 raise Exception("Unknown material element '%s'"%child.localName)
00425 
00426         return material
00427 
00428     def to_xml(self, doc):
00429         xml = doc.createElement("material")
00430         set_attribute(xml, "name", self.name)
00431         add( doc, xml, self.color )
00432 
00433         if self.texture is not None:
00434             text = doc.createElement("texture")
00435             text.setAttribute('filename', self.texture)
00436             xml.appendChild(text)
00437         return xml
00438 
00439 class Pose:
00440     def __init__(self, position=None, rotation=None):
00441         self.position = position
00442         self.rotation = rotation
00443 
00444     @staticmethod
00445     def parse(node):
00446         pose = Pose()
00447         if node.hasAttribute("xyz"):
00448             xyz = node.getAttribute('xyz').split()
00449             pose.position = map(float, xyz)
00450         if node.hasAttribute("rpy"):
00451             rpy = node.getAttribute('rpy').split()
00452             pose.rotation = map(float, rpy)
00453         return pose
00454 
00455     def to_xml(self, doc):
00456         xml = doc.createElement("origin")
00457         set_attribute(xml, 'xyz', self.position)
00458         set_attribute(xml, 'rpy', self.rotation)
00459         return xml
00460 
00461 class SafetyController:
00462     def __init__(self, velocity, position=None, lower=None, upper=None):
00463         self.velocity = velocity
00464         self.position = position
00465         self.lower = lower
00466         self.upper = upper
00467 
00468     @staticmethod
00469     def parse(node):
00470         sc = SafetyController( float(node.getAttribute('k_velocity')) )
00471         if node.hasAttribute('soft_lower_limit'):
00472             sc.lower = float( node.getAttribute('soft_lower_limit') )
00473         if node.hasAttribute('soft_upper_limit'):
00474             sc.upper = float( node.getAttribute('soft_upper_limit') )
00475         if node.hasAttribute('k_position'):
00476             sc.position = float( node.getAttribute('k_position') )
00477         return sc
00478 
00479     def to_xml(self, doc):
00480         xml = doc.createElement('safety_controller')
00481         set_attribute(xml, 'k_velocity', self.velocity)
00482         set_attribute(xml, 'k_position', self.position)
00483         set_attribute(xml, 'soft_upper_limit', self.upper)
00484         set_attribute(xml, 'soft_lower_limit', self.lower)
00485         return xml
00486         
00487 
00488 class Visual:
00489     def __init__(self, geometry=None, material=None, origin=None):
00490         self.geometry = geometry
00491         self.material = material
00492         self.origin = origin
00493 
00494     @staticmethod
00495     def parse(node):
00496         v = Visual()
00497         for child in children(node):
00498             if child.localName == 'geometry':
00499                 v.geometry = Geometry.parse(child)
00500             elif child.localName == 'origin':
00501                 v.origin = Pose.parse(child)
00502             elif child.localName == 'material':
00503                 v.material = Material.parse(child)
00504             else:
00505                 raise Exception("Unknown visual element '%s'"%child.localName)
00506         return v
00507 
00508     def to_xml(self, doc):
00509         xml = doc.createElement("visual")
00510         add( doc, xml, self.origin )
00511         add( doc, xml, self.geometry )
00512         add( doc, xml, self.material )
00513         return xml
00514 
00515 class Transmission:
00516     def __init__(self, name):
00517         self.name = name
00518         self.type = None
00519         self.actuator = None
00520         self.joint = None
00521         self.reduction = None
00522         self.children = list()
00523 
00524     @staticmethod
00525     def parse(node):
00526         t = Transmission(node.getAttribute('name'))
00527         t.type = node.getAttribute('type')
00528         for child in children(node):
00529             if child.localName == 'actuator':
00530                 t.actuator = child.getAttribute("name")
00531             elif child.localName == 'joint':
00532                 t.joint = child.getAttribute("name")
00533             elif child.localName == 'mechanicalReduction':
00534                 t.reduction = float(child.childNodes[0].nodeValue)
00535             else:
00536                 t.children.append(child)
00537 #               raise Exception("Unknown transmission element '%s'"%child.localName)
00538         return t
00539 
00540     def to_xml(self, doc):
00541         xml = doc.createElement("transmission")
00542         set_attribute(xml, "name", self.name)
00543         if self.type is not None:
00544             set_attribute(xml, "type", self.type)        
00545 
00546         if self.actuator is not None:
00547             actuator = doc.createElement("actuator")
00548             actuator.setAttribute('name', self.actuator)
00549             xml.appendChild(actuator)
00550         if self.joint is not None:
00551             joint = doc.createElement("joint")
00552             joint.setAttribute('name', self.joint)
00553             xml.appendChild(joint)
00554         if self.reduction is not None:
00555             reduction = doc.createElement("mechanicalReduction")
00556             reduction.appendChild( doc.createTextNode(pfloat(self.reduction)) )
00557             xml.appendChild(reduction)
00558         for child in self.children:
00559             xml.appendChild(child)
00560         return xml
00561 
00562 class Gazebo:
00563     def __init__(self):
00564         self.attributes = dict()
00565         self.children = list()
00566     
00567     @staticmethod
00568     def parse(node):
00569         g = Gazebo()
00570         g.name = node.nodeName
00571         for name, attr in node._attrs.items():
00572             g.attributes[name] = attr.value
00573         for child in children(node):
00574             if child.nodeName.find("sensor") == 0 or child.nodeName.find("controller") == 0 or \
00575                child.nodeName.find("body") == 0 or child.nodeName.find("joint") == 0:
00576                 g.children.append( Gazebo.parse(child) )
00577             else:
00578                 g.children.append(child)
00579         return g
00580 
00581     def to_xml(self, doc):
00582         xml = doc.createElement(self.name)
00583         for key, value in self.attributes.items():
00584             set_attribute(xml, key, value)
00585         for child in self.children:
00586             if "to_xml" in dir(child):
00587                 xml.appendChild(child.to_xml(doc))
00588             else:
00589                 xml.appendChild(child)
00590         return xml
00591 
00592 class URDF:
00593     def __init__(self, name=""):
00594         self.name = name
00595         self.elements = []
00596         self.links = {}
00597         self.joints = {}
00598         self.materials = {}
00599         self.transmissions = {}
00600 
00601         self.parent_map = {}
00602         self.child_map = {}
00603 
00604     def parse(self, xml_string):
00605         base = xml.dom.minidom.parseString(xml_string)
00606         robot = children(base)[0]
00607         self.name = robot.getAttribute('name')
00608         self.robot_attributes = robot.attributes
00609         for node in children(robot):
00610             if node.nodeType is node.TEXT_NODE:
00611                 print node.value
00612             if node.localName == 'joint':
00613                 self.add_joint( Joint.parse(node) )
00614             elif node.localName == 'link':
00615                 self.add_link( Link.parse(node) )
00616             elif node.localName == 'material':
00617                 self.elements.append( Material.parse(node) )
00618             elif node.localName == 'gazebo':
00619                 self.elements.append( Gazebo.parse(node) )
00620             elif node.localName == 'transmission':
00621                 self.add_transmission( Transmission.parse(node) )
00622             else:
00623                 self.elements.append(node)
00624                 print "Unknown robot element '%s'"%node.localName
00625                 #raise Exception("Unknown robot element '%s'"%node.localName)
00626         return self
00627 
00628     def load(self, filename):
00629         f = open(filename, 'r')
00630         return self.parse(f.read())
00631 
00632     def add_link(self, link):
00633         self.elements.append(link)
00634         self.links[link.name] = link
00635 
00636     def add_joint(self, joint):
00637         self.elements.append(joint)
00638         self.joints[joint.name] = joint
00639 
00640         self.parent_map[ joint.child ] = (joint.name, joint.parent)
00641         if joint.parent in self.child_map:
00642             self.child_map[joint.parent].append( (joint.name, joint.child) )
00643         else:
00644             self.child_map[joint.parent] = [ (joint.name, joint.child) ]
00645 
00646     def add_transmission(self, transmission):
00647         self.elements.append(transmission)
00648         self.transmissions[transmission.name] = transmission
00649 
00650     def get_chain(self, root, tip, joints=True, links=True):
00651         chain = []
00652         if links:
00653             chain.append(tip)
00654         link = tip
00655         while link != root:
00656             (joint, parent) = self.parent_map[link]
00657             if joints:
00658                 chain.append(joint)
00659             if links:
00660                 chain.append(parent)
00661             link = parent
00662         chain.reverse()
00663         return chain
00664 
00665     def to_xml(self):
00666         doc = Document()
00667         root = doc.createElement("robot")
00668         doc.appendChild(root)
00669         for i in range(self.robot_attributes.length):
00670             root.setAttribute(self.robot_attributes.item(i).name, self.robot_attributes.item(i).value)
00671         for element in self.elements:
00672             try:
00673                 root.appendChild(element.to_xml(doc))
00674             except:
00675                 root.appendChild(element)
00676 
00677         return doc.toprettyxml()
00678 
00679 def fixed_writexml(self, writer, indent="", addindent="", newl=""):
00680     addindent = "  "
00681     # indent = current indentation
00682     # addindent = indentation to add to higher levels
00683     # newl = newline string
00684     writer.write(indent+"<" + self.tagName)
00685 
00686     attrs = self._get_attributes()
00687     a_names = attrs.keys()
00688     a_names.sort()
00689 
00690     for a_name in a_names:
00691         writer.write(" %s=\"" % a_name)
00692         xml.dom.minidom._write_data(writer, attrs[a_name].value)
00693         writer.write("\"")
00694     if self.childNodes:
00695         if len(self.childNodes) == 1 \
00696           and self.childNodes[0].nodeType == xml.dom.minidom.Node.TEXT_NODE:
00697             writer.write(">")
00698             self.childNodes[0].writexml(writer, "", "", "")
00699             writer.write("</%s>%s" % (self.tagName, newl))
00700             return
00701         writer.write(">%s"%(newl))
00702         for node in self.childNodes:
00703             node.writexml(writer,indent+addindent,addindent,newl)
00704         writer.write("%s</%s>%s" % (indent,self.tagName,newl))
00705     else:
00706         writer.write("/>%s"%(newl))
00707 
00708 # replace minidom's function with ours
00709 xml.dom.minidom.Element.writexml = fixed_writexml
00710 


calibration_estimation
Author(s): Vijay Pradeep, Michael Ferguson
autogenerated on Sun Oct 5 2014 22:44:09