Source code for mani_skill.utils.structs.articulation_joint

from __future__ import annotations

import typing
from dataclasses import dataclass
from functools import cached_property
from typing import TYPE_CHECKING, List, Optional

import numpy as np
import sapien.physx as physx
import torch

from mani_skill.utils import common
from mani_skill.utils.structs.base import BaseStruct
from mani_skill.utils.structs.decorators import before_gpu_init
from mani_skill.utils.structs.pose import Pose
from mani_skill.utils.structs.types import Array

if TYPE_CHECKING:
    from mani_skill.envs.scene import ManiSkillScene
    from mani_skill.utils.structs.articulation import Articulation
    from mani_skill.utils.structs.link import Link


[docs]@dataclass class ArticulationJoint(BaseStruct[physx.PhysxArticulationJoint]): """ Wrapper around physx.PhysxArticulationJoint objects At the moment, all of the same joints across all sub scenes are restricted to having the same properties as each other """ index: torch.Tensor """index of this joint among all joints""" active_index: torch.Tensor """index of this joint amongst the active joints""" articulation: Optional[Articulation] = None child_link: Optional[Link] = None parent_link: Optional[Link] = None name: str = None _physx_articulations: List[physx.PhysxArticulation] = None def __str__(self): return f"<{self.name}: struct of type {self.__class__}; managing {self._num_objs} {self._objs[0].__class__} objects>" def __repr__(self): return self.__str__() def __hash__(self): return self.__maniskill_hash__ @classmethod def create( cls, physx_joints: List[physx.PhysxArticulationJoint], physx_articulations: List[physx.PhysxArticulation], scene: ManiSkillScene, scene_idxs: torch.Tensor, joint_index: torch.Tensor, active_joint_index: torch.Tensor = None, ): """Creates an object for managing articulation joints in articulations Note that the properties articulation, child_link, parent_link are by default None as they might not make sense in GPU sim and must be set by user """ return cls( index=joint_index, active_index=active_joint_index, _objs=physx_joints, _physx_articulations=physx_articulations, scene=scene, _scene_idxs=scene_idxs, ) # -------------------------------------------------------------------------- # # Additional useful functions not in SAPIEN original API # -------------------------------------------------------------------------- # @cached_property def _data_index(self): return torch.tensor( [ px_articulation.gpu_index for px_articulation in self._physx_articulations ], device=self.device, dtype=torch.int32, ) @property def qpos(self): """ The qpos of this joint in the articulation """ assert ( self.active_index is not None ), "Inactive joints do not have qpos/qvel values" if self.scene.gpu_sim_enabled: return self.px.cuda_articulation_qpos.torch()[ self._data_index, self.active_index ] else: return torch.tensor([self._physx_articulations[0].qpos[self.active_index]]) @property def qvel(self): """ The qvel of this joint in the articulation """ assert ( self.active_index is not None ), "Inactive joints do not have qpos/qvel values" if self.scene.gpu_sim_enabled: return self.px.cuda_articulation_qvel.torch()[ self._data_index, self.active_index ] else: return torch.tensor([self._physx_articulations[0].qvel[self.active_index]]) # -------------------------------------------------------------------------- # # Functions from physx.PhysxArticulationJoint # -------------------------------------------------------------------------- # # def get_armature(self) -> numpy.ndarray[numpy.float32, _Shape[m, 1]]: ... def get_child_link(self): return self.child_link def get_damping(self) -> float: return self.damping def get_dof(self) -> int: return self.dof def get_drive_mode(self): return self.drive_mode def get_drive_target(self): return self.drive_target # def get_drive_velocity_target(self) -> numpy.ndarray[numpy.float32, _Shape[m, 1]]: ... def get_force_limit(self): return self.force_limit def get_friction(self): return self.friction def get_global_pose(self): return self.global_pose def get_limits(self): return self.limits def get_name(self): return self.name def get_parent_link(self): return self.parent_link def get_pose_in_child(self): return self.pose_in_child def get_pose_in_parent(self): return self.pose_in_parent def get_stiffness(self) -> float: return self.stiffness def get_type(self): return self.type # def set_armature(self, armature: numpy.ndarray[numpy.float32, _Shape[m, 1]]) -> None: ... # @before_gpu_init def set_drive_properties( self, stiffness: float, damping: float, force_limit: float = 3.4028234663852886e38, mode: typing.Literal["force", "acceleration"] = "force", ): for joint in self._objs: joint.set_drive_properties(stiffness, damping, force_limit, mode) def set_drive_target(self, target: Array) -> None: self.drive_target = target def set_drive_velocity_target(self, velocity: Array) -> None: self.drive_velocity_target = velocity def set_friction(self, friction: float): self.friction = friction def set_limits(self, limits: Array) -> None: self.limits = limits # def set_name(self, name: str) -> None: ... # def set_pose_in_child(self, pose: sapien.pysapien.Pose) -> None: ... # def set_pose_in_parent(self, pose: sapien.pysapien.Pose) -> None: ... # def set_type(self, type: typing.Literal['fixed', 'revolute', 'revolute_unwrapped', 'prismatic', 'free']) -> None: ... # @property # def armature(self) -> numpy.ndarray[numpy.float32, _Shape[m, 1]]: # """ # :type: numpy.ndarray[numpy.float32, _Shape[m, 1]] # """ # @armature.setter # def armature(self, arg1: numpy.ndarray[numpy.float32, _Shape[m, 1]]) -> None: # pass # @property # def child_link(self) -> PhysxArticulationLinkComponent: # """ # :type: PhysxArticulationLinkComponent # """ @property def damping(self) -> torch.Tensor: return torch.tensor([obj.damping for obj in self._objs]) @property def dof(self) -> torch.Tensor: return torch.tensor([obj.dof for obj in self._objs]) @property def drive_mode(self) -> List[typing.Literal["force", "acceleration"]]: """ :type: typing.Literal['force', 'acceleration'] """ return [obj.drive_mode for obj in self._objs] @property def drive_target(self) -> torch.Tensor: if self.scene.gpu_sim_enabled: raise NotImplementedError( "Getting drive targets of individual joints is not implemented yet." ) else: return torch.from_numpy(self._objs[0].drive_target[None, :]) @drive_target.setter def drive_target(self, arg1: Array) -> None: arg1 = common.to_tensor(arg1, device=self.device) if self.scene.gpu_sim_enabled: raise NotImplementedError( "Setting drive targets of individual joints is not implemented yet." ) else: if arg1.shape == (): arg1 = arg1.reshape( 1, ) self._objs[0].drive_target = arg1.numpy() @property def drive_velocity_target(self) -> torch.Tensor: if self.scene.gpu_sim_enabled: raise NotImplementedError( "Cannot read drive velocity targets at the moment in GPU simulation" ) else: return torch.from_numpy(self._objs[0].drive_velocity_target[None, :]) @drive_velocity_target.setter def drive_velocity_target(self, arg1: Array) -> None: arg1 = common.to_tensor(arg1, device=self.device) if self.scene.gpu_sim_enabled: raise NotImplementedError( "Cannot set drive velocity targets at the moment in GPU simulation" ) else: if arg1.shape == (): arg1 = arg1.reshape( 1, ) self._objs[0].drive_velocity_target = arg1 @property def force_limit(self) -> torch.Tensor: return torch.tensor([obj.force_limit for obj in self._objs]) @property def friction(self) -> torch.Tensor: return torch.tensor([obj.friction for obj in self._objs]) @friction.setter # @before_gpu_init # TODO (stao): can we set this after gpu is initialized? def friction(self, arg1: float) -> None: for joint in self._objs: joint.friction = arg1 @property def global_pose(self) -> Pose: return self.pose_in_child * self.child_link.pose @property def limits(self) -> torch.Tensor: # TODO (stao): create a decorator that caches results once gpu sim is initialized for performance return common.to_tensor( np.array([obj.limits[0] for obj in self._objs]), device=self.device ) @limits.setter @before_gpu_init def limits(self, arg1: Array) -> None: for joint in self._objs: joint.limits = arg1 # @property # def name(self) -> str: # """ # :type: str # """ # @name.setter # def name(self, arg1: str) -> None: # pass # @property # def parent_link(self) -> PhysxArticulationLinkComponent: # """ # :type: PhysxArticulationLinkComponent # """ @cached_property def pose_in_child(self): raw_poses = np.stack( [np.concatenate([x.pose_in_child.p, x.pose_in_child.q]) for x in self._objs] ) return Pose.create(common.to_tensor(raw_poses), device=self.device) # @pose_in_child.setter # def pose_in_child(self, arg1: sapien.pysapien.Pose) -> None: # pass @cached_property def pose_in_parent(self): raw_poses = np.stack( [ np.concatenate([x.pose_in_parent.p, x.pose_in_parent.q]) for x in self._objs ] ) return Pose.create(common.to_tensor(raw_poses, device=self.device)) # @pose_in_parent.setter # def pose_in_parent(self, arg1: sapien.pysapien.Pose) -> None: # pass @property def stiffness(self) -> torch.Tensor: return torch.tensor([obj.stiffness for obj in self._objs]) @property def type( self, ) -> List[ typing.Literal["fixed", "revolute", "revolute_unwrapped", "prismatic", "free"] ]: return [obj.type for obj in self._objs] @type.setter @before_gpu_init def type( self, arg1: typing.Literal[ "fixed", "revolute", "revolute_unwrapped", "prismatic", "free" ], ) -> None: if self.scene.gpu_sim_enabled: return NotImplementedError("Cannot set type of the joint when using GPU") else: self._objs[0].type = arg1