Import CAD projects and cad-router v1

This commit is contained in:
Jerry
2026-07-22 19:38:36 +08:00
parent e4e0199bd5
commit af9f630c11
2253 changed files with 1994207 additions and 0 deletions
@@ -0,0 +1,155 @@
# build123d modeling patterns
Read this file when writing or repairing build123d Python source.
## Modeling objective
Create a valid STEP-ready BREP model, not a visual mesh. Prefer closed solids, explicit labels, and stable parametric dimensions. Define `gen_step()` returning the STEP-ready shape or labeled compound; the CLI owns output paths (see `step-generation.md`).
## Design strategy
Decide how the part is constructed before writing geometry code:
- **Choose the construction that makes the spec's dimensions direct parameters.** Profile-driven shapes get one closed sketch plus `extrude`/`revolve`/`sweep`/`loft`; block-and-feature parts get a base solid plus subtractive features. Prefer whichever construction lets the user's controlling dimensions appear as named parameters instead of derived values.
- **Decide part vs assembly before modeling.** Bodies that are separately manufactured, purchased, or movable belong in a labeled assembly (see `positioning.md`); monolithic manufacturing intent gets a single fused solid. Avoid unlabeled compounds of solids — multi-body output without occurrence labels loses traceability in inspection and viewer review.
- **Pick the origin and orientation from the functional datum before sculpting.** Model on the mating interface, mounting plane, or symmetry axis; see `positioning.md` for part-type origin defaults.
- **Order operations so fragile steps come last and failures localize.** Base solid → major additions → subtractive features → shell → through-wall holes → fillets and chamfers last. Fillets are the most failure-prone operation and every boolean invalidates selectors, so postpone them. Structure the source so each feature is a named step — a per-feature function or a distinct intermediate variable — so a failed operation points at exactly one feature and a parameter change touches one obvious place.
- **Overshoot boolean tools.** Extend cutting tools past the faces they enter and exit; for through-cuts, go roughly 1 mm beyond both faces. Coincident or coplanar tool/target faces are a classic kernel failure. Cut repeated or patterned features in one combined operation.
- **Sanity-check proportions before generating.** Compare the expected bounding box against the real-world object, wall thickness against overall size, and feature positions against edges and neighboring features. Order-of-magnitude and collision errors pass geometric validation but fail visual review.
## Topology stack
Think in this order:
```text
Vertex → Edge → Wire → Face → Shell → Solid → Compound
```
For assemblies, use these repo topology terms consistently:
- **Occurrence**: a placed node in the assembly tree. An occurrence has a parent, transform, path, and user-facing role such as `lid` or `m3_screw:front_left`.
- **Shape**: an exported geometry/body inside an occurrence. Shape rows own topology; faces and edges belong to a shape, and the shape belongs to an occurrence.
- **Face/edge**: selectable topology owned by a shape. Do not assume arbitrary faces or edges have persistent intent labels; inspect them by occurrence, shape, ordinal, surface/curve type, and measured geometry.
When inspecting topology, follow `assembly occurrence -> shape/body -> faces -> edges`. Every face/edge row should be traceable through both `occurrenceId` and `shapeId`.
For normal STEP output, return one of:
- a valid `Solid`
- a compound of valid solids
- a labeled assembly compound
Avoid returning loose wires, open faces, or construction surfaces unless the user explicitly requested them.
## Parameters first
Put meaningful dimensions in named variables:
```python
width = 80.0
depth = 50.0
thickness = 6.0
hole_diameter = 4.5
hole_offset_x = 30.0
hole_offset_y = 17.5
```
Avoid burying important numbers inside geometry calls.
## Coordinate system
Declare or comment the convention:
```text
Origin: center of primary part or chosen mating datum
XY: main base/sketch plane
+Z: up/extrusion direction
```
Use `Location`, `Plane`, and `Axis` intentionally. For positioning-sensitive tasks and source-level assembly relationships, read `positioning.md`.
## Builder contexts
Use the context that matches the geometry:
```python
with BuildLine() as path:
...
with BuildSketch() as profile:
...
with BuildPart() as part:
...
```
Typical flow:
```text
curves/paths → sketches/profiles → solids/features → labels → STEP
```
## Selection practices
Avoid fragile topology order when possible. Select by:
- axis or normal
- location or bounding position
- plane grouping
- feature intent
- stable construction plane
- inspected local selector ref for downstream validation
For source operations, prefer robust selectors such as top/bottom by axis or position rather than arbitrary list indexes.
## Assemblies and positioning
For assemblies, keep this file focused on BREP modeling patterns and labels. Use `positioning.md` as the single source of truth for:
- part-local coordinate conventions
- when to use `cadpy.assembly.AssemblyHelper`, build123d joints, or explicit `Location` transforms
- `connect_to()` behavior
- CLI `inspect align` as read-only selector-pair alignment validation
- frame, measure, and positioning report expectations
## Labels and assemblies
Label every exported part and assembly child with native build123d labels. Prefer concise intent labels through `cadpy.assembly` helpers:
```python
from cadpy.assembly import AssemblyHelper, label_shape
asm = AssemblyHelper("electronics_enclosure")
base = asm.add(make_base(), "base")
lid = asm.add(make_lid(), "lid")
boss = label_shape(Cylinder(radius=3.0, height=12.0), "m3_boss", "front_left")
```
Do not prefix labels with topology categories like assembly, component, feature, datum, mate, or hardware. The assembly tree and topology inspection already expose those structural categories. Use labels for the intent topology cannot reliably infer: role, placement, interface, repetition, or mating purpose. Feature labels survive STEP export best when the feature remains a labeled child shape in a `Compound`; boolean-subtracted or fused feature history should be represented by source parameters, named datums, and validation refs instead of assumed persistent feature labels.
Label for inspection:
- Label the root assembly.
- Label every exported part, subassembly/module, and repeated component occurrence.
- Use occurrence labels for assembly role and placement, especially repeated parts: `m3_screw:front_left`, `m3_screw:rear_right`.
- Use shape labels for retained exported geometry/body roles where useful.
- Use feature/datum labels only when that geometry remains exported as a child shape.
- Use named mate datums for source-level positioning intent, then validate the exported STEP topology and occurrence frames.
Occurrence and shape labels are exported through STEP names and surfaced in `STEP_topology` when available. The viewer uses occurrence labels for assembly/tree references and shape labels for shape references. Faces and edges inherit their context from `occurrenceId` and `shapeId`; do not promise persistent face/edge intent labels unless explicit tested support exists.
For repeated parts, keep occurrence labels, transforms, or joint connections explicit and inspect frames/positioning after generation.
## Common failure modes
- Fillet radius larger than local edge geometry.
- Open sketch profile produces invalid or missing face.
- Face selector changes after a boolean or fillet.
- Part origin is arbitrary and later alignment checks become ambiguous.
- Source-level joints are treated as if they were persistent STEP constraints rather than one-time source placement operations.
- Joint labels are missing, duplicated, or attached to the wrong local datum.
- `.connect_to()` fixes the wrong side of the relationship, moving the part intended to remain fixed.
Use `repair-loop.md` when generation or validation fails.
@@ -0,0 +1,130 @@
# CAD brief
Read this file when converting a user's request — prose, reference images, technical drawings, or a combination — into a CAD brief. The brief is an internal note-taking scaffold; do not ask the user to fill it out, and do not require the user to provide JSON. If the user supplied JSON voluntarily, extract the same information but continue the workflow in prose notes and build123d source.
## Goal
Convert the request into an actionable modeling brief before writing source or running tools. Every input modality funnels into the same brief; the downstream workflow does not change.
The brief should answer:
- What is being modeled, and is it a part, assembly, modification, inspection task, or secondary output request?
- What dimensions and units are specified, and which missing dimensions are inferable?
- Which features are required?
- Which faces, axes, origins, joints, or interfaces control positioning?
- What output files are requested?
- What must be validated before success is reported?
When inputs conflict, dimensioned sources win over image proportions. When two dimensioned sources conflict — prose says one value, a drawing callout says another — flag the conflict instead of silently choosing.
## Reference images
An image without stated dimensions is design intent, not a spec:
- Establish scale from one stated dimension or a known object in frame; if neither exists and fit matters, that is the one clarification question to ask.
- Estimate remaining proportions from the image and record them as assumptions like any other inferred value.
- Distinguish reproduction ("model this part") from inspiration ("something like this") in the brief; reproduction raises fidelity expectations, inspiration leaves freedom.
- For reproduction, plan a snapshot from the reference image's viewpoint and compare it against the image during visual review.
## Technical drawings
A drawing is a dimensioned contract. Extract it systematically:
- Read the title block and notes first: units, projection convention, revision, disclaimers.
- Identify which view is which — front/top/side, sections, details, iso — and which model axes each maps to before extracting numbers. Trust callouts and view labels, not layout conventions. Section views are the source of truth for internal features: bores, counterbore and blind-hole depths, wall sections.
- Convert every dimension callout into a named parameter and a validation target. Multiplicity (`4X`), `TYP.`, and thread/counterbore/countersink callouts expand into features plus checks.
- Never scale undimensioned geometry off the image. Derive it from stated dimensions when constrained; otherwise assume and report.
- Cross-check features across views; when views disagree, prefer the dimensioned view and flag the conflict.
- Success for a drawing-driven model: every drawing dimension is either verified by `measure`/`refs` after generation or explicitly reported as not verified.
## Brief format
Use concise Markdown notes, not a user-facing structured schema:
```text
CAD brief:
- Model: <part or assembly name>
- Task type: <new part, assembly, modification, inspection, secondary output>
- Inputs: <reference images or drawing views used; omit when prose-only>
- Units: <explicit or assumed>
- Coordinate convention: <origin, base plane, up axis>
- Overall dimensions: <width/depth/height or equivalent>
- Functional features: <holes, slots, ribs, bosses, pockets, shells, text, etc.>
- Manufacturing assumptions: <only when relevant>
- Positioning/mating: <interfaces, datums, child placements, joints, alignment rules>
- Paths: <generator .py, STEP target, secondary outputs if requested>
- Validation targets: <bbox, solid count, labels, spec-driven measurements, refs>
- Assumptions: <only meaningful inferred choices>
```
## Example: simple part
User says:
```text
Make a 100 mm by 60 mm by 6 mm mounting plate with rounded corners, four M4 clearance holes 10 mm in from the corners, and a 20 by 12 mm rectangular cutout in the center.
```
Agent brief:
```text
CAD brief:
- Model: mounting_plate, single STEP part.
- Units: millimeters.
- Origin: center of plate; base plane XY; +Z is thickness direction.
- Body: rounded rectangular plate, 100 × 60 × 6 mm.
- Corner radius: not specified; assume 3 mm.
- Holes: four 4.5 mm M4 clearance through-holes, 10 mm in from each corner.
- Cutout: centered rectangular through-cut, 20 × 12 mm.
- Validation: one positive-volume solid, bbox 100 × 60 × 6 mm, four holes, one center cutout, label mounting_plate.
```
## Example: assembly
User says:
```text
Design a two-piece enclosure, 120 by 80 by 35 mm, with a lid that sits on top and four screw bosses aligned between base and lid.
```
Agent brief:
```text
CAD brief:
- Model: enclosure assembly with base and lid.
- Units: millimeters.
- Assembly origin: center of enclosure footprint; +Z upward.
- Base: hollow lower shell, exterior 120 × 80 mm footprint; height derived from total height minus lid thickness.
- Lid: separate plate on top; assume 3 mm lid thickness unless user gave another value.
- Bosses: four aligned screw bosses; assume M3 unless unspecified dimensions make this unsafe.
- Positioning: base top face and lid bottom face are mating datums; screw axes must align; native build123d joints may be used if they clarify reusable mount points or motion.
- Validation: labeled base and lid children, bbox near 120 x 80 x 35 mm, aligned hole/boss axes.
```
## Clarification policy
Ask one focused question only when the missing information affects fit, safety, compliance, or makes the part impossible to model. Otherwise proceed with assumptions and report them.
Ask when:
- No dimensions are provided for a physical object, and no scale reference exists in the supplied images.
- A mating interface is described but the mating geometry is unspecified.
- The part is safety-critical, load-bearing, pressure-bearing, medical, or compliance-bound.
- The requested output depends on an absent source file or missing imported geometry.
Do not ask when:
- A default clearance hole standard is sufficient.
- A cosmetic fillet radius can be safely assumed.
- Origin/orientation can be chosen and reported.
- The user is asking for a conceptual first-pass CAD model.
## Success criteria
A brief is ready for modeling when it contains enough information to define:
- source file path and STEP target path
- units and local coordinate system
- named parameters
- feature plan and labels
- expected bounding box or key measurements
@@ -0,0 +1,143 @@
# Inspection and validation
Read this file for every generated STEP artifact and whenever the user asks for geometry facts, references, dimensions, mating, diffing, or frame inspection.
## Principle
Deterministic geometry checks decide pass/fail; mandatory snapshot review (see `snapshot-review.md`) catches semantic errors the deterministic checks did not encode. Scale the deterministic checks to the user's spec: every dimension, clearance, or relationship the user specified — including dimensions taken from a technical drawing — must be verified with `measure`, `align`, or `frame`. The facts/planes/positioning baseline runs for every generated artifact regardless of spec.
## Tool
The launcher lives in the CAD skill directory:
```bash
python scripts/inspect {refs|diff|frame|measure|align|worker|batch} ...
```
Inspection targets resolve from the command cwd; pass cwd-relative target paths. Common data-output flags: `--format json|text` (default is machine-readable), `--quiet`, `--verbose`.
Accepted target forms:
```text
path/to/entry
path/to/entry.step
```
Selector refs are local to the STEP/CAD entry target passed to the command. They do not include file paths:
```text
#o1.2
#o1.2.f1
#f1
```
Pass selector refs as `#...` tokens. The STEP/CAD file path or entry target is a separate CLI argument.
## Validation sequence
1. Generation completed and the STEP/STP file exists.
2. `refs --facts --planes --positioning` confirms scale, labels, major planes, and placement-ready references. Run this for every generated artifact.
3. Spec-driven checks: `measure` for every user-specified dimension, offset, or clearance; `align` for interfaces that should be flush or centered; `frame` for orientation and occurrence-placement expectations; `diff` for modifications that could affect unrelated geometry.
4. Snapshot the primary STEP/STP per `snapshot-review.md`, then convert every visual concern into a deterministic geometry check before it becomes a validation claim.
## Reference discovery
Compact facts and planes:
```bash
python scripts/inspect refs path/to/model.step \
--facts --planes --positioning
```
Detailed selector inspection:
```bash
python scripts/inspect refs path/to/model.step '#selector' \
--detail --positioning
```
Topology enumeration, only when needed:
```bash
python scripts/inspect refs path/to/model.step --topology
```
Plane options:
```bash
--plane-coordinate-tolerance FLOAT
--plane-min-area-ratio FLOAT
--plane-limit INT
```
Use lower plane limits and compact facts for normal validation. Use topology enumeration only for selector discovery, complex debugging, or when a feature cannot be verified through facts/planes/measurements; it can be expensive on large models.
## Measurement checks
Use `measure` for bounding distances, clearances, offsets, part spacing, plate thickness, hole-to-face distances, and alignment verification.
```bash
python scripts/inspect measure path/to/model.step \
--from '#selector_a' \
--to '#selector_b' \
--axis x
```
Axis may be inferred when possible, but specify `x`, `y`, or `z` for deterministic checks.
## Alignment checks
Use `align` when two exported STEP references should be flush or centered. It returns a translation delta between the selected refs; apply any required correction in the build123d source (see `positioning.md`), regenerate, and re-inspect.
```bash
python scripts/inspect align path/to/assembly.step \
--moving '#moving_selector' \
--target '#target_selector' \
--mode flush \
--axis z
```
## Frame inspection
Use `frame` to validate occurrence transforms and selected-reference world frames:
```bash
python scripts/inspect frame path/to/model.step '#selector'
```
Frame output is useful for assemblies, part-local-to-world conversion, and placement debugging.
## Diff checks
For modification tasks, compare before and after artifacts:
```bash
python scripts/inspect diff path/to/before.step path/to/after.step --planes
```
Use diff when a repair, feature addition, or source edit could affect unrelated geometry.
## Validation report content
Report only checks that were actually run or directly supported by tool output. If an important selector was inspected, return the local selector ref beside the owning CAD Viewer link.
Use this structure:
```text
Validation:
- STEP generation: passed/partial/failed
- Solids/assembly: <counts and labels>
- Bounding box: <dimensions and units>
- Major planes/refs: <summary>
- Positioning: <frame/measure/align results if relevant>
- Feature checks: <holes, cutouts, bosses, etc.>
- Visual review: `$cad-viewer` viewer link returned; CAD `scripts/snapshot` PNG/GIF included or skipped with reason; follow-up geometry checks for any visual findings
```
Do not claim:
- structural safety
- process certification
- tolerance compliance
- manufacturability beyond geometric plausibility
unless the relevant analysis or manufacturing data was explicitly performed.
@@ -0,0 +1,138 @@
# CAD parameters
Read this file when the user asks to parameterize or animate a STEP model, or when designing or reviewing CAD source parameters, `.step.js` sidecar parameters, CAD Viewer controls, or animation controls.
## Principle
Parameters are part of the model contract. A good parameter makes design intent explicit, maps to named geometry or motion, stays inside a valid range, and gives both users and LLMs enough context to predict what changing it will do.
Prefer parameter logic that preserves the mechanism or part constraints over logic that only looks plausible from one camera angle.
## Parameter Brief
Before coding, write a compact internal parameter brief:
- What geometry or motion each parameter controls.
- Units, defaults, min/max, step size, and whether the value is dimensionless.
- Which named features, datums, pivots, axes, faces, or local selector refs each parameter affects.
- Which values are independent inputs and which are derived from constraints.
- What validation proves the parameter is correct.
For assemblies and mechanisms, identify fixed pivots, moving pivots, link lengths, gear ratios, axes, joint limits, and branch choices before creating controls.
## Naming
Use snake_case semantic names that describe intent, matching the build123d Python source convention:
- Prefer `wall_thickness`, `bearing_clearance`, `hinge_angle_deg`, `lid_open`, `gear_ratio`, `link_travel`.
- Avoid names like `offset2`, `magic_scale`, `fix_angle`, `slider_a`, unless the source model itself uses a meaningful matching term.
- Encode units in names only when the value could otherwise be ambiguous, such as `_deg`, `_sec`, or `_mm` suffixes.
- Keep sidecar parameter ids aligned with the Python source parameters they mirror, and keep source constants, manifest feature ids, UI labels, and comments aligned enough that an LLM can trace a control to geometry.
- Module schema field names such as `schemaVersion`, `manifest.step.path`, and `durationSeconds` are fixed by the step-module schema; the snake_case convention applies to the parameter and feature ids you define.
For STEP sidecars, strongly prefer an explicit target link in the module manifest:
```js
export default {
manifest: {
schemaVersion: 1,
step: {
path: "models/path/to/model.step"
}
}
};
```
`manifest.step.path` must be a workspace-relative path, never an absolute path, URL, or path with `..` segments. This link is provenance for humans and tools, not a freshness contract; do not add hashes or staleness checks to STEP parameter modules. Keep the sidecar named `.<step-stem>.step.js` when it lives next to its STEP file so existing viewers can fall back to the same-filename convention if `manifest.step.path` is absent.
## Defaults And Bounds
Defaults should produce a useful, valid model or pose. Bounds should protect the model from impossible, self-intersecting, or misleading states.
- Use physically valid ranges where possible: joint limits, positive dimensions, manufacturable wall thickness, realistic clearances.
- Clamp in code even when the UI already declares `min` and `max`.
- Make `step` match the useful precision of the underlying model, not just the UI.
- Use booleans for true binary state, selects for discrete modes, colors for style-only values, and numbers only for ordered quantities.
- Keep debug parameters available when useful, but label them as inspection controls if they do not represent real design degrees of freedom.
## Derive, Do Not Drift
Compute dependent values from the real constraints:
- Use pivots, axes, centers, bounds, and measured link lengths instead of eyeballed translations.
- Compose assembly transforms around the correct local datum or joint, not around visual centers unless that is the actual design datum.
- For linkages, solve the kinematics from fixed pivots and link lengths. Do not interpolate through impossible intermediate points.
- For gears, preserve pitch-circle relationships, tooth counts, and angular ratios instead of tuning rotations by sight.
- For repeated features, derive positions from count, pitch, radius, and pattern axes.
If a parameter changes a source-level CAD generator, regenerate STEP and validate the exported geometry. If a STEP sidecar changes only viewer-time presentation, say so in labels/descriptions when ambiguity matters.
## Features And Refs
Named features are the bridge between parameters and geometry.
- Label source parts and assembly children explicitly.
- Expose sidecar `manifest.features` with stable local refs such as `#o1.2`; keep file identity in `manifest.step.path`.
- Prefer feature ids like `lid`, `hinge_pin`, `input_gear`, `lower_rocker`, not occurrence ids as public names.
- In code, group constants and transforms by feature role so the logic reads like the mechanism.
- Resolve and inspect refs when a parameter targets a specific face, edge, part, pivot, or assembly child.
## Animations
Animation parameters should drive the smallest real degrees of freedom and derive everything else.
- Use one normalized travel parameter for a mechanism when possible, then derive all dependent transforms from it.
- Make loops exact: the final pose must equal the initial pose, or the animation should ping-pong through a periodic function.
- Do not blend between incompatible kinematic branches. Switch branches only at a physically valid tangent, over-center, or singular pose.
- Keep hinge centers, mating faces, gear contacts, belt paths, and slider axes coincident throughout the animation.
- Separate style controls from mechanism controls: colors, visibility, highlights, clip/explode, speed, play/pause, and scrub should not alter the mechanical truth.
- Preserve source STEP/GLB material colors by default. Only override colors, add color controls, or assign viewer-time color styles when the user explicitly asks for recoloring, presentation styling, or diagnostic color coding.
- Use comments for non-obvious kinematic choices, especially branch selection, sign conventions, datum origin, and derived ratios.
For STEP sidecars, use JavaScript for live CAD Viewer interaction and Three.js hooks. Use Python/build123d as the source of truth for regenerating geometry. Python may generate `.step.js` modules, but CAD Viewer controls should not imply regeneration unless that workflow exists.
## Controls
Expose controls that make the model understandable, not every constant.
- Numeric dimensions: slider plus number input when the range is bounded and interactive; number input when the range is broad or precision-heavy.
- Angles and normalized travel: sliders with clear min/max and units.
- Visibility, enablement, and optional details: switches.
- Discrete modes: select or segmented control.
- Colors: color controls only when explicitly requested for viewer styling; otherwise keep imported material colors.
- Animation: play/pause, scrub, loop, reset, and speed controls.
Use concise labels and descriptions. A good description says what changes and what stays constrained.
## Validation
Validate parameter behavior at representative values:
- Defaults.
- Min and max.
- Mid travel.
- Boundary or branch-change poses.
- Values involved in user-reported failures.
Use deterministic checks first:
- `scripts/inspect refs --facts --planes --positioning` for scale, labels, frames, and major datums.
- `scripts/inspect frame`, `measure`, or `align` for pivots, axes, mating faces, and distances.
- Source-level assertions for derived dimensions or joint limits when practical.
Use CAD `scripts/snapshot` review for visual semantics, following `snapshot-review.md` for packet sizing and PNG-vs-GIF mode selection:
- Review several parameter poses, with GIFs for motion/animation review.
- Compare sidecar enabled vs disabled when viewer-time presentation is involved.
- Check for disconnected hinges, drifting pivots, collisions, impossible branch blends, and looping jumps.
- Convert visual concerns into measurements or explicit geometric facts before calling them fixed.
## Common Failure Patterns
- Eyeballed keyframes that violate real link lengths or mating constraints.
- A UI parameter that controls a visual transform but is named like a geometry parameter.
- Interpolating between two valid poses through invalid intermediate geometry.
- Transforming a part around its bounding-box center instead of its hinge, mate, or local frame.
- Letting a debug scale or offset create collisions outside the real design envelope.
- Using absolute paths, URLs, parent-directory escapes, or stale renamed STEP paths in sidecar `manifest.step.path`.
- Hiding a geometry issue with color, transparency, camera angle, or exploded spacing.
@@ -0,0 +1,260 @@
# Positioning logic, joints, and mating
Read this file when geometry has mating interfaces, repeated features, assembly children, axes, datums, motion, or user-specified alignment. This is the authoritative reference for assembly positioning, part-local origins, build123d joints, explicit `Location` transforms, CLI `inspect align`, and positioning report content.
## Core rule
Positioning is authored in source and validated after generation. Do not position parts by visually dragging or by editing exported STEP geometry. Use build123d parameters, local coordinate systems, `Location` transforms, `Plane`/`Axis` datums, `cadpy.assembly.AssemblyHelper` relationships, source-level `Joint` objects when useful, and labeled assembly children.
## Terminology
Use these terms carefully:
- **AssemblyHelper** is the preferred generated-script wrapper from `cadpy.assembly`. It records semantic relationships such as `face_to_face`, `coaxial`, `revolute`, and `linear`, then realizes them with native build123d joints.
- **build123d joints** are source-level objects such as `RigidJoint`, `RevoluteJoint`, `LinearJoint`, `CylindricalJoint`, and `BallJoint`. They are attached to `Solid` or `Compound` objects and can reposition parts with `connect_to()`.
- **CLI `inspect align`** is a selector-pair validation tool. It computes a read-only translation delta between selected local refs in a STEP/CAD entry. It does not edit source code, patch exported STEP files, or represent an authored mate feature. This is the one place that distinction is defined; the rest of the skill assumes it.
- **Mating intent** is the design relationship: flush, centered, coaxial, offset, hinge-like, slider-like, or otherwise datum-driven.
Use `AssemblyHelper` and build123d joints to express and compute source assembly placement where appropriate, then use CLI inspection to validate the generated STEP.
## Preferred assembly structure
For assemblies, prefer a mate/joint-driven structure over arbitrary transforms:
```text
root component
→ part-local coordinate systems
→ named datums / joint locations
→ AssemblyHelper semantic relationships backed by native build123d joints
→ labeled Compound assembly with verbose native labels
→ refs/measure/frame/align validation
```
A numeric `Location(...)` should usually correspond to a stated datum, offset, clearance, screw axis, face contact, or joint relationship.
Group a functional unit — a bearing, a gearbox stage, a fastener set — into a sub-assembly node with `asm.add_module(name, children)` when it is placed, reasoned about, or repeated as a unit; nested occurrence refs such as `#o1.12.1` then stay meaningful.
## Part-local positioning
For each part, define a local coordinate convention before modeling:
```text
- Origin: center, base datum, mounting interface, or functional axis.
- XY plane: main sketch/base plane unless another datum is dominant.
- +Z: extrusion/up direction.
- Named dimensions: offsets, hole spacing, boss spacing, clearances.
- Datum features: mating faces, screw axes, centerlines, locating tabs, rails.
```
Good defaults:
- Symmetric standalone parts: origin at body center.
- Plates: origin at footprint center; thickness along Z.
- Enclosures: origin at footprint center; base/lid mating surfaces controlled by Z parameters.
- Shaft/knob/axisymmetric parts: origin on rotational axis.
- Mating adapter plates: origin on the primary mounting datum or center of the bolt pattern.
## Feature placement inside a part
Use named parameters and local coordinates:
```python
hole_offset_x = 30
hole_offset_y = 17.5
hole_positions = [
(-hole_offset_x, -hole_offset_y),
( hole_offset_x, -hole_offset_y),
(-hole_offset_x, hole_offset_y),
( hole_offset_x, hole_offset_y),
]
with Locations(*hole_positions):
Hole(radius=hole_diameter / 2)
```
Avoid untraceable placement constants inside geometry calls. Put all meaningful offsets into parameters.
## AssemblyHelper pattern
Use `AssemblyHelper` for generated assembly scripts. It keeps the LLM-facing code intent-focused while still using native build123d labels, `Joint` objects, and `Compound` assemblies.
```python
from build123d import *
from cadpy.assembly import AssemblyHelper
base_height = 30.0
lid_thickness = 3.0
gasket_gap = 0.5
asm = AssemblyHelper("enclosure")
base = asm.add(make_base(), "base")
lid = asm.add(make_lid(), "lid")
base_seat = asm.rigid_frame(
base,
"lid_seat",
Location((0, 0, base_height / 2)),
)
lid_underside = asm.rigid_frame(
lid,
"underside",
Location((0, 0, -lid_thickness / 2)),
)
asm.face_to_face(base_seat, lid_underside, offset=gasket_gap)
def gen_step():
return asm.build()
```
The fixed target is listed first and the moving target second. In the example above, the base stays fixed and the lid moves. The helper records the relationship in source and calls native build123d `connect_to()` under the hood; exported STEP contains the resolved static placement and native assembly labels, not persistent external constraints.
Use helper labels intentionally:
```python
standoff = asm.feature(Cylinder(radius=3.0, height=12.0), "m3_standoff", "front_left")
hinge_axis = asm.rigid_frame(lid, "hinge_axis", Location((0, -25, 0)))
```
Assembly labels name the root occurrence. `asm.add()` labels child component occurrences and their exported shape context. For repeated hardware or library parts, use role/location labels such as `front_left` and `rear_right` so STEP topology and viewer selections remain traceable after export.
Feature labels survive best when the labeled geometry remains a child shape in a `Compound`. Labels on boolean-subtracted or fused feature history are not reliable STEP feature history.
Use the frame method that matches native build123d joint inputs: `rigid_frame()` and `ball_frame()` take a `Location`; `revolute_frame()`, `linear_frame()`, and `cylindrical_frame()` take an `Axis` plus optional native range/reference arguments.
## Imported components
For purchased or downloaded parts (see `$step-parts`), import the STEP file and add it like any authored part:
```python
from build123d import import_step
servo = asm.add(import_step("models/parts/sg90_servo.step"), "servo")
```
Imported geometry was not authored here, so do not assume its origin or orientation. Derive mating frames from inspected geometry: run `refs --facts --planes --positioning` and `measure` against the imported part, then define `asm.rigid_frame(...)` locations from the measured faces, axes, and bolt patterns. Validate the resulting mate exactly like an authored one.
## When to use build123d joints
Use `AssemblyHelper`/build123d joints when assembly intent is clearer as a relationship between part datums than as a raw transform:
- lid-to-base, cover-to-frame, bracket-to-rail, flange-to-pipe, pin-to-hole, shaft-to-bearing
- hinge, slider, screw-like, cylindrical, ball/gimbal, or other motion-positioned assemblies
- repeated or library components that already expose joints
- source assemblies where a change to one dimension should recompute part placement
Direct `Location(...)` transforms are acceptable for simple static layouts when they are parameterized and documented, such as a row of identical spacers or a visual exploded view.
Raw build123d joints are acceptable for advanced cases not covered by `AssemblyHelper`, but preserve the same fixed-first directionality: call `connect_to()` on the fixed/root joint and pass the moving part's joint as `other`. `connect_to()` is a source-generation operation. It repositions the moving part for the generated model; it is not a persistent external constraint in the exported STEP file.
## Joint type selection
Use the simplest joint that expresses the source-level relationship:
- `RigidJoint` / `asm.rigid_frame()`: fixed placement, face-to-face seating, mounting datums, imported components with known interfaces.
- `RevoluteJoint` / `asm.revolute_frame()`: hinge or rotational pose; define with an `Axis` and drive with an angle parameter for a static STEP pose.
- `LinearJoint` / `asm.linear_frame()`: slider, latch, telescoping component; define with an `Axis` and drive with a position parameter.
- `CylindricalJoint` / `asm.cylindrical_frame()`: combined axial translation and rotation, such as screw-like or pin-in-slot relationships.
- `BallJoint` / `asm.ball_frame()`: gimbal or spherical orientation relationship; define with a `Location` and angular ranges.
When only final static placement matters and no meaningful joint datum exists, use explicit `Location` transforms and validate them.
## Assembly positioning workflow
1. Choose the fixed/root component.
2. Define part-local frames and datums before modeling child placement.
3. Identify functional datums such as mating faces, screw axes, hinge axes, sliding axes, locating tabs, gasket offsets, or contact planes.
4. Name source-level joints or mating datums on each child with `asm.rigid_frame()`, `asm.revolute_frame()`, `asm.linear_frame()`, or another helper frame method.
5. Use `AssemblyHelper` relationship methods where they improve source clarity, otherwise use parameterized `Location` transforms.
6. Build a labeled `Compound` assembly with `asm.build()`.
7. Generate the assembly through the Python source, not by re-importing the generated STEP (see `step-generation.md`):
```bash
python scripts/step path/to/assembly.py
python scripts/inspect refs path/to/assembly.step --facts --planes --positioning
```
## CLI alignment validation
After generation, select moving and target refs from the local selector refs returned by `refs --positioning` and compute deltas:
```bash
python scripts/inspect align path/to/assembly.step \
--moving '#moving_selector' \
--target '#target_selector' \
--mode flush \
--axis z
```
Use `--mode flush` for coplanar face alignment. Use `--mode center` for centerline, plane-center, or symmetrical alignment where supported by the selected references. If the returned delta is outside tolerance, apply a source-level correction (see below), regenerate, and rerun inspection.
## Frame validation
Use `frame` to inspect an occurrence or selector's world frame:
```bash
python scripts/inspect frame path/to/assembly.step '#selector'
```
Use this when:
- a child appears in the wrong orientation
- a mating face is offset in world coordinates
- an axis is expected to align with X/Y/Z
- repeated parts should share orientation
- a downstream task needs a stable coordinate frame
## Measurement validation
Use `measure` for scalar checks:
```bash
python scripts/inspect measure path/to/assembly.step \
--from '#selector_a' \
--to '#selector_b' \
--axis z
```
Examples:
- lid bottom face to base top face should be 0 mm for flush contact
- two screw axes should have matching X/Y positions
- bracket mounting face should sit a specified distance from a datum plane
- spacer height should equal requested offset
## Source-level positioning corrections
When a positioning check fails, fix one of these in source:
- child `Location` translation
- child `Location` rotation
- `AssemblyHelper` relationship fixed/moving order or offset
- build123d joint location or axis
- part-local origin convention
- feature offset parameter
- sketch plane
- workplane selection
- assembly hierarchy
- symmetric placement signs
Then regenerate. Do not patch the exported STEP directly.
## Reporting positioning
In the final response, report only checks that were run:
```text
Positioning/joints:
- source used RigidJoint lid_seat → underside
- base/lid Z mate flush, delta 0.00 mm
- screw boss axis alignment: checked in XY by measurement
- lid occurrence frame: +Z up, origin at assembly centerline
```
If no positioning-sensitive features exist, say:
```text
Positioning: not applicable beyond centered part-local origin.
```
If a mate or alignment was intended but not checked, say `not checked`; do not imply success.
@@ -0,0 +1,169 @@
# Repair loop
Read this file when generation, export, inspection, positioning, snapshot review, CAD Viewer setup, or documentation validation fails.
## Loop
1. Read the failing command output.
2. Classify the failure.
3. Make the smallest responsible source or command change.
4. Rerun the failed command.
5. Rerun any dependent validation checks.
6. Report remaining risk or deliberate deviations.
## Failure classes and fixes
### Source import or syntax failure
Likely causes:
- invalid Python syntax
- missing import
- wrong build123d symbol
- function not named `gen_step()`
- executable code outside the intended function has side effects
Fix:
- correct imports and syntax
- ensure `gen_step()` returns the STEP-ready shape or compound
- keep output paths in CLI commands, not inside `gen_step()`
### Invalid or missing geometry
Likely causes:
- open sketch
- subtractive profile outside target
- zero thickness
- boolean operation failed
- construction geometry used as exported geometry
Fix:
- close profiles intended to become faces
- verify dimensions are positive
- make subtractive tools pass through when through-cuts are intended
- simplify the failing feature and rebuild incrementally
### Fillet or chamfer failure
Likely causes:
- radius/length exceeds local geometry
- selected edges include tiny or unintended edges
- boolean operation created complex edge topology
Fix:
- reduce radius/length
- filter selected edges more narrowly
- apply fillets later in the model
- split edge groups by feature intent
### Wrong scale or bounding box
Likely causes:
- units mismatch
- mistaken diameter/radius
- extrusion direction or amount wrong
- part not centered as assumed
- direct imported STEP uses unexpected units
Fix:
- check parameter values
- inspect facts and planes
- measure critical extents
- correct source dimensions or import handling
### Missing feature
Likely causes:
- wrong `Mode.ADD`/`Mode.SUBTRACT`
- feature profile not inside target
- blind cut too shallow
- selector changed after prior operation
Fix:
- confirm feature mode
- increase cut length for through-cuts
- inspect topology or planes
- regenerate and measure/check feature-specific refs
### Selector fragility
Likely causes:
- arbitrary index selection
- topology changed after fillet or boolean
- similar faces/edges are indistinguishable
Fix:
- select by axis, plane, position, normal, or inspected reference
- use `refs --facts --planes --positioning` to rediscover stable references
- add construction datums or simplify operations if needed
### Positioning or joint mismatch
Likely causes: wrong part-local origin or datum, reversed `AssemblyHelper` fixed/moving order, `.connect_to()` moving the wrong part, inverted joint axis, sign errors in symmetric placement, an explicit `Location` not recomputed after a parameter change, or a joint defined in world coordinates when a part-local datum was intended.
Fix:
- inspect `refs --positioning`, then `frame` and `align` on the relevant selectors
- verify the source-level `AssemblyHelper` target order, joint labels, and `joint_location` definitions
- apply the smallest source correction from the list in `positioning.md` (Source-level positioning corrections)
- regenerate the assembly from the Python source and rerun the failed check
### CAD Viewer startup or link failure
Likely causes:
- Node/npm unavailable
- CAD Viewer app not built or cannot start
- active Viewer URL is missing the absolute `?dir=` for the project
- returned link is missing an absolute `file=` path or points outside `?dir=`
Fix:
- rerun `$cad-viewer` with the same absolute `?dir=` for the project and an absolute `file=` path for each artifact
- return one documented Viewer link per requested file
- if unresolved, report the startup failure and rely on CLI facts/measurements plus snapshots for validation
### CAD `scripts/snapshot` failure
Likely causes:
- target input path is wrong, missing, or not a STEP/STP file or same-stem Python generator
- adjacent CAD Viewer GLB/topology artifact missing
- invalid render flags
Fix:
- generate STEP first, then snapshot the primary `.step`/`.stp` artifact
- retry only with simpler supported snapshot jobs, starting with a single `view` output before wireframe display or `section`
- choose modes and packet size per `snapshot-review.md`
## Diff after repair
Use `diff` when the fix might have affected unrelated geometry:
```bash
python scripts/inspect diff path/to/before.step path/to/after.step --planes
```
## Reporting failed repairs
If a check cannot be repaired in the current environment, report:
```text
- what failed
- what was tried
- which artifact is still usable
- which validation claims cannot be made
- what the next source-level correction should be
```
@@ -0,0 +1,84 @@
# Snapshot review
Read this file when choosing saved CAD `scripts/snapshot` outputs for primary STEP/STP artifacts.
## Policy
Snapshot validation is mandatory. Every created or visibly updated primary STEP/STP part or assembly gets at least one reviewed PNG snapshot; deterministic checks passing is not a reason to skip. Use CAD `scripts/snapshot` rather than opening the viewer manually or using Playwright; snapshots are faster, lighter, more precise, and more agent-friendly. Use PNGs for static reviews and GIFs for motion/animation reviews, including STEP-module parameter animation.
Skip saved snapshots only when no visible geometry was created or updated, or no valid artifact exists:
- pure format/export requests where geometry is unchanged
- source changes that do not alter visible geometry
- inspection-only tasks (for example direct measurement questions) that create or update nothing
- failed Python or STEP generation before a valid artifact exists
When skipping, report the reason and the deterministic evidence that still ran.
Do not loop on snapshots. Rerender only when a source repair changed visible geometry or when a specific visual finding needs confirmation.
## Packet sizing
One PNG is enough for a simple static part. Use the small multi-view packet when semantic errors are plausible from shape complexity or prompt intent:
- assemblies or more than one body/part
- holes on multiple faces or multiple axes
- shells, internal cavities, bores, passages, open enclosures, or section-critical features
- ribs, gussets, bosses, standoffs, slots, cutouts, lightening holes, fins, blades, or repeated patterns
- source repairs after a geometry, boolean, selector, or feature failure
- prompts where "looks like the requested object" is part of the task
- deterministic checks pass but visible semantics are still uncertain
## Small packet
Prefer a single `view` JSON job with these outputs:
```json
{
"input": "models/part.step",
"mode": "view",
"outputs": [
{ "path": "/tmp/render/iso.png", "camera": "iso" },
{ "path": "/tmp/render/iso_opposite.png", "camera": { "direction": [-1, 1, -0.8] } },
{ "path": "/tmp/render/top_ortho.png", "camera": "top" },
{ "path": "/tmp/render/front_ortho.png", "camera": "front" }
],
"render": { "viewLabels": true, "padding": 0.12, "sizeProfile": "diagnostic" }
}
```
The two opposed isometric views guarantee every face appears in at least one image — rear, left, and bottom features are covered by default, not by suspicion. The top ortho is the primary pattern/symmetry check and the front ortho the profile check.
Set `input` to the primary STEP/STP artifact using a relative or absolute path. The snapshot CLI derives its internal render root from that input path. It defaults to `appearance: "workbench"` and `display.mode: "solid"`, matching CAD Viewer; labeled/section views default to 1600x1200 when dimensions are omitted. Use `render.sizeProfile: "assembly"` or `"assembly-large"` for complex assemblies that need 1800x1200 or 1920x1440. For CAD review packets, use still-image render modes `view` and `section`; set `display.mode` to `solid`, `transparent`, `hidden_edges`, `hidden_lines_removed`, or `wireframe` when the visual check benefits from explicit CAD linework.
Use `--focus '#o1.2' ...` to render only specific part or subassembly occurrence refs, or `--hide '#o1.2' ...` to omit them. Do not combine focus and hide in the same snapshot command or job. These filters accept occurrence refs only, not face, edge, vertex, or shape selectors.
The snapshot CLI appends one shared UTC seconds timestamp before each output file extension when saving a packet, so readable paths like `iso_solid.png` become names such as `iso_solid_20260527T163012Z.png`.
## Targeted additions
Add views only when the brief or a failure mode calls for them:
- reference-image reproduction: one snapshot from the reference image's viewpoint for side-by-side comparison
- `section`: shell, bore, internal cavity, passage, blind hole, enclosure, or wall/floor relationship
- `display.mode: "solid"`: shaded CAD view with explicit edge linework
- `display.mode: "rendered"`: shaded material view without edge overlay
- `display.mode: "transparent"`: overlap, collision, enclosure readability, or hidden contact checks when transparency adds information and wireframe is too noisy
- `display.mode: "hidden_edges"`: opaque shaded context with hidden/occluded CAD edges visible through solids
- `display.mode: "hidden_lines_removed"`: line-focused review where hidden/occluded edges should be suppressed
- `display.mode: "wireframe"`: internal overlap, hidden interference, or assembly collision suspicion when full triangle wire is useful
- labeled or annotated review: use supported CAD Viewer refs, selections, screenshots, or GUI review links
Exploded or labeled review is an intent, not a render mode. Satisfy it through supported CAD Viewer mechanisms, supported JSON job settings, or the GUI link.
## Diagnostic review
Visual review is diagnostic, not authoritative. Convert every visual concern into a follow-up geometry check before using it as a validation claim:
- hole pattern appears asymmetric -> measure hole centers and compare offsets
- lid, child part, or occurrence appears offset -> inspect frames and mating deltas
- gusset, boss, standoff, rib, or plate may be floating -> inspect solid count, labels, connectivity, contact, or relevant distances
- cavity, bore, or blind hole looks wrong -> run section review, then measure wall thickness, depth, or through-condition
- repeated pattern looks uneven -> measure pattern centers, angular spacing, or occurrence frames
Final reports should include the generated snapshot PNG/GIFs or the documented skip reason, and state which deterministic checks support any visual finding.
@@ -0,0 +1,59 @@
# STEP generation
Read this file when generating or regenerating STEP/STP artifacts from build123d Python source or from direct STEP/STP targets.
## Tool
The launcher lives in the CAD skill directory:
```bash
python scripts/step [--kind {part|assembly}] targets... [flags]
```
Use explicit target paths only; target paths resolve from the command cwd unless absolute. Do not rely on directory-wide generation.
Plain generated Python targets write sibling `.step` outputs. Use `-o`/`--output` only with one plain generated Python target, or use `SOURCE.py=OUTPUT.step` positional pairs for per-target custom outputs. Paired output paths resolve from the command cwd and are valid only for generated Python sources, not direct STEP/STP inputs. Do not put output paths in the `gen_step()` return value; the CLI owns output paths.
## Generated Python source
This is the default path when designing from scratch or modifying a generated model. Generated build123d sources define:
```python
def gen_step():
...
return step_ready_shape_or_labeled_compound
```
Generated Python targets infer their kind from the source metadata and `gen_step()` return value; pass the source path directly:
```bash
python scripts/step path/to/part.py
python scripts/step path/to/part.py -o path/to/custom.step
python scripts/step path/to/a.py=out/a.step path/to/b.py=out/b.step
python scripts/step path/to/assembly.py
```
Passing a generated assembly `.step` directly treats it as imported native STEP and loses source-level assembly composition; pass the `.py` assembly source. For generated build123d assemblies, prefer `cadpy.assembly.AssemblyHelper` in the Python source so native labels, named mate frames, and source-level relationships are preserved before STEP export (see `positioning.md`).
## Direct STEP/STP imports
Use a direct STEP/STP target when no generator exists (imported or downloaded STEP) or the user explicitly identifies a STEP/STP file as the target. The GLB/topology artifacts are then generated from the STEP file itself:
```bash
python scripts/step --kind part path/to/imported.step
```
Direct targets support the same mesh sidecar flags as generator targets; read `supported-exports.md` for STL and 3MF sidecars.
## Viewer artifacts
Every `scripts/step` run also writes hidden adjacent GLB/topology artifacts as part of the normal build. They power CAD Viewer review, `$cad-viewer` workflows, and `scripts/inspect` refs, and are not optional in the STEP workflow.
## After generation
- Confirm the process succeeded and the STEP file exists and is non-empty.
- Run the baseline inspection and any spec-driven checks per `inspection-and-validation.md`:
```bash
python scripts/inspect refs path/to/model.step --facts --planes --positioning
```
@@ -0,0 +1,75 @@
# Supported exports
Read this file when the user requests STL, 3MF, or native GLB output from CAD geometry. For 2D DXF output, use the `$dxf` skill; DXF uses a separate `gen_dxf()` source contract.
## Policy
STL, 3MF, and native GLB are mesh sidecars, not substitutes for STEP. Generate and validate STEP first, then export requested sidecars from the same `scripts/step` run. Do not treat sidecar renders as CAD validation; inspect and snapshot the primary STEP per the standard workflow.
Native GLB sidecars are ordinary glTF 2.0 binary files for external tools: Y-up, meter-scaled, and free of the CAD Viewer `STEP_topology` extension. Do not confuse them with the hidden `.<name>.step.glb` CAD Viewer topology artifact.
## Tool
Use `scripts/step` with a generated Python source:
```bash
python scripts/step path/to/model.py \
--stl meshes/model.stl \
--3mf meshes/model.3mf \
--glb meshes/model.glb
```
When a generator exists, use the generator form. Use direct STEP/STP targets only when the generator is unavailable or the user explicitly identifies that file as the target:
```bash
python scripts/step --kind part path/to/model.step \
--stl meshes/model.stl \
--3mf meshes/model.3mf \
--glb meshes/model.glb
```
Sidecar paths must be relative `.stl`, `.3mf`, or `.glb` paths and are resolved beside the STEP output.
## Mesh tolerance
The default mesh density is `0.02` linear deflection and `0.05` angular deflection.
Use these flags when the default mesh density is wrong for the part:
```bash
--mesh-tolerance FLOAT
--mesh-angular-tolerance FLOAT
```
Use tighter tolerances for small curved parts or visual fidelity. Use looser tolerances for large simple geometry when file size matters.
## Workflow
1. Generate STEP from `gen_step()` with the requested sidecar flag(s).
2. Run facts/planes/positioning inspection on the STEP.
3. Report the STEP and the requested sidecar files.
Example:
```bash
python scripts/step models/bracket.py \
--stl meshes/bracket.stl \
--glb meshes/bracket.glb \
--mesh-tolerance 0.2 \
--mesh-angular-tolerance 0.2
python scripts/inspect refs models/bracket.step --facts --planes --positioning
```
## Reporting
```text
Files:
- STEP: /absolute/project/models/bracket.step
- STL: /absolute/project/models/meshes/bracket.stl
- GLB: /absolute/project/models/meshes/bracket.glb
Validation:
- STEP geometry validated; STL/3MF/native GLB generated as requested sidecars.
- Primary STEP/STP snapshot packet run/skipped and why.
```