Skip to content

Commit 1c75487

Browse files
kellyguo11matthewtrepteooctipus
authored
[Backport release/3.0.0] Clear self-collision filter pairs before finalizing shadow Newton model (#7505) (#7580)
# Description Backports #7505 to `release/3.0.0` by cherry-picking the merged commit `4b9ba22508895906bb856845153b6409b5e86b3c` with `-x` provenance. The [automatic backport run](https://github.com/isaac-sim/IsaacLab/actions/runs/33862657583) encountered the expected visualization-builder conflict, but its inferred resolution dropped the release-only `rename_builder_labels` import and failed Ruff with `F821`. This manual backport preserves the source change while retaining the release branch's API sequence: it strips collision filters and groups before composition and replication, calls the release signature of `replicate_builder_mapping`, and then calls `rename_builder_labels` separately. No runtime dependencies are added. | Field | Commit | |---|---| | Original merged change | `4b9ba22508895906bb856845153b6409b5e86b3c` | | Release base used | `355dc9ba107527d7baae7600e89229a89d4e4628` | | Proposed backport | `64c6d483803bf706404236516d92c8ec77ab8211` | ## Type of change - Bug fix (non-breaking change which fixes an issue) ## Release backport - This PR already targets the active release branch. ## Validation - Repository backport candidate validation passed with all five source paths preserved and no extra paths changed. - Full repository pre-commit suite passed against `release/3.0.0`, including changelog and Git LFS checks. - `uv run --no-project python -m compileall -q` passed for all three modified Python files. - `git diff --check upstream/release/3.0.0...HEAD` passed. - Focused Newton regression tests are pending Linux CI because the repository lockfile does not support the local macOS/arm64 host. The original source PR completed 53 checks successfully. ## Checklist - [x] I have read and understood the contribution guidelines - [x] I have run the available pre-commit checks - [x] Documentation changes are not needed for this backport - [ ] My changes generate no new runtime warnings (pending Linux CI) - [x] The original regression test is preserved in the backport - [x] Changelog fragments are preserved for both touched packages - [x] The original contributor is already listed in `CONTRIBUTORS.md` Co-authored-by: matthewtrepte <mtrepte@nvidia.com> Co-authored-by: Octi Zhang <zhengyuz@nvidia.com>
1 parent 5c610f0 commit 1c75487

5 files changed

Lines changed: 66 additions & 1 deletion

File tree

source/isaaclab/changelog.d/mtrepte-shadow-model-collision-filter-oom-test.skip

Whitespace-only changes.

source/isaaclab/test/sim/test_newton_manager_visualization_state.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,10 @@ def test_clone_visualization_builder_ignores_non_env_deformables_on_world_import
826826
UsdGeom.Xform.Define(stage, "/World/envs/env_1")
827827

828828
fake_builder = _FakeShadowBuilder(body_count=1, cloth_delta=3, track_usd=True)
829+
fake_builder.shape_collision_filter_pairs = []
830+
fake_builder.shape_collision_group = []
831+
fake_builder.shape_count = 0
832+
fake_builder.add_builder = lambda _builder: None
829833
clone_plan = SimpleNamespace(
830834
sources=("/World/envs/env_0",),
831835
destinations=("/World/envs/env_{}",),
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Fixed
2+
^^^^^
3+
4+
* Fixed PhysX-backend Newton visualization models replicating unused collision
5+
filters and contact pairs, which could exhaust memory during ``ModelBuilder.finalize()``.

source/isaaclab_newton/isaaclab_newton/physics/visualization_builder.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ def build_visualization_builder_from_stage_envs(
114114
shadow_entities, registry_groups = add_shadow_deformables_to_builder(
115115
builder, stage, env_paths, device=device, entries=deformable_entries, clone_plan=clone_plan
116116
)
117+
builder.shape_collision_filter_pairs = []
118+
builder.shape_collision_group[:] = [0] * builder.shape_count
117119
return builder, (shadow_entities, registry_groups)
118120

119121
if not env_paths:
@@ -148,6 +150,12 @@ def build_visualization_builder_from_stage_envs(
148150
schema_resolvers,
149151
ignore_paths=source_deformable_ignore_paths or None,
150152
)
153+
global_builder = builder
154+
builder = ModelBuilder(up_axis=up_axis) # Preserve Newton's compact empty filter store.
155+
for visual_builder in (global_builder, *source_builders.values()):
156+
visual_builder.shape_collision_filter_pairs = []
157+
visual_builder.shape_collision_group[:] = [0] * visual_builder.shape_count
158+
builder.add_builder(global_builder)
151159
replicate_builder_mapping(builder, sources, mapping, positions, quaternions, source_builders)
152160
rename_builder_labels(builder, sources, destinations, env_ids, mapping)
153161
shadow_entities, registry_groups = add_shadow_deformables_to_builder(

source/isaaclab_newton/test/cloner/test_rename_builder_labels.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from isaaclab_newton.physics import visualization_deformables as visualization_deformables_module
2222
from newton.solvers import SolverMuJoCo
2323

24-
from pxr import Usd, UsdGeom
24+
from pxr import Sdf, Usd, UsdGeom, UsdPhysics
2525

2626
from isaaclab.cloner import ClonePlan
2727
from isaaclab.scene_data.deformable_discovery import DeformableStageEntry
@@ -46,6 +46,8 @@
4646
class _FakeVisualizationModelBuilder:
4747
def __init__(self, up_axis=None):
4848
self.up_axis = up_axis
49+
self.shape_collision_filter_pairs = []
50+
self.shape_collision_group = []
4951
for attr in _VIS_BUILTIN_LABEL_ATTRS:
5052
setattr(self, attr, [])
5153
setattr(self, attr.replace("_label", "_world"), [])
@@ -86,6 +88,7 @@ def add_usd(self, stage, root_path=None, ignore_paths=None, schema_resolvers=Non
8688
for attr in _VIS_BUILTIN_LABEL_ATTRS:
8789
getattr(self, attr).append(f"{root_path}/{_VIS_LABEL_SUFFIXES[attr]}")
8890
getattr(self, attr.replace("_label", "_world")).append(self._current_world or 0)
91+
self.shape_collision_group.append(1)
8992
self.custom_attributes["mujoco:equality_constraint_label"].values.append(
9093
f"{root_path}/{_VIS_LABEL_SUFFIXES['equality_constraint_label']}"
9194
)
@@ -102,6 +105,7 @@ def add_builder(self, builder, xform=None):
102105
labels = getattr(builder, attr)
103106
getattr(self, attr).extend(labels)
104107
getattr(self, attr.replace("_label", "_world")).extend([self._current_world] * len(labels))
108+
self.shape_collision_group.extend(builder.shape_collision_group)
105109
eq_labels = builder.custom_attributes["mujoco:equality_constraint_label"].values
106110
self.custom_attributes["mujoco:equality_constraint_label"].values.extend(eq_labels)
107111
self.custom_attributes["mujoco:equality_constraint_world"].values.extend([self._current_world] * len(eq_labels))
@@ -473,6 +477,9 @@ def test_visualization_builder_imports_standalone_stage_as_one_world(self):
473477
self._define_xform(stage, "/World")
474478
self._define_xform(stage, "/World/Robot")
475479
builder = mock.Mock()
480+
builder.shape_collision_filter_pairs = []
481+
builder.shape_collision_group = []
482+
builder.shape_count = 0
476483
builder.add_usd.return_value = {"path_shape_map": {}}
477484

478485
with (
@@ -490,6 +497,47 @@ def test_visualization_builder_imports_standalone_stage_as_one_world(self):
490497
self.assertEqual(registry_groups, [])
491498
builder.add_usd.assert_called_once_with(stage, schema_resolvers=["newton", "physx"], ignore_paths=None)
492499

500+
def test_visualization_builder_disables_collision_pairs(self):
501+
stage = Usd.Stage.CreateInMemory()
502+
robot_path = "/World/envs/env_0/Robot"
503+
self._define_xform(stage, "/World")
504+
self._define_xform(stage, "/World/envs")
505+
self._define_xform(stage, "/World/envs/env_0")
506+
self._define_xform(stage, "/World/envs/env_1", (2.0, 0.0, 0.0))
507+
robot = UsdGeom.Xform.Define(stage, robot_path).GetPrim()
508+
UsdPhysics.ArticulationRootAPI.Apply(robot)
509+
robot.CreateAttribute("physxArticulation:enabledSelfCollisions", Sdf.ValueTypeNames.Bool).Set(False)
510+
for name, translation in (("A", 0.0), ("B", 1.0)):
511+
body_path = f"{robot_path}/{name}"
512+
body = UsdGeom.Xform.Define(stage, body_path)
513+
body.AddTranslateOp().Set((translation, 0.0, 0.0))
514+
UsdPhysics.RigidBodyAPI.Apply(body.GetPrim())
515+
collision = UsdGeom.Cube.Define(stage, f"{body_path}/Collision")
516+
collision.CreateSizeAttr(0.2)
517+
UsdPhysics.CollisionAPI.Apply(collision.GetPrim())
518+
joint = UsdPhysics.RevoluteJoint.Define(stage, f"{robot_path}/Joint")
519+
joint.CreateBody0Rel().SetTargets([Sdf.Path(f"{robot_path}/A")])
520+
joint.CreateBody1Rel().SetTargets([Sdf.Path(f"{robot_path}/B")])
521+
522+
clone_plan = ClonePlan(
523+
sources=(robot_path,),
524+
destinations=("/World/envs/env_{}/Robot",),
525+
clone_mask=torch.ones((1, 2), dtype=torch.bool),
526+
env_ids=torch.arange(2),
527+
)
528+
for env_paths, plan, expected_shape_count in (
529+
([], None, 2),
530+
([(0, "/World/envs/env_0"), (1, "/World/envs/env_1")], clone_plan, 4),
531+
):
532+
builder, _shadow_metadata = visualization_builder_module.build_visualization_builder_from_stage_envs(
533+
stage, env_paths, plan
534+
)
535+
model = builder.finalize(device="cpu")
536+
537+
self.assertEqual(model.shape_count, expected_shape_count)
538+
self.assertEqual(len(model.shape_collision_filter_pairs), 0)
539+
self.assertEqual(model.shape_contact_pair_count, 0)
540+
493541
def test_visualization_builder_rejects_clone_plan_without_environment_paths(self):
494542
"""A cloned scene must not be cached as an incomplete single-world model."""
495543
stage = Usd.Stage.CreateInMemory()

0 commit comments

Comments
 (0)