Source code for isaaclab.assets.rigid_object.rigid_object
# Copyright (c) 2022-2025, The Isaac Lab Project Developers.# All rights reserved.## SPDX-License-Identifier: BSD-3-Clausefrom__future__importannotationsimporttorchfromcollections.abcimportSequencefromtypingimportTYPE_CHECKINGimportomni.logimportomni.physics.tensors.impl.apiasphysxfrompxrimportUsdPhysicsimportisaaclab.simassim_utilsimportisaaclab.utils.mathasmath_utilsimportisaaclab.utils.stringasstring_utilsfrom..asset_baseimportAssetBasefrom.rigid_object_dataimportRigidObjectDataifTYPE_CHECKING:from.rigid_object_cfgimportRigidObjectCfg
[docs]classRigidObject(AssetBase):"""A rigid object asset class. Rigid objects are assets comprising of rigid bodies. They can be used to represent dynamic objects such as boxes, spheres, etc. A rigid body is described by its pose, velocity and mass distribution. For an asset to be considered a rigid object, the root prim of the asset must have the `USD RigidBodyAPI`_ applied to it. This API is used to define the simulation properties of the rigid body. On playing the simulation, the physics engine will automatically register the rigid body and create a corresponding rigid body handle. This handle can be accessed using the :attr:`root_physx_view` attribute. .. note:: For users familiar with Isaac Sim, the PhysX view class API is not the exactly same as Isaac Sim view class API. Similar to Isaac Lab, Isaac Sim wraps around the PhysX view API. However, as of now (2023.1 release), we see a large difference in initializing the view classes in Isaac Sim. This is because the view classes in Isaac Sim perform additional USD-related operations which are slow and also not required. .. _`USD RigidBodyAPI`: https://openusd.org/dev/api/class_usd_physics_rigid_body_a_p_i.html """cfg:RigidObjectCfg"""Configuration instance for the rigid object."""
[docs]def__init__(self,cfg:RigidObjectCfg):"""Initialize the rigid object. Args: cfg: A configuration instance. """super().__init__(cfg)
""" Properties """@propertydefdata(self)->RigidObjectData:returnself._data@propertydefnum_instances(self)->int:returnself.root_physx_view.count@propertydefnum_bodies(self)->int:"""Number of bodies in the asset. This is always 1 since each object is a single rigid body. """return1@propertydefbody_names(self)->list[str]:"""Ordered names of bodies in the rigid object."""prim_paths=self.root_physx_view.prim_paths[:self.num_bodies]return[path.split("/")[-1]forpathinprim_paths]@propertydefroot_physx_view(self)->physx.RigidBodyView:"""Rigid body view for the asset (PhysX). Note: Use this view with caution. It requires handling of tensors in a specific way. """returnself._root_physx_view""" Operations. """
[docs]defreset(self,env_ids:Sequence[int]|None=None):# resolve all indicesifenv_idsisNone:env_ids=slice(None)# reset external wrenchself._external_force_b[env_ids]=0.0self._external_torque_b[env_ids]=0.0
[docs]defwrite_data_to_sim(self):"""Write external wrench to the simulation. Note: We write external wrench to the simulation here since this function is called before the simulation step. This ensures that the external wrench is applied at every simulation step. """# write external wrenchifself.has_external_wrench:self.root_physx_view.apply_forces_and_torques_at_position(force_data=self._external_force_b.view(-1,3),torque_data=self._external_torque_b.view(-1,3),position_data=None,indices=self._ALL_INDICES,is_global=False,)
[docs]deffind_bodies(self,name_keys:str|Sequence[str],preserve_order:bool=False)->tuple[list[int],list[str]]:"""Find bodies in the rigid body based on the name keys. Please check the :meth:`isaaclab.utils.string_utils.resolve_matching_names` function for more information on the name matching. Args: name_keys: A regular expression or a list of regular expressions to match the body names. preserve_order: Whether to preserve the order of the name keys in the output. Defaults to False. Returns: A tuple of lists containing the body indices and names. """returnstring_utils.resolve_matching_names(name_keys,self.body_names,preserve_order)
""" Operations - Write to simulation. """
[docs]defwrite_root_state_to_sim(self,root_state:torch.Tensor,env_ids:Sequence[int]|None=None):"""Set the root state over selected environment indices into the simulation. The root state comprises of the cartesian position, quaternion orientation in (w, x, y, z), and linear and angular velocity. All the quantities are in the simulation frame. Args: root_state: Root state in simulation frame. Shape is (len(env_ids), 13). env_ids: Environment indices. If None, then all indices are used. """# set into simulationself.write_root_pose_to_sim(root_state[:,:7],env_ids=env_ids)self.write_root_velocity_to_sim(root_state[:,7:],env_ids=env_ids)
[docs]defwrite_root_com_state_to_sim(self,root_state:torch.Tensor,env_ids:Sequence[int]|None=None):"""Set the root center of mass state over selected environment indices into the simulation. The root state comprises of the cartesian position, quaternion orientation in (w, x, y, z), and linear and angular velocity. All the quantities are in the simulation frame. Args: root_state: Root state in simulation frame. Shape is (len(env_ids), 13). env_ids: Environment indices. If None, then all indices are used. """# set into simulationself.write_root_com_pose_to_sim(root_state[:,:7],env_ids=env_ids)self.write_root_com_velocity_to_sim(root_state[:,7:],env_ids=env_ids)
[docs]defwrite_root_link_state_to_sim(self,root_state:torch.Tensor,env_ids:Sequence[int]|None=None):"""Set the root link state over selected environment indices into the simulation. The root state comprises of the cartesian position, quaternion orientation in (w, x, y, z), and linear and angular velocity. All the quantities are in the simulation frame. Args: root_state: Root state in simulation frame. Shape is (len(env_ids), 13). env_ids: Environment indices. If None, then all indices are used. """# set into simulationself.write_root_link_pose_to_sim(root_state[:,:7],env_ids=env_ids)self.write_root_link_velocity_to_sim(root_state[:,7:],env_ids=env_ids)
[docs]defwrite_root_pose_to_sim(self,root_pose:torch.Tensor,env_ids:Sequence[int]|None=None):"""Set the root pose over selected environment indices into the simulation. The root pose comprises of the cartesian position and quaternion orientation in (w, x, y, z). Args: root_pose: Root poses in simulation frame. Shape is (len(env_ids), 7). env_ids: Environment indices. If None, then all indices are used. """# resolve all indicesphysx_env_ids=env_idsifenv_idsisNone:env_ids=slice(None)physx_env_ids=self._ALL_INDICES# note: we need to do this here since tensors are not set into simulation until step.# set into internal buffersself._data.root_state_w[env_ids,:7]=root_pose.clone()# convert root quaternion from wxyz to xyzwroot_poses_xyzw=self._data.root_state_w[:,:7].clone()root_poses_xyzw[:,3:]=math_utils.convert_quat(root_poses_xyzw[:,3:],to="xyzw")# set into simulationself.root_physx_view.set_transforms(root_poses_xyzw,indices=physx_env_ids)
[docs]defwrite_root_link_pose_to_sim(self,root_pose:torch.Tensor,env_ids:Sequence[int]|None=None):"""Set the root link pose over selected environment indices into the simulation. The root pose comprises of the cartesian position and quaternion orientation in (w, x, y, z). Args: root_pose: Root poses in simulation frame. Shape is (len(env_ids), 7). env_ids: Environment indices. If None, then all indices are used. """# resolve all indicesphysx_env_ids=env_idsifenv_idsisNone:env_ids=slice(None)physx_env_ids=self._ALL_INDICES# note: we need to do this here since tensors are not set into simulation until step.# set into internal buffersself._data.root_link_state_w[env_ids,:7]=root_pose.clone()self._data.root_state_w[env_ids,:7]=self._data.root_link_state_w[env_ids,:7]# convert root quaternion from wxyz to xyzwroot_poses_xyzw=self._data.root_link_state_w[:,:7].clone()root_poses_xyzw[:,3:]=math_utils.convert_quat(root_poses_xyzw[:,3:],to="xyzw")# set into simulationself.root_physx_view.set_transforms(root_poses_xyzw,indices=physx_env_ids)
[docs]defwrite_root_com_pose_to_sim(self,root_pose:torch.Tensor,env_ids:Sequence[int]|None=None):"""Set the root center of mass pose over selected environment indices into the simulation. The root pose comprises of the cartesian position and quaternion orientation in (w, x, y, z). The orientation is the orientation of the principle axes of inertia. Args: root_pose: Root center of mass poses in simulation frame. Shape is (len(env_ids), 7). env_ids: Environment indices. If None, then all indices are used. """# resolve all indicesifenv_idsisNone:local_env_ids=slice(env_ids)else:local_env_ids=env_idscom_pos=self.data.com_pos_b[local_env_ids,0,:]com_quat=self.data.com_quat_b[local_env_ids,0,:]root_link_pos,root_link_quat=math_utils.combine_frame_transforms(root_pose[...,:3],root_pose[...,3:7],math_utils.quat_rotate(math_utils.quat_inv(com_quat),-com_pos),math_utils.quat_inv(com_quat),)root_link_pose=torch.cat((root_link_pos,root_link_quat),dim=-1)self.write_root_link_pose_to_sim(root_pose=root_link_pose,env_ids=env_ids)
[docs]defwrite_root_velocity_to_sim(self,root_velocity:torch.Tensor,env_ids:Sequence[int]|None=None):"""Set the root center of mass velocity over selected environment indices into the simulation. The velocity comprises linear velocity (x, y, z) and angular velocity (x, y, z) in that order. NOTE: This sets the velocity of the root's center of mass rather than the roots frame. Args: root_velocity: Root center of mass velocities in simulation world frame. Shape is (len(env_ids), 6). env_ids: Environment indices. If None, then all indices are used. """# resolve all indicesphysx_env_ids=env_idsifenv_idsisNone:env_ids=slice(None)physx_env_ids=self._ALL_INDICES# note: we need to do this here since tensors are not set into simulation until step.# set into internal buffersself._data.root_state_w[env_ids,7:]=root_velocity.clone()self._data.body_acc_w[env_ids]=0.0# set into simulationself.root_physx_view.set_velocities(self._data.root_state_w[:,7:],indices=physx_env_ids)
[docs]defwrite_root_com_velocity_to_sim(self,root_velocity:torch.Tensor,env_ids:Sequence[int]|None=None):"""Set the root center of mass velocity over selected environment indices into the simulation. The velocity comprises linear velocity (x, y, z) and angular velocity (x, y, z) in that order. NOTE: This sets the velocity of the root's center of mass rather than the roots frame. Args: root_velocity: Root center of mass velocities in simulation world frame. Shape is (len(env_ids), 6). env_ids: Environment indices. If None, then all indices are used. """# resolve all indicesphysx_env_ids=env_idsifenv_idsisNone:env_ids=slice(None)physx_env_ids=self._ALL_INDICES# note: we need to do this here since tensors are not set into simulation until step.# set into internal buffersself._data.root_com_state_w[env_ids,7:]=root_velocity.clone()self._data.root_state_w[env_ids,7:]=self._data.root_com_state_w[env_ids,7:]self._data.body_acc_w[env_ids]=0.0# set into simulationself.root_physx_view.set_velocities(self._data.root_com_state_w[:,7:],indices=physx_env_ids)
[docs]defwrite_root_link_velocity_to_sim(self,root_velocity:torch.Tensor,env_ids:Sequence[int]|None=None):"""Set the root link velocity over selected environment indices into the simulation. The velocity comprises linear velocity (x, y, z) and angular velocity (x, y, z) in that order. NOTE: This sets the velocity of the root's frame rather than the roots center of mass. Args: root_velocity: Root frame velocities in simulation world frame. Shape is (len(env_ids), 6). env_ids: Environment indices. If None, then all indices are used. """# resolve all indicesifenv_idsisNone:local_env_ids=slice(env_ids)else:local_env_ids=env_idsroot_com_velocity=root_velocity.clone()quat=self.data.root_link_state_w[local_env_ids,3:7]com_pos_b=self.data.com_pos_b[local_env_ids,0,:]# transform given velocity to center of massroot_com_velocity[:,:3]+=torch.linalg.cross(root_com_velocity[:,3:],math_utils.quat_rotate(quat,com_pos_b),dim=-1)# write center of mass velocity to simself.write_root_com_velocity_to_sim(root_velocity=root_com_velocity,env_ids=env_ids)
""" Operations - Setters. """
[docs]defset_external_force_and_torque(self,forces:torch.Tensor,torques:torch.Tensor,body_ids:Sequence[int]|slice|None=None,env_ids:Sequence[int]|None=None,):"""Set external force and torque to apply on the asset's bodies in their local frame. For many applications, we want to keep the applied external force on rigid bodies constant over a period of time (for instance, during the policy control). This function allows us to store the external force and torque into buffers which are then applied to the simulation at every step. .. caution:: If the function is called with empty forces and torques, then this function disables the application of external wrench to the simulation. .. code-block:: python # example of disabling external wrench asset.set_external_force_and_torque(forces=torch.zeros(0, 3), torques=torch.zeros(0, 3)) .. note:: This function does not apply the external wrench to the simulation. It only fills the buffers with the desired values. To apply the external wrench, call the :meth:`write_data_to_sim` function right before the simulation step. Args: forces: External forces in bodies' local frame. Shape is (len(env_ids), len(body_ids), 3). torques: External torques in bodies' local frame. Shape is (len(env_ids), len(body_ids), 3). body_ids: Body indices to apply external wrench to. Defaults to None (all bodies). env_ids: Environment indices to apply external wrench to. Defaults to None (all instances). """ifforces.any()ortorques.any():self.has_external_wrench=Trueelse:self.has_external_wrench=False# to be safe, explicitly set value to zeroforces=torques=0.0# resolve all indices# -- env_idsifenv_idsisNone:env_ids=slice(None)# -- body_idsifbody_idsisNone:body_ids=slice(None)# broadcast env_ids if needed to allow double indexingifenv_ids!=slice(None)andbody_ids!=slice(None):env_ids=env_ids[:,None]# set into internal buffersself._external_force_b[env_ids,body_ids]=forcesself._external_torque_b[env_ids,body_ids]=torques
""" Internal helper. """def_initialize_impl(self):# create simulation viewself._physics_sim_view=physx.create_simulation_view(self._backend)self._physics_sim_view.set_subspace_roots("/")# obtain the first prim in the regex expression (all others are assumed to be a copy of this)template_prim=sim_utils.find_first_matching_prim(self.cfg.prim_path)iftemplate_primisNone:raiseRuntimeError(f"Failed to find prim for expression: '{self.cfg.prim_path}'.")template_prim_path=template_prim.GetPath().pathString# find rigid root primsroot_prims=sim_utils.get_all_matching_child_prims(template_prim_path,predicate=lambdaprim:prim.HasAPI(UsdPhysics.RigidBodyAPI))iflen(root_prims)==0:raiseRuntimeError(f"Failed to find a rigid body when resolving '{self.cfg.prim_path}'."" Please ensure that the prim has 'USD RigidBodyAPI' applied.")iflen(root_prims)>1:raiseRuntimeError(f"Failed to find a single rigid body when resolving '{self.cfg.prim_path}'."f" Found multiple '{root_prims}' under '{template_prim_path}'."" Please ensure that there is only one rigid body in the prim path tree.")articulation_prims=sim_utils.get_all_matching_child_prims(template_prim_path,predicate=lambdaprim:prim.HasAPI(UsdPhysics.ArticulationRootAPI))iflen(articulation_prims)!=0:ifarticulation_prims[0].GetAttribute("physxArticulation:articulationEnabled").Get():raiseRuntimeError(f"Found an articulation root when resolving '{self.cfg.prim_path}' for rigid objects. These are"f" located at: '{articulation_prims}' under '{template_prim_path}'. Please disable the articulation"" root in the USD or from code by setting the parameter"" 'ArticulationRootPropertiesCfg.articulation_enabled' to False in the spawn configuration.")# resolve root prim back into regex expressionroot_prim_path=root_prims[0].GetPath().pathStringroot_prim_path_expr=self.cfg.prim_path+root_prim_path[len(template_prim_path):]# -- object viewself._root_physx_view=self._physics_sim_view.create_rigid_body_view(root_prim_path_expr.replace(".*","*"))# check if the rigid body was createdifself._root_physx_view._backendisNone:raiseRuntimeError(f"Failed to create rigid body at: {self.cfg.prim_path}. Please check PhysX logs.")# log information about the rigid bodyomni.log.info(f"Rigid body initialized at: {self.cfg.prim_path} with root '{root_prim_path_expr}'.")omni.log.info(f"Number of instances: {self.num_instances}")omni.log.info(f"Number of bodies: {self.num_bodies}")omni.log.info(f"Body names: {self.body_names}")# container for data accessself._data=RigidObjectData(self.root_physx_view,self.device)# create buffersself._create_buffers()# process configurationself._process_cfg()# update the rigid body dataself.update(0.0)def_create_buffers(self):"""Create buffers for storing data."""# constantsself._ALL_INDICES=torch.arange(self.num_instances,dtype=torch.long,device=self.device)# external forces and torquesself.has_external_wrench=Falseself._external_force_b=torch.zeros((self.num_instances,self.num_bodies,3),device=self.device)self._external_torque_b=torch.zeros_like(self._external_force_b)# set information about rigid body into dataself._data.body_names=self.body_namesself._data.default_mass=self.root_physx_view.get_masses().clone()self._data.default_inertia=self.root_physx_view.get_inertias().clone()def_process_cfg(self):"""Post processing of configuration parameters."""# default state# -- root state# note: we cast to tuple to avoid torch/numpy type mismatch.default_root_state=(tuple(self.cfg.init_state.pos)+tuple(self.cfg.init_state.rot)+tuple(self.cfg.init_state.lin_vel)+tuple(self.cfg.init_state.ang_vel))default_root_state=torch.tensor(default_root_state,dtype=torch.float,device=self.device)self._data.default_root_state=default_root_state.repeat(self.num_instances,1)""" Internal simulation callbacks. """def_invalidate_initialize_callback(self,event):"""Invalidates the scene elements."""# call parentsuper()._invalidate_initialize_callback(event)# set all existing views to None to invalidate themself._physics_sim_view=Noneself._root_physx_view=None