Merge origin/pro6000_xh: differential drive, record/replay tools, scene assets

Conflicts resolved:
- assets.py: keep /home/tangger/LYT path (ours)
- mindrobot_cfg.py: keep our initial poses + fix path separator \\ → /
- mindrobot_left_arm_ik_env_cfg.py: keep our wheel_action using
  MINDBOT_WHEEL_JOINTS constant (cleaner than remote's hardcoded list)

From origin/pro6000_xh:
- scripts/tools/: record_demos.py, replay_demos.py, hdf5 tools (Isaac Lab copy)
- assets/: dryingbox.py, lab.py, table.py scene asset definitions
- tele_se3_with_wheel_agent.py: WheelKeyboard inlined, differential drive keys

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-14 13:44:02 +08:00
25 changed files with 3709 additions and 87 deletions

View File

@@ -0,0 +1,143 @@
# scripts/environments/teleoperation/mindrobot_keyboard.py
# Copyright (c) 2022-2026, The Isaac Lab Project Developers.
# SPDX-License-Identifier: BSD-3-Clause
"""Shared keyboard controllers for MindRobot teleoperation and demo recording.
This module contains ONLY class definitions (no argparse, no AppLauncher),
so it is safe to import from both standalone scripts and from record_demos.py.
"""
import weakref
import numpy as np
import torch
import carb
from isaaclab.devices import Se3Keyboard, Se3KeyboardCfg
class WheelKeyboard:
"""Differential-drive (skid-steer) keyboard controller.
Listens to arrow keys via Carb and produces a 4-D joint-velocity
command: [right_b, left_b, left_f, right_f] (rad/s).
Key mappings
─────────────────────────────────────────────────────
↑ (UP) forward [ v, v, v, v]
↓ (DOWN) backward [-v, -v, -v, -v]
← (LEFT) left turn [ v, -v, -v, v]
→ (RIGHT) right turn[-v, v, v, -v]
─────────────────────────────────────────────────────
"""
_WHEEL_KEYS = {"UP", "DOWN", "LEFT", "RIGHT"}
def __init__(self, wheel_speed: float = 5.0, sim_device: str = "cuda:0"):
self.wheel_speed = wheel_speed
self._sim_device = sim_device
self._wheel_vel = np.zeros(4)
self._key_map: dict[str, np.ndarray] = {}
self._create_bindings()
import omni
self._appwindow = omni.appwindow.get_default_app_window()
self._input = carb.input.acquire_input_interface()
self._keyboard = self._appwindow.get_keyboard()
self._keyboard_sub = self._input.subscribe_to_keyboard_events(
self._keyboard,
lambda event, *args, obj=weakref.proxy(self): obj._on_keyboard_event(event, *args),
)
def __del__(self):
self._input.unsubscribe_to_keyboard_events(self._keyboard, self._keyboard_sub)
def __str__(self) -> str:
return (
"WheelKeyboard (skid-steer chassis controller)\n"
"\t↑ UP — forward\n"
"\t↓ DOWN — backward\n"
"\t← LEFT — left turn (in-place)\n"
"\t→ RIGHT — right turn (in-place)"
)
def reset(self):
self._wheel_vel = np.zeros(4)
def advance(self) -> torch.Tensor:
return torch.tensor(self._wheel_vel.copy(), dtype=torch.float32, device=self._sim_device)
def _create_bindings(self):
v = self.wheel_speed
self._key_map = {
"UP": np.array([ v, v, v, v]),
"DOWN": np.array([-v, -v, -v, -v]),
"LEFT": np.array([ v, -v, -v, v]),
"RIGHT": np.array([-v, v, v, -v]),
}
def _on_keyboard_event(self, event, *args, **kwargs):
if event.type == carb.input.KeyboardEventType.KEY_PRESS:
key = event.input.name
if key in self._key_map:
self._wheel_vel += self._key_map[key]
if event.type == carb.input.KeyboardEventType.KEY_RELEASE:
key = event.input.name
if key in self._key_map:
self._wheel_vel -= self._key_map[key]
return True
class MindRobotCombinedKeyboard:
"""组合遥操作控制器:左臂 IK (Se3Keyboard 7D) + 底盘轮速 (WheelKeyboard 4D)
输出 11 维 action: [arm_IK(6) | wheel_vel(4) | gripper(1)]
与 MindRobotTeleopActionsCfg 中声明顺序一致。
Key bindings
─────────────────────────────────────────────
W/S/A/D/Q/E — arm IK translate X/Y/Z
Z/X T/G C/V — arm IK rotate roll/pitch/yaw
K — gripper toggle open/close
↑↓←→ — chassis forward/backward/turn
R — reset
─────────────────────────────────────────────
"""
def __init__(
self,
pos_sensitivity: float = 0.05,
rot_sensitivity: float = 0.05,
wheel_speed: float = 5.0,
sim_device: str = "cuda:0",
):
self._arm = Se3Keyboard(
Se3KeyboardCfg(
pos_sensitivity=pos_sensitivity,
rot_sensitivity=rot_sensitivity,
sim_device=sim_device,
)
)
self._wheel = WheelKeyboard(wheel_speed=wheel_speed, sim_device=sim_device)
def reset(self):
self._arm.reset()
self._wheel.reset()
def add_callback(self, key: str, callback):
"""Delegate callbacks to the arm keyboard (Se3Keyboard supports arbitrary key callbacks)."""
self._arm.add_callback(key, callback)
def advance(self) -> torch.Tensor:
arm_cmd = self._arm.advance() # (7,): [dx,dy,dz,rx,ry,rz, gripper]
arm_ik = arm_cmd[:6] # (6,)
gripper = arm_cmd[6:7] # (1,)
wheel_vel = self._wheel.advance() # (4,)
return torch.cat([arm_ik, wheel_vel, gripper]) # (11,)
def __str__(self) -> str:
return (
"MindRobotCombinedKeyboard\n"
" Arm (Se3Keyboard): W/S/A/D/Q/E + Z/X/T/G/C/V, K=gripper\n"
" Wheel (arrows): ↑↓←→\n"
" R = reset | L = save-success-and-reset"
)

View File

@@ -13,8 +13,6 @@ Extends teleop_se3_agent.py with differential-drive keyboard control:
"""
import argparse
import weakref
import numpy as np
import torch
from isaaclab.app import AppLauncher
@@ -35,98 +33,20 @@ simulation_app = app_launcher.app
import logging
import gymnasium as gym
import carb
from isaaclab.devices import Se3Keyboard, Se3KeyboardCfg
from isaaclab.envs import ManagerBasedRLEnvCfg
from isaaclab.managers import TerminationTermCfg as DoneTerm
import mindbot.tasks # noqa: F401
from isaaclab_tasks.utils import parse_env_cfg
# mindrobot_keyboard.py lives in the same directory as this script.
# Python adds the script's own directory to sys.path, so a direct import works.
from mindrobot_keyboard import WheelKeyboard, MindRobotCombinedKeyboard # noqa: E402
logger = logging.getLogger(__name__)
# ─────────────────────────────────────────────────────────────
# WheelKeyboard: 监听方向键,输出 4 维轮速 tensor
# ─────────────────────────────────────────────────────────────
class WheelKeyboard:
"""Differential-drive (skid-steer) keyboard controller.
Listens to arrow keys via Carb and produces a 4-D joint-velocity
command: [right_b, left_b, left_f, right_f] (rad/s).
Key mappings
─────────────────────────────────────────────────────
↑ (UP) forward [ v, v, v, v]
↓ (DOWN) backward [-v, -v, -v, -v]
← (LEFT) left turn [ v, -v, -v, v] right wheels fwd
→ (RIGHT) right turn[-v, v, v, -v] left wheels fwd
─────────────────────────────────────────────────────
"""
_WHEEL_KEYS = {"UP", "DOWN", "LEFT", "RIGHT"}
def __init__(self, wheel_speed: float = 5.0, sim_device: str = "cuda:0"):
self.wheel_speed = wheel_speed
self._sim_device = sim_device
# 4 维速度缓冲:[right_b, left_b, left_f, right_f]
self._wheel_vel = np.zeros(4)
# 按键 → 速度向量映射 (在 _create_bindings 中填充)
self._key_map: dict[str, np.ndarray] = {}
self._create_bindings()
# 订阅 Carb 键盘事件
import omni
self._appwindow = omni.appwindow.get_default_app_window()
self._input = carb.input.acquire_input_interface()
self._keyboard = self._appwindow.get_keyboard()
self._keyboard_sub = self._input.subscribe_to_keyboard_events(
self._keyboard,
lambda event, *args, obj=weakref.proxy(self): obj._on_keyboard_event(event, *args),
)
def __del__(self):
self._input.unsubscribe_to_keyboard_events(self._keyboard, self._keyboard_sub)
def __str__(self) -> str:
return (
"WheelKeyboard (skid-steer chassis controller)\n"
"\t↑ UP — forward\n"
"\t↓ DOWN — backward\n"
"\t← LEFT — left turn (in-place)\n"
"\t→ RIGHT — right turn (in-place)"
)
def reset(self):
self._wheel_vel = np.zeros(4)
def advance(self) -> torch.Tensor:
"""Returns 4-D wheel velocity tensor [right_b, left_b, left_f, right_f]."""
return torch.tensor(self._wheel_vel.copy(), dtype=torch.float32, device=self._sim_device)
def _create_bindings(self):
v = self.wheel_speed
# [right_b, left_b, left_f, right_f]
self._key_map = {
"UP": np.array([ v, v, v, v]), # 前进
"DOWN": np.array([-v, -v, -v, -v]), # 后退
"LEFT": np.array([ v, -v, -v, v]), # 左转
"RIGHT": np.array([-v, v, v, -v]), # 右转
}
def _on_keyboard_event(self, event, *args, **kwargs):
if event.type == carb.input.KeyboardEventType.KEY_PRESS:
key = event.input.name
if key in self._key_map:
self._wheel_vel += self._key_map[key]
if event.type == carb.input.KeyboardEventType.KEY_RELEASE:
key = event.input.name
if key in self._key_map:
self._wheel_vel -= self._key_map[key]
return True
# ─────────────────────────────────────────────────────────────
# main
# ─────────────────────────────────────────────────────────────
@@ -201,4 +121,4 @@ def main() -> None:
if __name__ == "__main__":
main()
simulation_app.close()
simulation_app.close()