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
@@ -0,0 +1,4 @@
Changed
^^^^^^^

* Reverted the release-only legacy OVRTX instance-cloning changes because the corresponding change has not merged into ``develop``.
9 changes: 3 additions & 6 deletions source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)

Expand Down
13 changes: 3 additions & 10 deletions source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_usd.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@

from pxr import Sdf, Usd, UsdGeom

from isaaclab.sim.utils import make_uninstanceable

logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -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.
Expand All @@ -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():

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Nested visuals disappear

When the default legacy renderer.clone_usd path exports a source asset containing nested instances, this removal leaves those instances as proxies. Legacy cloning then omits their visual geometry, so cloned environments can render without those visuals. Please retain the expansion on the anonymous export stage so the simulation stage remains unchanged.

# Legacy code path: keep env roots so we can query their xforms after opening stage
child_path = child.GetPath()
Expand Down
29 changes: 0 additions & 29 deletions source/isaaclab_ov/test/test_ovrtx_clone_plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
42 changes: 2 additions & 40 deletions source/isaaclab_ov/test/test_ovrtx_usd.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
Loading