CAD generation, inspection, and validation
Provenance: maintained in earthtojake/text-to-cad.
Use the installed local skill files as the runtime source of truth; the
repository link is only for provenance and release review.
Setup
This skill's commands are thin entrypoints over the cadgen distribution, which
carries the Python build runtime and the JavaScript it executes. Install it once:
python -m pip install -r requirements.txt
Rendering additionally needs a browser, which pip cannot supply:
python -m playwright install chromium
Purpose
Create or modify parametric CAD models from natural-language requirements, build validated STEP/STP (or mesh) outputs, inspect geometry references, and return checked outputs. STEP is the default output of CAD geometry and the one the inspection tools read; STL, 3MF, and native GLB are mesh outputs a model declares beside it — or instead of it, when the part is print-only. For assemblies, prefer cadgen.assembly.AssemblyHelper with source-level build123d joints, named mating datums, and native labels when the parts have functional assembly relationships.
There are two ways into the STEP workflow: build from a build123d model script (the default when designing from scratch or modifying a generated model), or import an existing STEP/STP file directly (when no script exists or the user explicitly targets the STEP file). Both are inspected, snapshotted and exported the same way.
Use this skill when
Use this skill when the user asks for CAD files, STEP/STP files, build123d source, selector refs such as #o1.2.f1, mechanical parts, assemblies, enclosures, brackets, fixtures, holes, counterbores, countersinks, slots, pockets, bosses, standoffs, ribs, fillets, chamfers, shells, source-level joints, mating, or measurements. Also use it when the user supplies reference images or 2D technical drawings of a part to reproduce or take design intent from.
Also use it when the user asks for STL, 3MF, or native GLB output from CAD geometry; load supported-exports.md for details. For 2D DXF drawings, use the $dxf skill; when a DXF projects from a 3D part, this skill owns the part and $dxf owns the drawing.
Do not use this skill for render-only concept art, CAM toolpaths, engineering certification, FEA conclusions, architectural BIM, or freehand illustration unless the user also needs CAD geometry.
Default assumptions
Use these defaults unless the user specifies otherwise. These are first-pass modeling defaults, not manufacturability, tolerance, or certification claims:
- Units: millimeters.
- Origin: per the part-type defaults in
references/positioning.md; center of the main part or assembly when nothing better applies.
- Base plane: XY.
- Up/extrusion axis: positive Z.
- Output geometry: closed, positive-volume solids unless the user requests surfaces or construction geometry.
- STEP structure: one valid solid, a compound of solids, or a labeled assembly compound.
- Assembly structure: fixed root part, part-local frames, named mating datums,
AssemblyHelper relationships backed by build123d joints where applicable, explicit generated placements, and verbose native labels.
- Small plastic enclosure wall: 2.0-3.0 mm when unspecified.
- Cosmetic fillet: 1.0-3.0 mm when safe for local geometry.
- M3/M4/M5 normal clearance holes: 3.4/4.5/5.5 mm unless another standard is requested.
Ask one focused clarification question only when missing information makes the model impossible, fit-critical, safety-critical, or compliance-bound. Otherwise proceed with explicit assumptions.
Tools and paths
The command surface (the cadgen console script, installed with the package):
python <model>.py # its __main__ calls the model, which builds it
cadgen step build IN OUT # re-emit an existing STEP as a new one, with kinematics
cadgen stl build ... # one door per mesh format; `3mf` and `glb` are the others
cadgen step inspect ... # refs, measure, align, frame, diff
cadgen step snapshot ... # PNG visual review packets, for STEP
cadgen stl snapshot ... # the same, for a mesh file; `3mf` and `glb` again
cadgen store why <model>.py # why the model is stale or current, clause by clause
cadgen daemon status # the warm workers and the jobs they are running
Scripts are RUN; commands take DOCUMENTS. python model.py is the one
source door — it writes every output the model declares and (only when the
model declares kinematics, animation, or mesh exports) its sidecar. Every
command above takes a .step/.stl/.dxf FILE, and one handed a .py says
so. A door asks one question of a document: does the store have a tree for
this file's bytes? If so it reads it; if not it compiles one from the bytes as
a job in the pool — generated or imported alike. A door never refuses a
document and never runs a script. Whether a document is behind its script
is the model's business (cadgen store why), not the door's.
Use the active project Python interpreter; treat python in examples as an interpreter placeholder. Every operational verb is a cadgen subcommand (python -m cadgen.cli <verb> is the PATH-independent equivalent). Use cadgen <verb> --help for the complete current interface; reference docs show recommended workflows, not every flag. Install per requirements.txt; cadgen doctor <skill-dir> verifies the installed cadgen matches this skill's pin (docs drift silently on a mismatched install).
Target paths resolve from the command's current working directory, not from the skill directory. Run commands from the workspace that owns the artifacts and pass cwd-relative target paths so project CAD files never resolve accidentally under the skill directory.
CAD references are #... selector tokens local to a target, for example #o1.2 or #o1.2.f1. Pass the STEP/CAD file as a separate target argument when using CAD CLIs.
A model
Generation has NO CLI. A model is a plain Python script: one parameterless
decorated function, built by calling it from __main__:
from cadgen import build123d as bd
from cadgen import step
WIDTH = 10.0
@step # or @step(out="../STEP/bracket.step") to relocate the output
def bracket():
return bd.Box(WIDTH, 10, 10)
if __name__ == "__main__":
bracket()
The rules, each enforced by the decorator or the build:
- The decorator only declares; a call builds. Importing a model module
never builds; a file without
if __name__ == "__main__": <model>() never
builds either — always end the script that way. python bracket.py writes
bracket.step beside the script and the model's result into the store; an
unchanged model is a fast no-op. --force rebuilds this model only.
- A model takes no parameters and its function is called with no
arguments. Parametric geometry lives in a plain factory the model calls
with its values (
def _bracket(width, thickness): ...); another
configuration is another model in another file, the way two part numbers
are two parts.
- The return is a bare build123d
Shape — a solid, a compound, or a
labeled assembly compound. Never a dict, never a path.
- Outputs are exactly what the decorators declare.
@step writes the
.step; @stl/@threemf/@glb stacked on it write meshes. STEP is not
required: a function with only @stl (or @glb, @threemf) — no @step
— is a full model with the same tree, record, build and no-op, whose outputs
are the meshes and which writes no .step and no sidecar. Use it for
print-only parts and render assets. references/supported-exports.md.
- Decorator arguments never change the geometry. They decide where the
files land (
out=), how they are written (mesh_tolerance=,
mesh_angular_tolerance=) and what the sidecar declares (kinematics=).
The geometry is the return value and nothing else: a Compound placing
children is packaged as occurrences, a single solid as one component, and
part/assembly is read off the tree. There is no kind= and no bake
point — a posed or differently configured export is authored geometry, or
another model.
- A sidecar only when strictly necessary.
<name>.step.json is written
only when the model declares kinematics=; a model that declares none has
no sidecar, and a rebuild that dropped the declaration deletes the stale
file. What a model declares about its outputs lives in its record, not in
a file beside the geometry.
- One model per file, as a rule of thumb. A model's identity is its file
plus its function (
plate.py::plate); a file holding one model is named by
its path alone. A file MAY hold several (a small family of variants): each is
its own record, output and job (a sole model writes <file>.step; models sharing a
file write <function>.step), but
they share the file's closure, so editing one rebuilds them all — which is
why one per file is the recommendation.
- Composition is a call. Import a sibling model and call it inside your
body (
from arm import arm … arm()); it returns the child's geometry.
references/step-generation.md has the whole composition contract.
from cadgen import build123d as bd is the canonical import — a lazy,
transparent re-export of build123d (same names, same behaviour) — so the
freshness gate and the warm-worker handoff run before any kernel import is
paid. Raw import build123d works but costs ~2.5s on every re-run.
- Per-run flags ride the script's argv:
--force, --json, --verbose,
--mesh-tolerance, --mesh-angular-tolerance.
Composition, freshness and builds
The essentials; references/step-generation.md has the code and the edge cases.
- Children are models you call. A parent's body imports sibling models
and calls them; each call returns that child's geometry (built if stale,
loaded from the store if current), and the parent's result LINKS to the
child's — stored once, shared by every parent. Place a child with
Pos/Rot/Location * child or child.moved(loc); never child.located(loc)
(it deep-copies the geometry, so the parent owns a copy instead of linking).
- Every build is parallel. A child call submits the child's build and
returns at once; siblings build on their own workers while the body keeps
going; the parent waits when it first reads the geometry — normally the
closing
bd.Compound(children=[...]). Nothing to configure, nothing to
annotate.
- Builds never wait on or cancel each other. Two runs of one model both
run; the store keeps the result whose sources match the files as they are
now, so the disk ends at the newer source. Editing a child while its parent
builds leaves the parent finished against the child it pinned.
- A rebuilt part does not update the assemblies that use it. Dependency
is pull: rebuild the parent (
python assembly.py) to pick up a child's
change. A child edit that yields identical geometry leaves parents current.
- What a rebuild tracks — models by result, constants by value, functions by
file. Importing a model function tracks that model by its result;
importing a module-level literal (
from plate import WIDTH) tracks the
value; importing anything else from a file (a helper function, a bd.
object) makes that whole file part of your model's source, so any edit to it
rebuilds you. Shared constants may live in a model file or in lib/.
- The environment is not an input. Model and
lib/ code takes no
parameter from os.environ, the working directory, the current time or a
random source: the gate tracks source by hash, constants by value and children by
result, and cannot see any of those — a value that changes geometry through
them leaves a stale result reading as current. A configuration is a factory
argument; another configuration is another model.
- A mirrored part is its own model. STEP cannot express a reflection, so
a right-hand part is a separate model file calling the same factory with
mirror=True (or mirroring the factory's result), not a mirrored child.
read_step files are inputs, not models. Replacing the file makes the
reader stale. To make an imported part first-class, wrap it:
@step def servo(): return read_step(...).
cadgen store why <model>.py is the freshness door: it prints the
gate's verdict clause by clause (record, closure files, constants, each
child's pinned vs current tree, tree objects, declared outputs). Reach for
it whenever a model did or did not rebuild when you expected it to.
Workers. A warm daemon is on by default: each model gets a persistent
worker (a second, an extra, when the model is asked for while already
building); spares stand by so a new model never pays the import; idle workers
unbind after ten minutes. Running builds are limited to one per core
(CADGEN_JOBS overrides); a parent waiting on its children holds no slot.
CADGEN_DAEMON=0 uses transient workers spawned for that one run — still
parallel, still the same store — and is the mode for tests and debugging.
cadgen daemon status lists workers, spares and the running/queued jobs.
Debugging notes. Do not alternate CADGEN_DAEMON=0 and daemon runs of one
model while a daemon build of it is in flight (the two are unbrokered; each
publishes what it built, and the publish rule keeps the newer source). One
project, one store. A build under another CADGEN_CACHE_DIR (a temp store,
a test) rewrites the same output files; the first store's records then see
outputs whose bytes they did not write, so its gate reports the model stale
(output changed: …) and every parent child stale: … — nothing is wrong,
the two stores simply disagree, and the next build under either settles it.
Module bodies stay cheap. A model file is imported on every rerun, before
the gate: a module-level read_step (computing a layout from a vendor STEP at
import) pays the kernel and the parse each time even when the model is
current — call read_step inside the body or a function it calls; the
hint: printed on such a run names the import site. Resets, smallest
first: python model.py --force rebuilds one model now; cadgen store forget <model.py> drops its record so the next run rebuilds it (children
untouched); cadgen store forget <file.step> drops the tree entry for that
file's bytes so the next open or door call compiles it again; cadgen store gc sweeps unreachable objects; clearing the store (rm -rf ~/.cache/cadgen, or $CADGEN_CACHE_DIR) is always safe — every model
reads as stale and rebuilds, and no project file is touched. The gate has no
cadgen-version clause, so a model built by a cadgen with a bug stays current
after the fix: forget the affected models (or the parents that link them),
or clear the store.
The store (~/.cache/cadgen, CADGEN_CACHE_DIR overrides) holds
objects/ — immutable, content-addressed components and trees — and index/
— the per-model records the gate reads, the op memo, and the mesh ledger. It
contains only derived results. The full contract is STORE.md in the
installed cadgen package.
Streams, progress and failures
Streams. stdout carries the result; stderr carries progress, timing, and failures. A model run prints <outcome> <document path> on stdout (built, current, or skipped-peer when a concurrent build of the same model finished first), and the two streams never interleave, so 2>/dev/null leaves a clean parseable result and >/dev/null leaves a readable log. JSON on stdout is always compact; pipe through jq . to read it. For machine-readable output: model runs, the build doors (step, stl, 3mf, glb) and snapshot take --json; inspect already emits JSON and takes --format text for prose. A model run's --json line carries outcome, document and tree (the result's hash). --verbose adds stage timing (and full tracebacks) on stderr. Output volume does not grow with model size.
The build tree. On a terminal, stderr shows the graph as the body's child calls reveal it — one refreshed block, each model submitted, queued, building · <phase> n/total, current, or ✓ <time>, finished subtrees folded to one line. With --json or a non-TTY, one JSON line per model transition (model, parent, state, phase, progress, elapsed) on stderr replaces the drawing; the result line on stdout comes last. After publishing, the root re-runs its gate once and says already stale: <child> changed during the build; rerun if it did.
Reporting progress from a model. A long build spends most of its wall time inside
the model body. Import the reporter — it binds to whichever build is running, and does
nothing when there is none:
from cadgen import report, track, step
@step
def housing():
report("bearing housing") # name the current phase
for rib in track(ribs, label=lambda r: r.name): # count through a work list
...
track() advances the count when an item's work is DONE and labels the item in flight, so a
reader sees "3 finished, now on engines". The phase surfaces on the model's line in the build
tree and — through the daemon's job ledger — as compiling · <phase> in the CAD Viewer for any
document the job writes, whoever started the job. Without this a multi-minute assembly says
nothing during its longest phase.
Failures print the exception and the frames in your own model, not the runtime's:
[cadgen] FAILED: ValueError: bad radius
[cadgen] src/widget.py:9 in bracket
[cadgen] return _profile(radius)
[cadgen] re-run with --verbose for the full traceback
A failed child raises at the site in the parent that first read its geometry, naming the call and carrying the child worker's output.
Snapshots
Snapshot inputs. One format, one door, and the same TARGET [OUT] grammar build uses. cadgen step snapshot renders .step/.stp documents — nothing else (a model script is refused by name: run python <model>.py, then snapshot the STEP it wrote). A mesh file goes to its own door: cadgen stl snapshot, cadgen 3mf snapshot, cadgen glb snapshot. A mesh has no CAD topology, so the STEP-only options (--focus/--hide, --display, --kinematics, --animation/--time, --mode section) are not on those commands at all — check --help and the door tells you what it can do. Robot descriptions belong to the urdf/srdf/sdf skills. Each door refuses what is not its own format, and names the door that takes it.
cadgen step snapshot STEP/bracket.step tmp/review.png
cadgen stl snapshot STL/bracket.stl tmp/mesh.png
Snapshot output. The path you name is the path you get:
cadgen step snapshot STEP/bracket.step tmp/review.png
# then Read tmp/review.png
OUT is written exactly as given (a relative path against the current working directory), cleared before the render and written atomically after it — so reuse one name while iterating, name the iterations (tmp/before.png, tmp/after.png) when you need to compare, and treat a missing file as the failure signal: there is never an older image at the path to mistake for output. A directory (tmp/) is the don't-care case and gets a generated timestamped name inside it, printed on the saved snapshot: line. The same rule applies per output in a JSON packet.
Theme and display. Theme settings live under one --theme, display settings under one --display — the viewer's two tabs, one option each. The default theme is snapshot: Workbench Light with the ground grid and origin axis removed, because in a still image those read as geometry rather than as orientation. Pass --theme workbench-light for the viewer's own look. Projection is a theme trait honoured by every format, so a snapshot frames the same way the viewport does.
Required workflow
Scale depth to the task: a simple part needs a short brief and few spec-driven checks; assemblies and fit-critical work need full positioning and alignment validation.
- Classify the task. New part, new assembly, source modification, direct STEP/STP inspection, reference selection, measurement/alignment check, snapshot review, or mesh output request.
- Load only the needed references. Use the triggers below instead of reading the whole reference set.
- Write a natural-language CAD brief. Extract dimensions, units, coordinate convention, feature intent, output paths, assumptions, and validation targets from all provided inputs — prose, reference images, technical drawings. Use
references/cad-brief.md.
- Check named purchasable components. When an assembly includes named off-the-shelf actuators, servos, motors, electronics boards, connectors, or other purchasable components, search
$step-parts before creating simplified placeholder geometry. If no exact match is found, record the miss and then use a documented bounding volume.
- Plan before coding. Define the constants and factory arguments, intent labels, source paths, expected bounding boxes, and any mating/positioning datums before editing.
- Edit source, not generated artifacts. Author a plain
.py model script with one decorated function (shared code lives in plain helper modules; see references/step-generation.md). When a model script exists, run IT, never hand-edit its exported STEP. Imported STEP/STP files (no script) are handed straight to cadgen step inspect, step snapshot and the mesh doors — each compiles whatever it needs on demand.
- Build explicit targets. Run each model script directly (
python <model>.py); do not sweep directories. A parent builds its children as it calls them, so running the root is the whole build. Declare @stl/@threemf/@glb outputs on the model, or run cadgen stl|3mf|glb build for one-off mesh files. For multi-model project structure, read references/project-layout.md.
- Validate geometrically. Run
cadgen step inspect refs <step-or-cad-target> --facts --planes --positioning as the baseline, then verify the dimensions and relationships the user's spec calls out with targeted measure, align, frame, or diff checks. Run cadgen step inspect validate <step-or-cad-target> for geometry soundness: refs --facts reports counts and bounds, and its ok field covers ref resolution only — an open shell and an inverted solid both pass it.
- Snapshot the primary STEP — snapshot validation is mandatory. After creating or visibly updating a STEP/STP part or assembly, ALWAYS run
cadgen step snapshot against it and review the output; deterministic checks passing is not a reason to skip. The only skip cases are documented in references/snapshot-review.md (no visible geometry changed, or no valid artifact exists); report the reason when skipping. A mesh-only model is reviewed with its format's snapshot door.
- Repair and rerun. If a check fails, change the smallest responsible source section, rebuild, and rerun the failed validation.
Handoff
After completing CAD work that creates or modifies .step, .stp, .stl, .3mf, or native .glb artifacts, you must ALWAYS hand the explicit file path(s) to $cad-viewer when that skill is installed. $cad-viewer must start CAD Viewer if it is not already running and return link(s) to the relevant created or updated file(s); include those live viewer link(s) in the final response. If $cad-viewer is unavailable or startup fails, report that and rely on CLI inspection plus snapshots instead of silently omitting the handoff. This rule applies to every workflow in this skill, including mesh outputs.
When verification snapshots are generated, include the saved PNG snapshot(s) in the final response. If no snapshot applies, or if snapshot generation fails, say why and report the deterministic validation that still ran.
Non-negotiables
- The model script is the source of truth. Every written file — STEP/STP, STL, 3MF, GLB, the sidecar — is a derived output; edit and rerun the script, never the outputs. Where a model declares a STEP, the STEP is the artifact that is inspected and snapshotted.
- Use named constants, closed solids, verbose native build123d labels, and source-controlled geometry intent.
- Author assembly positioning in source.
references/positioning.md is authoritative for AssemblyHelper, build123d joints, explicit Location transforms, and alignment validation.
- Do not use
git status, git diff, or file-size churn as CAD comparison for large exported STEP/STP, GLB, STL, or 3MF artifacts. Compare source changes, cadgen step inspect summaries, or snapshots instead; use path-limited git status only for bookkeeping.
- Report only checks that actually ran or are directly supported by tool output.
Progressive references
Load these files only when their trigger applies:
references/cad-brief.md — converting prose, reference images, and technical drawings into a CAD brief.
references/build123d-modeling.md — build123d modeling patterns, topology, selectors, features, labels.
references/step-generation.md — the model contract in full: composition (linked children, read_step inputs), what a rebuild tracks, mirrored parts, factories, the daemon and workers, imported STEP/STP files, and post-build steps.
references/inspection-and-validation.md — validation sequence, selector refs, facts, planes, measurements, alignment, diff, frame, and validation reporting.
references/snapshot-review.md — mandatory snapshot policy, packet sizing, targeted views, and converting visual findings into geometry checks.
references/positioning.md — part-local datums and origins, assembly transforms, build123d joints, CLI alignment validation, and positioning reports.
references/kinematics.md — articulating, posing, or animating a STEP model: typed mates (kinematics= on the decorators — mates, couplings, pose presets, export-at-pose), and the render module beside the document (<name>.step.js: the choreography contract, loaded by the viewer, read by no build).
references/supported-exports.md — STL/3MF/native GLB outputs: declared exports, mesh-only models, and the cadgen stl|3mf|glb build doors.
references/repair-loop.md — diagnosis and repair procedures.
references/project-layout.md — project structure for anything bigger than a couple of loose models: src/ for model scripts and shared code, format folders (STEP/, DXF/, STL/) for raw outputs, naming, and commit policy; references/project-template.md is the copyable exemplar. Read them when a project has more than a couple of models or when asked how to organise CAD code and artifacts.
references/migrations.md — the tooling disagreeing with a model you believe is correct: recognizing a project authored against an older cadgen, and where the migration guides live.
Final responses should include generated files, returned $cad-viewer viewer links, verification snapshots, validation actually run, assumptions, and caveats. Use references/inspection-and-validation.md for report structure.
1---2name: cad3description: Create, modify, inspect, and validate parametric CAD parts and assemblies authored as cadgen model scripts. Use for natural-language CAD specs, reference images, 2D technical drawings, STEP/STP generation or direct inspection, Python CAD source, source-level joints, selector references, geometry facts, measurements, mating deltas, snapshots, and STL/3MF/native GLB outputs from CAD geometry. Also covers project structure for multi-part CAD work - src/ for model scripts and shared code, format folders (STEP/, DXF/, STL/) for raw outputs, naming, and commit policy for projects with several @step/@dxf model scripts and imported source files; use it when starting a CAD project with more than a couple of models, when asked how to organize CAD code and artifacts, or when growing a flat folder of models into a project.4---5
6# CAD generation, inspection, and validation
7
8Provenance: maintained in [earthtojake/text-to-cad](https://github.com/earthtojake/text-to-cad).
9Use the installed local skill files as the runtime source of truth; the
10repository link is only for provenance and release review.
11
12## Setup
13
14This skill's commands are thin entrypoints over the `cadgen` distribution, which
15carries the Python build runtime and the JavaScript it executes. Install it once:
16
17```bash
18python -m pip install -r requirements.txt
19```
20
21Rendering additionally needs a browser, which pip cannot supply:
22
23```bash
24python -m playwright install chromium
25```
26
27## Purpose
28
29Create or modify parametric CAD models from natural-language requirements, build validated STEP/STP (or mesh) outputs, inspect geometry references, and return checked outputs. STEP is the default output of CAD geometry and the one the inspection tools read; STL, 3MF, and native GLB are mesh outputs a model declares beside it — or instead of it, when the part is print-only. For assemblies, prefer `cadgen.assembly.AssemblyHelper` with source-level build123d joints, named mating datums, and native labels when the parts have functional assembly relationships.
30
31There are two ways into the STEP workflow: build from a build123d model script (the default when designing from scratch or modifying a generated model), or import an existing STEP/STP file directly (when no script exists or the user explicitly targets the STEP file). Both are inspected, snapshotted and exported the same way.
32
33## Use this skill when
34
35Use this skill when the user asks for CAD files, STEP/STP files, build123d source, selector refs such as `#o1.2.f1`, mechanical parts, assemblies, enclosures, brackets, fixtures, holes, counterbores, countersinks, slots, pockets, bosses, standoffs, ribs, fillets, chamfers, shells, source-level joints, mating, or measurements. Also use it when the user supplies reference images or 2D technical drawings of a part to reproduce or take design intent from.
36
37Also use it when the user asks for STL, 3MF, or native GLB output from CAD geometry; load `supported-exports.md` for details. For 2D DXF drawings, use the `$dxf` skill; when a DXF projects from a 3D part, this skill owns the part and `$dxf` owns the drawing.
38
39Do not use this skill for render-only concept art, CAM toolpaths, engineering certification, FEA conclusions, architectural BIM, or freehand illustration unless the user also needs CAD geometry.
40
41## Default assumptions
42
43Use these defaults unless the user specifies otherwise. These are first-pass modeling defaults, not manufacturability, tolerance, or certification claims:
44
45- Units: millimeters.
46- Origin: per the part-type defaults in `references/positioning.md`; center of the main part or assembly when nothing better applies.
47- Base plane: XY.
48- Up/extrusion axis: positive Z.
49- Output geometry: closed, positive-volume solids unless the user requests surfaces or construction geometry.
50- STEP structure: one valid solid, a compound of solids, or a labeled assembly compound.
51- Assembly structure: fixed root part, part-local frames, named mating datums, `AssemblyHelper` relationships backed by build123d joints where applicable, explicit generated placements, and verbose native labels.
52- Small plastic enclosure wall: 2.0-3.0 mm when unspecified.
53- Cosmetic fillet: 1.0-3.0 mm when safe for local geometry.
54- M3/M4/M5 normal clearance holes: 3.4/4.5/5.5 mm unless another standard is requested.
55
56Ask one focused clarification question only when missing information makes the model impossible, fit-critical, safety-critical, or compliance-bound. Otherwise proceed with explicit assumptions.
57
58## Tools and paths
59
60The command surface (the `cadgen` console script, installed with the package):
61
62```bash
63python <model>.py # its __main__ calls the model, which builds it
64cadgen step build IN OUT # re-emit an existing STEP as a new one, with kinematics
65cadgen stl build ... # one door per mesh format; `3mf` and `glb` are the others
66cadgen step inspect ... # refs, measure, align, frame, diff
67cadgen step snapshot ... # PNG visual review packets, for STEP
68cadgen stl snapshot ... # the same, for a mesh file; `3mf` and `glb` again
69cadgen store why <model>.py # why the model is stale or current, clause by clause
70cadgen daemon status # the warm workers and the jobs they are running
71```
72
73**Scripts are RUN; commands take DOCUMENTS.** `python model.py` is the one
74source door — it writes every output the model declares and (only when the
75model declares kinematics, animation, or mesh exports) its sidecar. Every
76command above takes a `.step`/`.stl`/`.dxf` FILE, and one handed a `.py` says
77so. A door asks one question of a document: does the store have a tree for
78this file's bytes? If so it reads it; if not it compiles one from the bytes as
79a job in the pool — generated or imported alike. **A door never refuses a
80document and never runs a script.** Whether a document is behind its script
81is the model's business (`cadgen store why`), not the door's.
82
83Use the active project Python interpreter; treat `python` in examples as an interpreter placeholder. Every operational verb is a `cadgen` subcommand (`python -m cadgen.cli <verb>` is the PATH-independent equivalent). Use `cadgen <verb> --help` for the complete current interface; reference docs show recommended workflows, not every flag. Install per `requirements.txt`; `cadgen doctor <skill-dir>` verifies the installed cadgen matches this skill's pin (docs drift silently on a mismatched install).
84
85Target paths resolve from the command's current working directory, not from the skill directory. Run commands from the workspace that owns the artifacts and pass cwd-relative target paths so project CAD files never resolve accidentally under the skill directory.
86
87CAD references are `#...` selector tokens local to a target, for example `#o1.2` or `#o1.2.f1`. Pass the STEP/CAD file as a separate target argument when using CAD CLIs.
88
89## A model
90
91Generation has NO CLI. A model is a plain Python script: one parameterless
92decorated function, built by calling it from `__main__`:
93
94```python
95from cadgen import build123d as bd
96from cadgen import step
97
98WIDTH = 10.0
99
100
101@step # or @step(out="../STEP/bracket.step") to relocate the output
102def bracket():
103 return bd.Box(WIDTH, 10, 10)
104
105
106if __name__ == "__main__":
107 bracket()
108```
109
110The rules, each enforced by the decorator or the build:
111
112- **The decorator only declares; a call builds.** Importing a model module
113 never builds; a file without `if __name__ == "__main__": <model>()` never
114 builds either — always end the script that way. `python bracket.py` writes
115 `bracket.step` beside the script and the model's result into the store; an
116 unchanged model is a fast no-op. `--force` rebuilds this model only.
117- **A model takes no parameters** and its function is called with no
118 arguments. Parametric geometry lives in a plain factory the model calls
119 with its values (`def _bracket(width, thickness): ...`); another
120 configuration is another model in another file, the way two part numbers
121 are two parts.
122- **The return is a bare build123d `Shape`** — a solid, a compound, or a
123 labeled assembly compound. Never a dict, never a path.
124- **Outputs are exactly what the decorators declare.** `@step` writes the
125 `.step`; `@stl`/`@threemf`/`@glb` stacked on it write meshes. **STEP is not
126 required**: a function with only `@stl` (or `@glb`, `@threemf`) — no `@step`
127 — is a full model with the same tree, record, build and no-op, whose outputs
128 are the meshes and which writes no `.step` and no sidecar. Use it for
129 print-only parts and render assets. `references/supported-exports.md`.
130- **Decorator arguments never change the geometry.** They decide where the
131 files land (`out=`), how they are written (`mesh_tolerance=`,
132 `mesh_angular_tolerance=`) and what the sidecar declares (`kinematics=`).
133 The geometry is the return value and nothing else: a `Compound` placing
134 children is packaged as occurrences, a single solid as one component, and
135 `part`/`assembly` is read off the tree. There is no `kind=` and no bake
136 point — a posed or differently configured export is authored geometry, or
137 another model.
138- **A sidecar only when strictly necessary.** `<name>.step.json` is written
139 only when the model declares `kinematics=`; a model that declares none has
140 no sidecar, and a rebuild that dropped the declaration deletes the stale
141 file. What a model declares about its outputs lives in its record, not in
142 a file beside the geometry.
143- **One model per file, as a rule of thumb.** A model's identity is its file
144 plus its function (`plate.py::plate`); a file holding one model is named by
145 its path alone. A file MAY hold several (a small family of variants): each is
146 its own record, output and job (a sole model writes `<file>.step`; models sharing a
147 file write `<function>.step`), but
148 they share the file's closure, so editing one rebuilds them all — which is
149 why one per file is the recommendation.
150- **Composition is a call.** Import a sibling model and call it inside your
151 body (`from arm import arm` … `arm()`); it returns the child's geometry.
152 `references/step-generation.md` has the whole composition contract.
153- **`from cadgen import build123d as bd`** is the canonical import — a lazy,
154 transparent re-export of build123d (same names, same behaviour) — so the
155 freshness gate and the warm-worker handoff run before any kernel import is
156 paid. Raw `import build123d` works but costs ~2.5s on every re-run.
157- Per-run flags ride the script's argv: `--force`, `--json`, `--verbose`,
158 `--mesh-tolerance`, `--mesh-angular-tolerance`.
159
160## Composition, freshness and builds
161
162The essentials; `references/step-generation.md` has the code and the edge cases.
163
164- **Children are models you call.** A parent's body imports sibling models
165 and calls them; each call returns that child's geometry (built if stale,
166 loaded from the store if current), and the parent's result LINKS to the
167 child's — stored once, shared by every parent. Place a child with
168 `Pos/Rot/Location * child` or `child.moved(loc)`; never `child.located(loc)`
169 (it deep-copies the geometry, so the parent owns a copy instead of linking).
170- **Every build is parallel.** A child call submits the child's build and
171 returns at once; siblings build on their own workers while the body keeps
172 going; the parent waits when it first reads the geometry — normally the
173 closing `bd.Compound(children=[...])`. Nothing to configure, nothing to
174 annotate.
175- **Builds never wait on or cancel each other.** Two runs of one model both
176 run; the store keeps the result whose sources match the files as they are
177 now, so the disk ends at the newer source. Editing a child while its parent
178 builds leaves the parent finished against the child it pinned.
179- **A rebuilt part does not update the assemblies that use it.** Dependency
180 is pull: rebuild the parent (`python assembly.py`) to pick up a child's
181 change. A child edit that yields identical geometry leaves parents current.
182- **What a rebuild tracks — models by result, constants by value, functions by
183 file.** Importing a model function tracks that model by its result;
184 importing a module-level literal (`from plate import WIDTH`) tracks the
185 value; importing anything else from a file (a helper function, a `bd.`
186 object) makes that whole file part of your model's source, so any edit to it
187 rebuilds you. Shared constants may live in a model file or in `lib/`.
188- **The environment is not an input.** Model and `lib/` code takes no
189 parameter from `os.environ`, the working directory, the current time or a
190 random source: the gate tracks source by hash, constants by value and children by
191 result, and cannot see any of those — a value that changes geometry through
192 them leaves a stale result reading as current. A configuration is a factory
193 argument; another configuration is another model.
194- **A mirrored part is its own model.** STEP cannot express a reflection, so
195 a right-hand part is a separate model file calling the same factory with
196 `mirror=True` (or mirroring the factory's result), not a mirrored child.
197- **`read_step` files are inputs, not models.** Replacing the file makes the
198 reader stale. To make an imported part first-class, wrap it:
199 `@step def servo(): return read_step(...)`.
200- **`cadgen store why <model>.py`** is the freshness door: it prints the
201 gate's verdict clause by clause (record, closure files, constants, each
202 child's pinned vs current tree, tree objects, declared outputs). Reach for
203 it whenever a model did or did not rebuild when you expected it to.
204
205**Workers.** A warm daemon is on by default: each model gets a persistent
206worker (a second, an *extra*, when the model is asked for while already
207building); spares stand by so a new model never pays the import; idle workers
208unbind after ten minutes. Running builds are limited to one per core
209(`CADGEN_JOBS` overrides); a parent waiting on its children holds no slot.
210`CADGEN_DAEMON=0` uses transient workers spawned for that one run — still
211parallel, still the same store — and is the mode for tests and debugging.
212`cadgen daemon status` lists workers, spares and the running/queued jobs.
213
214**Debugging notes.** Do not alternate `CADGEN_DAEMON=0` and daemon runs of one
215model while a daemon build of it is in flight (the two are unbrokered; each
216publishes what it built, and the publish rule keeps the newer source). **One
217project, one store.** A build under another `CADGEN_CACHE_DIR` (a temp store,
218a test) rewrites the same output files; the first store's records then see
219outputs whose bytes they did not write, so its gate reports the model stale
220(`output changed: …`) and every parent `child stale: …` — nothing is wrong,
221the two stores simply disagree, and the next build under either settles it.
222**Module bodies stay cheap.** A model file is imported on every rerun, before
223the gate: a module-level `read_step` (computing a layout from a vendor STEP at
224import) pays the kernel and the parse each time even when the model is
225current — call `read_step` inside the body or a function it calls; the
226`hint:` printed on such a run names the import site. **Resets, smallest
227first:** `python model.py --force` rebuilds one model now; `cadgen store
228forget <model.py>` drops its record so the *next* run rebuilds it (children
229untouched); `cadgen store forget <file.step>` drops the tree entry for that
230file's bytes so the next open or door call compiles it again; `cadgen store
231gc` sweeps unreachable objects; **clearing the store (`rm -rf
232~/.cache/cadgen`, or `$CADGEN_CACHE_DIR`) is always safe** — every model
233reads as stale and rebuilds, and no project file is touched. The gate has no
234cadgen-version clause, so a model built by a cadgen with a bug stays current
235after the fix: `forget` the affected models (or the parents that link them),
236or clear the store.
237
238**The store** (`~/.cache/cadgen`, `CADGEN_CACHE_DIR` overrides) holds
239`objects/` — immutable, content-addressed components and trees — and `index/`
240— the per-model records the gate reads, the op memo, and the mesh ledger. It
241contains only derived results. The full contract is `STORE.md` in the
242installed `cadgen` package.
243
244## Streams, progress and failures
245
246**Streams.** stdout carries the result; stderr carries progress, timing, and failures. A model run prints `<outcome> <document path>` on stdout (`built`, `current`, or `skipped-peer` when a concurrent build of the same model finished first), and the two streams never interleave, so `2>/dev/null` leaves a clean parseable result and `>/dev/null` leaves a readable log. JSON on stdout is always compact; pipe through `jq .` to read it. For machine-readable output: model runs, the `build` doors (`step`, `stl`, `3mf`, `glb`) and `snapshot` take `--json`; `inspect` already emits JSON and takes `--format text` for prose. A model run's `--json` line carries `outcome`, `document` and `tree` (the result's hash). `--verbose` adds stage timing (and full tracebacks) on stderr. Output volume does not grow with model size.
247
248**The build tree.** On a terminal, stderr shows the graph as the body's child calls reveal it — one refreshed block, each model `submitted`, `queued`, `building · <phase> n/total`, `current`, or `✓ <time>`, finished subtrees folded to one line. With `--json` or a non-TTY, one JSON line per model transition (`model`, `parent`, `state`, `phase`, `progress`, `elapsed`) on stderr replaces the drawing; the result line on stdout comes last. After publishing, the root re-runs its gate once and says `already stale: <child> changed during the build; rerun` if it did.
249
250**Reporting progress from a model.** A long build spends most of its wall time inside
251the model body. Import the reporter — it binds to whichever build is running, and does
252nothing when there is none:
253
254```python
255from cadgen import report, track, step
256
257@step
258def housing():
259 report("bearing housing") # name the current phase
260 for rib in track(ribs, label=lambda r: r.name): # count through a work list
261 ...
262```
263
264`track()` advances the count when an item's work is DONE and labels the item in flight, so a
265reader sees "3 finished, now on engines". The phase surfaces on the model's line in the build
266tree and — through the daemon's job ledger — as `compiling · <phase>` in the CAD Viewer for any
267document the job writes, whoever started the job. Without this a multi-minute assembly says
268nothing during its longest phase.
269
270**Failures** print the exception and the frames *in your own model*, not the runtime's:
271
272```text
273[cadgen] FAILED: ValueError: bad radius
274[cadgen] src/widget.py:9 in bracket
275[cadgen] return _profile(radius)
276[cadgen] re-run with --verbose for the full traceback
277```
278
279A failed child raises at the site in the parent that first read its geometry, naming the call and carrying the child worker's output.
280
281## Snapshots
282
283**Snapshot inputs.** One format, one door, and the same `TARGET [OUT]` grammar `build` uses. `cadgen step snapshot` renders `.step`/`.stp` documents — nothing else (a model script is refused by name: run `python <model>.py`, then snapshot the STEP it wrote). A mesh file goes to its own door: `cadgen stl snapshot`, `cadgen 3mf snapshot`, `cadgen glb snapshot`. A mesh has no CAD topology, so the STEP-only options (`--focus`/`--hide`, `--display`, `--kinematics`, `--animation`/`--time`, `--mode section`) are not on those commands at all — check `--help` and the door tells you what it can do. Robot descriptions belong to the `urdf`/`srdf`/`sdf` skills. Each door refuses what is not its own format, and names the door that takes it.
284
285```bash
286cadgen step snapshot STEP/bracket.step tmp/review.png
287cadgen stl snapshot STL/bracket.stl tmp/mesh.png
288```
289
290**Snapshot output.** The path you name is the path you get:
291
292```bash
293cadgen step snapshot STEP/bracket.step tmp/review.png
294# then Read tmp/review.png
295```
296
297OUT is written exactly as given (a relative path against the current working directory), cleared before the render and written atomically after it — so reuse one name while iterating, name the iterations (`tmp/before.png`, `tmp/after.png`) when you need to compare, and treat a missing file as the failure signal: there is never an older image at the path to mistake for output. A directory (`tmp/`) is the don't-care case and gets a generated timestamped name inside it, printed on the `saved snapshot:` line. The same rule applies per output in a JSON packet.
298
299**Theme and display.** Theme settings live under one `--theme`, display settings under one `--display` — the viewer's two tabs, one option each. The default theme is `snapshot`: Workbench Light with the ground grid and origin axis removed, because in a still image those read as geometry rather than as orientation. Pass `--theme workbench-light` for the viewer's own look. Projection is a theme trait honoured by every format, so a snapshot frames the same way the viewport does.
300
301## Required workflow
302
303Scale depth to the task: a simple part needs a short brief and few spec-driven checks; assemblies and fit-critical work need full positioning and alignment validation.
304
3051. **Classify the task.** New part, new assembly, source modification, direct STEP/STP inspection, reference selection, measurement/alignment check, snapshot review, or mesh output request.
3062. **Load only the needed references.** Use the triggers below instead of reading the whole reference set.
3073. **Write a natural-language CAD brief.** Extract dimensions, units, coordinate convention, feature intent, output paths, assumptions, and validation targets from all provided inputs — prose, reference images, technical drawings. Use `references/cad-brief.md`.
3084. **Check named purchasable components.** When an assembly includes named off-the-shelf actuators, servos, motors, electronics boards, connectors, or other purchasable components, search `$step-parts` before creating simplified placeholder geometry. If no exact match is found, record the miss and then use a documented bounding volume.
3095. **Plan before coding.** Define the constants and factory arguments, intent labels, source paths, expected bounding boxes, and any mating/positioning datums before editing.
3106. **Edit source, not generated artifacts.** Author a plain `.py` model script with one decorated function (shared code lives in plain helper modules; see `references/step-generation.md`). When a model script exists, run IT, never hand-edit its exported STEP. Imported STEP/STP files (no script) are handed straight to `cadgen step inspect`, `step snapshot` and the mesh doors — each compiles whatever it needs on demand.
3117. **Build explicit targets.** Run each model script directly (`python <model>.py`); do not sweep directories. A parent builds its children as it calls them, so running the root is the whole build. Declare `@stl`/`@threemf`/`@glb` outputs on the model, or run `cadgen stl|3mf|glb build` for one-off mesh files. For multi-model project structure, read `references/project-layout.md`.
3128. **Validate geometrically.** Run `cadgen step inspect refs <step-or-cad-target> --facts --planes --positioning` as the baseline, then verify the dimensions and relationships the user's spec calls out with targeted `measure`, `align`, `frame`, or `diff` checks. Run `cadgen step inspect validate <step-or-cad-target>` for geometry soundness: `refs --facts` reports counts and bounds, and its `ok` field covers ref resolution only — an open shell and an inverted solid both pass it.
3139. **Snapshot the primary STEP — snapshot validation is mandatory.** After creating or visibly updating a STEP/STP part or assembly, ALWAYS run `cadgen step snapshot` against it and review the output; deterministic checks passing is not a reason to skip. The only skip cases are documented in `references/snapshot-review.md` (no visible geometry changed, or no valid artifact exists); report the reason when skipping. A mesh-only model is reviewed with its format's snapshot door.
31410. **Repair and rerun.** If a check fails, change the smallest responsible source section, rebuild, and rerun the failed validation.
315
316## Handoff
317
318After completing CAD work that creates or modifies `.step`, `.stp`, `.stl`, `.3mf`, or native `.glb` artifacts, you must ALWAYS hand the explicit file path(s) to `$cad-viewer` when that skill is installed. `$cad-viewer` must start CAD Viewer if it is not already running and return link(s) to the relevant created or updated file(s); include those live viewer link(s) in the final response. If `$cad-viewer` is unavailable or startup fails, report that and rely on CLI inspection plus snapshots instead of silently omitting the handoff. This rule applies to every workflow in this skill, including mesh outputs.
319
320When verification snapshots are generated, include the saved PNG snapshot(s) in the final response. If no snapshot applies, or if snapshot generation fails, say why and report the deterministic validation that still ran.
321
322## Non-negotiables
323
324- The model script is the source of truth. Every written file — STEP/STP, STL, 3MF, GLB, the sidecar — is a derived output; edit and rerun the script, never the outputs. Where a model declares a STEP, the STEP is the artifact that is inspected and snapshotted.
325- Use named constants, closed solids, verbose native build123d labels, and source-controlled geometry intent.
326- Author assembly positioning in source. `references/positioning.md` is authoritative for `AssemblyHelper`, build123d joints, explicit `Location` transforms, and alignment validation.
327- Do not use `git status`, `git diff`, or file-size churn as CAD comparison for large exported STEP/STP, GLB, STL, or 3MF artifacts. Compare source changes, `cadgen step inspect` summaries, or snapshots instead; use path-limited git status only for bookkeeping.
328- Report only checks that actually ran or are directly supported by tool output.
329
330## Progressive references
331
332Load these files only when their trigger applies:
333
334- `references/cad-brief.md` — converting prose, reference images, and technical drawings into a CAD brief.
335- `references/build123d-modeling.md` — build123d modeling patterns, topology, selectors, features, labels.
336- `references/step-generation.md` — the model contract in full: composition (linked children, `read_step` inputs), what a rebuild tracks, mirrored parts, factories, the daemon and workers, imported STEP/STP files, and post-build steps.
337- `references/inspection-and-validation.md` — validation sequence, selector refs, facts, planes, measurements, alignment, diff, frame, and validation reporting.
338- `references/snapshot-review.md` — mandatory snapshot policy, packet sizing, targeted views, and converting visual findings into geometry checks.
339- `references/positioning.md` — part-local datums and origins, assembly transforms, build123d joints, CLI alignment validation, and positioning reports.
340- `references/kinematics.md` — articulating, posing, or animating a STEP model: typed mates (`kinematics=` on the decorators — mates, couplings, pose presets, export-at-pose), and the render module beside the document (`<name>.step.js`: the choreography contract, loaded by the viewer, read by no build).
341- `references/supported-exports.md` — STL/3MF/native GLB outputs: declared exports, mesh-only models, and the `cadgen stl|3mf|glb build` doors.
342- `references/repair-loop.md` — diagnosis and repair procedures.
343- `references/project-layout.md` — project structure for anything bigger than a couple of loose models: `src/` for model scripts and shared code, format folders (`STEP/`, `DXF/`, `STL/`) for raw outputs, naming, and commit policy; `references/project-template.md` is the copyable exemplar. Read them when a project has more than a couple of models or when asked how to organise CAD code and artifacts.
344- `references/migrations.md` — the tooling disagreeing with a model you believe is correct: recognizing a project authored against an older cadgen, and where the migration guides live.
345
346Final responses should include generated files, returned `$cad-viewer` viewer links, verification snapshots, validation actually run, assumptions, and caveats. Use `references/inspection-and-validation.md` for report structure.