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 |
table_room_3x1200 |
the lab's white adjustable table (0.80 m top) with a YCB chips can (Pringles) in a 3.6 m wide x 7.2 m deep room with a grey stone-tile floor (infinite, hides the robot's) and a white sky: the robot spawns 3.5 m in front of the back wall and 2.5 m from the table centre (table turned 180 deg, centre 1.2 m from the white front wall, 3.6 x 1.8 m); 1200 x 1800 mm partitions (three on the back, six on each side); table_view and room_view cameras |
Pick up the chips can from the table |
table_room_1x3450 |
the same, with 3450 x 1800 mm partitions (one on the back, two on each side) and the table centre 0.45 m from the white front wall (3.25 m from the robot) | Pick up the chips can from the table |
table_room_1x3450_short |
table_room_1x3450 with one partition on each side: a 3.6 m wide x 3.75 m deep room, the robot 0.8 m in front of the back wall and still 2.5 m from the table, the table centre 0.45 m from the white front wall |
Pick up the chips can from the table |
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/build_room_meshes.py builds the table_room_* meshes (lab GLBs + YCB chips can) as z-up OBJ + PNG at real size
tools/grasp_diag.py scripted grasp (pick_cylinder, or a table_room world's chips can) 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 + table_room 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/conaffinityother than0(plain MuJoCo defaults, 1 and 1;0/0only for visual-only geoms, e.g. a detailed mesh over simple collision boxes). 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, so the robot'sangle="radian"applies: give orientations asquat, nevereuler/axisangle. A world<visual>overrides the robot scene's per element and attribute (e.g.<rgba haze>), and a world<texture type="skybox">replaces its sky. 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 is at (0.33, -0.15, 0.86) in the world, the cylinder centre
(0.075, 0.027, -0.018) m from it in the hand frame (the hand sits 2.9 mm right of and 1.7 mm above the TCP, on the
forearm's thread axis): that is the pose where the level palm clears the torso and the table, with the cylinder
7.7 cm further forward and 5.2 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.
--pringles <world> runs the same grasp on a table_room_* world's chips can (75 x 250 mm cylinder collision,
0.205 kg) where it spawns: the robot base, legs and waist are held in front of the table, at the base pose that puts
the can on the tuned TCP (0.33, -0.15) cell (about 0.4 m behind the can), and only the right arm moves (G1ArmIK). The
pinch is scaled to the can's radius, its axis (0.075, 0.0396) from the TCP, the TCP 10.8 cm above the table top; the
idle left hand is held higher and further back, clear of the table. In all three worlds it lifts the can with finger
contacts only: 25/25 on the +-2 cm grid, rise 9-10 cm, slip under 3 mm, palm 19 N, fingers 10-11 N, thumb 22 N; the
can tilts ~6 deg when closed and ~26 deg in the hand after the lift; the hands touch nothing but the can.
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, tests/test_table_room.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.
Third-party assets
The chips can of the table_room_* worlds (worlds/table_room_*/meshes/chips_can.*) is 001_chips_can of the
YCB object set (ycbbenchmarks.com), its Berkeley tsdf scan as mirrored by
ll4ma-lab/ycb-fixed-meshes, CC BY 4.0
(license). Changes: vertices merged, recentred on its axis, texture brightened (levels and gamma). YCB has no
google_16k scan of this can, so the world collides with a 75 x 250 mm cylinder (the YCB size) rather than the scan.