Add template selection and multiple copy printing features
- Implemented template selection: type 0=OK (green), type 1=NOK (red) - Added multiple copy printing (1-100 copies) - Extended file format to 5 fields: ARTICLE;NR_ART;SERIAL;TYPE;COUNT - Created OK/NOK SVG templates with visual distinction - Fixed PDF landscape orientation issues - Updated SumatraPDF to use noscale for exact dimensions - Auto-generate conf folder with default templates on first run - Made pystray optional for system tray functionality - Updated build scripts for Python 3.13 compatibility (Kivy 2.3+, PyInstaller 6.18) - Added comprehensive build documentation - Improved printer configuration guidance
This commit is contained in:
288
print_label.py
288
print_label.py
@@ -3,6 +3,7 @@ import barcode
|
||||
from barcode.writer import ImageWriter
|
||||
import time
|
||||
import os
|
||||
import sys
|
||||
import datetime
|
||||
import platform
|
||||
import subprocess
|
||||
@@ -173,7 +174,14 @@ def create_label_image(text):
|
||||
# Resize barcode to fit in row width
|
||||
barcode_width = label_width - left_margin - 10
|
||||
barcode_height = row_height - 25
|
||||
barcode_resized = barcode_img.resize((barcode_width, barcode_height), Image.LANCZOS)
|
||||
# Use high-quality resampling for crisp barcodes
|
||||
try:
|
||||
# Try newer Pillow API first
|
||||
from PIL.Image import Resampling
|
||||
barcode_resized = barcode_img.resize((barcode_width, barcode_height), Resampling.LANCZOS)
|
||||
except (ImportError, AttributeError):
|
||||
# Fallback for older Pillow versions
|
||||
barcode_resized = barcode_img.resize((barcode_width, barcode_height), Image.LANCZOS)
|
||||
label_img.paste(barcode_resized, (left_margin, row_y + 20))
|
||||
else:
|
||||
# Fallback: show value as text
|
||||
@@ -194,13 +202,14 @@ def create_label_image(text):
|
||||
return label_img
|
||||
|
||||
|
||||
def create_label_pdf(text):
|
||||
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" or single value
|
||||
svg_template (str): Path to specific SVG template to use (optional)
|
||||
|
||||
Returns:
|
||||
str: Path to the generated PDF file
|
||||
@@ -221,11 +230,17 @@ def create_label_pdf(text):
|
||||
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
pdf_filename = os.path.join(pdf_backup_dir, f"final_label_{timestamp}.pdf")
|
||||
|
||||
# Check for default SVG template
|
||||
svg_template = None
|
||||
default_svg = os.path.join('conf', 'label_template.svg')
|
||||
if os.path.exists(default_svg):
|
||||
svg_template = default_svg
|
||||
# Use SVG template for customizable layout
|
||||
if svg_template is None or not os.path.exists(svg_template):
|
||||
# Try default templates
|
||||
default_svg = os.path.join('conf', 'label_template.svg')
|
||||
if os.path.exists(default_svg):
|
||||
svg_template = default_svg
|
||||
print(f"Using SVG template: {default_svg}")
|
||||
else:
|
||||
print("SVG template not found, using fallback PDF generation")
|
||||
else:
|
||||
print(f"Using SVG template: {svg_template}")
|
||||
|
||||
# Check for default image path
|
||||
image_path = os.path.join('conf', 'accepted.png')
|
||||
@@ -233,9 +248,94 @@ def create_label_pdf(text):
|
||||
return generator.create_label_pdf(article, nr_art, serial, pdf_filename, image_path, svg_template)
|
||||
|
||||
|
||||
def configure_printer_quality(printer_name, width_mm=35, height_mm=25):
|
||||
"""
|
||||
Configure printer for high quality label printing (Windows only).
|
||||
Sets paper size, orientation, and QUALITY settings.
|
||||
|
||||
Args:
|
||||
printer_name (str): Name of the printer
|
||||
width_mm (int): Label width in millimeters (default 35)
|
||||
height_mm (int): Label height in millimeters (default 25)
|
||||
|
||||
Returns:
|
||||
bool: True if successful
|
||||
"""
|
||||
if SYSTEM != "Windows" or not WIN32_AVAILABLE:
|
||||
return False
|
||||
|
||||
try:
|
||||
import win32print
|
||||
import pywintypes
|
||||
|
||||
hprinter = win32print.OpenPrinter(printer_name)
|
||||
|
||||
try:
|
||||
# Get current printer properties
|
||||
props = win32print.GetPrinter(hprinter, 2)
|
||||
devmode = props.get("pDevMode")
|
||||
|
||||
if devmode is None:
|
||||
print("Could not get printer DEVMODE")
|
||||
return False
|
||||
|
||||
# CRITICAL: Set print quality to HIGHEST
|
||||
# This prevents dotted/pixelated text
|
||||
try:
|
||||
devmode.PrintQuality = 600 # 600 DPI (high quality)
|
||||
except:
|
||||
try:
|
||||
devmode.PrintQuality = 4 # DMRES_HIGH
|
||||
except:
|
||||
pass
|
||||
|
||||
# Set custom paper size
|
||||
try:
|
||||
devmode.PaperSize = 256 # DMPAPER_USER (custom size)
|
||||
devmode.PaperLength = height_mm * 10 # Height in 0.1mm units
|
||||
devmode.PaperWidth = width_mm * 10 # Width in 0.1mm units
|
||||
except:
|
||||
pass
|
||||
|
||||
# Set orientation to landscape
|
||||
try:
|
||||
devmode.Orientation = 2 # Landscape
|
||||
except:
|
||||
pass
|
||||
|
||||
# Set additional quality settings
|
||||
try:
|
||||
devmode.Color = 1 # Monochrome for labels
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
devmode.TTOption = 2 # DMTT_BITMAP - print TrueType as graphics (sharper)
|
||||
except:
|
||||
pass
|
||||
|
||||
# Apply settings
|
||||
try:
|
||||
props["pDevMode"] = devmode
|
||||
win32print.SetPrinter(hprinter, 2, props, 0)
|
||||
print(f"Printer configured: {width_mm}x{height_mm}mm @ HIGH QUALITY")
|
||||
return True
|
||||
except Exception as set_err:
|
||||
print(f"Could not apply printer settings: {set_err}")
|
||||
return False
|
||||
|
||||
finally:
|
||||
win32print.ClosePrinter(hprinter)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Could not configure printer quality: {e}")
|
||||
return False
|
||||
|
||||
|
||||
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
|
||||
@@ -258,66 +358,145 @@ def print_to_printer(printer_name, file_path):
|
||||
return True
|
||||
|
||||
elif SYSTEM == "Windows":
|
||||
# Windows: Print PDF using various methods
|
||||
# Windows: Print PDF silently without any viewer opening
|
||||
try:
|
||||
if WIN32_AVAILABLE:
|
||||
import win32print
|
||||
import win32api
|
||||
|
||||
if file_path.endswith('.pdf'):
|
||||
# Method 1: Try SumatraPDF for best silent printing
|
||||
sumatra_paths = [
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), 'conf', 'SumatraPDF.exe'),
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), 'conf', 'SumatraPDF-portable.exe'),
|
||||
# Try silent printing methods (no viewer opens)
|
||||
import os
|
||||
import winreg
|
||||
|
||||
printed = False
|
||||
|
||||
# Method 1: SumatraPDF (bundled inside exe or external)
|
||||
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)
|
||||
|
||||
# Also check app directory 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"))
|
||||
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",
|
||||
os.path.expandvars(r"%LOCALAPPDATA%\SumatraPDF\SumatraPDF.exe"),
|
||||
]
|
||||
])
|
||||
|
||||
sumatra_found = False
|
||||
for sumatra_path in sumatra_paths:
|
||||
if os.path.exists(sumatra_path):
|
||||
sumatra_found = True
|
||||
try:
|
||||
# Use SumatraPDF silent printing with high quality settings
|
||||
# noscale = print at actual size without scaling
|
||||
# landscape = force landscape orientation for 35x25mm labels
|
||||
# Note: SumatraPDF uses printer driver's quality settings
|
||||
# Use noscale with paper size specification for thermal printers
|
||||
# Format: "noscale,paper=<size>" where paper size matches PDF (35mm x 25mm)
|
||||
# SumatraPDF will use the PDF's page size when noscale is used
|
||||
subprocess.run([
|
||||
sumatra_path,
|
||||
'-print-to', printer_name,
|
||||
'-silent',
|
||||
'-print-settings', 'noscale,landscape',
|
||||
file_path
|
||||
], check=True, creationflags=subprocess.CREATE_NO_WINDOW)
|
||||
"-print-to",
|
||||
printer_name,
|
||||
file_path,
|
||||
"-print-settings",
|
||||
"noscale", # Preserve exact PDF page dimensions
|
||||
"-silent",
|
||||
"-exit-when-done"
|
||||
], check=False, creationflags=subprocess.CREATE_NO_WINDOW)
|
||||
print(f"Label sent to printer via SumatraPDF: {printer_name}")
|
||||
return True
|
||||
except Exception as sumatra_err:
|
||||
print(f"SumatraPDF print failed: {sumatra_err}")
|
||||
print(f"Note: Printer '{printer_name}' should be configured for 35mm x 25mm labels")
|
||||
printed = True
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"SumatraPDF error: {e}")
|
||||
|
||||
# Method 2: Use ShellExecute with printto (requires default PDF viewer)
|
||||
if not sumatra_found or WIN32_AVAILABLE:
|
||||
try:
|
||||
import win32api
|
||||
win32api.ShellExecute(
|
||||
0, "printto", file_path,
|
||||
f'"{printer_name}"', ".", 0
|
||||
)
|
||||
print(f"Label sent to printer via ShellExecute: {printer_name}")
|
||||
return True
|
||||
except Exception as shell_err:
|
||||
print(f"ShellExecute print failed: {shell_err}")
|
||||
# Method 3: Open PDF with default viewer (last resort)
|
||||
# Method 2: Adobe Reader silent printing
|
||||
if not printed:
|
||||
adobe_path = None
|
||||
for key_path in [
|
||||
r"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\AcroRd32.exe",
|
||||
r"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\Acrobat.exe"
|
||||
]:
|
||||
try:
|
||||
os.startfile(file_path, "print")
|
||||
print(f"Opened PDF for printing: {file_path}")
|
||||
print("Please close the PDF viewer after printing.")
|
||||
return True
|
||||
except Exception as startfile_err:
|
||||
print(f"Could not open PDF: {startfile_err}")
|
||||
print("PDF saved as backup only")
|
||||
return True
|
||||
key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, key_path)
|
||||
adobe_path, _ = winreg.QueryValueEx(key, "")
|
||||
winreg.CloseKey(key)
|
||||
break
|
||||
except:
|
||||
pass
|
||||
|
||||
if adobe_path and os.path.exists(adobe_path):
|
||||
try:
|
||||
subprocess.run([
|
||||
adobe_path,
|
||||
"/t", # Print and close
|
||||
file_path,
|
||||
printer_name
|
||||
], check=False, creationflags=subprocess.CREATE_NO_WINDOW)
|
||||
print(f"Label sent to printer via Adobe Reader: {printer_name}")
|
||||
printed = True
|
||||
except:
|
||||
pass
|
||||
|
||||
# Method 3: GhostScript (if installed)
|
||||
if not printed:
|
||||
gs_paths = [
|
||||
r"C:\Program Files\gs\gs10.02.1\bin\gswin64c.exe",
|
||||
r"C:\Program Files (x86)\gs\gs10.02.1\bin\gswin32c.exe",
|
||||
]
|
||||
# Try to find gswin in PATH
|
||||
try:
|
||||
gs_result = subprocess.run(['where', 'gswin64c'],
|
||||
capture_output=True, text=True, check=False)
|
||||
if gs_result.returncode == 0:
|
||||
gs_paths.insert(0, gs_result.stdout.strip().split('\n')[0])
|
||||
except:
|
||||
pass
|
||||
|
||||
for gs_path in gs_paths:
|
||||
if os.path.exists(gs_path):
|
||||
try:
|
||||
subprocess.run([
|
||||
gs_path,
|
||||
"-dNOPAUSE", "-dBATCH", "-dQUIET",
|
||||
f"-sDEVICE=mswinpr2",
|
||||
f"-sOutputFile=%printer%{printer_name}",
|
||||
file_path
|
||||
], check=False, creationflags=subprocess.CREATE_NO_WINDOW)
|
||||
print(f"Label sent to printer via GhostScript: {printer_name}")
|
||||
printed = True
|
||||
break
|
||||
except:
|
||||
pass
|
||||
|
||||
if not printed:
|
||||
# Fallback: Let user know and save PDF
|
||||
print("=" * 60)
|
||||
print("NOTICE: Silent PDF printing requires SumatraPDF")
|
||||
print("SumatraPDF not found (should be bundled inside the app)")
|
||||
print("If you built the app yourself, ensure SumatraPDF.exe is downloaded first.")
|
||||
print("=" * 60)
|
||||
print(f"PDF saved to: {file_path}")
|
||||
print("The PDF can be printed manually.")
|
||||
|
||||
return True
|
||||
else:
|
||||
# Non-PDF files: print silently with notepad
|
||||
subprocess.run(['notepad', '/p', file_path],
|
||||
check=False,
|
||||
# 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
|
||||
@@ -343,7 +522,7 @@ def print_to_printer(printer_name, file_path):
|
||||
return True
|
||||
|
||||
|
||||
def print_label_standalone(value, printer, preview=0, use_pdf=True):
|
||||
def print_label_standalone(value, printer, preview=0, use_pdf=True, svg_template=None):
|
||||
"""
|
||||
Print a label with the specified text on the specified printer.
|
||||
Always generates a PDF backup in pdf_backup and prints that PDF.
|
||||
@@ -353,6 +532,7 @@ def print_label_standalone(value, printer, preview=0, use_pdf=True):
|
||||
printer (str): The name of the printer to use
|
||||
preview (int): 0 = no preview, 1-3 = 3s preview, >3 = 5s preview
|
||||
use_pdf (bool): False to also generate a PNG if PDF generation fails
|
||||
svg_template (str): Path to specific SVG template to use (optional)
|
||||
|
||||
Returns:
|
||||
bool: True if printing was successful, False otherwise
|
||||
@@ -370,7 +550,7 @@ def print_label_standalone(value, printer, preview=0, use_pdf=True):
|
||||
|
||||
# Always generate a PDF backup and print that PDF for verification
|
||||
try:
|
||||
pdf_file = create_label_pdf(value)
|
||||
pdf_file = create_label_pdf(value, svg_template)
|
||||
if pdf_file and os.path.exists(pdf_file):
|
||||
print(f"PDF label created: {pdf_file}")
|
||||
print(f"PDF backup saved to: {pdf_file}")
|
||||
|
||||
Reference in New Issue
Block a user