Skip to content

Commit b8e0c86

Browse files
committed
feat(so101): add SO-101 cube-stack task with full-SE3 IK teleop
Add the TheRobotStudio SO-101 5-DOF arm and a cube-stacking task with both joint-position and absolute task-space (IK) control, driven over XR teleoperation. - Asset: SO101_CFG and SO101_HIGH_PD_CFG (stiffer PD for IK tracking). - Task: Isaac-Stack-Cube-SO101-v0 (joint-pos) and Isaac-Stack-Cube-SO101-IK-Abs-v0 (absolute pose IK), seated on the table and posed for top-down grasps within the arm's reach. - Controller: a full-SE3-pose differential IK over all 5 arm joints. The 3 linear task rows track position exactly while the 3 orientation rows are soft-weighted (scalar or per-axis, so wz=0 can drop the one DOF a 5-DOF arm cannot serve). Kept well-conditioned via manipulability-aware damped least squares (adaptive lambda keyed off the full weighted task Jacobian) and null-space joint-limit avoidance. - Action term: SO101PoseIKAction wiring the controller into a 7D [pos, quat_xyzw] command; teleop pipeline flattens to the 8D [pos, quat, gripper] action with a clutch-rebased, base-frame pose stream and an analog gripper. - Generalize the stack gripper observations/terminations from parallel-only (2-joint) to single- or N-jaw grippers. - Sim-free unit tests for the controller and the teleop pipeline wiring.
1 parent bd5c5ee commit b8e0c86

14 files changed

Lines changed: 1631 additions & 57 deletions

File tree

CONTRIBUTORS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ Guidelines for modifications:
110110
* Jingzhou Liu
111111
* Jinqi Wei
112112
* Jinyeob Kim
113+
* Jiwen Cai
113114
* Johnson Sun
114115
* Juana Du
115116
* Kaixi Bao
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Added
2+
^^^^^
3+
4+
* Added :obj:`~isaaclab_assets.robots.so101.SO101_CFG` and
5+
:obj:`~isaaclab_assets.robots.so101.SO101_HIGH_PD_CFG` configurations for the
6+
TheRobotStudio SO-101 5-DOF follower arm.

source/isaaclab_assets/isaaclab_assets/robots/__init__.pyi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ __all__ = [
3636
"RIDGEBACK_FRANKA_PANDA_CFG",
3737
"SAWYER_CFG",
3838
"SHADOW_HAND_CFG",
39+
"SO101_CFG",
40+
"SO101_HIGH_PD_CFG",
3941
"joint_parameter_lookup",
4042
"SPOT_CFG",
4143
"GO1_ACTUATOR_CFG",
@@ -85,6 +87,7 @@ from .quadcopter import CRAZYFLIE_CFG
8587
from .ridgeback_franka import RIDGEBACK_FRANKA_PANDA_CFG
8688
from .sawyer import SAWYER_CFG
8789
from .shadow_hand import SHADOW_HAND_CFG
90+
from .so101 import SO101_CFG, SO101_HIGH_PD_CFG
8891
from .spot import joint_parameter_lookup, SPOT_CFG
8992
from .unitree import (
9093
GO1_ACTUATOR_CFG,
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
2+
# All rights reserved.
3+
#
4+
# SPDX-License-Identifier: BSD-3-Clause
5+
6+
"""Configuration for the TheRobotStudio SO-101 follower arm.
7+
8+
The following configurations are available:
9+
10+
* :obj:`SO101_CFG`: SO-101 5-DOF arm with a single-jaw gripper.
11+
* :obj:`SO101_HIGH_PD_CFG`: SO-101 with a stiffer PD controller for task-space (IK) tracking.
12+
13+
The SO-101 is a low-cost 5-DOF arm (``shoulder_pan``, ``shoulder_lift``, ``elbow_flex``,
14+
``wrist_flex``, ``wrist_roll``) plus a single revolute ``gripper`` jaw. Because the arm has
15+
only 5 actuated DOF, it cannot achieve an arbitrary 6-DOF end-effector pose; task-space
16+
controllers should command the full pose but soft-weight the orientation rows so position is
17+
tracked exactly and orientation is best-effort (see the cube-stack IK-Abs task).
18+
19+
Reference: https://github.com/TheRobotStudio/SO-ARM100
20+
Actuator gains follow the values tuned for simulation in LeIsaac.
21+
"""
22+
23+
import isaaclab.sim as sim_utils
24+
from isaaclab.actuators import ImplicitActuatorCfg
25+
from isaaclab.assets.articulation import ArticulationCfg
26+
from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR
27+
28+
##
29+
# Configuration
30+
##
31+
32+
SO101_CFG = ArticulationCfg(
33+
spawn=sim_utils.UsdFileCfg(
34+
usd_path=f"{ISAAC_NUCLEUS_DIR}/Robots/RobotStudio/so101_new_calib/so101_new_calib.usd",
35+
activate_contact_sensors=False,
36+
rigid_props=sim_utils.RigidBodyPropertiesCfg(
37+
disable_gravity=False,
38+
max_depenetration_velocity=5.0,
39+
),
40+
articulation_props=sim_utils.ArticulationRootPropertiesCfg(
41+
enabled_self_collisions=True,
42+
solver_position_iteration_count=8,
43+
solver_velocity_iteration_count=0,
44+
fix_root_link=True,
45+
),
46+
),
47+
init_state=ArticulationCfg.InitialStateCfg(
48+
joint_pos={
49+
"shoulder_pan": 0.0,
50+
"shoulder_lift": 0.0,
51+
"elbow_flex": 0.0,
52+
"wrist_flex": 0.0,
53+
"wrist_roll": 0.0,
54+
"gripper": 0.0,
55+
},
56+
),
57+
actuators={
58+
"arm": ImplicitActuatorCfg(
59+
joint_names_expr=["shoulder_pan", "shoulder_lift", "elbow_flex", "wrist_flex", "wrist_roll"],
60+
effort_limit_sim=10.0,
61+
velocity_limit_sim=10.0,
62+
stiffness=17.8,
63+
damping=0.60,
64+
),
65+
"gripper": ImplicitActuatorCfg(
66+
joint_names_expr=["gripper"],
67+
effort_limit_sim=10.0,
68+
velocity_limit_sim=10.0,
69+
stiffness=17.8,
70+
damping=0.60,
71+
),
72+
},
73+
soft_joint_pos_limit_factor=1.0,
74+
)
75+
"""Configuration of the SO-101 follower arm with implicit actuators."""
76+
77+
78+
SO101_HIGH_PD_CFG = SO101_CFG.copy()
79+
SO101_HIGH_PD_CFG.spawn.rigid_props.disable_gravity = True
80+
SO101_HIGH_PD_CFG.actuators["arm"].stiffness = 400.0
81+
SO101_HIGH_PD_CFG.actuators["arm"].damping = 80.0
82+
"""Configuration of the SO-101 follower arm with stiffer PD control.
83+
84+
This configuration is useful for task-space control using differential IK, where the
85+
default low-stiffness gains track end-effector targets poorly.
86+
"""
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
Added
2+
^^^^^
3+
4+
* Added ``Isaac-Stack-Cube-SO101-v0`` and ``Isaac-Stack-Cube-SO101-IK-Abs-v0`` cube-stacking
5+
environments for the SO-101 5-DOF arm. The IK-Abs variant uses absolute task-space
6+
differential inverse kinematics and is seated on the table.
7+
* Generalized the cube-stack MDP gripper observations and terminations to support single-jaw
8+
grippers in addition to two-finger parallel grippers.
9+
* Added XR teleoperation for ``Isaac-Stack-Cube-SO101-IK-Abs-v0`` via an IsaacTeleop retargeting
10+
pipeline built from the SO-101 retargeters in ``isaacteleop.retargeters``. Controller poses are
11+
rebased into the robot base frame upstream via
12+
:attr:`~isaaclab_teleop.IsaacTeleopCfg.target_frame_prim_path` (set to the seated, +90 deg-yawed
13+
base prim), so the retargeters work directly in the IK command frame. End-effector motion is
14+
clutch-rebased around the pose captured on engage: the clutch latches on the first running frame
15+
(the headset "Play") and seeds its home from a static reset-origin (the gripper's pose in the
16+
base frame at the seated init pose) so engaging with a steady controller does not move the arm,
17+
then keeps a running home so a mid-task re-clutch resumes from the last commanded pose. The
18+
gripper jaw tracks the controller trigger continuously (analog).
19+
20+
Changed
21+
^^^^^^^
22+
23+
* **Breaking:** Reworked the ``Isaac-Stack-Cube-SO101-IK-Abs-v0`` teleop control to command the
24+
full end-effector SE3 pose. The arm action is now an 8-D
25+
``[pos_x, pos_y, pos_z, quat_x, quat_y, quat_z, quat_w, gripper]`` (orientation xyzw),
26+
replacing the previous 6-D ``[pos_x, pos_y, pos_z, pitch, roll, gripper]``. A single
27+
full-pose differential IK (``SO101PoseIKController``, ``command_type="pose"``) now solves all
28+
**5** arm joints (``shoulder_pan``, ``shoulder_lift``, ``elbow_flex``, ``wrist_flex``,
29+
``wrist_roll``) over a 6-row task: 3 linear rows track position exactly (weight 1) and 3
30+
orientation rows are soft-weighted by ``orientation_task_weight`` (default ``0.5``). This
31+
replaces the reduced 4-row ``[x, y, z, pitch]`` IK over 4 joints plus a separate
32+
``wrist_roll`` action. The manipulability-aware damped least squares and null-space
33+
joint-limit avoidance are retained (the damping ramp keys off the full weighted task Jacobian,
34+
so it is coupled to the orientation weight).
35+
36+
Migration: the controller / action classes were renamed
37+
``SO101PositionPitchIK{Controller,ControllerCfg,Action,ActionCfg}`` ->
38+
``SO101PoseIK{...}`` (modules ``position_pitch_ik_{controller,action}.py`` ->
39+
``pose_ik_{controller,action}.py``), the cfg field ``pitch_task_weight`` ->
40+
``orientation_task_weight``, and the ``wrist_roll_action`` term was removed (``wrist_roll`` is
41+
now solved by the IK). The SO-101 ``SO101WristRetargeter`` (and its ``SO101RollRetargeter``
42+
alias) was removed from ``isaacteleop.retargeters``; the clutch retargeter now drives the full
43+
pose with a fixed orientation calibration offset.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
2+
# All rights reserved.
3+
#
4+
# SPDX-License-Identifier: BSD-3-Clause
5+
import gymnasium as gym
6+
7+
##
8+
# Register Gym environments.
9+
##
10+
11+
##
12+
# Joint Position Control
13+
##
14+
15+
gym.register(
16+
id="Isaac-Stack-Cube-SO101-v0",
17+
entry_point="isaaclab.envs:ManagerBasedRLEnv",
18+
kwargs={
19+
"env_cfg_entry_point": f"{__name__}.stack_joint_pos_env_cfg:SO101CubeStackEnvCfg",
20+
},
21+
disable_env_checker=True,
22+
)
23+
24+
##
25+
# Inverse Kinematics - Absolute Pose Control
26+
##
27+
28+
gym.register(
29+
id="Isaac-Stack-Cube-SO101-IK-Abs-v0",
30+
entry_point="isaaclab.envs:ManagerBasedRLEnv",
31+
kwargs={
32+
"env_cfg_entry_point": f"{__name__}.stack_ik_abs_env_cfg:SO101CubeStackEnvCfg",
33+
},
34+
disable_env_checker=True,
35+
)
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
2+
# All rights reserved.
3+
#
4+
# SPDX-License-Identifier: BSD-3-Clause
5+
6+
"""Action term wiring the SO-101 full-pose SE3 IK controller.
7+
8+
Subclasses :class:`~isaaclab.envs.mdp.actions.task_space_actions.DifferentialInverseKinematicsAction`
9+
and swaps in :class:`SO101PoseIKController`. The base term already delegates ``action_dim`` to
10+
the controller and feeds it the full 6-row base-frame Jacobian, so the only override needed is
11+
the controller construction (and rebuilding the action buffers at the controller's 7-wide
12+
action dim).
13+
14+
.. note::
15+
:attr:`~isaaclab.envs.mdp.actions.actions_cfg.DifferentialInverseKinematicsActionCfg.clip`
16+
is not supported for this subclass. The base clips by action-dim axis using joint-name
17+
keys, but this term's action layout is the task-space pose ``[pos_xyz, quat_xyzw]``, which
18+
does not map to joint names. Setting ``clip`` raises :exc:`NotImplementedError`.
19+
"""
20+
21+
from __future__ import annotations
22+
23+
from dataclasses import MISSING
24+
from typing import TYPE_CHECKING
25+
26+
import torch
27+
28+
from isaaclab.envs.mdp.actions.actions_cfg import DifferentialInverseKinematicsActionCfg
29+
from isaaclab.envs.mdp.actions.task_space_actions import DifferentialInverseKinematicsAction
30+
from isaaclab.utils.configclass import configclass
31+
32+
from .pose_ik_controller import (
33+
SO101PoseIKController,
34+
SO101PoseIKControllerCfg,
35+
)
36+
37+
if TYPE_CHECKING:
38+
from isaaclab.envs import ManagerBasedEnv
39+
40+
41+
class SO101PoseIKAction(DifferentialInverseKinematicsAction):
42+
"""IK action term that uses the SO-101 full-pose SE3 controller."""
43+
44+
cfg: SO101PoseIKActionCfg
45+
46+
def __init__(self, cfg: SO101PoseIKActionCfg, env: ManagerBasedEnv):
47+
super().__init__(cfg, env)
48+
# Replace the base position/pose controller with the SO-101 full-pose one. Re-create the
49+
# raw/processed action buffers and scale (action_dim is the controller's 7).
50+
self._ik_controller = SO101PoseIKController(cfg=self.cfg.controller, num_envs=self.num_envs, device=self.device)
51+
self._raw_actions = torch.zeros(self.num_envs, self.action_dim, device=self.device)
52+
self._processed_actions = torch.zeros_like(self._raw_actions)
53+
self._scale = torch.zeros((self.num_envs, self.action_dim), device=self.device)
54+
self._scale[:] = torch.tensor(self.cfg.scale, device=self.device)
55+
# Action clipping is not supported for this term: the base clips by action-dim axis using
56+
# joint-name keys, but this term's action layout is the task-space pose
57+
# [pos_xyz, quat_xyzw], which does not map to joint names. Refuse rather than silently
58+
# clip the wrong axis.
59+
if self.cfg.clip is not None:
60+
raise NotImplementedError(
61+
"clip is not supported for SO101PoseIKAction (task-space [pos, quat] action "
62+
"does not map to joint-name clip keys)."
63+
)
64+
# Joint limits are injected lazily on the first apply (asset data is populated by then)
65+
# so the controller can do null-space joint-limit avoidance.
66+
self._limits_injected = False
67+
68+
def apply_actions(self) -> None:
69+
if not self._limits_injected:
70+
# Limits are uniform across envs for the SO-101; env 0 is representative.
71+
limits = self._asset.data.soft_joint_pos_limits.torch[0, self._joint_ids, :]
72+
self._ik_controller.set_joint_pos_limits(limits[:, 0].clone(), limits[:, 1].clone())
73+
self._limits_injected = True
74+
super().apply_actions()
75+
76+
77+
@configclass
78+
class SO101PoseIKActionCfg(DifferentialInverseKinematicsActionCfg):
79+
"""Configuration for :class:`SO101PoseIKAction`."""
80+
81+
class_type: type[SO101PoseIKAction] = SO101PoseIKAction
82+
controller: SO101PoseIKControllerCfg = MISSING

0 commit comments

Comments
 (0)