robot_description_builder/link/builder/
linkbuilder.rs1use 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#[derive(Debug, PartialEq, Clone, Default)]
31pub struct LinkBuilder {
32 pub(crate) name: String,
37 pub(crate) visuals: Vec<VisualBuilder>,
39 pub(crate) colliders: Vec<CollisionBuilder>,
40 pub(crate) intertial: Option<link_data::Inertial>,
42 pub(crate) joints: Vec<JointBuilder>,
43}
44
45impl LinkBuilder {
46 pub fn new(name: impl Into<String>) -> LinkBuilder {
48 Self {
49 name: name.into(),
50 ..Default::default()
51 }
52 }
53
54 pub fn add_visual(mut self, visual: VisualBuilder) -> Self {
56 self.visuals.push(visual);
57 self
58 }
59
60 pub fn add_collider(mut self, collider: CollisionBuilder) -> Self {
63 self.colliders.push(collider);
64 self
65 }
66
67 pub fn add_intertial(mut self, inertial: link_data::Inertial) -> Self {
71 self.intertial = Some(inertial);
72 self
73 }
74
75 pub fn build_tree(self) -> KinematicTree {
77 BuildLink::build_tree(self)
78 }
79}
80
81impl LinkBuilder {
83 pub fn name(&self) -> &String {
85 &self.name
86 }
87
88 pub fn visuals(&self) -> &Vec<VisualBuilder> {
91 &self.visuals
92 }
93
94 pub fn visuals_mut(&mut self) -> &mut Vec<VisualBuilder> {
97 &mut self.visuals
98 }
99
100 pub fn colliders(&self) -> &Vec<CollisionBuilder> {
103 &self.colliders
104 }
105
106 pub fn colliders_mut(&mut self) -> &mut Vec<CollisionBuilder> {
109 &mut self.colliders
110 }
111
112 pub fn joints(&self) -> &Vec<JointBuilder> {
115 &self.joints
116 }
117
118 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(), 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 let shape_data = root.read().unwrap().get_shape_data();
180
181 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 #[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}