robot_description_builder/link/
visual.rs1#[cfg(feature = "xml")]
2use quick_xml::{events::attributes::Attribute, name::QName};
3
4#[cfg(feature = "urdf")]
5use crate::to_rdf::to_urdf::ToURDF;
6use crate::{
7 identifiers::GroupID,
8 link::{builder::VisualBuilder, geometry::GeometryInterface},
9 link_data::geometry::GeometryShapeData,
10 material::Material,
11 transform::Transform,
12};
13
14#[derive(Debug)]
26pub struct Visual {
27 pub(crate) name: Option<String>,
32 pub(crate) transform: Option<Transform>,
38 pub(crate) geometry: Box<dyn GeometryInterface + Sync + Send>,
40 pub(crate) material: Option<Material>,
42}
43
44impl Visual {
45 pub fn builder(geometry: impl Into<Box<dyn GeometryInterface + Sync + Send>>) -> VisualBuilder {
47 VisualBuilder::new(geometry)
48 }
49
50 pub fn name(&self) -> Option<&String> {
93 self.name.as_ref()
94 }
95
96 pub fn transform(&self) -> Option<&Transform> {
98 self.transform.as_ref()
99 }
100
101 pub fn geometry(&self) -> &Box<dyn GeometryInterface + Sync + Send> {
103 &self.geometry
104 }
105
106 pub fn material(&self) -> Option<&Material> {
108 self.material.as_ref()
109 }
110
111 pub(crate) fn material_mut(&mut self) -> Option<&mut Material> {
113 self.material.as_mut()
114 }
115
116 pub fn rebuild(&self) -> VisualBuilder {
118 VisualBuilder {
119 name: self.name.clone(),
120 transform: self.transform,
121 geometry: self.geometry.boxed_clone(),
122 material_description: self.material.as_ref().map(Material::describe),
123 }
124 }
125
126 pub(crate) fn get_geometry_data(&self) -> GeometryShapeData {
127 GeometryShapeData {
128 transform: self.transform.unwrap_or_default(),
129 geometry: self.geometry.shape_container(),
130 }
131 }
132}
133
134#[cfg(feature = "urdf")]
135impl ToURDF for Visual {
136 fn to_urdf(
137 &self,
138 writer: &mut quick_xml::Writer<std::io::Cursor<Vec<u8>>>,
139 urdf_config: &crate::to_rdf::to_urdf::URDFConfig,
140 ) -> Result<(), quick_xml::Error> {
141 let mut element = writer.create_element("visual");
142 if let Some(name) = self.name() {
143 element = element.with_attribute(Attribute {
144 key: QName(b"name"),
145 value: name.display().as_bytes().into(),
146 });
147 }
148 element.write_inner_content(|writer| -> quick_xml::Result<()> {
149 if let Some(transform) = self.transform() {
151 transform.to_urdf(writer, urdf_config)?
152 }
153
154 self.geometry()
155 .shape_container()
156 .to_urdf(writer, urdf_config)?;
157 if let Some(material) = self.material() {
158 material.to_urdf(writer, urdf_config)?;
159 }
160 Ok(())
161 })?;
162
163 Ok(())
164 }
165}
166
167impl PartialEq for Visual {
168 fn eq(&self, other: &Self) -> bool {
169 self.name == other.name
170 && self.transform == other.transform
171 && *self.geometry == *other.geometry
172 && match (&self.material, &other.material) {
173 (None, None) => true,
174 (Some(own_material), Some(other_material)) => own_material == other_material,
175 _ => false,
176 }
177 }
178}
179
180impl Clone for Visual {
181 fn clone(&self) -> Self {
182 Self {
183 name: self.name.clone(),
184 transform: self.transform,
185 geometry: self.geometry.boxed_clone(),
186 material: self.material.clone(),
187 }
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use std::f32::consts::PI;
194 use test_log::test;
195
196 use crate::{
197 link::{
198 builder::VisualBuilder,
199 geometry::{BoxGeometry, CylinderGeometry, SphereGeometry},
200 visual::Visual,
201 },
202 transform::Transform,
203 };
204
205 #[cfg(feature = "urdf")]
206 mod to_urdf {
207 use super::{test, *};
208 use crate::{
209 material::MaterialDescriptor,
210 to_rdf::to_urdf::{ToURDF, URDFConfig, URDFMaterialReferences},
211 };
212 use std::io::Seek;
213
214 fn test_to_urdf_visual(visual: VisualBuilder, result: String, urdf_config: &URDFConfig) {
215 let mut writer = quick_xml::Writer::new(std::io::Cursor::new(Vec::new()));
216 assert!(visual.build().to_urdf(&mut writer, urdf_config).is_ok());
217
218 writer.get_mut().rewind().unwrap();
219
220 assert_eq!(
221 std::io::read_to_string(writer.into_inner()).unwrap(),
222 result
223 )
224 }
225
226 #[test]
227 fn no_name_no_origin_no_material() {
228 test_to_urdf_visual(
229 Visual::builder(BoxGeometry::new(1.0, 2.0, 3.0)),
230 String::from(r#"<visual><geometry><box size="1 2 3"/></geometry></visual>"#),
231 &URDFConfig::default(),
232 );
233 }
234
235 #[test]
236 fn name_no_origin_no_material() {
237 test_to_urdf_visual(
238 Visual::builder(CylinderGeometry::new(9., 6.258)).named("myLink_vis"),
239 String::from(
240 r#"<visual name="myLink_vis"><geometry><cylinder radius="9" length="6.258"/></geometry></visual>"#,
241 ),
242 &URDFConfig::default(),
243 );
244 }
245
246 #[test]
247 fn no_name_origin_no_material() {
248 test_to_urdf_visual(
249 Visual::builder(SphereGeometry::new(3.))
250 .transformed(Transform::new((4., 6.78, 1.), (PI, 2. * PI, 0.))),
251 String::from(
252 r#"<visual><origin xyz="4 6.78 1" rpy="3.1415927 6.2831855 0"/><geometry><sphere radius="3"/></geometry></visual>"#,
253 ),
254 &URDFConfig::default(),
255 );
256 }
257
258 #[test]
259 fn no_name_no_origin_material() {
260 test_to_urdf_visual(
261 Visual::builder(CylinderGeometry::new(4.5, 75.35)).materialized(
262 MaterialDescriptor::new_color(0.5, 0.55, 0.6, 1.).named("material_name"),
263 ),
264 String::from(
265 r#"<visual><geometry><cylinder radius="4.5" length="75.35"/></geometry><material name="material_name"><color rgba="0.5 0.55 0.6 1"/></material></visual>"#,
266 ),
267 &URDFConfig {
268 material_references: URDFMaterialReferences::OnlyMultiUseMaterials,
269 ..Default::default()
270 },
271 );
272 }
273
274 #[test]
275 fn name_origin_material() {
276 test_to_urdf_visual(
277 Visual::builder(CylinderGeometry::new(4.5, 75.35))
278 .named("some_col")
279 .transformed(Transform::new_translation(5.4, 9.1, 7.8))
280 .materialized(MaterialDescriptor::new_color(0.75, 0.5, 1., 1.)),
281 String::from(
282 r#"<visual name="some_col"><origin xyz="5.4 9.1 7.8"/><geometry><cylinder radius="4.5" length="75.35"/></geometry><material><color rgba="0.75 0.5 1 1"/></material></visual>"#,
283 ),
284 &URDFConfig::default(),
285 );
286 }
287 }
288}