use nabled_core::scalar::NabledReal;
use ndarray::Array1;
use crate::error::KinematicsError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DhConvention {
Standard,
Modified,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum JointType {
Revolute,
Prismatic,
}
#[derive(Debug, Clone, PartialEq)]
pub struct JointLimits<T> {
pub lower: T,
pub upper: T,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ChainSpec<T> {
pub convention: DhConvention,
pub joint_types: Vec<JointType>,
pub a: Array1<T>,
pub alpha: Array1<T>,
pub d: Array1<T>,
pub theta_offset: Array1<T>,
pub ee_index: Option<usize>,
}
impl<T: NabledReal> ChainSpec<T> {
pub fn from_dh(
convention: DhConvention,
joint_types: Vec<JointType>,
a: Array1<T>,
alpha: Array1<T>,
d: Array1<T>,
theta_offset: Array1<T>,
) -> Result<Self, KinematicsError> {
let n = joint_types.len();
if a.len() != n || alpha.len() != n || d.len() != n || theta_offset.len() != n {
return Err(KinematicsError::DimensionMismatch);
}
if n == 0 {
return Err(KinematicsError::EmptyChain);
}
Ok(Self { convention, joint_types, a, alpha, d, theta_offset, ee_index: None })
}
#[must_use]
pub fn num_joints(&self) -> usize { self.joint_types.len() }
#[must_use]
pub fn ee_joint_index(&self) -> usize {
self.ee_index.unwrap_or_else(|| self.num_joints().saturating_sub(1))
}
pub fn validate(&self) -> Result<(), KinematicsError> {
if self.joint_types.is_empty() {
return Err(KinematicsError::EmptyChain);
}
let n = self.num_joints();
if self.a.len() != n
|| self.alpha.len() != n
|| self.d.len() != n
|| self.theta_offset.len() != n
{
return Err(KinematicsError::DimensionMismatch);
}
if let Some(ee) = self.ee_index
&& ee >= n
{
return Err(KinematicsError::InvalidInput(format!(
"ee_index {ee} out of range for {n} joints"
)));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use ndarray::arr1;
use super::*;
#[test]
fn from_dh_validates_lengths() {
let chain = ChainSpec::from_dh(
DhConvention::Standard,
vec![JointType::Revolute, JointType::Revolute],
arr1(&[1.0_f64, 1.0]),
arr1(&[0.0, 0.0]),
arr1(&[0.0, 0.0]),
arr1(&[0.0, 0.0]),
)
.unwrap();
assert_eq!(chain.num_joints(), 2);
assert_eq!(chain.ee_joint_index(), 1);
}
#[test]
fn ee_index_selects_intermediate_frame() {
let mut chain = ChainSpec::from_dh(
DhConvention::Standard,
vec![JointType::Revolute, JointType::Revolute],
arr1(&[1.0_f64, 1.0]),
arr1(&[0.0, 0.0]),
arr1(&[0.0, 0.0]),
arr1(&[0.0, 0.0]),
)
.unwrap();
chain.ee_index = Some(0);
assert_eq!(chain.ee_joint_index(), 0);
chain.validate().unwrap();
}
}