ink-signature: pen-plotter pipeline for original wet-ink CMS signatures
The Standard no-login CMS path needs an ORIGINAL ink signature on paper (CMS-10114: 'Stamped, faxed or copied signatures will not be accepted'). This adds a pipeline to redraw the provider's own captured strokes in real ink with a pen on a CR-10 V2 (or any Marlin/GRBL machine) — original, in ink, never copied. - migration 090: esign_records.signature_vector (JSONB stroke paths, 0..1). - signing page now captures normalized stroke paths alongside the PNG; API stores a size-bounded vector for drawn signatures. - ink_signature_plotter.py (hardware-independent): fit strokes to the signature anchor box, PDF-pt -> bed-mm via jig offset, emit Marlin/GRBL G-code (Z pen or M280 servo/BLTouch), SVG toolpath preview, and render_signature_on_pdf (a digital twin that proves the toolpath lands on the cert line). Gated serial sender (dry_run default). - ink_signature_cli.py: end-to-end load-record -> gcode+preview, --test-box jig calibration, --plot to stream over USB. - Corrected CMS-10114 signature anchor to sit inside the Section 4A signing cell (above the bottom rule, below the label). - docs/ink-signature-plotter.md documents the CR-10 retrofit + interpretive risk. Tests: test_ink_signature.py 30/30, test_cms10114.py 27/27, test_paper_batch.py 15/15, API tsc clean, Astro build 58 pages.
This commit is contained in:
parent
e6a630ada1
commit
b0a8563a93
8 changed files with 994 additions and 19 deletions
125
docs/ink-signature-plotter.md
Normal file
125
docs/ink-signature-plotter.md
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
# Ink-signature pen-plotter pipeline
|
||||
|
||||
Produces a **genuine original ink signature** on a printed CMS form from a
|
||||
signature the provider drew online, by redrawing their own stroke paths with a
|
||||
pen mounted on a 3-axis motion system (a Creality CR-10 V2 here; any
|
||||
Marlin/GRBL machine works).
|
||||
|
||||
## Why this exists
|
||||
|
||||
The Standard (no-login) CMS filing path mails a paper form that must carry an
|
||||
**original ink signature** — the CMS-10114 states verbatim: *"All signatures
|
||||
must be original and signed in ink. Applications with signatures deemed not
|
||||
original will not be processed. Stamped, faxed or copied signatures will not be
|
||||
accepted."* A printed/stamped digital signature image is a copy. A pen plotter
|
||||
drawing real ink onto the one original sheet is **original, in ink, never
|
||||
copied** — categorically different from the three banned methods (stamp / fax /
|
||||
photocopy).
|
||||
|
||||
> **Interpretive risk (read this):** CMS guidance does not explicitly bless or
|
||||
> ban robotic/autopen signatures for the 855/10114. A reviewer could argue a
|
||||
> machine-applied mark isn't the provider's own hand. Treat the plotter as the
|
||||
> *fast* path and keep a true wet-signature mail-out as the conservative default
|
||||
> for filings where a rejection is costly, until real-world acceptance is
|
||||
> confirmed on a few live filings.
|
||||
|
||||
## Data flow
|
||||
|
||||
```
|
||||
signature pad (online) esign_records
|
||||
capture strokes (0..1) ───────► signature_vector (JSONB) [migration 090]
|
||||
+ raster PNG ───────► signature_data (digital stamp / audit)
|
||||
signature_anchors (form's signing box)
|
||||
│
|
||||
▼
|
||||
ink_signature_plotter.py (hardware-independent, fully tested)
|
||||
fit_strokes_to_box() strokes -> PDF points, fit anchor box, flip Y, rest on rule
|
||||
pdf_points_to_bed_mm() PDF pt -> bed mm via PlotterConfig (jig offset, 1pt=0.3528mm)
|
||||
emit_gcode() -> Marlin/GRBL G-code (Z pen lift, or M280 servo/BLTouch)
|
||||
render_signature_on_pdf() -> stamp strokes onto the real PDF (visual proof)
|
||||
render_preview_svg() -> toolpath preview
|
||||
send_gcode_serial() -> stream to /dev/ttyUSB0 (gated, dry_run=True default)
|
||||
│
|
||||
▼
|
||||
ink_signature_cli.py end-to-end: load record -> gcode + preview [+ --plot]
|
||||
```
|
||||
|
||||
## Coordinate frames
|
||||
|
||||
| Frame | Origin | Units |
|
||||
|---|---|---|
|
||||
| Capture box (signature pad) | top-left | fraction 0..1 |
|
||||
| PDF / signature anchors | bottom-left | points (1in = 72pt) |
|
||||
| Plotter bed (CR-10) | homed corner (front-left) | mm (1pt = 0.35278mm) |
|
||||
|
||||
A **paper jig** (corner stop on the bed) fixes the sheet so PDF (0,0) maps to
|
||||
`(jig_x_mm, jig_y_mm)`. The signature anchor box (same one the digital stamper
|
||||
uses) places the ink exactly on the cert line.
|
||||
|
||||
## CR-10 V2 retrofit (reversible)
|
||||
|
||||
1. **Pen holder:** print a spring-loaded CR-10 pen holder (clips to the X
|
||||
carriage / hotend shroud). The spring keeps even contact pressure.
|
||||
2. **Pen-up/down:**
|
||||
- *Default (Z mode):* pen fixed; G-code lifts/drops Z (`pen_up_mm` /
|
||||
`pen_down_mm`). The spring forgives over-press.
|
||||
- *Optional (servo/BLTouch mode):* `--servo-pen` emits `M280 P0 S<angle>` to
|
||||
deploy/stow instead of moving Z. Useful if you actuate the pen with a servo
|
||||
or repurpose the BLTouch.
|
||||
3. **Registration:** tape a corner jig to the bed so every sheet sits in the
|
||||
same place. The BLTouch can probe a fixed point to set a repeatable pen-touch
|
||||
Z reference.
|
||||
4. **Pen:** a smooth gel/rollerball refill gives the most hand-written, wet-ink
|
||||
look. Avoid dry fiber tips.
|
||||
5. **Linux:** the CR-10 enumerates as a CH340 serial device (`/dev/ttyUSB0`,
|
||||
115200 baud). No drivers needed — we stream G-code ourselves.
|
||||
|
||||
## Calibration
|
||||
|
||||
```bash
|
||||
# 1. Draw just the signature-box outline on a blank sheet (dry-run first to
|
||||
# inspect the gcode/preview, then --plot with a sheet in the jig):
|
||||
python scripts/workers/ink_signature_cli.py --order CO-XXXX --doc cms10114 --test-box
|
||||
python scripts/workers/ink_signature_cli.py --order CO-XXXX --doc cms10114 --test-box \
|
||||
--plot --home --port /dev/ttyUSB0
|
||||
|
||||
# 2. Adjust --jig-x / --jig-y until the rectangle lands on the cert line, and
|
||||
# --pen-down until the pen draws cleanly without digging in.
|
||||
```
|
||||
|
||||
## Plotting a signature
|
||||
|
||||
```bash
|
||||
# Generate gcode + SVG preview (safe anywhere, no hardware):
|
||||
python scripts/workers/ink_signature_cli.py --order CO-XXXX --doc cms10114
|
||||
|
||||
# Plot it (sheet loaded in jig, calibrated):
|
||||
python scripts/workers/ink_signature_cli.py --order CO-XXXX --doc cms10114 \
|
||||
--plot --home --port /dev/ttyUSB0 \
|
||||
--jig-x 22 --jig-y 24 --pen-down -0.2
|
||||
```
|
||||
|
||||
`send_gcode_serial` defaults to `dry_run=True`; the CLI only plots with `--plot`.
|
||||
|
||||
## Verification (no hardware required)
|
||||
|
||||
`scripts/tests/test_ink_signature.py` (30 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
|
||||
- `render_signature_on_pdf` stamps the strokes onto the **real CMS-10114 cert
|
||||
page** inside the signature cell (label ≤ y, ink ≥ bottom rule)
|
||||
|
||||
Because the PDF preview and the G-code share the same `fit_strokes_to_box`
|
||||
geometry, the preview is a faithful digital twin of what the pen will draw.
|
||||
|
||||
## Files
|
||||
|
||||
| File | Role |
|
||||
|---|---|
|
||||
| `api/migrations/090_esign_signature_vector.sql` | `signature_vector` JSONB column |
|
||||
| `site/public/portal/esign/index.html` | captures stroke paths alongside the PNG |
|
||||
| `api/src/routes/portal-esign-generic.ts` | stores the (size-bounded) vector |
|
||||
| `scripts/workers/services/ink_signature_plotter.py` | geometry + G-code + preview + serial |
|
||||
| `scripts/workers/ink_signature_cli.py` | end-to-end CLI (generate / calibrate / plot) |
|
||||
| `scripts/tests/test_ink_signature.py` | hardware-free correctness tests |
|
||||
Loading…
Add table
Add a link
Reference in a new issue