Skip to content

DYMO LabelManager 280 label printing - #78

Open
abbyfluoroethane wants to merge 3 commits into
develfrom
label/dymo-labelmanager-280
Open

DYMO LabelManager 280 label printing#78
abbyfluoroethane wants to merge 3 commits into
develfrom
label/dymo-labelmanager-280

Conversation

@abbyfluoroethane

Copy link
Copy Markdown
Member

Summary

  • Adds a compact label format sized for the DYMO LabelManager 280 (12mm/½in D1 tape, its max width) alongside the existing full-size QR label, on both the part and inventory /label routes.
  • Adds a direct-print flow: a "PRINT LABEL · DYMO" button opens an in-app printer picker and sends the job straight to a chosen CUPS printer, with no browser print dialog.

What's in here, and why it looks the way it does

Everything here was tuned against a real LabelManager 280 over USB, not guessed from datasheets — several details only became obvious with a printer in hand:

  • Geometry: the driver's native page is portrait (narrow = 12mm tape width, long = feed direction). Content is rotated 90° so it reads correctly once the tape is peeled off, and the page size snaps to one of the driver's own presets (2in or 3.5in, picked per-label from content length so short labels don't feed blank tape).
  • Machine-readable code: QR → unreadable at this print width (~1.5px/module) → a 1D barcode → bars bled together at print resolution → Data Matrix, which needs far fewer modules per side for a short identifier and is what actually scans reliably.
  • Two render paths, on purpose: the browser-print route (label_dymo.html, HTML/CSS + window.print()) and a new native core/dymo_label.py (reportlab) for direct-print, so OPAL doesn't need to bundle a headless browser at runtime. This is a deliberate tradeoff — the layout is maintained in two places instead of one — discussed and chosen explicitly over the Playwright alternative.
  • Direct-print talks to CUPS via lpstat/lp (core/printing.py) rather than a client-side print API, since OPAL runs on one machine and "print" means print to whatever's attached to that machine.

Test plan

  • uv run pytest — 932 passed, 1 skipped
  • uv run ruff check src/ tests/ — clean
  • Physical test prints against a DYMO LabelManager 280 over USB, both render paths, both page-length presets, short and long content, Data Matrix scan-verified with a phone camera

https://claude.ai/code/session_014jPbhRuDUZLie159gQxTDZ

abbyfluoroethane and others added 3 commits August 15, 2026 18:25
The existing part/inventory label templates target a 4in-class label
with a QR code, which doesn't fit the LabelManager 280 (max 12mm/1/2in
D1 tape, 180dpi over USB) — a QR isn't legible at that height. Add
fmt=dymo to /label, rendering a compact single-line text label sized
for the tape instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014jPbhRuDUZLie159gQxTDZ
Physical testing against a real LabelManager 280 over USB surfaced
several things the first pass got wrong that couldn't have been caught
without a printer in hand:

- The driver's native page is portrait (narrow = 12mm tape width, long
  = feed direction); content has to be pre-rotated -90deg in CSS to
  read correctly off the tape, and the @page size has to match one of
  the driver's declared presets exactly.
- QR is unreadable at this print width (~1.5px/module at 180dpi) — a
  1D barcode was tried next but its narrow bars bled together at this
  resolution too. Data Matrix needs far fewer modules per side for a
  short identifier (~16x16 vs QR's 21x21+) and is what actually scans.
- A fixed-width content box left blank tape wherever content was
  shorter than the cap; sizing to content, and picking between the two
  validated page-length presets based on actual label content, fixes
  the excess tape.

Also switches the label format itself to PN / name / SN·LOT · OPAL# ·
QTY per request, applies to both the part and inventory label routes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014jPbhRuDUZLie159gQxTDZ
Browsers don't expose a printer-selection API to web pages, so getting
"pick a printer in our own UI, hit Print, job just goes" requires the
server to render the label and dispatch it itself. Adds:

- core/dymo_label.py: reportlab-based native PDF renderer for the DYMO
  format, geometry carried over from the hardware-validated HTML/CSS
  version (rotation, page presets, cross-tape budget). Data Matrix goes
  through pystrich's own get_pilimage() — its `.matrix` attribute turned
  out to be stale data that dropped the finder-pattern border and only
  produced a scannable code once discovered and fixed on hardware.
- core/printing.py: CUPS printer discovery/dispatch via `lpstat`/`lp`.
  OPAL runs on one machine, so this talks straight to that machine's
  print system rather than through a client-side dialog.
- /label/printers and /label/print-direct web routes, a shared modal
  partial, and wiring on both the part and inventory detail pages.

This is a second implementation of the label layout alongside the HTML
one (a deliberate tradeoff over bundling a headless browser at runtime)
and was validated with real test prints against the LM280 over USB.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014jPbhRuDUZLie159gQxTDZ
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant