Skip to content

Commit c22bafc

Browse files
hougantc-nvdarwiltz
authored andcommitted
Add quest support for local loco-manipulation
* Switch delta time to use wall clock time instead of physics step time. Update config values to compensate. * Add support for decoupling anchor rotation as a toggle when pressing right controller A button. * Fix reset not working Refactor motion controller specific code into OpenXRDeviceMotionController. If we are doing only "handtracking" as teleop_device, then we don't want all the headset anchoring code. Change the pre render callbacks to be a list so other systems can hook into it as well Rename classes from Controller to MotionController to limit confusion with Robotics controllers Move the code to alter the stage until after the telop device has been instantiated so we know what device we dealing with and if we need to change the ground plane material Format with ./isaaclab -f Update changelog, contributors and extension.toml Switch to rendering_dt instead of wall clock for determinism Address AI code review comments. Instead of having fixed heigh on by default, make it optional in the case of being able to go into crouch. We don't need to add pre render to ManagerBasedEnv since that can be done through XRCore. Moving new motion controller retargeters into new files Moving OpenXRDevice motion controller logic backup into OpenXRDevice, and moving interface enums into DeviceBase to remove retargeter dependancy on OpenXRDevice Adding retargeter requiremnts call Removing bespoke xr env adjustments Undo xr anchor height offset Cleaning up teleop script Code cleanup Update code comments Fix typo in RetargeterBase Properly set sim.device for retargeters Fixing greptile feedback Fix logic error if xr anchor rotation is disabled on AR startup Follow naming conventions for get_requirments
1 parent ad441d9 commit c22bafc

32 files changed

+988
-113
lines changed

CONTRIBUTORS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ Guidelines for modifications:
7272
* HoJin Jeon
7373
* Hongwei Xiong
7474
* Hongyu Li
75+
* Hougant Chen
7576
* Huihua Zhao
7677
* Iretiayo Akinola
7778
* Jack Zeng

docs/source/how-to/cloudxr_teleoperation.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -721,11 +721,11 @@ Here's an example of setting up hand tracking:
721721
722722
# Create retargeters
723723
position_retargeter = Se3AbsRetargeter(
724-
bound_hand=OpenXRDevice.TrackingTarget.HAND_RIGHT,
724+
bound_hand=DeviceBase.TrackingTarget.HAND_RIGHT,
725725
zero_out_xy_rotation=True,
726726
use_wrist_position=False # Use pinch position (thumb-index midpoint) instead of wrist
727727
)
728-
gripper_retargeter = GripperRetargeter(bound_hand=OpenXRDevice.TrackingTarget.HAND_RIGHT)
728+
gripper_retargeter = GripperRetargeter(bound_hand=DeviceBase.TrackingTarget.HAND_RIGHT)
729729
730730
# Create OpenXR device with hand tracking and both retargeters
731731
device = OpenXRDevice(
@@ -919,15 +919,15 @@ The retargeting system is designed to be extensible. You can create custom retar
919919
Any: The transformed control commands for the robot.
920920
"""
921921
# Access hand tracking data using TrackingTarget enum
922-
right_hand_data = data[OpenXRDevice.TrackingTarget.HAND_RIGHT]
922+
right_hand_data = data[DeviceBase.TrackingTarget.HAND_RIGHT]
923923
924924
# Extract specific joint positions and orientations
925925
wrist_pose = right_hand_data.get("wrist")
926926
thumb_tip_pose = right_hand_data.get("thumb_tip")
927927
index_tip_pose = right_hand_data.get("index_tip")
928928
929929
# Access head tracking data
930-
head_pose = data[OpenXRDevice.TrackingTarget.HEAD]
930+
head_pose = data[DeviceBase.TrackingTarget.HEAD]
931931
932932
# Process the tracking data and apply your custom logic
933933
# ...

scripts/environments/teleoperation/teleop_se3_agent.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
#
44
# SPDX-License-Identifier: BSD-3-Clause
55

6-
"""Script to run a keyboard teleoperation with Isaac Lab manipulation environments."""
6+
"""Script to run teleoperation with Isaac Lab manipulation environments.
7+
8+
Supports multiple input devices (e.g., keyboard, spacemouse, gamepad) and devices
9+
configured within the environment (including OpenXR-based hand tracking or motion
10+
controllers)."""
711

812
"""Launch Isaac Sim Simulator first."""
913

@@ -13,7 +17,7 @@
1317
from isaaclab.app import AppLauncher
1418

1519
# add argparse arguments
16-
parser = argparse.ArgumentParser(description="Keyboard teleoperation for Isaac Lab environments.")
20+
parser = argparse.ArgumentParser(description="Teleoperation for Isaac Lab environments.")
1721
parser.add_argument("--num_envs", type=int, default=1, help="Number of environments to simulate.")
1822
parser.add_argument(
1923
"--teleop_device",
@@ -78,7 +82,7 @@
7882

7983
def main() -> None:
8084
"""
81-
Run keyboard teleoperation with Isaac Lab manipulation environment.
85+
Run teleoperation with an Isaac Lab manipulation environment.
8286
8387
Creates the environment, sets up teleoperation interfaces and callbacks,
8488
and runs the main simulation loop until the application is closed.
@@ -98,8 +102,6 @@ def main() -> None:
98102
env_cfg.terminations.object_reached_goal = DoneTerm(func=mdp.object_reached_goal)
99103

100104
if args_cli.xr:
101-
# External cameras are not supported with XR teleop
102-
# Check for any camera configs and disable them
103105
env_cfg = remove_camera_configs(env_cfg)
104106
env_cfg.sim.render.antialiasing_mode = "DLSS"
105107

@@ -204,7 +206,7 @@ def stop_teleoperation() -> None:
204206
)
205207
else:
206208
logger.error(f"Unsupported teleop device: {args_cli.teleop_device}")
207-
logger.error("Supported devices: keyboard, spacemouse, gamepad, handtracking")
209+
logger.error("Configure the teleop device in the environment config.")
208210
env.close()
209211
simulation_app.close()
210212
return
@@ -254,6 +256,7 @@ def stop_teleoperation() -> None:
254256

255257
if should_reset_recording_instance:
256258
env.reset()
259+
teleop_interface.reset()
257260
should_reset_recording_instance = False
258261
print("Environment reset complete")
259262
except Exception as e:

source/isaaclab/config/extension.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22

33
# Note: Semantic Versioning is used: https://semver.org/
4-
version = "0.48.3"
4+
version = "0.48.4"
55

66
# Description
77
title = "Isaac Lab framework for Robot Learning"

source/isaaclab/docs/CHANGELOG.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
Changelog
22
---------
33

4+
0.48.4 (2025-11-13)
5+
~~~~~~~~~~~~~~~~~~~
6+
7+
Added
8+
^^^^^
9+
10+
* Added OpenXR motion controller support for the G1 robot locomanipulation environment
11+
``Isaac-PickPlace-Locomanipulation-G1-Abs-v0``. This enables teleoperation using XR motion controllers
12+
in addition to hand tracking.
13+
* Added :class:`OpenXRDeviceMotionController` for motion controller-based teleoperation with headset anchoring control.
14+
* Added motion controller-specific retargeters:
15+
* :class:`G1TriHandControllerUpperBodyRetargeterCfg` for upper body and hand control using motion controllers.
16+
* :class:`G1LowerBodyStandingControllerRetargeterCfg` for lower body control using motion controllers.
17+
18+
419
0.48.3 (2025-11-13)
520
~~~~~~~~~~~~~~~~~~~
621

source/isaaclab/isaaclab/devices/device_base.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from abc import ABC, abstractmethod
1010
from collections.abc import Callable
1111
from dataclasses import dataclass, field
12+
from enum import Enum
1213
from typing import Any
1314

1415
from isaaclab.devices.retargeter_base import RetargeterBase, RetargeterCfg
@@ -58,9 +59,13 @@ def __init__(self, retargeters: list[RetargeterBase] | None = None):
5859
"""
5960
# Initialize empty list if None is provided
6061
self._retargeters = retargeters or []
62+
# Aggregate required features across all retargeters
63+
self._required_features = set()
64+
for retargeter in self._retargeters:
65+
self._required_features.update(retargeter.get_requirements())
6166

6267
def __str__(self) -> str:
63-
"""Returns: A string containing the information of joystick."""
68+
"""Returns: A string identifier for the device."""
6469
return f"{self.__class__.__name__}"
6570

6671
"""
@@ -123,3 +128,32 @@ def advance(self) -> torch.Tensor:
123128
# With multiple retargeters, return a tuple of outputs
124129
# Concatenate retargeted outputs into a single tensor
125130
return torch.cat([retargeter.retarget(raw_data) for retargeter in self._retargeters], dim=-1)
131+
132+
# -----------------------------
133+
# Shared data layout helpers (for retargeters across devices)
134+
# -----------------------------
135+
class TrackingTarget(Enum):
136+
"""Standard tracking targets shared across devices."""
137+
138+
HAND_LEFT = 0
139+
HAND_RIGHT = 1
140+
HEAD = 2
141+
CONTROLLER_LEFT = 3
142+
CONTROLLER_RIGHT = 4
143+
144+
class MotionControllerDataRowIndex(Enum):
145+
"""Rows in the motion-controller 2x7 array."""
146+
147+
POSE = 0
148+
INPUTS = 1
149+
150+
class MotionControllerInputIndex(Enum):
151+
"""Indices in the motion-controller input row."""
152+
153+
THUMBSTICK_X = 0
154+
THUMBSTICK_Y = 1
155+
TRIGGER = 2
156+
SQUEEZE = 3
157+
BUTTON_0 = 4
158+
BUTTON_1 = 5
159+
PADDING = 6

source/isaaclab/isaaclab/devices/openxr/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@
77

88
from .manus_vive import ManusVive, ManusViveCfg
99
from .openxr_device import OpenXRDevice, OpenXRDeviceCfg
10-
from .xr_cfg import XrCfg, remove_camera_configs
10+
from .xr_cfg import XrAnchorRotationMode, XrCfg, remove_camera_configs

source/isaaclab/isaaclab/devices/openxr/manus_vive.py

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import numpy as np
1414
from collections.abc import Callable
1515
from dataclasses import dataclass
16-
from enum import Enum
1716

1817
import carb
1918
from isaacsim.core.version import get_version
@@ -22,7 +21,6 @@
2221
from isaaclab.devices.retargeter_base import RetargeterBase
2322

2423
from ..device_base import DeviceBase, DeviceCfg
25-
from .openxr_device import OpenXRDevice
2624
from .xr_cfg import XrCfg
2725

2826
# For testing purposes, we need to mock the XRCore
@@ -61,13 +59,6 @@ class ManusVive(DeviceBase):
6159
data is transformed into robot control commands suitable for teleoperation.
6260
"""
6361

64-
class TrackingTarget(Enum):
65-
"""Enum class specifying what to track with Manus+Vive. Consistent with :class:`OpenXRDevice.TrackingTarget`."""
66-
67-
HAND_LEFT = 0
68-
HAND_RIGHT = 1
69-
HEAD = 2
70-
7162
TELEOP_COMMAND_EVENT_TYPE = "teleop_command"
7263

7364
def __init__(self, cfg: ManusViveCfg, retargeters: list[RetargeterBase] | None = None):
@@ -192,9 +183,9 @@ def _get_raw_data(self) -> dict:
192183
joint_name = HAND_JOINT_MAP[int(index)]
193184
result[hand][joint_name] = np.array(pose["position"] + pose["orientation"], dtype=np.float32)
194185
return {
195-
OpenXRDevice.TrackingTarget.HAND_LEFT: result["left"],
196-
OpenXRDevice.TrackingTarget.HAND_RIGHT: result["right"],
197-
OpenXRDevice.TrackingTarget.HEAD: self._calculate_headpose(),
186+
DeviceBase.TrackingTarget.HAND_LEFT: result["left"],
187+
DeviceBase.TrackingTarget.HAND_RIGHT: result["right"],
188+
DeviceBase.TrackingTarget.HEAD: self._calculate_headpose(),
198189
}
199190

200191
def _calculate_headpose(self) -> np.ndarray:

0 commit comments

Comments
 (0)