Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,10 @@ def test_clone_visualization_builder_ignores_non_env_deformables_on_world_import
UsdGeom.Xform.Define(stage, "/World/envs/env_1")

fake_builder = _FakeShadowBuilder(body_count=1, cloth_delta=3, track_usd=True)
fake_builder.shape_collision_filter_pairs = []
fake_builder.shape_collision_group = []
fake_builder.shape_count = 0
fake_builder.add_builder = lambda _builder: None
clone_plan = SimpleNamespace(
sources=("/World/envs/env_0",),
destinations=("/World/envs/env_{}",),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Fixed
^^^^^

* Fixed PhysX-backend Newton visualization models replicating unused collision
filters and contact pairs, which could exhaust memory during ``ModelBuilder.finalize()``.
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ def build_visualization_builder_from_stage_envs(
shadow_entities, registry_groups = add_shadow_deformables_to_builder(
builder, stage, env_paths, device=device, entries=deformable_entries, clone_plan=clone_plan
)
builder.shape_collision_filter_pairs = []
builder.shape_collision_group[:] = [0] * builder.shape_count
return builder, (shadow_entities, registry_groups)

if not env_paths:
Expand Down Expand Up @@ -147,6 +149,12 @@ def build_visualization_builder_from_stage_envs(
schema_resolvers,
ignore_paths=source_deformable_ignore_paths or None,
)
global_builder = builder
builder = ModelBuilder(up_axis=up_axis) # Preserve Newton's compact empty filter store.
for visual_builder in (global_builder, *source_builders.values()):
visual_builder.shape_collision_filter_pairs = []
visual_builder.shape_collision_group[:] = [0] * visual_builder.shape_count
builder.add_builder(global_builder)
replicate_builder_mapping(builder, sources, mapping, positions, quaternions, source_builders, destinations, env_ids)
shadow_entities, registry_groups = add_shadow_deformables_to_builder(
builder, stage, env_paths, device=device, entries=deformable_entries, clone_plan=clone_plan
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from isaaclab_newton.physics import visualization_builder as visualization_builder_module
from isaaclab_newton.physics import visualization_deformables as visualization_deformables_module

from pxr import Usd, UsdGeom
from pxr import Sdf, Usd, UsdGeom, UsdPhysics

from isaaclab.cloner import ClonePlan
from isaaclab.scene_data.deformable_discovery import DeformableStageEntry
Expand All @@ -40,6 +40,8 @@
class _FakeVisualizationModelBuilder:
def __init__(self, up_axis=None):
self.up_axis = up_axis
self.shape_collision_filter_pairs = []
self.shape_collision_group = []
for attr in _VIS_BUILTIN_LABEL_ATTRS:
setattr(self, attr, [])
setattr(self, attr.replace("_label", "_world"), [])
Expand Down Expand Up @@ -80,6 +82,7 @@ def add_usd(self, stage, root_path=None, ignore_paths=None, schema_resolvers=Non
for attr in _VIS_BUILTIN_LABEL_ATTRS:
getattr(self, attr).append(f"{root_path}/{_VIS_LABEL_SUFFIXES[attr]}")
getattr(self, attr.replace("_label", "_world")).append(self._current_world or 0)
self.shape_collision_group.append(1)
self.custom_attributes["mujoco:equality_constraint_label"].values.append(
f"{root_path}/{_VIS_LABEL_SUFFIXES['equality_constraint_label']}"
)
Expand All @@ -96,6 +99,7 @@ def add_builder(self, builder, xform=None):
labels = getattr(builder, attr)
getattr(self, attr).extend(labels)
getattr(self, attr.replace("_label", "_world")).extend([self._current_world] * len(labels))
self.shape_collision_group.extend(builder.shape_collision_group)
eq_labels = builder.custom_attributes["mujoco:equality_constraint_label"].values
self.custom_attributes["mujoco:equality_constraint_label"].values.extend(eq_labels)
self.custom_attributes["mujoco:equality_constraint_world"].values.extend([self._current_world] * len(eq_labels))
Expand Down Expand Up @@ -363,6 +367,9 @@ def test_visualization_builder_imports_standalone_stage_as_one_world(self):
self._define_xform(stage, "/World")
self._define_xform(stage, "/World/Robot")
builder = mock.Mock()
builder.shape_collision_filter_pairs = []
builder.shape_collision_group = []
builder.shape_count = 0
builder.add_usd.return_value = {"path_shape_map": {}}

with (
Expand All @@ -380,6 +387,47 @@ def test_visualization_builder_imports_standalone_stage_as_one_world(self):
self.assertEqual(registry_groups, [])
builder.add_usd.assert_called_once_with(stage, schema_resolvers=["newton", "physx"], ignore_paths=None)

def test_visualization_builder_disables_collision_pairs(self):
stage = Usd.Stage.CreateInMemory()
robot_path = "/World/envs/env_0/Robot"
self._define_xform(stage, "/World")
self._define_xform(stage, "/World/envs")
self._define_xform(stage, "/World/envs/env_0")
self._define_xform(stage, "/World/envs/env_1", (2.0, 0.0, 0.0))
robot = UsdGeom.Xform.Define(stage, robot_path).GetPrim()
UsdPhysics.ArticulationRootAPI.Apply(robot)
robot.CreateAttribute("physxArticulation:enabledSelfCollisions", Sdf.ValueTypeNames.Bool).Set(False)
for name, translation in (("A", 0.0), ("B", 1.0)):
body_path = f"{robot_path}/{name}"
body = UsdGeom.Xform.Define(stage, body_path)
body.AddTranslateOp().Set((translation, 0.0, 0.0))
UsdPhysics.RigidBodyAPI.Apply(body.GetPrim())
collision = UsdGeom.Cube.Define(stage, f"{body_path}/Collision")
collision.CreateSizeAttr(0.2)
UsdPhysics.CollisionAPI.Apply(collision.GetPrim())
joint = UsdPhysics.RevoluteJoint.Define(stage, f"{robot_path}/Joint")
joint.CreateBody0Rel().SetTargets([Sdf.Path(f"{robot_path}/A")])
joint.CreateBody1Rel().SetTargets([Sdf.Path(f"{robot_path}/B")])

clone_plan = ClonePlan(
sources=(robot_path,),
destinations=("/World/envs/env_{}/Robot",),
clone_mask=torch.ones((1, 2), dtype=torch.bool),
env_ids=torch.arange(2),
)
for env_paths, plan, expected_shape_count in (
([], None, 2),
([(0, "/World/envs/env_0"), (1, "/World/envs/env_1")], clone_plan, 4),
):
builder, _shadow_metadata = visualization_builder_module.build_visualization_builder_from_stage_envs(
stage, env_paths, plan
)
model = builder.finalize(device="cpu")

self.assertEqual(model.shape_count, expected_shape_count)
self.assertEqual(len(model.shape_collision_filter_pairs), 0)
self.assertEqual(model.shape_contact_pair_count, 0)

def test_visualization_builder_rejects_clone_plan_without_environment_paths(self):
"""A cloned scene must not be cached as an incomplete single-world model."""
stage = Usd.Stage.CreateInMemory()
Expand Down
Loading