Skip to content

protomotions

protomotions tracks a reference motion under physics with NVIDIA GEAR's Generalist Tracking Policy for the Unitree G1.

Deprecated, removed in 0.7

The DeprecationWarning points at wbc, which walks the G1 from velocity commands; it does not track a motion clip, so clip tracking has no in-tree replacement after 0.7.

This page plays a reference motion clip on a simulated Unitree G1 that balances and reacts to contact, and chains it after kimodo.

onnx_path and yaml_path take a local file each; a Hub id is not fetched for you.

pip install 'strands-robots[protomotions,sim-mujoco]'    # onnxruntime + pyyaml + huggingface_hub; the fences below also need mujoco

What it is

ProtoMotionsPolicy wraps the ONNX Generalist Tracking Policy (GTP) from NVIDIA GEAR's ProtoMotions framework, BeyondMimic-trained, published at cagataydev/protomotions-gtp-unitree-g1 (unified_pipeline.onnx plus its unified_pipeline.yaml sidecar). Each tick it reads root and anchor rotation plus joint position and velocity, looks ahead into a MotionPlayer reference window, and emits PD joint targets for the G1's 29 actuators. requires_images is False. Output is smoothed by the config's action_ema_alpha (1.0 is passthrough).

from strands_robots.policies import create_policy

policy = create_policy("protomotions", onnx_path="unified_pipeline.onnx", yaml_path="unified_pipeline.yaml", motion="walk.npz")
policy = create_policy("gtp_g1", onnx_path="unified_pipeline.onnx", motion="walk.npz")   # same provider

Constructor keywords

keyword type default
onnx_path str \| Path \| None None
yaml_path str \| Path \| None None
motion MotionPlayer \| dict[str, Any] \| str \| Path \| None None
session ProtoMotionsSession \| None None
providers list[str] \| None None
history_length int 1
no **kwargs: an unknown keyword is a TypeError

All keyword-only, no **kwargs: a typo is a TypeError at build time. Without yaml_path the ProtoMotionsConfig defaults apply; they match the shipped weights. motion may be a MotionPlayer, a cache dict, or a .npz / .pt path, and may be None at build time. providers defaults to ["CUDAExecutionProvider", "CPUExecutionProvider"]. history_length=1 matches the upstream checkpoint.

Per-call keywords

keyword meaning
motion swap in a new reference without rebuilding the policy (also policy.load_motion(...))
anchor_rot_xyzw, root_ang_vel_local supply these from an IMU on hardware instead of deriving them from the observation

The observation may carry the flat observation.state or the joint names in GTP_G1_JOINT_NAMES directly; either shape works.

From a prompt to a tracked motion

kimodo samples a qpos trajectory. strands_robots.policies.protomotions.bridge.qpos_to_motion_data(qpos, fps, proto_mjcf_path, control_dt=0.02) converts it into a MotionPlayer cache (body positions and rotations, finite-difference velocities). This policy tracks that cache.

from strands_robots.policies import create_policy
from strands_robots.policies.kimodo import KimodoConfig
from strands_robots.policies.kimodo._diffusers_agent import DiffusersKimodoAgent
from strands_robots.policies.protomotions.bridge import qpos_to_motion_data

config = KimodoConfig()
agent = DiffusersKimodoAgent(config)                                # the sampler KimodoPolicy builds internally
qpos = agent.sample("a person waving with the right hand", num_frames=120, diffusion_steps=100, guidance_scale=7.5, seed=0)
cache = qpos_to_motion_data(qpos, fps=config.native_fps, proto_mjcf_path="g1_bm_no_mesh_box_feet.xml")   # (frames, 7 + 29) -> MotionPlayer cache

tracker = create_policy("protomotions", onnx_path="unified_pipeline.onnx", motion=cache)

The MJCF is g1_bm_no_mesh_box_feet.xml from NVlabs/ProtoMotions (curl -LO https://raw.githubusercontent.com/NVlabs/ProtoMotions/main/protomotions/data/assets/mjcf/g1_bm_no_mesh_box_feet.xml); its three meshed siblings need Git LFS, otherwise the parse fails with decoder failed for mesh file.

Run it

Needs the extra and the two artifact files.

from strands_robots.simulation import create_simulation

sim = create_simulation("mujoco")
sim.create_world()
sim.add_robot("g1")
result = sim.run_policy(
    robot_name="g1",
    policy_provider="protomotions",
    policy_config={"onnx_path": "unified_pipeline.onnx", "yaml_path": "unified_pipeline.yaml", "motion": "walk.npz"},
    duration=5.0,
    control_frequency=50.0,
)
print(result["status"])

Limits

  • Unitree G1 only, 29 actuators, the ordering in GTP_G1_JOINT_NAMES.
  • The tracker needs a reference. With motion=None and no per-call motion, get_actions refuses.
  • Without a CUDA provider the ONNX runs on CPU at a lower rate; the default provider list falls through to it.
Edit page