Repository: isaac-sim/IsaacLab (Python)

Issue:
[Question] Filtering contacts between bodies
### Question

Currently, the contact sensor works well for checking the contact forces on various links using a prim path expression in the form of regex. For example, 

`ContactSensorCfg(prim_path="{ENV_REGEX_NS}/Robot/.*", history_length=3, track_air_time=True)
`

Is it possible to filter out the collision/contacts of the robot with only certain objects in the environment? Currently, there exists an argument for the `ContactSensorCfg` called `filter_prim_paths_expr` which is supposed to do this, but when I pass expressions such as `/World/envs/env_.*/Object/link_.*`, it throws out the error:

`[Error] [omni.physx.tensors.plugin] Filter pattern '/World/envs/env_.*/Object/link_.*' did not match the correct number of entries`.

The Contact Sensor uses the [RigidContactView](https://docs.omniverse.nvidia.com/py/isaacsim/source/extensions/omni.isaac.core/docs/index.html#omni.isaac.core.prims.RigidContactView) class from `omni.isaac.core` but the example in the documentation only shows contact filtering with one environment. Using a regex expression instead in the same example doesn't seem to work and returns a similar error as above.

Since the documentation is lacking for this use case, can the authors provide any guide to perform filtering contacts using regex expressions?
 
Thanks in advance :) 


Discussion: Unfortunately, the filtering reports work properly between one rigid body prim to many filter prim paths.

So instead of providing: `"{ENV_REGEX_NS}/Robot/.*"`, you'll need to provide a specific body. For instance, `"{ENV_REGEX_NS}/Robot/RH_FOOT"`. I think the same holds for the filter prim paths (where you'd need to manually list them instead of having `link_.*`). But you'll have to check that up.

Let me know if this fixes the issue for you!
Using "{ENV_REGEX_NS}/Robot/.*" for the prim path works, as explicated in the [Orbit Quadruped examples ](https://github.com/NVIDIA-Omniverse/orbit/blob/f71340ebbe10cdbf7aa50735329ebebdc97e3e97/source/extensions/omni.isaac.orbit_tasks/omni/isaac/orbit_tasks/locomotion/velocity/velocity_env_cfg.py#L72) where it is being used to detect [foot](https://github.com/NVIDIA-Omniverse/orbit/blob/f71340ebbe10cdbf7aa50735329ebebdc97e3e97/source/extensions/omni.isaac.orbit_tasks/omni/isaac/orbit_tasks/locomotion/velocity/velocity_env_cfg.py#L237) and [base](https://github.com/NVIDIA-Omniverse/orbit/blob/f71340ebbe10cdbf7aa50735329ebebdc97e3e97/source/extensions/omni.isaac.orbit_tasks/omni/isaac/orbit_tasks/locomotion/velocity/velocity_env_cfg.py#L259) contacts with the ground . But unfortunately, it is when using a similar expression in the filter prim path, the error pops up. 
Yes, but can you make a separate contact sensor for each part of the robot and see if filtering works then?

```python
lh_foot_contact_forces = ContactSensorCfg(prim_path="{ENV_REGEX_NS}/Robot/LH_FOOT", history_length=3, track_air_time=True, filter_prim_paths=["/World/envs/env_.*/Object/link_.*])
base_contact_forces = ContactSensorCfg(prim_path="{ENV_REGEX_NS}/Robot/base", history_length=3, track_air_time=True, filter_prim_paths=["/World/envs/env_.*/Object/link_.*])
```
Unfortunately not, it still throws up the error for `num_envs = 2`:

```
[Error] [omni.physx.tensors.plugin] Filter pattern '/World/envs/env_.*/Object/link_.*' did not match the correct number of entries (expected 2, found 0)
```
even though it clearly exists.

The contact filtering works by explicitly adding every link of every environment as below. However, this is highly redundant and probably makes it slower.

```python
filter_prim_paths=[f"/World/envs/env_{i}/Object/link{j}" for i in range(num_envs) for j in range(num_links)]
```

> The contact filtering works by explicitly adding every link of every environment as below. However, this is highly redundant and probably makes it slower.

It shouldn't make it slower since regex expressions are resolved internally by PhysX in the same way. If you only care about interaction with particular links of the object (i.e. you know all of them don't make sense), you should remove them.
Maybe we can fix the documentation on this to avoid confusion for future?
It seems to me that filtering doesn't work with regex expressions. So `filter_prim_paths_expr` should probably be renamed to `filter_prim_paths`. Furthermore, since we define `contact_sensor` in the config file, it's not straightforward to infer `num_envs`.  In my case, I hardcoded it to test it. Is there a better way to do so?
Ah wait. It doesn't work for `/World/envs/env_*/Object/link0`?
@shafeef901 I think I found the bug. Can you check the MR: https://github.com/NVIDIA-Omniverse/orbit/pull/385?



Fix (patch):
diff --git a/source/extensions/omni.isaac.orbit/config/extension.toml b/source/extensions/omni.isaac.orbit/config/extension.toml
index c54650f8f8c..cbac53220b0 100644
--- a/source/extensions/omni.isaac.orbit/config/extension.toml
+++ b/source/extensions/omni.isaac.orbit/config/extension.toml
@@ -1,7 +1,7 @@
 [package]
 
 # Note: Semantic Versioning is used: https://semver.org/
-version = "0.16.1"
+version = "0.16.2"
 
 # Description
 title = "ORBIT framework for Robot Learning"
diff --git a/source/extensions/omni.isaac.orbit/docs/CHANGELOG.rst b/source/extensions/omni.isaac.orbit/docs/CHANGELOG.rst
index 73b2e5850ef..a3a2a98c50b 100644
--- a/source/extensions/omni.isaac.orbit/docs/CHANGELOG.rst
+++ b/source/extensions/omni.isaac.orbit/docs/CHANGELOG.rst
@@ -1,6 +1,17 @@
 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)
 ~~~~~~~~~~~~~~~~~~~
 
diff --git a/source/extensions/omni.isaac.orbit/omni/isaac/orbit/scene/interactive_scene.py b/source/extensions/omni.isaac.orbit/omni/isaac/orbit/scene/interactive_scene.py
index feb39dbfe78..700a4845900 100644
--- a/source/extensions/omni.isaac.orbit/omni/isaac/orbit/scene/interactive_scene.py
+++ b/source/extensions/omni.isaac.orbit/omni/isaac/orbit/scene/interactive_scene.py
@@ -16,7 +16,7 @@
 
 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 @@ def _add_entities_from_cfg(self):
                         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):
diff --git a/source/extensions/omni.isaac.orbit/omni/isaac/orbit/sensors/contact_sensor/contact_sensor.py b/source/extensions/omni.isaac.orbit/omni/isaac/orbit/sensors/contact_sensor/contact_sensor.py
index 8a13203e7ed..35a2293a687 100644
--- a/source/extensions/omni.isaac.orbit/omni/isaac/orbit/sensors/contact_sensor/contact_sensor.py
+++ b/source/extensions/omni.isaac.orbit/omni/isaac/orbit/sensors/contact_sensor/contact_sensor.py
@@ -245,14 +245,18 @@ def _initialize_impl(self):
                 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
diff --git a/source/extensions/omni.isaac.orbit/omni/isaac/orbit/sim/simulation_cfg.py b/source/extensions/omni.isaac.orbit/omni/isaac/orbit/sim/simulation_cfg.py
index 994f7dcf9a6..d8d331508db 100644
--- a/source/extensions/omni.isaac.orbit/omni/isaac/orbit/sim/simulation_cfg.py
+++ b/source/extensions/omni.isaac.orbit/omni/isaac/orbit/sim/simulation_cfg.py
@@ -20,8 +20,8 @@
 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
diff --git a/source/extensions/omni.isaac.orbit/omni/isaac/orbit/sim/simulation_context.py b/source/extensions/omni.isaac.orbit/omni/isaac/orbit/sim/simulation_context.py
index 8e89ab5da1d..096a3132582 100644
--- a/source/extensions/omni.isaac.orbit/omni/isaac/orbit/sim/simulation_context.py
+++ b/source/extensions/omni.isaac.orbit/omni/isaac/orbit/sim/simulation_context.py
@@ -19,7 +19,7 @@
 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 @@ def clear_instance(cls):
     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.")