ππ Our AutoMech project won 2nd place in the Hardware AI Innovation track at the Microsoft Global Intern Hackathon 2026! ππ₯
Task-oriented CAD generation, closed-loop with physics. Instead of producing geometry that merely looks plausible, every design is simulated under the user's actual task and judged on what it did. Failures come back as metrics, frames, and concrete fix hints, and the design is revised until it physically works.
Thesis (shown empirically): numeric pose metrics gave a false PASS on the ANYmal stand-still run (tilt read 2.1Β°), while the VLM watching the frames correctly said FAIL β "tips onto its side by frame 3, ends overturned." A design is only as good as the judge, and the judge has to watch the machine work.
https://github.com/willejiang/AutoMech/raw/main/assets/AutoMech1.mp4
| Four-planet 4:1 reducer | Horizontal slider-crank |
|---|---|
![]() |
![]() |
| Exact 4:1 reduction; all four planets are carried and spin locally. | 24.006 mm stroke with forward and return motion. |
The reviewed cumulative audit scores AutoMech 860/1000 (5/10 strict mechanical successes). Under the same strict realization standard, Codex passes 1/10 and Claude Code passes 0/10. See the unified benchmark report for raw-versus-adjudicated evidence, exact-solid collision checks, and the temporary representation ablation.
One app, one command. npm run dev serves the AutoMech web UI, which spawns the
Python pipeline directly and streams its stages back over SSE. Everything runs on
your machine, on the CPU β no GPU, no Docker, no database.
- Maker =
maker2/β the CAD pipeline. An agent authors the machine as a build123d script, exports per-part meshes, and a VLM judges six rendered views. - Evaluator =
evaluator/β the physics half. MuJoCo drives the machine under the task and reports whether it did the job, not whether it looks right. Failures come back as metrics + video and drive the next iteration. - UI =
src/β React 19 + TanStack Start. Prompt bar, live pipeline timeline, orbitable 3D model, physics panel with the recorded MP4, and past runs.
npm install && npm run dev # β http://localhost:3000There is a second, older path in this repo β orchestrator/ driving Isaac Sim on a
GPU box. It is what the hackathon demo above ran, and it still works, but it is no
longer the main line. It is documented in
Appendix: the Isaac Sim path.
See maker2/PIPELINE.md for the pipeline internals and
DESIGN_LOOP.md for the loop's architecture.
The default path is single-agent: one agent authors the whole machine as one build123d script. Earlier versions split the machine across a boss and per-subassembly managers, and each seam between agents was a place for the assembly to go wrong; one agent holding the whole model has no seams to get wrong.
agent (LLM) prompt β ONE build123d script: every part, its pose, and how the
β parts join. Optionally grounded by the local KB and web search.
βΌ
build the script runs β per-part meshes + a rigid-conflict self-check
β (interference vs. clearance fits, unsupported parts, overlaps).
β Conflicts go back to the agent with coordinates, not adjectives.
βΌ
judge (VLM) six offscreen views; FAIL feeds concrete fixes into the next pass.
βΌ
MJCF assembled every part a flat body in world coordinates; joints become MuJoCo
β joints, static structure becomes equality welds, collision geometry
β is signed-distance (SDF) off the original mesh.
βΌ
physics (MuJoCo) strategy_selector picks the test; scenario_designer writes free
setup(m,d) / control(m,d,t) Python; the run measures whether the
machine DID THE JOB. Records an MP4 per test.
Why physics, and not just a prettier render. A gearbox that renders perfectly can still be a solid brick. The test drives the input and measures the output β parts that must turn, turn; parts that must stay put, stay put. The judge that matters is the one watching what the machine does.
Key files:
| Path | What |
|---|---|
maker2/run.py |
Driver + refine loop; writes result.json, run.json, per-thread thread.json. |
maker2/single_agent.py Β· maker2/prompts/ |
The default path: one agent β one build123d script β self-check β refine. |
maker2/mjcf_builder.py |
Parts β MJCF: flat world-space bodies, real mesh inertia, SDF collision, press-fit vs. clearance fits. |
maker2/physics.py |
strategy_selector β scenario_designer β MuJoCo run; encodes a per-test MP4. Support test runs in parallel. |
maker2/support_test.py |
Is every part actually held up by something, or is it floating? |
maker2/kb/ |
Retrieval corpus (fits, materials, what passive parts ride on) injected into the agent's prompt. |
maker2/config.py |
All settings; resolution order is defaults < JSON file < env vars < CLI. |
evaluator/run_scenario_mujoco.py |
Runs the scenario, including the designer's own setup/control functions. |
Still available with --hierarchy, for machines big enough that one script gets
unwieldy. A boss splits the machine into subassemblies and authors a graph of typed
seams (topology, never placement coordinates); one manager builds each
subassembly in isolation; a deterministic assembler solves the placement β gear
meshes land at the true center distance read off the built gears (module Γ teeth),
so they engage by construction rather than by an LLM guessing coordinates.
python -m maker2.run "a two-stage gear reducer" --hierarchy --kb --deep-think --jsonSee maker2/PIPELINE.md for every agent's I/O, the
deterministic gates between them, and how a rejection routes back (rebuild one
subassembly vs. re-plan the whole machine).
One machine, three processes. The browser talks to the app; the app spawns Python.
flowchart TB
B["Browser β prompt bar, pipeline timeline,<br/>3D canvas, physics panel + MP4"]
A["Node β TanStack Start / Nitro (src/)<br/>routes/api/* spawn Python, tee SSE to disk"]
P["Python β maker2 + evaluator<br/>build123d β meshes β MJCF β MuJoCo"]
G["Any OpenAI-compatible LLM gateway"]
D[("output/threads/<id>/<br/>events.ndjson Β· model.glb Β· MJCF Β· MP4")]
B -->|"GET /api/run-maker2-stream (SSE)"| A
A -->|"spawn python -m maker2.run --json"| P
P -->|"stdout stage lines β SSE events"| A
A -->|"stage / artifact / result"| B
P <-->|"chat + vision"| G
P --> D
A --> D
classDef c fill:#1e3a5f,stroke:#4a90d9,color:#fff
class B,A,P,G,D c
| Component | Where | GPU? |
|---|---|---|
src/ β UI + API routes |
your machine, Node | no |
maker2/ β CAD agents, MJCF build |
your machine, Python | no |
evaluator/run_scenario_mujoco.py β physics |
your machine, Python | no |
| LLM gateway | wherever you point it | β |
The NodeβPython handoff is a subprocess and its stdout, not a network call: the
API route spawns python -m maker2.run, turns each stage line into an SSE event, and
tees the stream to output/threads/<id>/events.ndjson so reopening a run replays it.
Nothing needs a database.
| Path | What |
|---|---|
src/ |
The app β React 19 + TanStack Start UI and the API routes that spawn the pipeline. |
maker2/ |
Maker β the CAD pipeline: agents, build123d geometry, MJCF assembly, gates, and the retrieval KB. |
evaluator/ |
Evaluator β the MuJoCo/PyBullet scenario runners, strategy_selector/scenario_designer, and the VLM judge. |
maker2/PIPELINE.md |
Pipeline internals: agent I/O and the deterministic gates between them. |
docs/ |
Findings and plans β notably CONTACT_PHYSICS_FINDINGS.md (what MuJoCo contact actually does at assembly scale). |
benchmark_scorer/ Β· benchmark_results.md |
Frozen Comfort v1 tasks, strict scorer, executable goldens, and unified results. |
orchestrator/ |
The older Isaac Sim loop β see the appendix. |
assets/ |
Demo recording and benchmark GIFs. |
Prerequisites
- Node.js
^20.19.0 || >=22.12.0, npm>=10. - Python 3.10+.
- An OpenAI-compatible LLM gateway β either your own key or a local proxy.
Install and run
npm install
python -m pip install -r maker2/requirements.txt
python -m pip install -r evaluator/requirements.txt # mujoco, trimesh, imageio-ffmpeg, ...
npm run dev # β http://localhost:3000Point it at a model. Open Settings in the sidebar and fill in the gateway URL,
model, and API key; "Save & test" makes a real call and tells you whether the gateway
answers. The key is stored server-side in .automech/llm.json (mode 0600,
gitignored) and never goes in the browser.
Equivalently, by environment β these win over the settings file:
| Variable | Purpose | Default |
|---|---|---|
FREECAD_AI_BASE_URL |
gateway base URL; the /v1 suffix is required |
http://127.0.0.1:8313/v1 |
FREECAD_AI_API_KEY |
key for that gateway | β |
FREECAD_AI_MODEL |
model id | claude-opus-4.8 |
PYTHON_BIN |
interpreter the app spawns | python3 |
Full resolution order is defaults < .automech/llm.json < environment < CLI flags
(maker2/config.py).
Or skip the UI and run the pipeline straight from the terminal:
python -m maker2.run "a hand-cranked gear reducer" --json --physics
python -m maker2.run "a two-stage gear reducer" --hierarchy --kb --deep-think --jsonTests are executable golden scripts, not a pytest suite β run one directly:
python -m maker2.tests.golden_two_gears
npm run typecheckThe loop that won the hackathon, kept because it still runs and because its thesis β trust the camera, not the pose numbers β is what the current physics test inherited. It is heavier: a GPU box, Docker, and Isaac Sim, none of which the main path needs.
orchestrator/ drives: generate β render β six-view visual gate β author a manifest β
simulate in Isaac Sim β feed failures back. Three execution locations, not two β the
client drives the server over SSH, and the server runs the GPU container.
flowchart TB
subgraph CLIENT["β CLIENT β your laptop / dev machine"]
direction TB
C2["orchestrator/automech_loop.py<br/>(--dry-run when box offline)"]
C3["render_views.py<br/>native OpenSCAD CLI β STL + 6 views"]
end
subgraph SERVER["β‘ SERVER HOST β GPU box (e.g. Aliyun A10), Ubuntu"]
direction TB
S1["evaluate.sh / loop.py / analyze.py<br/><b>plain host processes β NOT in Docker</b>"]
S2["host .env (VLM key)<br/>/data/physcad Β· /data/isaac-cache"]
S1 --- S2
end
subgraph DOCKER["β’ DOCKER CONTAINER β isaac-sim:6.0.1 on the server (GPU)"]
direction TB
D1["run_eval.py / run_scenario.py / run_eval_urdf.py<br/>+ isaaclab/*.sh<br/>(Isaac Sim + Isaac Lab API)"]
D2["sees only mounts:<br/>evaluator/ β /code (ro)<br/>/data/physcad β /work (rw)"]
D1 --- D2
end
C2 -->|"ssh / shell evaluate.sh (NOT an HTTP API)"| S1
S1 -->|"docker run --gpus all + mounts"| D1
D1 -->|"sim_result.json (shared mount)"| S1
S1 -->|"result.json"| C2
classDef client fill:#1e3a5f,stroke:#4a90d9,color:#fff
classDef server fill:#2d4a2d,stroke:#5cb85c,color:#fff
classDef docker fill:#5a2d2d,stroke:#d9534f,color:#fff
class C2,C3 client
class S1,S2 server
class D1,D2 docker
Why the split: the container has the GPU but must not hold the VLM API key, so
analyze.py runs on the host. The hostβcontainer handoff is a file on a shared
mount (sim_result.json), not a network call. Note the path rewrite: the host's
/data/physcad/... is the container's /work/... β same bytes, two names.
β οΈ Not in this repo (installed externally): Isaac Sim (thenvcr.io/nvidia/isaac-sim:6.0.1Docker image) and Isaac Lab (cloned from GitHub). The repo carries only the scripts that run inside that container. Isaac Sim Β· Isaac Lab.
Architecture in detail: DESIGN_LOOP.md Β·
evaluator/ARCHITECTURE.md Β·
orchestrator/README.md.
Client: Node β₯20.19, OpenSCAD native CLI + BOSL2/MCAD
(openscad.org), Python 3 with openai.
GPU box: an NVIDIA GPU with RT cores (A10 proven) + driver β₯ 595.58.03;
Docker + NVIDIA Container Toolkit; host dirs /data/physcad (mounts to
/work) and /data/isaac-cache; Python 3 with openai on the host.
China-network note:
nvcr.io(NGC) works for pulling Isaac Sim; Docker Hub andnvidia.github.ioare blocked. Use USTC/Tsinghua mirrors for apt + the NVIDIA Container Toolkit.debs, and the Tsinghua pip index for Isaac Lab (install_isaaclab.shalready setsPIP_INDEX_URL).
Done on the GPU server. Neither Isaac Sim nor Isaac Lab lives in this repo.
docker pull nvcr.io/nvidia/isaac-sim:6.0.1 # anonymous pull works; ~20 GBIsaac Sim Β·
NGC catalog.
Verify GPU-in-container: docker run --rm --gpus all nvcr.io/nvidia/isaac-sim:6.0.1 nvidia-smi.
Isaac Lab is cloned, not vendored. Put it in the host dir that mounts to
/work so it appears at /work/IsaacLab inside the container:
# on the host
cd /data/physcad
git clone https://github.com/isaac-sim/IsaacLab.git
# start the container with the GPU + mounts, OVERRIDING the entrypoint, as ROOT
# (base image runs as uid 1234; root is needed for pip installs + writable mounts).
# Disable OmniHub β it deadlocks in a reconnect loop and freezes training.
docker run -it --gpus all --runtime=nvidia --user root \
--entrypoint /bin/bash -e OMNI_HUB_DISABLE=1 \
-v /data/physcad:/work \
nvcr.io/nvidia/isaac-sim:6.0.1
# inside the container: run the project's installer (does ./isaaclab.sh --install rl,
# Tsinghua pip mirror, sanity-imports isaaclab + rsl_rl). The restructured source
# also needs the isaaclab_physx + isaaclab_contrib extensions installed:
bash /work/<repo>/evaluator/isaaclab/install_isaaclab.sh
/isaac-sim/python.sh -m pip install -e source/isaaclab_physx -e source/isaaclab_contrib
# from ANOTHER host shell, snapshot the container as a reusable image:
docker commit <container_id> isaac-lab:6.0.1Isaac Lab repo Β· Isaac Lab docs.
Version pairing caveat: Isaac Sim and Isaac Lab versions are tightly coupled. This repo targets Isaac Sim 6.0.1 paired with Isaac Lab 3.0.0-beta2 (what ran on ANYmal/Cassie + the dog). A fresh
pip install -emay pull a newer Isaac Lab whose API moved (RigidBodyMaterialCfgβisaaclab_physx), so pin the 3.0.x source and installisaaclab_physx+isaaclab_contribas above. If you pick a different version, confirm its Isaac Sim pairing in the installation guide first, and update the image tag inevaluator/evaluate.sh+evaluator/loop.pyto match.
mkdir -p /data/physcad /data/isaac-cache
cp evaluator/.env.example evaluator/.env # then fill in the VLM endpoint + keyevaluator/.env drives analyze.py / scenario_designer.py. It points at any
OpenAI-compatible LLM gateway; pick the VLM via AZURE_VLM_DEPLOYMENT using
provider/model ids (anthropic/claude-opus-4.8, openai/gpt-5.4,
google/gemini-3.1-pro-preview), or use Azure OpenAI directly β see the comments in
evaluator/.env.example.
(a) Evaluator on a single design dir (manifest + .scad/.stl):
# on the server host
cd evaluator
./evaluate.sh /data/physcad/<design_dir> # β <design_dir>/out/result.json(b) The iterating scenario-spec loop (URDF + task, revises until PASS):
python3 loop.py --urdf .../robot.urdf --asset-root ... \
--task "make sure it can stand still" --workdir /data/physcad/loop_x --max-iters 4(c) The full automation loop (maker β evaluator), from the client:
cd orchestrator
cp .env.example .env # then fill in the VLM endpoint + key
python automech_loop.py --task "quarter-car suspension that clears a 10cm curb" \
--dry-run --max-iters 3Drop --dry-run once the GPU box is up.
β οΈ --dry-runis NOT zero-setup. It stubs only the Isaac Sim step (so you don't need the GPU box / Docker). The stages before it still run for real and have hard prerequisites:
orchestrator/.envwith a working VLM endpoint + key β generation, the visual gate, and the URDF author all make live LLM/VLM calls. Without it the very first step fails withKeyError: 'AZURE_OPENAI_ENDPOINT'.- The native OpenSCAD CLI on PATH (
OPENSCAD_BINoropenscad) β the render stage compiles the.scadto STL + the 6 views; with no OpenSCAD it can't produce views and the gate fails closed.So
--dry-runexercises generate β render β visual gate β author β (stubbed) sim β feedback β everything except the GPU physics. To check pieces in isolation without the full loop, the "Test individual stages" section oforchestrator/README.mdruns render / gate / generation on their own; a built-in fully-offline mock of the whole loop is not yet wired.
Three container gotchas (handled in the scripts): the isaac-sim image's default entrypoint launches the WebRTC streamer and swallows your script β override it (
--entrypoint /isaac-sim/python.sh). Run the sim container detached (-d); an SSH "Connection reset" kills an attached container mid-run. And disable OmniHub (-e OMNI_HUB_DISABLE=1) β if its cache service fails to launch it spins in a reconnect loop and freezes training before the GPU engages (no checkpoints, GPU stuck idle).
- build123d β https://build123d.readthedocs.io/
- MuJoCo β https://mujoco.readthedocs.io/
- Isaac Sim β https://developer.nvidia.com/isaac-sim
- Isaac Lab (repo) β https://github.com/isaac-sim/IsaacLab
- Isaac Lab (docs / install) β https://isaac-sim.github.io/IsaacLab/
- OpenSCAD β https://openscad.org/
- Internal:
DESIGN_LOOP.mdΒ·evaluator/ARCHITECTURE.mdΒ·orchestrator/README.md

