Files
adaptronic_label-printer/print_label.py
adaptronic-label printer final b35f278c1e Remove GhostScript, use SumatraPDF only for printing
- Remove find_ghostscript() and print_pdf_with_ghostscript() from print_label.py
- SumatraPDF is now the primary print method with -print-settings noscale
- Fix SumatraPDF arg order: file_path moved to last position
- Add -appdata-dir so SumatraPDF reads conf/SumatraPDF-settings.txt (PrintScale=noscale)
- win32api ShellExecute kept as last-resort fallback
- Remove GhostScript bundling from LabelPrinter.spec and build_exe.py
- Add conf/ folder pre-flight check to build_exe.py
- Rebuild dist/LabelPrinter.exe (41 MB, SumatraPDF-only)
2026-02-25 10:00:56 +02:00

386 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import time
import os
import sys
import datetime
import platform
import subprocess
from print_label_pdf import PDFLabelGenerator
# Cross-platform printer support
try:
import cups
CUPS_AVAILABLE = True
except ImportError:
CUPS_AVAILABLE = False
try:
import win32api
import win32print
WIN32_AVAILABLE = True
except ImportError:
WIN32_AVAILABLE = False
SYSTEM = platform.system() # 'Linux', 'Windows', 'Darwin'
def get_available_printers():
"""
Get list of available printers (cross-platform).
Includes both local and network printers on Windows.
Returns:
list: List of available printer names, with "PDF" as fallback
"""
try:
if SYSTEM == "Linux" and CUPS_AVAILABLE:
# Linux: Use CUPS
conn = cups.Connection()
printers = conn.getPrinters()
return list(printers.keys()) if printers else ["PDF"]
elif SYSTEM == "Windows":
# Windows: Get local + connected printers (includes print server connections)
try:
printers = []
# PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS captures:
# - Locally installed printers
# - Printers connected from a print server (e.g. \\server\printer)
try:
flags = win32print.PRINTER_ENUM_LOCAL | win32print.PRINTER_ENUM_CONNECTIONS
for printer_info in win32print.EnumPrinters(flags):
printer_name = printer_info[2]
if printer_name and printer_name not in printers:
printers.append(printer_name)
except Exception as e:
print(f"Error enumerating printers: {e}")
# Add PDF as fallback option
if "PDF" not in printers:
printers.append("PDF")
return printers if printers else ["PDF"]
except Exception as e:
print(f"Error getting Windows printers: {e}")
return ["PDF"]
elif SYSTEM == "Darwin":
# macOS: Use lpstat command
try:
result = subprocess.run(["lpstat", "-p", "-d"],
capture_output=True, text=True)
printers = []
for line in result.stdout.split('\n'):
if line.startswith('printer'):
printer_name = line.split()[1]
printers.append(printer_name)
return printers if printers else ["PDF"]
except:
return ["PDF"]
else:
return ["PDF"]
except Exception as e:
print(f"Error getting printers: {e}")
return ["PDF"]
def create_label_pdf(text, svg_template=None):
"""
Create a high-quality PDF label with 3 rows: label + barcode for each field.
PDFs are saved to the pdf_backup folder.
Args:
text (str): Combined text in format "article;nr_art;serial;status" or single value
status: 1 = OK label, 0 = NOK label
svg_template (str): Path to specific SVG template to use (optional)
Returns:
str: Path to the generated PDF file
"""
# Parse the text input - using semicolon separator
parts = text.split(';') if ';' in text else [text, '', '']
article = parts[0].strip() if len(parts) > 0 else ''
nr_art = parts[1].strip() if len(parts) > 1 else ''
serial = parts[2].strip() if len(parts) > 2 else ''
status_flag = parts[3].strip() if len(parts) > 3 else '1'
# Create PDF using high-quality generator
generator = PDFLabelGenerator()
# Ensure pdf_backup folder exists
pdf_backup_dir = 'pdf_backup'
os.makedirs(pdf_backup_dir, exist_ok=True)
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
pdf_filename = os.path.join(pdf_backup_dir, f"final_label_{timestamp}.pdf")
# Select template/image based on status flag
# 1 = OK label, 0 = NOK label
selected_template = svg_template if svg_template and os.path.exists(svg_template) else None
default_svg = os.path.join('conf', 'label_template.svg')
ok_svg = os.path.join('conf', 'label_template_ok.svg')
nok_svg = os.path.join('conf', 'label_template_nok.svg')
if selected_template:
image_path = os.path.join('conf', 'accepted.png') if status_flag != '0' else os.path.join('conf', 'refused.png')
elif status_flag == '0':
# NOK label: prefer dedicated NOK SVG template, otherwise use refused image in standard layout
if os.path.exists(nok_svg):
selected_template = nok_svg
elif os.path.exists(default_svg):
selected_template = default_svg
image_path = os.path.join('conf', 'refused.png')
else:
# OK label (default): prefer dedicated OK SVG template, fallback to default SVG template
if os.path.exists(ok_svg):
selected_template = ok_svg
elif os.path.exists(default_svg):
selected_template = default_svg
image_path = os.path.join('conf', 'accepted.png')
return generator.create_label_pdf(article, nr_art, serial, pdf_filename, image_path, selected_template)
def print_to_printer(printer_name, file_path):
"""
Print file to printer (cross-platform).
Uses SumatraPDF for silent printing on Windows.
Args:
printer_name (str): Name of printer or "PDF" for PDF output
file_path (str): Path to file to print
Returns:
bool: True if successful
"""
try:
if printer_name == "PDF":
# PDF output - file is already saved
print(f"PDF output: {file_path}")
return True
elif SYSTEM == "Linux" and CUPS_AVAILABLE:
# Linux: Use CUPS
conn = cups.Connection()
conn.printFile(printer_name, file_path, "Label Print", {})
print(f"Label sent to printer: {printer_name}")
return True
elif SYSTEM == "Windows":
# Windows: Print PDF silently without any viewer opening
try:
if file_path.endswith('.pdf'):
printed = False
# Method 1: SumatraPDF sends the PDF at its exact page size
# (35 mm × 25 mm) directly to the printer driver with no scaling.
# The printer must already have a 35 mm × 25 mm label stock
# configured in its driver settings.
sumatra_paths = []
# Get the directory where this script/exe is running
if getattr(sys, 'frozen', False):
# Running as compiled executable
# PyInstaller extracts bundled files to sys._MEIPASS temp folder
if hasattr(sys, '_MEIPASS'):
# Check bundled version first (inside the exe)
bundled_sumatra = os.path.join(sys._MEIPASS, 'SumatraPDF.exe')
sumatra_paths.append(bundled_sumatra)
# Check app directory and conf subfolder for external version
app_dir = os.path.dirname(sys.executable)
sumatra_paths.append(os.path.join(app_dir, "SumatraPDF", "SumatraPDF.exe"))
sumatra_paths.append(os.path.join(app_dir, "SumatraPDF.exe"))
sumatra_paths.append(os.path.join(app_dir, "conf", "SumatraPDF.exe")) # conf subfolder next to exe
sumatra_paths.append(os.path.join(os.getcwd(), "conf", "SumatraPDF.exe")) # conf relative to cwd
else:
# Running as script - check local folders
app_dir = os.path.dirname(os.path.abspath(__file__))
sumatra_paths.append(os.path.join(app_dir, "SumatraPDF", "SumatraPDF.exe"))
sumatra_paths.append(os.path.join(app_dir, "SumatraPDF.exe"))
sumatra_paths.append(os.path.join(app_dir, "conf", "SumatraPDF.exe"))
# Then check system installations
sumatra_paths.extend([
r"C:\Program Files\SumatraPDF\SumatraPDF.exe",
r"C:\Program Files (x86)\SumatraPDF\SumatraPDF.exe",
])
for sumatra_path in sumatra_paths:
if os.path.exists(sumatra_path):
try:
print(f"Using SumatraPDF: {sumatra_path}")
print(f"Sending to printer: {printer_name}")
# Locate the conf folder that contains
# SumatraPDF-settings.txt so SumatraPDF reads
# our pre-configured PrintScale = noscale.
sumatra_dir = os.path.dirname(sumatra_path)
settings_candidates = [
# conf\ next to the exe (script mode)
os.path.join(os.path.dirname(os.path.abspath(__file__)), 'conf'),
# same folder as SumatraPDF.exe
sumatra_dir,
]
if getattr(sys, 'frozen', False):
settings_candidates.insert(0, os.path.join(os.path.dirname(sys.executable), 'conf'))
appdata_dir = next(
(d for d in settings_candidates
if os.path.exists(os.path.join(d, 'SumatraPDF-settings.txt'))),
None
)
# Build command:
# -print-settings noscale : send PDF at exact page size (35x25 mm)
# -appdata-dir : use our settings file (PrintScale=noscale)
# -silent / -exit-when-done : no UI, exit after spooling
# IMPORTANT: file_path must come LAST
cmd = [
sumatra_path,
"-print-to", printer_name,
"-print-settings", "noscale",
"-silent",
"-exit-when-done",
]
if appdata_dir:
cmd += ["-appdata-dir", appdata_dir]
print(f"SumatraPDF appdata-dir: {appdata_dir}")
cmd.append(file_path)
result = subprocess.run(
cmd,
check=False,
creationflags=subprocess.CREATE_NO_WINDOW,
capture_output=True, text=True, timeout=30)
if result.returncode != 0:
print(f"SumatraPDF returned code {result.returncode}")
if result.stderr:
print(f"SumatraPDF stderr: {result.stderr.strip()}")
else:
print(f"Label sent to printer via SumatraPDF: {printer_name}")
printed = True
break
except Exception as e:
print(f"SumatraPDF error: {e}")
# Method 2: win32api ShellExecute "print" verb last resort,
# uses whatever PDF application is registered as the default
# handler. This may briefly open the viewer, but it will
# send the job to the correct physical printer.
if not printed and WIN32_AVAILABLE:
try:
import win32api
print(f"Trying win32api ShellExecute print fallback → {printer_name}")
# Set the target printer as default temporarily is
# unreliable; instead, rely on the user having already
# selected the right default printer in Windows.
win32api.ShellExecute(
0, # hwnd
"print", # operation
file_path, # file
None, # parameters
".", # working dir
0 # show-command (SW_HIDE)
)
print("Label sent via ShellExecute print fallback.")
printed = True
except Exception as se_err:
print(f"win32api ShellExecute fallback failed: {se_err}")
if not printed:
print("All print methods failed. PDF saved as backup only.")
return False
return True
else:
# Non-PDF files
subprocess.run(['notepad', '/p', file_path],
check=False,
creationflags=subprocess.CREATE_NO_WINDOW)
print(f"Label sent to printer: {printer_name}")
return True
except Exception as e:
print(f"Windows print error: {e}")
print("PDF backup saved as fallback")
return True
elif SYSTEM == "Darwin":
# macOS: Use lp command
subprocess.run(["lp", "-d", printer_name, file_path], check=True)
print(f"Label sent to printer: {printer_name}")
return True
else:
print(f"Unsupported system: {SYSTEM}")
return False
except Exception as e:
print(f"Printer error: {str(e)}")
print("Label already saved to file as fallback...")
print(f"Label file: {file_path}")
return True
def print_label_standalone(value, printer, preview=0, svg_template=None):
"""
Generate a PDF label, save it to pdf_backup/, and print it on the printer.
The printer's own paper size and orientation settings are used as-is.
Args:
value (str): Label data in format "article;nr_art;serial;status"
printer (str): Printer name (or "PDF" to skip physical printing)
preview (int): 0 = print immediately; 1-3 = 3 s delay; >3 = 5 s delay
svg_template (str): Path to SVG template (optional; auto-selected if None)
Returns:
bool: True if sending to printer succeeded, False otherwise
"""
pdf_file = None
try:
# Step 1 Generate and save PDF to pdf_backup/
try:
pdf_file = create_label_pdf(value, svg_template)
if pdf_file and os.path.exists(pdf_file):
print(f"PDF label created: {pdf_file}")
else:
print("PDF generation failed no output file")
return False
except Exception as pdf_err:
print(f"PDF generation error: {pdf_err}")
return False
# Step 2 Optional countdown before printing
if isinstance(preview, str):
preview = int(preview)
if preview > 0:
preview_sec = 3 if 1 <= preview <= 3 else 5
print(f"Printing in {preview_sec} seconds… (Ctrl+C to cancel)")
try:
for i in range(preview_sec, 0, -1):
print(f" {i}...", end=" ", flush=True)
time.sleep(1)
print("\nPrinting now...")
except KeyboardInterrupt:
print("\nCancelled by user")
return False
# Step 3 Send to printer
print("Sending to printer...")
return print_to_printer(printer, pdf_file)
except Exception as e:
print(f"Error printing label: {str(e)}")
return False
finally:
if pdf_file and os.path.exists(pdf_file):
print("Cleanup complete PDF backup saved to pdf_backup/")
else:
print("Cleanup complete")
# Main code removed - import this module or run as part of the Kivy GUI application