---
name: cadquery-modeling
description: "Create, edit, or debug parametric 3D CAD parts in CadQuery (Python) that export to STEP for SolidWorks. Use when the request involves CadQuery, .step/.stl export, or modelling a physical part — enclosure, bracket, PCB, motor mount, cam, gear. Also use when a STEP will not open in CAD, or geometry looks correct in STL but is invalid as a solid. Applies a verification discipline before export and documented OCC pitfalls."
---

# CadQuery Modeling for Manufacturable CAD

This skill turns natural-language part requests into clean, verified, parametric
CadQuery code whose STEP output reliably opens in SolidWorks. It encodes hard-won
rules from real multi-part product design.

## Core philosophy

1. **Parametric first** — every dimension is a named variable at the top of the file.
2. **Verify before trusting** — a script that printed `[OK]` is NOT proof of success.
   Always check geometry validity and solid count before declaring done.
3. **Avoid OCC fragility** — prefer simple primitives + boolean operations over
   fragile chained features. See `references/occ_pitfalls.md`.
4. **One part, one file** — each part is its own `.py` producing its own STEP/STL.
   Assemblies are done in SolidWorks, not baked into a single STEP.

## Before creating anything: confirm where

Files land in whatever directory Claude Code was started in — that is the single
thing that determines whether the user finds their STEP later. Before creating a
project or the first part, **state the absolute path you are about to write to**,
and stop to confirm if it looks wrong:

- a cloud-synced path (`OneDrive`, `Dropbox`, `iCloud` — note that on Windows the
  Desktop and Documents folders usually ARE OneDrive)
- `Downloads`, a home directory root, or a system folder
- the `claude-cad` toolkit repo itself
- a path containing spaces, parentheses, or non-ASCII characters

If any of those apply, say so and suggest a clean location such as
`C:\cad_project` before writing files. Getting this wrong is silent: everything
succeeds, and the user simply can't find the output.

## The standard workflow

When the user asks for a part, follow this loop:

1. **Match the questions to how specified the request is.** Never invent a
   critical dimension — ask instead. Default to millimeters.
   - **Underspecified** (e.g. "a bracket to hold a 60 mm pipe"): don't guess, and
     don't refuse. Ask one focused batch of questions covering only what you truly
     can't proceed without — typically overall size, thickness/wall, the
     coordinate system/orientation, and any dimension that must match another part
     or a real component. Keep it short; propose sensible defaults the user can
     accept ("I'll assume 4 mm thick unless you say otherwise").
   - **Fully specified**: don't interrogate. Build it, and list any assumption you
     had to make in the final report.
   - If the user asks you to **write a spec first**, produce it as text (coordinate
     system, overall dimensions, every feature with size + position, interfaces)
     and get approval before writing code.
2. **Write the part file** following the structure in
   `references/part_file_structure.md`.
3. **Run it** and read the full terminal output, not just the last line.
4. **Verify** geometry validity, solid count, volume, and bounding box (see below).
5. **Report** the verification numbers to the user and note any assumptions made.

Read `references/occ_pitfalls.md` before writing any code that uses shell, fillet
after shell, countersinks, or lofts — the operations with a history of silent
corruption. Each pitfall now carries a **Verified behaviour** block saying what
the current kernel actually does; the one reliably reproduced silent failure is
shelling a solid whose removed face was filleted (Pitfall 4b).

## Project layout

Always use this structure so STEP files collect in one place for SolidWorks:

```
project/
├── parts/        # one .py per part
│   ├── housing.py
│   └── pcb.py
├── output/       # STEP + STL land here (git-ignored)
└── docs/
    └── assembly_guide.md
```

Resolve the output path **relative to the script file**, not the working
directory. A bare `"../output/..."` breaks the moment the script is run from the
project root instead of from `parts/` — the export silently lands one folder too
high. Always use this pattern:

```python
from pathlib import Path
OUT = Path(__file__).resolve().parent.parent / "output"
OUT.mkdir(parents=True, exist_ok=True)

cq.exporters.export(part, str(OUT / "housing.step"))
cq.exporters.export(part, str(OUT / "housing.stl"))
print(f"[OK] exported to {OUT}")
```

This works whether the user runs `py -3.11 parts/housing.py` from the project
root or `py -3.11 housing.py` from inside `parts/`. Printing the resolved path
also makes it obvious where the files went.

## Mandatory verification block

Every part file MUST verify before export — either with this inline block, or
(preferred) with the `check()` helper from `scripts/diagnose.py` shown after it.
One of the two is required; a bare `[OK]` print is never sufficient. STL is
forgiving and hides geometry errors that survive into the STEP — and CAD import
is no safety net either: SolidWorks has opened a provably invalid STEP without
warning (`references/occ_pitfalls.md`, "Do not count on CAD import to reject
bad geometry").

```python
# ===== Verification (do not skip) =====
from OCP.BRepCheck import BRepCheck_Analyzer

solid = part.val()
shape = solid.wrapped

# 1. Geometry validity — catches silent OCC corruption
analyzer = BRepCheck_Analyzer(shape)
is_valid = analyzer.IsValid()
print(f"[verify] IsValid = {is_valid}")
if not is_valid:
    raise RuntimeError("Invalid geometry — part is unusable; CAD may still open it without warning.")

# 2. Single-body check — catches stray cut/union debris
n_solids = len(solid.Solids())
print(f"[verify] solid count = {n_solids}")
if n_solids != 1:
    raise RuntimeError(f"Expected 1 solid, found {n_solids}.")

# 3. Volume — ASSERT it against a range DERIVED FROM YOUR PARAMETERS, not the
#    built result. Validity and solid count both pass on a part whose hollow/cut
#    silently did nothing (bbox too); volume is the tell. Compute expected_lo/hi from
#    the dimensions above (e.g. solid block minus the intended cavity, +/-15%).
#    NEVER run the build first and paste Volume()'s output back here.
vol = solid.Volume()
print(f"[verify] volume   = {vol:.2f} mm^3")
assert expected_lo <= vol <= expected_hi, (
    f"Volume {vol:.1f} outside derived range — a hollow/cut may have done nothing.")
print(f"[verify] bbox     = {solid.BoundingBox()}")

# Signature — always end with this line. Its presence in the output is how the
# user can tell the skill's checks actually ran (vs a part written with none).
print("[verify] claude-cad — checks applied")
```

### Prefer the diagnostic helper

The block above is the minimum. For real work, use `scripts/diagnose.py`, which
does the same checks but also *classifies* what's wrong and *safely auto-repairs*
the unambiguous cases (e.g. discarding tiny offcut debris from a multibody result):

**Setup (once per project):** copy `scripts/diagnose.py` from this skill into the
project's `parts/` folder, next to the part files. Python puts a script's own
directory on the import path, so `from diagnose import check` then resolves when
running `py -3.11 parts/<name>.py`. Without this copy the import fails with
`ModuleNotFoundError: No module named 'diagnose'`. (`project_init` also keeps a
canonical copy in `claude-cad/` alongside `preflight.py`; the `parts/` copy is the
one imports resolve against — keep them in sync, and `preflight.py` warns if they
drift.)

**Prefer `export_verified()` — it runs the checks and raises before it writes, so a
failing part is not written *by this function*.** This is a rule you follow, not a
mechanism that prevents export: `cq.exporters.export` is a plain function, so a
script that skips this helper bypasses every check. Prefer it so the safe path is
the easy one.

```python
from pathlib import Path
from diagnose import export_verified        # diagnose.py must be in parts/

OUT = Path(__file__).resolve().parent.parent / "output"

# expect_volume is DERIVED from the parameters above — e.g. solid block minus the
# intended cavity, +/-15%. A range is the honest form; NEVER paste Volume()'s output.
export_verified(part, OUT, "bottom_housing",
    expect_volume=(28000, 38500),           # derived band (preferred over a scalar)
    expect_bbox=(94, 65, 27.2),             # from the stated dimensions
    inside=[(0, 0, 1)],                     # design intent: floor is solid here
    outside=[(47, -12, 10)],                # design intent: this port is void here
    regression_volume=32163.55,             # optional: labelled re-run baseline
)
```

This verifies, exports STEP + STL, and writes `bottom_housing.verified.txt` beside
them — a durable record of what was checked, since terminal output vanishes when
the window closes. Use the lower-level `check()` / `assert_points()` directly only
for intermediate steps mid-build.

**The independent checks are `expect_bbox` and the `assert_points` — lead with
those.** The bounding box is read straight off the stated dimensions, and the
inside/outside points encode design intent (which wall a port is on) — they catch
a feature that is missing, misplaced, or on the wrong face, which validity cannot
see (they caught a microSD port cut into the wrong wall in the sbc-case example).

**`expect_volume` is the weakest check — DERIVE it, don't measure it.** Validity,
solid-count, and bbox all PASS on a part whose `.shell()` silently did nothing (the
outer box is unchanged), so among those numbers volume is the tell for that failure
(`references/occ_pitfalls.md`, Pitfall 4b) — an interior spot-check catches it too.
But volume only helps if it is computed from the parameters *before* building, as a
range (a silent no-op reads several times too high; ±15% catches it). If you omit
it, the report prints "VOLUME UNCHECKED" instead of a clean PASS.
Keep the exact measured figure only as a separate, labelled `regression_volume`.

**Use `assert_points` for anything with a position that matters** — ports, holes,
bosses, openings on specific walls. Geometry can be valid, single-bodied, and the
right volume while a feature sits on the *wrong face*; no topological check sees
that. Sample the middle of each opening (expect outside) and the wall beside it
(expect inside).

When a part fails, **don't guess** — follow the symptom→diagnosis→fix decision tree
in `references/diagnosis.md`. Auto-repair only touches safe, unambiguous cases;
invalid topology is reported (not patched) so a real design error is never hidden.

## Verification, in SolidWorks

After import, the user should confirm in SolidWorks:

- **Mass Properties** — center of mass on expected symmetry axes (X/Y ≈ 0 for a
  symmetric part) is the fastest correctness check.
- **Principal moments of inertia** — the relative sizes confirm which axis is
  long/short, catching swapped dimensions.
- The part opens as a **solid body**, not a surface.

See `references/solidworks_checks.md` for the exact menu paths and what each
number tells you.

## Reference files

Load these as needed:

| File | When to read |
|------|--------------|
| `references/occ_pitfalls.md` | **Before** any shell, fillet, countersink, or loft. This is the most important reference. |
| `references/diagnosis.md` | When a part fails verification — the symptom→diagnosis→fix decision tree, and how to auto-repair. |
| `references/part_file_structure.md` | When writing a new part file from scratch. |
| `references/verification.md` | When a STEP won't open, or geometry looks wrong. |
| `references/solidworks_checks.md` | When validating a part inside SolidWorks. |
| `references/environment.md` | When `cadquery` or `OCP` import fails, or on first setup. |

## Scripts

| Script | Purpose |
|--------|---------|
| `scripts/diagnose.py` | **The intelligence layer.** Diagnoses a part (classifies validity / multibody / degenerate issues) and safely auto-repairs what it can. Use `from diagnose import check` inside a build, or run it on a `.step` from the CLI. |
| `scripts/preflight.py` | Project-level: runs and verifies every `parts/*.py`, then writes a committable `VERIFICATION.md` summary at the project root. Copy it into the project's `claude-cad/` folder and run `py -3.11 claude-cad/preflight.py`. |
| `scripts/verify_step.py` | Lightweight standalone check of any STEP/STL: validity, solid count, volume, bbox. |

## Hard rules (never violate)

- Never invent a critical/interface dimension. Ask instead.
- Never declare success on `[OK]` alone — only after the verification block passes.
- Never use `cskHole` on a shelled (thin-wall) face — build a cutter solid
  instead. (Portability rule: the historical failure does not reproduce on the
  current kernel — see Pitfall 1's Verified behaviour — but the cutter pattern
  is explicit, uniform across hole types, and version-proof, at zero cost.)
- Never bake a multi-part assembly into a single STEP unless the user explicitly
  wants a static snapshot; do assemblies in SolidWorks.
- Always default to millimeters and state the unit.
- Always print the absolute output directory after exporting, so the user never
  has to guess where the file went.
- Never treat `IsValid` + single-solid as sufficient. Always check volume against
  a range **derived from the named parameters** (solid block minus intended
  cavity, or bbox volume × a stated fill fraction) — it is the only check that
  catches a hollow or cut that silently did nothing.
- Never derive `expect_volume` by running the build and pasting `Volume()`'s
  output back as the expectation — that checks the geometry against itself.
  Compute it from the dimensions, in a comment above it, as a range. Keep any
  measured figure only as a separate, labelled `regression_volume`.
- For any feature whose *position* matters, verify it with point classification
  (`assert_points`), not just topology — this is the strongest intent check, not
  an afterthought.
