diff --git a/source/isaaclab_ov/changelog.d/revert-unmerged-ovrtx-instance-cloning.rst b/source/isaaclab_ov/changelog.d/revert-unmerged-ovrtx-instance-cloning.rst new file mode 100644 index 000000000000..6a5528be4047 --- /dev/null +++ b/source/isaaclab_ov/changelog.d/revert-unmerged-ovrtx-instance-cloning.rst @@ -0,0 +1,4 @@ +Changed +^^^^^^^ + +* Reverted the release-only legacy OVRTX instance-cloning changes because the corresponding change has not merged into ``develop``. diff --git a/source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py b/source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py index d7cdd9b28619..641aa4af78a9 100644 --- a/source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py +++ b/source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py @@ -440,15 +440,12 @@ def prepare_stage(self, stage: Any, num_envs: int) -> None: # Composed scales must be read while the full stage is still live, before export trims it. self._capture_object_scales(stage, self._clone_plan) - # Unused variant rows retain placeholder source paths but have no spawned prim. - # Keep active rows independent so dynamic assets retain their compiled binding paths. - active_sources = tuple( - path for path, mask in zip(self._clone_plan.sources, self._clone_plan.clone_mask, strict=True) if mask.any() - ) + # The clone plan already identifies every source row. Keep those rows independent so + # backend bindings for dynamic assets retain the paths they were compiled against. self._exported_usd_string = export_stage_to_string( stage, num_envs, - source_paths=active_sources, + source_paths=self._clone_plan.sources, keep_env_roots=not self._use_ovstage, ) diff --git a/source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_usd.py b/source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_usd.py index 891a9e9c0a7d..2d008bd25e1e 100644 --- a/source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_usd.py +++ b/source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_usd.py @@ -14,8 +14,6 @@ from pxr import Sdf, Usd, UsdGeom -from isaaclab.sim.utils import make_uninstanceable - logger = logging.getLogger(__name__) @@ -428,10 +426,9 @@ def export_stage_to_string( anonymous session layer used only for export, so the input stage remains unchanged. When ``keep_env_roots`` is True (the legacy ``renderer.clone_usd`` path) the non-source env root prims stay - active so the exported stage retains a slot for every env. Nested instances in source subtrees are expanded - on the export session because legacy cloning otherwise drops their geometry. The ovstage ``stage.clone`` - path passes False, retaining source instancing and trimming the non-source env roots themselves; - ``stage.clone`` recreates them and the RenderProduct's camera relationship is re-authored after clone. + active so the exported stage retains a slot for every env. The ovstage ``stage.clone`` path passes False, which + additionally trims the non-source env roots themselves; ``stage.clone`` recreates them and the RenderProduct's + camera relationship is re-authored after clone. Args: stage: USD stage to export. @@ -458,10 +455,6 @@ def export_stage_to_string( prim_paths: list[Sdf.Path] = [] if keep_env_roots: - # Native legacy cloning omits instanced visuals; expand only the renderer's copy. - with Usd.EditContext(export_stage, export_session): - for source_path in source_paths: - make_uninstanceable(source_path, stage=export_stage) for child in envs_prim.GetChildren(): # Legacy code path: keep env roots so we can query their xforms after opening stage child_path = child.GetPath() diff --git a/source/isaaclab_ov/test/test_ovrtx_clone_plan.py b/source/isaaclab_ov/test/test_ovrtx_clone_plan.py index 265e8d201d39..c5d215ecc82d 100644 --- a/source/isaaclab_ov/test/test_ovrtx_clone_plan.py +++ b/source/isaaclab_ov/test/test_ovrtx_clone_plan.py @@ -350,35 +350,6 @@ def test_prepare_stage_rejects_non_dense_environment_ids(monkeypatch: pytest.Mon _make_ovrtx_renderer_without_backend().prepare_stage(_make_multi_env_stage(2), 2) -@pytest.mark.parametrize("use_ovstage", [False, True]) -def test_prepare_stage_ignores_unspawned_variant_sources(monkeypatch: pytest.MonkeyPatch, use_ovstage: bool): - """Unused variants retain plan rows but have no source prim to export.""" - num_envs, num_variants = 4, 16 - stage = _make_multi_env_stage(num_envs) - plan = ClonePlan( - sources=tuple(f"/World/envs/env_{index}/Robot" for index in range(num_variants)), - destinations=("/World/envs/env_{}/Robot",) * num_variants, - clone_mask=np.eye(num_variants, num_envs, dtype=np.bool_), - env_ids=np.arange(num_envs, dtype=np.int64), - positions=np.zeros((num_envs, 3), dtype=np.float32), - ) - _patch_simulation_context(monkeypatch, plan) - renderer = _make_ovrtx_renderer_without_backend() - renderer._use_ovstage = use_ovstage - - renderer.prepare_stage(stage, num_envs) - - exported_layer = Sdf.Layer.CreateAnonymous(".usda") - assert exported_layer.ImportFromString(renderer._exported_usd_string) - exported_stage = Usd.Stage.Open(exported_layer) - for source in plan.sources[:num_envs]: - assert exported_stage.GetPrimAtPath(source) - for source in plan.sources[num_envs:]: - assert not exported_stage.GetPrimAtPath(source) - assert renderer._clone_plan is plan - assert len(plan.sources) == num_variants - - def test_capture_object_scales_populates_source_and_destination_scale_array(): """Projected source scales reach the body array without replacing a real destination scale.""" stage = Usd.Stage.CreateInMemory() diff --git a/source/isaaclab_ov/test/test_ovrtx_usd.py b/source/isaaclab_ov/test/test_ovrtx_usd.py index d45cc07e059b..1dc05838c820 100644 --- a/source/isaaclab_ov/test/test_ovrtx_usd.py +++ b/source/isaaclab_ov/test/test_ovrtx_usd.py @@ -430,8 +430,8 @@ def test_export_stage_without_keep_env_roots_trims_non_source_env_roots(): """The ovstage clone path also trims the non-source env roots themselves. ``ovstage.Stage.clone`` requires every target path to not already exist, so the exported stage - must not retain env roots that the clone will recreate. The legacy ``renderer.clone_usd`` - path keeps the roots as placeholders. + must not retain env roots that the clone will recreate. This is the only difference from the + legacy ``renderer.clone_usd`` path, which keeps the roots as placeholders. """ num_envs = 4 stage = _make_multi_env_stage(num_envs) @@ -494,44 +494,6 @@ def test_export_stage_restores_active_state(): assert stage.GetPrimAtPath(f"{env_path}/Object_env{env_idx}_only").IsActive() -@pytest.mark.parametrize( - ("num_envs", "keep_env_roots", "keep_instances"), - [(4, True, False), (4, False, True), (1, True, True), (1, False, True)], -) -def test_export_stage_expands_nested_instances_only_for_legacy_cloning(num_envs, keep_env_roots, keep_instances): - """Legacy cloning receives concrete geometry without changing the simulation's instancing.""" - stage = _make_multi_env_stage(num_envs) - mesh = UsdGeom.Mesh.Define(stage, "/MeshPrototype/Mesh") - mesh.CreatePointsAttr([(0, 0, 0), (1, 0, 0), (0, 1, 0)]) - mesh.CreateFaceVertexCountsAttr([3]) - mesh.CreateFaceVertexIndicesAttr([0, 1, 2]) - visuals = UsdGeom.Xform.Define(stage, "/RobotPrototype/Visuals").GetPrim() - visuals.GetReferences().AddInternalReference("/MeshPrototype") - visuals.SetInstanceable(True) - robot_path = "/World/envs/env_0/Robot" - robot = stage.GetPrimAtPath(robot_path) - robot.GetReferences().AddInternalReference("/RobotPrototype") - robot.SetInstanceable(True) - mesh_path = f"{robot_path}/Visuals/Mesh" - assert robot.IsInstance() - assert stage.GetPrimAtPath(mesh_path).IsInstanceProxy() - input_layers = [layer.ExportToString() for layer in stage.GetLayerStack()] - - exported = export_stage_to_string(stage, num_envs, ("/World/envs/env_0",), keep_env_roots) - exported_layer = Sdf.Layer.CreateAnonymous("exported.usda") - exported_layer.ImportFromString(exported) - exported_stage = Usd.Stage.Open(exported_layer) - - assert exported_stage.GetPrimAtPath(robot_path).IsInstance() == keep_instances - assert exported_stage.GetPrimAtPath(f"{robot_path}/Visuals").IsInstanceable() == keep_instances - exported_mesh = exported_stage.GetPrimAtPath(mesh_path) - assert exported_mesh.IsInstanceProxy() == keep_instances - assert UsdGeom.Mesh(exported_mesh).GetPointsAttr().Get() == mesh.GetPointsAttr().Get() - assert [layer.ExportToString() for layer in stage.GetLayerStack()] == input_layers - assert robot.IsInstance() - assert stage.GetPrimAtPath(mesh_path).IsInstanceProxy() - - def test_create_scene_partition_attributes_all_envs(): """Scene partition attributes are authored on every env root and camera.""" num_envs = 4