Skip to content

Commit c8e97b5

Browse files
authored
[3.0.0-beta2] Fix ModuleNotFoundError: No module named 'isaacteleop' when parsing teleop env configs (#6116)
# Description Fixes a `ModuleNotFoundError: No module named 'isaacteleop'` that made several teleop-enabled environments crash at parse time on systems where `isaacteleop` is not installed (e.g. DGX Spark). This broke the following documented QA workflows: - `Isaac-NutPour-GR1T2-Pink-IK-Abs-v0` via `play.py` - `Isaac-G1-SteeringWheel-Locomanipulation` via SDG `generate_data.py` - G1 locomanipulation Mimic `generate_dataset.py` The root cause was in `isaaclab_teleop/isaac_teleop_cfg.py`, which imported `isaacteleop.teleop_session_manager` at **module load time**. Because `parse_env_cfg` both imports and instantiates env configs, any env referencing `IsaacTeleopCfg` raised the error before any simulation code ran. Fixes # (issue) ## Type of change - Bug fix (non-breaking change which fixes an issue) ## Checklist - [x] I have read and understood the [contribution guidelines](https://isaac-sim.github.io/IsaacLab/main/source/refs/contributing.html) - [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
1 parent ede902e commit c8e97b5

10 files changed

Lines changed: 94 additions & 81 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Changed
2+
^^^^^^^
3+
4+
* Removed the per-environment ``try/except ImportError`` guards around the
5+
``isaacteleop`` / ``isaaclab_teleop`` imports in the Galbot, Franka, GR1T2
6+
nut-pour, and GR1T2 exhaust-pipe task configs. The imports are now
7+
unconditional, matching the other teleop task configs, now that
8+
:class:`~isaaclab_teleop.IsaacTeleopCfg` no longer requires the optional
9+
``isaacteleop`` package at import time. No migration is needed:
10+
``isaaclab_teleop`` ships with Isaac Lab and teleoperation behavior is
11+
unchanged.

source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/pick_place/exhaustpipe_gr1t2_base_env_cfg.py

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,14 @@
33
#
44
# SPDX-License-Identifier: BSD-3-Clause
55

6-
import logging
76
import tempfile
87
from dataclasses import MISSING
98

10-
try:
11-
from isaaclab_teleop import XrCfg
9+
from isaaclab_teleop import XrCfg
1210

13-
_TELEOP_AVAILABLE = True
14-
except ImportError:
15-
_TELEOP_AVAILABLE = False
16-
logging.getLogger(__name__).warning("isaaclab_teleop is not installed. XR teleoperation features will be disabled.")
11+
# Marker consumed by ``env_test_utils._is_teleop_env`` to bucket teleop
12+
# environments in the test suite.
13+
_TELEOP_AVAILABLE = True
1714

1815
import isaaclab.envs.mdp as base_mdp
1916
import isaaclab.sim as sim_utils
@@ -328,8 +325,7 @@ def __post_init__(self):
328325
# List of image observations in policy observations
329326
self.image_obs_list = ["robot_pov_cam"]
330327

331-
if _TELEOP_AVAILABLE:
332-
self.xr = XrCfg(
333-
anchor_pos=(0.0, 0.0, 0.0),
334-
anchor_rot=(0.0, 0.0, 0.0, 1.0),
335-
)
328+
self.xr = XrCfg(
329+
anchor_pos=(0.0, 0.0, 0.0),
330+
anchor_rot=(0.0, 0.0, 0.0, 1.0),
331+
)

source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/pick_place/nutpour_gr1t2_base_env_cfg.py

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,14 @@
33
#
44
# SPDX-License-Identifier: BSD-3-Clause
55

6-
import logging
76
import tempfile
87
from dataclasses import MISSING
98

10-
try:
11-
from isaaclab_teleop import XrCfg
9+
from isaaclab_teleop import XrCfg
1210

13-
_TELEOP_AVAILABLE = True
14-
except ImportError:
15-
_TELEOP_AVAILABLE = False
16-
logging.getLogger(__name__).warning("isaaclab_teleop is not installed. XR teleoperation features will be disabled.")
11+
# Marker consumed by ``env_test_utils._is_teleop_env`` to bucket teleop
12+
# environments in the test suite.
13+
_TELEOP_AVAILABLE = True
1714

1815
import isaaclab.envs.mdp as base_mdp
1916
import isaaclab.sim as sim_utils
@@ -363,8 +360,7 @@ def __post_init__(self):
363360
# List of image observations in policy observations
364361
self.image_obs_list = ["robot_pov_cam"]
365362

366-
if _TELEOP_AVAILABLE:
367-
self.xr = XrCfg(
368-
anchor_pos=(0.0, 0.0, 0.0),
369-
anchor_rot=(0.0, 0.0, 0.0, 1.0),
370-
)
363+
self.xr = XrCfg(
364+
anchor_pos=(0.0, 0.0, 0.0),
365+
anchor_rot=(0.0, 0.0, 0.0, 1.0),
366+
)

source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/stack/config/franka/stack_ik_abs_env_cfg.py

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

6-
import logging
6+
from isaaclab_teleop import IsaacTeleopCfg
77

88
from isaaclab.controllers.differential_ik_cfg import DifferentialIKControllerCfg
99
from isaaclab.envs.mdp.actions.actions_cfg import DifferentialInverseKinematicsActionCfg
1010
from isaaclab.utils.configclass import configclass
1111

12-
try:
13-
import isaacteleop # noqa: F401 -- pipeline builders need isaacteleop at runtime
14-
from isaaclab_teleop import IsaacTeleopCfg
15-
16-
_TELEOP_AVAILABLE = True
17-
except ImportError:
18-
_TELEOP_AVAILABLE = False
19-
logging.getLogger(__name__).warning("isaaclab_teleop is not installed. XR teleoperation features will be disabled.")
12+
# Marker consumed by ``env_test_utils._is_teleop_env`` to bucket teleop
13+
# environments in the test suite.
14+
_TELEOP_AVAILABLE = True
2015

2116
from . import stack_joint_pos_env_cfg
2217

@@ -130,9 +125,8 @@ def __post_init__(self):
130125
)
131126

132127
# IsaacTeleop-based teleoperation pipeline
133-
if _TELEOP_AVAILABLE:
134-
self.isaac_teleop = IsaacTeleopCfg(
135-
pipeline_builder=_build_franka_stack_pipeline,
136-
sim_device=self.sim.device,
137-
xr_cfg=self.xr,
138-
)
128+
self.isaac_teleop = IsaacTeleopCfg(
129+
pipeline_builder=_build_franka_stack_pipeline,
130+
sim_device=self.sim.device,
131+
xr_cfg=self.xr,
132+
)

source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/stack/config/galbot/stack_joint_pos_env_cfg.py

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@
44
# SPDX-License-Identifier: BSD-3-Clause
55

66

7-
import logging
8-
97
from isaaclab_physx.assets import SurfaceGripperCfg
8+
from isaaclab_teleop import IsaacTeleopCfg
109

1110
from isaaclab.assets import RigidObjectCfg
1211
from isaaclab.envs.mdp.actions.actions_cfg import SurfaceGripperBinaryActionCfg
@@ -29,14 +28,9 @@
2928
raise_if_surface_gripper_on_newton,
3029
)
3130

32-
try:
33-
import isaacteleop # noqa: F401 -- pipeline builders need isaacteleop at runtime
34-
from isaaclab_teleop import IsaacTeleopCfg
35-
36-
_TELEOP_AVAILABLE = True
37-
except ImportError:
38-
_TELEOP_AVAILABLE = False
39-
logging.getLogger(__name__).warning("isaaclab_teleop is not installed. XR teleoperation features will be disabled.")
31+
# Marker consumed by ``env_test_utils._is_teleop_env`` to bucket teleop
32+
# environments in the test suite.
33+
_TELEOP_AVAILABLE = True
4034

4135
##
4236
# Pre-defined configs
@@ -342,12 +336,11 @@ def __post_init__(self):
342336
)
343337

344338
# IsaacTeleop-based teleoperation pipeline (left hand)
345-
if _TELEOP_AVAILABLE:
346-
self.isaac_teleop = IsaacTeleopCfg(
347-
pipeline_builder=lambda: _build_se3_abs_gripper_pipeline(hand_side="left"),
348-
sim_device=self.sim.device,
349-
xr_cfg=self.xr,
350-
)
339+
self.isaac_teleop = IsaacTeleopCfg(
340+
pipeline_builder=lambda: _build_se3_abs_gripper_pipeline(hand_side="left"),
341+
sim_device=self.sim.device,
342+
xr_cfg=self.xr,
343+
)
351344

352345

353346
@configclass
@@ -388,9 +381,8 @@ def __post_init__(self):
388381
self.scene.ee_frame.target_frames[0].prim_path = "{ENV_REGEX_NS}/Robot/right_suction_cup_tcp_link"
389382

390383
# IsaacTeleop-based teleoperation pipeline (right hand)
391-
if _TELEOP_AVAILABLE:
392-
self.isaac_teleop = IsaacTeleopCfg(
393-
pipeline_builder=lambda: _build_se3_abs_gripper_pipeline(hand_side="right"),
394-
sim_device=self.sim.device,
395-
xr_cfg=self.xr,
396-
)
384+
self.isaac_teleop = IsaacTeleopCfg(
385+
pipeline_builder=lambda: _build_se3_abs_gripper_pipeline(hand_side="right"),
386+
sim_device=self.sim.device,
387+
xr_cfg=self.xr,
388+
)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Fixed
2+
^^^^^
3+
4+
* Fixed :class:`~isaaclab_teleop.IsaacTeleopCfg` requiring the optional ``isaacteleop``
5+
package at import and construction time. Environments that reference it (e.g. the GR1T2
6+
and Unitree G1 pick-place and locomanipulation tasks) failed to parse with
7+
``No module named 'isaacteleop'`` on systems where ``isaacteleop`` is not installed
8+
(e.g. DGX Spark). The ``isaacteleop`` import is now deferred, and
9+
:attr:`~isaaclab_teleop.IsaacTeleopCfg.retargeting_execution` defaults to ``None`` and is
10+
resolved to IsaacTeleop's pipelined, deadline-paced default when a teleop session starts.

source/isaaclab_teleop/docs/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ rendering without blocking.
120120
| `retargeters_to_tune` | `Callable[[], list[BaseRetargeter]] \| None` | `None` | Retargeters to expose in the tuning UI |
121121
| `plugins` | `list[PluginConfig]` | `[]` | IsaacTeleop plugin configurations |
122122
| `sim_device` | `str` | `"cuda:0"` | Torch device for output action tensors |
123-
| `retargeting_execution` | `RetargetingExecutionConfig` | `mode="pipelined", pacing=DeadlinePacingConfig(safety_margin_s=0.025)` | IsaacTeleop retargeting execution settings |
123+
| `retargeting_execution` | `RetargetingExecutionConfig \| None` | `None` (resolved at session start to `mode="pipelined", pacing=DeadlinePacingConfig(safety_margin_s=0.025)`) | IsaacTeleop retargeting execution settings; deferred so the config imports without `isaacteleop` |
124124
| `teleoperation_active_default` | `bool` | `False` | Whether teleoperation is active on session start |
125125
| `app_name` | `str` | `"IsaacLabTeleop"` | Application name for the IsaacTeleop session |
126126

source/isaaclab_teleop/isaaclab_teleop/isaac_teleop_cfg.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
from pathlib import Path
1313
from typing import TYPE_CHECKING
1414

15-
from isaacteleop.teleop_session_manager import DeadlinePacingConfig, RetargetingExecutionConfig
16-
1715
from isaaclab.utils.configclass import configclass
1816

1917
from .control_events import TELEOP_CONTROL_CHANNEL_UUID
@@ -29,7 +27,7 @@
2927

3028
if TYPE_CHECKING:
3129
from isaacteleop.retargeting_engine.interface import BaseRetargeter, OutputCombiner
32-
from isaacteleop.teleop_session_manager import PluginConfig
30+
from isaacteleop.teleop_session_manager import PluginConfig, RetargetingExecutionConfig
3331

3432

3533
@configclass
@@ -97,16 +95,16 @@ def build_pipeline():
9795
sim_device: str = "cuda:0"
9896
"""Torch device string for placing output action tensors."""
9997

100-
retargeting_execution: RetargetingExecutionConfig = field(
101-
default_factory=lambda: RetargetingExecutionConfig(
102-
mode="pipelined",
103-
pacing=DeadlinePacingConfig(safety_margin_s=0.025),
104-
)
105-
)
98+
retargeting_execution: RetargetingExecutionConfig | None = None
10699
"""IsaacTeleop retargeting execution settings.
107100
108-
Isaac Lab opts into IsaacTeleop's pipelined execution by default. Set this
109-
to ``RetargetingExecutionConfig(mode="sync")`` for exact current-frame
101+
Left as ``None`` by default so that importing and constructing this config
102+
never requires the optional ``isaacteleop`` package (e.g. on platforms where
103+
it is not installed). When ``None``, Isaac Lab resolves it at session start to
104+
IsaacTeleop's pipelined, deadline-paced default
105+
(``RetargetingExecutionConfig(mode="pipelined", pacing=DeadlinePacingConfig(safety_margin_s=0.025))``),
106+
where ``isaacteleop`` is guaranteed to be available. Set this explicitly to
107+
``RetargetingExecutionConfig(mode="sync")`` for exact current-frame
110108
retargeting while debugging or comparing behavior.
111109
"""
112110

source/isaaclab_teleop/isaaclab_teleop/session_lifecycle.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,21 @@ def try_start_session(self) -> bool:
502502
"""
503503
return self._try_start_session()
504504

505+
def _resolved_retargeting_execution(self):
506+
"""Return the retargeting execution settings for the IsaacTeleop session.
507+
508+
:attr:`~isaaclab_teleop.IsaacTeleopCfg.retargeting_execution` defaults to
509+
``None`` so that constructing the config never requires the optional
510+
``isaacteleop`` package. The default is resolved here, at session-start
511+
time, where ``isaacteleop`` is guaranteed to be importable.
512+
"""
513+
if self._cfg.retargeting_execution is not None:
514+
return self._cfg.retargeting_execution
515+
516+
from isaacteleop.teleop_session_manager import DeadlinePacingConfig, RetargetingExecutionConfig
517+
518+
return RetargetingExecutionConfig(mode="pipelined", pacing=DeadlinePacingConfig(safety_margin_s=0.025))
519+
505520
def _try_start_session(self) -> bool:
506521
"""Attempt to create and start the IsaacTeleop session.
507522
@@ -563,7 +578,7 @@ def _try_start_session(self) -> bool:
563578
teleop_control_pipeline=self._teleop_control_pipeline,
564579
plugins=self._cfg.plugins,
565580
oxr_handles=oxr_handles,
566-
retargeting_execution=self._cfg.retargeting_execution,
581+
retargeting_execution=self._resolved_retargeting_execution(),
567582
mcap_config=mcap_config,
568583
)
569584

@@ -611,7 +626,7 @@ def _start_replay_session(self) -> bool:
611626
pipeline=self._pipeline,
612627
teleop_control_pipeline=self._teleop_control_pipeline,
613628
plugins=self._cfg.plugins,
614-
retargeting_execution=self._cfg.retargeting_execution,
629+
retargeting_execution=self._resolved_retargeting_execution(),
615630
mode=SessionMode.REPLAY,
616631
mcap_config=mcap_config,
617632
)

source/isaaclab_teleop/test/test_cloudxr_lifecycle.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -189,13 +189,12 @@ class TestRetargetingExecutionConfig:
189189
"""Tests for Isaac Lab's IsaacTeleop retargeting execution defaults."""
190190

191191
def test_session_config_receives_deadline_paced_pipelined_retargeting(self):
192-
"""The default retargeting execution config is passed into TeleopSession."""
192+
"""An unset retargeting execution config resolves to the pipelined default at session start."""
193193
cfg = _make_cfg()
194194

195-
assert cfg.retargeting_execution.mode == "pipelined"
196-
assert cfg.retargeting_execution.pacing.safety_margin_s == 0.025
197-
198-
sentinel_execution = cfg.retargeting_execution
195+
# The default is deferred (``None``) so that constructing the config never
196+
# requires the optional ``isaacteleop`` package.
197+
assert cfg.retargeting_execution is None
199198

200199
lifecycle = TeleopSessionLifecycle(cfg)
201200
lifecycle._pipeline = MagicMock()
@@ -213,7 +212,9 @@ def test_session_config_receives_deadline_paced_pipelined_retargeting(self):
213212
):
214213
assert lifecycle.try_start_session() is True
215214

216-
assert session_config_cls.call_args.kwargs["retargeting_execution"] is sentinel_execution
215+
resolved_execution = session_config_cls.call_args.kwargs["retargeting_execution"]
216+
assert resolved_execution.mode == "pipelined"
217+
assert resolved_execution.pacing.safety_margin_s == 0.025
217218

218219

219220
# ============================================================================

0 commit comments

Comments
 (0)