Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/architecture/lib-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ rendering, slicing support, and verification:
- `fit_camera_math.py`
- `fit_check.py`
- `assembly_cli.py`
- `assembly_pair_cli.py`
- `fits.py`
- `fits_apply.py`
- `fits_registry.py`
Expand All @@ -67,6 +68,7 @@ rendering, slicing support, and verification:
- `gcode_resume.py`
- `gcode_labels.py`
- `gcode_overhangs.py`
- `gcode_walls.py`
- `gcode_mix_gate.py`
- `gcode_scan.py`
- `stripe_contrast.py`
Expand Down Expand Up @@ -284,12 +286,14 @@ Every current root Python module is listed here so future additions are intentio
- `fit_check.py` — already-printed insert vs its socket: printed-size lock, per-layer clearance (manifold3d slices, shapely distances) and the open path along the insertion direction; backs `3d fit-check`.
- `fits.py` — the one fit/tolerance table (`data/fits.yaml`, schema `3d.fits/1`): fit types, aliases, printer x material x nozzle x layer corrections, `resolve()` with provenance and `fit_clearance` calibration readings.
- `assembly_cli.py` — the worker behind `3d assembly` (argument parsing, report assembly; geometry in `lib/assembly/`).
- `assembly_pair_cli.py` — the worker behind `3d assembly pair` (two-part fit check; geometry in `lib/assembly/pair.py`, `helical.py`, `printed.py`).
- `parts_3mf.py` — reads the `3d:role` / `3d:printable` tags of a project 3MF and guards print boundaries (`arrange`, `retarget`, `prep`) with `lib/parts.py`.
- `fits_apply.py` — pure text operations of `3d fits apply`: the OpenSCAD snippet of a resolved clearance and the idempotent rewrite of `// @fit`-marked declared clearances.
- `fits_registry.py` — bridge from a free-text printer/material name (fleet key, model, `Elegoo Rapid PLA+`) to the registries `fits.resolve` cites in its provenance.
- `gcode_resume.py` — resume a stopped multi-head Orca print above a layer (layer walk, machine state at a byte, resume header from the device's `resume:` profile, per-head retraction tracking, `--no-tower` off-bed purges); stdlib-only; backs `3d gcode-resume`. Its per-line parser (`advance`) and the streamed `state_at` (machine state at a printer's byte cursor or a layer) also give PrintOps incidents their G-code context.
- `gcode_labels.py` — the slicer's own labels in a G-code: object labels in both dialects (Klipper `EXCLUDE_OBJECT_*`, Bambu Studio `; start printing object` / `M624`…`M625` blocks named by their `; printing object` comment, `M486`, PrusaSlicer comments), layer-change markers, feature tags and line widths; stdlib-only; the one parser behind head-sequence, overhangs, visible-faces and the print-area plate section.
- `gcode_overhangs.py` — extrusion printed on nothing per object and layer of a sliced G-code (numpy/scipy raster of each layer's carrier, sparse reach/bridge/step-out tests on the pixels that stick out, layers split over forked workers); backs `3d gcode-overhangs` and the `overhangs` gate of `print-preflight`.
- `gcode_walls.py` — the outer-wall lines of one object, layer by layer, from a sliced G-code (object / layer labels via `gcode_labels`, width and path height per piece); backs the as-printed numbers of `3d assembly pair`.
- `gcode_scan.py` — one streaming pass over a sliced G-code, layer by layer: per-layer Z / height / extrusion runs (type, tool, XY) handed to consumers, plus M600 pauses, tool changes, grams per tool and feature type and footer settings; the whole file is never held; numpy only.
- `stripe_contrast.py` — perceptual layer-stripe contrast of a stack of single-colour layers at their real heights: eye-blurred profile vs its period mean, worst CIEDE2000 per layer (the `stripe contrast` check of `3d gcode-mix-gate`).
- `gcode_mix_gate.py` — the multi-colour gates of `3d gcode-mix-gate`: colour volumes attributed from the part STLs (pure / mixed per layer on the layer's own outer-wall evidence / accent / thin), pause, heads and prime tower, pocket void, layer heights, footer settings, and the `--mix-teeth` / `--cavity-teeth` controls that must FAIL (a gate that cannot fail is vacuous); numpy/scipy/trimesh.
Expand Down
2 changes: 1 addition & 1 deletion docs/commands/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ explain why the command exists, what its inputs mean, and how it fits into the l
| `convert` | [convert.md](convert.md) | Convert between formats (multicolour scad→Orca project [3MF](GLOSSARY.md#3mf) with per-object extruders, stl/usdz/png, retarget alias). |
| `generate` | [generate.md](generate.md) | Text description + named dimensions -> verified parametric `.scad` via an AI backend (generate/validate/render/check/fix loop). |
| `fit-niche` | [fit-niche.md](fit-niche.md) | Generate a parametric insert/plug that mates into a described cavity with FDM clearances. |
| `assembly` | [assembly.md](assembly.md) | Whole-assembly checks over declared mates: blocking intersections, per-mate fit clearance, insertion/removal sweeps on any axis, assembly order, finger/tool ergonomics, explode, BOM, 3MF positioning (`check`, `sweep`, `order`, `ergonomics`, `explode`, `bom`, `position`, `show`). |
| `assembly` | [assembly.md](assembly.md) | Whole-assembly checks over declared mates: blocking intersections, per-mate fit clearance, insertion/removal sweeps on any axis, assembly order, finger/tool ergonomics, explode, BOM, 3MF positioning (`check`, `sweep`, `order`, `ergonomics`, `explode`, `bom`, `position`, `show`), plus `pair`: two meshes, interpenetration, minimum gap and per-feature radial clearance of threads and ribs, model and as printed from G-code. |
| `fits` | [fits.md](fits.md) | Fit tolerances per fit type (snap/sliding/tight/press/glue) corrected for printer x material: `list`, `show`, `resolve` (with provenance), `apply` (snippet / correct declared clearances), `corrections`. |
| `library` | [library.md](library.md) | Local model-library database (searches, sources, designs, makes, photos, publications): `path`, `migrate`, `status`, `export`, offline; read-only `makes`, `publications`, `relations`, `graph DESIGN` (source <- remix <- make). |
| `make` | [make.md](make.md) | Record the prints (makes) of a design in the library: `add`, `status` (queued -> printing -> done/failed), `list`; local database only. |
Expand Down
75 changes: 74 additions & 1 deletion docs/commands/assembly.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ hooks and screws, and where each printed part sits on the plates. Umbrella #570,
#553 (declared insertion axis) and covers the assembly-motion part of #61.

Every subcommand takes `--json` (schema `3d.assembly/1`, `kind` = `check | sweep | sweeps | order | ergonomics | explode |
bom | position | show`). Exit codes: `0` ok, `1` a verdict failed (blocking intersection, blocked path, no access, no
bom | position | show | pair`). Exit codes: `0` ok, `1` a verdict failed (blocking intersection, blocked path, no access, no
straight order), `2` bad spec / usage, `127` missing dependency.

## Usage
Expand All @@ -23,6 +23,13 @@ straight order), `2` bad spec / usage, `127` missing dependency.
[--process NAME] [--filament NAME] [--preset-dir DIR]... [--set K=V]...
[--include-non-printable] [--reason TEXT] [--yes] [--json]
3d assembly show TARGET [--json]
3d assembly pair A B [--scale-a S] [--scale-b S] [--pose-a POSE] [--pose-b POSE] [--axis AXIS] [--axis-point X,Y,Z]
[--a-side inner|outer --b-side inner|outer [--pitch MM] [--hand auto|right|left] [--a-z LO:HI] [--b-z LO:HI]
[--a-r LO:HI] [--b-r LO:HI] [--feature-name N] | --features FILE]
[--min-radial-gap MM | --fit TYPE [--shape S] [--printer P] [--material M] [--nozzle MM] [--layer MM]]
[--min-clearance MM] [--zone XMIN,YMIN,ZMIN,XMAX,YMAX,ZMAX] [--max-overlap-mm3 V] [--seat-tol MM]
[--screw-to-best] [--gcode-a F --object-a NAME --print-a POSE] [--gcode-b F --object-b NAME --print-b POSE]
[--skip-below MM] [--max-faces N] [--json]
```

`TARGET` is an assembly spec (`assembly.yaml` / `.json`), a project directory holding `assembly.yaml` next to its
Expand All @@ -38,6 +45,7 @@ straight order), `2` bad spec / usage, `127` missing dependency.
3d assembly position assembly.yaml --3mf out/plates.3mf --base-project print/mole.3mf --yes
3d assembly position assembly.yaml --3mf out/plates.3mf --include-non-printable --reason "fit test" --yes
3d assembly show assembly.yaml
3d assembly pair rim.stl plug.stl --a-side inner --b-side outer --pitch 3.007 --screw-to-best
```

## The spec (`3d.assembly/1`)
Expand Down Expand Up @@ -85,6 +93,71 @@ loads: # carried in the report for the S4 solver
* **Roles**: `printed | vitamin | reference | stand_in` (`lib/parts.py`); a vitamin or reference model must name its source
URL and licence, a stand-in is labelled as invented in the BOM and drawn ghosted.

## `pair` - do two parts mate? (no spec)

`3d assembly check` judges declared mates of a spec. `pair` takes two meshes (STL / 3MF / OBJ / PLY), each with an optional
scale and pose into the assembled frame, and answers the question for one pair, down to a thread or a rib. A 3MF is read with the
stdlib reader (no lxml); one that holds several objects needs `plate.3mf#ID` (the refusal lists the ids), and its build-item
transform is part of the mesh:

```bash
# rim (the ball's bore) and plug at the print scale, plug seated under the rim; thread pitch 3.007 mm; model only
3d assembly pair rim.stl plug.stl --scale-a 2.00493695 --scale-b 2.00493695 --pose-b '{translate: [0, 0, -37.782]}' \
--a-side inner --b-side outer --pitch 3.007 --a-z -36.78:-28.28 --a-r 0:36.4 --b-z -36.78:-30.98 --b-r 0:36 --screw-to-best
# the same, as printed: the plug from its K1 G-code, the new rim from its slice; both placed on the bed automatically
3d assembly pair rim.stl plug.stl ... --gcode-a rim.gcode --object-a "rim.stl" --gcode-b plate.gcode --object-b "Plug x2.stl"
# several features from a file, the clearance the fit table asks for on this printer and plastic
3d assembly pair a.stl b.stl --features features.yaml --fit sliding --printer k1se --material PETG
```

* **interpenetration** - exact overlap regions (volume, depth, xyz as in `check`); a region thinner than `--seat-tol`
(0.02 mm) is a seat contact, not a collision. FAILs above `--max-overlap-mm3` (default 0.01): a designed press fit is allowed
only by saying how much.
* **clearance** - the exact minimum gap of the two solids (0 where parts seat on a face); `--zone BOX` measures only the surface
samples inside a box (spacing reported), which leaves a seat face out; `--min-clearance` makes it a verdict.
* **features** - the part of the question a volume cannot answer. A feature is the surface of each part around a COMMON AXIS
(`--axis`, `--axis-point`; z windows are the coordinate along it, r windows the radius from it): `--a-side inner` is the wall
of a bore, `outer` a boss; one of each. The surface radius is read against the PHASE `u` (a thread: `z/pitch -+ theta/2pi`, one
value per `u` for a perfect single-start thread; ribs and keys, `--pitch 0`: the angle), 360 bins, from the model's sections
every 0.12 mm. The bore minus the boss is the radial gap, at the posed phase and at every other relative phase - which is
screwing (a turn = an axial advance of one pitch) or clocking. Reported: the gap at the pose, the best phase and the tightest
radial gap there, the free play (mm of axial travel of a thread, degrees for ribs), male crest -> female root and female crest ->
male root clearances, handedness (detected, a mismatch FAILs: the threads cannot screw together), pitch coherence (a wrong pitch
shows as a large value and a warning), coverage of the phase bins (unmeasured = FAIL under 50 %), and the concentricity of each
feature's own axis with the declared one. FAILs when the best-phase gap is under `--min-radial-gap` (default 0.1) or the
`--fit TYPE` row (`clearance - tol` per face of `3d fits resolve`, printer x material corrected). The posed phase may interfere
while a free phase exists: that is a WARN with the move that fixes it, in turns, degrees and mm of advance.
A bore with no `--a-r` is read from the axis out to the boss's largest radius + 3 mm (else its own outer wall would count).
* **`--screw-to-best`** turns B about the axis to the best phase of the first feature before anything else is measured (a screwed
fit has no unique pose: the phase is free). The turn keeps B's axial position, so a part that seats on a face stays seated.
* **as printed** - `--gcode-a/--gcode-b` (+ `--object-a/b` when the plate holds several objects; the slicer's own object labels - a wrong or
ambiguous name, a name that matches several copies, or none on a multi-object plate lists the file's labels; `"Cup.stl id:0
copy 1"` picks one copy) read the OUTER WALL paths of the object (`lib/gcode_walls.py`: Orca / Snapmaker Orca / Bambu /
PrusaSlicer, arcs, relative extrusion). The model is put on the bed (`--print-a POSE`: orientation like a pose; without a
`translate` it is dropped on z = 0 and REGISTERED on the walls in XY by a translation-only fit - the slicer's own placement), the
per-layer deviation of the printed wall from the model's section at the layer mid height is reported (the wall centre line is
half a line width inside the surface; layers under `--skip-below` are listed apart: first-layer flare), overall and inside the
feature's window, and the feature clearance is recomputed with the printed surface (`centre line +- width/2` radially). A part
without a G-code is taken from its model, the report says which side is printed. The slicer's compensation
(`xy_hole_compensation`, elephant-foot, line width) therefore shows in the numbers, which a model-only check cannot see.

`features.yaml` (several features, the single-feature flags are the same keys):

```yaml
features:
- name: rim_thread
pitch: 3.007 # 0 / absent = ribs or keys
hand: auto # auto | right | left
min_gap_mm: 0.2 # per-feature requirement (else --min-radial-gap / --fit / 0.1)
a: {side: inner, z: [-36.78, -28.28], r: [0, 36.4]}
b: {side: outer, z: [-36.78, -30.98], r: [0, 36.0]}
```

A value that starts with `-` may be given after `=` (`--a-z=-36:-28`) or as the next argument (`--a-z -36:-28`).
Exit codes as the other subcommands: 0 ok, 1 a verdict failed, 2 bad input (an unknown object name, a window without surface, two
bores), 127 missing dependency. `--json` document: `kind: pair` with `parts`, `axis`, `interpenetration`, `clearance`, `features`
(each with `model` and, with a G-code, `as_printed`, `printed_vs_model`), `printed`, `fit`, `summary`.

## What each check does

* **check** - per pair of parts, exact overlap regions (manifold3d, coarse to fine: boxes, then a crop to the overlap
Expand Down
Loading
Loading