Commit 17d781ce authored by Mayank Mittal's avatar Mayank Mittal Committed by Mayank Mittal

Fixes parsing of filter prim path expressions in ContactSensor (#385)

PhysX view classes expect the expressions to be in "Glob" format instead
of "Regex". While we did this conversion for the body views, it was
missing for the filter prim paths expressions. This MR fixes this issue.

Fixes https://github.com/NVIDIA-Omniverse/orbit/issues/364

- Bug fix (non-breaking change which fixes an issue)

- [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./orbit.sh --format`
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] I have run all the tests with `./orbit.sh --test` and they pass
- [x] 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 440c57b4
[package]
# Note: Semantic Versioning is used: https://semver.org/
version = "0.16.1"
version = "0.16.2"
# Description
title = "ORBIT framework for Robot Learning"
......
Changelog
---------
0.16.2 (2024-04-26)
~~~~~~~~~~~~~~~~~~~
Fixed
^^^^^
* Fixed parsing of filter prim path expressions in the :class:`omni.isaac.orbit.sensors.ContactSensor` class.
Earlier, the filter prim paths given to the physics view was not being parsed since they were specified as
regex expressions instead of glob expressions.
0.16.1 (2024-04-20)
~~~~~~~~~~~~~~~~~~~
......
......@@ -16,7 +16,7 @@ from pxr import PhysxSchema
import omni.isaac.orbit.sim as sim_utils
from omni.isaac.orbit.assets import Articulation, ArticulationCfg, AssetBaseCfg, RigidObject, RigidObjectCfg
from omni.isaac.orbit.sensors import FrameTransformerCfg, SensorBase, SensorBaseCfg
from omni.isaac.orbit.sensors import ContactSensorCfg, FrameTransformerCfg, SensorBase, SensorBaseCfg
from omni.isaac.orbit.terrains import TerrainImporter, TerrainImporterCfg
from .interactive_scene_cfg import InteractiveSceneCfg
......@@ -348,6 +348,11 @@ class InteractiveScene:
target_frame.prim_path = target_frame.prim_path.format(ENV_REGEX_NS=self.env_regex_ns)
updated_target_frames.append(target_frame)
asset_cfg.target_frames = updated_target_frames
elif isinstance(asset_cfg, ContactSensorCfg):
updated_filter_prim_paths_expr = []
for filter_prim_path in asset_cfg.filter_prim_paths_expr:
updated_filter_prim_paths_expr.append(filter_prim_path.format(ENV_REGEX_NS=self.env_regex_ns))
asset_cfg.filter_prim_paths_expr = updated_filter_prim_paths_expr
self._sensors[asset_name] = asset_cfg.class_type(asset_cfg)
elif isinstance(asset_cfg, AssetBaseCfg):
......
......@@ -245,14 +245,18 @@ class ContactSensor(SensorBase):
f"Sensor at path '{self.cfg.prim_path}' could not find any bodies with contact reporter API."
"\nHINT: Make sure to enable 'activate_contact_sensors' in the corresponding asset spawn configuration."
)
# construct regex expression for the body names
body_names_regex = r"(" + "|".join(body_names) + r")"
body_names_regex = f"{self.cfg.prim_path.rsplit('/', 1)[0]}/{body_names_regex}"
# construct a new regex expression
# convert regex expressions to glob expressions for PhysX
body_names_glob = body_names_regex.replace(".*", "*")
filter_prim_paths_glob = [expr.replace(".*", "*") for expr in self.cfg.filter_prim_paths_expr]
# create a rigid prim view for the sensor
self._body_physx_view = self._physics_sim_view.create_rigid_body_view(body_names_regex.replace(".*", "*"))
self._body_physx_view = self._physics_sim_view.create_rigid_body_view(body_names_glob)
self._contact_physx_view = self._physics_sim_view.create_rigid_contact_view(
body_names_regex.replace(".*", "*"), filter_patterns=self.cfg.filter_prim_paths_expr
body_names_glob, filter_patterns=filter_prim_paths_glob
)
# resolve the true count of bodies
self._num_bodies = self.body_physx_view.count // self._num_envs
......
......@@ -31,19 +31,24 @@ class ContactSensorCfg(SensorBaseCfg):
"""
filter_prim_paths_expr: list[str] = list()
"""The list of primitive paths to filter contacts with.
"""The list of primitive paths (or expressions) to filter contacts with. Defaults to an empty list, in which case
no filtering is applied.
For example, if you want to filter contacts with the ground plane, you can set this to
``["/World/ground_plane"]``. In this case, the contact sensor will only report contacts
with the ground plane while using the :meth:`omni.isaac.core.prims.RigidContactView.get_contact_force_matrix`
method.
The contact sensor allows reporting contacts between the primitive specified with :attr:`prim_path` and
other primitives in the scene. For instance, in a scene containing a robot, a ground plane and an object,
you can obtain individual contact reports of the base of the robot with the ground plane and the object.
.. note::
The expression in the list can contain the environment namespace regex ``{ENV_REGEX_NS}`` which
will be replaced with the environment namespace.
Example: ``{ENV_REGEX_NS}/Object`` will be replaced with ``/World/envs/env_.*/Object``.
If an empty list is provided, then only net contact forces are reported.
"""
visualizer_cfg: VisualizationMarkersCfg = CONTACT_SENSOR_MARKER_CFG.replace(prim_path="/Visuals/ContactSensor")
"""The configuration object for the visualization markers. Defaults to CONTACT_SENSOR_MARKER_CFG.
Note:
.. note::
This attribute is only used when debug visualization is enabled.
"""
......@@ -5,7 +5,7 @@
"""Base class for sensors.
This class defines an interface for sensors similar to how the :class:`omni.isaac.orbit.robot.robot_base.RobotBase` class works.
This class defines an interface for sensors similar to how the :class:`omni.isaac.orbit.assets.AssetBase` class works.
Each sensor class should inherit from this class and implement the abstract methods.
"""
......
......@@ -20,8 +20,8 @@ from .spawners.materials import RigidBodyMaterialCfg
class PhysxCfg:
"""Configuration for PhysX solver-related parameters.
These parameters are used to configure the PhysX solver. For more information, see the PhysX 5 SDK
documentation.
These parameters are used to configure the PhysX solver. For more information, see the `PhysX 5 SDK
documentation`_.
PhysX 5 supports GPU-accelerated physics simulation. This is enabled by default, but can be disabled
through the flag `use_gpu`. Unlike CPU PhysX, the GPU simulation feature is not able to dynamically
......@@ -29,8 +29,8 @@ class PhysxCfg:
for GPU features. If insufficient buffer sizes are provided, the simulation will fail with errors and
lead to adverse behaviors. The buffer sizes can be adjusted through the `gpu_*` parameters.
References:
* PhysX 5 documentation: https://nvidia-omniverse.github.io/PhysX/
.. _PhysX 5 SDK documentation: https://nvidia-omniverse.github.io/PhysX/physx/5.3.1/_api_build/class_px_scene_desc.html
"""
use_gpu: bool = True
......
......@@ -19,7 +19,7 @@ import omni.physx
from omni.isaac.core.simulation_context import SimulationContext as _SimulationContext
from omni.isaac.core.utils.viewports import set_camera_view
from omni.isaac.version import get_version
from pxr import Gf, Usd
from pxr import Gf, PhysxSchema, Usd, UsdPhysics
from .simulation_cfg import SimulationCfg
from .spawners import DomeLightCfg, GroundPlaneCfg
......@@ -36,7 +36,7 @@ class SimulationContext(_SimulationContext):
* playing, pausing, stepping and stopping the simulation
* adding and removing callbacks to different simulation events such as physics stepping, rendering, etc.
This class inherits from the `omni.isaac.core.simulation_context.SimulationContext`_ class and
This class inherits from the :class:`omni.isaac.core.simulation_context.SimulationContext` class and
adds additional functionalities such as setting up the simulation context with a configuration object,
exposing other commonly used simulator-related functions, and performing version checks of Isaac Sim
to ensure compatibility between releases.
......@@ -65,8 +65,6 @@ class SimulationContext(_SimulationContext):
Based on above, for most functions in this class there is an equivalent function that is suffixed
with ``_async``. The ``_async`` functions are used in the Omniverse extension mode and
the non-``_async`` functions are used in the standalone python script mode.
.. _omni.isaac.core.simulation_context.SimulationContext: https://docs.omniverse.nvidia.com/py/isaacsim/source/extensions/omni.isaac.core/docs/index.html#module-omni.isaac.core.simulation_context
"""
class RenderMode(enum.IntEnum):
......@@ -503,8 +501,8 @@ class SimulationContext(_SimulationContext):
def _set_additional_physx_params(self):
"""Sets additional PhysX parameters that are not directly supported by the parent class."""
# obtain the physics scene api
physics_scene = self._physics_context._physics_scene # pyright: ignore [reportPrivateUsage]
physx_scene_api = self._physics_context._physx_scene_api # pyright: ignore [reportPrivateUsage]
physics_scene: UsdPhysics.Scene = self._physics_context._physics_scene
physx_scene_api: PhysxSchema.PhysxSceneAPI = self._physics_context._physx_scene_api
# assert that scene api is not None
if physx_scene_api is None:
raise RuntimeError("Physics scene API is None! Please create the scene first.")
......
......@@ -70,6 +70,18 @@ class ContactSensorSceneCfg(InteractiveSceneCfg):
contact_sensor: ContactSensorCfg = MISSING
"""Contact sensor configuration."""
shape_2: TestContactSensorRigidObjectCfg = None
"""RigidObject contact prim configuration. Defaults to None, i.e. not included in the scene.
This is a second prim used for testing contact filtering.
"""
contact_sensor_2: ContactSensorCfg = None
"""Contact sensor configuration. Defaults to None, i.e. not included in the scene.
This is a second contact sensor used for testing contact filtering.
"""
##
# Scene entity configurations.
......@@ -223,6 +235,67 @@ class TestContactSensor(unittest.TestCase):
"""Checks contact sensor values for contact time and air time for a sphere collision primitive."""
self._run_contact_sensor_test(shape_cfg=SPHERE_CFG)
def test_cube_stack_contact_filtering(self):
"""Checks contact sensor reporting for filtering stacked cube prims."""
for device in self.devices:
for num_envs in [1, 6, 24]:
with self.subTest(device=device, num_envs=num_envs):
with build_simulation_context(device=device, dt=self.sim_dt, add_lighting=True) as sim:
# Instance new scene for the current terrain and contact prim.
scene_cfg = ContactSensorSceneCfg(num_envs=num_envs, env_spacing=1.0, lazy_sensor_update=False)
scene_cfg.terrain = FLAT_TERRAIN_CFG.replace(prim_path="/World/ground")
# -- cube 1
scene_cfg.shape = CUBE_CFG.replace(prim_path="{ENV_REGEX_NS}/Cube_1")
scene_cfg.shape.init_state.pos = (0, -1.0, 1.0)
# -- cube 2 (on top of cube 1)
scene_cfg.shape_2 = CUBE_CFG.replace(prim_path="{ENV_REGEX_NS}/Cube_2")
scene_cfg.shape_2.init_state.pos = (0, -1.0, 1.525)
# -- contact sensor 1
scene_cfg.contact_sensor = ContactSensorCfg(
prim_path="{ENV_REGEX_NS}/Cube_1",
track_pose=True,
debug_vis=False,
update_period=0.0,
filter_prim_paths_expr=["{ENV_REGEX_NS}/Cube_2"],
)
# -- contact sensor 2
scene_cfg.contact_sensor_2 = ContactSensorCfg(
prim_path="{ENV_REGEX_NS}/Cube_2",
track_pose=True,
debug_vis=False,
update_period=0.0,
filter_prim_paths_expr=["{ENV_REGEX_NS}/Cube_1"],
)
scene = InteractiveScene(scene_cfg)
# Set variables internally for reference
self.sim = sim
self.scene = scene
# Play the simulation
self.sim.reset()
# Extract from scene for type hinting
contact_sensor: ContactSensor = self.scene["contact_sensor"]
contact_sensor_2: ContactSensor = self.scene["contact_sensor_2"]
# Check buffers have the right size
self.assertEqual(contact_sensor.contact_physx_view.filter_count, 1)
self.assertEqual(contact_sensor_2.contact_physx_view.filter_count, 1)
# Reset the contact sensors
self.scene.reset()
# Let the scene come to a rest
for _ in range(20):
self._perform_sim_step()
# Check values for cube 2
torch.testing.assert_close(
contact_sensor_2.data.force_matrix_w[:, :, 0], contact_sensor_2.data.net_forces_w
)
torch.testing.assert_close(
contact_sensor_2.data.force_matrix_w[:, :, 0], contact_sensor.data.force_matrix_w[:, :, 0]
)
"""
Internal helpers.
"""
......@@ -289,7 +362,7 @@ class TestContactSensor(unittest.TestCase):
sensor: The sensor reporting data to be verified by the contact sensor test.
mode: The contact test mode: either contact with ground plane or air time.
"""
# reset tge test state
# reset the test state
sensor.reset()
expected_last_test_contact_time = 0
expected_last_reset_contact_time = 0
......
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