|
8 | 8 |
|
9 | 9 | from compas.base import Base |
10 | 10 | from compas.files.xml import XML |
| 11 | +from compas.files.xml import XMLElement |
11 | 12 | from compas.utilities import memoize |
12 | 13 |
|
13 | 14 | __all__ = [ |
14 | 15 | 'URDF', |
15 | | - 'URDFParser' |
| 16 | + 'URDFElement', |
| 17 | + 'URDFParser', |
16 | 18 | ] |
17 | 19 |
|
18 | 20 |
|
@@ -44,9 +46,30 @@ class URDF(object): |
44 | 46 |
|
45 | 47 | """ |
46 | 48 |
|
47 | | - def __init__(self, xml): |
| 49 | + def __init__(self, xml=None): |
48 | 50 | self.xml = xml |
49 | | - self.robot = URDFParser.parse_element(xml.root, xml.root.tag) |
| 51 | + self._robot = None |
| 52 | + |
| 53 | + @property |
| 54 | + def robot(self): |
| 55 | + if self._robot is None: |
| 56 | + self._robot = URDFParser.parse_element(self.xml.root, self.xml.root.tag) |
| 57 | + return self._robot |
| 58 | + |
| 59 | + @robot.setter |
| 60 | + def robot(self, robot): |
| 61 | + robot_element = robot.get_urdf_element() |
| 62 | + root = robot_element.get_root() |
| 63 | + robot_element.add_children(root) |
| 64 | + self.xml = XML() |
| 65 | + self.xml.root = root |
| 66 | + self._robot = robot |
| 67 | + |
| 68 | + @classmethod |
| 69 | + def from_robot(cls, robot): |
| 70 | + urdf = cls() |
| 71 | + urdf.robot = robot |
| 72 | + return urdf |
50 | 73 |
|
51 | 74 | @classmethod |
52 | 75 | def from_file(cls, source): |
@@ -80,6 +103,84 @@ def from_string(cls, text): |
80 | 103 | """ |
81 | 104 | return cls(XML.from_string(text)) |
82 | 105 |
|
| 106 | + @classmethod |
| 107 | + def read(cls, source): |
| 108 | + """Parse a URDF file from a file path or file-like object. |
| 109 | +
|
| 110 | + Parameters |
| 111 | + ---------- |
| 112 | + source : str or file |
| 113 | + File path or file-like object. |
| 114 | +
|
| 115 | + Examples |
| 116 | + -------- |
| 117 | + >>> from compas.files import URDF |
| 118 | + >>> urdf = URDF.read('/urdf/ur5.urdf') |
| 119 | + """ |
| 120 | + return cls.from_file(source) |
| 121 | + |
| 122 | + def to_file(self, destination=None, prettify=False): |
| 123 | + """Writes the string representation of this URDF instance, |
| 124 | + including all sub-elements, to the ``destination``. |
| 125 | +
|
| 126 | + Parameters |
| 127 | + ---------- |
| 128 | + destination : str, optional |
| 129 | + Filepath where the URDF should be written. Defaults to |
| 130 | + the filepath of the associated XML object. |
| 131 | + prettify : bool, optional |
| 132 | + Whether the string should add whitespace for legibility. |
| 133 | + Defaults to ``False``. |
| 134 | +
|
| 135 | + Returns |
| 136 | + ------- |
| 137 | + ``None`` |
| 138 | +
|
| 139 | + """ |
| 140 | + if destination: |
| 141 | + self.xml.filepath = destination |
| 142 | + self.xml.write(prettify=prettify) |
| 143 | + |
| 144 | + def to_string(self, encoding='utf-8', prettify=False): |
| 145 | + """Generate a string representation of this URDF instance, |
| 146 | + including all sub-elements. |
| 147 | +
|
| 148 | + Parameters |
| 149 | + ---------- |
| 150 | + encoding : str, optional |
| 151 | + Output encoding (the default is 'utf-8') |
| 152 | + prettify : bool, optional |
| 153 | + Whether the string should add whitespace for legibility. |
| 154 | + Defaults to ``False``. |
| 155 | +
|
| 156 | + Returns |
| 157 | + ------- |
| 158 | + str |
| 159 | + String representation of the URDF. |
| 160 | +
|
| 161 | + """ |
| 162 | + return self.xml.to_string(encoding=encoding, prettify=prettify) |
| 163 | + |
| 164 | + def write(self, destination=None, prettify=False): |
| 165 | + """Writes the string representation of this URDF instance, |
| 166 | + including all sub-elements, to the ``destination``. |
| 167 | +
|
| 168 | + Parameters |
| 169 | + ---------- |
| 170 | + destination : str, optional |
| 171 | + Filepath where the URDF should be written. Defaults to |
| 172 | + the filepath of the associated XML object. |
| 173 | + prettify : bool, optional |
| 174 | + Whether the string should add whitespace for legibility. |
| 175 | + Defaults to ``False``. |
| 176 | +
|
| 177 | + Returns |
| 178 | + ------- |
| 179 | + ``None`` |
| 180 | +
|
| 181 | + """ |
| 182 | + self.to_file(destination=destination, prettify=prettify) |
| 183 | + |
83 | 184 |
|
84 | 185 | class URDFParser(object): |
85 | 186 | """Parse URDF elements into an object graph.""" |
@@ -107,7 +208,7 @@ def parse_element(cls, element, path=''): |
107 | 208 | """Recursively parse URDF element and its children. |
108 | 209 |
|
109 | 210 | If the parser type implements a class method ``from_urdf``, |
110 | | - it will use it to parse the elemenet, otherwise |
| 211 | + it will use it to parse the element, otherwise |
111 | 212 | a generic implementation that relies on conventions |
112 | 213 | will be used. |
113 | 214 |
|
@@ -202,6 +303,12 @@ class URDFGenericElement(Base): |
202 | 303 | """Generic representation for all URDF elements that |
203 | 304 | are not explicitly supported.""" |
204 | 305 |
|
| 306 | + def get_urdf_element(self): |
| 307 | + if not (hasattr(self, '_urdf_source') or hasattr(self, 'tag')): |
| 308 | + raise Exception('No tag found for element {}'.format(self)) |
| 309 | + tag = self.tag if hasattr(self, 'tag') else self._urdf_source.tag |
| 310 | + return URDFElement(tag, self.attr, self.elements, self.text) |
| 311 | + |
205 | 312 | @classmethod |
206 | 313 | def from_urdf(cls, attributes, elements, text): |
207 | 314 | el = cls() |
@@ -246,6 +353,22 @@ def to_json(self, filepath): |
246 | 353 | json.dump(self.data, f) |
247 | 354 |
|
248 | 355 |
|
| 356 | +class URDFElement(XMLElement): |
| 357 | + def __init__(self, tag, attributes=None, elements=None, text=None): |
| 358 | + elements = [e.get_urdf_element() for e in elements or [] if e is not None] |
| 359 | + super(URDFElement, self).__init__(tag, attributes, elements, text) |
| 360 | + self.redistribute_elements() |
| 361 | + |
| 362 | + def redistribute_elements(self): |
| 363 | + attributes = {} |
| 364 | + for key, value in self.attributes.items(): |
| 365 | + if hasattr(value, 'get_urdf_element'): |
| 366 | + self.elements.append(value.get_urdf_element()) |
| 367 | + else: |
| 368 | + attributes[key] = str(value) |
| 369 | + self.attributes = attributes |
| 370 | + |
| 371 | + |
249 | 372 | @memoize |
250 | 373 | def get_metadata(type): |
251 | 374 | metadata = dict() |
|
0 commit comments