Add portable Line-us pen-arm support to ink-signature pipeline

Adds a second machine class (small fan-shaped reach arm) alongside the
CR-10/AxiDraw rectangular-bed plotters, so wet signatures can be produced
while away from the home station.

ink_signature_plotter.py:
- PlotterConfig gains dialect (marlin|lineus) + name; new LineUsConfig
  (native units, pen height = per-move Z, reach annulus from shoulder pivot).
- Named machine profiles (cr10 default, axidraw, lineus) via load_profile().
- bed_mm_to_lineus_units(), check_reach() (annulus for lineus, rectangle for
  marlin), compute_jig_offset_for_box() (solves jig from the ACTUAL fitted ink
  extent so a wide cell line doesn't over-constrain a small arm).
- emit_gcode() dispatches to emit_marlin_gcode()/emit_lineus_gcode().
- send_lineus(): WiFi TCP 1337 (NUL-terminated, ok-acked) or USB serial,
  dry_run=True default (same gating as the CR-10 path).

ink_signature_cli.py: --profile, --solve-jig (auto-applies jig offset),
--lineus-host/--lineus-usb, reach-check that refuses to --plot out-of-reach
on Line-us.

Tests: 43 checks (was 30) covering profiles, reach check, jig solve, lineus
emitter, dry-run sender. Docs updated with profiles + portable workflow.
This commit is contained in:
justin 2026-06-07 03:45:46 -05:00
parent aafa76df83
commit 894d989445
4 changed files with 595 additions and 19 deletions

View file

@ -74,6 +74,59 @@ uses) places the ink exactly on the cert line.
5. **Linux:** the CR-10 enumerates as a CH340 serial device (`/dev/ttyUSB0`,
115200 baud). No drivers needed — we stream G-code ourselves.
## Machine profiles
The pipeline is machine-agnostic via named profiles (`--profile`):
| Profile | Machine | Dialect | Pen lift | Bed/reach |
|---|---|---|---|---|
| `cr10` (default) | Creality CR-10 V2 (home station) | Marlin | Z move | 300×300 mm rectangular |
| `axidraw` | AxiDraw / iDraw A4 pen plotter | Marlin/GRBL | servo (M280) | 210×297 mm rectangular |
| `lineus` | Line-us folding pen-arm (pocket) | Line-us GCode | pen Z per move | small fan-shaped annulus |
`load_profile()` returns a `PlotterConfig`; CLI flags (`--jig-x/-y`,
`--pen-down/-up`, `--servo-pen`) override individual fields.
## Portable option: Line-us (pocket pen-arm)
When away from the home CR-10, the **Line-us** is a palm-size folding 2-link arm
that draws with a real pen. It speaks its own GCode subset over **WiFi (TCP
1337)** or **USB serial**, acks each command with `"ok"`, and encodes pen height
as the **Z value of each move** (low Z = pen down, high Z = pen up), not mm.
Because the arm's reachable area is a **small fan-shaped annulus** in front of
its shoulder pivot (not a rectangular bed), two things differ from the CR-10:
1. **Reach checking.** `check_reach()` validates every planned point against the
annulus (`reach_min_units..reach_max_units` from the shoulder). The CLI prints
the report and **refuses to `--plot` on Line-us if any point is out of reach**
(an unreachable point would distort the signature).
2. **Jig solving.** The signature must be slid into the sweet spot. `--solve-jig`
calls `compute_jig_offset_for_box(box, cfg, vector=...)`, which aims the centre
of the **actual fitted ink extent** (not the full cell, which can be far wider
than the signature) at the annulus mid-radius and reach-checks the result. It
then applies the computed `jig_x_mm/jig_y_mm` automatically.
A small registration jig (a corner stop + a marked cell on a card) holds the
paper so the solved offset is repeatable.
```bash
# Generate gcode + preview for Line-us, auto-solving the jig (safe, dry-run):
python scripts/workers/ink_signature_cli.py --order CO-XXXX --doc cms10114 \
--profile lineus --solve-jig
# Plot over WiFi (default host line-us.local:1337), sheet in the jig:
python scripts/workers/ink_signature_cli.py --order CO-XXXX --doc cms10114 \
--profile lineus --solve-jig --plot --lineus-host line-us.local
# Or plot over USB serial instead of WiFi:
python scripts/workers/ink_signature_cli.py --order CO-XXXX --doc cms10114 \
--profile lineus --solve-jig --plot --lineus-usb --port /dev/ttyACM0
```
`send_lineus()` defaults to `dry_run=True` (same gating as the CR-10 path); it
only moves the arm with `--plot`.
## Calibration
```bash
@ -103,10 +156,14 @@ python scripts/workers/ink_signature_cli.py --order CO-XXXX --doc cms10114 \
## Verification (no hardware required)
`scripts/tests/test_ink_signature.py` (30 checks) proves the geometry:
`scripts/tests/test_ink_signature.py` (43 checks) proves the geometry:
- strokes fit inside the anchor box, aspect preserved, Y flipped, ink on the rule
- PDF-pt → bed-mm uses the jig offset + correct unit scale, stays on the bed
- G-code framing (pen up/down, feeds, park), servo mode, over-bed warning
- machine profiles (cr10 / axidraw / lineus) load with the right dialect/bed
- Line-us reach check rejects out-of-reach plots; `--solve-jig` brings the
signature into reach; the Line-us emitter homes, encodes pen Z, and warns
on out-of-reach; `send_lineus()` is dry-run safe over both TCP and USB
- `render_signature_on_pdf` stamps the strokes onto the **real CMS-10114 cert
page** inside the signature cell (label ≤ y, ink ≥ bottom rule)