Skip to content

Adds Collision Group feature to Interactive Scene#5165

Open
michaellin6 wants to merge 3 commits intoisaac-sim:developfrom
michaellin6:mlin/feat/collision_group
Open

Adds Collision Group feature to Interactive Scene#5165
michaellin6 wants to merge 3 commits intoisaac-sim:developfrom
michaellin6:mlin/feat/collision_group

Conversation

@michaellin6
Copy link
Copy Markdown
Contributor

Description

Currently, there is only the option to add collision filter inter-envs, but no easy way to specify it intra-env. This PR adds such feature.

Fixes # (issue)

Type of change

  • New feature (non-breaking change which adds functionality)

Screenshots

Collision Filter Isaac Lab

Checklist

  • I have read and understood the contribution guidelines
  • I have run the pre-commit checks with ./isaaclab.sh --format
  • I have made corresponding changes to the documentation
  • 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
  • I have added my name to the CONTRIBUTORS.md or my name already exists there

@github-actions github-actions bot added documentation Improvements or additions to documentation isaac-lab Related to Isaac Lab team labels Apr 3, 2026
@michaellin6
Copy link
Copy Markdown
Contributor Author

michaellin6 commented Apr 3, 2026

run_franka_collision_groups.py
franka_collision_env_cfg_v2.py
This is an example that works with this new feature.

@michaellin6 michaellin6 requested a review from ooctipus April 3, 2026 21:41
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 3, 2026

Greptile Summary

This PR introduces CollisionGroupCfg and the collision_groups field on InteractiveSceneCfg, giving users fine-grained control over which assets can collide within the same environment. The feature is implemented via USD PhysicsCollisionGroup prims created under /World/collisions with InvertCollisionGroupFilterAttr=True (allowlist semantics), and a mutual-agreement matrix ensures that a group with collides_with=[] can never be forced into a collision by another group.

Key observations:

  • Silent filter_collisions suppression (P1): When collision_groups is configured the if/elif in __init__ silently discards filter_collisions=True (the default). No warning is emitted, so users who rely on the default are given no indication that inter-env isolation is now handled differently. A logger.warning() call when both flags are active would make this explicit.

  • Path substitution correctness (P1): Env-index path rewriting uses str.replace(env_prim_paths[0], env_prim_path), which replaces every occurrence of the env-0 prefix in the path string. If an asset's prim path happens to contain the env prefix more than once the resulting path will be wrong. A prefix-slice approach would be safer.

  • Docstring inconsistency (P2): collision_groups's docstring says it is "orthogonal to filter_collisions", implying they work independently; the tutorial correctly says it replaces filter_collisions. These two statements contradict each other.

  • Sdf.TokenListOp.Create receives a set (P2): Both global_group and per-env collision group prim specs pass a Python set literal {\"CollectionAPI:colliders\"} where an ordered list is expected.

  • The test suite covers prim structure, mutual-agreement semantics, None/empty-list semantics, and error cases, but does not include an explicit test for the filter_collisions=True + collision_groups interaction.

Confidence Score: 3/5

Merging is risky without addressing the silent filter_collisions suppression and the str.replace path substitution bug.

The core collision-group logic (matrix, USD prim creation, inter-env isolation) looks sound and is well-tested. However, two P1 issues reduce confidence: (1) the default filter_collisions=True is silently ignored with no logging, which will silently break user expectations, and (2) the str.replace-based env-path rewriting is fragile and can produce corrupt prim paths for assets whose path contains the env prefix more than once. The docstring/set issues are minor but add to the overall noise.

source/isaaclab/isaaclab/scene/interactive_scene.py — _apply_collision_groups path substitution and the filter_collisions warning gap; source/isaaclab/isaaclab/scene/interactive_scene_cfg.py — docstring wording.

Important Files Changed

Filename Overview
source/isaaclab/isaaclab/scene/interactive_scene.py Core implementation of _apply_collision_groups — creates USD PhysicsCollisionGroup prims for per-env intra-env filtering; has two logic concerns: filter_collisions silently ignored without warning, and str.replace() path substitution may corrupt paths that contain the env prefix more than once.
source/isaaclab/isaaclab/scene/interactive_scene_cfg.py Adds CollisionGroupCfg dataclass and collision_groups field to InteractiveSceneCfg; docstring says the two settings are "orthogonal" but the code makes them mutually exclusive — a minor documentation inconsistency.
source/isaaclab/test/scene/test_interactive_scene.py Five new collision-group tests covering prim creation, mutual-agreement matrix, None semantics, and invalid-reference validation; no test explicitly checks the warning/behavior when filter_collisions=True is combined with collision_groups.
source/isaaclab/isaaclab/scene/init.pyi Exports CollisionGroupCfg alongside the existing public API — straightforward and correct.
docs/source/tutorials/02_scene/create_scene.rst Adds a "Collision Groups" section to the tutorial with accurate semantics (mutual agreement, allowlist, empty-list isolation) — content is clear and consistent with the implementation.
source/isaaclab/config/extension.toml Bumps version from 4.5.25 to 4.5.26 — correct minor version bump for a new feature.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[InteractiveScene.__init__] --> B[_add_entities_from_cfg\npopulates _global_prim_paths]
    B --> C[clone_environments]
    C --> D{physx backend?}
    D -- No --> E[Done]
    D -- Yes --> F{collision_groups set?}
    F -- Yes --> G[_apply_collision_groups]
    F -- No --> H{filter_collisions=True?}
    H -- Yes --> I[filter_collisions\n_global_prim_paths]
    H -- No --> E
    G --> G1[Step 1: Collect entity prim paths\nfrom cfg.__dict__]
    G1 --> G2[Step 2: Validate assets and\ncollides_with references]
    G2 --> G3[Step 3: Build collision matrix\nmutual-agreement logic]
    G3 --> G4[Step 4: Resolve env_0 prim paths\nper group]
    G4 --> G5[Step 5: Set InvertCollisionGroupFilterAttr=True\non PhysxSceneAPI]
    G5 --> G6[Step 6: Create USD prims under\n/World/collisions via Sdf.ChangeBlock]
    G6 --> G7[Per-env PhysicsCollisionGroup\nwith filteredGroups allowlist]
    G7 --> G8{has global paths?}
    G8 -- Yes --> G9[Create global_group prim\nwire all env groups to it]
    G8 -- No --> E
    G9 --> E
Loading

Comments Outside Diff (2)

  1. source/isaaclab/isaaclab/scene/interactive_scene.py, line 322-325 (link)

    P1 str.replace() replaces ALL occurrences — env path appearing twice in an asset path will be double-substituted

    The pattern used to map from env_0 paths to per-env paths is:

    env_asset_path = env0_asset_path.replace(self.env_prim_paths[0], env_prim_path)

    str.replace() replaces every occurrence of the substring. If env_prim_paths[0] (e.g. /World/envs/env_0) happens to appear more than once in the asset prim path (e.g. /World/envs/env_0/SomeGroup/env_0_detail), the second occurrence will also be rewritten — producing a corrupt path like /World/envs/env_1/SomeGroup/env_1_detail.

    The same risk applies to the earlier env_regex_ns substitution. A safer approach is to replace only the leading prefix:

    prefix = self.env_prim_paths[0]
    env_asset_path = env_prim_path + env0_asset_path[len(prefix):]

    This only replaces the leading segment and is immune to accidental substring matches further down the path.

  2. source/isaaclab/isaaclab/scene/interactive_scene.py, line 283 (link)

    P2 Sdf.TokenListOp.Create receives a set instead of a list

    {"CollectionAPI:colliders"} is a Python set (unordered). Sdf.TokenListOp.Create expects an ordered sequence for its prependedItems argument. For a single element this is harmless today, but if a second API schema is ever appended here the order will be non-deterministic.

    The same pattern also appears on line 309.

    Also fix line 309 in the same way.

Reviews (1): Last reviewed commit: "adding documentation changes" | Re-trigger Greptile

Comment on lines +215 to +218
if self.cfg.collision_groups:
self._apply_collision_groups()
elif self.cfg.filter_collisions:
self.filter_collisions(self._global_prim_paths)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 filter_collisions=True silently ignored with no warning

When collision_groups is set, the default value of filter_collisions=True is silently swallowed by the elif. Since users almost never explicitly set filter_collisions=False, every user who enables collision_groups will hit this branch without knowing it. If filter_collisions is True and collision_groups is also set, a warning should be emitted so the behaviour is transparent:

if self.cfg.collision_groups:
    if self.cfg.filter_collisions:
        logger.warning(
            "Both 'collision_groups' and 'filter_collisions=True' are set. "
            "'filter_collisions' is ignored when 'collision_groups' is configured — "
            "inter-environment isolation is handled by _apply_collision_groups instead."
        )
    self._apply_collision_groups()
elif self.cfg.filter_collisions:
    self.filter_collisions(self._global_prim_paths)

Without this warning, users who rely on the default filter_collisions=True will get no indication that that flag has no effect.

Comment on lines +171 to +172
within the same environment can collide. This is orthogonal to :attr:`filter_collisions`,
which controls *inter*-environment collision isolation.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Docstring says "orthogonal" but code makes the two settings mutually exclusive

The phrase "orthogonal to filter_collisions" implies the two can be used independently and simultaneously, but the code in interactive_scene.py is an if/elif — when collision_groups is set, filter_collisions is silently ignored. The tutorial documentation actually says the opposite: collision_groups replaces filter_collisions. This is a contradiction that will confuse users.

Consider revising to:

Suggested change
within the same environment can collide. This is orthogonal to :attr:`filter_collisions`,
which controls *inter*-environment collision isolation.
When set, this attribute **replaces** :attr:`filter_collisions`: inter-environment collision
isolation is handled internally by the collision group logic, so :attr:`filter_collisions`
is ignored even if it is ``True`` (the default).

Copy link
Copy Markdown

@isaaclab-review-bot isaaclab-review-bot bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Isaac Lab Review Bot

Summary

This PR adds intra-environment collision filtering via named collision groups. The implementation correctly creates USD PhysicsCollisionGroup prims with allowlist semantics using InvertCollisionGroupFilter. However, there's a critical issue with unassigned assets breaking inter-env isolation, plus documentation inconsistencies that will confuse users.

Design Assessment

Problem solved: Allow fine-grained collision filtering between assets within the same environment (e.g., a phantom sensor that shouldn't collide with anything).

Is this the right design? The approach of creating per-env collision groups with allowlist semantics is sound and follows PhysX best practices. The mutual-agreement collision matrix is a good design choice. However:

  1. Alternative considered: Single "default" group for unassigned assets — The current design leaves unassigned assets outside all collision groups, which breaks inter-env isolation (critical bug). A safer design would auto-create a "default" group containing all unassigned assets, ensuring they still get inter-env isolation.

  2. Alternative considered: Keep filter_collisions for unassigned assets — Could run filter_collisions for assets not in any collision group, but this would create conflicting group semantics.

Does it follow the framework's ownership model? Yes — collision group setup belongs in InteractiveScene during scene construction, alongside the existing filter_collisions logic.

Verdict: Acceptable short-term, but needs a fix for unassigned assets. The current implementation silently breaks inter-env isolation when any asset is not assigned to a collision group. This is a footgun that will cause hard-to-debug physics issues.

Architecture Impact

  • InteractiveSceneCfg gains collision_groups field — additive, non-breaking
  • InteractiveScene.__init__ now branches between _apply_collision_groups() and filter_collisions() — changes collision setup path but maintains existing behavior when collision_groups=None
  • Scene module exports CollisionGroupCfg — additive change to public API

No cross-module callers affected since this is a new feature.

Implementation Verdict

Significant concerns — One critical issue (unassigned assets), one changelog error, and documentation inconsistencies.

Test Coverage

✅ Tests included and well-structured:

  • test_collision_groups_prim_creation — verifies USD prim structure
  • test_collision_groups_mutual_agreement — verifies allowlist logic
  • test_collision_groups_collides_with_none — verifies None vs [] semantics
  • test_collision_groups_invalid_asset_name — error handling
  • test_collision_groups_invalid_group_reference — error handling
  • test_collision_groups_none_preserves_existing_behavior — backward compat

🟡 Missing test: No test for unassigned assets scenario — should verify that assets not in any group still get inter-env isolation (or that a warning/error is raised).

CI Status

  • pre-commit: ✅ passed
  • Build Base Docker Image: ❌ failed (likely infra, not PR-related)
  • Other tests: skipped (blocked by Docker build)

Findings

🔴 Critical: interactive_scene.py — Unassigned assets break inter-env isolation
When collision_groups is set, filter_collisions is skipped entirely. Assets not assigned to any collision group are not added to any PhysicsCollisionGroup prim. With InvertCollisionGroupFilter=True, these unassigned prims collide with everything including assets in other environments — silently breaking inter-env isolation.

Fix options:

  1. Auto-create a "default" group containing all unassigned assets
  2. Raise an error if any asset with prim_path containing env_regex_ns is not in a group
  3. Emit a warning documenting the limitation

🟡 Warning: CHANGELOG.rst:17 — Documents fix not in this PR
The changelog mentions fixing CreateShaderPrimFromSdrCommand in visual_materials.py, but this file is not changed in the PR. Remove this entry or include the actual fix.

🟡 Warning: interactive_scene_cfg.py:171 — Misleading "orthogonal" claim
Docstring says collision_groups is "orthogonal to filter_collisions" but they are actually mutually exclusive — when collision_groups is set, filter_collisions is ignored.

🔵 Improvement: interactive_scene_cfg.py:47 — Clarify self-collision behavior
Docstring says collides_with=[] means "collides with nothing (fully isolated)" but the code always enables self-collision (name_a == name_b check). Should say "collides only with itself" or "isolated from other groups".

# finer-grained intra-env restrictions).
if self.cfg.collision_groups:
self._apply_collision_groups()
elif self.cfg.filter_collisions:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Critical: Unassigned assets break inter-env isolation

When collision_groups is set, filter_collisions is skipped. But assets NOT assigned to any collision group won't be in any PhysicsCollisionGroup prim. With InvertCollisionGroupFilter=True, these unassigned prims will collide with everything — including assets in other environments.

Example: Scene with robot, table, sensor where only robot and table are in collision groups. The unassigned sensor collides across all envs.

Suggested fix: After Step 2 validation, check for unassigned assets and either:

  1. Auto-create a "default" group for them
  2. Raise ValueError listing unassigned assets
  3. At minimum, log a warning
Suggested change
elif self.cfg.filter_collisions:
if self.cfg.collision_groups:
self._apply_collision_groups()
# TODO: Warn or handle assets not assigned to any collision group
elif self.cfg.filter_collisions:

Fixed
^^^^^

* Fixed ``CreateShaderPrimFromSdrCommand`` call in :func:`~isaaclab.sim.spawners.materials.visual_materials.spawn_preview_surface`
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Warning: This fix is not in this PR

The visual_materials.py file is not modified in this PR. Either:

  1. Remove this changelog entry, or
  2. Include the actual fix in this PR
Suggested change
* Fixed ``CreateShaderPrimFromSdrCommand`` call in :func:`~isaaclab.sim.spawners.materials.visual_materials.spawn_preview_surface`

belong to each group and which groups are allowed to collide with each other.

When set, the scene creates USD ``PhysicsCollisionGroup`` prims that control which assets
within the same environment can collide. This is orthogonal to :attr:`filter_collisions`,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Misleading: collision_groups and filter_collisions are mutually exclusive, not orthogonal

When collision_groups is set, filter_collisions is completely ignored (see interactive_scene.py lines 216-218). This docstring will confuse users into thinking both can be used together.

Suggested change
within the same environment can collide. This is orthogonal to :attr:`filter_collisions`,
within the same environment can collide. When set, this **replaces** :attr:`filter_collisions`
for collision filteringthe scene handles both inter-environment isolation and intra-environment
filtering through the collision groups mechanism.

"""List of collision group names that this group is allowed to collide with.

* ``None`` (default): This group collides with all other defined collision groups.
* ``[]`` (empty list): This group collides with nothing (fully isolated).
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Improvement: Clarify that self-collision is always enabled

The phrase "collides with nothing (fully isolated)" is misleading — assets within the same group always collide with each other due to the name_a == name_b check in _apply_collision_groups.

Suggested change
* ``[]`` (empty list): This group collides with nothing (fully isolated).
* ``[]`` (empty list): This group is isolated from all other groups (only collides with itself).

within the same environment can collide. This is orthogonal to :attr:`filter_collisions`,
which controls *inter*-environment collision isolation.

Assets not assigned to any collision group follow default physics behavior and collide
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Critical: This statement is incorrect

With InvertCollisionGroupFilter=True, assets not in any collision group will collide with everything — not just "everything in their environment". They bypass inter-env isolation entirely.

Suggested change
Assets not assigned to any collision group follow default physics behavior and collide
.. warning::
Assets not assigned to any collision group are not protected by inter-environment
collision filtering. They may collide with assets in other environments. Assign all
physics-enabled assets to at least one collision group when using this feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation isaac-lab Related to Isaac Lab team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant