Files
adaptronic_label-printer/print_label.py

569 lines
21 KiB
Python

from PIL import Image, ImageDraw, ImageFont
import barcode
from barcode.writer import ImageWriter
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_image(text):
"""
Create a label image with 3 rows: label + barcode for each field.
Args:
text (str): Combined text in format "SAP|CANTITATE|LOT" or single value
Returns:
PIL.Image: The generated label image
"""
# Parse the text input
parts = text.split('|') if '|' in text else [text, '', '']
sap_nr = parts[0].strip() if len(parts) > 0 else ''
cantitate = parts[1].strip() if len(parts) > 1 else ''
lot_number = parts[2].strip() if len(parts) > 2 else ''
# Label dimensions (narrower, 3 rows)
label_width = 800 # 8 cm
label_height = 600 # 6 cm
# Create canvas
label_img = Image.new('RGB', (label_width, label_height), 'white')
draw = ImageDraw.Draw(label_img)
# Row setup - 3 equal rows
row_height = label_height // 3
left_margin = 15
row_spacing = 3
# Fonts
font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"
try:
label_font = ImageFont.truetype(font_path, 16)
value_font = ImageFont.truetype(font_path, 14)
except IOError:
label_font = ImageFont.load_default()
value_font = ImageFont.load_default()
# Data for 3 rows
rows_data = [
("SAP-Nr", sap_nr),
("Cantitate", cantitate),
("Lot Nr", lot_number),
]
# Generate barcodes first
CODE128 = barcode.get_barcode_class('code128')
writer_options = {
"write_text": False,
"module_width": 0.4,
"module_height": 8,
"quiet_zone": 2,
"font_size": 0
}
barcode_images = []
for _, value in rows_data:
if value:
try:
code = CODE128(value[:25], writer=ImageWriter())
filename = code.save('temp_barcode', options=writer_options)
barcode_img = Image.open(filename)
barcode_images.append(barcode_img)
except:
barcode_images.append(None)
else:
barcode_images.append(None)
# Draw each row with label and barcode
for idx, ((label_name, value), barcode_img) in enumerate(zip(rows_data, barcode_images)):
row_y = idx * row_height
# Draw label name
draw.text(
(left_margin, row_y + 3),
label_name,
fill='black',
font=label_font
)
# Draw barcode if available
if barcode_img:
# Resize barcode to fit in row width
barcode_width = label_width - left_margin - 10
barcode_height = row_height - 25
# 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
draw.text(
(left_margin, row_y + 25),
value if value else "(empty)",
fill='black',
font=value_font
)
# Clean up temporary barcode files
try:
if os.path.exists('temp_barcode.png'):
os.remove('temp_barcode.png')
except:
pass
return label_img
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 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
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 WIN32_AVAILABLE:
import win32print
import win32api
if file_path.endswith('.pdf'):
# 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",
])
for sumatra_path in sumatra_paths:
if os.path.exists(sumatra_path):
try:
# 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,
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}")
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}")
# Do not launch default PDF viewers (Adobe/Edge/etc.) as fallback.
if not printed:
print("SumatraPDF not found or failed. PDF saved as backup only (no viewer launched).")
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, 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.
Args:
value (str): The text to print on the label
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
"""
# Track generated files
file_created = False
temp_file = None
pdf_file = None
try:
# Debug output
print(f"Preview value: {preview}")
print(f"Preview type: {type(preview)}")
print(f"Using format: {'PDF' if use_pdf else 'PNG'}")
# Always generate a PDF backup and print that PDF for verification
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}")
print(f"PDF backup saved to: {pdf_file}")
else:
print("PDF generation returned no file path")
except Exception as pdf_err:
print(f"PDF generation failed: {pdf_err}")
# Optionally also create the label image (PNG)
if not pdf_file or not os.path.exists(pdf_file):
if not use_pdf:
label_img = create_label_image(value)
temp_file = 'final_label.png'
label_img.save(temp_file)
print(f"PNG label created: {temp_file}")
else:
temp_file = pdf_file
file_created = True
if not temp_file or not os.path.exists(temp_file):
print("No label file created for printing")
return False
# Convert preview to int if it's a string
if isinstance(preview, str):
preview = int(preview)
if preview > 0: # Any value above 0 shows a preview message
# Calculate preview duration in seconds
if 1 <= preview <= 3:
preview_sec = 3 # 3 seconds
else: # preview > 3
preview_sec = 5 # 5 seconds
print(f"Printing in {preview_sec} seconds... (Press Ctrl+C to cancel)")
# Simple countdown timer using time.sleep
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
# Print after preview
print("Sending to printer...")
return print_to_printer(printer, temp_file)
else:
print("Direct printing without preview...")
# Direct printing without preview (preview = 0)
return print_to_printer(printer, temp_file)
except Exception as e:
print(f"Error printing label: {str(e)}")
return False
finally:
# This block always executes, ensuring cleanup
if pdf_file and os.path.exists(pdf_file):
print("Cleanup complete - PDF backup saved to pdf_backup folder")
else:
print("Cleanup complete - label file retained for reference")
# Main code removed - import this module or run as part of the Kivy GUI application