# Cadquery Assembly

> Orchestrate end-to-end creation of a multi-part CadQuery assembly from a natural-language description, in two phases — top-down high-level architecture (user-verified envelopes) followed by bottom-up detailing (functional geometry → geometric finish → DFM) — with rendering and interference audits between every step. Use when the user asks to "create an assembly", references `leo-monolith/resources/functions/system-messages/cad-generation.txt`, or describes a mechanical product with multiple components that need to be modeled together. Drives the full loop by delegating to `cadquery-high-level-design`, `cadquery-part`, `cadquery-part-detailing`, `cadquery-manufacturability`, `cadquery-interference-check`, and `cadquery-render`.

- Skill: `leoai-org/cadquery-assembly` (Agent Skill)
- Install (CLI): `npx skillmds@latest add leoai-org/cadquery-assembly`
- Raw SKILL.md: https://api.skillmd.com/api/skills/leoai-org/cadquery-assembly/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: leoai-org (https://skillmd.com/u/leoai-org)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/leoai-org/cadquery-assembly

---


# CadQuery assembly — two-phase orchestrator

## Purpose

Produce a mechanically meaningful, manufacturable, visually verified CadQuery assembly. This skill is the **orchestrator**: it never writes part geometry directly. It owns decomposition, the shared dimension contract, workflow gates, and final export — and **delegates** every geometry pass to the sub-skills below.

Poor results usually come from skipping gates (no render, no user sign-off on Phase A), building all geometry in one file, duplicating features across passes, or placing parts with ad-hoc offsets instead of a shared `common.py` contract. This skill exists to prevent those failures.

## Authoritative system prompt

All CadQuery code MUST follow every rule in:

`/Users/dimiasael/leo/leo-monolith/resources/functions/system-messages/cad-generation.txt`

Read it once at the start of the session. It defines imports, the `result` contract, Critical Pitfalls, boolean rules, the **multi-file** layout (`common.py`, `build_<part>()`, sub-assembly files, `assembly.py`), assembly mates, and mechanical interfaces.

## Reference material

- Examples: `/Users/dimiasael/articraft/sdk/_examples/cadquery/*.md` — read 2–3 closest matches (assemblies: `raspberry_pi_3_model_b_assembly.md`, `a_parametric_enclosure.md`, `remote_enclosure.md`).
- API docs: `/Users/dimiasael/articraft/sdk/_docs/cadquery/*.md`
- Tessellate + render: `/Users/dimiasael/leo/sandboxed-python-runner/src/tessellate_snippet.py`, `render_snippet.py`
- Good in-repo reference layout: `/Users/dimiasael/leo/coffee_maker/` (`common.py`, `parts/`, `*_assembly.py`, `assembly.py`)

## Project layout (mandatory for Phase B onward)

Match `cad-generation.txt` multi-file structure — **not** a single monolithic script:

```
<project>/
  common.py                 # ALL shared dimensions + coordinate convention + mate constants
  high_level_assembly.py    # Phase-A envelope baseline (kept for reference)
  parts/
    <function>.py           # build_<function>() -> cq.Workplane; result = build_<function>()
  <group>_assembly.py       # build_<group>_assembly() -> cq.Assembly
  assembly.py               # entrypoint; result = root cq.Assembly
  tools/
    render_preview.py       # optional; see cadquery-render
    interference_audit.py   # optional; see cadquery-interference-check
```

Rules:
- **Every dimension that two parts share** (hole PCD, bore Ø, stack height, snap ledge width) lives in `common.py` — never duplicated in part files.
- Part files **import from `common.py`** only for shared values; local-only dims stay in the part file.
- Sub-assemblies import part builders; `assembly.py` imports sub-assemblies only (no grandchild part imports).
- Do **not** paste part geometry into `assembly.py` or sub-assembly files.

## Sub-skill boundaries (do not blur these)

| Pass | Skill | Adds | Must NOT add |
|------|--------|------|----------------|
| Envelope layout | `cadquery-high-level-design` | Primitives, placement, decomposition | Holes, fillets, ribs, shells, fasteners |
| Functional | `cadquery-part` | Primary solid, pockets, mating interfaces (bores, spigots, slots), hollow walls | Fillets, counterbores, engraving, procurement comments |
| Finish | `cadquery-part-detailing` | Ribs, gussets, lead-in chamfers, cosmetic fillets | New holes, new mates, fasteners, vendor BOM text |
| DFM | `cadquery-manufacturability` | Tap/clearance/counterbore, draft, process checks, vendor BOM, P/N engraving | Structural ribs (already in detailing) |

If a feature appears in the wrong pass, **delete it from that file** and re-run the correct pass — do not stack duplicates.

## The two-phase flow

```
┌────────────────────────────────────────────────────────────────────────┐
│ PHASE A — TOP-DOWN (user-verified architecture)                        │
│  1. decompose → part list + mates + process guesses                    │
│  2. common.py (envelopes + coords + MATE_* constants)                    │
│  3. cadquery-high-level-design → high_level_assembly.py                │
│  4. cadquery-render → read PNGs                                        │
│  5. ASK USER to confirm decomposition + proportions                      │
│  6. iterate until confirmed                                            │
└────────────────────────────────────────────────────────────────────────┘
                                  ↓
┌────────────────────────────────────────────────────────────────────────┐
│ PHASE B — BOTTOM-UP (leaves first; one part at a time)                 │
│  For each part (see build order):                                      │
│    1. cadquery-part          → parts/<name>.py                         │
│    2. smoke exec + cadquery-render                                     │
│    3. cadquery-interference-check (part + mating neighbors)            │
│    4. cadquery-part-detailing                                        │
│    5. cadquery-render                                                  │
│    6. cadquery-manufacturability                                       │
│    7. cadquery-render + swap into <group>_assembly.py / assembly.py  │
│  Final: global interference → final render → STEP export               │
└────────────────────────────────────────────────────────────────────────┘
```

## Phase A — top-down

### 1. Decompose (orchestrator)

Before any code, produce a decomposition table (also written into `common.py` as a comment block):

| Function | Sub-assembly | Envelope (mm) | Origin rule | Mates (partner → joint) | OTS? |
|----------|--------------|---------------|-------------|-------------------------|------|

Use function names (`drive_shaft`, `left_bracket`) — never `part1`. If ambiguous, pick the most realistic mechanical split and state assumptions in the table — do not block on questions unless safety-critical dimensions are missing.

### 2. Invoke `cadquery-high-level-design`

Hand off the decomposition. It must create **`common.py`** and **`high_level_assembly.py`** with nested `cq.Assembly` trees mirroring the final product hierarchy.

### 3. Render and user gate (non-skippable)

Invoke `cadquery-render` on `high_level_assembly.py`. **Read all five PNGs.** Present decomposition + one screenshot summary.

Ask explicitly: **"Is this the right decomposition and proportion? Anything to add, remove, or resize before detailing?"**

Do **not** start Phase B until the user confirms (or gives explicit resize instructions you apply and re-render).

### 4. Freeze the contract

After confirmation, treat `common.py` envelope values as **frozen** unless the user changes them. Phase B parts must not invent new global dimensions.

## Phase B — bottom-up

### Build order

1. **Leaves** — pins, spacers, fasteners-as-parts, small inserts, vendor envelopes
2. **Anchors** — base plates, hubs, shafts, fixed references
3. **Structures** — housings, brackets, lids that reference anchors
4. **Sub-assemblies** — compose children only after all their parts pass the per-part loop
5. **Root `assembly.py`** — last

When a later part invalidates an earlier one, **re-enter that part's pass** (usually functional or DFM) — do not patch only the assembly file.

### Per-part loop (mandatory handoff payload)

For each part, invoke sub-skills with a explicit payload:

```text
Part: <function_name>
File: parts/<function_name>.py
Process: machined | sheet_metal | print | cast
Envelope from common: <dims>
Mating partners:
  - <partner>: <joint kind> — this part provides: clearance | pilot | spigot | socket | weld face | snap beam
Interface constants from common: <MATE_* names and values>
```

#### B.1 `cadquery-part` → smoke test

After the part file is written, **always** run:

```bash
cd <project> && python -c "import runpy; ns=runpy.run_path('parts/<name>.py'); assert ns.get('result') is not None; s=ns['result'].val(); bb=s.BoundingBox(); print(bb)"
```

Bounding box must be non-degenerate (each axis span > 0.1 mm). A part collapsed to a point means wrong origin or empty Workplane — fix before continuing.

#### B.2 `cadquery-render` on the part file

Inspect PNGs for missing features and wrong scale.

#### B.3 `cadquery-interference-check` (local)

Temporary assembly: this part + direct neighbors (detailed or envelope). Fix unintended clashes **before** detailing — detailing ribs often make clashes worse.

#### B.4 `cadquery-part-detailing`

#### B.5 `cadquery-render` — check fillets actually appeared (sharp edge = dropped selector)

#### B.6 `cadquery-manufacturability`

For bolted joints: **clearance holes on the clamped part, tap pilots on the receiving part** — coordinated via `common.py` hole positions. Never drill only one side of a joint.

#### B.7 Swap into assembly

Update the relevant `*_assembly.py` to `from parts.<name> import build_<name>` and `assy.add(build_<name>(), ...)`. Re-render the sub-assembly, then root when needed.

Prefer **`Assembly.constrain(...).solve()`** over hand-tuned `cq.Location` when two parts share a planar or coaxial interface — see `cad-generation.txt` Assembly mates section.

## Placement and origins (orchestrator enforces)

Document in `common.py`:

```python
# COORDINATE CONVENTION
# Origin: <where>
# +X / +Y / +Z: <directions>
# Table/floor: Z = ...
```

**Floor-standing parts** — build with bottom at Z=0, not centered on Z:

```python
# ✅ bottom on table; top at Z = height
part = cq.Workplane("XY").box(w, d, h, centered=(True, True, False))
# ❌ do NOT also offset by h/2 in Location unless you know the part is Z-centered
```

**Part-local origins** — each `build_<part>()` is modeled in its own frame; sub-assemblies apply `loc=`. Never bake global assembly offsets into the part file unless that part is the global anchor (e.g. base plate at world origin).

## Global checks before STEP

1. `cadquery-interference-check` on full `assembly.py` — 0 unintended clashes, 0 unintended gaps on declared flush mates
2. `cadquery-render` on `assembly.py` — all parts visible, correct envelope vs `common.py`
3. Code scan: no `rect(centered=False)`, no `cq.selectors.*`, no compound boolean tools, top-level `result =` on every file
4. `python -c` exec on `assembly.py` succeeds

## STEP export

Never put `cq.exporters.export()` in generated model files. From project root:

```bash
cd <project> && python -c "
import cadquery as cq, runpy
ns = runpy.run_path('assembly.py')
cq.exporters.export(ns['result'], 'assembly.step')
print('exported', 'assembly.step')
"
```

## Hard rules — quick recap

1. Top-level `result = ...` on every file.
2. Multi-file layout with `common.py` + `build_*()` — no monolithic assembly geometry.
3. No `rect(centered=False)` — use `.center(dx, dy).rect(w, h)`.
4. String edge selectors only.
5. Single-extrusion boolean tools, one `cut`/`union` at a time.
6. No `.translate()` on empty Workplanes — use `Workplane(plane, origin=...)` or translate after geometry exists.
7. No reused rotated Workplane for multiple cuts — rebuild each tool.
8. No loft/sweep as boolean tools — shell instead.
9. Every joint modeled on **both** sides where applicable.
10. Vendor parts: interface geometry in functional pass; procurement comment in DFM pass; use `step.parts` or injected STEP when available.

## When to stop

Stop when: (a) user confirmed Phase A; (b) every part completed functional → render → local interference → detailing → render → DFM → render, and is swapped into its sub-assembly; (c) global interference clean; (d) five views of `assembly.py` correct; (e) STEP exports without OCCT errors.

## Anti-patterns (reject immediately)

| Symptom | Fix |
|---------|-----|
| All parts in one `.py` file | Split per multi-file rules |
| Parts float / gaps at mates | Shared `MATE_*` in `common.py`; use constraints or recalc `loc` from anchor faces |
| Duplicate holes from part + DFM | Functional = interface bore only; DFM = clearance/counterbore/tap |
| Fillets in functional part file | Move to detailing pass |
| Skipped Phase A user confirmation | Stop and render high-level; ask user |
| Render never opened | Always Read PNGs — do not assume pass |
| `high_level` used `z = h/2` Location on Z-centered box | Use `centered=(True,True,False)` at Z=0 |
| 30+ parts flat on root assembly | Group into ≤6 sub-assemblies |

