Skip to content
Merged
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
2 changes: 2 additions & 0 deletions docs/architecture/lib-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ Target package: `lib/geometry/`, with subpackages only when a real boundary appe
`dossier.py` / `dossier_render.py` / `webui.py` / `render.py` (`3d printers show NAME`: name
resolution, the concurrent read-only live reads, the web UI probe, the text; `render.py` is
the status text shared with `3d printer status`), `preflight.py` (pre-print checks),
`colour_fix.py` (the CIEDE2000 slot colour tolerance and the `--fix` / `--fix-colour` rewrite of a
G-code + project 3MF + sidecars to the loaded colour),
`move_bounds.py` (G-code moves and arc extremes vs axis travel / printable area),
`force_hatch.py` (the owner's Telegram hatch approval that `3d printer start --force` needs),
`watch.py` / `watch_run.py` / `watch_sinks.py` (`3d printer watch`: detectors, streams,
Expand Down
10 changes: 7 additions & 3 deletions docs/commands/print-preflight.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,18 @@ input is missing is SKIP and its line says what to pass — it never passes sile
```
3d print-preflight FILE.gcode [--printer NAME] [--axis-limits X0:X1,Y0:Y1|NAME] [--source-3mf FILE.3mf]
[--visible [OBJ:]SPEC]... [--bridge-max MM] [--printops-api URL] [--offline]
[--printops-snapshot FILE] [--accept preset_match|mix_calibration|spool_in_sync --reason TEXT] [--json] [--secs S] [--no-bed-move] [--fix]
[--printops-snapshot FILE] [--accept preset_match|mix_calibration|spool_in_sync --reason TEXT] [--json] [--secs S] [--no-bed-move]
[--colour-delta-e N] [--fix [--fix-max-delta-e N]]
```

`--fix` adds the thumbnails the G-code lacks, in place (header only; mtime kept, a current slice-warnings record follows the new SHA-256), then runs the checks. It is the only option that writes.
`--fix` first adds the thumbnails the G-code lacks, in place (header only; mtime kept, a current slice-warnings record follows the new SHA-256), and, with a live `--printer`, rewrites the file's filament colours to the ones the printer reports; then it runs the checks. It is the only option that writes.

| Check | When | FAIL means |
|---|---|---|
| `thumbnails` | always | The G-code lacks preview images its `; thumbnails =` spec asks for (48x48, 96x96 and 300x300 PNG when it names none). The line names the exact fix, `3d gcode-thumbnails add 'FILE'`; `--fix` runs it. [Layers](gcode-thumbnails.md#layers-a-thumbnail-cannot-be-missing) add thumbnails earlier, so this FAIL means the G-code came from outside them. |
| `printer` | `--printer` | The printer is printing, paused, busy, or its state cannot be read. |
| `bed` | `--printer` with `watch.bed.enabled` (SKIP otherwise) | The camera sees objects on the bed or the build plate missing against the empty-bed reference; WARN when it cannot tell (no reference at this Z, plate out of view, camera down). See [Bed state](printer.md#bed-state). On an idle printer with `watch.bed.auto_view` (the U1: its camera sees 15 % of the plate at the Z 200 rest) the bed is moved in Z only to where the whole plate is in view, judged there and put back, and the line names the Z used; never while printing/paused, never homing; `--no-bed-move` judges at the current Z. |
| `slot Tn` | `--printer` on a printer with per-head data (Snapmaker U1) | Tool *n* is used by the G-code but head T*n+1* is empty, or its colour/material differs from `; filament_colour` / `; filament_type`. |
| `slot Tn` | `--printer` on a printer with per-head data (Snapmaker U1) | Tool *n* is used by the G-code but head T*n+1* is empty, its material differs from `; filament_type`, or its colour is further than `--colour-delta-e` (CIEDE2000, default 10) from `; filament_colour`. The line shows the distance; `--fix` aligns the file with the printer, [`3d printer slot set`](printer.md#slot-colour) the printer with the file. |
| `axis travel` | always (SKIP when the limits are unknown) | A move, or the bulge of an arc, leaves the printer's X/Y travel anywhere in the file. See [`3d gcode-bounds`](gcode-bounds.md). |
| `print area` | always | A plate move leaves the G-code's `; printable_area`, or an extruding path crosses `; bed_exclude_area` (vendor blocks exempt: `;TYPE:Custom`; on Bambu Studio G-code everything outside its object blocks, wipe tower and features — see [`3d gcode-bounds`](gcode-bounds.md)). |
| `macros` | when the G-code calls macros | A macro (PAUSE, a vendor toolchange, …) is not in the printer's `trusted_macros`: its moves cannot be checked. |
Expand Down Expand Up @@ -136,6 +137,9 @@ Options:
- `--bridge-max MM` — longest span the overhangs gate counts as a bridge; overrides the printer's `bridge_max_mm`: `3d print-preflight out/porch.gcode --axis-limits u1 --bridge-max 25`
- `--json` — JSON report (`checks[]` with `name`, `status`, `detail`, `lines`): `3d print-preflight out/part.gcode --printer u1 --json | jq '.checks[] | select(.status=="FAIL")'`
- `--no-bed-move` — the `bed` line judges at the current Z: the printer's bed is not moved to the best inspection Z (`watch.bed.auto_view`): `3d print-preflight out/part.gcode --printer u1 --no-bed-move`
- `--colour-delta-e N` — the `slot Tn` colour tolerance, CIEDE2000 (default 10, the `colour.warn_delta_e` of `fleet/policy.yaml`). The slot line shows the distance either way. The beige PETG of 2026-11-04 (sliced `#E8D8B0`, shown by the U1 as `#FEE5A5`) is ΔE 6.4 and passes; white against that beige is 20.0 and fails; red against blue is 52.9. A **material** mismatch is always a FAIL, whatever the colour: `3d print-preflight out/part.gcode --printer u1 --colour-delta-e 5`
- `--fix` — before the checks, first add the thumbnails the G-code lacks (in place, header only, mtime kept; no printer needed), then, with a live `--printer`, rewrite `; filament_colour` / `; extruder_colour` for every tool the G-code uses to the colour the printer reports, in the G-code **and** in its project 3MF (`--source-3mf`, else `NAME.3mf` next to the G-code; `Metadata/project_settings.config`, `Metadata/plate_N.json`). The toolpath bytes, mode and mtime stay; `NAME.slice-warnings.json` follows the new G-code SHA-256 and `NAME.manifest.json` gets the new slot colour; the old and new SHA-256 of every rewritten file go to stderr (the file's hash changed: upload it again). A head further than `--fix-max-delta-e` away is refused with nothing written (another filament, not the same one reported differently). Without a live `--printer` (none given, `--offline`, an offline printer) only the thumbnails are added and stderr says the colours were not aligned. It is the one option that writes: `3d print-preflight out/plate.gcode --printer u1 --fix`
- `--fix-max-delta-e N` — the largest ΔE `--fix` rewrites across (default 30): `3d print-preflight out/plate.gcode --printer u1 --fix --fix-max-delta-e 40`
- `--secs S` — Creality: seconds to listen for pushed state: `3d print-preflight out/roof.gcode --printer k1se --secs 5`
- `--printops-api URL` — 3d-api for the PrintOps gates (default `$PRINTOPS_API`, else `printops.yaml`): `3d print-preflight out/part.gcode --printer u1 --printops-api http://192.168.0.39:8765`
- `--offline` — no network: no printer contact, PrintOps state from the snapshot (the slicer hook mode); together with `--printops-api` it is a usage error: `3d print-preflight out/part.gcode --offline`
Expand Down
63 changes: 63 additions & 0 deletions docs/commands/printer.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ Epic: [#90](https://github.com/alex-mextner/3d-cli/issues/90).
3d printer status NAME [--json] [--secs S]
3d printer upload NAME FILE.gcode [--as REMOTE]
3d printer start NAME FILE.gcode --yes [--as REMOTE] [--source-3mf X.3mf] [--bridge-max MM] [--force] [--secs S] [--no-bed-move]
[--colour-delta-e N] [--fix-colour [--fix-max-delta-e N]]
3d printer slot set NAME T<n> --material M --color #RRGGBB [--brand B] [--sub-type S] [--dry-run] [--force-official] [--secs S] [--json]
3d printer slot show NAME [--secs S] [--json]
3d printer cancel NAME --yes
3d printer evidence NAME [--job ID | --last] [--out DIR] [--gcode FILE.gcode] [--note TEXT] [--klippy-log FILE] [--job-file FILE]
3d printer guard NAME --job-id ID --yes [--interval S] [--evidence DIR]
Expand Down Expand Up @@ -86,6 +89,56 @@ Options:
head-sequence check on it and `3d halves-check` (without it, the head-sequence check uses
`NAME.3mf` next to `NAME.gcode` when present).
`3d printer start u1 out/base.gcode --yes --source-3mf print/base.3mf`
- `--colour-delta-e N` — `start`: the preflight `slot Tn` colour tolerance, CIEDE2000, default 10
(see [Slot colour](#slot-colour)). `3d printer start u1 out/plate.gcode --yes --colour-delta-e 8`
- `--fix-colour` — `start`: first rewrite the file (and its project 3MF) to the colours the printer
reports, print the old/new SHA-256, upload the fixed file, then run the preflight and start.
`3d printer start u1 out/plate.gcode --yes --fix-colour`
- `--fix-max-delta-e N` — the largest ΔE `--fix-colour` rewrites across (default 30).
`3d printer start u1 out/plate.gcode --yes --fix-colour --fix-max-delta-e 40`

## Slot colour

The preflight `slot Tn` line compares the colour the slicer wrote (`; filament_colour`) with the
colour the printer reports for that head by **CIEDE2000 ΔE**, default tolerance 10
(`--colour-delta-e`), and prints the distance. The same beige PETG is `#E8D8B0` in the slice and
`#FEE5A5` on the U1 screen: ΔE 6.4, PASS. A material mismatch is always a FAIL. Three ways out of a
real mismatch, none of them `--force`:

```bash
# 1. the file is wrong about the colour: rewrite it at the source (G-code + project 3MF + sidecars)
3d print-preflight out/plate.gcode --printer u1 --fix
3d printer start u1 out/plate.gcode --yes --fix-colour # rewrite, upload the fixed file, preflight, start
# 2. the printer is wrong about what is loaded: write the head's record (idle printers only)
3d printer slot set u1 T4 --material PETG --color '#E8D8B0' --brand Elegoo --dry-run
3d printer slot set u1 T4 --material PETG --color '#E8D8B0' --brand Elegoo
# 3. load the filament the file was sliced for
```

`--fix` / `--fix-colour` set entry *n* of `filament_colour` and `extruder_colour` to the printer's
colour for every tool the G-code uses, in the G-code and in its project 3MF
(`Metadata/project_settings.config`, `plate_N.json`), keep mode and mtime, move
`NAME.slice-warnings.json` to the new G-code SHA-256 and update `NAME.manifest.json`, and print the
old and new SHA-256 of each file (the hash is part of the stored file name, hence the upload in
`start --fix-colour`). A head whose colour is further than `--fix-max-delta-e` (default 30) from the
slice is refused with nothing written: that is another filament, and relabelling it would hide a
wrong-colour print.

`slot set NAME T<n> --material M --color #RRGGBB [--brand B] [--sub-type S] [--dry-run]
[--force-official]` is `3d printers filament set NAME/T<n>` (`--brand` is its `--vendor`); `T<n>`
is the label the printer shows (U1: T1..T4 = G-code tools T0..T3). `slot show NAME` reads the heads.

**What the Snapmaker U1 accepts** (Snapmaker/u1-klipper `print_task_config.py`, issue #400): the
touchscreen's own Klipper command `SET_PRINT_FILAMENT_CONFIG CONFIG_EXTRUDER=<i> VENDOR=… FILAMENT_TYPE=…
FILAMENT_SUBTYPE=… FILAMENT_COLOR_RGBA=RRGGBBFF`, sent through `POST /printer/gcode/script` (no
webhook endpoint exists for it) and read back from `print_task_config`. It needs vendor, type and
sub-type together (defaults Generic / Basic), persists to `print_task.json`, resets the head's
pressure advance (the answer prints the K it has now), and refuses a head with official RFID filament
unless `--force-official`. 3d refuses before sending when the fleet entry is not `access: full`
(read-only and offline printers), while printing or paused, or when Klipper is not ready. Not used:
the RFID path `FILAMENT_DT_UPDATE` / the Extended Firmware's `POST /printer/filament_detect/set`
(it marks the slot official, which locks later writes). Other adapters (Creality) have no
per-head write: `slot set` exits 2 and `slot show` is the read-only comparison.

## Examples

Expand Down Expand Up @@ -827,6 +880,16 @@ RIG_HATCH_REQUEST_3D_PRINTER_START_FORCE="loaded_filament_match: slice has no co
`RIG_HATCH_REQUEST_3D_PRINTER_START_FORCE=approved:hgr-…`, redeems.
- Approved: the start continues, prints `FORCED: starting despite preflight FAIL (...)`, and the
helper appends the use to `~/.config/agent-tools/overrides.log`.
- If the daemon could not even post the card (`tg-ctl ask-forward not-posted … daemon declined:
reason=send-failed`: its Telegram `sendMessage` failed, 2026-11-04 it was `getaddrinfo EAI_AGAIN
api.telegram.org`, a resolver blip while plain `tg` worked) nothing was posted, so the ask is
repeated after 3, 10 and 30 s (four requests in all, each reported on stderr). If it still
fails the start is refused with the way around Telegram: the file is already stored on the
printer, so start it **from the printer screen** (Files, then Print) — the screen does not run
these checks, do it only when the listed FAILs are understood — and with the log to read
(`~/.config/tg-cli/tg-ctl.*.log`, lines `sendMessage failed` / `ask-forward dropped`).
The daemon itself sends once and never retries (tg-cli `forwardAndPostQuestion`), which is why
the retry lives here.
- Without `--force`, without the variable, denied, timed out, or without a trusted `tg-ctl` /
helper (`~/dev/agent-tools`, then `~/xp/agent-tools`, owned by you or root and not
group/world-writable): exit 1, nothing is sent to the printer.
Expand Down
Loading
Loading