Source code for seqikpy.kinematic_chain

""" Module that contains a set of kinematic chains."""

from abc import ABC, abstractmethod
from collections import namedtuple
from typing import Dict, List
import warnings

import numpy as np
from ikpy.chain import Chain
from ikpy.link import OriginLink, URDFLink

from seqikpy.data import NMF_TEMPLATE
from seqikpy.utils import calculate_body_size

# Ignore the warnings
warnings.filterwarnings("ignore")

# Axes as a named tuple to ensure immutability
AxesTuple = namedtuple("AxesTuple", "X_AXIS Y_AXIS Z_AXIS")
Axes = AxesTuple(X_AXIS=[1, 0, 0], Y_AXIS=[0, 1, 0], Z_AXIS=[0, 0, 1])


[docs] class KinematicChainBase(ABC): """Abstract class to create kinematic chains for the legs. Parameters ---------- bounds_dof : Dict[str, np.ndarray] Dictionary that contains the bounds of joint degrees-of-freedom. legs_list : List[str] List of legs for which the kinematic chains are created. body_size : Dict[str, float], optional Dictionary that contains the size of different body parts, by default None """ def __init__( self, bounds_dof: Dict[str, np.ndarray], legs_list: List[str], body_size: Dict[str, float] = None, ) -> None: # NMF size is calculated internally if size is not provided self.body_size = ( calculate_body_size(NMF_TEMPLATE, legs_list) if body_size is None else body_size ) self.bounds_dof = bounds_dof def __call__(self): print("Base kinematic chain is called.")
[docs] @abstractmethod def create_leg_chain(self, leg_name: str, **kwargs) -> Chain: """Returns the respective leg chain based on the stage. Parameters ---------- leg_name : str Name of the leg, RF or LF Returns ------- KinematicChain of a given leg """ raise NotImplementedError
[docs] class KinematicChainSeq(KinematicChainBase): """Sequential kinematic chain class to create a kinematic chain stage-by-stage. Parameters ---------- bounds_dof : Dict[str, np.ndarray] Dictionary that contains the bounds of joint degrees-of-freedom. legs_list : List[str] List of legs for which the kinematic chains are created. body_size : Dict[str, float], optional Dictionary that contains the size of different body parts, by default None NOTE ---- This implementation follows Yaw-Pitch-Roll order. """ def __call__(self): print("Sequential kinematic chain is called.")
[docs] def create_leg_chain(self, leg_name: str, **kwargs) -> Chain: """Returns the respective leg chain based on the stage. Parameters ---------- leg_name : str Name of the leg, i.e., RF, LF, etc. angles (kwargs) : Dict[str, np.ndarray] Joint angles calculated in the previous step, None at the first step, by default None stage (kwargs) : int Stage number, (1,2,3,4) t (kwargs) : int Time step, by default 0 Returns ------- KinematicChain at the respective stage and time step. Raises ------ ValueError If the stage number is not in (1,2,3,4) ValueError If the leg name is not in ["RF", "LF", "RM", "LM", "RH", "LH"] """ angles = kwargs.get("angles", None) stage = kwargs.get("stage", 1) t = kwargs.get("t", 0) if not leg_name in ["RF", "LF", "RM", "LM", "RH", "LH"]: raise ValueError(f"Unknown leg name ({leg_name}) is provided!") if not 1 <= stage <= 4: raise ValueError(f"Unknown stage number ({stage}) number is provided!") if stage == 1: kinematic_chain = self.create_leg_chain_stage_1(leg_name) elif stage == 2: kinematic_chain = self.create_leg_chain_stage_2( leg_name, angles=angles, t=t ) elif stage == 3: kinematic_chain = self.create_leg_chain_stage_3( leg_name, angles=angles, t=t ) elif stage == 4: kinematic_chain = self.create_leg_chain_stage_4( leg_name, angles=angles, t=t ) return kinematic_chain
[docs] def create_leg_chain_stage_1( self, leg_name: str, ) -> Chain: """Leg chain to calculate thorax/coxa pitch and yaw. Parameters ---------- leg_name : str Leg name, e.g., RF, LF, RM, LM, RH, LH Returns ------- Chain Chain of the leg at stage 1, which calculates thorax/coxa pitch and yaw joint angles and only contains Coxa leg segment. """ kinematic_chain = [ OriginLink(), URDFLink( name=f"{leg_name}_ThC_yaw", origin_translation=[0, 0, 0], origin_orientation=[0, 0, 0], rotation=Axes.X_AXIS, joint_type="revolute", bounds=self.bounds_dof[f"{leg_name}_ThC_yaw"], ), URDFLink( name=f"{leg_name}_ThC_pitch", origin_translation=[0, 0, 0], origin_orientation=[0, 0, 0], rotation=Axes.Y_AXIS, joint_type="revolute", bounds=self.bounds_dof[f"{leg_name}_ThC_pitch"], ), URDFLink( name=f"{leg_name}_CTr_pitch", origin_translation=[0, 0, -self.body_size[f"{leg_name}_Coxa"]], origin_orientation=[0, 0, 0], rotation=Axes.Y_AXIS, joint_type="revolute", bounds=self.bounds_dof[f"{leg_name}_CTr_pitch"], ), ] return Chain(name="chain_stage_1", links=kinematic_chain)
[docs] def create_leg_chain_stage_2( self, leg_name: str, angles: Dict[str, np.ndarray], t: int ) -> Chain: """Leg chain to calculate thorax/coxa roll and coxa/femur pitch. Parameters ---------- leg_name : str Leg name, e.g., RF, LF, RM, LM, RH, LH Returns ------- Chain Chain of the leg at stage 2, which calculates thorax/coxa roll and coxa/femur pitch joint angles and contains Coxa and Femur leg segments. """ kinematic_chain = [ OriginLink(), URDFLink( name=f"{leg_name}_ThC_yaw", origin_translation=[0, 0, 0], origin_orientation=[ angles[f"Angle_{leg_name}_ThC_yaw"][t], 0, 0, ], rotation=None, joint_type="fixed", bounds=self.bounds_dof[f"{leg_name}_ThC_yaw"], ), URDFLink( name=f"{leg_name}_ThC_pitch", origin_translation=[0, 0, 0], origin_orientation=[ 0, angles[f"Angle_{leg_name}_ThC_pitch"][t], 0, ], rotation=None, joint_type="fixed", bounds=self.bounds_dof[f"{leg_name}_ThC_pitch"], ), URDFLink( name=f"{leg_name}_ThC_roll", origin_translation=[0, 0, 0], origin_orientation=[0, 0, 0], rotation=Axes.Z_AXIS, joint_type="revolute", bounds=self.bounds_dof[f"{leg_name}_ThC_roll"], ), URDFLink( name=f"{leg_name}_CTr_pitch", origin_translation=[0, 0, -self.body_size[f"{leg_name}_Coxa"]], origin_orientation=[0, 0, 0], rotation=Axes.Y_AXIS, joint_type="revolute", bounds=self.bounds_dof[f"{leg_name}_CTr_pitch"], ), URDFLink( name=f"{leg_name}_FTi_pitch", origin_translation=[0, 0, -self.body_size[f"{leg_name}_Femur"]], origin_orientation=[0, 0, 0], rotation=Axes.Y_AXIS, joint_type="revolute", bounds=self.bounds_dof[f"{leg_name}_FTi_pitch"], ), ] return Chain(name="chain_stage_2", links=kinematic_chain)
[docs] def create_leg_chain_stage_3( self, leg_name: str, angles: Dict[str, np.ndarray], t: int ) -> Chain: """Leg chain to calculate coxa/femur roll and femur/tibia pitch. Parameters ---------- leg_name : str Leg name, e.g., RF, LF, RM, LM, RH, LH Returns ------- Chain Chain of the leg at stage 3, which calculates coxa/femur roll and femur/tibia pitch joint angles and contains Coxa, Femur and Tibia leg segments. """ kinematic_chain = [ OriginLink(), URDFLink( name=f"{leg_name}_ThC_yaw", origin_translation=[0, 0, 0], origin_orientation=[ angles[f"Angle_{leg_name}_ThC_yaw"][t], 0, 0, ], rotation=None, joint_type="fixed", bounds=self.bounds_dof[f"{leg_name}_ThC_yaw"], ), URDFLink( name=f"{leg_name}_ThC_pitch", origin_translation=[0, 0, 0], origin_orientation=[ 0, angles[f"Angle_{leg_name}_ThC_pitch"][t], 0, ], rotation=None, joint_type="fixed", bounds=self.bounds_dof[f"{leg_name}_ThC_pitch"], ), URDFLink( name=f"{leg_name}_ThC_roll", origin_translation=[0, 0, 0], origin_orientation=[ 0, 0, angles[f"Angle_{leg_name}_ThC_roll"][t], ], rotation=None, joint_type="fixed", bounds=self.bounds_dof[f"{leg_name}_ThC_roll"], ), URDFLink( name=f"{leg_name}_CTr_pitch", origin_translation=[0, 0, -self.body_size[f"{leg_name}_Coxa"]], origin_orientation=[ 0, angles[f"Angle_{leg_name}_CTr_pitch"][t], 0, ], rotation=None, joint_type="fixed", bounds=self.bounds_dof[f"{leg_name}_CTr_pitch"], ), URDFLink( name=f"{leg_name}_CTr_roll", origin_translation=[0, 0, 0], origin_orientation=[0, 0, 0], rotation=Axes.Z_AXIS, joint_type="revolute", bounds=self.bounds_dof[f"{leg_name}_CTr_roll"], ), URDFLink( name=f"{leg_name}_FTi_pitch", origin_translation=[0, 0, -self.body_size[f"{leg_name}_Femur"]], origin_orientation=[0, 0, 0], rotation=Axes.Y_AXIS, joint_type="revolute", bounds=self.bounds_dof[f"{leg_name}_FTi_pitch"], ), URDFLink( name=f"{leg_name}_TiTa_pitch", origin_translation=[0, 0, -self.body_size[f"{leg_name}_Tibia"]], origin_orientation=[0, 0, 0], rotation=Axes.Y_AXIS, joint_type="revolute", bounds=self.bounds_dof[f"{leg_name}_TiTa_pitch"], ), ] return Chain(name="chain_stage_3", links=kinematic_chain)
[docs] def create_leg_chain_stage_4( self, leg_name: str, angles: Dict[str, np.ndarray], t: int ) -> Chain: """Leg chain to calculate tibia/tarsus pitch. Parameters ---------- leg_name : str Leg name, e.g., RF, LF, RM, LM, RH, LH Returns ------- Chain Chain of the leg at stage 4, which calculates coxa/femur roll and tibia/tarsus pitch joint angles and contains the entire leg, i.e., Coxa, Femur, Tibia and Tarsus. """ kinematic_chain = [ OriginLink(), URDFLink( name=f"{leg_name}_ThC_yaw", origin_translation=[0, 0, 0], origin_orientation=[ angles[f"Angle_{leg_name}_ThC_yaw"][t], 0, 0, ], rotation=None, joint_type="fixed", bounds=self.bounds_dof[f"{leg_name}_ThC_yaw"], ), URDFLink( name=f"{leg_name}_ThC_pitch", origin_translation=[0, 0, 0], origin_orientation=[ 0, angles[f"Angle_{leg_name}_ThC_pitch"][t], 0, ], rotation=None, joint_type="fixed", bounds=self.bounds_dof[f"{leg_name}_ThC_pitch"], ), URDFLink( name=f"{leg_name}_ThC_roll", origin_translation=[0, 0, 0], origin_orientation=[ 0, 0, angles[f"Angle_{leg_name}_ThC_roll"][t], ], rotation=None, joint_type="fixed", bounds=self.bounds_dof[f"{leg_name}_ThC_roll"], ), URDFLink( name=f"{leg_name}_CTr_pitch", origin_translation=[0, 0, -self.body_size[f"{leg_name}_Coxa"]], origin_orientation=[ 0, angles[f"Angle_{leg_name}_CTr_pitch"][t], 0, ], rotation=None, joint_type="fixed", bounds=self.bounds_dof[f"{leg_name}_CTr_pitch"], ), URDFLink( name=f"{leg_name}_CTr_roll", origin_translation=[0, 0, 0], origin_orientation=[ 0, 0, angles[f"Angle_{leg_name}_CTr_roll"][t], ], rotation=None, joint_type="fixed", bounds=self.bounds_dof[f"{leg_name}_CTr_roll"], ), URDFLink( name=f"{leg_name}_FTi_pitch", origin_translation=[0, 0, -self.body_size[f"{leg_name}_Femur"]], origin_orientation=[ 0, angles[f"Angle_{leg_name}_FTi_pitch"][t], 0, ], rotation=None, joint_type="fixed", bounds=self.bounds_dof[f"{leg_name}_FTi_pitch"], ), URDFLink( name=f"{leg_name}_TiTa_pitch", origin_translation=[0, 0, -self.body_size[f"{leg_name}_Tibia"]], origin_orientation=[0, 0, 0], rotation=Axes.Y_AXIS, joint_type="revolute", bounds=self.bounds_dof[f"{leg_name}_TiTa_pitch"], ), URDFLink( name=f"{leg_name}_Claw", origin_translation=[ 0, 0, -self.body_size[f"{leg_name}_Tarsus"], ], origin_orientation=[0, 0, 0], rotation=[0, 0, 0], joint_type="revolute", bounds=[-np.pi, np.pi], ), ] return Chain(name="chain_stage_4", links=kinematic_chain)
[docs] class KinematicChainGeneric(KinematicChainBase): """Generic kinematic chain class to create one kinematic chain for the entire leg. Parameters ---------- bounds_dof : Dict[str, np.ndarray] Dictionary that contains the bounds of joint degrees-of-freedom. legs_list : List[str] List of legs for which the kinematic chains are created. body_size : Dict[str, float], optional Dictionary that contains the size of different body parts, by default None """ def __call__(self): print("Generic kinematic chain is called.")
[docs] def create_leg_chain(self, leg_name: str, **kwargs) -> Chain: """Returns the respective leg chain based on the stage. Parameters ---------- leg_name : str Name of the leg, RF or LF angles : Dict[str, np.ndarray], optional Joint angles calculated in the previous step, None at the first step, by default None Returns ------- The entire KinematicChain of a given leg """ if not leg_name in ["RF", "LF", "RM", "LM", "RH", "LH"]: raise ValueError(f"Unknown leg name ({leg_name}) is provided!") kinematic_chain = [ OriginLink(), URDFLink( name=f"{leg_name}_ThC_roll", origin_translation=[0, 0, 0], origin_orientation=[0, 0, 0], rotation=Axes.Z_AXIS, joint_type="revolute", bounds=self.bounds_dof[f"{leg_name}_ThC_roll"], ), URDFLink( name=f"{leg_name}_ThC_yaw", origin_translation=[0, 0, 0], origin_orientation=[0, 0, 0], rotation=Axes.X_AXIS, joint_type="revolute", bounds=self.bounds_dof[f"{leg_name}_ThC_yaw"], ), URDFLink( name=f"{leg_name}_ThC_pitch", origin_translation=[0, 0, 0], origin_orientation=[0, 0, 0], rotation=Axes.Y_AXIS, joint_type="revolute", bounds=self.bounds_dof[f"{leg_name}_ThC_pitch"], ), URDFLink( name=f"{leg_name}_CTr_pitch", origin_translation=[0, 0, -self.body_size[f"{leg_name}_Coxa"]], origin_orientation=[0, 0, 0], rotation=Axes.Y_AXIS, joint_type="revolute", bounds=self.bounds_dof[f"{leg_name}_CTr_pitch"], ), URDFLink( name=f"{leg_name}_CTr_roll", origin_translation=[0, 0, 0], origin_orientation=[0, 0, 0], rotation=Axes.Z_AXIS, joint_type="revolute", bounds=self.bounds_dof[f"{leg_name}_CTr_roll"], ), URDFLink( name=f"{leg_name}_FTi_pitch", origin_translation=[0, 0, -self.body_size[f"{leg_name}_Femur"]], origin_orientation=[0, 0, 0], rotation=Axes.Y_AXIS, joint_type="revolute", bounds=self.bounds_dof[f"{leg_name}_FTi_pitch"], ), URDFLink( name=f"{leg_name}_TiTa_pitch", origin_translation=[0, 0, -self.body_size[f"{leg_name}_Tibia"]], origin_orientation=[0, 0, 0], rotation=Axes.Y_AXIS, joint_type="revolute", bounds=self.bounds_dof[f"{leg_name}_TiTa_pitch"], ), URDFLink( name=f"{leg_name}_Claw", origin_translation=[ 0, 0, -self.body_size[f"{leg_name}_Tarsus"], ], origin_orientation=[0, 0, 0], rotation=[0, 0, 0], joint_type="revolute", bounds=[-np.pi, np.pi], ), ] return Chain(name="chain", links=kinematic_chain)