mod joint_tranform_mode;
mod jointbuilder;
#[cfg(not(feature = "smart-joint-extension"))]
mod smartjointbuilder;
#[cfg(feature = "smart-joint-extension")]
pub mod smartjointbuilder;
pub mod joint_data;
pub use joint_tranform_mode::JointTransformMode;
pub(crate) use jointbuilder::BuildJointChain;
pub use jointbuilder::{BuildJoint, JointBuilder};
pub use smartjointbuilder::SmartJointBuilder;
#[cfg(feature = "xml")]
use std::borrow::Cow;
use std::sync::{Arc, Weak};
#[cfg(feature = "urdf")]
use crate::to_rdf::to_urdf::ToURDF;
use crate::{
chained::Chained, cluster_objects::kinematic_data_tree::KinematicDataTree,
identifiers::GroupID, link::Link, transform::Transform, ArcLock, WeakLock,
};
#[cfg(any(feature = "xml"))]
use quick_xml::{events::attributes::Attribute, name::QName};
#[derive(Debug)]
pub struct Joint {
pub(crate) name: String,
pub(crate) tree: Weak<KinematicDataTree>,
pub(crate) parent_link: WeakLock<Link>,
pub(crate) child_link: ArcLock<Link>, pub(crate) joint_type: JointType,
origin: Transform,
axis: Option<(f32, f32, f32)>,
calibration: joint_data::CalibrationData,
dynamics: joint_data::DynamicsData,
limit: Option<joint_data::LimitData>,
mimic: Option<joint_data::MimicData>,
safety_controller: Option<joint_data::SafetyControllerData>,
me: WeakLock<Joint>,
}
impl Joint {
pub fn name(&self) -> &String {
&self.name
}
pub fn joint_type(&self) -> JointType {
self.joint_type
}
pub fn parent_link(&self) -> ArcLock<Link> {
self.parent_link
.upgrade()
.expect("Joint's parent link should be set")
}
pub fn child_link(&self) -> ArcLock<Link> {
Arc::clone(&self.child_link)
}
pub(crate) fn child_link_ref(&self) -> &ArcLock<Link> {
&self.child_link
}
pub fn origin(&self) -> &Transform {
&self.origin
}
pub fn axis(&self) -> Option<(f32, f32, f32)> {
self.axis
}
pub fn rebuild(&self) -> JointBuilder {
#[cfg(any(feature = "logging", test))]
log::info!(target: "JointBuilder","Rebuilding: {}", self.name());
JointBuilder {
name: self.name.clone(),
joint_type: self.joint_type,
origin: self.origin.into(),
axis: self.axis,
calibration: self.calibration,
dynamics: self.dynamics,
limit: self.limit,
mimic: self.mimic.clone().map(|mimic| mimic.into()),
safety_controller: self.safety_controller,
..Default::default()
}
}
pub(crate) fn rebuild_branch_continued(&self) -> JointBuilder {
#[cfg(any(feature = "logging", test))]
log::info!(target: "JointBuilder","Rebuilding: {}", self.name());
JointBuilder {
child: Some(self.child_link.read().unwrap().rebuild_branch_continued()), ..self.rebuild()
}
}
pub fn rebuild_branch(&self) -> Chained<JointBuilder> {
#[cfg(any(feature = "logging", test))]
log::info!(target: "JointBuilder","Starting Branch Rebuilding: {}", self.name());
Chained(self.rebuild_branch_continued())
}
pub(crate) fn yank(&self) -> JointBuilder {
let builder = self.rebuild_branch_continued();
#[cfg(any(feature = "logging", test))]
log::info!("Yanked Joint \"{}\"", self.name());
self.parent_link()
.write()
.unwrap() .joints_mut()
.retain(|joint| !Arc::ptr_eq(&self.get_self(), joint));
*self.tree.upgrade().unwrap().newest_link.write().unwrap() = Weak::clone(&self.parent_link);
builder
}
pub fn get_self(&self) -> ArcLock<Joint> {
Weak::upgrade(&self.me).unwrap()
}
pub fn get_weak_self(&self) -> WeakLock<Joint> {
Weak::clone(&self.me)
}
}
#[cfg(feature = "urdf")]
impl ToURDF for Joint {
fn to_urdf(
&self,
writer: &mut quick_xml::Writer<std::io::Cursor<Vec<u8>>>,
urdf_config: &crate::to_rdf::to_urdf::URDFConfig,
) -> Result<(), quick_xml::Error> {
let element = writer
.create_element("joint")
.with_attribute(Attribute {
key: QName(b"name"),
value: self.name().display().as_bytes().into(),
})
.with_attribute(Attribute {
key: QName(b"type"),
value: self.joint_type().into(),
});
element.write_inner_content(|writer| {
let origin = self.origin();
if origin.contains_some() {
origin.to_urdf(writer, urdf_config)?;
}
writer
.create_element("parent")
.with_attribute(Attribute {
key: QName(b"link"),
value: self
.parent_link()
.read()
.unwrap() .name()
.display()
.as_bytes()
.into(),
})
.write_empty()?;
writer
.create_element("child")
.with_attribute(Attribute {
key: QName(b"link"),
value: self
.child_link()
.read()
.unwrap() .name()
.display()
.as_bytes()
.into(),
})
.write_empty()?;
if let Some((x, y, z)) = &self.axis {
writer
.create_element("axis")
.with_attribute(Attribute {
key: QName(b"xyz"),
value: format!("{} {} {}", x, y, z).as_bytes().into(),
})
.write_empty()?;
}
self.calibration.to_urdf(writer, urdf_config)?;
self.dynamics.to_urdf(writer, urdf_config)?;
if let Some(limit) = &self.limit {
limit.to_urdf(writer, urdf_config)?;
}
if let Some(mimic) = &self.mimic {
mimic.to_urdf(writer, urdf_config)?;
}
if let Some(safety_contoller) = &self.safety_controller {
safety_contoller.to_urdf(writer, urdf_config)?;
}
Ok(())
})?;
self.child_link()
.read()
.unwrap() .to_urdf(writer, urdf_config)?;
Ok(())
}
}
impl PartialEq for Joint {
fn eq(&self, other: &Self) -> bool {
Weak::ptr_eq(&self.me, &other.me)
&& self.name == other.name
&& Weak::ptr_eq(&self.tree, &other.tree)
&& Weak::ptr_eq(&self.parent_link, &other.parent_link)
&& Arc::ptr_eq(&self.child_link, &other.child_link)
&& self.joint_type == other.joint_type
&& self.origin == other.origin
&& self.axis() == other.axis()
&& self.calibration == other.calibration
&& self.dynamics == other.dynamics
&& self.limit == other.limit
&& self.mimic == other.mimic
&& self.safety_controller == other.safety_controller
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
pub enum JointType {
#[default]
Fixed, Revolute, Continuous, Prismatic, Floating, Planar, }
impl ToString for JointType {
fn to_string(&self) -> String {
match self {
JointType::Fixed => String::from("fixed"),
JointType::Revolute => String::from("revolute"),
JointType::Continuous => String::from("continuous"),
JointType::Prismatic => String::from("prismatic"),
JointType::Floating => String::from("floating"),
JointType::Planar => String::from("planar"),
}
}
}
#[cfg(any(feature = "xml"))]
impl From<JointType> for Cow<'_, [u8]> {
fn from(value: JointType) -> Self {
value.to_string().into_bytes().into()
}
}
#[cfg(test)]
mod tests {
use crate::{
cluster_objects::KinematicInterface,
joint::{joint_data, smartjointbuilder::SmartJointBuilder, JointBuilder, JointType},
link::{
builder::LinkBuilder,
link_data::{
geometry::{BoxGeometry, CylinderGeometry, SphereGeometry},
Visual,
},
},
linkbuilding::{CollisionBuilder, VisualBuilder},
material::MaterialDescriptor,
transform::Transform,
};
use test_log::test;
#[test]
fn rebuild() {
let tree = LinkBuilder::new("root").build_tree();
tree.get_newest_link()
.try_write()
.unwrap()
.try_attach_child(
LinkBuilder::new("child"),
SmartJointBuilder::new("Joint1")
.fixed()
.add_transform(Transform::new_translation(2.0, 3.0, 5.0)),
)
.unwrap();
let rebuilder = tree
.get_joint("Joint1")
.unwrap()
.try_read()
.unwrap()
.rebuild();
assert_eq!(
rebuilder,
JointBuilder::new("Joint1", crate::JointType::Fixed).add_origin_offset((2.0, 3.0, 5.0))
)
}
#[test]
fn yank_simple() {
let material_red = MaterialDescriptor::new_color(1., 0., 0., 1.).named("Red");
let tree = LinkBuilder::new("link-0")
.add_collider(CollisionBuilder::new(BoxGeometry::new(1.0, 2.0, 3.0)))
.add_visual(
Visual::builder(BoxGeometry::new(1.0, 2.0, 3.0)).materialized(material_red.clone()),
)
.build_tree();
tree.get_root_link()
.try_write()
.unwrap()
.try_attach_child(
LinkBuilder::new("link-1")
.add_collider(
CollisionBuilder::new(SphereGeometry::new(4.))
.tranformed(Transform::new_translation(2., 0., 0.)),
)
.add_visual(
Visual::builder(SphereGeometry::new(4.))
.tranformed(Transform::new_translation(2., 0., 0.))
.materialized(material_red.clone()),
),
SmartJointBuilder::new("joint-0")
.add_transform(Transform::new_translation(1.0, 0., 0.))
.fixed(),
)
.unwrap();
assert_eq!(tree.get_links().try_read().unwrap().len(), 2);
assert_eq!(tree.get_joints().try_read().unwrap().len(), 1);
assert_eq!(tree.get_materials().try_read().unwrap().len(), 1);
let builder = tree.yank_joint("joint-0").unwrap();
assert_eq!(tree.get_links().try_read().unwrap().len(), 1);
assert_eq!(tree.get_joints().try_read().unwrap().len(), 0);
assert_eq!(
builder.0,
JointBuilder {
name: "joint-0".into(),
joint_type: JointType::Fixed,
origin: Transform {
translation: Some((1., 0., 0.)),
rotation: None
}
.into(),
child: Some(LinkBuilder {
name: "link-1".into(),
visual_builders: vec![VisualBuilder::new_full(
None,
Some(Transform {
translation: Some((2., 0., 0.)),
rotation: None
}),
SphereGeometry::new(4.),
Some(material_red.clone())
)],
colliders: vec![CollisionBuilder::new_full(
None,
Some(Transform {
translation: Some((2., 0., 0.)),
rotation: None
}),
SphereGeometry::new(4.)
)],
..Default::default()
}),
..Default::default() }
);
}
#[test]
fn yank_less_simple() {
let tree = {
let material_red = MaterialDescriptor::new_color(1., 0., 0., 1.).named("Red");
LinkBuilder::new("link-0")
.add_collider(CollisionBuilder::new(BoxGeometry::new(1.0, 2.0, 3.0)))
.add_visual(
Visual::builder(BoxGeometry::new(1.0, 2.0, 3.0))
.materialized(material_red.clone()),
)
.build_tree()
};
tree.get_root_link()
.try_write()
.unwrap()
.try_attach_child(
{
let tmp_tree = LinkBuilder::new("link-1")
.add_collider(
CollisionBuilder::new(SphereGeometry::new(4.))
.tranformed(Transform::new_translation(2., 0., 0.)),
)
.add_visual(
Visual::builder(SphereGeometry::new(4.))
.tranformed(Transform::new_translation(2., 0., 0.))
.materialized(
MaterialDescriptor::new_color(0., 0., 1., 1.).named("Blue"),
),
)
.build_tree();
tmp_tree
.get_root_link()
.write()
.unwrap()
.try_attach_child(
LinkBuilder::new("link-1-1").add_visual(
Visual::builder(CylinderGeometry::new(0.5, 18.))
.named("link-1-1-vis")
.tranformed(Transform::new_translation(9., 0.5, 0.))
.materialized(MaterialDescriptor::new_color(
0.5, 0.5, 0.5, 0.75,
)),
),
SmartJointBuilder::new("joint-1-1")
.revolute()
.add_transform(Transform::new_translation(4., 0., 0.))
.with_axis((0., 0., 1.))
.with_limit(100., 1000.)
.set_upper_limit(std::f32::consts::FRAC_PI_6)
.set_lower_limit(-std::f32::consts::FRAC_PI_6),
)
.unwrap();
tmp_tree
},
SmartJointBuilder::new("joint-0")
.add_transform(Transform::new_translation(1.0, 0., 0.))
.fixed(),
)
.unwrap();
tree.get_root_link()
.write()
.unwrap()
.try_attach_child(
LinkBuilder::new("link-2").build_tree(),
JointBuilder::new("joint-2", JointType::Fixed).add_origin_offset((0., 0., 1.5)),
)
.unwrap();
assert_eq!(tree.get_links().try_read().unwrap().len(), 4);
assert_eq!(tree.get_joints().try_read().unwrap().len(), 3);
assert_eq!(tree.get_materials().try_read().unwrap().len(), 2);
assert_eq!(tree.get_root_link().try_read().unwrap().name(), "link-0");
assert_eq!(tree.get_newest_link().try_read().unwrap().name(), "link-2");
{
let tree = tree.clone();
let yanked_branch = tree.yank_joint("joint-2");
assert!(yanked_branch.is_some());
assert_eq!(tree.get_links().try_read().unwrap().len(), 3);
assert_eq!(tree.get_joints().try_read().unwrap().len(), 2);
assert_eq!(tree.get_materials().try_read().unwrap().len(), 2);
{
let mut link_keys: Vec<String> = tree
.get_links()
.try_read()
.unwrap()
.keys()
.cloned()
.collect();
link_keys.sort();
assert_eq!(link_keys, vec!["link-0", "link-1", "link-1-1"]);
}
{
let mut joint_keys: Vec<String> = tree
.get_joints()
.try_read()
.unwrap()
.keys()
.cloned()
.collect();
joint_keys.sort();
assert_eq!(joint_keys, vec!["joint-0", "joint-1-1"]);
}
{
let mut material_keys: Vec<String> = tree
.get_materials()
.try_read()
.unwrap()
.keys()
.cloned()
.collect();
material_keys.sort();
assert_eq!(material_keys, vec!["Blue", "Red"]);
}
assert_eq!(tree.get_root_link().read().unwrap().name(), "link-0");
assert_eq!(tree.get_newest_link().read().unwrap().name(), "link-0");
assert_eq!(
yanked_branch.unwrap().0,
JointBuilder {
name: "joint-2".into(),
joint_type: JointType::Fixed,
origin: Transform {
translation: Some((0., 0., 1.5)),
..Default::default()
}
.into(),
child: Some(LinkBuilder::new("link-2")),
..Default::default()
}
)
}
{
let tree = tree.clone();
let yanked_branch = tree.yank_joint("joint-1-1");
assert!(yanked_branch.is_some());
assert_eq!(tree.get_links().try_read().unwrap().len(), 3);
assert_eq!(tree.get_joints().try_read().unwrap().len(), 2);
assert_eq!(tree.get_materials().try_read().unwrap().len(), 2);
{
let mut link_keys: Vec<String> = tree
.get_links()
.try_read()
.unwrap()
.keys()
.cloned()
.collect();
link_keys.sort();
assert_eq!(link_keys, vec!["link-0", "link-1", "link-2"]);
}
{
let mut joint_keys: Vec<String> = tree
.get_joints()
.try_read()
.unwrap()
.keys()
.cloned()
.collect();
joint_keys.sort();
assert_eq!(joint_keys, vec!["joint-0", "joint-2"]);
}
{
let mut material_keys: Vec<String> = tree
.get_materials()
.try_read()
.unwrap()
.keys()
.cloned()
.collect();
material_keys.sort();
assert_eq!(material_keys, vec!["Blue", "Red"]);
}
assert_eq!(tree.get_root_link().read().unwrap().name(), "link-0");
assert_eq!(tree.get_newest_link().read().unwrap().name(), "link-1");
assert_eq!(
yanked_branch.unwrap().0,
JointBuilder {
name: "joint-1-1".into(),
joint_type: JointType::Revolute,
origin: Transform {
translation: Some((4., 0., 0.)),
..Default::default()
}
.into(),
child: Some(LinkBuilder {
name: "link-1-1".into(),
visual_builders: vec![VisualBuilder::new_full(
Some("link-1-1-vis".into()),
Some(Transform {
translation: Some((9., 0.5, 0.)),
..Default::default()
}),
CylinderGeometry::new(0.5, 18.),
Some(MaterialDescriptor::new_color(0.5, 0.5, 0.5, 0.75))
)],
..Default::default()
}),
axis: Some((0., 0., 1.)),
limit: Some(joint_data::LimitData {
effort: 100.,
velocity: 1000.,
lower: Some(-std::f32::consts::FRAC_PI_6),
upper: Some(std::f32::consts::FRAC_PI_6),
}),
..Default::default()
}
)
}
{
let tree = tree.clone();
let yanked_branch = tree.yank_joint("joint-0");
assert!(yanked_branch.is_some());
assert_eq!(tree.get_links().try_read().unwrap().len(), 2);
assert_eq!(tree.get_joints().try_read().unwrap().len(), 1);
assert_eq!(tree.get_materials().try_read().unwrap().len(), 2);
{
let mut link_keys: Vec<String> = tree
.get_links()
.try_read()
.unwrap()
.keys()
.cloned()
.collect();
link_keys.sort();
assert_eq!(link_keys, vec!["link-0", "link-2"]);
}
{
let mut joint_keys: Vec<String> = tree
.get_joints()
.try_read()
.unwrap()
.keys()
.cloned()
.collect();
joint_keys.sort();
assert_eq!(joint_keys, vec!["joint-2"]);
}
{
let mut material_keys: Vec<String> = tree
.get_materials()
.try_read()
.unwrap()
.keys()
.cloned()
.collect();
material_keys.sort();
assert_eq!(material_keys, vec!["Blue", "Red"]);
}
assert_eq!(tree.get_root_link().read().unwrap().name(), "link-0");
assert_eq!(tree.get_newest_link().read().unwrap().name(), "link-0");
assert_eq!(
yanked_branch.unwrap().0,
JointBuilder {
name: "joint-0".into(),
origin: Transform {
translation: Some((1., 0., 0.)),
..Default::default()
}
.into(),
child: Some(LinkBuilder {
name: "link-1".into(),
visual_builders: vec![VisualBuilder::new_full(
None,
Some(Transform {
translation: Some((2., 0., 0.)),
..Default::default()
}),
SphereGeometry::new(4.),
Some(MaterialDescriptor::new_color(0., 0., 1., 1.,).named("Blue"))
)],
colliders: vec![CollisionBuilder::new(SphereGeometry::new(4.))
.tranformed(Transform::new_translation(2., 0., 0.))],
joints: vec![JointBuilder {
name: "joint-1-1".into(),
joint_type: JointType::Revolute,
origin: Transform {
translation: Some((4., 0., 0.)),
..Default::default()
}
.into(),
child: Some(LinkBuilder {
name: "link-1-1".into(),
visual_builders: vec![VisualBuilder::new_full(
Some("link-1-1-vis".into()),
Some(Transform {
translation: Some((9., 0.5, 0.)),
..Default::default()
}),
CylinderGeometry::new(0.5, 18.),
Some(MaterialDescriptor::new_color(0.5, 0.5, 0.5, 0.75))
)],
..Default::default()
}),
axis: Some((0., 0., 1.)),
limit: Some(joint_data::LimitData {
effort: 100.,
velocity: 1000.,
lower: Some(-std::f32::consts::FRAC_PI_6),
upper: Some(std::f32::consts::FRAC_PI_6),
}),
..Default::default()
}],
..Default::default()
}),
..Default::default()
}
)
}
}
}