1use crate::deserialize::*;
2use crate::errors::*;
3use serde::Serialize;
4
5use std::mem;
6use std::path::Path;
7
8fn sort_link_joint(string: &str) -> Result<String> {
10 let mut e: xml::Element = string.parse().map_err(UrdfError::new)?;
11 let mut links = Vec::new();
12 let mut joints = Vec::new();
13 let mut materials = Vec::new();
14 for c in mem::take(&mut e.children) {
15 if let xml::Xml::ElementNode(xml_elm) = c {
16 if xml_elm.name == "link" {
17 links.push(sort_visual_collision(xml_elm));
18 } else if xml_elm.name == "joint" {
19 joints.push(xml::Xml::ElementNode(xml_elm));
20 } else if xml_elm.name == "material" {
21 materials.push(xml::Xml::ElementNode(xml_elm));
22 }
23 }
24 }
25 let mut new_elm = e;
26 links.extend(joints);
27 links.extend(materials);
28 new_elm.children = links;
29 Ok(format!("{new_elm}"))
30}
31
32fn sort_visual_collision(mut elm: xml::Element) -> xml::Xml {
33 let mut visuals = Vec::new();
34 let mut collisions = Vec::new();
35 for c in mem::take(&mut elm.children) {
36 if let xml::Xml::ElementNode(xml_elm) = c {
37 if xml_elm.name == "visual" || xml_elm.name == "inertial" {
38 visuals.push(xml::Xml::ElementNode(xml_elm));
39 } else if xml_elm.name == "collision" {
40 collisions.push(xml::Xml::ElementNode(xml_elm));
41 }
42 }
43 }
44 let mut new_elm = elm;
45 visuals.extend(collisions);
46 new_elm.children = visuals;
47 xml::Xml::ElementNode(new_elm)
48}
49
50pub fn read_file<P: AsRef<Path>>(path: P) -> Result<Robot> {
60 read_from_string(&std::fs::read_to_string(path)?)
61}
62
63pub fn read_from_string(string: &str) -> Result<Robot> {
116 let sorted_string = sort_link_joint(string)?;
117 serde_xml_rs::from_str(&sorted_string).map_err(UrdfError::new)
118}
119
120pub fn write_to_string(robot: &Robot) -> Result<String> {
121 let mut buffer = String::new();
122 let mut s = quick_xml::se::Serializer::new(&mut buffer);
123 s.indent(' ', 2);
124 robot.serialize(s).map_err(UrdfError::new)?;
125 Ok(buffer)
126}
127
128#[cfg(test)]
129mod tests {
130 use crate::{read_from_string, write_to_string};
131 use crate::{Geometry, JointType, Robot};
132 use assert_approx_eq::assert_approx_eq;
133
134 fn check_robot(robot: &Robot) {
135 assert_eq!(robot.name, "robot");
136
137 assert_eq!(robot.links.len(), 3);
139 let link = &robot.links[0];
140 assert_eq!(link.name, "shoulder1");
141 let xyz = link.inertial.origin.xyz;
142 assert_approx_eq!(xyz[0], 0.0);
143 assert_approx_eq!(xyz[1], 0.0);
144 assert_approx_eq!(xyz[2], 0.5);
145 let rpy = link.inertial.origin.rpy;
146 assert_approx_eq!(rpy[0], 0.0);
147 assert_approx_eq!(rpy[1], 0.0);
148 assert_approx_eq!(rpy[2], 0.0);
149 assert_approx_eq!(link.inertial.mass.value, 1.0);
150 assert_approx_eq!(link.inertial.inertia.ixx, 100.0);
151 assert_approx_eq!(link.inertial.inertia.ixy, 0.0);
152 assert_approx_eq!(link.inertial.inertia.ixz, 0.0);
153 assert_approx_eq!(link.inertial.inertia.iyy, 100.0);
154 assert_approx_eq!(link.inertial.inertia.ixz, 0.0);
155 assert_approx_eq!(link.inertial.inertia.izz, 100.0);
156
157 assert_eq!(link.visual.len(), 3);
158 let xyz = &link.visual[0].origin.xyz;
159 assert_approx_eq!(xyz[0], 0.1);
160 assert_approx_eq!(xyz[1], 0.2);
161 assert_approx_eq!(xyz[2], 0.3);
162 let rpy = &link.visual[0].origin.rpy;
163 assert_approx_eq!(rpy[0], -0.1);
164 assert_approx_eq!(rpy[1], -0.2);
165 assert_approx_eq!(rpy[2], -0.3);
166
167 let xyz = &link.visual[1].origin.xyz;
169 assert_approx_eq!(xyz[0], 0.1);
170 assert_approx_eq!(xyz[1], 0.2);
171 assert_approx_eq!(xyz[2], 0.3);
172 let rpy = &link.visual[1].origin.rpy;
173 assert_approx_eq!(rpy[0], -0.1);
174 assert_approx_eq!(rpy[1], -0.2);
175 assert_approx_eq!(rpy[2], -0.3);
176
177 let xyz = &link.visual[2].origin.xyz;
178 assert_approx_eq!(xyz[0], 0.1);
179 assert_approx_eq!(xyz[1], 0.2);
180 assert_approx_eq!(xyz[2], 0.3);
181 let rpy = &link.visual[2].origin.rpy;
182 assert_approx_eq!(rpy[0], -0.1);
183 assert_approx_eq!(rpy[1], -0.2);
184 assert_approx_eq!(rpy[2], -0.3);
185
186 assert!(link.visual[0].material.is_some());
188 let mat = link.visual[0].material.as_ref().unwrap();
189 assert_eq!(mat.name, "Cyan");
190 let rgba = mat.color.clone().unwrap().rgba;
191 assert_approx_eq!(rgba[0], 0.0);
192 assert_approx_eq!(rgba[1], 1.0);
193 assert_approx_eq!(rgba[2], 1.0);
194 assert_approx_eq!(rgba[3], 1.0);
195
196 match &link.visual[0].geometry {
197 Geometry::Box { size } => {
198 assert_approx_eq!(size[0], 1.0f64);
199 assert_approx_eq!(size[1], 2.0f64);
200 assert_approx_eq!(size[2], 3.0f64);
201 }
202 _ => panic!("geometry error"),
203 }
204 match &link.visual[1].geometry {
205 Geometry::Mesh {
206 ref filename,
207 scale,
208 } => {
209 assert_eq!(filename, "aa.dae");
210 assert!(scale.is_none());
211 }
212 _ => panic!("geometry error"),
213 }
214 match &link.visual[2].geometry {
215 Geometry::Mesh {
216 ref filename,
217 scale,
218 } => {
219 assert_eq!(filename, "bbb.dae");
220 let scale = scale.as_ref().unwrap();
221 assert_approx_eq!(scale[0], 2.0);
222 assert_approx_eq!(scale[1], 3.0);
223 assert_approx_eq!(scale[2], 4.0);
224 }
225 _ => panic!("geometry error"),
226 }
227
228 assert_eq!(link.collision.len(), 1);
229 let xyz = &link.collision[0].origin.xyz;
230 assert_approx_eq!(xyz[0], 0.0);
231 assert_approx_eq!(xyz[1], 0.0);
232 assert_approx_eq!(xyz[2], 0.0);
233 let rpy = &link.collision[0].origin.rpy;
234 assert_approx_eq!(rpy[0], 0.0);
235 assert_approx_eq!(rpy[1], 0.0);
236 assert_approx_eq!(rpy[2], 0.0);
237 match &link.collision[0].geometry {
238 Geometry::Cylinder { radius, length } => {
239 assert_approx_eq!(radius, 1.0);
240 assert_approx_eq!(length, 0.5);
241 }
242 _ => panic!("geometry error"),
243 }
244
245 assert_eq!(robot.links[1].name, "elbow1");
246 assert_eq!(robot.links[2].name, "wrist1");
247
248 assert_eq!(robot.materials.len(), 1);
250 let mat = &robot.materials[0];
251 assert_eq!(mat.name, "blue");
252 assert!(mat.color.is_some());
253 let rgba = mat.color.clone().unwrap().rgba;
254 assert_approx_eq!(rgba[0], 0.0);
255 assert_approx_eq!(rgba[1], 0.0);
256 assert_approx_eq!(rgba[2], 0.8);
257 assert_approx_eq!(rgba[3], 1.0);
258
259 assert_eq!(robot.joints.len(), 2);
261 let joint = &robot.joints[0];
262 assert_eq!(joint.name, "shoulder_pitch");
263 assert_eq!(joint.parent.link, "shoulder1");
264 assert_eq!(joint.child.link, "elbow1");
265 assert_eq!(joint.joint_type, JointType::Revolute);
266 assert_approx_eq!(joint.limit.upper, 1.0);
267 assert_approx_eq!(joint.limit.lower, -1.0);
268 assert_approx_eq!(joint.limit.effort, 0.0);
269 assert_approx_eq!(joint.limit.velocity, 1.0);
270 assert_eq!(joint.calibration.as_ref().unwrap().rising, None);
271 assert_eq!(joint.calibration.as_ref().unwrap().falling, None);
272 assert_approx_eq!(joint.dynamics.as_ref().unwrap().damping, 0.0);
273 assert_approx_eq!(joint.dynamics.as_ref().unwrap().friction, 0.0);
274 assert_eq!(joint.mimic.as_ref().unwrap().joint, "elbow1");
275 assert_approx_eq!(joint.safety_controller.as_ref().unwrap().k_velocity, 10.0);
276 assert!(joint.mimic.as_ref().unwrap().multiplier.is_none());
277 assert!(joint.mimic.as_ref().unwrap().offset.is_none());
278 let xyz = &joint.axis.xyz;
279 assert_approx_eq!(xyz[0], 0.0);
280 assert_approx_eq!(xyz[1], 1.0);
281 assert_approx_eq!(xyz[2], -1.0);
282
283 let joint = &robot.joints[1];
284 assert_eq!(joint.name, "shoulder_pitch");
285 assert_eq!(joint.parent.link, "elbow1");
286 assert_eq!(joint.child.link, "wrist1");
287 assert_eq!(joint.joint_type, JointType::Revolute);
288 assert_approx_eq!(joint.limit.upper, 1.0);
289 assert_approx_eq!(joint.limit.lower, -2.0);
290 assert_approx_eq!(joint.limit.effort, 0.0);
291 assert_approx_eq!(joint.limit.velocity, 1.0);
292 assert_approx_eq!(joint.dynamics.as_ref().unwrap().damping, 10.0);
293 assert_approx_eq!(joint.dynamics.as_ref().unwrap().friction, 1.0);
294 assert_eq!(joint.mimic.as_ref().unwrap().joint, "shoulder1");
295 assert_approx_eq!(joint.mimic.as_ref().unwrap().multiplier.unwrap(), 5.0);
296 assert_approx_eq!(joint.mimic.as_ref().unwrap().offset.unwrap(), 1.0);
297 assert_approx_eq!(joint.safety_controller.as_ref().unwrap().k_position, 10.0);
298 assert_approx_eq!(joint.safety_controller.as_ref().unwrap().k_velocity, 1.0);
299 assert_approx_eq!(
300 joint.safety_controller.as_ref().unwrap().soft_lower_limit,
301 -0.5
302 );
303 assert_approx_eq!(
304 joint.safety_controller.as_ref().unwrap().soft_upper_limit,
305 1.0
306 );
307 let xyz = &joint.axis.xyz;
308 assert_approx_eq!(xyz[0], 0.0);
309 assert_approx_eq!(xyz[1], 1.0);
310 assert_approx_eq!(xyz[2], 0.0);
311 }
312
313 #[test]
314 fn deserialization() {
315 let s = r#"
316 <robot name="robot" xmlns="http://www.ros.org">
317 <material name="blue">
318 <color rgba="0.0 0.0 0.8 1.0"/>
319 </material>
320
321 <link name="shoulder1">
322 <inertial>
323 <origin xyz="0 0 0.5" rpy="0 0 0"/>
324 <mass value="1"/>
325 <inertia ixx="100" ixy="0" ixz="0" iyy="100" iyz="0" izz="100" />
326 </inertial>
327 <visual>
328 <origin xyz="0.1 0.2 0.3" rpy="-0.1 -0.2 -0.3" />
329 <geometry>
330 <box size="1.0 2.0 3.0" />
331 </geometry>
332 <material name="Cyan">
333 <color rgba="0 1.0 1.0 1.0"/>
334 </material>
335 </visual>
336 <visual>
337 <geometry>
338 <mesh filename="aa.dae" />
339 </geometry>
340 <origin xyz="0.1 0.2 0.3" rpy="-0.1 -0.2 -0.3" />
341 </visual>
342 <collision>
343 <origin xyz="0 0 0" rpy="0 0 0"/>
344 <geometry>
345 <cylinder radius="1" length="0.5"/>
346 </geometry>
347 </collision>
348 <visual>
349 <origin xyz="0.1 0.2 0.3" rpy="-0.1 -0.2 -0.3" />
350 <geometry>
351 <mesh filename="bbb.dae" scale="2.0 3.0 4.0" />
352 </geometry>
353 </visual>
354 </link>
355 <joint name="shoulder_pitch" type="revolute">
356 <origin xyz="0.0 0.0 0.1" />
357 <parent link="shoulder1" />
358 <child link="elbow1" />
359 <axis xyz="0 1 -1" />
360 <calibration />
361 <dynamics />
362 <mimic joint="elbow1" />
363 <safety_controller k_velocity="10" />
364 <limit lower="-1" upper="1.0" effort="0" velocity="1.0"/>
365 </joint>
366 <link name="elbow1" />
367 <link name="wrist1" />
368 <joint name="shoulder_pitch" type="revolute">
369 <origin xyz="0.0 0.0 0.0" />
370 <parent link="elbow1" />
371 <child link="wrist1" />
372 <axis xyz=" 0 1 0 " />
373 <calibration falling="1" rising="1" />
374 <dynamics damping="10.0" friction="1" />
375 <mimic joint="shoulder1" offset="1" multiplier="5" />
376 <safety_controller k_position="10" k_velocity="1" soft_lower_limit="-0.5" soft_upper_limit="1" />
377 <limit lower=" -2" upper="1.0 " effort="0 " velocity=" 1.0 "/>
378 </joint>
379 </robot>
380 "#;
381 let robot = read_from_string(s).unwrap();
382 check_robot(&robot);
383
384 let s = write_to_string(&robot).unwrap();
386 assert!(!s.contains("Robot"), "{s}"); let robot = read_from_string(&s).unwrap();
388 check_robot(&robot);
389 }
390}