93afa3c417
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.
100 lines
3.5 KiB
Python
100 lines
3.5 KiB
Python
"""
|
|
Compare the old and new page geometry against the driver's real media.
|
|
|
|
The PT-E560BT driver reports 24 x 65 mm (landscape). This shows what the driver
|
|
receives in each configuration and whether it has to rescale or rotate.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
from label_config import load_config
|
|
from ptouch_label import LabelConfig, PtouchLabelRenderer, compute_geometry
|
|
from reportlab.lib.units import mm
|
|
from pypdf import PdfReader
|
|
|
|
# What the installed driver advertises (queried earlier from the Windows spooler).
|
|
DRIVER_W, DRIVER_H = 65.0, 24.0
|
|
|
|
|
|
def describe(label, cfg, inventory="INV-1"):
|
|
renderer = PtouchLabelRenderer(cfg)
|
|
path = os.path.join("pdf_backup", f"_geom_{label}.pdf")
|
|
os.makedirs("pdf_backup", exist_ok=True)
|
|
renderer.render_pdf(inventory, path)
|
|
|
|
page = PdfReader(path).pages[0]
|
|
pw = float(page.mediabox.width) / mm
|
|
ph = float(page.mediabox.height) / mm
|
|
ox, oy = cfg.origin_mm
|
|
|
|
print(f"\n--- {label} ---")
|
|
print(f" match_driver_media : {cfg.match_driver_media}")
|
|
print(f" PDF page sent : {pw:.2f} x {ph:.2f} mm")
|
|
print(f" label content : {cfg.page_width_mm:.2f} x {cfg.page_height_mm:.2f} mm")
|
|
print(f" content origin : ({ox:.2f}, {oy:.2f}) mm")
|
|
|
|
page_rotated = abs(pw - DRIVER_H) < 0.5 and abs(ph - DRIVER_W) < 0.5
|
|
exact_match = abs(pw - DRIVER_W) < 0.5 and abs(ph - DRIVER_H) < 0.5
|
|
|
|
print(f" driver media : {DRIVER_W:.2f} x {DRIVER_H:.2f} mm")
|
|
if exact_match:
|
|
print(" driver action : NONE (exact size match)")
|
|
elif page_rotated:
|
|
print(" driver action : rotate 90 deg, then fit")
|
|
else:
|
|
print(" driver action : RESCALE + reposition (distorts geometry)")
|
|
|
|
x_scale = DRIVER_W / pw if not exact_match else 1.0
|
|
y_scale = DRIVER_H / ph if not exact_match else 1.0
|
|
if not exact_match:
|
|
print(f" implied scale : x{x_scale:.3f} by y{y_scale:.3f}")
|
|
|
|
os.remove(path)
|
|
return exact_match
|
|
|
|
|
|
def main() -> int:
|
|
saved = load_config()
|
|
# Use the saved settings so this reflects what will really be printed.
|
|
print("=" * 70)
|
|
print("PRINT GEOMETRY CHECK")
|
|
print("=" * 70)
|
|
print(f"Saved settings: match_driver_media={saved.match_driver_media}")
|
|
print(f" logo_path={saved.logo_path or '(none)'}")
|
|
print(f" static_lines={saved.static_lines}")
|
|
|
|
old = LabelConfig()
|
|
old.match_driver_media = False
|
|
old_ok = describe("OLD (page 63x20, no media match)", old)
|
|
|
|
new = saved
|
|
new.match_driver_media = True
|
|
new_ok = describe("NEW (page 65x24 matching driver media)", new)
|
|
|
|
print("\n" + "=" * 70)
|
|
print(f"OLD config needs driver rescaling : {not old_ok}")
|
|
print(f"NEW config is an exact size match : {new_ok}")
|
|
print("=" * 70)
|
|
|
|
if new_ok:
|
|
cfg = saved
|
|
geo = compute_geometry(cfg)
|
|
ox, oy = cfg.origin_mm
|
|
print("\nContent placement on the 65 x 24 mm page:")
|
|
print(f" label occupies x = {ox:.2f} .. {ox + cfg.page_width_mm:.2f} mm")
|
|
print(f" y = {oy:.2f} .. {oy + cfg.page_height_mm:.2f} mm")
|
|
print(f" slack left/right : {ox:.2f} mm each side")
|
|
print(f" slack top/bottom : {oy:.2f} mm each side")
|
|
print(f" expected printed : {cfg.page_width_mm:.0f} wide x "
|
|
f"{cfg.page_height_mm:.0f} mm long")
|
|
|
|
return 0 if new_ok else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|