Instructions to use k-valentin/unitree-g1-mujoco with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- LeRobot
How to use k-valentin/unitree-g1-mujoco 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
Commit History
Today
-
feat: configurable yaw jitter of world objects (WORLD_RANDOMIZE_YAW)
d0e4913 -
fix: clamp jittered world objects to the support in the world frame
The support box was read in its body's frame, so a table placed away from the origin (the table_room worlds) clamped the jittered can to the robot's feet.2db7c5d -
fix: centre the AmazingHand connector on the forearm thread and make it white
Unitree's 23dof forearm mesh has its threaded end centred (+-2.9, 1.7) mm off the wrist roll axis, so the connector and hand now sit on the thread; the mount frame and TCP stay on the roll axis. The connector is white like the other printed parts.e0c234c -
feat: white AmazingHand palm and frames with the palm and top shells
The hand plate, wrist interface and finger frames are white as printed (Pollen's export has them grey). Pollen's palm and top shells, missing from its MJCF export, are added as visual-only meshes with the hand plate's pose; the palm collision box is unchanged.ebee4b4 -
feat: let a world override the scene's visual settings and skybox
A world <visual> overrides the scene's per element and attribute, and a world skybox replaces the scene's (MuJoCo draws only the first). A world object's free joint is never taken for a robot joint, whatever its name (chips_can contains "hip").4e3ec12 -
fix: remove the red com_marker sphere from the scene
824d359 -
feat(d455): use the lab unit's factory calibration for the head camera
Fx/Fy/PPX/PPY from the D455's own calibration (S/N 244622300576) replace the datasheet values. MuJoCo's principalpixel is 320 - PPX, 240 - PPY, the opposite sign to the image offset; the d435i comment and the test now say so.b31bea8
7 days ago
-
feat: mount the AmazingHand on the lab's PLA connector
Verified signatureAdds the Amazing Hand Connector (Onshape STEP export + STL) as a visual-only body in each ee_mount, 28.2 g PLA with COM/inertia from the STEP, and moves the hand ring to x = 0.048 m in the mount frame (0.132 m along the 23dof wrist_roll_link, flange seated on the forearm end face). The hand roll about the forearm axis and the 29dof mount stay placeholders.d3f5c71 -
refactor!: move the worlds and the scripted grasp out to the worlds repo
assets/worlds/pick_cylinder, tools/grasp_diag.py and tools/thumb_map.py now live in unitree-g1-mujoco-worlds, which this repo only resolves by WORLD spec. The world tests become generic mechanism tests against a robot-agnostic fixture world (tabletop) and cover resolution, name clashes, several objects, relative asset paths and the hand collision bit. The README describes the world contract and what the robot adds.5978dab -
feat: add the hand collision bit and attach welds when merging a world
A world is now robot-agnostic MuJoCo content. compose.py ORs the end effectors' collision bit into the conaffinity of every colliding world geom, adds the {side}_grasp_{body} welds for each free-joint body when WORLD_GRASP=attach, and rejects worlds that reuse a robot name or reference names they do not define. The attach grasp in base_sim handles several objects, picking the one nearest the TCP inside the grasp zone. pick_cylinder no longer names robot bodies or collision bits.d013b50 -
refactor: resolve worlds by name, path or repo with a world.yaml
Worlds are moving to their own repo. sim/worlds.py resolves a WORLD spec (folder, world.xml, name via UNITREE_G1_MUJOCO_WORLDS_ROOT or WORLDS_REPO, owner/repo[@rev]:name) and validates the optional world.yaml. Cameras are discovered from the world file, the jitter support geom and the default jitter come from world.yaml, and compose.py takes the resolved world.xml path and makes its relative file paths absolute.d14801e -
feat: default WORLD_GRASP to physics
The scripted finger-contact grasp lifts the cylinder 25/25 on the +-2 cm spawn grid, so the physical grasp is now the default. The kinematic attach weld stays available as the fallback for pick-up data collection.7dcaa9b -
fix(env): hydrate the Hub snapshot unless it is complete
The XR teleop downloads the same snapshot with allow_patterns for sim/mjcf/*.py and assets/*.xml. That leaves assets/scene.xml in place, so the old check skipped the full download and importing sim.simulator_factory failed. Check sentinels that no such partial pattern matches instead.7b6c963
14 days ago
-
merge: physical AmazingHand grasp passes the pick_cylinder grid (exp/physics-compliance)
7328c8a -
fix: mirror the optional pad axis of the left thumb
The left distal frames are mirrored about the palm plane (checked on the composed model: mapping the right pad axes through the mirror gives the same finger axis and a sign-flipped thumb z), so build(pads=True) now gives the left thumb pad 0 -0.97 0.23. ToolsTests checks both sides.3dd7b95 -
test: physical grasp through LeRobot's closure_to_q lifts the cylinder
lerobot_params() closes with AMAZING_HAND.closure_to_q (open -35/+35, closed +60/-60, thumb +75/-75, linear, all fingers together): 25/25 on the +-2 cm grid, so the teleop's current constants need no change. grasp_diag gains --lerobot.7ce3206 -
feat: physical grasp of the pick_cylinder passes 25/25 with a dynamic arm
physics_params() is the tuned grasp: the cylinder (now 15 cm tall) 7.5 cm along the fingers and 3 cm off the palm, fingers to +-70 deg, thumb last, a diagonal lift, and the arm driven through the bridge with LeRobot's gains and G1ArmIK gravity torques at TCP (0.33, -0.15, 0.86), where the level palm clears the torso and the table. It lifts 8-9 cm with 1-2 mm slip on the +-2 cm spawn grid with the stock rigid hand. The physics test no longer expects a failure; WORLD_GRASP stays attach by default.1d70c18 -
feat: optional soft fingertip pads in the AmazingHand generator
build(pads=True) puts each distal body's collision geoms on a slide joint (0-5 mm, 500 N/m, 2 Ns/m) along the closed-grasp contact normal. At the dynamic-arm grasp cell pads (300-800 N/m) and softer finger loops (solref 0.003-0.008) did not change the outcome (rigid 25/25, pad 500 + loop 0.005 25/25, pad 500 21/25), so the committed parts file stays rigid.3fdbb79 -
fix: pin the harness base so the dynamic arm tracks under the teleop gains
The base was held by overwriting qpos, so it free-fell inside every step (feet-floor contacts and gravity on a 35 kg free joint) and its mass-matrix coupling fed up to 1.5 Nm into the shoulder, which the gravity feed-forward then fought (13-62 mm error). A 1e4 armature on the held dofs and no floor contact remove it: with LeRobot's default arm gains and G1ArmIK gravity torques the grasp pose is held within 1.3 mm. GraspParams gains arm_gains and gravity.0bd2b7c -
merge: closed-stage grasp diagnostics (exp/physics-lift)
663100f -
merge: attach-on-close kinematic grasp (feat/attach-grasp)
52a9157 -
feat: grasp_diag closed-stage force diagnostics, dynamic-arm and forward-lift options
0e9f6f5 -
feat: attach-on-close kinematic grasp for the pick_cylinder world (WORLD_GRASP)
A weld per hand is composed in for WORLD_GRASP=attach (default) and activated at the current relative pose when the AmazingHand's fingers 1-3 close past 0.6 with the object in the grasp zone; it releases at 0.4.43d0244 -
feat: thumb pose map tool and thumb open/closed poses for the side grasp
aba4c8d -
docs: pick_cylinder world section with the final cylinder, option and grasp status
c6cc337 -
feat: grasp_diag procedure per the plan with per-finger close windows, contact forces and timeline diagnostics
Pre-grasp 0.10 m back, 1.5 s approach, 1.2 s close with the thumb starting at 40 %, 0.10 m lift. The lift test stays an expected failure: 0/25 on the spawn grid.6f4ce12 -
feat: grasp physics per the reference hand: slim finger cores, fingertip capsules, elliptic cone world option
The pick_cylinder world sets elliptic cones, impratio 10 and implicitfast; compose merges a world option into the scene. The cylinder is r 0.025 / h 0.12 / 0.05 kg with priority 2 contact parameters over the finger pads, on a 0.755 m table. Finger joints get damping.b44c447 -
feat: move the pick_cylinder spawn into the 23dof arm's reachable band and test the reach
The sim grasp test now uses tools/grasp_diag.py; the closed-hand lift is an expected failure until the hand collision and grasp procedure are reworked.222185d -
feat: grasp_diag tool with first-contact, orientation and PNG diagnostics for the scripted grasp
9ef3581 -
fix: write composed scenes atomically so parallel processes never read a partial file
71321f8 -
wip: open AmazingHands at world reset, grasp test tweaks and header numbers
7f358ba -
feat: AmazingHand collision geoms so the fingers can grasp
Fit a capsule per phalanx and a box for the palm plate to the visual meshes (group 3, contype 2, conaffinity 0: they touch only world objects whose geoms carry conaffinity bit 2), limit the finger actuators to the SCS0009 stall torque and lower the passive linkage friction so that torque can move them. pick_cylinder becomes a narrow, tall cylinder on a 0.75 m table, graspable by the right hand (tests/test_worlds.py closes it and lifts it), and WORLD_RANDOMIZE keeps the cylinder 5 cm inside the table edge.5e6b21b -
feat: pick_cylinder world (table + free cylinder) selectable with WORLD
WORLD merges assets/worlds/<name>.xml into the composed scene; pick_cylinder adds a table, a free-jointed cylinder and a fixed table_view camera that is published next to the end effector's cameras. The cylinder is put back at its spawn pose on every reset, jittered by WORLD_RANDOMIZE (xy metres, +-15 deg yaw).601e2cb
20 days ago
-
feat: {side}_tcp site in every end effector for XR arm IK
The point that follows the operator's tracked wrist, in the mount frame. Placeholder until CAD: xr_teleoperate's G1_29 IK end effector (wrist_yaw_link +0.05 m).e83e679 -
test: forward viewer keys to the external keyboard backend explicitly
78491ec -
fix: compose from the Hugging Face cache, whose files are symlinks
compose.py resolved __file__ and mesh paths, which in the Hub cache follows each symlink into blobs/: ROOT became the cache root (assets not found) and mesh paths lost their extension. Paths are now made absolute without following symlinks. HubCacheLayoutTests composes a model through a symlinked copy of the repo laid out like the cache.d547669 -
feat: datasheet camera intrinsics for the D435i and D455 head sensors
The head cameras carry pinhole intrinsics for the recorded 640x480 color mode (resolution, focalpixel, principalpixel, sensorsize) instead of a field of view, derived from the Intel D400-series datasheet until each unit's calibration can be read: native focal lengths from the datasheet H and V FOVs are averaged (square pixels) and scaled to the 640x480 mode, with the principal point at the image centre. - d435i: 1920x1080, 69x42 deg -> f = 623.01 px (54.4 x 42.1 deg) - d455: 1280x800, 90x65 deg -> f = 380.36 px (80.1 x 64.5 deg) Each sensor file documents the derivation and how to replace it with rs-enumerate-devices -c values; a test checks the compiled intrinsics.9a8db34 -
feat: independent head mount and head sensor; stock D435i sensor
HEAD is replaced by HEAD_MOUNT (fixed | pan_tilt) and HEAD_SENSOR (d435i | d455), chosen independently (UNITREE_G1_MUJOCO_HEAD_MOUNT / _HEAD_SENSOR). The bodies' built-in head_camera becomes a head_sensor_mount at the same pose, oriented so a sensor looks along its +x with +z up; pan_tilt replaces it with its own. sensors/d435i.xml is the stock G1 head camera (fovy 42, the D435i color vertical FOV; it was MuJoCo's default 45). Every option now comes from the argument, else config.yaml, else the default. Composed model names follow LeRobot's g1-<body>-<end_effector>-<head_mount>-<head_sensor>. All 20 previous combinations keep their joints, actuators, masses and camera poses (old none = fixed + d435i, d455_pan_tilt = pan_tilt + d455).c19bad2 -
docs: list the keyboard teleop's t/g base height keys
9ae1da2 -
refactor: drop the grippers/hands end effector aliases; dex1 and dex3 are the only names
8f0a59a -
refactor: drop the dummy end effector alias; rubber_hand is the only name
dd0f1dc -
refactor: split the head into a pan/tilt mount and a D455 sensor
heads/d455_pan_tilt.xml becomes heads/pan_tilt.xml, the mechanism (servos, joints, actuators, tilt bracket) with a head_sensor_mount at the bracket tip, and sensors/d455.xml, the head_camera and its optics in that mount's frame. compose.py's HEADS pairs them: d455_pan_tilt = (pan_tilt, d455), so another camera on the same mount is a new sensors file and one HEADS entry, while the HEAD names LeRobot uses stay. The tilt bracket is head_tilt_link (was d455_link), with its lab mesh renamed to match. All 20 composed models keep their camera poses, joints, actuators and masses. The docs no longer claim the head meshes are not shipped.3b31532 -
refactor: extract bodies and end effectors into reusable parts files
Every model is now one bare body plus parts files, so each end effector and head is defined once and reused on either body: - assets/g1_29dof.xml, assets/g1_23dof.xml: Unitree's bodies without hands and with the bare wrist inertia (the 23dof wrist-roll link is now wrist_roll_link, like on 29dof) - assets/end_effectors/{rubber_hand,dex1,dex3,amazing_hand}.xml: each a left/right_ee_mount body in the 29dof wrist_yaw_link frame with the assets, defaults, actuators, sensors and equality constraints it uses - assets/heads/d455_pan_tilt.xml: the pan/tilt head in the 23dof torso frame rubber_hand, dex3 and the bodies are extracted once from Unitree's full models (named in each file; the originals are in history at 1801640); dex1 and amazing_hand are generated by tools/build_dex1_model.py and tools/build_amazing_hand.py, and ToolsTests checks they are up to date. sim/mjcf/compose.py only attaches parts. The full Dex1/Dex3/rubber-hand models and URDFs, the fused 23dof wrist meshes, p…739c738 -
chore: remove unused exoskeleton URDFs and AmazingHand export sidecars
assets/exo_{left,right}.urdf and assets/meshes_exo_* are not used here: LeRobot's exoskeleton IK loads them from lerobot/unitree-g1-mujoco. The AmazingHand additional.xml, joints_properties.xml and config.json are onshape-to-robot sidecars already inlined into robot.xml.1801640 -
refactor: group model composition in sim/mjcf; test the asset generators
sim/model_builder.py, sim/amazing_hand.py and sim/pan_tilt_head.py only build MJCF, unlike the rest of sim/ (the simulator, DDS bridge, head/hand device), so they move to sim/mjcf/ as compose.py, amazing_hand.py and pan_tilt_head.py. tools/build_dex1_model.py takes an output directory, and ToolsTests checks that both tools reproduce the committed assets (the forearm meshes test skips while the meshes are git-lfs pointers).5fb894a -
refactor: name models by body and end effector; move builders into sim/ and tools/
Assets are named g1_<body>_<end_effector>: g1_{29,23}dof_rubber_hand.xml (were *_no_hand, though they carry the rubber hands), g1_29dof_dex1.xml /.urdf (were with_grippers / with_dex1_1), g1_29dof_dex3.xml/.urdf (were with_hand / g1_body29_hand14), and one scene.xml template (was scene_hands_cameras.xml). The composer builds every scene, so the fixed scene_{23,29,33,43}dof.xml, scene_23dof_ah_d455.xml, g1_23dof_ah_d455.xml and g1_29dof_with_hand_cameras.xml are removed; it adds Dex1's wrist cameras to Dex3 itself. build_g1_ah_model.py is split into sim/pan_tilt_head.py and sim/amazing_hand.py (runtime parts, absolute mesh paths) and tools/build_forearm_meshes.py; build_gripper_model.py becomes tools/build_dex1_model.py and only writes the Dex1 MJCF/URDF. Both tools reproduce the committed files (only the model names changed), and the composed models are unchanged.5896a2a -
feat: compose any body / end effector / head at load time
sim/model_builder.py builds the scene from the bare 29dof/23dof body, an end effector and the head, so every end effector fits either body: rubber_hand (stock passive hand, the base model as is), none (bare wrist), dex1, dex3 (parts of the validated gripper/hand models) and amazing_hand (build_g1_ah_model's graft). Each is defined in the 29dof wrist-yaw frame and mounted there on 29dof, and at the wrist pitch+yaw offsets (+0.084 m) on the 23dof wrist-roll link. Without the rubber hand the wrist gets the bare inertia; the pan/tilt head is shifted into the 29dof torso frame when needed. DDS actuators keep the Hub's interleaved order for motor_effort_limit_list. model_config accepts any combination (dummy is now an alias of rubber_hand; none means a bare wrist) and SimHeadHandDevice serves only the head/hand motors the model has. build_g1_ah_model reads the AmazingHand MJCF from assets/amazing_hand/.6964f84 -
refactor: import the head/hand bridge from lerobot.robots.unitree_g1
cea1831
21 days ago
-
Name the head and hand joints in the G1 MJCF style, matching lerobot's Unitree-style motor names
Head joints/actuators: xl330_joint -> head_yaw_joint, d455_joint -> head_pitch_joint. Hand joints/actuators are named by Feetech servo ID: {side}_hand_finger{i}_motor{j} -> left_hand_motor11_joint..left_hand_motor18_joint and right_hand_motor1_joint..right_hand_motor8_joint. SimHeadHandDevice maps lerobot's kHeadYaw / kLeftHandMotor11 names onto them (mjcf_joint_name). Link names keep the lab URDF's part names. The model is regenerated with build_g1_ah_model.py --head-meshes: the unchanged generator reproduces the previous XML byte for byte, and the new one differs only by the renamed joints/actuators. Requires the matching lerobot (feat/g1_ah_d455, head/hand rename).ad37865 -
Match the head camera's field of view to the D455 color stream at 640x480
fovy 42 gave a ~54x42 deg view, much narrower than the real camera. The D455 color sensor is 90x65 deg at 1280x800; its 640x480 mode keeps the vertical field, so head_camera now uses fovy 65 (~81x65 deg, fy ~377 px).8866e77