Skip to content

Commit 64c6d48

Browse files
matthewtrepteooctipus
authored andcommitted
Clear self-collision filter pairs before finalizing shadow Newton model (isaac-sim#7505)
- The PhysX-backend shadow Newton visualization model never runs collision detection, but USD-authored self-collision filter pairs (`physxArticulation:enabledSelfCollisions`) were still imported into it and replicated across every cloned env. - At real training env counts, this filter-pair set could reach billions of entries, causing `ModelBuilder.finalize()` to run out of memory. - Clears `builder.shape_collision_filter_pairs` before finalizing the shadow model since it has no use for them. - [x] `uv run python -m pytest source/isaaclab/test/sim/test_newton_manager_visualization_state.py` — 23 passed, including new regression test `test_ensure_visualization_model_clears_shape_collision_filter_pairs_before_finalize` - [x] `uv run python -m pytest source/isaaclab/test/sim/test_simulation_context_visualizers.py source/isaaclab_newton/test/physics/test_newton_manager_abstraction.py` — 200 passed - [x] `uv run isaaclab -f` - [x] <!-- backport-active-release --> Backport this pull request to the active release branch after it merges into `develop` --------- Co-authored-by: Octi Zhang <zhengyuz@nvidia.com> (cherry picked from commit 4b9ba22)
1 parent 355dc9b commit 64c6d48

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)