1mod joint_tranform_mode;
2mod jointbuilder;
3
4#[cfg(not(feature = "smart-joint-extension"))]
5mod smartjointbuilder;
6#[cfg(feature = "smart-joint-extension")]
7pub mod smartjointbuilder;
8
9pub mod joint_data;
13
14pub use joint_tranform_mode::JointTransformMode;
15pub(crate) use jointbuilder::BuildJointChain;
16pub use jointbuilder::{BuildJoint, JointBuilder};
17pub use smartjointbuilder::SmartJointBuilder;
18
19#[cfg(feature = "xml")]
20use std::borrow::Cow;
21use std::sync::{Arc, Weak};
22
23#[cfg(feature = "urdf")]
24use crate::to_rdf::to_urdf::ToURDF;
25use crate::{
26 chained::Chained,
27 cluster_objects::kinematic_data_tree::KinematicDataTree,
28 identifiers::GroupID,
29 link::Link,
30 transform::Transform,
31 utils::{ArcLock, ArcRW, WeakLock},
32 yank_errors::{RebuildBranchError, YankJointError},
33};
34
35#[cfg(feature = "xml")]
36use quick_xml::{events::attributes::Attribute, name::QName};
37
38#[derive(Debug)]
39pub struct Joint {
40 pub(crate) name: String,
42 pub(crate) tree: Weak<KinematicDataTree>,
44 pub(crate) parent_link: WeakLock<Link>,
46 pub(crate) child_link: ArcLock<Link>, pub(crate) joint_type: JointType,
49 transform: Transform,
53 axis: Option<(f32, f32, f32)>,
54 calibration: joint_data::CalibrationData,
55 dynamics: joint_data::DynamicsData,
56 limit: Option<joint_data::LimitData>,
57 mimic: Option<joint_data::MimicData>,
59 safety_controller: Option<joint_data::SafetyControllerData>,
60
61 me: WeakLock<Joint>,
62}
63
64impl Joint {
65 pub fn name(&self) -> &String {
66 &self.name
67 }
68
69 pub fn joint_type(&self) -> JointType {
70 self.joint_type
71 }
72
73 pub fn parent_link(&self) -> ArcLock<Link> {
79 self.parent_link
81 .upgrade()
82 .expect("Joint's parent link should be set")
83 }
84
85 pub fn child_link(&self) -> ArcLock<Link> {
88 Arc::clone(&self.child_link)
89 }
90
91 pub(crate) fn child_link_ref(&self) -> &ArcLock<Link> {
93 &self.child_link
94 }
95
96 pub fn transform(&self) -> &Transform {
97 &self.transform
98 }
99
100 pub fn axis(&self) -> Option<(f32, f32, f32)> {
101 self.axis
103 }
104
105 pub fn rebuild(&self) -> JointBuilder {
111 #[cfg(any(feature = "logging", test))]
112 log::info!(target: "JointBuilder","Rebuilding: {}", self.name());
113 JointBuilder {
114 name: self.name.clone(),
115 joint_type: self.joint_type,
116 child: None,
118 transform: self.transform.into(),
119 axis: self.axis,
120 calibration: self.calibration,
121 dynamics: self.dynamics,
122 limit: self.limit,
123 mimic: self.mimic.clone().map(|mimic| mimic.into()),
124 safety_controller: self.safety_controller,
125 }
126 }
127
128 pub(crate) fn rebuild_branch_continued(&self) -> Result<JointBuilder, RebuildBranchError> {
129 #[cfg(any(feature = "logging", test))]
130 log::info!(target: "JointBuilder","Rebuilding: {}", self.name());
131 Ok(JointBuilder {
132 child: Some(self.child_link.mread()?.rebuild_branch_continued()?),
133 ..self.rebuild()
134 })
135 }
136
137 pub fn rebuild_branch(&self) -> Result<Chained<JointBuilder>, RebuildBranchError> {
139 #[cfg(any(feature = "logging", test))]
140 log::info!(target: "JointBuilder","Starting Branch Rebuilding: {}", self.name());
141 Ok(Chained(self.rebuild_branch_continued()?))
142 }
143
144 pub(crate) fn yank(&self) -> Result<JointBuilder, YankJointError> {
152 let builder = self.rebuild_branch_continued()?;
153
154 #[cfg(any(feature = "logging", test))]
155 log::info!("Yanked Joint \"{}\"", self.name());
156
157 self.parent_link()
158 .mwrite()?
159 .joints_mut()
160 .retain(|joint| !Arc::ptr_eq(&self.get_self(), joint));
161
162 *self.tree.upgrade().unwrap().newest_link.write().unwrap() = Weak::clone(&self.parent_link);
164
165 Ok(builder)
166 }
167
168 pub fn get_self(&self) -> ArcLock<Joint> {
170 Weak::upgrade(&self.me).unwrap()
172 }
173
174 pub fn get_weak_self(&self) -> WeakLock<Joint> {
176 Weak::clone(&self.me)
177 }
178}
179
180#[cfg(feature = "urdf")]
181impl ToURDF for Joint {
182 fn to_urdf(
183 &self,
184 writer: &mut quick_xml::Writer<std::io::Cursor<Vec<u8>>>,
185 urdf_config: &crate::to_rdf::to_urdf::URDFConfig,
186 ) -> Result<(), quick_xml::Error> {
187 let element = writer
188 .create_element("joint")
189 .with_attribute(Attribute {
190 key: QName(b"name"),
191 value: self.name().display().as_bytes().into(),
192 })
193 .with_attribute(Attribute {
194 key: QName(b"type"),
195 value: self.joint_type().into(),
196 });
197
198 element.write_inner_content(|writer| -> quick_xml::Result<()> {
199 let transform = self.transform();
200 if transform.contains_some() {
201 transform.to_urdf(writer, urdf_config)?;
202 }
203
204 writer
205 .create_element("parent")
206 .with_attribute(Attribute {
207 key: QName(b"link"),
208 value: self
209 .parent_link()
210 .read()
211 .unwrap() .name()
213 .display()
214 .as_bytes()
215 .into(),
216 })
217 .write_empty()?;
218
219 writer
220 .create_element("child")
221 .with_attribute(Attribute {
222 key: QName(b"link"),
223 value: self
224 .child_link()
225 .read()
226 .unwrap() .name()
228 .display()
229 .as_bytes()
230 .into(),
231 })
232 .write_empty()?;
233
234 if let Some((x, y, z)) = &self.axis {
235 writer
237 .create_element("axis")
238 .with_attribute(Attribute {
239 key: QName(b"xyz"),
240 value: format!("{} {} {}", x, y, z).as_bytes().into(),
241 })
242 .write_empty()?;
243 }
244
245 self.calibration.to_urdf(writer, urdf_config)?;
246 self.dynamics.to_urdf(writer, urdf_config)?;
247
248 if let Some(limit) = &self.limit {
249 limit.to_urdf(writer, urdf_config)?;
250 }
251
252 if let Some(mimic) = &self.mimic {
254 mimic.to_urdf(writer, urdf_config)?;
255 }
256
257 if let Some(safety_contoller) = &self.safety_controller {
258 safety_contoller.to_urdf(writer, urdf_config)?;
259 }
260
261 Ok(())
262 })?;
263
264 self.child_link()
265 .read()
266 .unwrap() .to_urdf(writer, urdf_config)?;
268 Ok(())
269 }
270}
271
272impl PartialEq for Joint {
274 fn eq(&self, other: &Self) -> bool {
275 Weak::ptr_eq(&self.me, &other.me)
276 && self.name == other.name
277 && Weak::ptr_eq(&self.tree, &other.tree)
278 && Weak::ptr_eq(&self.parent_link, &other.parent_link)
279 && Arc::ptr_eq(&self.child_link, &other.child_link)
280 && self.joint_type == other.joint_type
281 && self.transform == other.transform
282 && self.axis() == other.axis()
283 && self.calibration == other.calibration
284 && self.dynamics == other.dynamics
285 && self.limit == other.limit
286 && self.mimic == other.mimic
287 && self.safety_controller == other.safety_controller
288 }
290}
291
292#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
305pub enum JointType {
306 #[default]
311 Fixed,
312 Revolute,
319 Continuous,
328 Prismatic,
335 Floating,
343 Planar,
351}
352
353impl ToString for JointType {
354 fn to_string(&self) -> String {
355 match self {
356 JointType::Fixed => String::from("fixed"),
357 JointType::Revolute => String::from("revolute"),
358 JointType::Continuous => String::from("continuous"),
359 JointType::Prismatic => String::from("prismatic"),
360 JointType::Floating => String::from("floating"),
361 JointType::Planar => String::from("planar"),
362 }
363 }
364}
365
366#[cfg(feature = "xml")]
367impl From<JointType> for Cow<'_, [u8]> {
368 fn from(value: JointType) -> Self {
369 value.to_string().into_bytes().into()
370 }
371}
372
373#[cfg(test)]
374mod tests {
375
376 use crate::{
377 cluster_objects::KinematicInterface,
378 joint::{joint_data, smartjointbuilder::SmartJointBuilder, JointBuilder, JointType},
379 link::{
380 builder::LinkBuilder,
381 link_data::{
382 geometry::{BoxGeometry, CylinderGeometry, SphereGeometry},
383 Visual,
384 },
385 },
386 linkbuilding::{CollisionBuilder, VisualBuilder},
387 material::MaterialDescriptor,
388 transform::Transform,
389 };
390 use test_log::test;
391
392 #[test]
393 fn rebuild() {
394 let tree = LinkBuilder::new("root").build_tree();
395 tree.get_newest_link()
396 .try_write()
397 .unwrap()
398 .try_attach_child(
399 SmartJointBuilder::new("Joint1")
400 .fixed()
401 .add_transform(Transform::new_translation(2.0, 3.0, 5.0)),
402 LinkBuilder::new("child"),
403 )
404 .unwrap();
405
406 let rebuilder = tree
407 .get_joint("Joint1")
408 .unwrap()
409 .try_read()
410 .unwrap()
411 .rebuild();
412 assert_eq!(
413 rebuilder,
414 JointBuilder::new("Joint1", crate::JointType::Fixed).add_origin_offset((2.0, 3.0, 5.0))
415 )
416 }
417
418 #[test]
419 fn yank_simple() {
420 let material_red = MaterialDescriptor::new_color(1., 0., 0., 1.).named("Red");
421
422 let tree = LinkBuilder::new("link-0")
423 .add_collider(CollisionBuilder::new(BoxGeometry::new(1.0, 2.0, 3.0)))
424 .add_visual(
425 Visual::builder(BoxGeometry::new(1.0, 2.0, 3.0)).materialized(material_red.clone()),
426 )
427 .build_tree();
428
429 tree.get_root_link()
430 .try_write()
431 .unwrap()
432 .try_attach_child(
433 SmartJointBuilder::new("joint-0")
434 .add_transform(Transform::new_translation(1.0, 0., 0.))
435 .fixed(),
436 LinkBuilder::new("link-1")
437 .add_collider(
438 CollisionBuilder::new(SphereGeometry::new(4.))
439 .transformed(Transform::new_translation(2., 0., 0.)),
440 )
441 .add_visual(
442 Visual::builder(SphereGeometry::new(4.))
443 .transformed(Transform::new_translation(2., 0., 0.))
444 .materialized(material_red.clone()),
445 ),
446 )
447 .unwrap();
448
449 assert_eq!(tree.get_links().try_read().unwrap().len(), 2);
450 assert_eq!(tree.get_joints().try_read().unwrap().len(), 1);
451 assert_eq!(tree.get_materials().try_read().unwrap().len(), 1);
452
453 let builder = tree.yank_joint("joint-0").unwrap();
463
464 assert_eq!(tree.get_links().try_read().unwrap().len(), 1);
465 assert_eq!(tree.get_joints().try_read().unwrap().len(), 0);
466
467 assert_eq!(
468 builder.0,
469 JointBuilder {
470 name: "joint-0".into(),
471 joint_type: JointType::Fixed,
472 transform: Transform {
473 translation: Some((1., 0., 0.)),
474 rotation: None
475 }
476 .into(),
477 child: Some(LinkBuilder {
478 name: "link-1".into(),
479 visuals: vec![VisualBuilder::new_full(
480 None,
481 Some(Transform {
482 translation: Some((2., 0., 0.)),
483 rotation: None
484 }),
485 SphereGeometry::new(4.),
486 Some(material_red.clone())
487 )],
488 colliders: vec![CollisionBuilder::new_full(
489 None,
490 Some(Transform {
491 translation: Some((2., 0., 0.)),
492 rotation: None
493 }),
494 SphereGeometry::new(4.)
495 )],
496 ..Default::default()
497 }),
498 ..Default::default() }
500 );
501
502 }
504
505 #[test]
506 fn yank_less_simple() {
507 let tree = {
508 let material_red = MaterialDescriptor::new_color(1., 0., 0., 1.).named("Red");
509
510 LinkBuilder::new("link-0")
511 .add_collider(CollisionBuilder::new(BoxGeometry::new(1.0, 2.0, 3.0)))
512 .add_visual(
513 Visual::builder(BoxGeometry::new(1.0, 2.0, 3.0))
514 .materialized(material_red.clone()),
515 )
516 .build_tree()
517 };
518
519 tree.get_root_link()
520 .try_write()
521 .unwrap()
522 .try_attach_child(
523 SmartJointBuilder::new("joint-0")
524 .add_transform(Transform::new_translation(1.0, 0., 0.))
525 .fixed(),
526 {
527 let tmp_tree = LinkBuilder::new("link-1")
528 .add_collider(
529 CollisionBuilder::new(SphereGeometry::new(4.))
530 .transformed(Transform::new_translation(2., 0., 0.)),
531 )
532 .add_visual(
533 Visual::builder(SphereGeometry::new(4.))
534 .transformed(Transform::new_translation(2., 0., 0.))
535 .materialized(
536 MaterialDescriptor::new_color(0., 0., 1., 1.).named("Blue"),
537 ),
538 )
539 .build_tree();
540
541 tmp_tree
542 .get_root_link()
543 .write()
544 .unwrap()
545 .try_attach_child(
546 SmartJointBuilder::new("joint-1-1")
547 .revolute()
548 .add_transform(Transform::new_translation(4., 0., 0.))
549 .with_axis((0., 0., 1.))
550 .with_limit(100., 1000.)
551 .set_upper_limit(std::f32::consts::FRAC_PI_6)
552 .set_lower_limit(-std::f32::consts::FRAC_PI_6),
553 LinkBuilder::new("link-1-1").add_visual(
554 Visual::builder(CylinderGeometry::new(0.5, 18.))
555 .named("link-1-1-vis")
556 .transformed(Transform::new_translation(9., 0.5, 0.))
557 .materialized(MaterialDescriptor::new_color(
558 0.5, 0.5, 0.5, 0.75,
559 )),
560 ),
561 )
562 .unwrap();
563
564 tmp_tree
565 },
566 )
567 .unwrap();
568
569 tree.get_root_link()
570 .write()
571 .unwrap()
572 .try_attach_child(
573 JointBuilder::new("joint-2", JointType::Fixed).add_origin_offset((0., 0., 1.5)),
574 LinkBuilder::new("link-2").build_tree(),
575 )
576 .unwrap();
577
578 assert_eq!(tree.get_links().try_read().unwrap().len(), 4);
579 assert_eq!(tree.get_joints().try_read().unwrap().len(), 3);
580 assert_eq!(tree.get_materials().try_read().unwrap().len(), 2);
581
582 assert_eq!(tree.get_root_link().try_read().unwrap().name(), "link-0");
583 assert_eq!(tree.get_newest_link().try_read().unwrap().name(), "link-2");
584
585 {
586 let tree = tree.clone();
587 let yanked_branch = tree.yank_joint("joint-2");
588
589 assert!(yanked_branch.is_some());
590
591 assert_eq!(tree.get_links().try_read().unwrap().len(), 3);
592 assert_eq!(tree.get_joints().try_read().unwrap().len(), 2);
593 assert_eq!(tree.get_materials().try_read().unwrap().len(), 2);
594
595 {
596 let mut link_keys: Vec<String> = tree
597 .get_links()
598 .try_read()
599 .unwrap()
600 .keys()
601 .cloned()
602 .collect();
603 link_keys.sort();
604
605 assert_eq!(link_keys, vec!["link-0", "link-1", "link-1-1"]);
606 }
607 {
608 let mut joint_keys: Vec<String> = tree
609 .get_joints()
610 .try_read()
611 .unwrap()
612 .keys()
613 .cloned()
614 .collect();
615 joint_keys.sort();
616
617 assert_eq!(joint_keys, vec!["joint-0", "joint-1-1"]);
618 }
619 {
620 let mut material_keys: Vec<String> = tree
621 .get_materials()
622 .try_read()
623 .unwrap()
624 .keys()
625 .cloned()
626 .collect();
627 material_keys.sort();
628
629 assert_eq!(material_keys, vec!["Blue", "Red"]);
630 }
631
632 assert_eq!(tree.get_root_link().read().unwrap().name(), "link-0");
633 assert_eq!(tree.get_newest_link().read().unwrap().name(), "link-0");
634
635 assert_eq!(
636 yanked_branch.unwrap().0,
637 JointBuilder {
638 name: "joint-2".into(),
639 joint_type: JointType::Fixed,
640 transform: Transform {
641 translation: Some((0., 0., 1.5)),
642 ..Default::default()
643 }
644 .into(),
645 child: Some(LinkBuilder::new("link-2")),
646 ..Default::default()
647 }
648 )
649 }
650
651 {
652 let tree = tree.clone();
653 let yanked_branch = tree.yank_joint("joint-1-1");
654
655 assert!(yanked_branch.is_some());
656
657 assert_eq!(tree.get_links().try_read().unwrap().len(), 3);
658 assert_eq!(tree.get_joints().try_read().unwrap().len(), 2);
659 assert_eq!(tree.get_materials().try_read().unwrap().len(), 2);
660
661 {
662 let mut link_keys: Vec<String> = tree
663 .get_links()
664 .try_read()
665 .unwrap()
666 .keys()
667 .cloned()
668 .collect();
669 link_keys.sort();
670
671 assert_eq!(link_keys, vec!["link-0", "link-1", "link-2"]);
672 }
673 {
674 let mut joint_keys: Vec<String> = tree
675 .get_joints()
676 .try_read()
677 .unwrap()
678 .keys()
679 .cloned()
680 .collect();
681 joint_keys.sort();
682
683 assert_eq!(joint_keys, vec!["joint-0", "joint-2"]);
684 }
685 {
686 let mut material_keys: Vec<String> = tree
687 .get_materials()
688 .try_read()
689 .unwrap()
690 .keys()
691 .cloned()
692 .collect();
693 material_keys.sort();
694
695 assert_eq!(material_keys, vec!["Blue", "Red"]);
696 }
697
698 assert_eq!(tree.get_root_link().read().unwrap().name(), "link-0");
699 assert_eq!(tree.get_newest_link().read().unwrap().name(), "link-1");
700
701 assert_eq!(
702 yanked_branch.unwrap().0,
703 JointBuilder {
704 name: "joint-1-1".into(),
705 joint_type: JointType::Revolute,
706 transform: Transform {
707 translation: Some((4., 0., 0.)),
708 ..Default::default()
709 }
710 .into(),
711 child: Some(LinkBuilder {
712 name: "link-1-1".into(),
713 visuals: vec![VisualBuilder::new_full(
714 Some("link-1-1-vis".into()),
715 Some(Transform {
716 translation: Some((9., 0.5, 0.)),
717 ..Default::default()
718 }),
719 CylinderGeometry::new(0.5, 18.),
720 Some(MaterialDescriptor::new_color(0.5, 0.5, 0.5, 0.75))
721 )],
722 ..Default::default()
723 }),
724 axis: Some((0., 0., 1.)),
725 limit: Some(joint_data::LimitData {
726 effort: 100.,
727 velocity: 1000.,
728 lower: Some(-std::f32::consts::FRAC_PI_6),
729 upper: Some(std::f32::consts::FRAC_PI_6),
730 }),
731 ..Default::default()
732 }
733 )
734 }
735
736 {
737 let tree = tree.clone();
738 let yanked_branch = tree.yank_joint("joint-0");
739
740 assert!(yanked_branch.is_some());
741
742 assert_eq!(tree.get_links().try_read().unwrap().len(), 2);
743 assert_eq!(tree.get_joints().try_read().unwrap().len(), 1);
744 assert_eq!(tree.get_materials().try_read().unwrap().len(), 2);
745
746 {
747 let mut link_keys: Vec<String> = tree
748 .get_links()
749 .try_read()
750 .unwrap()
751 .keys()
752 .cloned()
753 .collect();
754 link_keys.sort();
755
756 assert_eq!(link_keys, vec!["link-0", "link-2"]);
757 }
758 {
759 let mut joint_keys: Vec<String> = tree
760 .get_joints()
761 .try_read()
762 .unwrap()
763 .keys()
764 .cloned()
765 .collect();
766 joint_keys.sort();
767
768 assert_eq!(joint_keys, vec!["joint-2"]);
769 }
770 {
771 let mut material_keys: Vec<String> = tree
772 .get_materials()
773 .try_read()
774 .unwrap()
775 .keys()
776 .cloned()
777 .collect();
778 material_keys.sort();
779
780 assert_eq!(material_keys, vec!["Blue", "Red"]);
781 }
782
783 assert_eq!(tree.get_root_link().read().unwrap().name(), "link-0");
784 assert_eq!(tree.get_newest_link().read().unwrap().name(), "link-0");
785
786 assert_eq!(
787 yanked_branch.unwrap().0,
788 JointBuilder {
789 name: "joint-0".into(),
790 transform: Transform {
791 translation: Some((1., 0., 0.)),
792 ..Default::default()
793 }
794 .into(),
795 child: Some(LinkBuilder {
796 name: "link-1".into(),
797 visuals: vec![VisualBuilder::new_full(
798 None,
799 Some(Transform {
800 translation: Some((2., 0., 0.)),
801 ..Default::default()
802 }),
803 SphereGeometry::new(4.),
804 Some(MaterialDescriptor::new_color(0., 0., 1., 1.,).named("Blue"))
805 )],
806 colliders: vec![CollisionBuilder::new(SphereGeometry::new(4.))
807 .transformed(Transform::new_translation(2., 0., 0.))],
808 joints: vec![JointBuilder {
809 name: "joint-1-1".into(),
810 joint_type: JointType::Revolute,
811 transform: Transform {
812 translation: Some((4., 0., 0.)),
813 ..Default::default()
814 }
815 .into(),
816 child: Some(LinkBuilder {
817 name: "link-1-1".into(),
818 visuals: vec![VisualBuilder::new_full(
819 Some("link-1-1-vis".into()),
820 Some(Transform {
821 translation: Some((9., 0.5, 0.)),
822 ..Default::default()
823 }),
824 CylinderGeometry::new(0.5, 18.),
825 Some(MaterialDescriptor::new_color(0.5, 0.5, 0.5, 0.75))
826 )],
827 ..Default::default()
828 }),
829 axis: Some((0., 0., 1.)),
830 limit: Some(joint_data::LimitData {
831 effort: 100.,
832 velocity: 1000.,
833 lower: Some(-std::f32::consts::FRAC_PI_6),
834 upper: Some(std::f32::consts::FRAC_PI_6),
835 }),
836 ..Default::default()
837 }],
838 ..Default::default()
839 }),
840 ..Default::default()
841 }
842 )
843 }
844 }
845}