Skip to content

Repository files navigation

Hexapod Model Editor

Browser-based editor, analysis suite and simulator for a tendon-driven, BLDC-actuated hexapod. Everything runs client-side: physics is MuJoCo compiled to WebAssembly via @mujoco/mujoco, rendering is react-three-fiber. There is no backend and no server-side Python — the app works fully offline once loaded.

Shared publicly as a portfolio piece. All rights reserved — see License.

Contents

Prototype: the design study behind this editor

Before there was an editor, there was a non-browser design study that arrived at the robot's spec and caught its own errors along the way. That study is kept at prototype/ — not part of the app's runtime or test suite (that's reference/, above, the curated port target), but the record of how the numbers in reference/ and docs/SPEC.md were derived:

  • prototype/readme.md — a retrospective on the whole study, start to finish: what was built, nine confident results that turned out wrong (grouped by kind, with what actually caught each one), and how those errors became the ADR's §12 pitfalls list and §10 acceptance tests.
  • prototype/leg/README.md — the single-motor, three-segment tendon leg: pulley routing and gain optimisation for a straight foot path, cable sizing, BLDC motor/drivetrain sizing, and the parallel gravity-compensation and series jump/creep springs. Leg prototype in a native MuJoCo viewer
  • prototype/hexapod/README.md — the six-leg layout revision, from a radial hexagon to the rectangular 3+3 layout the current spec uses, including two corrections logged inline (an unneeded fourth leg segment, and a swing-clearance measurement taken in the wrong reference frame).

See docs/ADR-001-hexapod-editor.md for the full design record (nine decisions, eight implementation phases) and docs/SPEC.md for the hexapod design spec.

Installation

Requires Node.js 20+ and npm.

npm install

This pulls in @mujoco/mujoco (pinned to 3.11.0) along with the rest of the toolchain (Vite, React, three.js, vitest). No native build step and no Python are needed to install or run the editor itself — Python is only needed later, to run an exported bundle (see Exporting a bundle).

Running the app

Start the Vite dev server:

npm run dev

This prints a local URL (typically http://localhost:5173). Open it in a recent Chromium, Firefox, or Safari — WebGL2 and WebAssembly are required, which every current desktop browser supports. The hexapod should appear standing on a floor plane within a second or two of page load.

Other scripts:

npm run build     # production build (tsc -b && vite build) -> dist/
npm run preview   # serve the production build locally, to sanity-check it
npm test          # run the vitest suite (see Testing below)
npm run lint      # oxlint
npm run export    # write a Python export bundle to disk (see below)

Deployment

npm run build produces a static site in dist/ — an index.html, a JS bundle, and the MuJoCo WASM binary (~10 MB uncompressed, ~2.5 MB gzipped). There is no server-side component, so deployment is just "serve dist/ as static files":

npm run build
npx serve dist        # or any static file server / CDN / GitHub Pages / S3+CloudFront

Two things to check on whatever host you use:

  • The WASM file must be served with a correct MIME type (application/wasm). Most static hosts get this right by default; if the app fails to load with a WASM-related console error, check this first.
  • The app needs no HTTP API, so any static host works — there's nothing to configure beyond serving files and (ideally) enabling gzip/brotli compression for the WASM binary, given its size.

Using the app

The layout, top to bottom:

HEXAEDITOR UI

  • Hierarchy (left): the model's body tree. Click a body to select it — selection drives both the Inspector and the Viewport's gizmos.
  • Viewport (centre): the live 3D scene, synced from MuJoCo's own model state every frame. Orbit with the mouse. Selecting a body with an editable geometric parameter shows a real 3D transform gizmo scoped to exactly that parameter: a translate arrow for segment length, mount offset, or hip position; a rotate ring for mount azimuth/tilt/splay — each restricted to the single axis that field actually drives, so dragging can't put the model in a state with no parameter to write the result back to. A floating readout shows the live value while dragging; the edit commits on release.
  • Inspector (right): text/number fields for whatever's selected — segment lengths, motor specs, cable diameters, gait tuning, drive ratios, and so on. Edits commit on blur/drag-release, not on every keystroke, so the model doesn't reload on every intermediate value.

Edit mode

The default mode: a static pose at the nominal crouch angle, gizmos live, no physics stepping. Every parameter edit here re-runs the derivation cascade (gain optimisation, pulley radii, joint ranges, mass, etc. — see ADR §4) and regenerates the MuJoCo model from scratch (never mutates a loaded model in place, per ADR-4).

Play mode

Real-time physics stepping with the current tripod gait. Gated on gait validity: the Play button is greyed out with "(gait stale)" until a tier-2 gait evaluation exists for the current parameters — click "run gait analysis" in the toolbar first, or a Sweep/Dashboard gait run satisfies the same gate.

Keyboard controls once in Play:

key action
W / S walk forward / backward
A / D turn left / right
Space stop and stand
+ / - gait frequency
[ / ] stride (yaw amplitude)
P toggle gravity-compensation springs

A live readout (speed, tilt, per-axis RMS current, watts, temperature rise) is shown on screen while walking.

Demo: walking in the browser

The hexapod walking the tripod gait live in Play mode, entirely in-browser (MuJoCo/WASM physics, react-three-fiber rendering — no server round-trip).

Hexapod walking in the browser editor's Play mode

Record mode

A headless-speed run: walks the current gait for a fixed duration, captures a full speed/tilt trace (not just a single summary number), and reports the average and max speed once done. Useful for a more thorough read on a gait than the single "run gait analysis" snapshot.

Dashboard

Bottom panel, collapsible, one card per analysis (ADR §6/§8):

card tier what it shows
Gain optimisation 1 tendon gains and pulley radii, live
Foot path 1 straightness and stroke of the foot trajectory
Yaw lever 1 horizontal hip-to-foot moment arm
Mass budget 0 per-item mass breakdown vs the 7.5 kg design target
Climb torque 0 required yaw torque/ratio to climb, from mass and lever
Thermal (rated) 0 steady-state temperature rise at rated current
Gait 2 speed/tilt/stand-height/current from a full physics run

Every figure carries a provenance badge — [CALC] (computed formula) or [SIM] (from an actual physics run). Tier 0/1 cards update live as you edit parameters; the Gait card is tier 2 (explicit "run" button, a few seconds) and shows a "stale" indicator (without clearing its last result) the moment any parameter changes underneath it.

Sweep

Second bottom panel, collapsed by default (a sweep is a longer, explicit action). Define a 2D parameter grid over any two of the gait knobs (frequency, amplitude, stance, lift), hit "run sweep", and get three heatmaps back — speed, tilt, and current — one cell per grid point, each computed by an actual physics run dispatched across a Web Worker pool. Progress and cancellation are both live during a run.

Once a sweep completes, a "best safe cell" panel picks the fastest grid point that stays within the reference fall-tilt threshold (35°) and each drive's rated continuous current — with a one-click "apply to gait params" button that commits that point's frequency/amplitude/stance/lift back into the model.

Saving and loading a model

The editor keeps no state outside the current page — reload the tab and you're back to the default hexapod. Save (toolbar, top right) downloads the current edited model as a plain JSON file (hexapod-params-<timestamp>.json); Load opens one back up via a file picker and fully replaces the live model, including re-running the whole derivation cascade for the loaded geometry.

This is a snapshot of the editable parameters (segment lengths, mounting, layout, cable/drive/gait tuning, ...) — not the same thing as an exported bundle, which is a derived, training-ready artifact with its own params.json shape. A saved file round-trips back into the editor; an exported bundle does not.

A few things worth knowing:

  • Loading a file with a plausible-but-wrong shape (missing a field, wrong type, wrong-length array) is rejected up front with a specific error in the toolbar (e.g. load failed: params.segments: missing) rather than corrupting the model or failing obscurely somewhere downstream.
  • A loaded model has no gait result for its own geometry yet, so Play and Record go back to gated ("gait stale") until you run gait analysis again — the same gate any other parameter edit triggers.
  • Loading while in Play mode drops back to Edit automatically, so the physics loop doesn't keep stepping a simulation that was just replaced out from under it mid-walk.

Exporting a bundle

The point of the export is training-ready portability (ADR-9): a self-contained directory with a stable API, meant for reinforcement learning or offline analysis outside the browser, not just a static snapshot for viewing.

From the browser

Click Export in the toolbar (top right). This is an explicit action — it runs a real 4-frequency gait sweep at full settle/measure timing to populate the bundle's expected numbers, so it takes roughly 15–25 seconds. When it finishes, your browser downloads a single zip file, hexapod-export-<hash>.zip, containing seven files:

hexapod.xml         params.json         README.md
hexapod_env.py       hexapod_gait.py     hexapod_viewer.py
check_export.py

Unzip it into a directory before using them.

Caveat: the browser can't run Python, so this path's README.md does not include a pre-run check_export.py output — it just tells you to run it yourself once the files are in place.

From the command line (recommended)

npm run export                    # writes to ./export-bundle/
npm run export -- /path/to/out    # or a directory of your choosing

This does everything the browser button does, and additionally: copies reference/check_export.py in unmodified, actually runs it against the freshly-written bundle (if python3/python is on PATH), and embeds the real check output into README.md — matching ADR-9's spec for the README exactly. If no Python interpreter is found, it says so in the README instead of failing the export.

Running the exported hexapod

The bundle needs a Python environment with mujoco (matching the pinned 3.11.0 used by the browser build) and numpy; gymnasium if you want to use hexapod_env.py as a real Gym environment; mujoco-mjx + jax only if you want the MJX compatibility check to actually run instead of skipping itself.

python3 -m venv venv && source venv/bin/activate
pip install mujoco numpy gymnasium   # gymnasium optional; mujoco-mjx+jax optional
cd export-bundle                     # or wherever you moved the downloaded files
python check_export.py               # verify the bundle before trusting it
python hexapod_viewer.py             # interactive viewer, WASD to walk
python hexapod_viewer.py --test      # headless self-check, no display needed

hexapod_env.py exposes HexapodEnv (Gymnasium API: reset()/step(), 12-dim normalised action space, 42-dim observation) and the standalone envelope() function the environment uses internally to convert PD torque demand into an actual motor torque/current, clamped by the motor's torque/speed curve — every joint is driven through this envelope, never a bare MuJoCo <position> actuator, which is exactly what check_export.py's "BLDC ENVELOPE" check group exists to verify. A <position> actuator applies kp * error with no such limit and produces a policy that commands torque the real drivetrain can never deliver.

hexapod_gait.py exposes GaitPolicy, the same optimised tripod gait the editor itself walks with, wrapped as act(obs) -> action — the same calling convention a trained RL policy would have, useful as an imitation target, a residual-policy base, or just a sanity-check baseline.

Demo: the exported bundle in MuJoCo

The same gait, replayed outside the browser — hexapod_viewer.py running the exported hexapod.xml and GaitPolicy in a native MuJoCo viewer, driven through the BLDC torque/speed envelope rather than an ideal servo.

Exported bundle walking in a native MuJoCo viewer

Testing

npm test

Runs the full vitest suite, including tests that load real MuJoCo WASM and step actual physics (not mocked) — some tests take a few seconds each for that reason. Two dynamics-table rows (2.5 Hz and 3.0 Hz) are known, pre-existing near-misses against the ADR §10 acceptance table's tight ±0.10 km/h tolerance, documented in docs/ADR-001-hexapod-editor.md.

Project layout

src/
  model/          parameter model, derivation DAG, XML generator, gain optimiser,
                  session save/load (serialize.ts)
  sim/            MuJoCo wrapper, BLDC envelope, gait controller, worker pool, sweeps
  ui/             React components -- Viewport, Hierarchy, Inspector, Toolbar,
                  Dashboard, Sweep, gizmos, save/load file I/O (paramsFile.ts)
  export/         Python bundle generator (params.json/README templating,
                  the three static .py sources, hash/zip/download helpers)
scripts/
  export-bundle.ts   Node CLI for `npm run export`
reference/        the original Python prototype this project ports from,
                  plus check_export.py (the bundle's own self-check contract)
docs/
  ADR-001-hexapod-editor.md   the design record -- decisions, phases, acceptance tests
  SPEC.md                     the robot's design spec

License

All rights reserved — see LICENSE. This repository (code, documentation, specifications, and design files alike) is shared publicly for portfolio review only; no permission is granted to use, copy, modify, or redistribute it, or any derivative of the hardware design it describes, without prior written permission.

About

Browser-based editor, analysis suite, and simulator for a tendon-driven, BLDC-actuated hexapod — MuJoCo/WASM physics, react-three-fiber rendering, fully client-side.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages