Skip to content

Commit ac61052

Browse files
committed
features: Implement locomanipulation system with improved IK control
- Added locomotion and locomanipulation policies with agile observation - Enhanced IK controller with gravity compensation and tuned gains - Expanded test coverage for ik - Added unit tests for new pink kinematics config class, and new custom pink frame task
1 parent 53ad9c2 commit ac61052

File tree

57 files changed

+2287
-3381
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+2287
-3381
lines changed

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@
1010
*.pt filter=lfs diff=lfs merge=lfs -text
1111
*.jit filter=lfs diff=lfs merge=lfs -text
1212
*.hdf5 filter=lfs diff=lfs merge=lfs -text
13+
*.npz filter=lfs diff=lfs merge=lfs -text

source/isaaclab/docs/CHANGELOG.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Changelog
77
Added
88
^^^^^
99

10-
* Added teleoperation environments for Unitree G1. This includes an environment with lower body fixed and upper body
10+
* Added teleoperation environments for Unitree G1. This includes an environment with lower body fixed and upper body
1111
controller by IK, and an environment with the lower body controlled by a policy and the upper body controlled by IK.
1212

1313

source/isaaclab/isaaclab/controllers/local_frame_task.py

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,30 @@
1-
from typing import Union, Sequence
1+
# Copyright (c) 2022-2025, 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+
26
import numpy as np
7+
from collections.abc import Sequence
38

49
import pinocchio as pin
5-
6-
from pink.tasks.frame_task import FrameTask
710
from pink.exceptions import TargetNotSet
11+
from pink.tasks.frame_task import FrameTask
812

913
from isaaclab.controllers.pink_kinematics_configuration import PinkKinematicsConfiguration
1014

15+
1116
class LocalFrameTask(FrameTask):
1217
"""
1318
A task that computes error in a local (custom) frame.
1419
Inherits from FrameTask but overrides compute_error.
1520
"""
1621

17-
def __init__(self,
22+
def __init__(
23+
self,
1824
frame: str,
1925
base_link_frame_name: str,
20-
position_cost: Union[float, Sequence[float]],
21-
orientation_cost: Union[float, Sequence[float]],
26+
position_cost: float | Sequence[float],
27+
orientation_cost: float | Sequence[float],
2228
lm_damping: float = 0.0,
2329
gain: float = 1.0,
2430
):
@@ -30,6 +36,7 @@ def __init__(self,
3036
"""
3137
super().__init__(frame, position_cost, orientation_cost, lm_damping, gain)
3238
self.base_link_frame_name = base_link_frame_name
39+
self.transform_target_to_base = None
3340

3441
def set_target(self, transform_target_to_base: pin.SE3) -> None:
3542
"""Set task target pose in the world frame.
@@ -40,9 +47,7 @@ def set_target(self, transform_target_to_base: pin.SE3) -> None:
4047
"""
4148
self.transform_target_to_base = transform_target_to_base.copy()
4249

43-
def set_target_from_configuration(
44-
self, configuration: PinkKinematicsConfiguration
45-
) -> None:
50+
def set_target_from_configuration(self, configuration: PinkKinematicsConfiguration) -> None:
4651
"""Set task target pose from a robot configuration.
4752
4853
Args:
@@ -61,18 +66,12 @@ def compute_error(self, configuration: PinkKinematicsConfiguration) -> np.ndarra
6166
if self.transform_target_to_base is None:
6267
raise ValueError(f"no target set for frame '{self.frame}'")
6368

64-
transform_frame_to_base = configuration.get_transform(
65-
self.frame,
66-
self.base_link_frame_name
67-
)
68-
transform_target_to_frame = transform_frame_to_base.actInv(
69-
self.transform_target_to_base
70-
)
69+
transform_frame_to_base = configuration.get_transform(self.frame, self.base_link_frame_name)
70+
transform_target_to_frame = transform_frame_to_base.actInv(self.transform_target_to_base)
7171

7272
error_in_frame: np.ndarray = pin.log(transform_target_to_frame).vector
7373
return error_in_frame
7474

75-
7675
def compute_jacobian(self, configuration: PinkKinematicsConfiguration) -> np.ndarray:
7776
r"""Compute the frame task Jacobian.
7877
@@ -97,13 +96,8 @@ def compute_jacobian(self, configuration: PinkKinematicsConfiguration) -> np.nda
9796
"""
9897
if self.transform_target_to_base is None:
9998
raise TargetNotSet(f"no target set for frame '{self.frame}'")
100-
transform_frame_to_base = configuration.get_transform(
101-
self.frame,
102-
self.base_link_frame_name
103-
)
104-
transform_frame_to_target = self.transform_target_to_base.actInv(
105-
transform_frame_to_base
106-
)
99+
transform_frame_to_base = configuration.get_transform(self.frame, self.base_link_frame_name)
100+
transform_frame_to_target = self.transform_target_to_base.actInv(transform_frame_to_base)
107101
jacobian_in_frame = configuration.get_frame_jacobian(self.frame)
108102
J = -pin.Jlog6(transform_frame_to_target) @ jacobian_in_frame
109103
return J

source/isaaclab/isaaclab/controllers/pink_ik.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@
1313
import torch
1414

1515
from pink import solve_ik
16+
1617
from isaaclab.assets import ArticulationCfg
1718
from isaaclab.controllers.null_space_posture_task import NullSpacePostureTask
1819
from isaaclab.controllers.pink_kinematics_configuration import PinkKinematicsConfiguration
1920
from isaaclab.utils.string import resolve_matching_names_values
21+
2022
from .pink_ik_cfg import PinkIKControllerCfg
2123

2224

@@ -77,7 +79,7 @@ def _setup_joint_ordering_mappings(self):
7779
"""Setup joint ordering mappings between Isaac Lab and Pink conventions."""
7880
pink_joint_names = self.pink_configuration.all_joint_names_pinocchio_order
7981
isaac_lab_joint_names = self.cfg.all_joint_names
80-
82+
8183
# Type assertions for linter clarity
8284
assert pink_joint_names is not None, "pink_joint_names should not be None"
8385
assert isaac_lab_joint_names is not None, "isaac_lab_joint_names should not be None"
@@ -93,7 +95,7 @@ def _setup_joint_ordering_mappings(self):
9395
# Create reordering arrays for controlled joints only
9496
pink_controlled_joint_names = self.pink_configuration.controlled_joint_names_pinocchio_order
9597
isaac_lab_controlled_joint_names = self.cfg.controlled_joint_names
96-
98+
9799
# Type assertions for linter clarity
98100
assert pink_controlled_joint_names is not None, "pink_controlled_joint_names should not be None"
99101
assert isaac_lab_controlled_joint_names is not None, "isaac_lab_controlled_joint_names should not be None"
@@ -105,7 +107,6 @@ def _setup_joint_ordering_mappings(self):
105107
[pink_controlled_joint_names.index(isaac_lab_joint) for isaac_lab_joint in isaac_lab_controlled_joint_names]
106108
)
107109

108-
109110
"""
110111
Operations.
111112
"""
@@ -168,9 +169,9 @@ def compute(
168169
# Solve IK using Pink's solver
169170
try:
170171
velocity = solve_ik(
171-
self.pink_configuration,
172-
self.cfg.variable_input_tasks + self.cfg.fixed_input_tasks,
173-
dt,
172+
self.pink_configuration,
173+
self.cfg.variable_input_tasks + self.cfg.fixed_input_tasks,
174+
dt,
174175
solver="osqp",
175176
safety_break=self.cfg.fail_on_joint_limit_violation,
176177
)
@@ -194,8 +195,7 @@ def compute(
194195

195196
# Add the velocity changes to the current joint positions to get the target joint positions
196197
target_joint_pos = torch.add(
197-
joint_vel_isaac_lab,
198-
torch.tensor(curr_controlled_joint_pos, device=self.device, dtype=torch.float32)
198+
joint_vel_isaac_lab, torch.tensor(curr_controlled_joint_pos, device=self.device, dtype=torch.float32)
199199
)
200200

201201
return target_joint_pos

source/isaaclab/isaaclab/controllers/pink_ik_cfg.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ class PinkIKControllerCfg:
7070
"""Fail the IK solver if a joint limit is violated."""
7171
hand_rotational_offset: tuple[float, float, float, float] = (1.0, 0.0, 0.0, 0.0)
7272
"""Rotational offset quaternion (w, x, y, z) between USD hand orientation and nominal orientation.
73-
73+
7474
The nominal orientation is defined as thumbs pointing up and fingers pointing forward.
7575
This offset is applied to align the USD hand orientation with the expected nominal pose.
7676
Default is identity quaternion (1, 0, 0, 0) representing no rotation.

source/isaaclab/isaaclab/controllers/pink_kinematics_configuration.py

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
from typing import Optional
1+
# Copyright (c) 2022-2025, 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
25

36
import numpy as np
4-
import pinocchio as pin
5-
from pinocchio.robot_wrapper import RobotWrapper
67

8+
import pinocchio as pin
79
from pink.configuration import Configuration
810
from pink.exceptions import FrameNotFound
11+
from pinocchio.robot_wrapper import RobotWrapper
12+
913

1014
class PinkKinematicsConfiguration(Configuration):
1115
"""
@@ -64,14 +68,21 @@ def __init__(
6468
if joint_name not in self._controlled_joint_names:
6569
joints_to_lock.append(self.full_model.getJointId(joint_name))
6670

67-
self.controlled_model = pin.buildReducedModel(self.full_model, joints_to_lock, self.full_q)
68-
self.controlled_data = self.controlled_model.createData()
69-
self.controlled_q = self.full_q[self._controlled_joint_indices]
71+
72+
if len(joints_to_lock) == 0:
73+
# No joints to lock, controlled model is the same as full model
74+
self.controlled_model = self.full_model
75+
self.controlled_data = self.full_data
76+
self.controlled_q = self.full_q
77+
else:
78+
self.controlled_model = pin.buildReducedModel(self.full_model, joints_to_lock, self.full_q)
79+
self.controlled_data = self.controlled_model.createData()
80+
self.controlled_q = self.full_q[self._controlled_joint_indices]
7081

7182
# Pink will should only have the controlled model
7283
super().__init__(self.controlled_model, self.controlled_data, self.controlled_q, copy_data, forward_kinematics)
7384

74-
def update(self, q: Optional[np.ndarray] = None) -> None:
85+
def update(self, q: np.ndarray | None = None) -> None:
7586
"""Update configuration to a new vector.
7687
7788
Calling this function runs forward kinematics and computes
@@ -84,6 +95,10 @@ def update(self, q: Optional[np.ndarray] = None) -> None:
8495
raise ValueError("q must have the same length as the number of joints in the model")
8596
if q is not None:
8697
super().update(q[self._controlled_joint_indices])
98+
99+
q_readonly = q.copy()
100+
q_readonly.setflags(write=False)
101+
self.full_q = q_readonly
87102
pin.computeJointJacobians(self.full_model, self.full_data, q)
88103
pin.updateFramePlacements(self.full_model, self.full_data)
89104
else:
@@ -121,14 +136,12 @@ def get_frame_jacobian(self, frame: str) -> np.ndarray:
121136
if not self.full_model.existFrame(frame):
122137
raise FrameNotFound(frame, self.full_model.frames)
123138
frame_id = self.full_model.getFrameId(frame)
124-
J: np.ndarray = pin.getFrameJacobian(
125-
self.full_model, self.full_data, frame_id, pin.ReferenceFrame.LOCAL
126-
)
139+
J: np.ndarray = pin.getFrameJacobian(self.full_model, self.full_data, frame_id, pin.ReferenceFrame.LOCAL)
127140
return J[:, self._controlled_joint_indices]
128141

129142
def get_transform_frame_to_world(self, frame: str) -> pin.SE3:
130143
"""Get the pose of a frame in the current configuration.
131-
We override this method from the super class to solve the issue that in the default
144+
We override this method from the super class to solve the issue that in the default
132145
Pink implementation, the frame placements do not take into account the non-controlled joints
133146
being not at initial pose (which is a bad assumption when they are controlled by other controllers like a lower body controller).
134147
@@ -155,4 +168,4 @@ def controlled_joint_names_pinocchio_order(self) -> list[str]:
155168
@property
156169
def all_joint_names_pinocchio_order(self) -> list[str]:
157170
"""Get the names of all joints in the order of the pinocchio model."""
158-
return self._all_joint_names
171+
return self._all_joint_names

source/isaaclab/isaaclab/devices/openxr/retargeters/humanoid/unitree/data/configs/dex-retargeting/g1_hand_left_dexpilot.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
# Copyright (c) 2022-2025, 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+
16
retargeting:
27
finger_tip_link_names:
38
- thumb_tip

source/isaaclab/isaaclab/devices/openxr/retargeters/humanoid/unitree/data/configs/dex-retargeting/g1_hand_right_dexpilot.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
# Copyright (c) 2022-2025, 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+
16
retargeting:
27
finger_tip_link_names:
38
- thumb_tip

source/isaaclab/isaaclab/devices/teleop_device_factory.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,18 @@
1515
from isaaclab.devices.gamepad import Se2Gamepad, Se2GamepadCfg, Se3Gamepad, Se3GamepadCfg
1616
from isaaclab.devices.keyboard import Se2Keyboard, Se2KeyboardCfg, Se3Keyboard, Se3KeyboardCfg
1717
from isaaclab.devices.openxr.retargeters import (
18-
GR1T2Retargeter,
19-
GR1T2RetargeterCfg,
20-
G1UpperBodyRetargeter,
21-
G1UpperBodyRetargeterCfg,
2218
G1LowerBodyRetargeter,
2319
G1LowerBodyRetargeterCfg,
20+
G1UpperBodyRetargeter,
21+
G1UpperBodyRetargeterCfg,
22+
GR1T2Retargeter,
23+
GR1T2RetargeterCfg,
2424
GripperRetargeter,
2525
GripperRetargeterCfg,
2626
Se3AbsRetargeter,
2727
Se3AbsRetargeterCfg,
2828
Se3RelRetargeter,
2929
Se3RelRetargeterCfg,
30-
G1UpperBodyRetargeter,
31-
G1UpperBodyRetargeterCfg,
32-
G1LowerBodyRetargeter,
33-
G1LowerBodyRetargeterCfg,
3430
)
3531
from isaaclab.devices.retargeter_base import RetargeterBase, RetargeterCfg
3632
from isaaclab.devices.spacemouse import Se2SpaceMouse, Se2SpaceMouseCfg, Se3SpaceMouse, Se3SpaceMouseCfg

0 commit comments

Comments
 (0)