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:
@@ -0,0 +1,99 @@
|
||||
"""
|
||||
Dump the full DEVMODE of the PT-E560BT driver.
|
||||
|
||||
The printed length is far shorter than the page we send, which suggests the
|
||||
driver is cropped or scaled by a setting of its own. This prints every field
|
||||
that could cause that, so the behaviour can be explained rather than guessed.
|
||||
"""
|
||||
|
||||
import win32print
|
||||
|
||||
PRINTER = "Brother PT-E560BT"
|
||||
|
||||
ORIENTATION = {1: "portrait", 2: "landscape"}
|
||||
|
||||
FIELDS_OF_INTEREST = [
|
||||
"PaperSize",
|
||||
"PaperLength",
|
||||
"PaperWidth",
|
||||
"Orientation",
|
||||
"Scale",
|
||||
"Copies",
|
||||
"DefaultSource",
|
||||
"PrintQuality",
|
||||
"Color",
|
||||
"Duplex",
|
||||
"TTOption",
|
||||
"Collate",
|
||||
"FormName",
|
||||
"LogPixels",
|
||||
"BitsPerPel",
|
||||
"Nup",
|
||||
"DisplayFlags",
|
||||
"DisplayFrequency",
|
||||
"ICMMethod",
|
||||
"ICMIntent",
|
||||
"MediaType",
|
||||
"DitherType",
|
||||
"Reserved1",
|
||||
"Reserved2",
|
||||
"PanningWidth",
|
||||
"PanningHeight",
|
||||
]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
info = win32print.GetPrinter(win32print.OpenPrinter(PRINTER), 2)
|
||||
dm = info.get("pDevMode")
|
||||
|
||||
print(f"Printer : {PRINTER}")
|
||||
print(f"Driver : {info.get('pDriverName')}")
|
||||
print(f"Port : {info.get('pPortName')}")
|
||||
|
||||
print(f"\nDEVMODE size : {dm.Size} bytes")
|
||||
print(f"DriverExtra : {dm.DriverExtra} bytes")
|
||||
print(f"Fields set (bitset): 0x{dm.Fields:x}")
|
||||
|
||||
print("\nFields of interest:")
|
||||
for name in FIELDS_OF_INTEREST:
|
||||
if not hasattr(dm, name):
|
||||
continue
|
||||
try:
|
||||
value = getattr(dm, name)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
print(f" {name:<18}: <error {exc}>")
|
||||
continue
|
||||
extra = ""
|
||||
if name == "Orientation":
|
||||
extra = f" ({ORIENTATION.get(value, '?')})"
|
||||
elif name in ("PaperLength", "PaperWidth") and isinstance(value, int):
|
||||
extra = f" ({value / 10:.1f} mm)"
|
||||
print(f" {name:<18}: {value}{extra}")
|
||||
|
||||
# Any other integer attributes the struct exposes.
|
||||
print("\nAll remaining numeric fields:")
|
||||
shown = set(FIELDS_OF_INTEREST)
|
||||
for name in sorted(dir(type(dm))):
|
||||
if name.startswith("_") or name in shown:
|
||||
continue
|
||||
try:
|
||||
value = getattr(dm, name)
|
||||
except Exception: # noqa: BLE001
|
||||
continue
|
||||
if isinstance(value, int) and value != 0:
|
||||
print(f" {name:<20}: {value}")
|
||||
|
||||
# Explain the relationship between page, printable area and paper length.
|
||||
print("\nInterpretation:")
|
||||
pl = dm.PaperLength / 10
|
||||
pw = dm.PaperWidth / 10
|
||||
print(f" paper wxh as stored : {pw:.1f} x {pl:.1f} mm "
|
||||
f"(orientation {ORIENTATION.get(dm.Orientation, '?')})")
|
||||
if dm.Orientation == 2:
|
||||
print(f" rotated for printing: {pl:.1f} mm along tape x {pw:.1f} mm across")
|
||||
print(f" dmScale : {dm.Scale}% (100 = no driver scaling)")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user