Skip to content

Commit 1bd244f

Browse files
Nan-Tetheriacopybara-github
authored andcommitted
Copybara import of the project:
-- 1c079ef by Nan Wang <nanwang@tetheria.ai>: add tetheria aero hand open example -- 068c1d6 by Nan Wang <nanwang@tetheria.ai>: update license and update the action scale config -- cc92835 by Nan Wang <nanwang@tetheria.ai>: Remove stray CSV and ignore it -- eaa5587 by Nan Wang <nanwang@tetheria.ai>: rm gitignore -- b385d4c by Nan Wang <nanwang@tetheria.ai>: add back gitignore -- 892705c by Nan Wang <nanwang@tetheria.ai>: update README -- 0c5fe74 by Nan Wang <nanwang@tetheria.ai>: update README -- 9841c98 by Nan Wang <nanwang@tetheria.ai>: rm unneeded pics -- 49504c0 by Nan Wang <nanwang@tetheria.ai>: rm urdf -- f33113b by Nan Wang <nanwang@tetheria.ai>: rm format changes -- 8eb257d by Nan Wang <nanwang@tetheria.ai>: Apply isort fixes; resolve previous comments -- 0c4f806 by Nan Wang <nanwang@tetheria.ai>: correct weird format -- 2983abb by Nan Wang <nanwang@tetheria.ai>: update naming to match convention -- 9662241 by Nan Wang <nanwang@tetheria.ai>: update a cube -- 04f5b6c by Nan Wang <nanwang@tetheria.ai>: update license heading, remove gifs, add small pngs, and move assets to menagerie -- 2f2cb95 by Nan Wang <nanwang@tetheria.ai>: dir error for mesh -- 0afef9d by Nan Wang <nanwang@tetheria.ai>: update mesh dir remove other materials -- b2dd13e by Nan Wang <nanwang@tetheria.ai>: remove unnecessary png -- bbd21b1 by Nan Wang <nanwang@tetheria.ai>: remove unworking dir -- 0567cd5 by Nan Wang <nanwang@tetheria.ai>: remove configs about impl but use it as flag -- b843526 by Nan Wang <nanwang@tetheria.ai>: update menagerie sha FUTURE_COPYBARA_INTEGRATE_REVIEW=#223 from TetherIA:main b843526 DO NOT SUBMIT PiperOrigin-RevId: 836394388 Change-Id: I7044aaa6fa335404a23a8437c5c9e50a8cb62891
1 parent 8a0f509 commit 1bd244f

File tree

17 files changed

+1670
-2
lines changed

17 files changed

+1670
-2
lines changed

mujoco_playground/_src/manipulation/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from mujoco_playground._src.manipulation.franka_emika_panda_robotiq import push_cube as robotiq_push_cube
2929
from mujoco_playground._src.manipulation.leap_hand import reorient as leap_cube_reorient
3030
from mujoco_playground._src.manipulation.leap_hand import rotate_z as leap_rotate_z
31-
31+
from mujoco_playground._src.manipulation.aero_hand import rotate_z as aero_hand_rotate_z
3232

3333
_envs = {
3434
"AlohaHandOver": aloha_handover.HandOver,
@@ -40,6 +40,7 @@
4040
"PandaRobotiqPushCube": robotiq_push_cube.PandaRobotiqPushCube,
4141
"LeapCubeReorient": leap_cube_reorient.CubeReorient,
4242
"LeapCubeRotateZAxis": leap_rotate_z.CubeRotateZAxis,
43+
"AeroCubeRotateZAxis": aero_hand_rotate_z.CubeRotateZAxis,
4344
}
4445

4546
_cfgs = {
@@ -52,11 +53,13 @@
5253
"PandaRobotiqPushCube": robotiq_push_cube.default_config,
5354
"LeapCubeReorient": leap_cube_reorient.default_config,
5455
"LeapCubeRotateZAxis": leap_rotate_z.default_config,
56+
"AeroCubeRotateZAxis": aero_hand_rotate_z.default_config,
5557
}
5658

5759
_randomizer = {
5860
"LeapCubeRotateZAxis": leap_rotate_z.domain_randomize,
5961
"LeapCubeReorient": leap_cube_reorient.domain_randomize,
62+
"AeroCubeRotateZAxis": aero_hand_rotate_z.domain_randomize,
6063
}
6164

6265

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Tetheria Aero Hand Open with Tendon-Driven Actuation
2+
3+
This directory introduces a tendon-driven manipulation example that extends MuJoCo Playground with support for tendon-level control and observation in reinforcement learning tasks.
4+
5+
The model is adapted from the [Tetheria Aero Hand Open](https://docs.tetheria.ai/), featuring a physically accurate tendon system that emulates cable-driven actuation. In this setup, both the policy inputs and observations are defined in the tendon space, providing a complete example of training and deploying tendon-driven controllers and under-actuated fingers in MuJoCo.
6+
7+
An overview of the hand is shown below:
8+
9+
| ![Rock](imgs/rock.png) | ![Paper](imgs/paper.png) | ![Scissor](imgs/scissor.png) |
10+
|------------------------|------------------------|------------------------|
11+
12+
13+
## 1. Tendon-Driven MuJoCo Model
14+
15+
### 1.1 Modeling
16+
17+
The mechanical design is derived from URDF files, ensuring accurate representation of the real hand structure. The actuation system in the simulator models the cable design in the real hand through three key components:
18+
19+
#### 1.1.1 Tendon Drives
20+
The tendons drive the hand to close the fingers and control the thumbs. These are modeled as spatial tendons in MuJoCo that follow the exact routing paths of the real cables.
21+
22+
#### 1.1.2 Springs
23+
The springs, which are also modeled by tendon components in MuJoCo, provide the forces to pull the fingers in the backward direction. This creates the restoring forces necessary for finger extension.
24+
25+
#### 1.1.3 Pulleys
26+
The pulleys, which are modeled as cylinders, organize the cables and springs to ensure they are routed in a similar way to the real hand. Careful placement of these pulleys ensures accurate tendon routing.
27+
28+
| front view| close-up of index|
29+
|------------------------|------------------------|
30+
| ![skeleton](imgs/skeleton.png) | ![index](imgs/index_close_up.png)
31+
32+
### 1.2 Parameters
33+
34+
#### 1.2.1 Mechanical Parameters
35+
- **Joint limits, mass, and inertia**: Come directly from URDF and are accurate to the real hand
36+
- **Pulley placement**: Positioned precisely where they are placed in the real hand, ensuring cable and spring routes match the real system
37+
- **Validation**: The range of tendon between fully open and fully closed fingers in simulation (0.0459454) closely matches the real hand (0.04553) without manual adjustment
38+
39+
#### 1.2.2 Tendon and Spring Specifications
40+
- **Tendon properties**: Use the same specifications as those in the real hand
41+
- **Spring properties**: Match real hand specifications, except for the spring on the DIP joint, which is adjusted as a compromise to achieve similar joint space behavior as the real hand
42+
43+
#### 1.2.3 Control Parameters
44+
All remaining parameters, including:
45+
- Joint damping values
46+
- Actuator gains
47+
- Joint-specific damping coefficients
48+
49+
These are fine-tuned to satisfy both similar joint behaviors in simulation and the real world.
50+
51+
52+
## 2. Training your own policy
53+
54+
We introduce a **z-axis rotation task** for the **Tetheria Aero Hand Open**, optimized using the following reward formulation:
55+
56+
$$
57+
\text{reward} = 1.0 \times \text{angular velocity}
58+
- 1.0 \times \text{action rate}
59+
+ \text{termination} (-100.0)
60+
$$
61+
62+
The optimization variables include the **tendon lengths** and the **thumb abduction joint**, which correspond to the real hand’s actuation system.
63+
This setup ensures that the same control inputs and sensory data can be directly applied for **sim-to-real deployment** on the physical Tetheria Aero Hand Open.
64+
65+
66+
To train policies for the Tetheria Hand:
67+
68+
```bash
69+
70+
# Run the training script
71+
python learning/train_jax_ppo.py --env_name TetheriaCubeRotateZAxis
72+
```
73+
74+
Although the reward curves from different training runs may vary due to stochasticity in the learning process, they consistently **converge toward a positive reward**.
75+
76+
## 3. Running a pretrained policy
77+
78+
79+
To test trained policies in simulation:
80+
81+
```bash
82+
# Run the simulation rollout script
83+
python learning/train_jax_ppo.py --env_name TetheriaCubeRotateZAxis --play_only --load_checkpoint_path path/to/checkpoints
84+
```
85+
86+
This will:
87+
- Load the trained policy
88+
- Run episodes in the MuJoCo simulation
89+
- Display the hand performing manipulation tasks
90+
91+
## File Structure
92+
93+
### Core Implementation
94+
- **`tetheria_hand_tendon_constants.py`** - Constants and configuration
95+
- **`rotate_z.py`** - Cube rotation task implementation
96+
97+
### XML Models
98+
- **`xmls/right_hand.xml`** - Main hand model with tendon system
99+
- **`xmls/scene_mjx_cube.xml`** - Manipulation scene
100+
- **`xmls/reorientation_cube.xml`** - Cube reorientation task
101+
102+
## Key Features
103+
104+
- **Accurate tendon modeling**: Direct translation from real hand cable system
105+
- **Precise pulley placement**: Matches real hand routing exactly
106+
- **Validated parameters**: Tendon ranges match real hand within 0.1%
107+
108+
---
109+
110+
*This implementation provides a high-fidelity tendon-driven hand model that closely matches the real robotic hand, enabling effective sim-to-real transfer for manipulation tasks.*
111+
112+
## Acknowledgements
113+
Our code is built upon
114+
- MuJoCo playground - https://github.com/google-deepmind/mujoco_playground
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright 2025 TetherIA Inc.
2+
# Copyright 2025 DeepMind Technologies Limited
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
# ==============================================================================
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Copyright 2025 TetherIA Inc.
2+
# Copyright 2025 DeepMind Technologies Limited
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
# ==============================================================================
16+
17+
"""Constants for TetherIA Aero Hand Open."""
18+
19+
from mujoco_playground._src import mjx_env
20+
21+
ROOT_PATH = mjx_env.ROOT_PATH / "manipulation" / "aero_hand"
22+
CUBE_XML = ROOT_PATH / "xmls" / "scene_mjx_cube.xml"
23+
24+
NQ = 16
25+
NV = 16
26+
NU = 7
27+
28+
JOINT_NAMES = [
29+
# index
30+
"right_index_mcp_flex",
31+
"right_index_pip",
32+
"right_index_dip",
33+
# middle
34+
"right_middle_mcp_flex",
35+
"right_middle_pip",
36+
"right_middle_dip",
37+
# ring
38+
"right_ring_mcp_flex",
39+
"right_ring_pip",
40+
"right_ring_dip",
41+
# pinky
42+
"right_pinky_mcp_flex",
43+
"right_pinky_pip",
44+
"right_pinky_dip",
45+
# thumb
46+
"right_thumb_cmc_abd",
47+
"right_thumb_cmc_flex",
48+
"right_thumb_mcp",
49+
"right_thumb_ip",
50+
]
51+
52+
ACTUATOR_NAMES = [
53+
# index
54+
"right_index_A_tendon",
55+
# middle
56+
"right_middle_A_tendon",
57+
# ring
58+
"right_ring_A_tendon",
59+
# pinky
60+
"right_pinky_A_tendon",
61+
# thumb
62+
"right_thumb_A_cmc_abd",
63+
"right_th1_A_tendon",
64+
"right_th2_A_tendon",
65+
]
66+
67+
FINGERTIP_NAMES = [
68+
"if_tip",
69+
"mf_tip",
70+
"rf_tip",
71+
"pf_tip",
72+
"th_tip",
73+
]
74+
75+
76+
SENSOR_TENDON_NAMES = [
77+
"len_if",
78+
"len_mf",
79+
"len_rf",
80+
"len_pf",
81+
"len_th1",
82+
"len_th2",
83+
]
84+
85+
SENSOR_JOINT_NAMES = [
86+
"len_th_abd",
87+
]
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Copyright 2025 TetherIA Inc.
2+
# Copyright 2025 DeepMind Technologies Limited
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
# ==============================================================================
16+
17+
"""Base classes for TetherIA Aero Hand Open."""
18+
19+
from typing import Any, Dict, Optional, Union
20+
21+
from etils import epath
22+
import jax
23+
import jax.numpy as jp
24+
from ml_collections import config_dict
25+
import mujoco
26+
from mujoco import mjx
27+
28+
from mujoco_playground._src import mjx_env
29+
from mujoco_playground._src.manipulation.aero_hand import aero_hand_constants as consts
30+
31+
32+
def get_assets() -> Dict[str, bytes]:
33+
assets = {}
34+
path = mjx_env.MENAGERIE_PATH / "tetheria_aero_hand_open"
35+
mjx_env.update_assets(assets, path / "assets")
36+
mjx_env.update_assets(assets, consts.ROOT_PATH / "xmls", "*.xml")
37+
mjx_env.update_assets(
38+
assets, consts.ROOT_PATH / "xmls" / "reorientation_cube_textures"
39+
)
40+
mjx_env.update_assets(assets, consts.ROOT_PATH / "xmls" / "assets")
41+
return assets
42+
43+
44+
class AeroHandEnv(mjx_env.MjxEnv):
45+
"""Base class for Aero Hand environments."""
46+
47+
def __init__(
48+
self,
49+
xml_path: str,
50+
config: config_dict.ConfigDict,
51+
config_overrides: Optional[Dict[str, Union[str, int, list[Any]]]] = None,
52+
) -> None:
53+
super().__init__(config, config_overrides)
54+
self._model_assets = get_assets()
55+
self._mj_model = mujoco.MjModel.from_xml_string(
56+
epath.Path(xml_path).read_text(), assets=self._model_assets
57+
)
58+
self._mj_model.opt.timestep = self._config.sim_dt
59+
60+
self._mj_model.vis.global_.offwidth = 3840
61+
self._mj_model.vis.global_.offheight = 2160
62+
63+
self._mjx_model = mjx.put_model(self._mj_model)
64+
self._xml_path = xml_path
65+
66+
# Sensor readings.
67+
68+
def get_palm_position(self, data: mjx.Data) -> jax.Array:
69+
return mjx_env.get_sensor_data(self.mj_model, data, "palm_position")
70+
71+
def get_cube_position(self, data: mjx.Data) -> jax.Array:
72+
return mjx_env.get_sensor_data(self.mj_model, data, "cube_position")
73+
74+
def get_cube_orientation(self, data: mjx.Data) -> jax.Array:
75+
return mjx_env.get_sensor_data(self.mj_model, data, "cube_orientation")
76+
77+
def get_cube_linvel(self, data: mjx.Data) -> jax.Array:
78+
return mjx_env.get_sensor_data(self.mj_model, data, "cube_linvel")
79+
80+
def get_cube_angvel(self, data: mjx.Data) -> jax.Array:
81+
return mjx_env.get_sensor_data(self.mj_model, data, "cube_angvel")
82+
83+
def get_cube_angacc(self, data: mjx.Data) -> jax.Array:
84+
return mjx_env.get_sensor_data(self.mj_model, data, "cube_angacc")
85+
86+
def get_cube_upvector(self, data: mjx.Data) -> jax.Array:
87+
return mjx_env.get_sensor_data(self.mj_model, data, "cube_upvector")
88+
89+
def get_cube_goal_orientation(self, data: mjx.Data) -> jax.Array:
90+
return mjx_env.get_sensor_data(self.mj_model, data, "cube_goal_orientation")
91+
92+
def get_cube_goal_upvector(self, data: mjx.Data) -> jax.Array:
93+
return mjx_env.get_sensor_data(self.mj_model, data, "cube_goal_upvector")
94+
95+
def get_fingertip_positions(self, data: mjx.Data) -> jax.Array:
96+
"""Get fingertip positions relative to the grasp site."""
97+
return jp.concatenate([
98+
mjx_env.get_sensor_data(self.mj_model, data, f"{name}_position")
99+
for name in consts.FINGERTIP_NAMES
100+
])
101+
102+
# Accessors.
103+
104+
@property
105+
def xml_path(self) -> str:
106+
return self._xml_path
107+
108+
@property
109+
def action_size(self) -> int:
110+
return self._mjx_model.nu
111+
112+
@property
113+
def mj_model(self) -> mujoco.MjModel:
114+
return self._mj_model
115+
116+
@property
117+
def mjx_model(self) -> mjx.Model:
118+
return self._mjx_model
119+
120+
121+
def uniform_quat(rng: jax.Array) -> jax.Array:
122+
"""Generate a random quaternion from a uniform distribution."""
123+
u, v, w = jax.random.uniform(rng, (3,))
124+
return jp.array([
125+
jp.sqrt(1 - u) * jp.sin(2 * jp.pi * v),
126+
jp.sqrt(1 - u) * jp.cos(2 * jp.pi * v),
127+
jp.sqrt(u) * jp.sin(2 * jp.pi * w),
128+
jp.sqrt(u) * jp.cos(2 * jp.pi * w),
129+
])
60 KB
Loading
123 KB
Loading
85.3 KB
Loading
105 KB
Loading
41.2 KB
Loading

0 commit comments

Comments
 (0)