Skip to content
Draft
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
5 changes: 4 additions & 1 deletion scripts/benchmarks/benchmark_xform_prim_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@

AppLauncher.add_app_launcher_args(parser)
args_cli = parser.parse_args()
args_cli.enable_cameras = True

# launch omniverse app
app_launcher = AppLauncher(args_cli)
Expand Down Expand Up @@ -105,7 +106,9 @@ def benchmark_xform_prim_view( # noqa: C901
# Setup scene
print(" Setting up scene")
# Clear stage
sim_utils.create_new_stage()

use_fabric: bool = "fabric" in api.lower()
sim_utils.create_new_stage(create_fabric_stage=use_fabric)
# Create simulation context
start_time = time.perf_counter()
sim_cfg = sim_utils.SimulationCfg(
Expand Down
4 changes: 3 additions & 1 deletion source/isaaclab/isaaclab/sensors/camera/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,9 @@ def _initialize_impl(self):
super()._initialize_impl()
# Create a view for the sensor with Fabric enabled for fast pose queries, otherwise position will be stale.
self._view = XformPrimView(
self.cfg.prim_path, device=self._device, stage=self.stage, sync_usd_on_fabric_write=True
self.cfg.prim_path,
device=self._device,
stage=self.stage,
)
# Check that sizes are correct
if self._view.count != self._num_envs:
Expand Down
7 changes: 5 additions & 2 deletions source/isaaclab/isaaclab/sensors/camera/tiled_camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,11 @@ def _initialize_impl(self):
# the view keeps references to the prims located in the stage
self.renderer.prepare_stage(self.stage, self._num_envs)

# Create a view for the sensor
self._view = XformPrimView(self.cfg.prim_path, device=self._device, stage=self.stage)
self._view = XformPrimView(
self.cfg.prim_path,
device=self._device,
stage=self.stage,
)
Comment thread
bareya marked this conversation as resolved.
# Check that sizes are correct
if self._view.count != self._num_envs:
raise RuntimeError(
Expand Down
50 changes: 44 additions & 6 deletions source/isaaclab/isaaclab/sim/utils/stage.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,28 +130,65 @@ def _modify_path(asset_path: str) -> str:
pass


def create_new_stage() -> Usd.Stage:
def create_new_stage(create_fabric_stage: bool = False) -> Usd.Stage:
"""Create a new in-memory USD stage.

Creates a new stage using pure USD (``Usd.Stage.CreateInMemory()``).
When ``create_fabric_stage`` is False (default), creates a pure USD stage
via ``Usd.Stage.CreateInMemory()``.

When ``create_fabric_stage`` is True, creates the stage via
``usdrt.Usd.Stage.CreateInMemory()`` which provides a paired Fabric store
alongside the USD stage. USD notice handling is enabled so that subsequent
prim creation on the USD stage automatically propagates into Fabric. Both
the USD stage and the USDRT stage handle are kept alive in a thread-local
context to prevent garbage collection from releasing the Fabric resources.

If Kit is running and Kit extensions need to discover this stage (e.g.
PhysX, ``isaacsim.core.prims.Articulation``), call
:func:`attach_stage_to_usd_context` after scene setup.

Args:
create_fabric_stage: If True, create the stage through USDRT with a
backing Fabric store and enable USD-to-Fabric notice-driven sync.
Defaults to False.

Returns:
Usd.Stage: The created USD stage.
The created USD stage.

Example:
>>> import isaaclab.sim as sim_utils
>>>
>>> sim_utils.create_new_stage()
Usd.Stage.Open(rootLayer=Sdf.Find('anon:0x7fba6c04f840:World7.usd'),
sessionLayer=Sdf.Find('anon:0x7fba6c01c5c0:World7-session.usda'),
pathResolverContext=<invalid repr>)
Usd.Stage.Open(rootLayer=Sdf.Find('anon:0x...'), ...)
>>> sim_utils.create_new_stage(create_fabric_stage=True)
Usd.Stage.Open(rootLayer=Sdf.Find('anon:0x...'), ...)
"""

if create_fabric_stage:
import uuid

import usdrt

rt_stage = usdrt.Usd.Stage.CreateInMemory(f"World_{uuid.uuid4().hex[:8]}.usda")
stage = UsdUtils.StageCache.Get().Find(Usd.StageCache.Id.FromLongInt(rt_stage.GetStageId()))

# Storing stage in the context is required, so both stages aren't going to be garbage collected.
_context.stage = stage
_context.rt_stage = rt_stage

stage_id = rt_stage.GetStageIdAsStageId()
fabric_id = rt_stage.GetFabricId()
srw_id = rt_stage.GetStageReaderWriterId()

pop = usdrt.population.IUtils()
pop.set_enable_usd_notice_handling(stage_id, fabric_id, True)
pop.populate_from_usd(srw_id, stage_id, usdrt.Sdf.Path("/"), 0)
pop.apply_pending_usd_updates(stage_id, srw_id, 0)
return stage

stage: Usd.Stage = Usd.Stage.CreateInMemory()
_context.stage = stage
_context.rt_stage = None
UsdUtils.StageCache.Get().Insert(stage)
return stage

Expand Down Expand Up @@ -400,6 +437,7 @@ def close_stage() -> bool:
stage_cache = UsdUtils.StageCache.Get()
stage_cache.Clear()
_context.stage = None
_context.rt_stage = None

return True

Expand Down
6 changes: 6 additions & 0 deletions source/isaaclab/isaaclab/sim/views/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@
# SPDX-License-Identifier: BSD-3-Clause

__all__ = [
"FabricBackend",
"UsdBackend",
"XformBackend",
"XformPrimView",
]

from .xform_backend import XformBackend
from .xform_fabric_backend import FabricBackend
from .xform_prim_view import XformPrimView
from .xform_usd_backend import UsdBackend
110 changes: 110 additions & 0 deletions source/isaaclab/isaaclab/sim/views/xform_backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause

from __future__ import annotations

from collections.abc import Sequence
from typing import Protocol

import torch


class XformBackend(Protocol):
"""Protocol defining the interface for :class:`XformPrimView` transform backends.

Implementations provide read/write access to prim transforms through either
the USD or Fabric data path. :class:`XformPrimView` delegates all transform
operations to a *primary* backend and optionally replicates writes to one or
more *sync* backends.
"""

def initialize(self) -> None:
"""Perform any deferred initialisation required by the backend."""
...

def set_world_poses(
self,
positions: torch.Tensor | None = None,
orientations: torch.Tensor | None = None,
indices: Sequence[int] | None = None,
) -> None:
"""Set world-space poses for the managed prims.

Args:
positions: World-space positions, shape ``(M, 3)`` [m].
orientations: World-space quaternions ``(x, y, z, w)``, shape ``(M, 4)``.
indices: Subset of prim indices to update. ``None`` means all.
"""
...

def get_world_poses(
self,
indices: Sequence[int] | None = None,
) -> tuple[torch.Tensor, torch.Tensor]:
"""Return world-space ``(positions, orientations)`` for the managed prims.

Args:
indices: Subset of prim indices to query. ``None`` means all.

Returns:
``(positions, orientations)`` with shapes ``(M, 3)`` and ``(M, 4)``.
"""
...

def set_local_poses(
self,
translations: torch.Tensor | None = None,
orientations: torch.Tensor | None = None,
indices: Sequence[int] | None = None,
) -> None:
"""Set local-space poses (relative to each prim's parent).

Args:
translations: Local-space translations, shape ``(M, 3)`` [m].
orientations: Local-space quaternions ``(x, y, z, w)``, shape ``(M, 4)``.
indices: Subset of prim indices to update. ``None`` means all.
"""
...

def get_local_poses(
self,
indices: Sequence[int] | None = None,
) -> tuple[torch.Tensor, torch.Tensor]:
"""Return local-space ``(translations, orientations)`` for the managed prims.

Args:
indices: Subset of prim indices to query. ``None`` means all.

Returns:
``(translations, orientations)`` with shapes ``(M, 3)`` and ``(M, 4)``.
"""
...

def set_scales(
self,
scales: torch.Tensor,
indices: Sequence[int] | None = None,
) -> None:
"""Set scales for the managed prims.

Args:
scales: Scales, shape ``(M, 3)``.
indices: Subset of prim indices to update. ``None`` means all.
"""
...

def get_scales(
self,
indices: Sequence[int] | None = None,
) -> torch.Tensor:
"""Return scales for the managed prims.

Args:
indices: Subset of prim indices to query. ``None`` means all.

Returns:
Scales tensor of shape ``(M, 3)``.
"""
...
Loading
Loading