Commit 5991713b authored by cosmith-nvidia's avatar cosmith-nvidia Committed by Kelly Guo

Configures XR teleop camera placement per-task with an XrCfg env configuration (#282)

Currently, the camera placement for the stacking task is set to a
task-specific
view in each main script (record_demos.py, teleop_se3_agent.py). This is
not
scalable for when we need to add more tasks with different camera
placements,
or more main scripts.

Instead, this change adds an XrCfg with "xr_anchor_pos" and
"xr_anchor_rot" fields
which can specify the XR anchor prim per-task (e.g. in the stacking base
task).

Specifically: the pose of this prim will be placed at the origin of the
XR
device's local coordinate frame. This is achieved by setting the XR
anchor mode
to "custom anchor", which determines the coordinate frame transformation
between
the simulation and the XR device's local frame.

The default camera placement is set to the scene origin pose (in case a
custom
anchor is not set for a task).

- Bug fix (non-breaking change which fixes an issue)

- [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./isaaclab.sh --format`
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] I have updated the changelog and the corresponding version in the
extension's `config/extension.toml` file
- [x] I have added my name to the `CONTRIBUTORS.md` or my name already
exists there
parent 799a4f7f
...@@ -47,6 +47,7 @@ Guidelines for modifications: ...@@ -47,6 +47,7 @@ Guidelines for modifications:
* Cheng-Rong Lai * Cheng-Rong Lai
* Chenyu Yang * Chenyu Yang
* Clemens Schwarke * Clemens Schwarke
* Connor Smith
* CY (Chien-Ying) Chen * CY (Chien-Ying) Chen
* David Yang * David Yang
* Dorsa Rohani * Dorsa Rohani
......
...@@ -39,9 +39,9 @@ app.xr.enabled = true ...@@ -39,9 +39,9 @@ app.xr.enabled = true
# xr settings # xr settings
xr.ui.enabled = false xr.ui.enabled = false
xrstage.profile.ar.anchorMode = "active camera"
xr.depth.aov = "GBufferDepth" xr.depth.aov = "GBufferDepth"
defaults.xr.profile.ar.renderQuality = "off" defaults.xr.profile.ar.renderQuality = "off"
defaults.xr.profile.ar.anchorMode = "custom anchor"
rtx.rendermode = "RaytracedLighting" rtx.rendermode = "RaytracedLighting"
# Register extension folder from this repo in kit # Register extension folder from this repo in kit
......
...@@ -38,8 +38,6 @@ import torch ...@@ -38,8 +38,6 @@ import torch
import omni.log import omni.log
from isaaclab.devices import Se3Gamepad, Se3HandTracking, Se3Keyboard, Se3SpaceMouse from isaaclab.devices import Se3Gamepad, Se3HandTracking, Se3Keyboard, Se3SpaceMouse
from isaaclab.envs import ViewerCfg
from isaaclab.envs.ui import ViewportCameraController
from isaaclab.managers import TerminationTermCfg as DoneTerm from isaaclab.managers import TerminationTermCfg as DoneTerm
import isaaclab_tasks # noqa: F401 import isaaclab_tasks # noqa: F401
...@@ -97,10 +95,8 @@ def main(): ...@@ -97,10 +95,8 @@ def main():
elif args_cli.teleop_device.lower() == "handtracking": elif args_cli.teleop_device.lower() == "handtracking":
from isaacsim.xr.openxr import OpenXRSpec from isaacsim.xr.openxr import OpenXRSpec
teleop_interface = Se3HandTracking(OpenXRSpec.XrHandEXT.XR_HAND_RIGHT_EXT, False, True) teleop_interface = Se3HandTracking(env_cfg.xr, OpenXRSpec.XrHandEXT.XR_HAND_RIGHT_EXT, False, True)
teleop_interface.add_callback("RESET", env.reset) teleop_interface.add_callback("RESET", env.reset)
viewer = ViewerCfg(eye=(-0.25, -0.3, 0.5), lookat=(0.6, 0, 0), asset_name="viewer")
ViewportCameraController(env, viewer)
else: else:
raise ValueError( raise ValueError(
f"Invalid device interface '{args_cli.teleop_device}'. Supported: 'keyboard', 'spacemouse', 'gamepad'," f"Invalid device interface '{args_cli.teleop_device}'. Supported: 'keyboard', 'spacemouse', 'gamepad',"
......
...@@ -69,9 +69,7 @@ import torch ...@@ -69,9 +69,7 @@ import torch
import omni.log import omni.log
from isaaclab.devices import Se3HandTracking, Se3Keyboard, Se3SpaceMouse from isaaclab.devices import Se3HandTracking, Se3Keyboard, Se3SpaceMouse
from isaaclab.envs import ViewerCfg
from isaaclab.envs.mdp.recorders.recorders_cfg import ActionStateRecorderManagerCfg from isaaclab.envs.mdp.recorders.recorders_cfg import ActionStateRecorderManagerCfg
from isaaclab.envs.ui import ViewportCameraController
import isaaclab_tasks # noqa: F401 import isaaclab_tasks # noqa: F401
from isaaclab_tasks.utils.parse_cfg import parse_env_cfg from isaaclab_tasks.utils.parse_cfg import parse_env_cfg
...@@ -180,10 +178,8 @@ def main(): ...@@ -180,10 +178,8 @@ def main():
elif args_cli.teleop_device.lower() == "handtracking": elif args_cli.teleop_device.lower() == "handtracking":
from isaacsim.xr.openxr import OpenXRSpec from isaacsim.xr.openxr import OpenXRSpec
teleop_interface = Se3HandTracking(OpenXRSpec.XrHandEXT.XR_HAND_RIGHT_EXT, False, True) teleop_interface = Se3HandTracking(env_cfg.xr, OpenXRSpec.XrHandEXT.XR_HAND_RIGHT_EXT, False, True)
teleop_interface.add_callback("RESET", reset_recording_instance) teleop_interface.add_callback("RESET", reset_recording_instance)
viewer = ViewerCfg(eye=(-0.25, -0.3, 0.5), lookat=(0.6, 0, 0), asset_name="viewer")
ViewportCameraController(env, viewer)
else: else:
raise ValueError( raise ValueError(
f"Invalid device interface '{args_cli.teleop_device}'. Supported: 'keyboard', 'spacemouse', 'handtracking'." f"Invalid device interface '{args_cli.teleop_device}'. Supported: 'keyboard', 'spacemouse', 'handtracking'."
......
...@@ -118,8 +118,8 @@ Changed ...@@ -118,8 +118,8 @@ Changed
* ``set_fixed_tendon_limit`` → ``set_fixed_tendon_position_limit`` * ``set_fixed_tendon_limit`` → ``set_fixed_tendon_position_limit``
0.34.9 (2025-03-04) 0.34.10 (2025-03-04)
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~
Fixed Fixed
^^^^^ ^^^^^
...@@ -129,7 +129,7 @@ Fixed ...@@ -129,7 +129,7 @@ Fixed
outputs are requested. outputs are requested.
0.34.8 (2025-03-04) 0.34.9 (2025-03-04)
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
Fixed Fixed
...@@ -139,7 +139,7 @@ Fixed ...@@ -139,7 +139,7 @@ Fixed
with other modalities such as RGBA and depth. with other modalities such as RGBA and depth.
0.34.7 (2025-03-04) 0.34.8 (2025-03-04)
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
Added Added
...@@ -151,7 +151,7 @@ Added ...@@ -151,7 +151,7 @@ Added
which didn't allow setting the joint position and velocity separately. which didn't allow setting the joint position and velocity separately.
0.34.6 (2025-03-02) 0.34.7 (2025-03-02)
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
Fixed Fixed
...@@ -162,7 +162,7 @@ Fixed ...@@ -162,7 +162,7 @@ Fixed
was always set to False, which led to incorrect contact sensor settings for the spawned assets. was always set to False, which led to incorrect contact sensor settings for the spawned assets.
0.34.5 (2025-03-02) 0.34.6 (2025-03-02)
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
Changed Changed
...@@ -180,7 +180,7 @@ Removed ...@@ -180,7 +180,7 @@ Removed
* Removed the attribute ``disable_contact_processing`` from :class:`~isaaclab.sim.SimulationContact`. * Removed the attribute ``disable_contact_processing`` from :class:`~isaaclab.sim.SimulationContact`.
0.34.4 (2025-03-01) 0.34.5 (2025-03-01)
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
Added Added
...@@ -209,7 +209,7 @@ Changed ...@@ -209,7 +209,7 @@ Changed
they should use the :attr:`isaaclab.actuators.ImplicitActuatorCfg.velocity_limit_sim` attribute instead. they should use the :attr:`isaaclab.actuators.ImplicitActuatorCfg.velocity_limit_sim` attribute instead.
0.34.3 (2025-02-28) 0.34.4 (2025-02-28)
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
Added Added
...@@ -220,6 +220,16 @@ Added ...@@ -220,6 +220,16 @@ Added
Support for other Isaac Sim builds will become available in Isaac Sim 5.0. Support for other Isaac Sim builds will become available in Isaac Sim 5.0.
0.34.3 (2025-02-26)
~~~~~~~~~~~~~~~~~~~
Added
^^^^^
* Enablec specifying the placement of the simulation when viewed in an XR device. This is achieved by
adding an ``XrCfg`` environment configuration with ``anchor_pos`` and ``anchor_rot`` parameters.
0.34.2 (2025-02-21) 0.34.2 (2025-02-21)
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
......
...@@ -10,6 +10,7 @@ import weakref ...@@ -10,6 +10,7 @@ import weakref
from collections.abc import Callable from collections.abc import Callable
import carb import carb
import carb.input
import omni import omni
from ..device_base import DeviceBase from ..device_base import DeviceBase
......
...@@ -6,3 +6,4 @@ ...@@ -6,3 +6,4 @@
"""Keyboard device for SE(2) and SE(3) control.""" """Keyboard device for SE(2) and SE(3) control."""
from .se3_handtracking import Se3HandTracking from .se3_handtracking import Se3HandTracking
from .xr_cfg import XrCfg
...@@ -13,6 +13,7 @@ from typing import Final ...@@ -13,6 +13,7 @@ from typing import Final
import carb import carb
from ..device_base import DeviceBase from ..device_base import DeviceBase
from .xr_cfg import XrCfg
with contextlib.suppress(ModuleNotFoundError): with contextlib.suppress(ModuleNotFoundError):
from isaacsim.xr.openxr import OpenXR, OpenXRSpec from isaacsim.xr.openxr import OpenXR, OpenXRSpec
...@@ -20,6 +21,8 @@ with contextlib.suppress(ModuleNotFoundError): ...@@ -20,6 +21,8 @@ with contextlib.suppress(ModuleNotFoundError):
from . import teleop_command from . import teleop_command
import isaacsim.core.utils.prims as prim_utils
from isaaclab.markers import VisualizationMarkers from isaaclab.markers import VisualizationMarkers
from isaaclab.markers.config import FRAME_MARKER_CFG from isaaclab.markers.config import FRAME_MARKER_CFG
...@@ -49,6 +52,7 @@ class Se3HandTracking(DeviceBase): ...@@ -49,6 +52,7 @@ class Se3HandTracking(DeviceBase):
def __init__( def __init__(
self, self,
xr_cfg: XrCfg | None,
hand, hand,
abs=True, abs=True,
zero_out_xy_rotation=False, zero_out_xy_rotation=False,
...@@ -62,6 +66,7 @@ class Se3HandTracking(DeviceBase): ...@@ -62,6 +66,7 @@ class Se3HandTracking(DeviceBase):
self._previous_rot = Rotation.identity() self._previous_rot = Rotation.identity()
self._previous_grip_distance = 0.0 self._previous_grip_distance = 0.0
self._previous_gripper_command = False self._previous_gripper_command = False
self._xr_cfg = xr_cfg or XrCfg()
self._hand = hand self._hand = hand
self._abs = abs self._abs = abs
self._zero_out_xy_rotation = zero_out_xy_rotation self._zero_out_xy_rotation = zero_out_xy_rotation
...@@ -88,6 +93,13 @@ class Se3HandTracking(DeviceBase): ...@@ -88,6 +93,13 @@ class Se3HandTracking(DeviceBase):
self._frame_marker_cfg.markers["frame"].scale = (0.1, 0.1, 0.1) self._frame_marker_cfg.markers["frame"].scale = (0.1, 0.1, 0.1)
self._goal_marker = VisualizationMarkers(self._frame_marker_cfg.replace(prim_path="/Visuals/ee_goal")) self._goal_marker = VisualizationMarkers(self._frame_marker_cfg.replace(prim_path="/Visuals/ee_goal"))
# Specify the placement of the simulation when viewed in an XR device using a prim.
prim_utils.create_prim(
"/XRAnchor", "Xform", position=self._xr_cfg.anchor_pos, orientation=self._xr_cfg.anchor_rot
)
carb.settings.get_settings().set_string("/persistent/xr/profile/ar/anchorMode", "custom anchor")
carb.settings.get_settings().set_string("/xrstage/profile/ar/customAnchor", "/XRAnchor")
def __del__(self): def __del__(self):
return return
......
# Copyright (c) 2025, The Isaac Lab Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
# Ignore private usage of variables warning.
# pyright: reportPrivateUsage=none
from __future__ import annotations
from isaaclab.app import AppLauncher, run_tests
# Can set this to False to see the GUI for debugging.
HEADLESS = True
# Launch omniverse app.
app_launcher = AppLauncher(headless=HEADLESS, kit_args="--enable isaacsim.xr.openxr")
simulation_app = app_launcher.app
import numpy as np
import unittest
import carb
import omni.usd
from isaacsim.core.prims import XFormPrim
from isaacsim.xr.openxr import OpenXRSpec
from isaaclab.devices import Se3HandTracking
from isaaclab.devices.openxr import XrCfg
from isaaclab.envs import ManagerBasedEnv, ManagerBasedEnvCfg
from isaaclab.scene import InteractiveSceneCfg
from isaaclab.utils import configclass
@configclass
class EmptyManagerCfg:
"""Empty manager."""
pass
@configclass
class EmptySceneCfg(InteractiveSceneCfg):
"""Configuration for an empty scene."""
pass
@configclass
class EmptyEnvCfg(ManagerBasedEnvCfg):
"""Configuration for the empty test environment."""
scene: EmptySceneCfg = EmptySceneCfg(num_envs=1, env_spacing=1.0)
actions: EmptyManagerCfg = EmptyManagerCfg()
observations: EmptyManagerCfg = EmptyManagerCfg()
def __post_init__(self):
"""Post initialization."""
self.decimation = 5
self.episode_length_s = 30.0
self.sim.dt = 0.01 # 100Hz
self.sim.render_interval = 2
class TestSe3HandTracking(unittest.TestCase):
"""Test for Se3HandTracking"""
def test_xr_anchor(self):
env_cfg = EmptyEnvCfg()
env_cfg.xr = XrCfg(anchor_pos=(1, 2, 3), anchor_rot=(0, 1, 0, 0))
# Create a new stage.
omni.usd.get_context().new_stage()
# Create environment.
env = ManagerBasedEnv(cfg=env_cfg)
device = Se3HandTracking(env_cfg.xr, OpenXRSpec.XrHandEXT.XR_HAND_RIGHT_EXT)
# Check that the xr anchor prim is created with the correct pose.
xr_anchor_prim = XFormPrim("/XRAnchor")
self.assertTrue(xr_anchor_prim.is_valid())
position, orientation = xr_anchor_prim.get_world_poses()
np.testing.assert_almost_equal(position.tolist(), [[1, 2, 3]])
np.testing.assert_almost_equal(orientation.tolist(), [[0, 1, 0, 0]])
# Check that xr anchor mode and custom anchor are set correctly.
self.assertEqual(carb.settings.get_settings().get("/persistent/xr/profile/ar/anchorMode"), "custom anchor")
self.assertEqual(carb.settings.get_settings().get("/xrstage/profile/ar/customAnchor"), "/XRAnchor")
device.reset()
env.close()
def test_xr_anchor_default(self):
env_cfg = EmptyEnvCfg()
# Create a new stage.
omni.usd.get_context().new_stage()
# Create environment.
env = ManagerBasedEnv(cfg=env_cfg)
device = Se3HandTracking(None, OpenXRSpec.XrHandEXT.XR_HAND_RIGHT_EXT)
# Check that the xr anchor prim is created with the correct default pose.
xr_anchor_prim = XFormPrim("/XRAnchor")
self.assertTrue(xr_anchor_prim.is_valid())
position, orientation = xr_anchor_prim.get_world_poses()
np.testing.assert_almost_equal(position.tolist(), [[0, 0, 0]])
np.testing.assert_almost_equal(orientation.tolist(), [[1, 0, 0, 0]])
# Check that xr anchor mode and custom anchor are set correctly.
self.assertEqual(carb.settings.get_settings().get("/persistent/xr/profile/ar/anchorMode"), "custom anchor")
self.assertEqual(carb.settings.get_settings().get("/xrstage/profile/ar/customAnchor"), "/XRAnchor")
device.reset()
env.close()
if __name__ == "__main__":
run_tests()
# Copyright (c) 2025, The Isaac Lab Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
# ignore private usage of variables warning
# pyright: reportPrivateUsage=none
from __future__ import annotations
from isaaclab.utils import configclass
@configclass
class XrCfg:
"""Configuration for viewing and interacting with the environment through an XR device."""
anchor_pos: tuple[float, float, float] = (0.0, 0.0, 0.0)
"""Specifies the position (in m) of the simulation when viewed in an XR device.
Specifically: this position will appear at the origin of the XR device's local coordinate frame.
"""
anchor_rot: tuple[float, float, float] = (1.0, 0.0, 0.0, 0.0)
"""Specifies the rotation (as a quaternion) of the simulation when viewed in an XR device.
Specifically: this rotation will determine how the simulation is rotated with respect to the
origin of the XR device's local coordinate frame.
This quantity is only effective if :attr:`xr_anchor_pos` is set.
"""
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
from dataclasses import MISSING from dataclasses import MISSING
from isaaclab.devices.openxr import XrCfg
from isaaclab.scene import InteractiveSceneCfg from isaaclab.scene import InteractiveSceneCfg
from isaaclab.sim import SimulationCfg from isaaclab.sim import SimulationCfg
from isaaclab.utils import configclass from isaaclab.utils import configclass
...@@ -221,3 +222,6 @@ class DirectMARLEnvCfg: ...@@ -221,3 +222,6 @@ class DirectMARLEnvCfg:
The contents of the list cannot be modified during the entire training process. The contents of the list cannot be modified during the entire training process.
""" """
xr: XrCfg | None = None
"""Configuration for viewing and interacting with the environment through an XR device."""
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
from dataclasses import MISSING from dataclasses import MISSING
from isaaclab.devices.openxr import XrCfg
from isaaclab.scene import InteractiveSceneCfg from isaaclab.scene import InteractiveSceneCfg
from isaaclab.sim import SimulationCfg from isaaclab.sim import SimulationCfg
from isaaclab.utils import configclass from isaaclab.utils import configclass
...@@ -225,3 +226,6 @@ class DirectRLEnvCfg: ...@@ -225,3 +226,6 @@ class DirectRLEnvCfg:
wait_for_textures: bool = True wait_for_textures: bool = True
"""True to wait for assets to be loaded completely, False otherwise. Defaults to True.""" """True to wait for assets to be loaded completely, False otherwise. Defaults to True."""
xr: XrCfg | None = None
"""Configuration for viewing and interacting with the environment through an XR device."""
...@@ -12,6 +12,7 @@ configuring the environment instances, viewer settings, and simulation parameter ...@@ -12,6 +12,7 @@ configuring the environment instances, viewer settings, and simulation parameter
from dataclasses import MISSING from dataclasses import MISSING
import isaaclab.envs.mdp as mdp import isaaclab.envs.mdp as mdp
from isaaclab.devices.openxr import XrCfg
from isaaclab.managers import EventTermCfg as EventTerm from isaaclab.managers import EventTermCfg as EventTerm
from isaaclab.managers import RecorderManagerBaseCfg as DefaultEmptyRecorderManagerCfg from isaaclab.managers import RecorderManagerBaseCfg as DefaultEmptyRecorderManagerCfg
from isaaclab.scene import InteractiveSceneCfg from isaaclab.scene import InteractiveSceneCfg
...@@ -117,3 +118,6 @@ class ManagerBasedEnvCfg: ...@@ -117,3 +118,6 @@ class ManagerBasedEnvCfg:
wait_for_textures: bool = True wait_for_textures: bool = True
"""True to wait for assets to be loaded completely, False otherwise. Defaults to True.""" """True to wait for assets to be loaded completely, False otherwise. Defaults to True."""
xr: XrCfg | None = None
"""Configuration for viewing and interacting with the environment through an XR device."""
...@@ -7,6 +7,7 @@ from dataclasses import MISSING ...@@ -7,6 +7,7 @@ from dataclasses import MISSING
import isaaclab.sim as sim_utils import isaaclab.sim as sim_utils
from isaaclab.assets import ArticulationCfg, AssetBaseCfg from isaaclab.assets import ArticulationCfg, AssetBaseCfg
from isaaclab.devices.openxr import XrCfg
from isaaclab.envs import ManagerBasedRLEnvCfg from isaaclab.envs import ManagerBasedRLEnvCfg
from isaaclab.managers import ObservationGroupCfg as ObsGroup from isaaclab.managers import ObservationGroupCfg as ObsGroup
from isaaclab.managers import ObservationTermCfg as ObsTerm from isaaclab.managers import ObservationTermCfg as ObsTerm
...@@ -177,6 +178,11 @@ class StackEnvCfg(ManagerBasedRLEnvCfg): ...@@ -177,6 +178,11 @@ class StackEnvCfg(ManagerBasedRLEnvCfg):
events = None events = None
curriculum = None curriculum = None
xr: XrCfg = XrCfg(
anchor_pos=(-0.1, -0.5, -1.05),
anchor_rot=(0.866, 0, 0, -0.5),
)
def __post_init__(self): def __post_init__(self):
"""Post initialization.""" """Post initialization."""
# general settings # general settings
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment