Skip to content

Commit 8c67938

Browse files
committed
Add EE pose transform to ego coordinate
1 parent ce1652e commit 8c67938

File tree

5 files changed

+179
-2
lines changed

5 files changed

+179
-2
lines changed

isaaclab_arena/embodiments/g1/g1.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
from isaaclab_arena_g1.g1_env.mdp import g1_observations as g1_observations_mdp
3535
from isaaclab_arena_g1.g1_env.mdp.actions.g1_decoupled_wbc_joint_action_cfg import G1DecoupledWBCJointActionCfg
3636
from isaaclab_arena_g1.g1_env.mdp.actions.g1_decoupled_wbc_pink_action_cfg import G1DecoupledWBCPinkActionCfg
37+
from isaaclab_arena_g1.g1_env.mdp.actions.g1_decoupled_wbc_pink_world_frame_action_cfg import (
38+
G1DecoupledWBCPinkWorldFrameActionCfg,
39+
)
3740

3841

3942
class G1EmbodimentBase(EmbodimentBase):
@@ -115,9 +118,16 @@ def __init__(
115118
initial_pose: Pose | None = None,
116119
camera_offset: Pose | None = _DEFAULT_G1_CAMERA_OFFSET,
117120
use_tiled_camera: bool = False, # Default to regular for single env
121+
use_world_frame_actions: bool = False, # Set to True for VR/motion controller teleop
118122
):
119123
super().__init__(enable_cameras, initial_pose)
120-
self.action_config = G1WBCPinkActionCfg()
124+
125+
# Use world frame action config if needed (e.g., for VR controllers)
126+
if use_world_frame_actions:
127+
self.action_config = G1DecoupledWBCPinkWorldFrameActionCfg()
128+
else:
129+
self.action_config = G1WBCPinkActionCfg()
130+
121131
self.observation_config = G1WBCPinkObservationsCfg()
122132
self.observation_config.policy.concatenate_terms = self.concatenate_observation_terms
123133
self.observation_config.wbc.concatenate_terms = self.concatenate_observation_terms

isaaclab_arena_environments/galileo_g1_locomanip_pick_and_place_environment.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,23 @@ def get_env(self, args_cli: argparse.Namespace):
2121
background = self.asset_registry.get_asset_by_name("galileo_locomanip")()
2222
pick_up_object = self.asset_registry.get_asset_by_name(args_cli.object)()
2323
blue_sorting_bin = self.asset_registry.get_asset_by_name("blue_sorting_bin")()
24-
embodiment = self.asset_registry.get_asset_by_name(args_cli.embodiment)(enable_cameras=args_cli.enable_cameras)
24+
25+
# Check if we need world frame actions (for VR/motion controllers)
26+
use_world_frame_actions = (
27+
args_cli.teleop_device in ["motion_controllers", "openxr"]
28+
and args_cli.embodiment == "g1_wbc_pink"
29+
)
30+
31+
# Create embodiment with appropriate action config
32+
if use_world_frame_actions:
33+
embodiment = self.asset_registry.get_asset_by_name(args_cli.embodiment)(
34+
enable_cameras=args_cli.enable_cameras,
35+
use_world_frame_actions=True
36+
)
37+
else:
38+
embodiment = self.asset_registry.get_asset_by_name(args_cli.embodiment)(
39+
enable_cameras=args_cli.enable_cameras
40+
)
2541

2642
if args_cli.teleop_device is not None:
2743
teleop_device = self.device_registry.get_device_by_name(args_cli.teleop_device)()

isaaclab_arena_g1/g1_env/mdp/actions/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,11 @@
22
# All rights reserved.
33
#
44
# SPDX-License-Identifier: Apache-2.0
5+
6+
from .g1_decoupled_wbc_pink_world_frame_action import G1DecoupledWBCPinkWorldFrameAction
7+
from .g1_decoupled_wbc_pink_world_frame_action_cfg import G1DecoupledWBCPinkWorldFrameActionCfg
8+
9+
__all__ = [
10+
"G1DecoupledWBCPinkWorldFrameAction",
11+
"G1DecoupledWBCPinkWorldFrameActionCfg",
12+
]
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Copyright (c) 2025-2026, The Isaac Lab Arena Project Developers (https://github.com/isaac-sim/IsaacLab-Arena/blob/main/CONTRIBUTORS.md).
2+
# All rights reserved.
3+
#
4+
# SPDX-License-Identifier: Apache-2.0
5+
6+
from __future__ import annotations
7+
8+
import torch
9+
from typing import TYPE_CHECKING
10+
11+
from isaaclab.utils.math import quat_apply_inverse, quat_inv, quat_mul
12+
13+
from isaaclab_arena_g1.g1_env.mdp.actions.g1_decoupled_wbc_pink_action import G1DecoupledWBCPinkAction
14+
15+
if TYPE_CHECKING:
16+
from isaaclab.envs import ManagerBasedEnv
17+
18+
from .g1_decoupled_wbc_pink_world_frame_action_cfg import G1DecoupledWBCPinkWorldFrameActionCfg
19+
20+
21+
class G1DecoupledWBCPinkWorldFrameAction(G1DecoupledWBCPinkAction):
22+
"""Action term for G1 WBC Pink that transforms world-frame wrist poses to base-frame.
23+
24+
This action term extends the base G1DecoupledWBCPinkAction to handle VR controller inputs
25+
that provide wrist poses in world coordinates. It automatically transforms these to the
26+
robot's base frame before passing them to the IK controller.
27+
28+
This is necessary because VR controllers (like Quest) output absolute world positions,
29+
but the robot's IK expects positions relative to its base. Without this transformation,
30+
when the robot moves, the hands would try to stay at their original world position and
31+
drift away from the body.
32+
"""
33+
34+
cfg: G1DecoupledWBCPinkWorldFrameActionCfg
35+
36+
def __init__(self, cfg: G1DecoupledWBCPinkWorldFrameActionCfg, env: ManagerBasedEnv):
37+
"""Initialize the action term.
38+
39+
Args:
40+
cfg: The configuration for this action term.
41+
env: The environment in which the action term will be applied.
42+
"""
43+
super().__init__(cfg, env)
44+
45+
def process_actions(self, actions: torch.Tensor):
46+
"""Process the input actions with world-to-base frame transformation.
47+
48+
This method first transforms wrist poses from world frame to robot base frame
49+
(if enabled in config), then calls the parent class's process_actions.
50+
51+
Args:
52+
actions: The input actions tensor.
53+
54+
Expected action layout (23 elements):
55+
[0:1] left_hand_state (0=open, 1=close)
56+
[1:2] right_hand_state (0=open, 1=close)
57+
[2:5] left_wrist_pos (x,y,z in world frame)
58+
[5:9] left_wrist_quat (w,x,y,z in world frame)
59+
[9:12] right_wrist_pos (x,y,z in world frame)
60+
[12:16] right_wrist_quat (w,x,y,z in world frame)
61+
[16:19] navigate_cmd (x, y, angular_z)
62+
[19:20] base_height
63+
[20:23] torso_orientation_rpy
64+
"""
65+
if self.cfg.transform_to_base_frame:
66+
actions = self._transform_wrist_poses_to_base_frame(actions)
67+
68+
# Call parent class to do the actual processing
69+
super().process_actions(actions)
70+
71+
def _transform_wrist_poses_to_base_frame(self, actions: torch.Tensor) -> torch.Tensor:
72+
"""Transform wrist positions and orientations from world frame to robot base frame.
73+
74+
The VR controllers give world-space positions, but the IK expects positions
75+
relative to the robot's base. Without this, when the robot moves, the hands
76+
will try to stay at their original world position and drift away from the body.
77+
78+
Args:
79+
actions: Input actions with wrist poses in world frame.
80+
81+
Returns:
82+
Actions with wrist poses transformed to robot base frame.
83+
"""
84+
# Get robot's current base pose (position and orientation in world frame)
85+
robot_base_pos = self._asset.data.root_link_pos_w[0, :3] # Shape: (3,)
86+
robot_base_quat = self._asset.data.root_link_quat_w[0] # Shape: (4,) in (w,x,y,z) format
87+
88+
# Clone actions to avoid modifying the input
89+
actions = actions.clone()
90+
91+
# Batch process both wrists together for efficiency
92+
# Stack positions: shape (2, 3) for left and right
93+
wrist_pos_world = torch.stack([actions[0, 2:5], actions[0, 9:12]], dim=0)
94+
95+
# Step 1: Translate to remove base position offset
96+
wrist_pos_translated = wrist_pos_world - robot_base_pos
97+
98+
# Step 2: Rotate into robot's local frame (batch operation)
99+
wrist_pos_base = quat_apply_inverse(robot_base_quat.unsqueeze(0), wrist_pos_translated)
100+
101+
# Transform orientations to robot base frame (batch operation)
102+
# Stack quaternions: shape (2, 4) for left and right
103+
wrist_quat_world = torch.stack([actions[0, 5:9], actions[0, 12:16]], dim=0)
104+
robot_base_quat_inv = quat_inv(robot_base_quat.unsqueeze(0)) # Shape: (1, 4)
105+
robot_base_quat_inv = robot_base_quat_inv.expand(2, -1) # Expand to (2, 4) for batching
106+
wrist_quat_base = quat_mul(robot_base_quat_inv, wrist_quat_world)
107+
108+
# Update action with base-relative poses
109+
actions[0, 2:5] = wrist_pos_base[0]
110+
actions[0, 5:9] = wrist_quat_base[0]
111+
actions[0, 9:12] = wrist_pos_base[1]
112+
actions[0, 12:16] = wrist_quat_base[1]
113+
114+
return actions
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Copyright (c) 2025-2026, The Isaac Lab Arena Project Developers (https://github.com/isaac-sim/IsaacLab-Arena/blob/main/CONTRIBUTORS.md).
2+
# All rights reserved.
3+
#
4+
# SPDX-License-Identifier: Apache-2.0
5+
6+
from isaaclab.managers.action_manager import ActionTerm
7+
from isaaclab.utils import configclass
8+
9+
from .g1_decoupled_wbc_pink_action_cfg import G1DecoupledWBCPinkActionCfg
10+
from .g1_decoupled_wbc_pink_world_frame_action import G1DecoupledWBCPinkWorldFrameAction
11+
12+
13+
@configclass
14+
class G1DecoupledWBCPinkWorldFrameActionCfg(G1DecoupledWBCPinkActionCfg):
15+
"""Configuration for G1 WBC Pink action with world-to-base frame transformation.
16+
17+
This configuration enables automatic transformation of wrist poses from world frame
18+
to robot base frame, which is necessary for VR controller inputs (e.g., Quest).
19+
"""
20+
21+
class_type: type[ActionTerm] = G1DecoupledWBCPinkWorldFrameAction
22+
"""Specifies the action term class type for G1 WBC Pink with world frame transformation."""
23+
24+
transform_to_base_frame: bool = True
25+
"""Whether to transform wrist poses from world frame to robot base frame.
26+
27+
Set this to True when using VR controllers or other devices that output world-space poses.
28+
Set to False if the input poses are already in the robot's base frame.
29+
"""

0 commit comments

Comments
 (0)