robot_description_builder/
material.rs

1//! The `Material` System
2//!
3//! TODO: MODULE DOC
4// DOCS TODO:
5//  - Module
6//  - Material
7//  - MaterialDescriptor
8mod descriptor;
9pub(crate) mod stage;
10
11pub mod data;
12pub use descriptor::MaterialDescriptor;
13
14use std::sync::{Arc, RwLock};
15
16#[cfg(feature = "urdf")]
17use crate::to_rdf::to_urdf::{ToURDF, URDFMaterialMode};
18#[cfg(feature = "xml")]
19use quick_xml::events::attributes::Attribute;
20
21#[cfg(feature = "wrapper")]
22use crate::utils::ArcLock;
23
24use crate::{
25	cluster_objects::{
26		kinematic_data_errors::AddMaterialError, kinematic_data_tree::KinematicDataTree,
27	},
28	identifiers::GroupID,
29	utils::{errored_read_lock, ArcRW},
30};
31
32use data::{MaterialData, MaterialDataReference};
33use stage::MaterialStage;
34
35/// A struct to represents a `Material` of a `Visual` geometry.
36///
37/// A [`Material`] can be constructed via the [`MaterialDescriptor`].
38///
39/// A [`Material`] can contain either:
40///  - a RGBA color
41///  - a Texture
42///
43/// See [`MaterialDescriptor`] for more information.
44#[derive(Debug, PartialEq, Clone)]
45pub struct Material(MaterialKind);
46
47impl Material {
48	/// Creates a new unnamed `Material` from a `MaterialData`.
49	pub(crate) fn new_unnamed(data: MaterialData) -> Self {
50		Self(MaterialKind::Unnamed(data))
51	}
52
53	/// Creates a new named `Material` which still has to be initilized.
54	pub(crate) fn new_named_uninited(name: impl Into<String>, data: MaterialData) -> Self {
55		Self(MaterialKind::Named {
56			name: name.into(),
57			data: MaterialStage::PreInit(data),
58		})
59	}
60
61	/// Creates a new named `Material`, which is already initilized.
62	pub(crate) fn new_named_inited(
63		name: impl Into<String>,
64		data: Arc<RwLock<MaterialData>>,
65	) -> Self {
66		Self(MaterialKind::Named {
67			name: name.into(),
68			data: MaterialStage::Initialized(data),
69		})
70	}
71
72	/// Register the `Material` in the `KinematicDataTree`.
73	// TODO: Safe poisoned Locks when `mutex_unpoison` #96469 becomes stable.
74	pub(crate) fn initialize(&mut self, tree: &KinematicDataTree) -> Result<(), AddMaterialError> {
75		match &mut self.0 {
76			// An unnamed Material does not have to be initialized.
77			MaterialKind::Unnamed(_) => Ok(()),
78			MaterialKind::Named { name, data } => {
79				let material_data = match data {
80					MaterialStage::PreInit(data) => {
81						let material_data_index = Arc::clone(&tree.material_index);
82
83						// Check if there already exists a `Material` with the same name
84						let other_material = material_data_index.mread()?.get(name).map(Arc::clone);
85
86						match other_material {
87							Some(other_material) => {
88								if *other_material
89									.read()
90									/* In the future the lock could be saved but waiting for
91									"This is a nightly-only experimental API. (mutex_unpoison #96469)" */
92									.map_err(|_| errored_read_lock(&other_material))?
93									== *data
94								{
95									other_material
96								} else {
97									return Err(AddMaterialError::Conflict(name.clone()));
98								}
99							}
100							None => {
101								let material_data = Arc::new(RwLock::new(data.clone()));
102								assert!(material_data_index
103									.mwrite()?
104									.insert(name.clone(), Arc::clone(&material_data))
105									.is_none());
106								material_data
107							}
108						}
109					}
110					MaterialStage::Initialized(data) => Arc::clone(data),
111				};
112				data.initialize(material_data);
113				Ok(())
114			}
115		}
116	}
117
118	/// The `name` of the `Material` if any.
119	///
120	/// Returns the `Some(name)` for a named [`Material`] and `None` for an unnamed [`Material`].
121	pub fn name(&self) -> Option<&String> {
122		match &self.0 {
123			MaterialKind::Named { name, data: _ } => Some(name),
124			MaterialKind::Unnamed(_) => None,
125		}
126	}
127
128	/// Get a reference to the `MaterialData` as a [`MaterialDataReference`].
129	// TODO: EXPAND docs
130	pub fn material_data(&self) -> MaterialDataReference {
131		match &self.0 {
132			MaterialKind::Named { name: _, data } => data.data(),
133			MaterialKind::Unnamed(data) => data.into(),
134		}
135	}
136
137	/// Describes the `Material` to reform a [`MaterialDescriptor`].
138	///
139	/// This can be used to clone the `Material` for use in a different `Link`.
140	pub fn describe(&self) -> MaterialDescriptor {
141		let descriptor = MaterialDescriptor::new_data(self.material_data().try_into().unwrap()); //FIXME: Unwrap not OK
142		match &self.0 {
143			MaterialKind::Named { name, data: _ } => descriptor.named(name),
144			MaterialKind::Unnamed(_) => descriptor,
145		}
146	}
147}
148
149#[cfg(feature = "urdf")]
150impl ToURDF for Material {
151	fn to_urdf(
152		&self,
153		writer: &mut quick_xml::Writer<std::io::Cursor<Vec<u8>>>,
154		urdf_config: &crate::to_rdf::to_urdf::URDFConfig,
155	) -> Result<(), quick_xml::Error> {
156		let mut element = writer.create_element("material");
157
158		match &self.0 {
159			MaterialKind::Named { name, data } => {
160				element = element.with_attribute(Attribute {
161					key: quick_xml::name::QName(b"name"),
162					value: name.display().as_bytes().into(),
163				});
164				match (urdf_config.direct_material_ref, data.used_count()) {
165					(URDFMaterialMode::Referenced, 2..) => element.write_empty()?,
166					(URDFMaterialMode::FullMaterial, _) | (URDFMaterialMode::Referenced, _) => {
167						element.write_inner_content(|writer| data.to_urdf(writer, urdf_config))?
168					}
169				}
170			}
171			MaterialKind::Unnamed(data) => {
172				element.write_inner_content(|writer| data.to_urdf(writer, urdf_config))?
173			}
174		};
175		Ok(())
176	}
177}
178
179#[cfg(feature = "wrapper")]
180impl From<(String, ArcLock<MaterialData>)> for Material {
181	fn from(value: (String, ArcLock<MaterialData>)) -> Self {
182		let name = value.0;
183		let data = value.1;
184
185		Self::new_named_inited(name, data)
186	}
187}
188
189/// An enum to unify named and unnamed `Material` into a single type.
190#[derive(Debug, PartialEq)]
191enum MaterialKind {
192	/// A variant to represent a named [`Material`].
193	///
194	/// It keeps track of the `name` of the [`Material`] and its data which needs to be initialized.
195	/// This is ensured via [`MaterialStage`].
196	Named { name: String, data: MaterialStage },
197	/// A variant to represent unnamed [`Material`].
198	///
199	/// Tt holds [`MaterialData`].
200	Unnamed(MaterialData),
201}
202
203impl From<MaterialKind> for Material {
204	fn from(value: MaterialKind) -> Self {
205		Self(value)
206	}
207}
208
209impl Clone for MaterialKind {
210	fn clone(&self) -> Self {
211		match self {
212			Self::Named { name, data } => Self::Named {
213				name: name.clone(),
214				data: data.clone(),
215			},
216			Self::Unnamed(arg0) => Self::Unnamed(arg0.clone()),
217		}
218	}
219}
220
221#[cfg(test)]
222mod tests {
223	// use crate::material::Material;
224	use crate::material::MaterialDescriptor;
225	use test_log::test;
226
227	// #[test]
228	// fn rebuild() {
229	// 	// assert_eq!(MaterialDescriptor::new_color(9., 1., 2., 1.).build().rebuild(), );
230	// }
231
232	#[cfg(feature = "urdf")]
233	mod to_urdf {
234		use super::{test, MaterialDescriptor};
235		use crate::{
236			link::builder::{LinkBuilder, VisualBuilder},
237			link_data::geometry::BoxGeometry,
238			to_rdf::to_urdf::{ToURDF, URDFConfig, URDFMaterialMode},
239			KinematicInterface,
240		};
241		use std::io::Seek;
242
243		fn test_to_urdf_material(
244			material_builder: MaterialDescriptor,
245			result: String,
246			urdf_config: &URDFConfig,
247		) {
248			let mut writer = quick_xml::Writer::new(std::io::Cursor::new(Vec::new()));
249			assert!(material_builder
250				.build()
251				.to_urdf(&mut writer, urdf_config)
252				.is_ok());
253
254			writer.get_mut().rewind().unwrap();
255
256			assert_eq!(
257				std::io::read_to_string(writer.into_inner()).unwrap(),
258				result
259			)
260		}
261
262		#[test]
263		fn color_no_name_full() {
264			test_to_urdf_material(
265				MaterialDescriptor::new_color(0.2, 0.4, 0.6, 0.8),
266				String::from(r#"<material><color rgba="0.2 0.4 0.6 0.8"/></material>"#),
267				&URDFConfig::default(),
268			);
269		}
270
271		#[test]
272		fn color_name_full() {
273			test_to_urdf_material(
274				MaterialDescriptor::new_color(0.2, 0.4, 0.6, 0.8).named("test_material"),
275				String::from(
276					r#"<material name="test_material"><color rgba="0.2 0.4 0.6 0.8"/></material>"#,
277				),
278				&URDFConfig::default(),
279			);
280		}
281
282		#[test]
283		fn color_name_ref() {
284			let tree = LinkBuilder::new("link")
285				.add_visual(VisualBuilder::new_full(
286					None,
287					None,
288					BoxGeometry::new(1., 1., 1.),
289					MaterialDescriptor::new_color(0.2, 0.4, 0.6, 0.8)
290						.named("test_material")
291						.into(),
292				))
293				.build_tree();
294
295			let mut writer = quick_xml::Writer::new(std::io::Cursor::new(Vec::new()));
296			assert!(tree
297				.get_material("test_material")
298				.unwrap()
299				.to_urdf(
300					&mut writer,
301					&URDFConfig {
302						direct_material_ref: URDFMaterialMode::Referenced,
303						..Default::default()
304					}
305				)
306				.is_ok());
307
308			writer.get_mut().rewind().unwrap();
309
310			assert_eq!(
311				std::io::read_to_string(writer.into_inner()).unwrap(),
312				String::from(r#"<material name="test_material"/>"#)
313			)
314		}
315
316		#[test]
317		fn texture_no_name_full() {
318			test_to_urdf_material(
319				MaterialDescriptor::new_texture("package://robot_description/..."),
320				String::from(
321					r#"<material><texture filename="package://robot_description/..."/></material>"#,
322				),
323				&URDFConfig::default(),
324			);
325		}
326
327		#[test]
328		fn texture_name_full() {
329			test_to_urdf_material(
330				MaterialDescriptor::new_texture("package://robot_description/...")
331					.named("texture_material"),
332				String::from(
333					r#"<material name="texture_material"><texture filename="package://robot_description/..."/></material>"#,
334				),
335				&URDFConfig::default(),
336			);
337		}
338
339		#[test]
340		fn texture_name_ref() {
341			let tree = LinkBuilder::new("link")
342				.add_visual(VisualBuilder::new_full(
343					None,
344					None,
345					BoxGeometry::new(1., 1., 1.),
346					MaterialDescriptor::new_texture("package://robot_description/...")
347						.named("texture_material")
348						.into(),
349				))
350				.build_tree();
351
352			let mut writer = quick_xml::Writer::new(std::io::Cursor::new(Vec::new()));
353			assert!(tree
354				.get_material("texture_material")
355				.unwrap()
356				.to_urdf(
357					&mut writer,
358					&URDFConfig {
359						direct_material_ref: URDFMaterialMode::Referenced,
360						..Default::default()
361					}
362				)
363				.is_ok());
364
365			writer.get_mut().rewind().unwrap();
366
367			assert_eq!(
368				std::io::read_to_string(writer.into_inner()).unwrap(),
369				String::from(r#"<material name="texture_material"/>"#)
370			)
371		}
372	}
373}