Add P-touch Labels for Asset: 63x20 mm label format for PT-E560BT

New three-column label format for Brother P-touch EDGE PT-E560BT asset tags:
logo | three static lines + variable field | QR code.

Layout and rendering
- ptouch_label.py: geometry engine with PDF and preview renderers. Both
  consume the same geometry, so the preview matches the print exactly.
- Code symbols are snapped to whole printer dots per module. Fractional
  modules are the usual reason a small QR code will not scan.
- QR is maximised to the printable height by default (16.4 mm, 4 dots per
  module) instead of a fixed 16 mm box (12.3 mm, 3 dots per module).
- Code payload is composed as prefix + inventory + suffix. A legacy
  {inventory} template is migrated on first save.

Hardware handling
- print_area.py reads the driver's real printable area via GetDeviceCaps.
  The PT-E560BT head is 128 dots at 180 dpi, so the maximum print height is
  18.06 mm even on 24 mm tape; the driver reports media as 65x24 mm but only
  about 61x18 mm is reachable. Content is fitted to the reachable area,
  otherwise the driver silently crops it.
- set_printer_resolution.py reads/switches the driver's resolution mode. The
  driver offers 180x180 (symmetric) and 180x360 (anisotropic, 2x across the
  tape); the anisotropic mode scaled output incorrectly.

GUI
- label_printer_ptouch_gui.py: 600x300 two-column panel - controls on the
  left, live preview on the right. Advanced settings for logo, static lines,
  code prefix/suffix and error correction.
- native_file_dialog.py: the standard Windows file picker, run in a
  subprocess because Tk and Kivy both want the main event loop.

Printing
- Print failures are reported honestly. print_to_printer previously returned
  True unconditionally, so a failed print claimed success and was logged as
  SUCCESS.
- SumatraPDF exit codes are now checked (4 = no printer, 5 = driver error).
- The PDF is sent with noscale so driver-side scaling cannot distort the QR
  module pitch.

Tests
- validate_ptouch_layout.py decodes the QR back out of the rendered preview
  module by module, proving the preview and PDF are the same layout.
- test_ptouch_pipeline.py writes to a temp config so it cannot overwrite
  operator settings.
- test_gui_layout.py exercises the real app class headlessly.
- test_print_reporting.py covers the failure paths.

Diagnostics: check_print_area.py, check_print_geometry.py, check_devmode.py,
print_calibration.py, print_measure_scale.py, capture_print_job.py.
This commit is contained in:
2026-09-25 17:30:11 +03:00
parent 58082ed171
commit 93afa3c417
22 changed files with 3877 additions and 23 deletions
+160
View File
@@ -0,0 +1,160 @@
# PT-E560BT Label Format (63 x 20 mm)
Three-column label layout for the Brother P-touch EDGE **PT-E560BT**.
```
0 20 mm 45 mm 63 mm
+---------+------------+--------+ 0
| | static 1 | Q R |
| LOGO | static 2 | C O | 1 mm non-printable gutter
| 16x16 | static 3 | D E |
| | VARIABLE | |
+---------+------------+--------+ 20 mm
20 mm 25 mm 18 mm
```
## Interface
Run the new GUI:
```bash
python label_printer_ptouch_gui.py
```
| Element | Purpose |
|---|---|
| **Inventory number** | The single variable value per label. Becomes the 4th text line **and** is embedded in the QR code. |
| **PRINT LABEL** | Generates the PDF and sends it to the selected printer. |
| **Preview area** | Renders the label at true print proportions. Updates as you type. |
| **Advanced settings** | Logo image, the three static text lines, QR prefix/suffix, QR error correction. Saved to `label_config.json`. |
The old three-row format is still available via `label_printer_gui.py` and is
unchanged.
## Hardware constraints
Verified against Brother's published specifications *and* the installed Windows
driver (via `GetDeviceCaps`), which agree:
| Spec | Value | Consequence |
|---|---|---|
| Print resolution | **180 dpi**, 128-dot head | 128 / 180 x 25.4 = **18.06 mm** is the hard height limit |
| Max print height | **18.0 mm**, only with 24 mm tape | A 20 mm design cannot fit; it is not a margin setting |
| Max tape width | 24 mm | **24 mm tape required** |
Because the head reaches only ~18 mm of the 24 mm tape, ~3 mm is unused on each
side. The label content is sized to the driver's **printable area**, read at
runtime by `print_area.py`, rather than to the nominal media size.
### Driver resolution modes
The Brother driver offers two modes, and the choice matters:
| Mode | xdpi | ydpi | Notes |
|---|---|---|---|
| 1 | 180 | 180 | symmetric - use this |
| 2 | 180 | 360 | **anisotropic**, 2x across the tape |
In the anisotropic mode the driver reports twice the horizontal dots, and a page
authored in millimetres can be scaled by the wrong factor and print short.
`set_printer_resolution.py` reads and switches this setting:
```bash
python set_printer_resolution.py # show current mode
python set_printer_resolution.py --mode 180 # symmetric (recommended)
python set_printer_resolution.py --restore # undo
```
## Why the code is dot-aligned
A thermal head cannot print a fraction of a dot. If a QR symbol is scaled to an
arbitrary size, its modules end up unevenly sized (some 3 dots wide, some 4),
which is the usual reason a small QR code will not scan.
`build_code_bitmap()` snaps the symbol to an integer number of printer dots per
module and returns the resulting exact physical size. At 16 mm available and 29
modules (a typical short payload), that is **3 dots per module = 12.28 mm**,
which scans comfortably.
`code_density_warning()` estimates density for the current payload and the GUI
shows a warning when the code becomes too dense to be reliable, instead of
silently printing an unscannable label.
## Code payload
The code is composed as **fixed prefix + inventory + fixed suffix**, configured in
Advanced settings:
| Prefix | Inventory | Suffix | Encoded |
|---|---|---|---|
| `INV-` | `1234` | *(empty)* | `INV-1234` |
| `INV-` | `1234` | `-A` | `INV-1234-A` |
| *(empty)* | `1234` | *(empty)* | `1234` |
The `Encodes:` line in Advanced settings shows the exact payload live as you type,
so the result can be confirmed before printing.
A legacy `{inventory}` template (e.g. `INV:{inventory}`) is still accepted and is
migrated to prefix/suffix on first save, so older config files keep working.
**Keep the payload short.** The QR is maximised to fill the printable height
(~16.4 mm at 29 modules), which gives **4 printer dots per module** for a short
payload. Longer strings raise the QR version, reducing dots per module; the GUI
warns before printing a code that would be too dense to scan reliably. Error
correction `L` allows a longer string than `M`/`Q`/`H` at the same physical size.
## Maximising the QR
By default `qr_maximize` is enabled: the code column takes the full printable
height so the symbol is as large as the hardware allows, and the text column
shrinks to make room (never below `MIN_TEXT_COL_MM`, so the three static lines
keep fitting).
| Setting | QR size | Dots/module |
|---|---|---|
| `qr_maximize: false` (16 mm box) | 12.28 mm | 3 |
| `qr_maximize: true` (default) | 16.37 mm | 4 |
The symbol is flush to the right edge of the printable area when maximised. If
your unit feeds the tape slightly off-centre, reduce `qr_box_mm` and set
`qr_maximize: false` to leave clearance.
## Logo
- Formats: PNG, JPG, JPEG, BMP, GIF. Greyscale and alpha are handled (transparent
areas are flattened to white).
- The logo is scaled to fit the 16 x 16 mm cell preserving aspect ratio.
- Thermal transfer printing is 1-bit, so logos should be **black-and-white line
art**. Photographs dither poorly. `logo_dither` controls this.
## Files
| File | Role |
|---|---|
| `ptouch_label.py` | Layout engine: geometry, code generation, PDF + preview renderers |
| `label_config.py` | Settings persistence (`label_config.json`) |
| `label_printer_ptouch_gui.py` | Kivy GUI (this format) |
| `print_label.py` | Printer discovery and silent printing (extended) |
| `validate_ptouch_layout.py` | Geometry + QR round-trip validation |
| `test_ptouch_pipeline.py` | End-to-end test (no hardware needed) |
## Tests
```bash
python validate_ptouch_layout.py # page size, boxes, QR round-trip
python test_ptouch_pipeline.py # config, PDF, printing, QR scaling
```
`validate_ptouch_layout.py` re-reads the rendered QR symbol out of the preview
image, module by module, and compares it against the source matrix. That is what
proves the preview and the PDF are the same layout, and that the symbol is
geometrically intact after dot-alignment.
## Printing notes
Only SumatraPDF printing honours the scale setting. The PDF is sent with
`-print-settings noscale` because driver-side scaling ("fit to page") would
distort the QR module pitch and break scanning.
In the printer driver, set the media to the **63 x 20 mm** continuous tape and
disable any auto-rotate. The TZe tape must be **24 mm**.