robot_description_builder/joint/
jointbuilder.rs

1use std::sync::{Arc, RwLock, Weak};
2
3use itertools::Itertools;
4use nalgebra::{vector, Matrix3};
5
6use crate::{
7	cluster_objects::kinematic_data_tree::KinematicDataTree,
8	identifiers::GroupIDChanger,
9	joint::{joint_data, joint_tranform_mode::JointTransformMode, Joint, JointType},
10	link::{
11		builder::{BuildLink, LinkBuilder},
12		Link, LinkShapeData,
13	},
14	transform::{Mirror, MirrorUpdater, Transform},
15	utils::{ArcLock, WeakLock},
16};
17
18pub trait BuildJoint: Into<JointBuilder> {
19	// Creates the joint ?? and subscribes it to the right right places
20	fn build(
21		self,
22		tree: Weak<KinematicDataTree>,
23		parent_link: WeakLock<Link>,
24		child_link: ArcLock<Link>,
25		parent_shape_data: LinkShapeData,
26	) -> ArcLock<Joint>;
27}
28
29// NOTE: Removed Trait bound due for `Chained<JointBuilder>`
30pub(crate) trait BuildJointChain {
31	fn build_chain(
32		self,
33		tree: &Weak<KinematicDataTree>,
34		parent_link: &WeakLock<Link>,
35		parent_shape_data: LinkShapeData,
36	) -> ArcLock<Joint>;
37}
38
39#[derive(Debug, PartialEq, Clone, Default)]
40pub struct JointBuilder {
41	pub(crate) name: String,
42	pub(crate) joint_type: JointType,
43	// TODO: Maybe add a Figure it out???
44	/// The transform from the origin of the parent to the origin of this `JointBuilder`.
45	///
46	/// In URDF this field is refered to as `<origin>`.
47	pub(crate) transform: JointTransformMode,
48	pub(crate) child: Option<LinkBuilder>,
49
50	//Consider making everything below pub to remove need for all the functions
51	// TODO: MAYBE CHANGE TO Vec3D Or something
52	pub(crate) axis: Option<(f32, f32, f32)>,
53	pub(crate) calibration: joint_data::CalibrationData,
54	pub(crate) dynamics: joint_data::DynamicsData,
55	pub(crate) limit: Option<joint_data::LimitData>,
56	pub(crate) mimic: Option<joint_data::MimicBuilderData>,
57	pub(crate) safety_controller: Option<joint_data::SafetyControllerData>,
58}
59
60// TODO: maybe add new_full ? or make fields public
61impl JointBuilder {
62	pub fn new(name: impl Into<String>, joint_type: JointType) -> Self {
63		Self {
64			name: name.into(),
65			joint_type,
66			..Default::default()
67		}
68	}
69
70	// TODO: rename transform
71	pub fn add_origin_offset(mut self, offset: (f32, f32, f32)) -> Self {
72		match &mut self.transform {
73			JointTransformMode::Direct(transform) => transform.translation = Some(offset),
74			JointTransformMode::FigureItOut(_) => todo!("Don't know how to do this"),
75		};
76		self
77	}
78
79	// TODO: rename transform
80	pub fn add_origin_rotation(mut self, rotation: (f32, f32, f32)) -> Self {
81		match &mut self.transform {
82			JointTransformMode::Direct(tranform) => tranform.rotation = Some(rotation),
83			JointTransformMode::FigureItOut(_) => todo!("Don't know how to do this yet"),
84		}
85		self
86	}
87
88	pub fn set_transform_simple(&mut self, transform: Transform) {
89		self.transform = JointTransformMode::Direct(transform);
90	}
91
92	// Nominated for Deprication
93	// Maybe Not??
94	#[inline]
95	pub(crate) fn with_transform<JTM>(&mut self, transform: JTM)
96	where
97		JTM: Into<JointTransformMode>,
98	{
99		self.transform = transform.into();
100	}
101
102	// TODO: HAS A CONFUSING NAME WITH SmartJointBuilder::with_axis, which consumes
103	/// Add the [`axis`](JointBuilder::axis) to the `JointBuillder`.
104	#[inline]
105	pub fn with_axis(&mut self, axis: (f32, f32, f32)) {
106		self.axis = Some(axis);
107	}
108
109	/// Add the full [`CalibrationData`](joint_data::CalibrationData) to the `JointBuillder`.
110	#[inline]
111	pub(crate) fn with_calibration_data(&mut self, calibration_data: joint_data::CalibrationData) {
112		self.calibration = calibration_data;
113	}
114
115	/// Add the full [`DynamicsData`](joint_data::DynamicsData) to the `JointBuillder`.
116	#[inline]
117	pub(crate) fn with_dynamics_data(&mut self, dynamics_data: joint_data::DynamicsData) {
118		self.dynamics = dynamics_data;
119	}
120
121	/// Add the full [`LimitData`](joint_data::LimitData) to the `JointBuillder`.
122	#[inline]
123	pub(crate) fn with_limit_data(&mut self, limit_data: joint_data::LimitData) {
124		self.limit = Some(limit_data);
125	}
126
127	/// Add the full [`MimicData`](joint_data::MimicData) to the `JointBuillder`.
128	#[inline]
129	pub(crate) fn with_mimic_data(&mut self, mimic_data: joint_data::MimicBuilderData) {
130		self.mimic = Some(mimic_data);
131	}
132
133	/// Add the full [`SafetyControllerData`](joint_data::SafetyControllerData) to the `JointBuillder`.
134	#[inline]
135	pub(crate) fn with_safety_controller(
136		&mut self,
137		safety_controller_data: joint_data::SafetyControllerData,
138	) {
139		self.safety_controller = Some(safety_controller_data);
140	}
141}
142
143// Mostly for Python Wrapper
144/// Non Builder functions
145impl JointBuilder {
146	pub fn name(&self) -> &String {
147		&self.name
148	}
149
150	pub fn joint_type(&self) -> &JointType {
151		&self.joint_type
152	}
153
154	pub fn transform(&self) -> Option<&Transform> {
155		// TODO: Maybe add a advanced mode for figure it out?
156		match &self.transform {
157			JointTransformMode::Direct(transform) => match transform.contains_some() {
158				true => Some(transform),
159				false => None,
160			},
161			JointTransformMode::FigureItOut(_) => todo!("?"),
162		}
163	}
164
165	pub fn transform_mut(&mut self) -> Option<&mut Transform> {
166		// TODO: Maybe add a advanced mode for figure it out?
167		match &mut self.transform {
168			JointTransformMode::Direct(transform) => match transform.contains_some() {
169				true => Some(transform),
170				false => None,
171			},
172			JointTransformMode::FigureItOut(_) => todo!("?"),
173		}
174	}
175
176	// TODO: Transform
177
178	pub fn child(&self) -> Option<&LinkBuilder> {
179		self.child.as_ref()
180	}
181
182	pub fn axis(&self) -> Option<(f32, f32, f32)> {
183		self.axis
184	}
185
186	pub fn axis_mut(&mut self) -> Option<&mut (f32, f32, f32)> {
187		self.axis.as_mut()
188	}
189
190	pub fn calibration(&self) -> &joint_data::CalibrationData {
191		&self.calibration
192	}
193
194	pub fn calibration_mut(&mut self) -> &mut joint_data::CalibrationData {
195		&mut self.calibration
196	}
197
198	pub fn dynamics(&self) -> &joint_data::DynamicsData {
199		&self.dynamics
200	}
201
202	pub fn dynamics_mut(&mut self) -> &mut joint_data::DynamicsData {
203		&mut self.dynamics
204	}
205
206	pub fn limit(&self) -> Option<&joint_data::LimitData> {
207		self.limit.as_ref()
208	}
209
210	pub fn limit_mut(&mut self) -> &mut Option<joint_data::LimitData> {
211		&mut self.limit
212	}
213
214	pub fn mimic(&self) -> Option<&joint_data::MimicBuilderData> {
215		self.mimic.as_ref()
216	}
217
218	pub fn mimic_mut(&mut self) -> &mut Option<joint_data::MimicBuilderData> {
219		&mut self.mimic
220	}
221
222	pub fn safety_controller(&self) -> Option<&joint_data::SafetyControllerData> {
223		self.safety_controller.as_ref()
224	}
225
226	pub fn safety_controller_mut(&mut self) -> &mut Option<joint_data::SafetyControllerData> {
227		&mut self.safety_controller
228	}
229}
230
231impl Mirror for JointBuilder {
232	fn mirrored(&self, mirror_matrix: &Matrix3<f32>) -> Self {
233		let (transform, new_mirror_matrix) = self.transform.mirrored_update_matrix(mirror_matrix);
234		Self {
235			name: self.name.clone(), // FIXME: Rename
236			joint_type: self.joint_type,
237			transform,
238			child: self
239				.child
240				.as_ref()
241				.map(|link_builder| link_builder.mirrored(&new_mirror_matrix)),
242			axis: match (self.joint_type, self.axis) {
243				(JointType::Fixed | JointType::Floating, _) => None, // TODO: Figure out if this clause should be moved down to allow for Fixed and Floating with axis if desired?
244				(_, Some((x, y, z))) => Some(
245					(new_mirror_matrix * vector![x, y, z] * -1.)
246						// Theoretically not necessary, but float rounding errors are a thing | TODO: Figure out if this improves the situation or makes it worse
247						.normalize()
248						.iter()
249						.copied()
250						.collect_tuple()
251						// Unwrapping here to ensure that we collect to a Tuple3 | TODO: Change to expect? or remove
252						.unwrap(),
253				),
254				(
255					JointType::Revolute
256					| JointType::Continuous
257					| JointType::Prismatic
258					| JointType::Planar,
259					None,
260				) => Some(
261					(new_mirror_matrix * vector![1., 0., 0.] * -1.)
262						.normalize() // Theoretically not necessary, but float rounding errors are a thing | TODO: Figure out if this improves the situation or makes it worse
263						.iter()
264						.copied()
265						.collect_tuple()
266						.unwrap(), // Unwrapping here to ensure that we collect to a Tuple3 | TODO: Change to expect? or remove
267				),
268			},
269			calibration: self.calibration,             // TODO: Is this Correct?
270			dynamics: self.dynamics,                   // TODO: Is this Correct?
271			limit: self.limit,                         // TODO: Is this Correct?
272			mimic: self.mimic.as_ref().cloned(),       // TODO: Is this Correct?
273			safety_controller: self.safety_controller, // TODO: Is this Correct?
274		}
275	}
276}
277
278impl BuildJoint for JointBuilder {
279	fn build(
280		self,
281		tree: Weak<KinematicDataTree>,
282		parent_link: WeakLock<Link>,
283		child_link: ArcLock<Link>,
284		parent_link_size_data: LinkShapeData,
285	) -> ArcLock<Joint> {
286		let joint = Arc::new_cyclic(|me| -> RwLock<Joint> {
287			RwLock::new(Joint {
288				name: self.name,
289				tree: Weak::clone(&tree),
290				parent_link,
291				child_link,
292				joint_type: self.joint_type,
293				transform: self.transform.apply(parent_link_size_data),
294				axis: self.axis,
295				calibration: self.calibration,
296				dynamics: self.dynamics,
297				limit: self.limit,
298				mimic: self.mimic.map(|mimic| mimic.to_mimic_data(&tree)),
299				safety_controller: self.safety_controller,
300				me: Weak::clone(me),
301			})
302		});
303
304		tree.upgrade().unwrap().try_add_joint(&joint).unwrap(); // FIXME: Figure out if Unwrap is Ok here?
305		joint
306	}
307}
308
309impl BuildJointChain for JointBuilder {
310	fn build_chain(
311		self,
312		tree: &Weak<KinematicDataTree>,
313		parent_link: &WeakLock<Link>,
314		parent_shape_data: LinkShapeData,
315	) -> ArcLock<Joint> {
316		#[cfg(any(feature = "logging", test))]
317		log::trace!("Building a Joint[name ='{}']", self.name);
318
319		Arc::new_cyclic(|me| {
320			RwLock::new(Joint {
321				name: self.name,
322				tree: Weak::clone(tree),
323				parent_link: Weak::clone(parent_link),
324				// This is Ok, since the Joint can only be attached with specific functions.
325				child_link: self.child.expect("When Building Kinematic Branches Joints should have a child link, since a Joint only makes sense when attachted to a Parent and a Child").build_chain(tree, me),
326				joint_type: self.joint_type,
327				transform: self.transform.apply(parent_shape_data),
328				axis: self.axis,
329				calibration: self.calibration,
330				dynamics: self.dynamics,
331				limit: self.limit,
332				mimic: self
333					.mimic
334					.map(|mimic| mimic.to_mimic_data(tree)),
335				safety_controller: self.safety_controller,
336				me: Weak::clone(me),
337			})
338		})
339	}
340}
341
342impl GroupIDChanger for JointBuilder {
343	unsafe fn change_group_id_unchecked(&mut self, new_group_id: &str) {
344		self.name.change_group_id_unchecked(new_group_id);
345
346		if let Some(link_builder) = self.child.as_mut() {
347			link_builder.change_group_id_unchecked(new_group_id);
348		}
349	}
350
351	fn apply_group_id(&mut self) {
352		self.name.apply_group_id();
353
354		if let Some(link_builder) = self.child.as_mut() {
355			link_builder.apply_group_id();
356		}
357	}
358}
359
360#[cfg(test)]
361mod tests {
362	use super::{JointBuilder, JointType};
363	use test_log::test;
364
365	mod group_id_changer {
366		use super::{test, JointBuilder, JointType};
367		use crate::identifiers::{GroupIDChanger, GroupIDError};
368
369		#[test]
370		fn change_group_id_unchecked_simple() {
371			#[inline]
372			fn test(
373				name: impl Into<String>,
374				joint_type: JointType,
375				new_group_id: &str,
376				result: &str,
377			) {
378				let mut joint_builder = JointBuilder::new(name, joint_type);
379				unsafe {
380					joint_builder.change_group_id_unchecked(new_group_id);
381				}
382				assert_eq!(joint_builder.name, result)
383			}
384
385			test(
386				"leg_[[M09da]]_joint_1",
387				JointType::Fixed,
388				"C10df",
389				"leg_[[C10df]]_joint_1",
390			);
391
392			test(
393				"leg_[[M09da]]_joint_1",
394				JointType::Fixed,
395				"",
396				"leg_[[]]_joint_1",
397			);
398
399			test(
400				"leg_[[M09da]]_joint_1",
401				JointType::Fixed,
402				"[[tsst",
403				"leg_[[[[tsst]]_joint_1",
404			);
405
406			test(
407				"leg_[[M09da]]_joint_1",
408				JointType::Fixed,
409				"tsst]]",
410				"leg_[[tsst]]]]_joint_1",
411			);
412		}
413
414		#[test]
415		#[ignore = "TODO"]
416		fn test_change_group_id_unchecked_advanced() {
417			todo!("Chained things")
418		}
419
420		#[test]
421		fn change_group_id_simple() {
422			#[inline]
423			fn test(
424				name: impl Into<String>,
425				joint_type: JointType,
426				group_id: &str,
427				change_result: Result<(), GroupIDError>,
428				final_name: &str,
429			) {
430				let mut joint_builder = JointBuilder::new(name, joint_type);
431				assert_eq!(joint_builder.change_group_id(group_id), change_result);
432				assert_eq!(joint_builder.name, final_name)
433			}
434
435			test(
436				"leg_[[M09da]]_joint_1",
437				JointType::Fixed,
438				"C10df",
439				Ok(()),
440				"leg_[[C10df]]_joint_1",
441			);
442			test(
443				"leg_[[M09da]]_joint_1",
444				JointType::Fixed,
445				"",
446				Err(GroupIDError::new_empty()),
447				"leg_[[M09da]]_joint_1",
448			);
449			test(
450				"leg_[[M09da]]_joint_1",
451				JointType::Fixed,
452				"[[tsst",
453				Err(GroupIDError::new_open("[[tsst")),
454				"leg_[[M09da]]_joint_1",
455			);
456			test(
457				"leg_[[M09da]]_joint_1",
458				JointType::Fixed,
459				"tsst]]",
460				Err(GroupIDError::new_close("tsst]]")),
461				"leg_[[M09da]]_joint_1",
462			);
463		}
464
465		#[test]
466		#[ignore = "TODO"]
467		fn change_group_id_advanced() {
468			todo!()
469		}
470
471		#[test]
472		fn apply_group_id_simple() {
473			#[inline]
474			fn test(name: impl Into<String>, joint_type: JointType, final_name: &str) {
475				let mut joint_builder = JointBuilder::new(name, joint_type);
476				joint_builder.apply_group_id();
477				assert_eq!(joint_builder.name, final_name)
478			}
479
480			test(
481				"leg_[[M09da]]_joint_1",
482				JointType::Fixed,
483				"leg_M09da_joint_1",
484			);
485			test(
486				"leg_[[M09daf_joint_1",
487				JointType::Fixed,
488				"leg_[[M09daf_joint_1",
489			);
490			test(
491				"leg_sM09da]]_joint_1",
492				JointType::Fixed,
493				"leg_sM09da]]_joint_1",
494			);
495			test(
496				"leg_[\\[M09da]\\]_joint_1",
497				JointType::Fixed,
498				"leg_[[M09da]]_joint_1",
499			);
500		}
501
502		#[test]
503		#[ignore = "TODO"]
504		fn apply_group_id_advanced() {
505			todo!()
506		}
507	}
508}