new-site/docs/ink-signature-plotter.md
justin b0a8563a93 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.
2026-06-07 02:34:17 -05:00

5.7 KiB

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

# 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

# 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