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
2 changes: 2 additions & 0 deletions .github/actions/run-tests/run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,8 @@ run_tests() {
-e "HOME=/tmp/isaaclab-ci-home"
-e "XDG_CACHE_HOME=/tmp/isaaclab-ci-home/.cache"
-e "XDG_DATA_HOME=/tmp/isaaclab-ci-home/.local/share"
# The image's /var/cache/hub belongs to uid 1000, not necessarily the host uid.
-e "HUB__CACHE__PATH=/tmp/isaaclab-ci-home/.cache/hub"
-e "USER=${host_user}"
-e "LOGNAME=${host_user}"
)
Expand Down
45 changes: 31 additions & 14 deletions docs/source/concepts/backends_and_presets.rst
Original file line number Diff line number Diff line change
Expand Up @@ -304,20 +304,37 @@ Renderers and visualizers instead select their implementations through their
configuration's ``class_type``. Their selection is independent of physics;
renderer instances are shared through the simulation registry described below.

Physics manager lifecycle

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.

🟡 Warning · Scope Complexity — Unrelated rewrite drops lifecycle contract docs

This rewrite deletes the PhysicsManager lifecycle section — forward/step/reset/close and PhysicsEvent MODEL_INIT/PHYSICS_READY/STOP, including close() dispatching STOP — which this diff does not change. Keep that section alongside the new construction text.

^^^^^^^^^^^^^^^^^^^^^^^^^

Each backend implements :class:`~isaaclab.physics.PhysicsManager`, the abstract
base class that owns its simulation lifecycle. Implementations initialize their
engine from a :class:`~isaaclab.sim.SimulationContext`, update kinematics with
``forward()``, advance simulation with ``step()``, reset state with ``reset()``,
and release resources with ``close()``.

The manager exposes :class:`~isaaclab.physics.PhysicsEvent` callbacks for
cross-backend lifecycle work. ``MODEL_INIT`` occurs during scene construction,
``PHYSICS_READY`` after physics initialization, and ``STOP`` before native resources are replaced
or shut down.
The concrete ``close()`` implementation dispatches the ``STOP`` event.
Construction and updates
^^^^^^^^^^^^^^^^^^^^^^^^

The task or standalone script composes the simulation. Preset resolution belongs to
``isaaclab_tasks`` and finishes before calling the core launcher, which accepts concrete
configurations and applies launch options. Scene construction and updates follow this order:

1. :class:`~isaaclab.sim.SimulationContext` constructs the selected physics manager and owns
the resource registry. :class:`~isaaclab.scene.InteractiveScene` owns scene assets and
sensors. A :class:`~isaaclab.cloner.ClonePlan` declares their source definitions,
environment layout, and replication routes before native cloning.
2. Clone contexts prepare their requirements before dispatch. Consumers that share a native
representation combine their asset routes, so physics and rendering do not populate the
same representation independently. Copies come from the plan; authored prototype metadata,
such as mesh topology or scale, is read during setup.
3. Reset initializes physics and binds consumers to its published state. The
:class:`~isaaclab.physics.PhysicsEvent` callbacks order model preparation and physics
readiness. Declared rendering consumers bind before the first graph capture, and visualizers
initialize before the simulation starts playing.
4. Physics steps and explicit state writes invalidate the appropriate publications. Asset
reads and :ref:`scene-data-provider` reads refresh derived state lazily. A read after a
state write does not require a new physics step. Matching native formats are borrowed;
consumers reuse conversions for an unchanged publication.

Camera-backed views and recordings borrow a camera's output; the camera owns its render product
and buffers, while the simulation owns the renderer. Closing a window leaves its borrowed camera
alive. Native GL perspective views instead render published scene state directly. Teleop and XR
retain bound host Fabric matrices for pose reads; they do not copy poses through GPU buffers.

Shared resource lifetime
^^^^^^^^^^^^^^^^^^^^^^^^

``SimulationContext`` keeps native resources and renderers in one registry.
``get_or_create_backend(backend_cfg)`` reuses a resource when the configuration
Expand Down
6 changes: 3 additions & 3 deletions docs/source/concepts/scene_data_providers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ The system has three layers:
calls return the supplied destination. Destination caches retain indexing metadata, not the
consumer's buffers, and expire with the destination.
- :meth:`SceneDataProvider.get_camera_transforms`: discovers per-camera, per-env world
transforms from the USD stage.
- :attr:`SceneDataProvider.num_envs`: environment count inferred from
``/World/envs/env_<id>`` prims.
transforms from the USD stage. Runtime visualizers use bound scene cameras instead.
- :attr:`SceneDataProvider.num_envs`: world count from :class:`~isaaclab.cloner.ClonePlan`,
or ``0`` before a plan is published. Native-only clones need no corresponding USD prims.

3. Backend implementations:

Expand Down
14 changes: 10 additions & 4 deletions docs/source/how-to/cloning.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,24 @@ asset configurations and an active simulation context ``sim``:

.. code-block:: python

from isaaclab.scene import InteractiveScene, InteractiveSceneCfg
from isaaclab.utils import configclass, replace
from isaaclab.cloner import CloneCfg
from isaaclab.scene import InteractiveSceneCfg
from isaaclab.utils import configclass, instantiate, replace

@configclass
class BananaFrankaSceneCfg(InteractiveSceneCfg):
banana = replace(banana_cfg, prim_path="{ENV_REGEX_NS}/Banana")
franka = replace(franka_cfg, prim_path="{ENV_REGEX_NS}/Franka")

scene = InteractiveScene(BananaFrankaSceneCfg(num_envs=16, env_spacing=2.0))
scene = instantiate(BananaFrankaSceneCfg(
num_envs=16, env_spacing=2.0, clone_cfg=CloneCfg(clone_template="/Lab/Cell{}")
))
sim.reset()

This creates sixteen environments, each with one banana and one Franka.
This creates sixteen environments at ``/Lab/Cell0`` through ``/Lab/Cell15``, each
with one banana and one Franka. ``{ENV_REGEX_NS}`` follows the declared template;
omitting ``clone_cfg`` keeps the default ``/World/envs/env_{}`` layout. Shared
assets such as a ground plane keep their explicitly declared paths.
Declare cameras alongside the assets so they participate in the same lifecycle.
For varying assets across environments, see :doc:`multi_asset_spawning`.

Expand Down
30 changes: 15 additions & 15 deletions scripts/benchmarks/benchmark_view_comparison.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@

import isaaclab.sim as sim_utils
from isaaclab.sim.views import FrameView
from isaaclab.utils.warp import ProxyArray

try:
from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg
Expand Down Expand Up @@ -138,9 +139,9 @@ def benchmark_newton(num_iterations: int) -> dict[str, float]:
from pxr import Gf

from isaaclab.assets import RigidObjectCfg
from isaaclab.scene import InteractiveScene, InteractiveSceneCfg
from isaaclab.scene import InteractiveSceneCfg
from isaaclab.sim import build_simulation_context
from isaaclab.utils import configclass
from isaaclab.utils import configclass, instantiate

timing_results = {}

Expand All @@ -162,11 +163,11 @@ class _SceneCfg(InteractiveSceneCfg):
ctx = build_simulation_context(device=args_cli.device, sim_cfg=sim_cfgs["newton"], add_ground_plane=True)
sim = ctx.__enter__()
sim._app_control_on_stop_handle = None
InteractiveScene(_SceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0))
scene = instantiate(_SceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0))

stage = sim_utils.get_current_stage()
for i in range(args_cli.num_envs):
prim = stage.DefinePrim(f"/World/envs/env_{i}/Cube/Sensor", "Xform")
for env_path in scene.env_prim_paths:
prim = stage.DefinePrim(f"{env_path}/Cube/Sensor", "Xform")
sim_utils.standardize_xform_ops(prim)
prim.GetAttribute("xformOp:translate").Set(Gf.Vec3d(0.1, 0.0, 0.05))
prim.GetAttribute("xformOp:orient").Set(Gf.Quatd(1.0, 0.0, 0.0, 0.0))
Expand All @@ -175,7 +176,7 @@ class _SceneCfg(InteractiveSceneCfg):
print(f" Newton scene setup: {time.perf_counter() - start_time:.4f}s")

start_time = time.perf_counter()
view = NewtonSiteFrameView("/World/envs/env_.*/Cube/Sensor", device=args_cli.device)
view = NewtonSiteFrameView(f"{scene.env_regex_ns}/Cube/Sensor", device=args_cli.device)
num_prims = view.count
timing_results["init"] = time.perf_counter() - start_time

Expand Down Expand Up @@ -265,19 +266,18 @@ def _run_pose_benchmarks(
num_prims: int,
num_iterations: int,
timing_results: dict,
positions: wp.array,
orientations: wp.array,
positions: ProxyArray,
orientations: ProxyArray,
):
"""Shared benchmark loop for get/set world poses on any FrameView."""
start_time = time.perf_counter()
for _ in range(num_iterations):
view.get_world_poses()
timing_results["get_world_poses"] = (time.perf_counter() - start_time) / num_iterations

new_positions = wp.clone(positions)
new_positions_t = wp.to_torch(new_positions)
new_positions_t[:, 2] += 0.5
expected_positions = new_positions_t.clone()
new_positions = positions.torch.clone()
new_positions[:, 2] += 0.5
expected_positions = new_positions.clone()

start_time = time.perf_counter()
for _ in range(num_iterations):
Expand All @@ -286,9 +286,9 @@ def _run_pose_benchmarks(
timing_results["set_world_poses"] = (time.perf_counter() - start_time) / num_iterations

ret_pos, ret_quat = view.get_world_poses()
ret_pos_t = wp.to_torch(ret_pos)
ret_quat_t = wp.to_torch(ret_quat)
ori_t = wp.to_torch(orientations)
ret_pos_t = ret_pos.torch
ret_quat_t = ret_quat.torch
ori_t = orientations.torch

pos_ok = torch.allclose(ret_pos_t, expected_positions, atol=1e-4, rtol=0)
quat_ok = torch.allclose(ret_quat_t, ori_t, atol=1e-4, rtol=0)
Expand Down
8 changes: 4 additions & 4 deletions scripts/benchmarks/benchmark_xform_prim_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,19 +96,19 @@ def benchmark_frame_view( # noqa: C901
sim = ctx.__enter__()
sim._app_control_on_stop_handle = None
scene_cfg = _NewtonSceneCfg(num_envs=num_envs, env_spacing=2.0)
instantiate(scene_cfg)
scene = instantiate(scene_cfg)

stage = sim_utils.get_current_stage()
for i in range(num_envs):
prim = stage.DefinePrim(f"/World/envs/env_{i}/Object/Sensor", "Xform")
for env_path in scene.env_prim_paths:
prim = stage.DefinePrim(f"{env_path}/Object/Sensor", "Xform")
sim_utils.standardize_xform_ops(prim)
prim.GetAttribute("xformOp:translate").Set(Gf.Vec3d(0.1, 0.0, 0.05))
prim.GetAttribute("xformOp:orient").Set(Gf.Quatd(1.0, 0.0, 0.0, 0.0))

sim.reset()

start_time = time.perf_counter()
xform_view = NewtonSiteFrameView("/World/envs/env_.*/Object/Sensor", device=device)
xform_view = NewtonSiteFrameView(f"{scene.env_regex_ns}/Object/Sensor", device=device)
timing_results["init"] = time.perf_counter() - start_time
cleanup = lambda: ctx.__exit__(None, None, None) # noqa: E731

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1190,6 +1190,7 @@ def _run_single_replay(
# its IPC connection.
teleop_interface = create_isaac_teleop_device(
isaac_teleop_cfg,
env_prim_path=env.scene.env_prim_paths[0],
sim_device=args_cli.device,
callbacks={},
cloudxr_env_file=None,
Expand Down
1 change: 1 addition & 0 deletions scripts/environments/teleoperation/teleop_se3_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ def stop_teleoperation() -> None:

teleop_interface = create_isaac_teleop_device(
env_cfg.isaac_teleop,
env_prim_path=env.scene.env_prim_paths[0],
sim_device=args_cli.device,
callbacks=teleoperation_callbacks,
cloudxr_env_file=args_cli.cloudxr_env,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,16 +306,6 @@ def sync_simulation_state(env: LocomanipulationSDGEnv):
env.scene.update(dt=env.physics_dt)


def _set_sensor_camera_view():
"""Set the Sim GUI viewport to display the robot_pov_cam sensor view."""
import omni.kit.viewport.utility

viewport = omni.kit.viewport.utility.get_active_viewport()
if viewport is not None:
cam_prim_path = "/World/envs/env_0/Robot/torso_link/d435_link/camera"
viewport.set_active_camera(cam_prim_path)


def project_robot_state_into_env(env: LocomanipulationSDGEnv, input_episode_data: EpisodeData) -> torch.Tensor:
"""Project the recorded robot pose and joint state into the current environment.

Expand Down Expand Up @@ -919,7 +909,11 @@ def replay(
env.recorder_manager.record_post_reset(env_ids=[0])

if sensor_camera_view:
_set_sensor_camera_view()
import omni.kit.viewport.utility

viewport = omni.kit.viewport.utility.get_active_viewport()
if viewport is not None:
viewport.set_active_camera(str(env.scene["robot_pov_cam"]._view.prims[0].GetPath()))

# Initialize state machine
output_data = LocomanipulationSDGOutputData()
Expand Down
12 changes: 10 additions & 2 deletions scripts/tools/record_demos.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,11 @@ def create_environment_config(


def setup_teleop_device(
env_cfg: ManagerBasedRLEnvCfg, callbacks: dict[str, Callable], use_isaac_teleop: bool = False
env_cfg: ManagerBasedRLEnvCfg,
callbacks: dict[str, Callable],
use_isaac_teleop: bool = False,
*,
env_prim_path: str | None = None,
) -> object:
"""Set up the teleoperation device based on configuration.

Expand All @@ -358,6 +362,7 @@ def setup_teleop_device(

Args:
env_cfg: Environment configuration containing the selected teleop devices.
env_prim_path: Concrete environment root for task-relative teleop frames.
callbacks: Dictionary mapping callback keys to functions that will be
attached to the teleop device
use_isaac_teleop: Whether to use Isaac Capture stack instead of native stack
Expand All @@ -374,6 +379,7 @@ def setup_teleop_device(

teleop_interface = create_isaac_teleop_device(
env_cfg.isaac_teleop,
env_prim_path=env_prim_path,
sim_device=args_cli.device,
callbacks=callbacks,
cloudxr_env_file=args_cli.cloudxr_env,
Expand Down Expand Up @@ -580,7 +586,9 @@ def stop_recording_instance():
"RESET": reset_recording_instance,
}

teleop_interface = setup_teleop_device(env.cfg, teleoperation_callbacks, use_isaac_teleop)
teleop_interface = setup_teleop_device(
env.cfg, teleoperation_callbacks, use_isaac_teleop, env_prim_path=env.scene.env_prim_paths[0]
)

haptic_driver = None
if use_isaac_teleop:
Expand Down
2 changes: 1 addition & 1 deletion scripts/tutorials/05_controllers/run_osc.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ class SceneCfg(InteractiveSceneCfg):
)

contact_forces = ContactSensorCfg(
prim_path="/World/envs/env_.*/TiltedWall",
prim_path="{ENV_REGEX_NS}/TiltedWall",
update_period=0.0,
history_length=2,
debug_vis=False,
Expand Down
2 changes: 1 addition & 1 deletion scripts/tutorials/08_mpm/rigid_body_equivalence.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ class ShapeSpec(NamedTuple):
RAMP_ROTATION = (math.sin(0.5 * RAMP_ANGLE_RAD), 0.0, 0.0, math.cos(0.5 * RAMP_ANGLE_RAD))
START_Y = 1.70
INITIAL_CLEARANCE = 0.08
RIGID_BODY_PATTERN = r"/World/envs/env_.*/Rigid_.*"
RIGID_BODY_PATTERN = r"{ENV_REGEX_NS}/Rigid_.*"
CAMERA_EYE = (0.0, -26.0, 6.55)
CAMERA_TARGET = (0.0, -2.5, 2.15)
CAMERA_FOCAL_LENGTH = 18.0
Expand Down
2 changes: 2 additions & 0 deletions source/isaaclab/changelog.d/pr10-clone-paths.fixed.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
* Fixed Kit marker environment ownership for scenes with custom clone namespaces.
* Fixed scene camera discovery and visualizer camera selection to use declared clone paths.
6 changes: 3 additions & 3 deletions source/isaaclab/isaaclab/markers/visualization_markers.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,18 +278,18 @@ def _ensure_backends_initialized(self) -> None:
)
)
if needs_kit_backend:
self._ensure_kit_backend()
self._ensure_kit_backend(sim)
if any(
viz.supports_markers() and not viz.pumps_app_update() and viz.cfg.enable_markers for viz in sim.visualizers
):
self._ensure_newton_backend()

def _ensure_kit_backend(self) -> None:
def _ensure_kit_backend(self, sim: sim_utils.SimulationContext | None = None) -> None:
"""Create the Kit marker backend if it is not already active."""
from isaaclab_visualizers.kit.kit_visualization_markers import KitVisualizationMarkers

if not any(isinstance(backend, KitVisualizationMarkers) for backend in self._backends):
self._backends.append(KitVisualizationMarkers(self.cfg, visible=self._is_visible))
self._backends.append(KitVisualizationMarkers(self.cfg, visible=self._is_visible, sim=sim))

def _ensure_newton_backend(self) -> None:
"""Create the Newton-family marker backend if it is not already active."""
Expand Down
6 changes: 3 additions & 3 deletions source/isaaclab/isaaclab/scene/interactive_scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ def _collect_asset_cfgs(self) -> tuple[list[Any], tuple[tuple[int, ...], ...] |
def filter_collisions(self, global_prim_paths: list[str] | None = None):
"""Filter environments collisions.

Disables collisions between the environments in ``/World/envs/env_.*`` and enables collisions with the prims
Disables collisions between the declared environments and enables collisions with the prims
in global prim paths (e.g. ground plane).

Args:
Expand Down Expand Up @@ -300,12 +300,12 @@ def device(self) -> str:

@property
def env_ns(self) -> str:
"""The namespace ``/World/envs`` in which all environments are created."""
"""The parent namespace of the declared environment template."""
return self._env_fmt.rsplit("/", 1)[0]

@property
def env_regex_ns(self) -> str:
"""The namespace ``/World/envs/env_[^/]+`` in which all environments are created."""
"""The declared environment template with its instance slot expanded to a path expression."""
return self._env_fmt.format("[^/]+")

@property
Expand Down
Loading
Loading