robot_description_builder/link/builder/
linkbuilder.rs

1use std::sync::{Arc, RwLock, Weak};
2
3use nalgebra::Matrix3;
4
5use super::{BuildLink, CollisionBuilder, VisualBuilder};
6use crate::{
7	cluster_objects::{kinematic_data_tree::KinematicDataTree, KinematicTree},
8	identifiers::GroupIDChanger,
9	joint::{BuildJointChain, Joint, JointBuilder},
10	link::{link_data, Link, LinkParent, LinkShapeData},
11	transform::Mirror,
12	utils::{ArcLock, WeakLock},
13};
14
15/// The builder for the `Link` type.
16///
17/// The `LinkBuilder` is used to construct a [`Link`].
18/// It can be attached to a pre-existing [`Link`] in a [`KinematicTree`] or a [`Robot`](crate::cluster_objects::Robot).
19/// Or a new [`KinematicTree`] can be started with this `LinkBuilder` as the blueprint for the `root_link`.
20///
21/// This will configure most of the link data:
22/// - **`name`**: The [_string identifier_](crate::identifiers) (or name) of this [`Link`]. For practical purposes, it is recommended to use unique identifiers/names.
23/// - **[`visuals`](super::VisualBuilder)** (0+): The builders for the [`Visual`](crate::link::Visual) elements associated with this [`Link`].
24/// - **[`colliders`](super::CollisionBuilder)** (0+): The builders for the [`Collision`](crate::link::Collision) elements associated with this [`Link`].
25/// - **[`joints`](crate::joint::JointBuilder)** (0+): The buiders for the child [`Joints`](crate::joint::Joint) of this [`Link`].
26/// (This field should be/is assumed as empty on a bare `LinkBuilder`, but can optionally be non-empty in a [`Chained<LinkBuilder>`](crate::chained::Chained))<br/>
27/// This field only be filled when reconstructing or yanking a pre-existing [`Link`]/[`Joint`].
28/// - **[`inertial`](crate::link::inertial::Inertial)** (Optional): The [`Inertial`](crate::link::inertial::Inertial) data for this [`Link`].
29// TODO: Check if something is missing?
30#[derive(Debug, PartialEq, Clone, Default)]
31pub struct LinkBuilder {
32	// All fields are pub(crate) so I can struct initialize in rebuild
33	/// The [_string identifier_](crate::identifiers) or name of this `Link`.
34	///
35	/// For practical purposes, it is recommended to use unique identifiers/names.
36	pub(crate) name: String,
37	// TODO: Figure out if we make this immutable on a `Link` and only allow editting throug the builder.
38	pub(crate) visuals: Vec<VisualBuilder>,
39	pub(crate) colliders: Vec<CollisionBuilder>,
40	// TODO: Calulate Inertial?
41	pub(crate) intertial: Option<link_data::Inertial>,
42	pub(crate) joints: Vec<JointBuilder>,
43}
44
45impl LinkBuilder {
46	/// Create a new [`LinkBuilder`] with the specified `name`.
47	pub fn new(name: impl Into<String>) -> LinkBuilder {
48		Self {
49			name: name.into(),
50			..Default::default()
51		}
52	}
53
54	/// Adds a [`VisualBuilder`] to this `LinkBuilder`.
55	pub fn add_visual(mut self, visual: VisualBuilder) -> Self {
56		self.visuals.push(visual);
57		self
58	}
59
60	// TODO: Not really sure if this is the way... but it is how clap does it.
61	/// Adds a [`CollisionBuilder`] to this `LinkBuilder`.
62	pub fn add_collider(mut self, collider: CollisionBuilder) -> Self {
63		self.colliders.push(collider);
64		self
65	}
66
67	// TODO: Naming not inline with convention
68	// Not happy with the added `add_` but otherwise name colliding with getter
69	/// Sets the [`Inertial`](link_data::Inertial) (`inertial`) of this `LinkBuilder`.
70	pub fn add_intertial(mut self, inertial: link_data::Inertial) -> Self {
71		self.intertial = Some(inertial);
72		self
73	}
74
75	/// Creates a [`KinematicTree`] by building this `LinkBuilder`.
76	pub fn build_tree(self) -> KinematicTree {
77		BuildLink::build_tree(self)
78	}
79}
80
81/// Non-builder methods
82impl LinkBuilder {
83	/// Gets a reference to the `name` of this `LinkBuilder`.
84	pub fn name(&self) -> &String {
85		&self.name
86	}
87
88	// TODO: Maybe Change to Iterator
89	/// Gets a reference to the `visuals` of this `LinkBuilder`.
90	pub fn visuals(&self) -> &Vec<VisualBuilder> {
91		&self.visuals
92	}
93
94	// TODO: Maybe Change to Iterator
95	/// Gets a mutable reference to the `visuals` of this `LinkBuilder`.
96	pub fn visuals_mut(&mut self) -> &mut Vec<VisualBuilder> {
97		&mut self.visuals
98	}
99
100	// TODO: Maybe Change to Iterator
101	/// Gets a reference to the `colliders` of this `LinkBuilder`.
102	pub fn colliders(&self) -> &Vec<CollisionBuilder> {
103		&self.colliders
104	}
105
106	// TODO: Maybe Change to Iterator
107	/// Gets a mutable reference to the `colliders` of this `LinkBuilder`.
108	pub fn colliders_mut(&mut self) -> &mut Vec<CollisionBuilder> {
109		&mut self.colliders
110	}
111
112	// TODO: Maybe Change to Iterator
113	/// Gets a reference to the `joints` of this `LinkBuilder`.
114	pub fn joints(&self) -> &Vec<JointBuilder> {
115		&self.joints
116	}
117
118	/// Gets an optional reference to the [`Inertial`](link_data::Inertial) of this `LinkBuilder`.
119	pub fn inertial(&self) -> Option<&link_data::Inertial> {
120		self.intertial.as_ref()
121	}
122}
123
124impl Mirror for LinkBuilder {
125	fn mirrored(&self, mirror_matrix: &Matrix3<f32>) -> Self {
126		Self {
127			name: self.name.clone(), // TODO: rename mirrored
128			visuals: self
129				.visuals
130				.iter()
131				.map(|visual_builder| visual_builder.mirrored(mirror_matrix))
132				.collect(),
133			colliders: self
134				.colliders
135				.iter()
136				.map(|collider_builder| collider_builder.mirrored(mirror_matrix))
137				.collect(),
138			intertial: self
139				.intertial
140				.as_ref()
141				.map(|intertial_data| intertial_data.mirrored(mirror_matrix)),
142			joints: self
143				.joints
144				.iter()
145				.map(|joint_builder| joint_builder.mirrored(mirror_matrix))
146				.collect(),
147		}
148	}
149}
150
151impl BuildLink for LinkBuilder {
152	fn build(self, tree: &Weak<KinematicDataTree>) -> ArcLock<Link> {
153		#[cfg(any(feature = "logging", test))]
154		log::info!("Making a Link[name = \"{}\"]", self.name);
155
156		Arc::new_cyclic(|me| {
157			RwLock::new(Link {
158				name: self.name,
159				tree: Weak::clone(tree),
160				direct_parent: LinkParent::KinematicTree(Weak::clone(tree)),
161				child_joints: Vec::new(),
162				inertial: self.intertial,
163				visuals: self.visuals.into_iter().map(VisualBuilder::build).collect(),
164				colliders: self
165					.colliders
166					.into_iter()
167					.map(CollisionBuilder::build)
168					.collect(),
169				me: Weak::clone(me),
170			})
171		})
172	}
173
174	fn start_building_chain(self, tree: &Weak<KinematicDataTree>) -> ArcLock<Link> {
175		let joint_builders = self.joints.clone();
176		let root = self.build(tree);
177
178		// This unwrap is Ok since the Link has just been build
179		let shape_data = root.read().unwrap().get_shape_data();
180
181		// This unwrap is Ok since the Link has just been build
182		root.write().unwrap().child_joints = joint_builders
183			.into_iter()
184			.map(|joint_builder| {
185				joint_builder.build_chain(tree, &Arc::downgrade(&root), shape_data.clone())
186			})
187			.collect();
188		root
189	}
190
191	fn build_chain(
192		self,
193		tree: &Weak<KinematicDataTree>,
194		parent_joint: &WeakLock<Joint>,
195	) -> ArcLock<Link> {
196		let shape_data = self.get_shape_data();
197
198		Arc::new_cyclic(|me| {
199			RwLock::new(Link {
200				name: self.name,
201				tree: Weak::clone(tree),
202				direct_parent: LinkParent::Joint(Weak::clone(parent_joint)),
203				child_joints: self
204					.joints
205					.into_iter()
206					.map(|joint_builder| joint_builder.build_chain(tree, me, shape_data.clone()))
207					.collect(),
208				inertial: self.intertial,
209				visuals: self.visuals.into_iter().map(VisualBuilder::build).collect(),
210				colliders: self
211					.colliders
212					.into_iter()
213					.map(CollisionBuilder::build)
214					.collect(),
215				me: Weak::clone(me),
216			})
217		})
218	}
219
220	fn get_shape_data(&self) -> LinkShapeData {
221		LinkShapeData::new(
222			self.visuals()
223				.iter()
224				.map(|visual| visual.get_geometry_data()),
225		)
226	}
227}
228
229impl GroupIDChanger for LinkBuilder {
230	unsafe fn change_group_id_unchecked(&mut self, new_group_id: &str) {
231		self.name.change_group_id_unchecked(new_group_id);
232
233		self.visuals_mut()
234			.iter_mut()
235			.for_each(|visual_builder| visual_builder.change_group_id_unchecked(new_group_id));
236		self.colliders_mut()
237			.iter_mut()
238			.for_each(|collision_builder| {
239				collision_builder.change_group_id_unchecked(new_group_id)
240			});
241
242		self.joints
243			.iter_mut()
244			.for_each(|joint_builder| joint_builder.change_group_id_unchecked(new_group_id));
245	}
246
247	fn apply_group_id(&mut self) {
248		self.name.apply_group_id();
249
250		self.visuals_mut()
251			.iter_mut()
252			.for_each(|visual_builder| visual_builder.apply_group_id());
253		self.colliders_mut()
254			.iter_mut()
255			.for_each(|collision_builder| collision_builder.apply_group_id());
256
257		self.joints
258			.iter_mut()
259			.for_each(|joint_builder| joint_builder.apply_group_id());
260	}
261}
262
263#[cfg(test)]
264mod tests {
265	use super::{BuildLink, LinkBuilder};
266	use crate::{
267		link::{
268			builder::{CollisionBuilder, VisualBuilder},
269			geometry::{BoxGeometry, CylinderGeometry, GeometryShapeData, SphereGeometry},
270			link_shape_data::LinkShapeData,
271		},
272		transform::Transform,
273	};
274	use test_log::test;
275	//TODO: Write test
276
277	#[test]
278	fn get_shape_data() {
279		{
280			let link_builder = LinkBuilder::new("a Link");
281
282			assert_eq!(
283				link_builder.get_shape_data(),
284				LinkShapeData {
285					main_geometry: GeometryShapeData {
286						transform: Transform::default(),
287						geometry: SphereGeometry::new(0.).into()
288					},
289					geometries: vec![GeometryShapeData {
290						transform: Transform::default(),
291						geometry: SphereGeometry::new(0.).into()
292					}]
293				}
294			)
295		}
296		{
297			let link_builder = LinkBuilder::new("a Link")
298				.add_visual(
299					VisualBuilder::new(BoxGeometry::new(10., 20., 30.)).named("a link's visual"),
300				)
301				.add_collider(
302					CollisionBuilder::new(SphereGeometry::new(3.)).named("this does not get used"),
303				);
304
305			assert_eq!(
306				link_builder.get_shape_data(),
307				LinkShapeData {
308					main_geometry: GeometryShapeData {
309						transform: Transform::default(),
310						geometry: BoxGeometry::new(10., 20., 30.).into()
311					},
312					geometries: vec![GeometryShapeData {
313						transform: Transform::default(),
314						geometry: BoxGeometry::new(10., 20., 30.).into()
315					}]
316				}
317			)
318		}
319		{
320			let link_builder = LinkBuilder::new("a Link")
321				.add_visual(
322					VisualBuilder::new(CylinderGeometry::new(1., 2.))
323						.transformed(Transform::new_translation(5., 0., 16.)),
324				)
325				.add_visual(
326					VisualBuilder::new(BoxGeometry::new(10., 20., 30.)).named("a link's visual"),
327				)
328				.add_collider(
329					CollisionBuilder::new(SphereGeometry::new(3.)).named("this does not get used"),
330				);
331
332			assert_eq!(
333				link_builder.get_shape_data(),
334				LinkShapeData {
335					main_geometry: GeometryShapeData {
336						transform: Transform::new_translation(5., 0., 16.),
337						geometry: CylinderGeometry::new(1., 2.).into()
338					},
339					geometries: vec![
340						GeometryShapeData {
341							transform: Transform::new_translation(5., 0., 16.),
342							geometry: CylinderGeometry::new(1., 2.).into()
343						},
344						GeometryShapeData {
345							transform: Transform::default(),
346							geometry: BoxGeometry::new(10., 20., 30.).into()
347						}
348					]
349				}
350			)
351		}
352	}
353
354	mod group_id_changer {
355		use super::{test, LinkBuilder};
356		use crate::identifiers::{GroupIDChanger, GroupIDError};
357
358		#[test]
359		fn change_group_id_unchecked_simple() {
360			#[inline]
361			fn test(name: impl Into<String>, new_group_id: &str, result: &str) {
362				let mut link_builder = LinkBuilder::new(name);
363				unsafe {
364					link_builder.change_group_id_unchecked(new_group_id);
365				}
366				assert_eq!(link_builder.name, result)
367			}
368
369			test("leg_[[M09da]]_link_1", "C10df", "leg_[[C10df]]_link_1");
370			test("leg_[[M09da]]_link_1", "", "leg_[[]]_link_1");
371			test("leg_[[M09da]]_link_1", "[[tsst", "leg_[[[[tsst]]_link_1");
372			test("leg_[[M09da]]_link_1", "tsst]]", "leg_[[tsst]]]]_link_1");
373		}
374
375		#[test]
376		#[ignore = "TODO"]
377		fn change_group_id_unchecked_advanced() {
378			todo!()
379		}
380
381		#[test]
382		fn change_group_id_simple() {
383			#[inline]
384			fn test(
385				name: impl Into<String>,
386				new_group_id: &str,
387				change_result: Result<(), GroupIDError>,
388				result: &str,
389			) {
390				let mut link_builder = LinkBuilder::new(name);
391				assert_eq!(link_builder.change_group_id(new_group_id), change_result);
392				assert_eq!(link_builder.name, result)
393			}
394
395			test(
396				"leg_[[M09da]]_link_1",
397				"C10df",
398				Ok(()),
399				"leg_[[C10df]]_link_1",
400			);
401			test(
402				"leg_[[M09da]]_link_1",
403				"",
404				Err(GroupIDError::new_empty()),
405				"leg_[[M09da]]_link_1",
406			);
407			test(
408				"leg_[[M09da]]_link_1",
409				"[[tsst",
410				Err(GroupIDError::new_open("[[tsst")),
411				"leg_[[M09da]]_link_1",
412			);
413			test(
414				"leg_[[M09da]]_link_1",
415				"tsst]]",
416				Err(GroupIDError::new_close("tsst]]")),
417				"leg_[[M09da]]_link_1",
418			);
419		}
420
421		#[test]
422		#[ignore = "TODO"]
423		fn change_group_id_advanced() {
424			todo!()
425		}
426
427		#[test]
428		fn apply_group_id_simple() {
429			#[inline]
430			fn test(name: impl Into<String>, result: &str) {
431				let mut link_builder = LinkBuilder::new(name);
432				link_builder.apply_group_id();
433				assert_eq!(link_builder.name, result)
434			}
435
436			test("leg_[[M09da]]_link_1", "leg_M09da_link_1");
437			test("leg_[[M09daf_link_1", "leg_[[M09daf_link_1");
438			test("leg_sM09da]]_link_1", "leg_sM09da]]_link_1");
439			test("leg_[\\[M09da]\\]_link_1", "leg_[[M09da]]_link_1");
440		}
441
442		#[test]
443		#[ignore = "TODO"]
444		fn apply_group_id_advanced() {
445			todo!()
446		}
447	}
448}