A Claude Code skill that turns a plain-English part description into a verified, printable STL — modelled in Python against a real CAD kernel, with an interactive slider page for tuning dimensions before export.
you: "make me an enclosure for a Pi Zero 2 W, rounded corners, USB-C cutout"
│
▼
Claude writes a parametric model (fillet → shell → chamfer → bosses → port)
│
▼
opens the live bench — sliders, live constraint checks, exact kernel views
│
▼
you drag the sliders until the checks go green, click "Hand these to Claude"
│
▼
Claude reads the values back and exports a gated, watertight STL
Most "AI CAD" demos stop at "here's a box with a hole in it." This skill is built for the part after that one — the enclosure with rounded corners and a shelled cavity, the round-to-rectangular duct, the bracket where a wall thickness of 1.8 mm instead of 2.2 mm is the difference between a part that prints and one that doesn't.
It does that by modelling against build123d, a real B-rep kernel (OCCT — the same lineage as FreeCAD and OpenCascade), not a mesh approximation. Fillets are fillets. Shells are shells. Every export is gated by a script that checks the mesh is watertight, correctly wound, and within 0.5% of the kernel's own exact volume — and it deletes the file rather than hand you a part that's wrong.
-
Python 3.10+
-
A venv with
build123dinstalled (pulls the OCCT kernel — the one real cost, ~500 MB download):python -m venv cad-bench-venv cad-bench-venv/Scripts/python -m pip install build123d trimesh numpy # Windows cad-bench-venv/bin/python -m pip install build123d trimesh numpy # macOS/Linux -
Node.js on
PATH, forscripts/check_bench.py(validates a bench page's recipes headlessly before you publish them). -
Only if a part uses a live 3D preview (rare — see below): run
npm installonce in this repo's root to pullthree/three-bvh-csgforscripts/check_mesh_parity.py. Everything else needs nothing frompackage.json.
See HOWTO.md for the end-user side of the workflow above —
what you actually see and do at each step, walked through with one of the
skill's own worked examples (models/boss_plate.py) and its real output.
Sometimes the part already exists as a loose, source-less STL and the job is to turn that into a parametric model, not design something new — different failure mode, different method.
First, is it even the right kind of shape? inspect_stl.py screens
for this automatically, before anything else:
python scripts/inspect_stl.py part.stl
shape screen : FEATURE-BASED (this mesh decomposes into primitives —
flat faces, fillets, circular/rectangular
cross-sections. Reverse-engineering it
is worth attempting.)
A sculpted/organic mesh — a character model, anything with continuously varying freeform curvature — fails this screen and says so up front, instead of quietly producing plausible-looking-but-meaningless numbers off a mesh that was never built from primitives. If it passes:
python scripts/inspect_stl.py part.stl # every distinct Z the mesh
# actually has vertices at —
# never guess sample heights
python scripts/inspect_stl.py part.stl --z 0.2 # detail one height, split
# into radius bands so two
# loops sharing one Z don't
# get read as one shape
If the STL is several parts in one file (a body, a bracket, a multi-piece assembly exported as one mesh), split it first:
python scripts/segment_stl.py assembly.stl
4 disjoint components (by connectivity, not by name):
#0 volume=10445.09mm^3 LARGEST
#1 volume= 419.29mm^3 GROUND-CONTACT, PERIPHERAL, MIRROR-PAIR with #2
#2 volume= 419.29mm^3 GROUND-CONTACT, PERIPHERAL, MIRROR-PAIR with #1
#3 volume= 90.00mm^3 THIN-SHELL
It reports structural facts only — volume rank, verified mirror pairs
(checked geometrically, not just "similar size"), which piece touches the
ground, which is a thin shell — and deliberately never guesses a real name
like "bracket" or "leg." A part's own print orientation has nothing to do
with which way is "up" or "front" for the object itself, so a
position-based name guess is a coin flip dressed up as an answer. Map the
tags to real names yourself before deleting anything — --export-parts
writes each component to its own STL for a quick look.
If the new part has to mate flush against one of these pieces, fit
that surface specifically, not the whole neighbouring part's shape:
classify its faces by whether their outward normal points toward the
neighbour's centroid, then tighten that classification until a primitive
fit (usually a sphere or cylinder) converges — full method, plus the
solid-vs-shell decision that determines whether the fixed curvature
becomes a boolean construction constraint or a validation check, in
references/stl-reverse-engineering.md §6.
Either way: gate the reconstruction against the original mesh's own volume, not just the rebuild's internal tessellation consistency — and if a feature mates to something real outside the file (a motor shaft, a bearing, a fastener), check that part's own spec before trusting a literal mesh reading; a precisely-measured modelling mistake is still a mistake.
Dimensions that come from that kind of external requirement, rather than
free design choice, can be marked locked:true on a bench slider — it
renders disabled and dimmed with a 🔒 and the source noted in a tooltip,
so nobody drags a datasheet dimension off-spec while tuning the sliders
next to it. See references/bench-artifact.md.
Most parts get a flat 2D sketch on the bench page (a top view, a section) —
cheap, exact for anything prismatic, and the default. A genuinely curved
part (a loft, a doubly-curved surface) can instead get a real 3D preview,
rendered live as sliders move with three.js — but only when it's actually
worth the cost. references/live-mesh-preview.md has the 3-question
checklist (most parts fail the first question and stay on the plain
sketch); if a part does get one, scripts/check_mesh_parity.py is a
mandatory gate — it verifies the live-rendered mesh actually matches the
real Python/OCCT model (volume + bounding box, at every slider extreme),
because a live preview that quietly stops matching the real geometry is
worse than no live preview at all.
Join parts into a machine without typing a single world coordinate. Each part names its connection points (computed from its own dimensions, so a resize carries through); an assembly file joins them:
asm = A.Assembly("hinge_box", base="base", base_joint="free", path=__file__)
asm.add("base", part="box")
asm.add("lid", part="lid")
asm.join("base.hinge", "lid.hinge", "revolute", flip=True, limits=(0, 110),
motor=A.Motor(torque=0.05, speed=2))
asm.use_rules("lid fit", lid="lid", base="base")
asm.run()In a project the file lives in cad/assemblies/; Claude runs it with
python scripts/cad.py assemble hinge_box, which prints one line:
hinge_box: 2 parts, 1 joint, 2/2 rules ok -> cad\exports\hinge_box
- Rules, confirmed by you. Before resizing, Claude writes down what a
part is for and the rules that follow ("bore = motor shaft + 0.1"), and
uses them only after you say yes. Rules that set a size carry it through
(change the box width, the lid width follows); rules that check a size warn
when broken. Saved as plain text, per project (
cad/rules/) or worldwide for every project (library/rules/in the skill, git-ignored), so the next similar part comes with suggestions. - Checks that warn, never block: under-defined parts (naming what can still move), overlapping parts, connection points stuck at a typed-in number while their part resizes, broken rules.
- Outputs in
cad/exports/<name>/: a placement report, a picture with connector axis markers, a STEP of the whole assembly, and a MuJoCo model (bodies, joints, motors, masses and inertias from the CAD geometry) that is checked to load and step. - Reference:
references/assembly.md. Design:docs/superpowers/specs/2026-10-18-cad-bench-assemblies-design.md. Needspip install mujocoin the venv.
Option A — one file. Download cad-bench.skill from
this repo and drop it into a Claude Code chat. Claude will offer a Save
skill button on the file card; click it and you're done.
Option B — clone it.
git clone https://github.com/softnetics/CAD-Bench-Claude-Skill.git ~/.claude/skills/cad-benchEither way, Claude picks it up automatically the next time a request looks like CAD/STL/3D-printing work — brackets, mounts, enclosures, adapters, standoffs, ducts, jigs. You don't need to name the skill for it to trigger.
cad-bench/
├── SKILL.md the workflow Claude follows
├── HOWTO.md the end-user walkthrough, with real screenshots
├── docs/superpowers/specs/ design documents for planned work
├── package.json three.js/three-bvh-csg, dev-only tooling for
│ check_mesh_parity.py -- npm install, never a
│ runtime dependency of anything published
├── scripts/
│ ├── brep.py build123d wrapper: selectors, safe fillet/chamfer,
│ │ the Windows-font-crash workaround, export + gate
│ ├── verify.py the watertight/volume/bbox gate, shared by every export
│ ├── check_bench.py headless validator for a CAD Bench slider page
│ ├── check_mesh_parity.py verifies a live-preview mesh() matches the
│ │ real Python/OCCT model -- volume + bbox, at
│ │ every slider extreme
│ ├── cad.py the plain-word CLI: init, checkpoint, try, revert,
│ │ keep, back/forward, auto, history, serve, sense,
│ │ drawing, assemble
│ ├── assembly.py connectors, joins, placement and the warn-only checks
│ ├── rules.py confirmed rules that set and check sizes across parts
│ ├── asm_export.py runs an assembly: report, picture, STEP, MuJoCo model
│ ├── asm_render.py the assembly picture with connector markers
│ ├── mjcf.py the MuJoCo model, checked to load and step
│ ├── build_skill_bundle.py rebuilds cad-bench.skill (the one-file install)
│ ├── cadproj.py a project's cad/ folder: layout and settings
│ ├── cadvc.py git-backed checkpoints and experiments, cad/ only
│ ├── bench_server.py the live bench: warm kernel + JSON API on 127.0.0.1
│ ├── partmodel.py loads a part file, resolves its values, builds it
│ ├── views.py exact hidden-line views from the kernel, as SVG
│ ├── sense.py dimension sensing: redundant / no-effect / hard-coded
│ ├── new_part.py scaffolds a new model + matching bench recipe
│ ├── inspect_stl.py first look at an existing STL: shape screen
│ │ (feature-based vs. organic/sculpted), every
│ │ distinct Z, radius bands
│ └── segment_stl.py splits a multi-body STL into disjoint parts,
│ reports structural facts (never guesses names)
├── references/
│ ├── brep.md operation-by-operation lookup: fillet, chamfer,
│ │ shell, loft, selectors, the failure modes of each
│ ├── bench-artifact.md the slider-recipe format, locked sliders, the
│ │ Claude hand-off wiring
│ ├── verification.md why each gate exists, how to prove a selection is right
│ ├── stl-reverse-engineering.md method for turning a loose STL into a model,
│ │ including fitting a MATING surface (§6) and the
│ │ solid-vs-shell construction decision
│ ├── live-mesh-preview.md when a live 3D preview is worth building, the
│ │ mesh(p,THREE,CSG) contract, how to verify it
│ └── assembly.md connectors, joins, rules, checks, STEP / MuJoCo
├── models/ worked, runnable examples
│ ├── boss_plate.py — plain prismatic: plate, bosses, bores
│ ├── enclosure.py — fillet → shell → chamfer, internal bosses, a port
│ ├── duct.py — loft: round-to-rectangular transition
│ └── _parity_demo.py/.recipe.js — check_mesh_parity.py's own test
│ fixture (a box with a bore), not a real part
├── assets/
│ ├── live-bench.html the live local bench page (served by bench_server.py)
│ └── bench-template.html the shareable claude.ai CAD Bench page, ready to publish
└── tests/ pytest + node tests (dev only)
Fillets, chamfers, shells and lofts are exact geometry, not a faceted guess — that's the entire point of building on a real kernel instead of mesh CSG. What it can't do yet: thread modelling, sheet-metal unfolding, FEA, sculpted/organic shapes (a character model, anything with continuously varying freeform curvature — there's no feature tree to recover), or file formats other than STL/STEP. If you need those, this skill will tell you so rather than quietly hand you something wrong.
Three steps, in order. Steps 1 and 2 are available now; step 3 is planned.
The designs are in
docs/superpowers/specs/2026-10-18-cad-bench-foundation-design.md
and
docs/superpowers/specs/2026-10-18-cad-bench-assemblies-design.md.
-
Foundation — available
- Your parts live with your project, not in the skill. Invoking the
skill in any folder creates a
cad/folder there for one part or one machine:<your project>/cad/ parts/ <name>.py, <name>.params.json, <name>.recipe.js assemblies/ <name>.py: parts joined into a machine (step 2) rules/ <kind>.rules: confirmed rules that carry sizes between parts exports/ STL / STEP / drawings, rebuilt on demand, not versionedmodels/in this repo stays as worked examples only. - Version control in plain words, git underneath. Inside an existing repo (firmware, say) CAD checkpoints are ordinary commits next to the rest, and a CAD experiment never touches non-CAD files or your branch. Every command prints one line.
- Live local bench: a local Python server keeps the kernel warm and redraws exact top / front / side / iso views, hidden edges dashed, about 50 ms after each slider move. Typed values are used exactly (no clamping or step snapping). The bench flags sliders that duplicate each other or do nothing, and features pinned by a hard-coded number. A History tab shows each part's branch graph with a drawing per checkpoint.
The commands (Claude runs them for you;
--project DIRto point at a project from elsewhere):python scripts/cad.py init create or reopen cad/ here python scripts/cad.py checkpoint [name] snapshot now (manual by default) python scripts/cad.py try <idea> start an experiment python scripts/cad.py revert <reason> abandon it (kept on the graph) python scripts/cad.py keep the experiment becomes the main version python scripts/cad.py back | forward undo / redo across checkpoints python scripts/cad.py auto on|off checkpoint at every model change: each bench hand-off and each model edit python scripts/cad.py history [part] one-line summary (--json: full list) python scripts/cad.py serve the live bench, on 127.0.0.1 python scripts/cad.py sense <part> redundant / no-effect / hard-coded flags python scripts/cad.py drawing <part> exact views to cad/exports/<part>.svg python scripts/cad.py assemble <name> run cad/assemblies/<name>.py: report, picture, STEP, MuJoCo model python scripts/new_part.py --project DIR --name <part> --params "a=1,b=2" - Your parts live with your project, not in the skill. Invoking the
skill in any folder creates a
-
Assemblies — available (see Assemblies above)
- Parts join at named connection points computed from their own dimensions, so a resize carries through. Under-defined placement, overlaps, hard-coded connectors and broken rules warn.
- Rules you confirm carry sizes between parts (the lid width follows the
box width), per project in
cad/rules/or worldwide. - Exports a placement report, a picture with connector markers, a STEP of the whole assembly, and a MuJoCo model that is checked to load and step.
python scripts/cad.py assemble <name> run cad/assemblies/<name>.py python scripts/rules.py list <assembly.py> the saved rule kinds, one line -
Simulation — planned: MuJoCo, local. Real part specs from datasheets, an animation of the assembly moving, and a verdict on whether the chosen parts can do the job, with the minimum specs that would.
MIT — see LICENSE.