3d is a command-line + web toolkit for the whole FDM (filament 3D-printing) lifecycle:
parametric modeling (OpenSCAD-first) → render & view → mesh / printability / collision
verification → AI-assisted design, animation, simulation, matching → slicing & print prep.
It is engineering-first today (functional parts, fits, gates) and grows toward art later.
Everything is one discoverable dispatcher: 3d <command>, scriptable, composable, with
structured, actionable errors (what failed, why, and the exact fix).
It is general-purpose across 3D FDM work. One of the pipelines it ships is a reference-photo match loop (camera-locked render → silhouette score → LLM numeric-delta edits → manifold/printability gates → accept-only-if-it-improves) — see Reference-match pipeline — one example workflow among many.
3d is a Swiss-army knife for the whole 3D FDM lifecycle — not a single-purpose tool. Major use cases:
- Reference-photo match — tune a parametric model to match a photo (one pipeline among many)
- Design from scratch with AI — text-to-3d, dimensions-and-sketch-to-3d, parametric skeleton generation (planned)
- Parts & fixtures — design brackets, mounts, connectors, enclosures with parametric constraints
- Animation & motion — kinematics, motion verification (planned)
- Simulation & analysis — FEA, strength, thermal, collision detection (planned)
- Format conversion & AR — export to USDZ/GLB/STEP, view in AR (partial: USDZ ready, GLB/STEP planned)
- Slicing & print monitoring — slice to G-code (ready), monitor prints, failure recovery (planned)
- Batch & automated workflows — multi-angle renders, batch exports, CI gates
The reference-photo match pipeline is documented below as one example workflow.
Your kid's stroller bracket snapped. You photograph the broken piece, write a rough
parametric bracket.scad, then let the CLI match it to the photo and verify it is printable.
# 1. Fit the camera to the photo so the render matches the viewpoint
3d fit-camera bracket.scad photo.jpg --out camera.json --draw-axes
# 2. Match loop: nudge parameters until silhouette matches the photo
3d match bracket.scad photo.jpg --rounds 10 --ortho --cam "$(jq -r .camera_arg camera.json)"
# 3. Verify the result is manifold and printable
3d check bracket.scad --mesh --printability
# 4. Export and slice
3d export bracket.scad -o bracket.stl
3d slice bracket.stl -o bracket.gcodeYou want a custom mount for a Wi-Fi router. You measure the hole spacing, write a parametric model, then gate it before printing.
# 1. Scaffold a project
3d init router-mount --no-input
# 2. Edit router-mount/router-mount.scad with your dimensions
# 3. Render preview
3d render router-mount/router-mount.scad --view 3-4 -o preview.png
# 4. Check printability (wall thickness, overhangs, min features)
3d check router-mount/router-mount.scad --printability
# 5. If it passes, export and slice
3d export router-mount/router-mount.scad -o router-mount.stl
3d slice router-mount.stl -o router-mount.gcodeYou have a photo of an old machine part and want a replica. The CLI finds the camera pose, then an AI agent iteratively adjusts the model.
# 1. Preprocess the reference (subject mask + depth)
3d preprocess vintage.jpg -o work/
# 2. Fit camera to the masked reference
3d fit-camera replica.scad work/mask.png --out camera.json
# 3. Agent-driven match loop (dry-run to test the pipeline first)
3d match replica.scad work/mask.png --rounds 5 --dry-run --ortho --cam "$(jq -r .camera_arg camera.json)"
# 4. Run the real loop with an LLM critic
3d match replica.scad work/mask.png --rounds 15 --ortho --cam "$(jq -r .camera_arg camera.json)"Add a 3d check step to your repository CI so no broken geometry ever reaches the printer.
# In your CI script (GitHub Actions, GitLab CI, etc.)
3d check models/*.scad --mesh --printabilityA non-manifold model or a part with 0.4 mm walls on a 0.6 mm nozzle exits non-zero, so the CI job fails and the PR is blocked.
Generate a full set of views for a catalog or documentation page.
3d render bracket.scad --multi docs/assets/bracket/This produces bracket_front.png, bracket_back.png, bracket_left.png, bracket_right.png, bracket_top.png, bracket_iso.png
concurrently — one command, six angles.
Every 3d command is a plain Unix tool: it reads files, writes files, prints text to stdout,
and exits with a meaningful code. This means you can compose them exactly like grep, awk,
or make.
# Save full render output (including possible OpenSCAD warnings) to a log
3d render bracket.scad --view 3-4 -o bracket.png > render.log 2>&1
# Extract just the IoU score from a machine-parseable score run
3d score bracket.png ref.jpg | grep IoU | awk -F= '{print $2}' > iou.txt# Only export if the model is valid, only slice if export succeeded
3d check bracket.scad && 3d export bracket.scad -o bracket.stl && 3d slice bracket.stl -o bracket.gcode
# If check fails, open the preview to debug
3d check bracket.scad || (3d render bracket.scad --view 3-4 -o debug.png && open debug.png)# Render using the fitted camera from a previous step
3d render bracket.scad --ortho --cam "$(jq -r .camera_arg camera.json)" -o fit.png
# Run all gates and email the log if something breaks
3d check assembly.scad --collision verify/collision.json > check.log 2>&1 || mail -s "CHECK FAIL" me@example.com < check.log# Render two parts simultaneously, then compare them
3d render part-a.scad --view iso -o a.png & PID1=$!
3d render part-b.scad --view iso -o b.png & PID2=$!
wait $PID1 $PID2 && 3d compare a.png b.png3d is designed to be driven by agents — not just humans. The output is machine-parseable,
errors are structured, and every command is idempotent and scriptable.
An agent (LLM, Claude Code, Codex, etc.) writes or edits a .scad file. The CLI
immediately validates it:
# Agent writes a new model
# ...
# Agent runs the gate
3d check new_part.scad --mesh --printability
# If exit code != 0, agent reads the structured error, fixes the model, and retries.The match loop is the canonical agent-driven workflow:
# Agent proposes a parameter change
# 1. Render with current params
3d render model.scad --ortho --cam "$(jq -r .camera_arg camera.json)" -o render.png
# 2. Score against reference
3d score render.png ref.jpg
# 3. Check manifold / printability
3d check model.scad
# 4. Agent reads the score, decides the next edit, and repeats.Because 3d score prints AE=..., IoU=..., CLOSENESS=... as plain KEY=VALUE lines,
agents can parse them with a simple regex — no JSON schema needed.
# .github/workflows/3d-gate.yml
- name: 3D geometry gate
run: |
3d check models/*.scad --mesh --printability
3d lint --allHonest status: the package is not on PyPI yet. The supported, working path today is running it from a clone (
./bin/3d). Standardpipx/uv tool/pipinstall is the TARGET once packaging lands (see ROADMAP §29) — thelib/layout is being restructured into an importablethreedpackage with a3dconsole-script entry point.
Current working path — run from a clone:
git clone https://github.com/alex-mextner/3d-cli
cd 3d-cli
./bin/3d help # or symlink bin/3d onto your PATH: ln -s "$PWD/bin/3d" ~/.local/bin/3dTarget path (after packaging, §29):
pipx install 3d-cli # or: uv tool install 3d-cli / pip install 3d-cli
3d helpPython deps resolve per call: 3d prefers a repo .venv, then uv run --with <deps> (no
global installs), then system python3. With uv on PATH nothing needs pre-installing. For
a fast offline path:
uv sync --all-extras # creates .venv from the lockfile3d auto-installs what it can on first run (OpenSCAD libraries clone into libs/; Python
deps resolve per call via uv/.venv), and every command either works or fails with a clear
"install X" message naming the exact per-OS command. Run 3d doctor to inspect what is
present or missing.
External tools — system programs you install yourself (the CLI prints the exact per-OS line
when one is missing; brew/apt/winget):
| Tool | Purpose | Tier |
|---|---|---|
| OpenSCAD | the modeling engine — render, export, section, params, validate | required |
| ImageMagick | silhouette / overlay / score image diffs | required for the match pipeline |
python3 + uv |
runtime for the python subcommands; uv resolves their deps per call (no global installs) |
required (uv recommended) |
| a slicer — OrcaSlicer / Bambu Studio / PrusaSlicer | G-code export & sliceability gate (3d slice) |
optional |
| ffmpeg | video export and animation pipelines | optional |
| Blender | photoreal render (planned) — installed on demand, not bootstrapped | optional |
Python packages — resolved automatically by uv/.venv; you normally never install these by
hand. Only the heavyweight ones worth knowing about:
- core (auto): the mesh stack
trimesh+manifold3d(watertight / manifold / volume) andpyyaml(the3d.yamlproject model). - optional extras:
opencv+pillow(preprocess),pyvista(collision --viz),fastapi/uvicorn(web). The full pinned set lives inpyproject.toml(preprocess/viz/web/devextras) +uv.lock.
A missing optional dependency degrades only the command that needs it — never the whole CLI.
3d doctor prints PASS/MISSING per item with the exact per-OS install line for anything absent.
3d doctor # read-only: report present/missing + the exact install command per OSThe CLI bootstraps OpenSCAD libraries (BOSL2, NopSCADlib) into libs/ on the first 3d
invocation (once, quiet, non-fatal offline) and auto-exports OPENSCADPATH, so
include <BOSL2/std.scad> just resolves with no manual step.
Run 3d <command> --help for full options. Examples below assume examples/cube.scad.
For the full registered command map and per-command docs, see
docs/commands/README.md.
render is the one view/section command. multi/section remain as thin aliases.
| Command | What |
|---|---|
3d render <file.scad> [--view NAME] |
Single CGAL view. Camera computed from the model bounding box + the named direction. Default view: iso. |
3d render <file.scad> --multi [outdir] [--render] |
Render all standard angles (front/back/left/right/top/iso) concurrently. |
3d render <file.scad> --section -o out.png [--plane …] [--color] |
True cross-section: generic STL-cut (any geometry) or --color per-part assembly mode. |
3d render <file.scad> --cam ex,..,cz |
Manual 6-param vector camera (wins over --view). |
3d preview <file.scad> |
Fast throwntogether preview (no CGAL). |
3d multi … / 3d section … |
back-compat aliases for render --multi / render --section. |
--view names: front back left right top bottom iso 3-4 front-left front-right rear-left rear-right. 3-4 is the canonical three-quarter hero angle (azimuth 45°,
elevation 30°). With the trimesh mesh stack present the camera is placed exactly from the
model's bounding-box centroid + diagonal; without it, render orbits along the view
direction with --autocenter --viewall (so view selection always works, mesh stack or not).
3d render examples/cube.scad --view left -o left.png
3d render examples/cube.scad --view 3-4 --ortho
3d render examples/cube.scad --multi previews/ --render
3d render examples/cube.scad --section --plane YZ -o sec.png # generic cut (any geometry)
3d render assembly.scad --section --color --plane YZ -o sec.png # per-part coloured assembly
3d render examples/cube.scad --cam 130,-600,52,130,0,52 --ortho --size 1600x700The match loop wants a 6-param vector camera ex,ey,ez,cx,cy,cz (eye → center) plus
--ortho. The 7-param gimbal form (...,dist) with dist=0 renders an empty frame —
render/silhouette/score reject a non-6 --cam value.
The generic --section exports the model to STL once, then difference(import(stl), halfspace) with the colour outside the cut so the cut face takes the part colour — it
cuts arbitrary geometry with no cut-contract needed. --color is the richer per-part
assembly mode (the assembly must honour -D cut=true and colour each part outside its own
difference). All section cameras are 6-param vector cameras, never a 7-param gimbal.
| Command | What |
|---|---|
3d export <file.scad> |
STL/3MF with manifold/self-intersect validation. Nonzero exit on bad geometry. |
3d validate <file.scad> |
Fast syntax check (no render). |
3d params <file.scad> [--json] |
Extract Customizer-style parameters. |
3d om <file.scad> <selector> |
Query .scad object-model annotations as JSON. |
3d usdz <file.scad|file.stl> |
Export a colored USDZ for Apple AR Quick Look. |
3d export examples/cube.scad -o cube.stl # PASS, exit 0
3d export examples/cube.scad -o cube.3mf -D 'width=80'
3d validate examples/cube.scad
3d params examples/cube.scad --json
3d om annotated.scad '#valve'
3d usdz examples/cube.scad -o cube.usdzexport validates the produced mesh with the trimesh/manifold3d stack (watertight +
manifold) when available — so a non-manifold part exits 1 even when OpenSCAD's modern
backend emits no text warning. Without the mesh stack it degrades to log-grep and tells
you to run 3d mesh for the full check.
check is the one verification command — the master acceptance gate. With no
selection flags it runs ALL applicable gates; selectors run a subset; --skip excludes.
| Command | What |
|---|---|
3d check <file.scad> [parts…] |
All applicable gates: manifold + consistency + printability (+ collision/silhouette when data is supplied). Prints a per-gate breakdown + >>> CHECK: PASS/FAIL. |
3d check … --mesh | --manifold | --consistency | --printability |
run only the named core gate(s). |
3d check … --skip GATE |
exclude a gate (manifold|consistency|printability|collision|silhouette). |
3d check … --collision cfg.json / --ref img |
supply data; the collision/silhouette gate then runs (never narrows the core set). |
3d acceptance <assembly.scad> |
back-compat alias for check (all gates). |
3d mesh <file.stl|3mf|.scad> |
watertight / manifold / self-intersection / volume (trimesh + open3d/manifold3d; falls back to openscad warnings). |
3d printability <file.scad> |
wall / min-feature / overhang / orientation (FDM, PLA/PETG). |
3d collision <config.json> |
generic collision/penetration engine (static / --frame / --viz). |
3d lint [--all | paths...] |
advisory repository lint rules. |
3d check examples/cube.scad # all applicable gates
3d check examples/cube.scad --mesh # only the manifold gate
3d check asm.scad --skip printability
3d check asm.scad --collision verify/collision.json --ref ref.jpg
3d mesh cube.stl
3d collision verify/collision.json --frame # per-frame timeline gate--collision and --ref supply data, not a selector: they make the collision /
silhouette gate applicable but never narrow the core gate set — so a supplied config can
never silently skip a HARD gate (no false PASS). For a genuine subset, use --skip
or name the core gates explicitly.
The collision engine is project-agnostic: a JSON config supplies the placement .scad,
part list, phases, intended-contact whitelist, and EPS/touch thresholds — all paths
resolved relative to the config file's directory.
Match a parametric model to a reference photo by viewpoint and silhouette, for when you have a photo of a real object and want a printable part with the same proportions and pose.
You photograph a bracket, write a rough parametric
bracket.scad, then3d fit-cameralocks the camera to the photo and3d matchnudges the parameters until the rendered silhouette matches the photo — keeping only edits that raise the silhouette IoU and stay manifold.
| Command | What |
|---|---|
3d silhouette <file.scad> |
camera-locked render → binary silhouette mask. |
3d overlay <render.png> <reference.png> |
difference / 50% ghost / canny edge-overlay diagnostics. |
3d score <render.png|file.scad> <reference> |
silhouette AE + IoU (machine-parseable KEY=VALUE lines). |
3d match <assembly.scad> <reference> |
forced-monotonic acceptance loop (render→score→critic→apply→accept/revert + changelog). |
3d fit-camera <model.scad> <reference> |
fit an OpenSCAD camera to a reference photo by maximizing silhouette IoU; saves the viewpoint + a fit render + an overlay. |
3d preprocess <reference.jpg> |
subject mask + proportional depth (SAM2/Depth-Anything V2 if installable, else OpenCV fallback). |
3d compare <model.scad|render.png> <reference.jpg> |
segmented model/reference comparison with IoU + SSIM/DSSIM and artifacts. |
3d silhouette examples/cube.scad -o mask.png --ortho --cam 130,-600,52,130,0,52
3d overlay render.png ref.jpg -o work/
3d score model.scad ref.jpg # renders, then scores
3d score mask_a.png mask_b.png --masks # compare two ready masks
3d match model.scad ref.jpg --rounds 8 --dry-run # exercise the loop without the LLM
3d fit-camera model.scad ref.jpg --out camera.json --draw-axes
3d preprocess ref.jpg -o work/ --force-fallback # OpenCV grabCut + pseudo-depthfit-camera searches the camera pose (azimuth, elevation, distance, pan-x, pan-z
orbiting the look-at) to maximize silhouette IoU between the CGAL render and the
reference, then writes camera.json with the fitted 6-param vector camera_arg, the
per-param values, the IoU, plus <out>_fit.png (full-res fit) and <out>_overlay.png
(render-cyan over reference-red ghost). The optimizer is random-search → coordinate-descent
with a deterministic seed. Crucially it is scale-free: it exports a temporary STL,
reads the model's bounding-box centroid + diagonal, and derives the distance/pan bounds and
refine steps from that diagonal — so a 20 mm cube and a 300 mm assembly both fit without
hardcoded numbers. --center overrides the auto look-at; --draw-axes overlays each
silhouette's PCA principal axis + bounding-box contour so axis/contour alignment is visible.
Different builds never reach IoU = 1 (the shapes differ) — the point is best alignment of
the bounding silhouette so viewpoint, scale and gross proportions match. Use the result:
openscad --render --camera="$(jq -r .camera_arg camera.json)" -o view.png model.scadscore prints AE=, AE_NORM=, IoU=, CLOSENESS=, FRAME=, OVERLAY= — one per
line, machine-parseable. An empty render mask scores IoU=0 (never rewards a blank frame).
match is the forced-monotonic loop: the critic (codex, optional) proposes ONE numeric
param delta; the IoU/AE metric + manifold gate dispose. A change is kept iff the score
strictly improves AND the model stays a clean manifold; else it is reverted. Every step is
logged to <work>/changelog.md, which is fed back to the critic so it never re-proposes a
reverted edit (the anti-FlipFlop defense). Tunable parameters are derived from the
constants file (numeric scalars) — restrict with --params a,b,c, or point at a separate
--constants FILE. --dry-run skips the LLM and synthesises deterministic edits to
smoke-test the machinery.
| Command | What |
|---|---|
3d slice <stl|3mf|file.scad> |
slice to G-code via the installed slicer; --dry-run = sliceability gate (nonzero exit on failure). |
3d slice-check <3mf|stl> |
headless verify a 3MF with no GUI: does it OPEN, how many PLATES, does it SLICE all plates? Exit 0 only if every check passes. |
3d slice part.stl -o part.gcode
3d slice part.scad --dry-run # .scad → STL → slice, gate only
3d slice part.3mf --profile "machine.json,process.json" --printer "Bambu Lab A1"
3d slice-check model.3mf # open + plate count + slice all plates
3d slice-check model.3mf --no-slice --plates 4 # open + assert 4 plates, no slicingSlicer auto-detection preference: OrcaSlicer → Bambu Studio → PrusaSlicer. Found on PATH
and on macOS app bundles (/Applications/OrcaSlicer.app/..., BambuStudio.app,
PrusaSlicer.app); force a specific one with SLICER=/path/to/binary. The three share
heritage but the CLIs diverged, so each gets its own invocation: PrusaSlicer is -g --output out.gcode, OrcaSlicer/Bambu are --slice 0 --outputdir <dir> (the produced G-code
is relocated to your -o path). Those core flags are the verified part of the contract;
--printer is best-effort (no single agreed printer-preset flag exists across the three
— it routes through the profile-load mechanism, so prefer --profile for control).
--dry-run slices as a pass/fail oracle and discards the G-code. --check is a deprecated alias. A .scad input is exported
to STL first via 3d export. If no slicer is installed, 3d slice fails with the exact
per-OS install command (e.g. brew install --cask orcaslicer) — never broken.
| Command | What |
|---|---|
3d init [path] |
scaffold a 3d.yaml project skeleton. |
3d projects list|add|remove |
manage the project registry used by 3d web. |
3d materials list|show |
inspect FDM material names and properties. |
3d printers list|show |
inspect printer names, bed volumes, and nozzle metadata. |
3d metrics list|show |
inspect persisted command metrics JSONL records. |
3d doctor |
report present/missing deps + the exact install command per OS (read-only). |
3d init my-bracket --no-input
3d projects add ./my-bracket
3d materials list
3d printers list
3d doctor # PASS/MISSING table (read-only)OpenSCAD libraries auto-install on the first run, python deps resolve via uv/.venv
per-call, and 3d doctor prints the exact install command for anything still missing.
Repository development commands live in rig.yaml scripts:
dev run test # ruff + pytest + mypy — all must pass
dev run test -- -k registryA fresh clone has no dev pre-commit gate until you wire it in (git never tracks
.git/hooks/). The gate (ruff → pytest → mypy via dev run test) lives as a tracked
source at scripts/hooks/pre-commit; install it once:
scripts/install-dev-hooks.sh # copies the tracked hook into .git/hooks/pre-commitIt is idempotent (safe to re-run) and the installed hook blocks any commit whose repo
test gate fails. The hook prefers dev run test, so rig.yaml scripts remain the
primary development runner surface. If the external agent-tools dev runner is missing
from the PATH that Git hooks receive, or cannot execute this repo's test script, the hook
falls back to reading rig.yaml with Python/PyYAML and running the literal
scripts.test command. The hook still fails closed when neither path can execute the
checks. In a fresh clone/worktree, run uv sync --extra dev first so .venv/bin/python,
PyYAML, ruff, pytest, and mypy are available; the fallback also expects uv to be on the
hook PATH because scripts.test is a uv run ... command.
The hook runs the whole gate (ruff + pytest + mypy over the working tree, not just
staged files), so commits take as long as dev run test does — and, like any
working-tree gate, it judges your working tree, not the exact staged snapshot (an
unstaged fix can green a commit; an unrelated unstaged break can block one).
This repo-dev gate is distinct from the 3d init user-facing pre-commit template
(assets/templates/pre-commit): that one gates a user's
.scad project via 3d check in the project 3d init scaffolds, while this one gates
this repo's own Python source. They never collide — they live in different repos.
If your machine uses a global core.hooksPath composing dispatcher (e.g. agent-tools'
~/.config/git/hooks), that composer already runs the repo-local .git/hooks/pre-commit
as its first stage, so the tracked hook carries no dispatcher line of its own and needs no
extra wiring. If instead your existing hook calls a global-hooks dispatcher inline (the
case when a local core.hooksPath pointing at this repo's hooks dir bypasses the
composer), the installer preserves that dispatcher prefix and splices the dev gate in
below it, so your secret-scan and the dev gate both keep running. An existing, differing
hook is backed up to .git/hooks/pre-commit.bak first.
One sharp edge: if you set a local core.hooksPath, make it absolute (the
installer prints the exact git config core.hooksPath … line). A relative
core.hooksPath = .git/hooks works in the main checkout but is silently bypassed in a
linked worktree — .git is a file there, so the relative path resolves to nothing and
commits skip the gate. The installer warns when it sees a relative value. With no
core.hooksPath at all, git uses the common hooks dir by default and the gate just works
everywhere.
| Command | What |
|---|---|
3d web [--root DIR] [--port N] [--open] |
local FastAPI + SSE + three.js dashboard for your projects and for watching AI agents work live (optional web tier). |
3d web --root ~/models --open # scan that root, open the dashboard
3d web --port 9000 # override the default 8733See docs/commands/web.md for the full feature list. The web tier
(fastapi/uvicorn/markdown) is optional — the core geometry/render/check pipeline does
not need it; a missing dep is a warning in 3d doctor, not a failure.
BOSL2 + NopSCADlib auto-install on the first 3d invocation (cloned into libs/, once,
gated by ~/.config/3d-cli/.bootstrapped, non-fatal if offline), and OPENSCADPATH is
auto-exported by the CLI — so include <BOSL2/std.scad> just resolves, no manual step.
3d libs list # show installed libraries
3d libs path # print OPENSCADPATH (for your own non-3d shells)
# re-install: rm ~/.config/3d-cli/.bootstrapped && 3d help3d keeps all its state under one config dir and one data dir (ROADMAP §23):
~/.config/3d-cli/—web.json, the first-run bootstrap marker (.bootstrapped), the projects registry, and registry overrides. (Honors$XDG_CONFIG_HOME.)~/.local/share/3d-cli/— generated state, including the longitudinal metrics store. (Honors$XDG_DATA_HOME.)
bin/3d thin Python dispatcher (resolves REPO_ROOT through the symlink)
lib/cli/dispatch.py routing + registry build + structured-error rendering
lib/cli/registry.py the command registry (Command + discover()) — the plugin extension point
lib/cli/env.py tool discovery, OS/install table, OPENSCADPATH export, first-run bootstrap
lib/cli/paths.py the single source of truth for config/data dirs (~/.config/3d-cli, …)
lib/cli/pyrun.py run a lib/*.py tool with its deps (.venv -> uv -> system python3)
lib/cli/imaging.py ImageMagick orchestration + the pure score (IoU/AE) math
lib/project.py the 3d.yaml project model + loader (the project spine, §5/§15)
lib/errors.py structured CLI error types (WHAT/WHY/remediation/accepted/install)
lib/commands/*.py one self-registering module per subcommand (drop a file = add a command)
lib/*.py heavy python tools (render/mesh/collision/printability/preprocess/match/fit)
lib/web/ the web dashboard app (FastAPI + SSE + three.js SPA)
tests/ ruff + pytest unit tests + the CLI smoke harness + mypy gate (`dev run test`)
docs/commands/ per-command documentation fragments
docs/critic-prompts.md the vision-critic prompt patterns
libs/ OpenSCAD libraries cloned on demand (gitignored)
examples/cube.scad trivial test part
pyproject.toml python deps (uv project: core + optional extras preprocess/viz/web/dev)
uv.lock locked dependency set (uv sync)
Adding a command is a one-file change — see lib/cli/registry.py (and AGENTS.md) for the
command-authoring contract. bin/3d and the shared files need no edits.
3d is a tool for an agent, not a standalone CLI you grade on its own. The right unit
of comparison is the combo Claude Code (or any coding agent) + 3d — the agent supplies
the intelligence (it generates the model, reads the score, decides the next edit) and 3d
supplies the deterministic 3D operations (render, silhouette-IoU score, manifold/printability
gates, slice). Judging 3d alone — "the CLI has no neural mesh generator, so text→3D is a
dash" — measures the wrong thing. The agent is the generator; 3d is its hands.
So the comparison is agent + 3d against the wave of AI 3D-generation services —
hosted text-to-3D and image-to-3D platforms like Meshy,
Tripo, Rodin / Hyper3D,
Kaedim, and Sloyd. They take a
prompt or a photo and return a textured mesh in seconds, in a browser, billed by credits, and
mostly stop there. The combo takes a prompt or a photo and drives it all the way to a
printable physical part, locally and scriptably.
The pitch in one line: an agent + 3d can take text or an image → a real, editable,
printable model end-to-end — a parametric / CAD route to 3D (not neural mesh-gen), driven
by an agent loop the SaaS don't have. From a text prompt the agent writes a parametric
OpenSCAD model (3d ai assembles the context bundle it reasons over);
from a reference photo the agent + 3d fit-camera/match/score iterate that model toward
the photo's silhouette under a forced-monotonic IoU + manifold
gate. The output is a source model you can diff in git, gate for FDM, and slice — no upload,
no credits.
| Capability | agent + 3d | Meshy | Tripo | Rodin (Hyper3D) | Kaedim | Sloyd |
|---|---|---|---|---|---|---|
| Text → 3D | ✓ (agent writes a parametric OpenSCAD model; 3d ai bundles the context) |
✓ | ✓ | ✓ | — | ✓ |
| Image → 3D | ~→✓ (agent + fit-camera/match/score silhouette-IoU loop toward the photo) |
✓ | ✓ | ✓ | ✓ | ✓ |
| Route to 3D | parametric / CAD (editable, mechanical) | neural mesh | neural mesh | neural mesh | neural mesh | parametric templates |
| Parametric / source-editable model | ✓ (OpenSCAD, git-diffable) | — | — | — | — | ~ (hosted slider templates) |
| Mesh repair & manifold check | ✓ | ~ (analyze/repair API) | ~ (remesh/clean topo) | ~ (clean surfaces) | ~ (human-in-loop) | — |
| FDM printability gates (wall / overhang / orientation) | ✓ | ~ (printability analysis only) | — | — | — | — |
| Slice → G-code | ✓ (delegates to installed slicer) | — (exports 3MF, no slice) | — | — | — | — |
| Print prep / job planning | ~ (dry-run job plan; monitoring planned) | — | — | — | — | — |
| AR / USDZ export | ✓ (3d usdz) |
✓ (USDZ export) | ~ | — | — | — |
| Agent-native loop (generate → score → decide → repeat) | ✓ | — | — | — | — | — |
| Local / no hosted service / no credits | ✓ | — | — | — | — | — |
| Scriptable CLI / no web app | ✓ | — (REST API) | — (REST API) | — (REST API) | — (REST API) | — (REST API) |
✓ = yes, ~ = partial, — = no.
Where the SaaS genuinely win — said plainly: for organic, concept, character and one-shot geometry — "a chunky robot", a creature, a single photo of an irregular sculpt — Meshy, Tripo, Rodin, Kaedim and Sloyd return a finished, textured, riggable mesh in seconds with one click and zero local setup. The parametric route is not equivalent there: an agent writing OpenSCAD is strong on mechanical / matchable shapes (brackets, mounts, enclosures, parts with measurable proportions) and weak on free-form organic surfaces, and it costs reasoning rounds the SaaS skip. For concept art, game/film assets and arbitrary photo→organic-mesh, the neural services are simply the right tool and the combo is not pretending to beat them.
Where the agent + 3d is honestly stronger: the printable-part endgame and the
agent loop. The SaaS hand you a mesh and stop — usually a hosted, credit-gated, often
non-printable one. Meshy is the closest: it has Analyze Printability / Repair Printability
APIs, a multi-color 3MF export, and even links out to Bambu Studio / OrcaSlicer — but it still
stops at a 3MF; it does not run FDM wall/overhang/orientation gates or the slice itself
(mark ~). The others stop at the mesh entirely. The combo runs the manifold + FDM
printability gates, performs (delegates) the slice, emits a job plan, exports USDZ for AR, and
does it all as a local, pipeable, exit-code-gated CLI an agent drives in a loop — with no
upload, no credit meter, and a parametric source model you can diff in git. The two ends are
complementary: generate organic meshes with a SaaS when you need them, but for a
text-or-photo → printable mechanical part, an agent + 3d does the whole pipeline itself.
Part of the HyperIDE.ai agent toolchain:
- tg-cli — simple Telegram CLI to send messages, photos & files, and a two-way agent bridge (reports, Q→buttons, voice/rich)
- review-cli — agentic, priority-ordered failover multi-model code-review board (brainstorm/quorum, spec-web, dashboard)
- rig-cli — umbrella dev-env driver: sets up a repo from config — skills, hooks, CI, dep-bootstrap; reconciles drift
- agent-tools — the shared catalog
rigapplies: portable agent skills, agent-hooks, the global git-hook dispatcher, CI gates, and MCP servers - draw-cli — text-to-image via Hugging Face
- hyperide.ai — Figma replacement inside VS Code. Edit React components directly through AST/LSP without AI hallucinations, token waste, or context-window limits. Works for indie vibe-coding and for enterprise teams with split design/dev roles.
Each CLI registers a skill into your agent harnesses (<tool> install-skill) so agents know it exists — see Install.