A lightweight sim-to-sim framework for evaluating trained locomotion policies on the DeepRobotics M20 legged-wheeled robot. Policies are trained in IsaacLab (Isaac Sim) and deployed directly into MuJoCo for cross-simulator validation — no hardware required.
Author: hadelan
m20_sim2sim/
├── deploy/
│ ├── play_mujoco.py # Main entry point
│ ├── locomotion_policy_wrapper.py # ONNX policy inference wrapper
│ └── utils/mujoco/
│ ├── terrain.py # Procedural terrain generation
│ └── assets/ # Scene XML files & heightfield image
├── policy/
│ ├── exported/
│ │ ├── policy.onnx # Exported policy (ONNX format)
│ │ └── policy.pt # Exported policy (PyTorch format)
│ └── params/
│ ├── env.yaml # Training environment config
│ └── agent.yaml # PPO agent config
└── deep_robotics_model/
└── M20/
├── M20_mjcf/ # MuJoCo MJCF model
├── M20_urdf/ # URDF model
└── M20_usd/ # USD model (for Isaac Sim)
- Python >= 3.10
- MuJoCo >= 3.0
mujocoPython bindingsonnxruntimenumpypygame(joystick input)opencv-python(video recording)matplotlibpyyamlscipy(for Perlin terrain generation)
Install dependencies:
pip install mujoco onnxruntime numpy pygame opencv-python matplotlib pyyaml scipycd deploy
python play_mujoco.py| Key | Action |
|---|---|
A |
Activate locomotion policy |
S |
Deactivate policy (hold standing position) |
R |
Reset simulation |
0 |
Start / stop video recording |
Ctrl-C |
Quit and show diagnostic plots |
The right-hand numpad sends persistent velocity commands (press once → stays active until changed).
| Key | Command |
|---|---|
8 |
Forward (+vx) |
2 |
Backward (−vx) |
4 |
Strafe left (+vy) |
6 |
Strafe right (−vy) |
7 |
Turn left / CCW (+wz) |
9 |
Turn right / CW (−wz) |
5 |
Stop (clear all numpad commands) |
Numpad commands stack additively with joystick input and are reset when the policy is deactivated (S) or the simulation is reset (R).
If a joystick is detected, use it to command the robot while the policy is active:
| Axis | Command |
|---|---|
| Left stick Y | Forward / backward velocity (±2.0 m/s) |
| Left stick X | Lateral velocity (±1.0 m/s) |
| Right stick X | Yaw rate (±1.0 rad/s) |
If no joystick is connected, velocity commands are fixed at zero.
Edit the SCENE variable at the top of deploy/play_mujoco.py:
SCENE = 'flat' # flat ground plane
SCENE = 'random_boxes' # randomly placed boxes
SCENE = 'random_pyramids' # stacked pyramid obstacles
SCENE = 'perlin' # Perlin-noise heightfieldChange TERRAIN_SEED for different terrain layouts (same seed = reproducible terrain):
TERRAIN_SEED = 10| Parameter | Default | Description |
|---|---|---|
SIM_DT |
5e-3 s |
Physics timestep |
DECIMATION |
4 |
Policy runs every N sim steps (policy freq = 50 Hz) |
RENDER_HZ |
30 |
Viewer render frequency |
RAMP_STEPS |
200 |
Smooth ramp-up steps when activating policy |
Defined in deploy/locomotion_policy_wrapper.py:
| Parameter | Value | Description |
|---|---|---|
KP_LEG |
80.0 | Leg joint position gain |
KD_LEG |
2.0 | Leg joint velocity gain |
KD_WHEEL |
0.6 | Wheel velocity gain |
EFFORT_LEG |
76.4 Nm | Leg joint torque limit |
EFFORT_WHEEL |
21.6 Nm | Wheel torque limit |
Replace the files in policy/exported/ with your own exported policy:
policy.onnx— required for MuJoCo inferencepolicy.pt— optional PyTorch checkpoint
The observation space expected by the wrapper (57 dims):
[0:3] base angular velocity (body frame) × 0.25
[3:6] projected gravity (body frame) × 1.0
[6:9] velocity commands [vx, vy, wz] × 1.0
[9:25] joint positions relative to default (wheels zeroed) × 1.0
[25:41] joint velocities × 0.05
[41:57] last action × 1.0
After quitting (Ctrl-C), the following plots are displayed automatically:
- Leg joint torques over time (per joint, per leg)
- Leg joint velocities over time
- Torque–velocity operating point scatter (colour = power)
- |Torque|–|velocity| scatter with constant-power curves
- Wheel velocity and torque over time
Press 0 in the viewer to start/stop recording. Videos are saved as m20_video_<timestamp>.mp4 in the working directory.