- name
- cadcode
- description
- Use when the user wants to create, edit, or print a parametric 3D model from a natural-language description — "phone stand", "wall mount", "honeycomb tray", "GoPro adapter", "vase" — or to tweak, re-render, or fix an existing CadQuery `.py` part for hobbyist 3D printing.
# CADCode — hobbyist 3D CAD via CadQuery
## Purpose
Turn natural-language descriptions of 3D parts into printable, inspectable
3D models. The source of truth is **CadQuery Python** (B-rep on OpenCASCADE
— same kernel as SolidWorks / FreeCAD). Every generated `.py` file is a
small, editable parametric program. The user owns the file; tweak
parameters, re-render, re-print.
Optimised for **hobbyist 3D printing**, not commercial CAD. The deliverable
is an archival STEP plus a watertight STL that the user's slicer can ingest
and that the viewer renders as the preview.
## Treat the design as a project
**A design is a small software project, not a single script.** Trivial parts
(a cube with a hole, a plate, a single hex tray) fit in one `.py` file.
Anything bigger — multi-part assemblies, designs with many features, any
part with more than ~120 lines of code — gets a project directory.
A project looks like:
```
my_design/
├── spec.md design intent (English, human-readable)
├── params.py ALL dimensions + manufacturing constants
├── validation.py runtime constraints (printability, fit, sanity)
├── main.py entrypoint — defines `gen_step()` (preferred) or
│ assigns `result` (legacy single-file form)
├── parts/ one file per physical part
│ ├── __init__.py
│ ├── base.py
│ └── cover.py
├── features/ reusable feature functions (cutouts, vents, …)
│ └── __init__.py
└── assemblies/ positioning + union of parts
├── __init__.py
└── product.py
```
`scripts/cad <project_dir>/` calls cadpy's artifact pipeline, which reads
``main.py`` with the project directory on ``sys.path`` (so ``from params
import Params`` and ``from parts.base import …`` work), then calls
``gen_step()``. **Use `Skill(skill='cadcode')` and `Read`
`templates/project_skeleton/` when you need the canonical layout** — copy
it to the user's workspace, edit, run.
Rules of the project format:
- **All dimensions live in `params.py`.** Geometry code never hardcodes
numbers. The user (or you next turn) edits a value once; nothing else
changes. Bad: `.box(120, 80, 35).shell(-3)`. Good: `.box(p.width, p.depth, p.height).shell(-p.wall)`.
- **`main.py` defines `gen_step()`** at module scope. It returns one of:
a ``cq.Workplane`` / ``cq.Shape`` (single solid), a ``cq.Assembly``
(multi-part hierarchy with names + colors + locations), or an
envelope ``dict`` like ``{"shape": <…>, "stl": True, "mesh_tolerance":
0.03}`` when you want to tune mesh fidelity or request extra output
formats (see the [Artifact-control envelope](#artifact-control-envelope)
section). The legacy ``result = <shape>`` form is still accepted for
trivial single-file scripts — the runner treats it as if
``gen_step()`` returned ``result``.
- **Every mating interface shares one dimension.** Both halves of a mate
derive from a **single** base value in `params.py`, with the FDM clearance
applied in exactly **one** place — never size the two halves independently.
Full rule + the helpers that enforce it: step 6 of "Running the loop".
- **One file per physical part** under `parts/`. Each part knows nothing
about its siblings; it builds in its own local frame.
- **Each feature is its own function.** `add_left_usb_c_cutout(part, p)`,
not nested inline. Compose them in a pipeline so each edit has a clear
target.
- **Assembly = positioning + union, never geometry.** Build parts in
`parts/`, place them in `assemblies/`.
- **`validation.py` runs at startup** with `assert` checks on Params.
Bad dimensions fail loudly before paying a render cycle.
### Artifact-control envelope
For most parts, return the shape directly from ``gen_step()`` and let the
defaults handle the export. When you need control:
```python
def gen_step():
body = build_my_part(p)
return {
"shape": body, # required: cq.Workplane | cq.Shape
"mesh_tolerance": 0.03, # mm, default 0.05
"mesh_angular_tolerance": 2.0, # deg, default 3.0
}
```
The envelope keys (``shape`` | ``instances`` | ``children`` for content;
``mesh_tolerance`` / ``mesh_angular_tolerance`` for output) are all that the
cadpy pipeline accepts — unknown keys raise. The ``.stl`` is always written;
no envelope flag is needed.
See `references/project-structure.md` for the long version.
## The loop
The cadcode skill turns you into a self-correcting CAD designer. **You close
the feedback loop yourself** — do not hand a possibly-broken model to the
user for verification.
```
understand task → inspect repo → make plan → edit .py → run scripts/cad
↑ ↓
└────────── fix ←─── read failure / render ←────────────┘
```
What "fix" means in practice:
- ``ok=false``: read the traceback, change the smallest responsible line, re-run.
- ``is_solid=false`` or volume far off expected: load `references/repair-loop.md`, classify, fix, re-run.
- ``warnings`` non-empty: deterministic geometry defects — any non-`info` warning is **blocking** (full taxonomy: Non-negotiables). For ``disconnected_bodies``, anchor the floating feature to the body (`references/patterns/anchor-to-body.md`) or, for a mechanism, solve the joint so the links actually meet (`references/kinematic-placement.md`). For ``collision``, reposition or resize so mating faces meet at the right clearance. Fix and re-run.
- Preview STL looks wrong (proportions off, hole misplaced, parts misaligned, a member poking through a plate): edit the `.py` and re-run. **Always inspect every part** — geometry can be valid (`is_solid=true`, no warnings) but still wrong.
You have everything you need to close the loop on your own:
- The user's prompt and any attached reference image (inspect).
- The current workspace files including prior `.py` versions (inspect).
- `scripts/cad` for compile + solid check + STEP/STL/metadata export (run).
- `scripts/check` for a quick validation when you only need a sanity check (run).
- This SKILL.md + the references for domain knowledge (plan).
**Iterate until the model is correct.** Soft cap of 4 iterations before you
ask the user a clarifying question — past that, you're probably guessing
about user intent rather than fixing a geometry bug. Closing the loop is
what makes you feel like an engineer instead of an autocomplete.
## Plan-phase design discipline
When Vibe runs you in its **Plan phase** (enforced by the phase system
prompt: no writing `.py`, no running the generator), you write no geometry —
you produce the plan the user approves before the build. That plan
is an **engineering spec**, not a sales pitch. Hold it to five rules:
1. **Exact measurements.** Every dimension, quantity, and metric is a precise
number with a unit. Never "about", "roughly", or "approximately" — if you
don't know a value, derive it (below) or ask the user.
2. **Component-level breakdown.** List each distinct part with its outer
dimensions, material, and purpose, and state exactly how parts connect —
joint/feature type, mating dimensions, clearance/tolerance, attachment
points, alignment. A single-part object still lists its one part.
3. **Physical correctness.** Account for gravity, balance, load-bearing, center
of mass, structural stability, and FDM layer-line orientation. State your
assumptions and confirm the design behaves under real-world conditions. Show
only the checks that apply — for a part with no load case (decorative, a
loose-fit cover), say so in a clause rather than inventing a load.
4. **Show the math.** For each derived or load-bearing number, show the formula
and the values used so a reader can check it: `name = formula = value unit`.
5. **Verification checklist.** End the plan with the explicit list the build will
clear one item at a time, each paired with how it is checked — a SANITY check
(a number / `validate()` assert / `functional` warning) and a VISUAL check
(which render, or a **cross-section** for an interior interface). Two groups:
**(A) per component** — every feature sits on solid material (no tooth / peg /
boss / rib over a void, hole, or notch) and depths actually reach; **(B) per
interface** — each mating pair (peg/socket, clutch, gear, tab/slot, lip/groove)
actually meets and can transmit its force (form-fitting, not a smooth pocket
over round pegs), with the right clearance and a reachable assembly path.
**Scale to the request.** A trivial edit ("make the wall 2 mm thicker", "move
the holes 5 mm apart") needs only the exact before→after values and any physical
consequence — one to three lines. A new part or any multi-part / load-bearing
design gets the full treatment.
**Aesthetic discipline.** A Vibe part should look like a premium consumer
product (Apple-anchored, but a broad high-end range — see
`references/industrial-design.md`), not a blocky CAD default. For any
user-facing part, give each part in the plan a one-line **`Form`** clause naming
its radius language (the unified corner/edge radius and where it's applied) and
its primary surface treatment (e.g. "4 mm unified vertical radius, 1 mm top
chamfer, calm front face, fasteners hidden on the back"). This is **secondary to
function and printability** — never trade away strength, wall thickness,
tolerance, clearance, or print orientation for looks; if an aesthetic choice
would compromise the part, say so and pick function. Trivial edits skip the
`Form` clause.
**Assembly & functional discipline.** A part that is a valid solid but can't be
assembled or used is a failure — whether it can't be *installed* (a MagSafe stand
whose puck *pocket* is perfect but whose **captive cable + connector collar**
can't pass the opening) or can't *function* (a dial that sits clear of its drive
pegs, so turning it does nothing). For any design with a real component or a
moving / mating mechanism, the plan must include:
- **Assembly & setup sequence** — the ordered steps to assemble and set up the
finished print (install each component, route its cable/connector, place the
device), and the clearance each step needs. Model the WHOLE component,
including captive cables, connector collars, and plugs — **web-search the
component's dimensions** (body, cable Ø, connector-collar Ø×len) and state them
as assumptions the user can correct.
- **Functional requirements** — what it must do (hold / charge / route / rest /
remove / drive / mesh), each tied to a dimension and carried into the
Verification checklist (rule 5).
Read `references/component-integration.md` for the discipline. Encode the
constraints two ways (see the build loop): hard `validate()` asserts for
impossible fits, and `functional` warnings for assembly-feasibility. Trivial
edits skip this.
### Where the numbers come from — source them, don't guess
Every number you put in the model comes from exactly one of three places. Know
which, and never invent one.
1. **Real-world dimensions of a named product** (a phone, a motor, a doorbell, a
bearing you don't recognize, a mount standard, a connector collar) — **web-search
the manufacturer/catalog spec**, then **state it as an assumption the user can
correct** and round for printing. Don't carry these from memory; they drift by
model/region and a confident guess is the classic failure. If a search can't
pin a specific device, say so and ask the user for the dimension.
2. **Hardware the cadlib helpers already cover** (screws, nuts, bearings, magnets,
heat-set inserts, common cable jackets) — the helper owns the dimensions; pass
it a named size (`bearing="608"`, `screw_size="M3"`) and let
`cadlib/tables.py` supply the geometry. Don't transcribe those numbers into
your model. For an open-ended fit the helper takes a raw dimension
(`cable_diameter=…`) — that's where a web-searched value goes.
3. **Generic FDM best-practice** (tolerances, wall thickness, boss sizing, fits) —
use the rules below, which are formulas, not lookups:
| Best-practice rule | Load for the why |
|---|---|
| `wall = N × nozzle` (0.4 mm nozzle → 0.8 / 1.2 / 1.6 / 2.0 / 2.8 mm; 2.0 mm enclosure, 2.8 mm + ribs load-bearing) | `references/patterns/wall-thickness-rules.md` |
| Clearance hole `= nominal + 0.3–0.4 mm`; self-tap `= major − 0.3 mm`; cbore `= cap-head Ø + 0.5 mm` | `references/hobbyist-defaults.md` |
| Boss OD `= 2·clearance + 2·wall`; screw engagement `= 2·screw-Ø` | `references/patterns/screw-boss.md` |
| FDM slop: press-fit `+0.2`, hand-assembly `+0.4`, snap/interference `0.3–0.5 mm` | `references/hobbyist-defaults.md` |
| Rib vs wall stiffness (one rib ≈ 5–10× cheaper than doubling walls; `h³`) | `references/patterns/rib-stiffener.md` |
Material properties (e.g. PETG ≈ 0.6× PLA stiffness and creeps under sustained
load) are starting assumptions — keep the engineering formula but label the
constant as one to verify/web-search for the user's actual material.
### Physics checklist — what to show
- **Tip-over / balance:** center of mass vs support footprint. Compute the
horizontal CoM offset and compare to the base edge:
`x_CoM < base_overhang` ⇒ stable; report the margin.
- **Load path / bearing stress:** where weight enters, what carries it to the
ground or mount, and the fastener/wall that takes the reaction.
- **Stiffness / deflection:** wall thickness and ribs for the stated load;
remember doubling thickness is 8× stiffer (`h³`), a rib is usually cheaper.
- **FDM layer orientation:** a load pulling *across* the layer lines is far
weaker (e.g. boss pull-out drops ~50%). State the print orientation wherever
strength matters.
- **Build volume:** confirm the part fits the printer (a Bambu bed is ≈ 256 mm
cube — verify for the user's printer model; cadpy's sanity bound is 200 × 200 mm).
- **Assumptions to state:** material (and its density/stiffness), applied load,
orientation in use, support condition (free-standing, wall-mounted, clamped).
Label every assumed input (a phone's mass, a bag's weight) as an assumption
the user can correct — never present a guess as a measured fact, and never
fabricate a load just to fill the section. Skip checks that don't apply and
say why.
End the Physics check with a one-line verdict: stable / load-safe / printable
under the stated assumptions, or the condition that would make it fail.
### Default to ONE premium part; split only when it must come apart
**Most consumer objects are a single, sculpted premium part** — a phone stand, a
knob, a bracket, a wall mount, a vase, a MagSafe stand. Default to **one
well-proportioned solid body** with any component (charger puck, cable, bearing,
phone) integrated into it as a recess / pocket / channel. A premium object reads
as one continuous form, **not a flat plate bolted to a flat base**.
Reach for multiple printed parts **only** when the object physically must come
apart: a lid or removable cover, a part with a moving joint (hinge, linkage), a
shape that can't print in one orientation, or anything larger than the bed. *A
phone stand is one part; a box with a lid is two.* When unsure, choose one part —
a unified body looks better and has nothing to misfit. The multi-part fit /
collision / shared-dimension discipline below applies **only** to designs that
are genuinely several printed parts; never split a single object to satisfy it.
### Spec format — the shape to fill in
> **What I'll make** — one line.
> **Parts** — usually **one**. Give it outer dims, material, purpose. *Only if
> the design is genuinely multi-part*, add an entry per printed part and state
> exactly how each connects (joint type, the shared mating dimension, the
> clearance per side).
> **Form** — the premium read in one line: the unified corner/edge radius and
> the primary surface treatment (the aesthetic discipline above +
> `references/industrial-design.md`). A solid, resolved body — never a thin slab.
> **Measurements & math** — each derived/load-bearing number as
> `name = formula = value unit` (e.g. `wall = 7 perim · 0.4 mm = 2.8 mm`).
> **Physics check** — only the checks that apply (tip-over CoM vs base, load
> path, stiffness, layer orientation, build volume), then a one-line **Verdict**.
**Worked example — MagSafe phone stand (one premium part).** A solid, gently
tapered wedge body — *not* a flat plate: ~75 × 80 mm base, ~95 mm tall, leaning
~12° back; a Ø56 × 3 mm puck recess sunk into the front face, the cable channeled
out the back (model the puck **and** its captive cable + connector collar —
`references/component-integration.md`); 3 mm walls, 4 mm unified vertical radius,
floor ballast low for stability, an 8 mm front lip the phone rests on. One
printed part, charger integrated — mimic `assets/example_magsafe_stand.py`.
(These numbers are illustrative; the puck/cable/collar dimensions are
web-sourced from Apple's spec and stated as assumptions — see rule 1 above.)
*Only if the design is multi-part*, make each connection explicit and numeric
(e.g. "base + lid, 0.2 mm slip fit on a 2 mm lip; four M3 self-tap bosses, 6 mm
engagement, on an 80 × 60 mm bolt pattern") — a multi-part plan that doesn't
state how the parts join is incomplete.
## Use this skill when
The user asks for any of:
- A specific printable part: phone stand, wall hook, bracket, mount, jig,
enclosure, knob, organizer, hex tray, gridfinity bin, vase, GoPro/action-
camera adapter, replacement knob, light cover, cable clip.
- A CadQuery `.py` file, parametric model, or STL/STEP output.
- Editing an existing CadQuery file: "make the wall 2mm thicker", "add
fillets to the top edges", "move the screw holes 5mm apart".
- A printable replacement part with a stated device + dimensions.
Do **not** use this skill for: render-only concept art, FEA / simulation,
robotics description files (URDF / SDF), or 2D laser-cut DXF. If a sibling
skill is installed for those domains, use it; otherwise tell the user this
skill is not the right tool.
## Default assumptions
Use these defaults unless the user specifies otherwise:
- **Units**: millimeters.
- **Origin**: center of the main body, base plane on `XY`, height along `+Z`.
عرض على GitHub