Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
14811d2
Moving retargeter and device declariation out of factory and into the…
rwiltz Oct 30, 2025
7080b61
Support agile loco manipulation for g1 robot using motion controllers…
hougantc-nvda Oct 22, 2025
e0f00f8
* Switch delta time to use wall clock time instead of physics step ti…
hougantc-nvda Oct 24, 2025
1e70d7d
Refactor motion controller specific code into OpenXRDeviceMotionContr…
hougantc-nvda Oct 24, 2025
4755119
Change the pre render callbacks to be a list so other systems can hoo…
hougantc-nvda Oct 24, 2025
b31b65f
Rename classes from Controller to MotionController to limit confusion…
hougantc-nvda Oct 24, 2025
de9c1fe
Move the code to alter the stage until after the telop device has bee…
hougantc-nvda Oct 24, 2025
f607ec8
Format with ./isaaclab -f
hougantc-nvda Oct 24, 2025
dfed6ea
Update changelog, contributors and extension.toml
hougantc-nvda Oct 24, 2025
9ce5a70
Switch to rendering_dt instead of wall clock for determinism
hougantc-nvda Oct 27, 2025
3fc05af
Address AI code review comments.
hougantc-nvda Oct 28, 2025
9d8b244
Instead of having fixed heigh on by default, make it optional in the …
hougantc-nvda Oct 31, 2025
e9b09e7
We don't need to add pre render to ManagerBasedEnv since that can be …
hougantc-nvda Nov 3, 2025
39b415f
Moving new motion controller retargeters into new files
rwiltz Nov 5, 2025
1fefaf0
Moving OpenXRDevice motion controller logic backup into OpenXRDevice,…
rwiltz Nov 5, 2025
44ad99e
Adding retargeter requiremnts call
rwiltz Nov 5, 2025
ed28313
Removing bespoke xr env adjustments
rwiltz Nov 5, 2025
ebe57f1
Undo xr anchor height offset
rwiltz Nov 5, 2025
d94375e
Cleaning up teleop script
rwiltz Nov 5, 2025
3ff0ce8
Code cleanup
rwiltz Nov 5, 2025
c267aaf
Update code comments
rwiltz Nov 5, 2025
f35e00b
Fix typo in RetargeterBase
rwiltz Nov 5, 2025
6a69037
Properly set sim.device for retargeters
rwiltz Nov 5, 2025
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
1 change: 1 addition & 0 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ Guidelines for modifications:
* HoJin Jeon
* Hongwei Xiong
* Hongyu Li
* Hougant Chen
* Huihua Zhao
* Iretiayo Akinola
* Jack Zeng
Expand Down
17 changes: 7 additions & 10 deletions docs/source/how-to/cloudxr_teleoperation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -721,11 +721,11 @@ Here's an example of setting up hand tracking:

# Create retargeters
position_retargeter = Se3AbsRetargeter(
bound_hand=OpenXRDevice.TrackingTarget.HAND_RIGHT,
bound_hand=DeviceBase.TrackingTarget.HAND_RIGHT,
zero_out_xy_rotation=True,
use_wrist_position=False # Use pinch position (thumb-index midpoint) instead of wrist
)
gripper_retargeter = GripperRetargeter(bound_hand=OpenXRDevice.TrackingTarget.HAND_RIGHT)
gripper_retargeter = GripperRetargeter(bound_hand=DeviceBase.TrackingTarget.HAND_RIGHT)

# Create OpenXR device with hand tracking and both retargeters
device = OpenXRDevice(
Expand Down Expand Up @@ -919,34 +919,31 @@ The retargeting system is designed to be extensible. You can create custom retar
Any: The transformed control commands for the robot.
"""
# Access hand tracking data using TrackingTarget enum
right_hand_data = data[OpenXRDevice.TrackingTarget.HAND_RIGHT]
right_hand_data = data[DeviceBase.TrackingTarget.HAND_RIGHT]

# Extract specific joint positions and orientations
wrist_pose = right_hand_data.get("wrist")
thumb_tip_pose = right_hand_data.get("thumb_tip")
index_tip_pose = right_hand_data.get("index_tip")

# Access head tracking data
head_pose = data[OpenXRDevice.TrackingTarget.HEAD]
head_pose = data[DeviceBase.TrackingTarget.HEAD]

# Process the tracking data and apply your custom logic
# ...

# Return control commands in appropriate format
return torch.tensor([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0]) # Example output

3. Register your retargeter with the factory by adding it to the ``RETARGETER_MAP``:
3. Register your retargeter by setting ``retargeter_type`` on the config class:

.. code-block:: python

# Import your retargeter at the top of your module
from my_package.retargeters import MyCustomRetargeter, MyCustomRetargeterCfg

# Add your retargeter to the factory
from isaaclab.devices.teleop_device_factory import RETARGETER_MAP

# Register your retargeter type with its constructor
RETARGETER_MAP[MyCustomRetargeterCfg] = MyCustomRetargeter
# Link the config to the implementation for factory construction
MyCustomRetargeterCfg.retargeter_type = MyCustomRetargeter

4. Now you can use your custom retargeter in teleop device configurations:

Expand Down
15 changes: 9 additions & 6 deletions scripts/environments/teleoperation/teleop_se3_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
#
# SPDX-License-Identifier: BSD-3-Clause

"""Script to run a keyboard teleoperation with Isaac Lab manipulation environments."""
"""Script to run teleoperation with Isaac Lab manipulation environments.

Supports multiple input devices (e.g., keyboard, spacemouse, gamepad) and devices
configured within the environment (including OpenXR-based hand tracking or motion
controllers)."""

"""Launch Isaac Sim Simulator first."""

Expand All @@ -13,7 +17,7 @@
from isaaclab.app import AppLauncher

# add argparse arguments
parser = argparse.ArgumentParser(description="Keyboard teleoperation for Isaac Lab environments.")
parser = argparse.ArgumentParser(description="Teleoperation for Isaac Lab environments.")
parser.add_argument("--num_envs", type=int, default=1, help="Number of environments to simulate.")
parser.add_argument(
"--teleop_device",
Expand Down Expand Up @@ -76,7 +80,7 @@

def main() -> None:
"""
Run keyboard teleoperation with Isaac Lab manipulation environment.
Run teleoperation with an Isaac Lab manipulation environment.

Creates the environment, sets up teleoperation interfaces and callbacks,
and runs the main simulation loop until the application is closed.
Expand All @@ -96,8 +100,6 @@ def main() -> None:
env_cfg.terminations.object_reached_goal = DoneTerm(func=mdp.object_reached_goal)

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

Expand Down Expand Up @@ -200,7 +202,7 @@ def stop_teleoperation() -> None:
)
else:
omni.log.error(f"Unsupported teleop device: {args_cli.teleop_device}")
omni.log.error("Supported devices: keyboard, spacemouse, gamepad, handtracking")
omni.log.error("Configure the teleop device in the environment config.")
env.close()
simulation_app.close()
return
Expand Down Expand Up @@ -250,6 +252,7 @@ def stop_teleoperation() -> None:

if should_reset_recording_instance:
env.reset()
teleop_interface.reset()
should_reset_recording_instance = False
print("Environment reset complete")
except Exception as e:
Expand Down
2 changes: 1 addition & 1 deletion source/isaaclab/config/extension.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]

# Note: Semantic Versioning is used: https://semver.org/
version = "0.47.4"
version = "0.47.5"

# Description
title = "Isaac Lab framework for Robot Learning"
Expand Down
9 changes: 9 additions & 0 deletions source/isaaclab/docs/CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
Changelog
---------

0.47.5 (2025-10-30)
~~~~~~~~~~~~~~~~~~~

Changed
^^^^^^^

* Moved retargeter and device declaration out of factory and into the devices/retargeters themselves.


0.47.4 (2025-10-30)
~~~~~~~~~~~~~~~~~~~

Expand Down
42 changes: 41 additions & 1 deletion source/isaaclab/isaaclab/devices/device_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from abc import ABC, abstractmethod
from collections.abc import Callable
from dataclasses import dataclass, field
from enum import Enum
from typing import Any

from isaaclab.devices.retargeter_base import RetargeterBase, RetargeterCfg
Expand All @@ -18,8 +19,14 @@
class DeviceCfg:
"""Configuration for teleoperation devices."""

# Whether teleoperation should start active by default
teleoperation_active_default: bool = True
# Torch device string to place output tensors on
sim_device: str = "cpu"
# Retargeters that transform device data into robot commands
retargeters: list[RetargeterCfg] = field(default_factory=list)
# Concrete device class to construct for this config. Set by each device module.
device_type: type["DeviceBase"] | None = None


@dataclass
Expand Down Expand Up @@ -52,9 +59,13 @@ def __init__(self, retargeters: list[RetargeterBase] | None = None):
"""
# Initialize empty list if None is provided
self._retargeters = retargeters or []
# Aggregate required features across all retargeters
self._required_features = set()
for retargeter in self._retargeters:
self._required_features.update(retargeter.GetRequirements())

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

"""
Expand Down Expand Up @@ -117,3 +128,32 @@ def advance(self) -> torch.Tensor:
# With multiple retargeters, return a tuple of outputs
# Concatenate retargeted outputs into a single tensor
return torch.cat([retargeter.retarget(raw_data) for retargeter in self._retargeters], dim=-1)

# -----------------------------
# Shared data layout helpers (for retargeters across devices)
# -----------------------------
class TrackingTarget(Enum):
"""Standard tracking targets shared across devices."""

HAND_LEFT = 0
HAND_RIGHT = 1
HEAD = 2
CONTROLLER_LEFT = 3
CONTROLLER_RIGHT = 4

class MotionControllerDataRowIndex(Enum):
"""Rows in the motion-controller 2x7 array."""

POSE = 0
INPUTS = 1

class MotionControllerInputIndex(Enum):
"""Indices in the motion-controller input row."""

THUMBSTICK_X = 0
THUMBSTICK_Y = 1
TRIGGER = 2
SQUEEZE = 3
BUTTON_0 = 4
BUTTON_1 = 5
PADDING = 6
23 changes: 13 additions & 10 deletions source/isaaclab/isaaclab/devices/gamepad/se2_gamepad.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

"""Gamepad controller for SE(2) control."""

from __future__ import annotations

import numpy as np
import torch
import weakref
Expand All @@ -18,16 +20,6 @@
from ..device_base import DeviceBase, DeviceCfg


@dataclass
class Se2GamepadCfg(DeviceCfg):
"""Configuration for SE2 gamepad devices."""

v_x_sensitivity: float = 1.0
v_y_sensitivity: float = 1.0
omega_z_sensitivity: float = 1.0
dead_zone: float = 0.01


class Se2Gamepad(DeviceBase):
r"""A gamepad controller for sending SE(2) commands as velocity commands.

Expand Down Expand Up @@ -209,3 +201,14 @@ def _resolve_command_buffer(self, raw_command: np.ndarray) -> np.ndarray:
command[command_sign] *= -1

return command


@dataclass
class Se2GamepadCfg(DeviceCfg):
"""Configuration for SE2 gamepad devices."""

v_x_sensitivity: float = 1.0
v_y_sensitivity: float = 1.0
omega_z_sensitivity: float = 1.0
dead_zone: float = 0.01
device_type: type[DeviceBase] = Se2Gamepad
24 changes: 13 additions & 11 deletions source/isaaclab/isaaclab/devices/gamepad/se3_gamepad.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

"""Gamepad controller for SE(3) control."""

from __future__ import annotations

import numpy as np
import torch
import weakref
Expand All @@ -18,17 +20,6 @@
from ..device_base import DeviceBase, DeviceCfg


@dataclass
class Se3GamepadCfg(DeviceCfg):
"""Configuration for SE3 gamepad devices."""

gripper_term: bool = True
dead_zone: float = 0.01 # For gamepad devices
pos_sensitivity: float = 1.0
rot_sensitivity: float = 1.6
retargeters: None = None


class Se3Gamepad(DeviceBase):
"""A gamepad controller for sending SE(3) commands as delta poses and binary command (open/close).

Expand Down Expand Up @@ -264,3 +255,14 @@ def _resolve_command_buffer(self, raw_command: np.ndarray) -> np.ndarray:
delta_command[delta_command_sign] *= -1

return delta_command


@dataclass
class Se3GamepadCfg(DeviceCfg):
"""Configuration for SE3 gamepad devices."""

gripper_term: bool = True
dead_zone: float = 0.01 # For gamepad devices
pos_sensitivity: float = 1.0
rot_sensitivity: float = 1.6
device_type: type[DeviceBase] = Se3Gamepad
21 changes: 12 additions & 9 deletions source/isaaclab/isaaclab/devices/keyboard/se2_keyboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

"""Keyboard controller for SE(2) control."""

from __future__ import annotations

import numpy as np
import torch
import weakref
Expand All @@ -17,15 +19,6 @@
from ..device_base import DeviceBase, DeviceCfg


@dataclass
class Se2KeyboardCfg(DeviceCfg):
"""Configuration for SE2 keyboard devices."""

v_x_sensitivity: float = 0.8
v_y_sensitivity: float = 0.4
omega_z_sensitivity: float = 1.0


class Se2Keyboard(DeviceBase):
r"""A keyboard controller for sending SE(2) commands as velocity commands.

Expand Down Expand Up @@ -178,3 +171,13 @@ def _create_key_bindings(self):
"NUMPAD_9": np.asarray([0.0, 0.0, -1.0]) * self.omega_z_sensitivity,
"X": np.asarray([0.0, 0.0, -1.0]) * self.omega_z_sensitivity,
}


@dataclass
class Se2KeyboardCfg(DeviceCfg):
"""Configuration for SE2 keyboard devices."""

v_x_sensitivity: float = 0.8
v_y_sensitivity: float = 0.4
omega_z_sensitivity: float = 1.0
device_type: type[DeviceBase] = Se2Keyboard
23 changes: 13 additions & 10 deletions source/isaaclab/isaaclab/devices/keyboard/se3_keyboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

"""Keyboard controller for SE(3) control."""

from __future__ import annotations

import numpy as np
import torch
import weakref
Expand All @@ -18,16 +20,6 @@
from ..device_base import DeviceBase, DeviceCfg


@dataclass
class Se3KeyboardCfg(DeviceCfg):
"""Configuration for SE3 keyboard devices."""

gripper_term: bool = True
pos_sensitivity: float = 0.4
rot_sensitivity: float = 0.8
retargeters: None = None


class Se3Keyboard(DeviceBase):
"""A keyboard controller for sending SE(3) commands as delta poses and binary command (open/close).

Expand Down Expand Up @@ -206,3 +198,14 @@ def _create_key_bindings(self):
"C": np.asarray([0.0, 0.0, 1.0]) * self.rot_sensitivity,
"V": np.asarray([0.0, 0.0, -1.0]) * self.rot_sensitivity,
}


@dataclass
class Se3KeyboardCfg(DeviceCfg):
"""Configuration for SE3 keyboard devices."""

gripper_term: bool = True
pos_sensitivity: float = 0.4
rot_sensitivity: float = 0.8
retargeters: None = None
device_type: type[DeviceBase] = Se3Keyboard
2 changes: 1 addition & 1 deletion source/isaaclab/isaaclab/devices/openxr/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@

from .manus_vive import ManusVive, ManusViveCfg
from .openxr_device import OpenXRDevice, OpenXRDeviceCfg
from .xr_cfg import XrCfg, remove_camera_configs
from .xr_cfg import XrAnchorRotationMode, XrCfg, remove_camera_configs
Loading