Install
openclaw skills install skills-sh:earthtojake/text-to-cad/cadCAD 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…
openclaw skills install skills-sh:earthtojake/text-to-cad/cadProvenance: 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.
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
If a command then fails with ImportError: DLL load failed while importing OCP,
run cadgen doctor: it probes the CAD kernel and names the cause (on Windows 11,
Smart App Control blocking the unsigned kernel module) and what to do. That is
a machine setting, not something to fix by reinstalling.
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 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.
Use these defaults unless the user specifies otherwise. These are first-pass modeling defaults, not manufacturability, tolerance, or certification claims:
references/positioning.md; center of the main part or assembly when nothing better applies.AssemblyHelper relationships backed by build123d joints where applicable, explicit generated placements, and verbose native labels.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.
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 with durable annotations
cadgen stl build ... # one door per mesh format; `3mf` and `glb` are the others
cadgen step inspect ... # refs, measure, align, frame, diff, interfere, validate
cadgen step snapshot ... # PNG visual review packets (and clip videos), 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, for STEP files
with kinematics, intrinsic material finishes, or animation, their 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) and that the CAD kernel loads.
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.
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:
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.def _bracket(width, thickness): ...); another
configuration is another model in another file, the way two part numbers
are two parts.Shape — a solid, a compound, or a
labeled assembly compound. Never a dict, never a path.@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.out=), how they are written (mesh_tolerance=,
mesh_angular_tolerance=) and what the sidecar declares (kinematics=,
materials=, animation=).
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.<name>.step.json is written
only when kinematics=, materials=, or animation= declares useful
annotations. It contains resolved declarations bound to the saved STEP's
byte hash; a model with none has no sidecar. Rebuilding removes dropped
sections and deletes an empty sidecar. Mesh-output declarations live in the
store record.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.from arm import arm … arm()); it returns the child's geometry.
references/step-generation.md has the whole composition contract.@memo. This optional decorator
reuses parameterized geometry inside a model without declaring files. Use
immutable value arguments and deterministic helpers, with no I/O, reporting,
child builds, process state or dependency mutation. Keep ordinary factories
for cheap work. Read the memoization section in references/step-generation.md
before using it; no cache/session utilities are needed.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, and so
does any module-level bd.<anything> — a constant, a default argument, or an
annotation such as -> bd.Shape, which Python evaluates at def time. Put
from __future__ import annotations at the top of a file whose factories are
annotated.--force, --json, --verbose,
--mesh-tolerance, --mesh-angular-tolerance.The essentials; references/step-generation.md has the code and the edge cases.
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).bd.Compound(children=[...]). Nothing to configure, nothing to
annotate.python assembly.py) to pick up a child's
change. A child edit that yields identical geometry leaves parents current.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/.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.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. 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:
[python widget.py] FAILED: ValueError: bad radius
[python widget.py] src/widget.py:9 in bracket
[python widget.py] return _profile(radius)
[python widget.py] src/widget.py:4 in _profile
[python widget.py] raise ValueError("bad radius")
[python widget.py] re-run with --verbose for the full traceback
Every line is prefixed with the command that produced it — [python <script>] for a
model run, [cadgen step snapshot] and the like for a door.
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.
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 focus/hide, kinematics, animation/video, section mode, exploded views and CAD-edge display modes remain STEP-only. Normal-CAD camera and format-neutral Display settings work across the snapshot doors; Render carries its own camera and presentation. 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). Conflicting Render/CAD controls are rejected before OUT is touched. After that check, OUT is cleared before input resolution and written atomically after rendering, so a later failure leaves no stale image from an earlier successful run. Reuse one name while iterating, and name the iterations (tmp/before.png, tmp/after.png) when you need to compare. 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.
Normal and Render snapshots. With no --render, snapshots use deterministic light CAD lighting, an orthographic isometric camera, normal shaded-with-edges display, and no grid or axis guides. --render light|dark opts into the photographic scene: perspective, shaded authored materials, softbox lighting, ground shadows, and no edges or guides. --render '{}' chooses the Light studio. It also accepts compact Render JSON or a path to it: studio is light or dark, quality is preview or final, exposure is -5..5, lighting controls rotation, size, and fill, backdrop controls color, transparent, ground, and groundPlacement (lowest, the default, puts the translucent floor at the model minimum; origin pins it to the document's Z=0 plane), and camera controls the photographic view. Final quality is the default and captures at L3 with 2x render scale; preview uses L1 and 1x. A perspective camera may set focalLength to 20..200 mm; projection and the positive orthographicHalfHeight view scale also belong in camera JSON. Top-level --camera and --display configure normal CAD snapshots and cannot be combined with Render. A packet output's camera remains an explicit per-image override.
Selection, kinematics and top-level quality.tessellation are also normal-CAD state and
cannot be combined with Render. Animation frames and sequences remain composable with Render.
Photographic Render supports view mode only.
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.
references/cad-brief.md.$step-parts before creating simplified placeholder geometry. If no exact match is found, record the miss and then use a documented bounding volume..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.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.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.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.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.
references/positioning.md is authoritative for AssemblyHelper, build123d joints, explicit Location transforms, and alignment validation.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.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 embedded animation= JavaScript module. Also how a clip leaves the repo: snapshot --video for pixels, glb build --animation for a GLB that carries the motion itself.references/supported-exports.md — STL/3MF/native GLB outputs: declared exports, mesh-only models, the cadgen stl|3mf|glb build doors, and glb build --animation (a clip baked into the file as glTF animation).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.
cc4e78482e05