diff --git a/src/unilab/base/backend_factory.py b/src/unilab/base/backend_factory.py
index dcfb6fa07..cdacaacb5 100644
--- a/src/unilab/base/backend_factory.py
+++ b/src/unilab/base/backend_factory.py
@@ -91,6 +91,8 @@ def env_backend_kwargs(cfg: "EnvCfg") -> dict[str, Any]:
# constructor never see an unknown keyword on the legacy default path.
if cfg.isaacsim_tensor_cuda_ipc:
result["isaacsim_tensor_cuda_ipc"] = True
+ if cfg.isaacsim_share_friction_materials:
+ result["share_friction_materials"] = True
# Forward the explicit Genesis device id only when a rank selected one;
# when absent, unisim-core's factory default applies and Genesis picks
# its own device.
diff --git a/src/unilab/base/base.py b/src/unilab/base/base.py
index db03f4404..ee86c2074 100644
--- a/src/unilab/base/base.py
+++ b/src/unilab/base/base.py
@@ -107,6 +107,11 @@ class EnvCfg:
# remains false so legacy NumPy shared-memory behavior is unchanged until a
# task owner explicitly negotiates the candidate tensor contract.
isaacsim_tensor_cuda_ipc: bool = False
+ # Opt-in PhysX material sharing for large scenes. Equal initial sliding
+ # friction values share a material and reset-time friction DR is disabled
+ # at capability negotiation. Keep this false when complete per-geom/per-env
+ # friction DR is required.
+ isaacsim_share_friction_materials: bool = False
# ``isaacsim`` PhysX solver overrides, forwarded to UniSim's bounded,
# readback-validated PhysxSolverConfig (unilabsim/unisim#251, #259).
# ``None`` keeps the PhysX scene defaults; the backend validates each
@@ -287,6 +292,11 @@ def validate(self):
raise ValueError(
f"isaacsim_tensor_cuda_ipc must be a boolean, got {self.isaacsim_tensor_cuda_ipc!r}"
)
+ if not isinstance(self.isaacsim_share_friction_materials, bool):
+ raise ValueError(
+ "isaacsim_share_friction_materials must be a boolean, got "
+ f"{self.isaacsim_share_friction_materials!r}"
+ )
# PhysX accepts zero velocity iterations (its scene minimum is 0) but
# requires at least one position iteration; mirror UniSim's
# PhysxSolverConfig bounds here so owner configs fail fast.
diff --git a/tests/base/test_isaacsim_backend.py b/tests/base/test_isaacsim_backend.py
index a2da8289c..ab2062ae1 100644
--- a/tests/base/test_isaacsim_backend.py
+++ b/tests/base/test_isaacsim_backend.py
@@ -148,6 +148,7 @@ def test_factory_routes_isaacsim_without_importing_kit(scene_file: str) -> None:
({"isaacsim_render_width": 0}, "isaacsim_render_width"),
({"isaacsim_render_height": True}, "isaacsim_render_height"),
({"isaacsim_tensor_cuda_ipc": 1}, "isaacsim_tensor_cuda_ipc"),
+ ({"isaacsim_share_friction_materials": 1}, "isaacsim_share_friction_materials"),
],
)
def test_env_cfg_rejects_invalid_isaacsim_render_settings(
@@ -219,6 +220,15 @@ def test_env_backend_kwargs_forwards_isaacsim_tensor_cuda_ipc() -> None:
assert kwargs["isaacsim_tensor_cuda_ipc"] is True
+def test_env_backend_kwargs_forwards_isaacsim_material_sharing() -> None:
+ """Material sharing is an explicit benchmark/task opt-in, not a default."""
+ from unilab.base.backend_factory import env_backend_kwargs
+
+ assert "share_friction_materials" not in env_backend_kwargs(EnvCfg())
+ kwargs = env_backend_kwargs(EnvCfg(isaacsim_share_friction_materials=True))
+ assert kwargs["share_friction_materials"] is True
+
+
def test_create_backend_rejects_legacy_isaacsim_tensor_cuda_ipc_runtime(
monkeypatch: pytest.MonkeyPatch,
) -> None:
diff --git a/tests/config/test_g1_motion_tracking_owners.py b/tests/config/test_g1_motion_tracking_owners.py
index bdb3bb5e6..41fddcd9d 100644
--- a/tests/config/test_g1_motion_tracking_owners.py
+++ b/tests/config/test_g1_motion_tracking_owners.py
@@ -210,6 +210,7 @@ def test_flashsac_g1_motion_tracking_isaacsim_opts_into_cuda_ipc_candidate() ->
assert cfg.training.sim_backend == "isaacsim"
assert cfg.env.tensor_runtime is True
assert cfg.env.isaacsim_tensor_cuda_ipc is True
+ assert cfg.env.isaacsim_share_friction_materials is True
scene = cfg.env.scene
assert scene.model_file is None
@@ -269,6 +270,7 @@ def test_isaacsim_tensor_fixture_materializes_into_manager_config() -> None:
cfg.validate()
assert cfg.isaacsim_tensor_cuda_ipc is True
+ assert cfg.isaacsim_share_friction_materials is True
assert cfg.tensor_runtime is True
assert cfg.scene is not None
assert cfg.scene.entity_assets
diff --git a/tests/fixtures/isaacsim_g1_tensor_cuda_ipc/flat_floor_entity.xml b/tests/fixtures/isaacsim_g1_tensor_cuda_ipc/flat_floor_entity.xml
index f87cc1603..ea2b7f014 100644
--- a/tests/fixtures/isaacsim_g1_tensor_cuda_ipc/flat_floor_entity.xml
+++ b/tests/fixtures/isaacsim_g1_tensor_cuda_ipc/flat_floor_entity.xml
@@ -2,7 +2,9 @@
-
+
+
diff --git a/tests/fixtures/isaacsim_g1_tensor_cuda_ipc/isaacsim_candidate_overlay.yaml b/tests/fixtures/isaacsim_g1_tensor_cuda_ipc/isaacsim_candidate_overlay.yaml
index 199dcf2ff..52f2cf182 100644
--- a/tests/fixtures/isaacsim_g1_tensor_cuda_ipc/isaacsim_candidate_overlay.yaml
+++ b/tests/fixtures/isaacsim_g1_tensor_cuda_ipc/isaacsim_candidate_overlay.yaml
@@ -14,8 +14,11 @@ training:
env:
tensor_runtime: true
isaacsim_device_id: 0
- isaacsim_worker_timeout_s: 120.0
+ isaacsim_worker_timeout_s: 300.0
isaacsim_tensor_cuda_ipc: true
+ # This throughput fixture does not use geom-friction reset DR. Sharing equal
+ # initial native materials keeps 2048 clones below PhysX's 64K material cap.
+ isaacsim_share_friction_materials: true
scene:
# IsaacSim consumes the mapped physical-scene profile. The task source,
# not an all-zero EntityInitialState, remains authoritative for the 29
diff --git a/tests/scripts/test_g1_flashsac_backend.py b/tests/scripts/test_g1_flashsac_backend.py
index ef0ec390a..779e3bda6 100644
--- a/tests/scripts/test_g1_flashsac_backend.py
+++ b/tests/scripts/test_g1_flashsac_backend.py
@@ -116,6 +116,8 @@ def fake_create_backend(
assert captured["backend_type"] == backend
assert captured["base_name"] == expected
+ if backend == "isaacsim":
+ assert captured["share_friction_materials"] is True
def test_build_backend_binds_newton_process_device_before_construction(