Instructions to use k-valentin/unitree-g1-mujoco-worlds with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- LeRobot
How to use k-valentin/unitree-g1-mujoco-worlds with LeRobot:
# No code snippets available yet for this library. # To use this model, check the repository files and the library's documentation. # Want to help? PRs adding snippets are welcome at: # https://github.com/huggingface/huggingface.js
- Notebooks
- Google Colab
- Kaggle
Unitree G1 MuJoCo worlds
Robot-agnostic MuJoCo worlds (tables, objects, fixed cameras) for
k-valentin/unitree-g1-mujoco, the Unitree G1 simulation LeRobot
loads as a Hub env. The robot repo resolves a world by name, merges it into its scene and does everything robot-specific
(see "What the robot adds"); a world file knows nothing about the robot.
| World | What | Task |
|---|---|---|
pick_cylinder |
table with a free upright cylinder (r 2.5 cm, h 15 cm, 50 g) and a table_view camera |
Pick up the cylinder from the table with the right hand |
Use
The robot repo takes a world through WORLD (config.yaml, make_env(world=...) or UNITREE_G1_MUJOCO_WORLD):
# by name, downloaded from this repo (only worlds/pick_cylinder/ is fetched)
UNITREE_G1_MUJOCO_WORLD=pick_cylinder
# pinned to a revision (tag, branch or commit), or from another repo
UNITREE_G1_MUJOCO_WORLD=k-valentin/unitree-g1-mujoco-worlds@<revision>:pick_cylinder
# a local checkout of this repo: every world is resolved from it by name
UNITREE_G1_MUJOCO_WORLDS_ROOT=/path/to/unitree-g1-mujoco-worlds UNITREE_G1_MUJOCO_WORLD=pick_cylinder
# a local folder with a world.xml, or a .xml file
UNITREE_G1_MUJOCO_WORLD=/path/to/my_world
from env import make_env # the robot repo's env.py
env = make_env(world="pick_cylinder", world_randomize=0.02)
WORLD_GRASP (physics, the default, or attach), WORLD_RANDOMIZE and the other options are described in the robot
repo's README.
Layout
worlds/<name>/world.xml the MuJoCo content (required)
worlds/<name>/world.yaml world facts (optional)
worlds/<name>/... anything world.xml references with a relative path (meshes, textures)
tools/robot.py finds the robot repo for the tools and tests
tools/grasp_diag.py scripted pick_cylinder grasp with diagnostics
tools/thumb_map.py AmazingHand thumb pose map used to pick the grasp's thumb poses
tests/ contract checks of every world, and the pick_cylinder + G1 tests
The contract
- World frame: x forward, y left, z up; the origin is on the floor where the robot spawns; the robot faces +x.
- Every body with a free joint is an object: the robot resets it, jitters it and can grasp it. Name it.
- Every
<camera>of the world is published by the robot. Name it, and do not reuse a name the robot has (head_camera,left_wrist_cam,right_wrist_cam,global_view). - A world never names a robot body, site, mount, camera or collision bit: no
<equality>to robot bodies, no*_ee_mount/*_tcp, nocontype/conaffinity(plain MuJoCo defaults, 1 and 1). Names must not clash with the robot's (the robot repo rejects a world that reuses one) and defaults must live in a<default class=...>. <option>is merged into the robot's (the world wins per attribute);<compiler>is dropped. Relativefile=paths of meshes, textures, height fields and skins are resolved against the world's folder.world.yamlholds world facts, with these optional keys only (the robot rejects unknown ones):
| Key | Meaning |
|---|---|
support |
name of the box geom objects are jittered on, e.g. table_top; their edge stays at least 5 cm inside it |
randomize |
default xy jitter in metres of the objects at start and on every reset (+-15 deg yaw too); the robot's WORLD_RANDOMIZE overrides it, 0 switches it off |
tasks |
list of task strings |
What the robot adds
When it merges a world, unitree-g1-mujoco ORs the AmazingHand collision bit (2) into the conaffinity of every
colliding world geom, so hands touch world objects but never the robot, the floor or themselves; with
WORLD_GRASP=attach it adds an inactive {side}_grasp_{body} weld from each hand mount to every object; it starts and
resets the AmazingHands in their open pose; and it publishes the world's cameras.
pick_cylinder
World frame as above, for a robot standing at pelvis height 0.74 m (the GR00T controller's default standing pose):
- table top z = 0.755 m (0.04 m thick), x 0.20..0.80, y -0.40..0.40: the front edge is 0.20 m ahead of the pelvis. The hand's rest pose (arms at zero, TCP at z 0.835) has its lowest finger 1.3 cm above it;
- cylinder r = 0.025 m, h = 0.15 m, 0.05 kg, centre (0.328, -0.071, 0.83): 0.328 m ahead of the pelvis, 0.071 m to the
right and 0.09 m above it. Its contact parameters (friction 1.0,
solref 0.008, priority 2) win over the finger pads' (priority 1). The AmazingHand's palm cavity is about 4 cm wide and its fingers span 10 cm in height, so a much thicker or shorter cylinder cannot be wrapped without the lowest finger touching the table; table_view: fixed camera in front of the table, 0.65 m ahead and 0.7 m left of the cylinder, looking at it;<option>is elliptic cones withimpratio10 andimplicitfast(the grasp reference's settings); the timestep comes from the robot.
Clearance to the robot's collision geoms, arms at zero: bodies at least 5.5 cm from the table standing and 7 cm hanging on the elastic band (pelvis ~0.92 m), 10 cm from the cylinder.
Scripted side grasp (G1, right AmazingHand)
tools/grasp_diag.py runs a side grasp (fingers forward, palm facing the cylinder, thumb up) with first-contact,
orientation and contact-force diagnostics. --physics is the tuned one, --lerobot closes through LeRobot's
closure_to_q, --sweep runs the +-2 cm spawn grid, --world-grasp attach the kinematic grasp, --png-dir writes
pictures.
With WORLD_GRASP=physics the right AmazingHand lifts the cylinder with finger contacts only: 25/25 on the grid, rise
8-9 cm, slip 1-2 mm, closed-stage normal forces of 10-30 N. It is a palm-and-thumb pinch with finger 3 at the
cylinder's far side, not a wrap: the cylinder sits 7.5 cm along the fingers and 3 cm off the palm in the right_tcp
frame, fingers 1-3 close to +-70 deg, the thumb from open (-80, -70) to (40, -70) deg over the last 60 % of the 1.2 s
close, then a diagonal lift (+8 cm forward per 10 cm up) because the 5-DoF arm holds the palm level only on a slanted
plane. The harness drives the arm through the bridge with LeRobot's default gains plus G1ArmIK gravity torques (the
teleop's path) and tracks within 3 mm. The right TCP sits (0.075, 0.03, -0.02) m from the cylinder centre in the hand
frame, at (0.33, -0.15, 0.86) in the world: that is the pose where the level palm clears the torso and the table, with
the cylinder 7.7 cm further forward and 4.9 cm further right than the spawn (the harness moves it). At TCP
(0.28, -0.09) the IK pose has the shoulder link inside the torso, which a teleported arm never notices and a PD-driven
one cannot reach. The rest of the closed hand's cage is larger than the cylinder, so grasps nearer the palm or the
fingertips tilt it. Not confirmed on hardware or in a teleop session.
WORLD_GRASP=attach is the fallback that makes pick-up data collectable without relying on the physical grasp: once an
AmazingHand closes (closure >= 0.6) with the cylinder's centre in its grasp zone, the robot welds the cylinder to the
hand at the current relative pose until the hand opens again (closure <= 0.4).
Add a world
- Create
worlds/<name>/world.xmlin the world frame above, with named free bodies and cameras and plain collision defaults. Put meshes and textures next to it and reference them with relative paths. - Optionally add
worlds/<name>/world.yaml(support,randomize,tasks). - Check it with
python -m unittest discover -s tests -k test_worlds(no robot needed) and by loading it:UNITREE_G1_MUJOCO_WORLDS_ROOT=$PWD UNITREE_G1_MUJOCO_WORLD=<name> python run_sim.pyin the robot repo.
Tests
MUJOCO_GL=egl python -m unittest discover -s tests -v
tests/test_worlds.py needs only MuJoCo. tests/test_pick_cylinder.py and the tools use the robot repo:
UNITREE_G1_MUJOCO_ROOT if set, else a full snapshot of k-valentin/unitree-g1-mujoco (meshes included, so set it to a
local checkout whose meshes are real files). The scripted grasp and reach tests also need xr_teleoperate (its
G1ArmIK) and are skipped without it; the LeRobot closure test needs lerobot.