Unverified Commit 59c0b2db authored by Mayank Mittal's avatar Mayank Mittal Committed by GitHub

Adds documentation on the frames for asset data (#742)

# Description

This MR adds docstrings in articulation and rigid object data to clarify
the exact frames they are reported in. This was previously causing
confusion as PhysX reports the transforms in actor frame and velocities
in CoM frame. Right now, there is no mechanism to get all this data in
the same frame, so the best we can do is document behavior.

## Type of change

- This change requires a documentation update

## Checklist

- [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./isaaclab.sh --format`
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] I have updated the changelog and the corresponding version in the
extension's `config/extension.toml` file
- [x] I have added my name to the `CONTRIBUTORS.md` or my name already
exists there
parent 1334f280
...@@ -18,6 +18,16 @@ class ArticulationData(RigidObjectData): ...@@ -18,6 +18,16 @@ class ArticulationData(RigidObjectData):
This class extends the :class:`RigidObjectData` class to provide additional data for This class extends the :class:`RigidObjectData` class to provide additional data for
an articulation mainly related to the joints and tendons. an articulation mainly related to the joints and tendons.
An articulation is comprised of multiple rigid bodies or links. For a rigid body, there are two frames
of reference that are used:
- Actor frame: The frame of reference of the rigid body prim. This typically corresponds to the Xform prim
with the rigid body schema.
- Center of mass frame: The frame of reference of the center of mass of the rigid body.
Depending on the settings, the two frames may not coincide with each other. In the robotics sense, the actor frame
can be interpreted as the link frame.
""" """
_root_physx_view: physx.ArticulationView _root_physx_view: physx.ArticulationView
...@@ -211,7 +221,11 @@ class ArticulationData(RigidObjectData): ...@@ -211,7 +221,11 @@ class ArticulationData(RigidObjectData):
@property @property
def root_state_w(self): def root_state_w(self):
"""Root state ``[pos, quat, lin_vel, ang_vel]`` in simulation world frame. Shape is (num_instances, 13).""" """Root state ``[pos, quat, lin_vel, ang_vel]`` in simulation world frame. Shape is (num_instances, 13).
The position and quaternion are of the articulation root's actor frame. Meanwhile, the linear and angular
velocities are of the articulation root's center of mass frame.
"""
if self._root_state_w.timestamp < self._sim_timestamp: if self._root_state_w.timestamp < self._sim_timestamp:
# read data from simulation # read data from simulation
pose = self._root_physx_view.get_root_transforms().clone() pose = self._root_physx_view.get_root_transforms().clone()
...@@ -225,7 +239,11 @@ class ArticulationData(RigidObjectData): ...@@ -225,7 +239,11 @@ class ArticulationData(RigidObjectData):
@property @property
def body_state_w(self): def body_state_w(self):
"""State of all bodies `[pos, quat, lin_vel, ang_vel]` in simulation world frame. """State of all bodies `[pos, quat, lin_vel, ang_vel]` in simulation world frame.
Shape is (num_instances, num_bodies, 13).""" Shape is (num_instances, num_bodies, 13).
The position and quaternion are of all the articulation links's actor frame. Meanwhile, the linear and angular
velocities are of the articulation links's center of mass frame.
"""
if self._body_state_w.timestamp < self._sim_timestamp: if self._body_state_w.timestamp < self._sim_timestamp:
# read data from simulation # read data from simulation
poses = self._root_physx_view.get_link_transforms().clone() poses = self._root_physx_view.get_link_transforms().clone()
...@@ -238,7 +256,10 @@ class ArticulationData(RigidObjectData): ...@@ -238,7 +256,10 @@ class ArticulationData(RigidObjectData):
@property @property
def body_acc_w(self): def body_acc_w(self):
"""Acceleration of all bodies. Shape is (num_instances, num_bodies, 6).""" """Acceleration of all bodies. Shape is (num_instances, num_bodies, 6).
This quantity is the acceleration of the articulation links' center of mass frame.
"""
if self._body_acc_w.timestamp < self._sim_timestamp: if self._body_acc_w.timestamp < self._sim_timestamp:
# read data from simulation and set the buffer data and timestamp # read data from simulation and set the buffer data and timestamp
self._body_acc_w.data = self._root_physx_view.get_link_accelerations() self._body_acc_w.data = self._root_physx_view.get_link_accelerations()
...@@ -247,12 +268,18 @@ class ArticulationData(RigidObjectData): ...@@ -247,12 +268,18 @@ class ArticulationData(RigidObjectData):
@property @property
def body_lin_acc_w(self) -> torch.Tensor: def body_lin_acc_w(self) -> torch.Tensor:
"""Linear acceleration of all bodies in simulation world frame. Shape is (num_instances, num_bodies, 3).""" """Linear acceleration of all bodies in simulation world frame. Shape is (num_instances, num_bodies, 3).
This quantity is the linear acceleration of the articulation links' center of mass frame.
"""
return self.body_acc_w[..., 0:3] return self.body_acc_w[..., 0:3]
@property @property
def body_ang_acc_w(self) -> torch.Tensor: def body_ang_acc_w(self) -> torch.Tensor:
"""Angular acceleration of all bodies in simulation world frame. Shape is (num_instances, num_bodies, 3).""" """Angular acceleration of all bodies in simulation world frame. Shape is (num_instances, num_bodies, 3).
This quantity is the angular acceleration of the articulation links' center of mass frame.
"""
return self.body_acc_w[..., 3:6] return self.body_acc_w[..., 3:6]
@property @property
......
...@@ -19,6 +19,15 @@ class RigidObjectData: ...@@ -19,6 +19,15 @@ class RigidObjectData:
the root rigid body and the state of all the bodies in the object. The data is stored in the simulation the root rigid body and the state of all the bodies in the object. The data is stored in the simulation
world frame unless otherwise specified. world frame unless otherwise specified.
For a rigid body, there are two frames of reference that are used:
- Actor frame: The frame of reference of the rigid body prim. This typically corresponds to the Xform prim
with the rigid body schema.
- Center of mass frame: The frame of reference of the center of mass of the rigid body.
Depending on the settings of the simulation, the actor frame and the center of mass frame may be the same.
This needs to be taken into account when interpreting the data.
The data is lazily updated, meaning that the data is only updated when it is accessed. This is useful The data is lazily updated, meaning that the data is only updated when it is accessed. This is useful
when the data is expensive to compute or retrieve. The data is updated when the timestamp of the buffer when the data is expensive to compute or retrieve. The data is updated when the timestamp of the buffer
is older than the current simulation timestamp. The timestamp is updated whenever the data is updated. is older than the current simulation timestamp. The timestamp is updated whenever the data is updated.
...@@ -81,7 +90,11 @@ class RigidObjectData: ...@@ -81,7 +90,11 @@ class RigidObjectData:
## ##
default_root_state: torch.Tensor = None default_root_state: torch.Tensor = None
"""Default root state ``[pos, quat, lin_vel, ang_vel]`` in local environment frame. Shape is (num_instances, 13).""" """Default root state ``[pos, quat, lin_vel, ang_vel]`` in local environment frame. Shape is (num_instances, 13).
The position and quaternion are of the rigid body's actor frame. Meanwhile, the linear and angular velocities are
of the center of mass frame.
"""
default_mass: torch.Tensor = None default_mass: torch.Tensor = None
""" Default mass provided by simulation. Shape is (num_instances, num_bodies).""" """ Default mass provided by simulation. Shape is (num_instances, num_bodies)."""
...@@ -92,7 +105,11 @@ class RigidObjectData: ...@@ -92,7 +105,11 @@ class RigidObjectData:
@property @property
def root_state_w(self): def root_state_w(self):
"""Root state ``[pos, quat, lin_vel, ang_vel]`` in simulation world frame. Shape is (num_instances, 13).""" """Root state ``[pos, quat, lin_vel, ang_vel]`` in simulation world frame. Shape is (num_instances, 13).
The position and orientation are of the rigid body's actor frame. Meanwhile, the linear and angular
velocities are of the rigid body's center of mass frame.
"""
if self._root_state_w.timestamp < self._sim_timestamp: if self._root_state_w.timestamp < self._sim_timestamp:
# read data from simulation # read data from simulation
pose = self._root_physx_view.get_transforms().clone() pose = self._root_physx_view.get_transforms().clone()
...@@ -105,12 +122,20 @@ class RigidObjectData: ...@@ -105,12 +122,20 @@ class RigidObjectData:
@property @property
def body_state_w(self): def body_state_w(self):
"""State of all bodies `[pos, quat, lin_vel, ang_vel]` in simulation world frame. Shape is (num_instances, 1, 13).""" """State of all bodies `[pos, quat, lin_vel, ang_vel]` in simulation world frame. Shape is (num_instances, 1, 13).
The position and orientation are of the rigid bodies' actor frame. Meanwhile, the linear and angular
velocities are of the rigid bodies' center of mass frame.
"""
return self.root_state_w.view(-1, 1, 13) return self.root_state_w.view(-1, 1, 13)
@property @property
def body_acc_w(self): def body_acc_w(self):
"""Acceleration of all bodies. Shape is (num_instances, 1, 6).""" """Acceleration of all bodies. Shape is (num_instances, 1, 6).
This quantity is the acceleration of the rigid bodies' center of mass frame. The acceleration
is computed using finite differencing of the linear and angular velocities of the bodies.
"""
if self._body_acc_w.timestamp < self._sim_timestamp: if self._body_acc_w.timestamp < self._sim_timestamp:
# note: we use finite differencing to compute acceleration # note: we use finite differencing to compute acceleration
self._body_acc_w.data = self._root_physx_view.get_accelerations().unsqueeze(1) self._body_acc_w.data = self._root_physx_view.get_accelerations().unsqueeze(1)
...@@ -135,60 +160,98 @@ class RigidObjectData: ...@@ -135,60 +160,98 @@ class RigidObjectData:
@property @property
def root_pos_w(self) -> torch.Tensor: def root_pos_w(self) -> torch.Tensor:
"""Root position in simulation world frame. Shape is (num_instances, 3).""" """Root position in simulation world frame. Shape is (num_instances, 3).
This quantity is the position of the actor frame of the root rigid body.
"""
return self.root_state_w[:, :3] return self.root_state_w[:, :3]
@property @property
def root_quat_w(self) -> torch.Tensor: def root_quat_w(self) -> torch.Tensor:
"""Root orientation (w, x, y, z) in simulation world frame. Shape is (num_instances, 4).""" """Root orientation (w, x, y, z) in simulation world frame. Shape is (num_instances, 4).
This quantity is the orientation of the actor frame of the root rigid body.
"""
return self.root_state_w[:, 3:7] return self.root_state_w[:, 3:7]
@property @property
def root_vel_w(self) -> torch.Tensor: def root_vel_w(self) -> torch.Tensor:
"""Root velocity in simulation world frame. Shape is (num_instances, 6).""" """Root velocity in simulation world frame. Shape is (num_instances, 6).
This quantity contains the linear and angular velocities of the root rigid body's center of mass frame.
"""
return self.root_state_w[:, 7:13] return self.root_state_w[:, 7:13]
@property @property
def root_lin_vel_w(self) -> torch.Tensor: def root_lin_vel_w(self) -> torch.Tensor:
"""Root linear velocity in simulation world frame. Shape is (num_instances, 3).""" """Root linear velocity in simulation world frame. Shape is (num_instances, 3).
This quantity is the linear velocity of the root rigid body's center of mass frame.
"""
return self.root_state_w[:, 7:10] return self.root_state_w[:, 7:10]
@property @property
def root_ang_vel_w(self) -> torch.Tensor: def root_ang_vel_w(self) -> torch.Tensor:
"""Root angular velocity in simulation world frame. Shape is (num_instances, 3).""" """Root angular velocity in simulation world frame. Shape is (num_instances, 3).
This quantity is the angular velocity of the root rigid body's center of mass frame.
"""
return self.root_state_w[:, 10:13] return self.root_state_w[:, 10:13]
@property @property
def root_lin_vel_b(self) -> torch.Tensor: def root_lin_vel_b(self) -> torch.Tensor:
"""Root linear velocity in base frame. Shape is (num_instances, 3).""" """Root linear velocity in base frame. Shape is (num_instances, 3).
This quantity is the linear velocity of the root rigid body's center of mass frame with respect to the
rigid body's actor frame.
"""
return math_utils.quat_rotate_inverse(self.root_quat_w, self.root_lin_vel_w) return math_utils.quat_rotate_inverse(self.root_quat_w, self.root_lin_vel_w)
@property @property
def root_ang_vel_b(self) -> torch.Tensor: def root_ang_vel_b(self) -> torch.Tensor:
"""Root angular velocity in base world frame. Shape is (num_instances, 3).""" """Root angular velocity in base world frame. Shape is (num_instances, 3).
This quantity is the angular velocity of the root rigid body's center of mass frame with respect to the
rigid body's actor frame.
"""
return math_utils.quat_rotate_inverse(self.root_quat_w, self.root_ang_vel_w) return math_utils.quat_rotate_inverse(self.root_quat_w, self.root_ang_vel_w)
@property @property
def body_pos_w(self) -> torch.Tensor: def body_pos_w(self) -> torch.Tensor:
"""Positions of all bodies in simulation world frame. Shape is (num_instances, num_bodies, 3).""" """Positions of all bodies in simulation world frame. Shape is (num_instances, num_bodies, 3).
This quantity is the position of the rigid bodies' actor frame.
"""
return self.body_state_w[..., :3] return self.body_state_w[..., :3]
@property @property
def body_quat_w(self) -> torch.Tensor: def body_quat_w(self) -> torch.Tensor:
"""Orientation (w, x, y, z) of all bodies in simulation world frame. Shape is (num_instances, num_bodies, 4).""" """Orientation (w, x, y, z) of all bodies in simulation world frame. Shape is (num_instances, num_bodies, 4).
This quantity is the orientation of the rigid bodies' actor frame.
"""
return self.body_state_w[..., 3:7] return self.body_state_w[..., 3:7]
@property @property
def body_vel_w(self) -> torch.Tensor: def body_vel_w(self) -> torch.Tensor:
"""Velocity of all bodies in simulation world frame. Shape is (num_instances, num_bodies, 6).""" """Velocity of all bodies in simulation world frame. Shape is (num_instances, num_bodies, 6).
This quantity contains the linear and angular velocities of the rigid bodies' center of mass frame.
"""
return self.body_state_w[..., 7:13] return self.body_state_w[..., 7:13]
@property @property
def body_lin_vel_w(self) -> torch.Tensor: def body_lin_vel_w(self) -> torch.Tensor:
"""Linear velocity of all bodies in simulation world frame. Shape is (num_instances, num_bodies, 3).""" """Linear velocity of all bodies in simulation world frame. Shape is (num_instances, num_bodies, 3).
This quantity is the linear velocity of the rigid bodies' center of mass frame.
"""
return self.body_state_w[..., 7:10] return self.body_state_w[..., 7:10]
@property @property
def body_ang_vel_w(self) -> torch.Tensor: def body_ang_vel_w(self) -> torch.Tensor:
"""Angular velocity of all bodies in simulation world frame. Shape is (num_instances, num_bodies, 3).""" """Angular velocity of all bodies in simulation world frame. Shape is (num_instances, num_bodies, 3).
This quantity is the angular velocity of the rigid bodies' center of mass frame.
"""
return self.body_state_w[..., 10:13] return self.body_state_w[..., 10:13]
...@@ -39,7 +39,21 @@ class ContactSensor(SensorBase): ...@@ -39,7 +39,21 @@ class ContactSensor(SensorBase):
in the asset. in the asset.
The sensor can be configured to report the contact forces on a set of bodies with a given The sensor can be configured to report the contact forces on a set of bodies with a given
filter pattern. Please check the documentation on `RigidContactView`_ for more details. filter pattern using the :attr:`ContactSensorCfg.filter_prim_paths_expr`. This is useful
when you want to report the contact forces between the sensor bodies and a specific set of
bodies in the scene. The data can be accessed using the :attr:`ContactSensorData.force_matrix_w`.
Please check the documentation on `RigidContactView`_ for more details.
The reporting of the filtered contact forces is only possible as one-to-many. This means that only one
sensor body in an environment can be filtered against multiple bodies in that environment. If you need to
filter multiple sensor bodies against multiple bodies, you need to create separate sensors for each sensor
body.
As an example, suppose you want to report the contact forces for all the feet of a robot against an object
exclusively. In that case, setting the :attr:`ContactSensorCfg.prim_path` and
:attr:`ContactSensorCfg.filter_prim_paths_expr` with ``{ENV_REGEX_NS}/Robot/.*_FOOT`` and ``{ENV_REGEX_NS}/Object``
respectively will not work. Instead, you need to create a separate sensor for each foot and filter
it against the object.
.. _PhysX ContactReporter: https://docs.omniverse.nvidia.com/kit/docs/omni_usd_schema_physics/104.2/class_physx_schema_physx_contact_report_a_p_i.html .. _PhysX ContactReporter: https://docs.omniverse.nvidia.com/kit/docs/omni_usd_schema_physics/104.2/class_physx_schema_physx_contact_report_a_p_i.html
.. _RigidContactView: https://docs.omniverse.nvidia.com/py/isaacsim/source/extensions/omni.isaac.core/docs/index.html#omni.isaac.core.prims.RigidContactView .. _RigidContactView: https://docs.omniverse.nvidia.com/py/isaacsim/source/extensions/omni.isaac.core/docs/index.html#omni.isaac.core.prims.RigidContactView
...@@ -321,11 +335,13 @@ class ContactSensor(SensorBase): ...@@ -321,11 +335,13 @@ class ContactSensor(SensorBase):
force_matrix_w = self.contact_physx_view.get_contact_force_matrix(dt=self._sim_physics_dt) force_matrix_w = self.contact_physx_view.get_contact_force_matrix(dt=self._sim_physics_dt)
force_matrix_w = force_matrix_w.view(-1, self._num_bodies, num_filters, 3) force_matrix_w = force_matrix_w.view(-1, self._num_bodies, num_filters, 3)
self._data.force_matrix_w[env_ids] = force_matrix_w[env_ids] self._data.force_matrix_w[env_ids] = force_matrix_w[env_ids]
# obtain the pose of the sensor origin # obtain the pose of the sensor origin
if self.cfg.track_pose: if self.cfg.track_pose:
pose = self.body_physx_view.get_transforms().view(-1, self._num_bodies, 7)[env_ids] pose = self.body_physx_view.get_transforms().view(-1, self._num_bodies, 7)[env_ids]
pose[..., 3:] = convert_quat(pose[..., 3:], to="wxyz") pose[..., 3:] = convert_quat(pose[..., 3:], to="wxyz")
self._data.pos_w[env_ids], self._data.quat_w[env_ids] = pose.split([3, 4], dim=-1) self._data.pos_w[env_ids], self._data.quat_w[env_ids] = pose.split([3, 4], dim=-1)
# obtain the air time # obtain the air time
if self.cfg.track_air_time: if self.cfg.track_air_time:
# -- time elapsed since last update # -- time elapsed since last update
......
...@@ -44,6 +44,11 @@ class ContactSensorCfg(SensorBaseCfg): ...@@ -44,6 +44,11 @@ class ContactSensorCfg(SensorBaseCfg):
Example: ``{ENV_REGEX_NS}/Object`` will be replaced with ``/World/envs/env_.*/Object``. Example: ``{ENV_REGEX_NS}/Object`` will be replaced with ``/World/envs/env_.*/Object``.
.. attention::
The reporting of filtered contacts only works when the sensor primitive :attr:`prim_path` corresponds to a
single primitive in that environment. If the sensor primitive corresponds to multiple primitives, the
filtering will not work as expected. Please check :class:`~omni.isaac.lab.sensors.contact_sensor.ContactSensor`
for more details.
""" """
visualizer_cfg: VisualizationMarkersCfg = CONTACT_SENSOR_MARKER_CFG.replace(prim_path="/Visuals/ContactSensor") visualizer_cfg: VisualizationMarkersCfg = CONTACT_SENSOR_MARKER_CFG.replace(prim_path="/Visuals/ContactSensor")
......
...@@ -20,7 +20,7 @@ class ContactSensorData: ...@@ -20,7 +20,7 @@ class ContactSensorData:
Shape is (N, 3), where N is the number of sensors. Shape is (N, 3), where N is the number of sensors.
Note: Note:
If the :attr:`ContactSensorCfg.track_pose` is False, then this qunatity is None. If the :attr:`ContactSensorCfg.track_pose` is False, then this quantity is None.
""" """
quat_w: torch.Tensor | None = None quat_w: torch.Tensor | None = None
...@@ -29,26 +29,34 @@ class ContactSensorData: ...@@ -29,26 +29,34 @@ class ContactSensorData:
Shape is (N, 4), where N is the number of sensors. Shape is (N, 4), where N is the number of sensors.
Note: Note:
If the :attr:`ContactSensorCfg.track_pose` is False, then this qunatity is None. If the :attr:`ContactSensorCfg.track_pose` is False, then this quantity is None.
""" """
net_forces_w: torch.Tensor | None = None net_forces_w: torch.Tensor | None = None
"""The net contact forces in world frame. """The net normal contact forces in world frame.
Shape is (N, B, 3), where N is the number of sensors and B is the number of bodies in each sensor. Shape is (N, B, 3), where N is the number of sensors and B is the number of bodies in each sensor.
Note:
This quantity is the sum of the normal contact forces acting on the sensor bodies. It must not be confused
with the total contact forces acting on the sensor bodies (which also includes the tangential forces).
""" """
net_forces_w_history: torch.Tensor | None = None net_forces_w_history: torch.Tensor | None = None
"""The net contact forces in world frame. """The net normal contact forces in world frame.
Shape is (N, T, B, 3), where N is the number of sensors, T is the configured history length Shape is (N, T, B, 3), where N is the number of sensors, T is the configured history length
and B is the number of bodies in each sensor. and B is the number of bodies in each sensor.
In the history dimension, the first index is the most recent and the last index is the oldest. In the history dimension, the first index is the most recent and the last index is the oldest.
Note:
This quantity is the sum of the normal contact forces acting on the sensor bodies. It must not be confused
with the total contact forces acting on the sensor bodies (which also includes the tangential forces).
""" """
force_matrix_w: torch.Tensor | None = None force_matrix_w: torch.Tensor | None = None
"""The contact forces filtered between the sensor bodies and filtered bodies in world frame. """The normal contact forces filtered between the sensor bodies and filtered bodies in world frame.
Shape is (N, B, M, 3), where N is the number of sensors, B is number of bodies in each sensor Shape is (N, B, M, 3), where N is the number of sensors, B is number of bodies in each sensor
and ``M`` is the number of filtered bodies. and ``M`` is the number of filtered bodies.
......
...@@ -63,8 +63,6 @@ class SensorsSceneCfg(InteractiveSceneCfg): ...@@ -63,8 +63,6 @@ class SensorsSceneCfg(InteractiveSceneCfg):
# ground plane # ground plane
ground = TerrainImporterCfg( ground = TerrainImporterCfg(
num_envs=2048,
env_spacing=3.0,
prim_path="/World/ground", prim_path="/World/ground",
max_init_terrain_level=None, max_init_terrain_level=None,
terrain_type="generator", terrain_type="generator",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment