1use std::fmt::Write;
4
5use geop_lib_sheet::xml_escape;
6
7use crate::urdf::{Inertial, JointType, Origin, Robot, Visual};
8
9fn number(value: f64) -> String {
13 let (plain, exponent) = (format!("{value}"), format!("{value:e}"));
14 if exponent.len() < plain.len() {
15 exponent
16 } else {
17 plain
18 }
19}
20
21fn numbers(values: &[f64]) -> String {
23 let values: Vec<String> = values.iter().map(|&v| number(v)).collect();
24 values.join(" ")
25}
26
27fn origin(out: &mut String, indent: &str, origin: &Origin) {
28 let _ = writeln!(
29 out,
30 r#"{indent}<origin xyz="{}" rpy="{}"/>"#,
31 numbers(&origin.xyz),
32 numbers(&origin.rpy)
33 );
34}
35
36fn inertial(out: &mut String, inertial: &Inertial) {
37 let i = &inertial.inertia;
38 out.push_str(" <inertial>\n");
39 origin(
40 out,
41 " ",
42 &Origin {
43 xyz: inertial.center,
44 rpy: [0.0; 3],
45 },
46 );
47 let _ = writeln!(out, r#" <mass value="{}"/>"#, number(inertial.mass));
48 let _ = writeln!(
49 out,
50 r#" <inertia ixx="{}" ixy="{}" ixz="{}" iyy="{}" iyz="{}" izz="{}"/>"#,
51 number(i[0][0]),
52 number(i[0][1]),
53 number(i[0][2]),
54 number(i[1][1]),
55 number(i[1][2]),
56 number(i[2][2])
57 );
58 out.push_str(" </inertial>\n");
59}
60
61fn shape(out: &mut String, element: &str, visual: &Visual) {
63 let _ = writeln!(
64 out,
65 r#" <{element} name="{}">"#,
66 xml_escape(&visual.name)
67 );
68 origin(out, " ", &visual.origin);
69 let _ = writeln!(
70 out,
71 r#" <geometry><mesh filename="{}" scale="0.001 0.001 0.001"/></geometry>"#,
72 xml_escape(&visual.mesh)
73 );
74 let _ = writeln!(out, " </{element}>");
75}
76
77impl Robot {
78 pub fn to_urdf(&self) -> String {
80 let mut out = String::new();
81 out.push_str("<?xml version=\"1.0\"?>\n");
82 out.push_str(
83 "<!-- Exported by geop. Lengths in metres, angles in radians, masses in kilograms; \
84 meshes in millimetres, scaled. geop models no actuators: joint effort and \
85 velocity are 0 — set them for the drives. -->\n",
86 );
87 let _ = writeln!(out, r#"<robot name="{}">"#, xml_escape(&self.name));
88 for link in &self.links {
89 let _ = writeln!(out, r#" <link name="{}">"#, xml_escape(&link.name));
90 if let Some(i) = &link.inertial {
91 inertial(&mut out, i);
92 }
93 for visual in &link.visuals {
94 shape(&mut out, "visual", visual);
95 }
96 for visual in &link.visuals {
97 shape(&mut out, "collision", visual);
98 }
99 out.push_str(" </link>\n");
100 }
101 for joint in &self.joints {
102 let _ = writeln!(
103 out,
104 r#" <joint name="{}" type="{}">"#,
105 xml_escape(&joint.name),
106 joint.kind.name()
107 );
108 let _ = writeln!(out, r#" <parent link="{}"/>"#, xml_escape(&joint.parent));
109 let _ = writeln!(out, r#" <child link="{}"/>"#, xml_escape(&joint.child));
110 origin(&mut out, " ", &joint.origin);
111 let _ = writeln!(out, r#" <axis xyz="{}"/>"#, numbers(&joint.axis));
112 match joint.kind {
113 JointType::Revolute { lower, upper } | JointType::Prismatic { lower, upper } => {
114 let _ = writeln!(
115 out,
116 r#" <limit lower="{}" upper="{}" effort="0" velocity="0"/>"#,
117 number(lower),
118 number(upper)
119 );
120 }
121 JointType::Continuous => {}
122 }
123 if let Some(mimic) = &joint.mimic {
124 let _ = writeln!(
125 out,
126 r#" <mimic joint="{}" multiplier="{}" offset="0"/>"#,
127 xml_escape(&mimic.joint),
128 number(mimic.multiplier)
129 );
130 }
131 out.push_str(" </joint>\n");
132 }
133 out.push_str("</robot>\n");
134 out
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use crate::urdf::{Joint, Link, Mimic};
142
143 #[test]
146 fn a_small_robot_is_written() {
147 let at = |x: f64| Origin {
148 xyz: [x, 0.0, 0.0],
149 rpy: [0.0; 3],
150 };
151 let robot = Robot {
152 name: "a<b>".into(),
153 links: vec![
154 Link {
155 name: "base".into(),
156 parts: Vec::new(),
157 inertial: None,
158 visuals: Vec::new(),
159 },
160 Link {
161 name: "wheel".into(),
162 parts: vec!["wheel".into()],
163 inertial: Some(Inertial {
164 mass: 0.5,
165 center: [0.0; 3],
166 inertia: [[1.0, 0.0, 0.0], [0.0, 2.0, 0.0], [0.0, 0.0, 3.0]],
167 }),
168 visuals: vec![Visual {
169 name: "wheel".into(),
170 origin: at(0.0),
171 mesh: "meshes/wheel.stl".into(),
172 }],
173 },
174 ],
175 joints: vec![Joint {
176 name: "add_part(wheel,m1)".into(),
177 kind: JointType::Continuous,
178 parent: "base".into(),
179 child: "wheel".into(),
180 origin: at(0.25),
181 axis: [0.0, 0.0, 1.0],
182 mimic: Some(Mimic {
183 joint: "motor\"1".into(),
184 multiplier: -0.5,
185 }),
186 }],
187 };
188 let urdf = robot.to_urdf();
189 assert!(urdf.contains(r#"<robot name="a<b>">"#), "{urdf}");
190 assert!(urdf.contains(r#"<joint name="add_part(wheel,m1)" type="continuous">"#));
191 assert!(!urdf.contains("<limit"));
192 assert!(urdf.contains(r#"<origin xyz="0.25 0 0" rpy="0 0 0"/>"#));
193 assert!(urdf.contains(r#"<mimic joint="motor"1" multiplier="-0.5" offset="0"/>"#));
194 assert!(urdf.contains(r#"<inertia ixx="1" ixy="0" ixz="0" iyy="2" iyz="0" izz="3"/>"#));
195 assert_eq!(
196 urdf.matches("<mesh ").count(),
197 2,
198 "a visual and a collision"
199 );
200 assert!(urdf.ends_with("</robot>\n"));
201 }
202}