[docs]classSingleCPUArticulation(Articulation):"""Use Pinocchio as the backend for single articulation."""def__init__(self,spec:Union[ArticulationSpec,Sequence[ArticulationSpec]],device:Device="cpu"):ifisinstance(spec,Sequence):iflen(spec)!=1:raiseValueError(f"Expected a single articulation spec, got {len(spec)}")spec=spec[0]ifdevice!="cpu":raiseValueError(f"Only CPU is supported for SingleCPUArticulation, got {device}")super().__init__([spec],device=device)# ------------------------------ pinocchio ------------------------------ifself.first_spec.format!="urdf":raiseValueError(f"Only URDF is supported for single articulation, got {self.first_spec.format}")
ifself.pin_model.nq!=self.first_spec.num_full_joints:raiseValueError(f"Number of degrees of freedom mismatch: pinocchio model has {self.pin_model.nq} DOFs, but spec has {self.first_spec.num_full_joints} joints.")self._ours_to_pin_joint_indices=[self.first_spec.full_joint_names.index(name)fornameinself.pin_model.names[1:]# skip `universe`]
[docs]defforward_kinematics_pinocchio(self,joint_values:Float[np.ndarray,"num_dofs"],# noqa: F821joint_names:Optional[List[str]]=None,root_poses:Optional[Float[np.ndarray,"4 4"]]=None,clamp_joint_values:bool=True,)->Float[np.ndarray,"num_links 4 4"]:"""Forward kinematics using Pinocchio. Args: joint_values (np.ndarray): Active joint values of shape (num_dofs,). joint_names (List[str], optional): Names corresponding to the joint_values. If provided, values will be reordered to match the internal active joint order. root_poses (np.ndarray, optional): Root pose transformation of shape (4, 4). Defaults to identity. clamp_joint_values (bool, optional): Whether to clamp joint values to joint limits. Defaults to True. Returns: np.ndarray: Link poses in the world frame of shape (num_links, 4, 4). """ifjoint_values.shape!=(self.first_spec.num_dofs,):raiseValueError(f"Expected joint_values shape ({self.first_spec.num_dofs},), got {joint_values.shape}")active_joint_values=joint_values.copy()ifjoint_namesisnotNone:iflen(joint_names)!=self.first_spec.num_dofs:raiseValueError(f"Length mismatch: joint_names ({len(joint_names)}) vs num_dofs ({self.first_spec.num_dofs})")ifjoint_names!=self.first_spec.active_joint_names:reorder_indices=[joint_names.index(name)fornameinself.first_spec.active_joint_names]active_joint_values=active_joint_values[reorder_indices]ifclamp_joint_valuesandself.first_spec.joint_limitsisnotNone:limits=self.first_spec.joint_limitsactive_joint_values=np.clip(active_joint_values,limits[:,0],limits[:,1])full_joint_values=self.apply_mimic_joints_numpy(active_joint_values,clamp_joint_values=clamp_joint_values)pin.forwardKinematics(self.pin_model,self.pin_data,full_joint_values[self._ours_to_pin_joint_indices])pin.updateFramePlacements(self.pin_model,self.pin_data)link_poses_list=[self.pin_data.oMf[self.pin_model.getFrameId(lname)].homogeneousforlnameinself.first_spec.link_names]link_poses=np.stack(link_poses_list,axis=0).astype(np.float32)ifroot_posesisnotNone:final_poses=root_poses@link_poseselse:final_poses=link_posesreturnfinal_poses