diff --git a/.gitignore b/.gitignore
index 96a9dad..7edc1e6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,12 @@
label/
build/
+dist/
logs/
pdf_backup/
venv/
-
+__pycache__/
+*.pyc
+*.pyo
+*.spec
+*.egg-info/
+.eggs/
diff --git a/BUILD_EXECUTABLE.md b/BUILD_EXECUTABLE.md
new file mode 100644
index 0000000..0f34560
--- /dev/null
+++ b/BUILD_EXECUTABLE.md
@@ -0,0 +1,303 @@
+# Build Windows Executable (.exe)
+
+## Quick Start
+
+**Option 1: One Command (Recommended)**
+```bash
+build_windows.bat
+```
+
+**Option 2: Python Script**
+```bash
+python build_exe.py
+```
+
+**Option 3: Direct PyInstaller**
+```bash
+pyinstaller LabelPrinter.spec
+```
+
+---
+
+## Requirements
+
+- **Windows** 10/11
+- **Python** 3.10 - 3.13 (Python 3.14+ may have compatibility issues)
+- **Dependencies** installed (see below)
+
+---
+
+## Step-by-Step Build Process
+
+### 1. Install Python Dependencies
+
+If not already installed:
+```bash
+pip install -r requirements_windows.txt
+```
+
+Or install manually:
+```bash
+pip install python-barcode pillow reportlab kivy==2.2.1 pyinstaller==6.1.0 pywin32 wmi watchdog svglib cairosvg
+```
+
+### 2. Build the Executable
+
+Run the build script:
+```bash
+build_windows.bat
+```
+
+This will:
+- ✅ Install/upgrade dependencies
+- ✅ Clean old build artifacts
+- ✅ Create `LabelPrinter.exe` in `dist/` folder
+- ⏱️ Takes 5-15 minutes
+
+### 3. Test the Executable
+
+```bash
+cd dist
+LabelPrinter.exe
+```
+
+---
+
+## What Gets Included
+
+The executable is **self-contained** and includes:
+- ✅ All Python code
+- ✅ All dependencies (Kivy, ReportLab, svglib, cairosvg, etc.)
+- ✅ Auto-generates `conf/` folder on first run
+- ✅ Auto-generates `pdf_backup/` and `logs/` folders
+
+**On first run, the app automatically creates:**
+```
+LabelPrinter.exe location/
+├── conf/
+│ ├── app.conf (default settings)
+│ ├── label_template.svg (default template)
+│ ├── label_template_ok.svg (OK labels - green)
+│ └── label_template_nok.svg (NOK labels - red)
+├── pdf_backup/ (generated PDFs)
+└── logs/ (print logs)
+```
+
+---
+
+## Output
+
+After successful build:
+
+```
+📁 dist/
+ └── LabelPrinter.exe (Single file, ~80-120 MB)
+```
+
+**File size**: 80-120 MB (includes Python runtime + all libraries)
+
+---
+
+## Distribution
+
+### To Deploy on Other Windows Machines:
+
+**Simply copy `LabelPrinter.exe` to the target machine!**
+
+The executable is **100% self-contained**:
+- ✅ No Python installation needed
+- ✅ No dependencies needed
+- ✅ Auto-creates all required folders on first run
+- ✅ Generates default templates automatically
+
+**First run will create:**
+```
+[wherever you put LabelPrinter.exe]/
+├── LabelPrinter.exe ← Copy just this file!
+├── conf/ ← Auto-generated on first run
+│ ├── app.conf
+│ ├── label_template.svg
+│ ├── label_template_ok.svg
+│ └── label_template_nok.svg
+├── pdf_backup/ ← Auto-generated
+└── logs/ ← Auto-generated
+```
+
+**To customize templates on target machine:**
+1. Run `LabelPrinter.exe` once (generates conf folder)
+2. Edit SVG files in the `conf/` folder
+3. Restart the application
+
+**Requirements on target machine:**
+- ✅ Windows 10/11
+- ✅ Thermal printer configured (35mm x 25mm paper size)
+- ⚠️ No other software required!
+
+---
+
+## Troubleshooting
+
+### Build Fails - Missing Dependencies
+```bash
+pip install --upgrade pip setuptools wheel
+pip install -r requirements_windows.txt
+```
+
+### Build Fails - PyInstaller Error
+```bash
+pip install --upgrade pyinstaller==6.1.0
+```
+
+### Executable Won't Start
+- Check Windows Defender didn't quarantine it
+- Run as Administrator once to register
+- Check Event Viewer for errors
+
+### Executable is Huge (>150 MB)
+This is normal! It includes:
+- Python runtime (~40 MB)
+- Kivy framework (~30 MB)
+- ReportLab, PIL, etc. (~20 MB)
+- Your code + data (~10 MB)
+
+### First Run is Slow
+- First launch takes 10-30 seconds (Kivy initialization)
+- Subsequent launches are faster (~2-5 seconds)
+- This is normal behavior
+
+---
+
+## Advanced Options
+
+### Build with Console (for debugging)
+Edit `build_windows.bat` and remove `--windowed`:
+```bat
+pyinstaller label_printer_gui.py ^
+ --onefile ^
+ --name=LabelPrinter ^
+ ...
+```
+
+### Add Custom Icon
+```bat
+pyinstaller label_printer_gui.py ^
+ --onefile ^
+ --windowed ^
+ --icon=icon.ico ^
+ ...
+```
+
+### Reduce File Size
+Not recommended, but possible:
+```bat
+pyinstaller label_printer_gui.py ^
+ --onefile ^
+ --windowed ^
+ --strip ^
+ --exclude-module=tkinter ^
+ --exclude-module=matplotlib ^
+ ...
+```
+
+---
+
+## What's Built
+
+**Source Files Compiled:**
+- `label_printer_gui.py` (Main GUI)
+- `print_label.py` (Printing logic)
+- `print_label_pdf.py` (PDF generation)
+
+**Data Files Included:**
+- `conf/app.conf` (Settings)
+- `conf/label_template.svg` (Default template)
+- `conf/label_template_ok.svg` (OK labels - green)
+- `conf/label_template_nok.svg` (NOK labels - red)
+- `conf/accepted.png` (Checkmark image)
+
+**Output Folders:**
+- `pdf_backup/` - Stores generated PDFs
+- `logs/` - Stores print logs
+
+---
+
+## Build Times
+
+- **First build**: 10-15 minutes (PyInstaller analyzes all dependencies)
+- **Rebuild**: 5-10 minutes (cached analysis)
+- **Clean build**: 10-15 minutes (removed cache)
+
+---
+
+## File Structure After Build
+
+```
+📁 Project Root/
+├── 📄 label_printer_gui.py
+├── 📄 print_label.py
+├── 📄 print_label_pdf.py
+├── 📄 build_windows.bat ← Run this
+├── 📄 build_exe.py
+├── 📄 LabelPrinter.spec
+│
+├── 📁 dist/
+│ ├── 📄 LabelPrinter.exe ← Your executable!
+│ ├── 📁 conf/
+│ ├── 📁 pdf_backup/
+│ └── 📁 logs/
+│
+├── 📁 build/ (temp files, can delete)
+└── 📁 conf/
+ ├── app.conf
+ ├── label_template.svg
+ ├── label_template_ok.svg
+ └── label_template_nok.svg
+```
+
+---
+
+## Clean Build
+
+To remove all build artifacts:
+```bash
+rmdir /s /q build
+rmdir /s /q dist
+del *.spec
+```
+
+Then rebuild:
+```bash
+build_windows.bat
+```
+
+---
+
+## Testing
+
+After building, test with:
+
+1. **Run the executable**:
+ ```bash
+ dist\LabelPrinter.exe
+ ```
+
+2. **Configure monitoring**:
+ - File: `C:\Users\Public\Documents\check.txt`
+
+3. **Test print**:
+ - Add to check.txt: `COM-001;ART-123;SN-001;0;1`
+ - Should print 1 OK label
+
+4. **Test template switching**:
+ - `COM-002;ART-456;SN-002;1;1` → NOK label (red)
+
+---
+
+## Support
+
+If build fails, check:
+1. Python version (3.10-3.13 required)
+2. All dependencies installed
+3. Windows 10/11 (not Windows 7/8)
+4. Disk space (need ~500MB for build)
+5. Antivirus not blocking PyInstaller
diff --git a/build_exe.py b/build_exe.py
index 79d271c..1cda591 100644
--- a/build_exe.py
+++ b/build_exe.py
@@ -46,6 +46,12 @@ args = [
'--hidden-import=reportlab',
'--hidden-import=print_label',
'--hidden-import=print_label_pdf',
+ '--hidden-import=svglib',
+ '--hidden-import=cairosvg',
+ '--hidden-import=watchdog',
+ '--hidden-import=watchdog.observers',
+ '--hidden-import=watchdog.events',
+ '--hidden-import=pystray',
]
if __name__ == '__main__':
diff --git a/build_windows.bat b/build_windows.bat
index 8fdb3fc..b33edf8 100644
--- a/build_windows.bat
+++ b/build_windows.bat
@@ -39,8 +39,8 @@ echo.
REM Install dependencies
echo [3/5] Installing dependencies...
-echo Installing: python-barcode, pillow, reportlab, kivy, pyinstaller, pywin32, wmi...
-pip install python-barcode pillow reportlab kivy==2.2.1 pyinstaller==6.1.0 pywin32 wmi
+echo Installing: python-barcode, pillow, reportlab, kivy, pyinstaller, pywin32, wmi, watchdog, svglib, cairosvg, pystray...
+pip install python-barcode pillow reportlab kivy pyinstaller pywin32 wmi watchdog svglib cairosvg pystray
if errorlevel 1 (
echo ERROR: Failed to install dependencies
pause
@@ -72,6 +72,12 @@ pyinstaller label_printer_gui.py ^
--hidden-import=reportlab ^
--hidden-import=print_label ^
--hidden-import=print_label_pdf ^
+ --hidden-import=svglib ^
+ --hidden-import=cairosvg ^
+ --hidden-import=watchdog ^
+ --hidden-import=watchdog.observers ^
+ --hidden-import=watchdog.events ^
+ --hidden-import=pystray ^
-y
if errorlevel 1 (
diff --git a/check_pdf_size.py b/check_pdf_size.py
new file mode 100644
index 0000000..3966087
--- /dev/null
+++ b/check_pdf_size.py
@@ -0,0 +1,20 @@
+from reportlab.lib.pagesizes import landscape
+from reportlab.lib.utils import ImageReader
+from reportlab.pdfgen import canvas
+import os
+
+# Check the test PDF file
+if os.path.exists('test_label.pdf'):
+ file_size = os.path.getsize('test_label.pdf')
+ print(f'test_label.pdf exists ({file_size} bytes)')
+ print(f'Expected: 35mm x 25mm landscape (99.2 x 70.9 points)')
+ print(f'')
+ print(f'Open test_label.pdf in a PDF viewer to verify:')
+ print(f' - Page size should be wider than tall')
+ print(f' - Content should be correctly oriented')
+ print(f'')
+ print(f'In Adobe Reader: File > Properties > Description')
+ print(f' Page size should show: 3.5 x 2.5 cm or 1.38 x 0.98 in')
+else:
+ print('test_label.pdf not found')
+
diff --git a/conf/label_template_nok.svg b/conf/label_template_nok.svg
new file mode 100644
index 0000000..9ab1d55
--- /dev/null
+++ b/conf/label_template_nok.svg
@@ -0,0 +1,64 @@
+
+
+
+
diff --git a/conf/label_template_ok.svg b/conf/label_template_ok.svg
new file mode 100644
index 0000000..843c10f
--- /dev/null
+++ b/conf/label_template_ok.svg
@@ -0,0 +1,63 @@
+
+
+
+
diff --git a/configure_printer_paper_size.ps1 b/configure_printer_paper_size.ps1
new file mode 100644
index 0000000..225b045
--- /dev/null
+++ b/configure_printer_paper_size.ps1
@@ -0,0 +1,83 @@
+# Configure Thermal Printer for 35mm x 25mm Labels
+# Run as Administrator if needed
+
+Write-Host "====================================" -ForegroundColor Cyan
+Write-Host "Label Printer Paper Size Setup" -ForegroundColor Cyan
+Write-Host "Target: 35mm x 25mm (Landscape)" -ForegroundColor Cyan
+Write-Host "====================================" -ForegroundColor Cyan
+Write-Host ""
+
+# Get list of printers
+$printers = Get-Printer | Select-Object Name, DriverName, PortName
+Write-Host "Available Printers:" -ForegroundColor Yellow
+$printers | Format-Table -AutoSize
+
+Write-Host ""
+Write-Host "MANUAL CONFIGURATION STEPS:" -ForegroundColor Green
+Write-Host "===========================" -ForegroundColor Green
+Write-Host ""
+Write-Host "1. Open Control Panel > Devices and Printers" -ForegroundColor White
+Write-Host "2. Right-click your thermal printer (e.g., Citizen CTS-310 or Zebra ZD420)" -ForegroundColor White
+Write-Host "3. Select 'Printing Preferences'" -ForegroundColor White
+Write-Host "4. Look for 'Paper Size' or 'Media' settings:" -ForegroundColor White
+Write-Host " - Size: Custom or 35mm x 25mm" -ForegroundColor Cyan
+Write-Host " - Width: 35mm (1.38 inches)" -ForegroundColor Cyan
+Write-Host " - Height: 25mm (0.98 inches)" -ForegroundColor Cyan
+Write-Host " - Orientation: Landscape" -ForegroundColor Cyan
+Write-Host " - Media Type: LABELS (not Continuous)" -ForegroundColor Cyan
+Write-Host "5. Click 'Apply' and 'OK'" -ForegroundColor White
+Write-Host ""
+Write-Host "IMPORTANT FOR ZEBRA PRINTERS:" -ForegroundColor Red
+Write-Host "- Must set Media Type = 'Labels' (enables gap sensor)" -ForegroundColor Yellow
+Write-Host "- If set to 'Continuous', printer will print on entire roll" -ForegroundColor Yellow
+Write-Host ""
+Write-Host "CRITICAL PRINTER SETTINGS:" -ForegroundColor Magenta
+Write-Host "- Darkness: 10-15 (thermal printers)" -ForegroundColor White
+Write-Host "- Speed: Medium" -ForegroundColor White
+Write-Host "- Print Mode: Thermal Transfer or Direct Thermal" -ForegroundColor White
+Write-Host ""
+
+# Show printer properties access
+Write-Host "To open printer properties directly, run:" -ForegroundColor Green
+Write-Host 'control printers' -ForegroundColor Cyan
+Write-Host ""
+
+# Check if running as admin
+$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
+if ($isAdmin) {
+ Write-Host "✓ Running as Administrator" -ForegroundColor Green
+} else {
+ Write-Host "⚠ Not running as Administrator (some settings may require elevation)" -ForegroundColor Yellow
+}
+
+Write-Host ""
+Write-Host "For Citizen CTS-310 printers:" -ForegroundColor Cyan
+Write-Host " 1. Open Devices and Printers" -ForegroundColor White
+Write-Host " 2. Right-click 'Citizen CTS-310II' > Printer Properties" -ForegroundColor White
+Write-Host " 3. Device Settings tab > Form to Tray Assignment" -ForegroundColor White
+Write-Host " 4. Click 'Create Form' button" -ForegroundColor White
+Write-Host " 5. Form name: Label35x25" -ForegroundColor White
+Write-Host " 6. Width: 35mm, Height: 25mm" -ForegroundColor White
+Write-Host " 7. Save and select this form in Printing Preferences" -ForegroundColor White
+Write-Host ""
+
+Write-Host "For Zebra ZD420/ZD421 printers:" -ForegroundColor Cyan
+Write-Host " 1. Open Zebra Setup Utility (ZSU)" -ForegroundColor White
+Write-Host " 2. Select your printer" -ForegroundColor White
+Write-Host " 3. Click 'Printer Configuration'" -ForegroundColor White
+Write-Host " 4. Media section:" -ForegroundColor White
+Write-Host " - Media Type: Label Stock" -ForegroundColor Cyan
+Write-Host " - Label Width: 35mm" -ForegroundColor Cyan
+Write-Host " - Label Height: 25mm" -ForegroundColor Cyan
+Write-Host " 5. Print section:" -ForegroundColor White
+Write-Host " - Darkness: 10-15" -ForegroundColor Cyan
+Write-Host " - Print Speed: 4 ips (medium)" -ForegroundColor Cyan
+Write-Host " 6. Click 'Send to Printer'" -ForegroundColor White
+Write-Host ""
+
+Write-Host "After configuration, test print with:" -ForegroundColor Green
+Write-Host " python label_printer_gui.py" -ForegroundColor Cyan
+Write-Host ""
+Write-Host "Press any key to open Printer settings..." -ForegroundColor Yellow
+$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
+control printers
diff --git a/label_printer_gui.py b/label_printer_gui.py
index 6920277..8fa91e1 100644
--- a/label_printer_gui.py
+++ b/label_printer_gui.py
@@ -28,10 +28,17 @@ from print_label import print_label_standalone, get_available_printers
from kivy.clock import Clock
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
-import pystray
-from pystray import MenuItem as item
from PIL import Image, ImageDraw
+# Optional system tray support
+try:
+ import pystray
+ from pystray import MenuItem as item
+ PYSTRAY_AVAILABLE = True
+except ImportError:
+ PYSTRAY_AVAILABLE = False
+ print("Warning: pystray not available. System tray functionality disabled.")
+
# Set window size - portrait/phone dimensions (375x667 like iPhone)
# Adjusted to be slightly wider for touch-friendly UI
Window.size = (420, 700)
@@ -63,6 +70,8 @@ class LabelPrinterApp(App):
def __init__(self, **kwargs):
super().__init__(**kwargs)
+ # Initialize conf folder with default templates
+ self.initialize_conf_folder()
# Build printer display names and mapping to full names
full_printers = get_available_printers()
self.printer_display_map = {} # display_name -> full_name
@@ -109,6 +118,107 @@ class LabelPrinterApp(App):
"""Resolve display name back to full printer name for printing."""
return self.printer_display_map.get(display_name, display_name)
+ def initialize_conf_folder(self):
+ """
+ Initialize conf folder with default templates and configuration.
+ Creates folder and files if they don't exist.
+ """
+ conf_dir = 'conf'
+
+ # Create conf folder if it doesn't exist
+ if not os.path.exists(conf_dir):
+ os.makedirs(conf_dir, exist_ok=True)
+ print(f"Created conf folder: {conf_dir}")
+
+ # Define default SVG template (minimal 35mm x 25mm with placeholders)
+ default_svg_template = '''
+'''
+
+ # OK template (green checkmark)
+ ok_svg_template = '''
+'''
+
+ # NOK template (red text and checkmark)
+ nok_svg_template = '''
+'''
+
+ # Create SVG template files if they don't exist
+ templates = {
+ 'label_template.svg': default_svg_template,
+ 'label_template_ok.svg': ok_svg_template,
+ 'label_template_nok.svg': nok_svg_template,
+ }
+
+ for filename, content in templates.items():
+ file_path = os.path.join(conf_dir, filename)
+ if not os.path.exists(file_path):
+ try:
+ with open(file_path, 'w', encoding='utf-8') as f:
+ f.write(content)
+ print(f"Created template: {file_path}")
+ except Exception as e:
+ print(f"Failed to create {file_path}: {e}")
+
+ # Create default app.conf if it doesn't exist
+ config_path = os.path.join(conf_dir, 'app.conf')
+ if not os.path.exists(config_path):
+ default_config = '''[Settings]
+file_path = C:\\Users\\Public\\Documents\\check.txt
+printer = PDF
+'''
+ try:
+ with open(config_path, 'w', encoding='utf-8') as f:
+ f.write(default_config)
+ print(f"Created config: {config_path}")
+ except Exception as e:
+ print(f"Failed to create {config_path}: {e}")
+
def cleanup_old_pdfs(self, days=5):
"""
Delete PDF files older than specified days from pdf_backup folder.
@@ -469,6 +579,10 @@ class LabelPrinterApp(App):
"""
Minimize the application to system tray.
"""
+ if not PYSTRAY_AVAILABLE:
+ print("System tray not available - window will minimize normally")
+ return
+
if self.is_minimized:
return
@@ -652,9 +766,13 @@ class LabelPrinterApp(App):
print(f"Error clearing file: {e}")
def read_file_variables(self):
- """Read variables from monitored file"""
+ """Read variables from monitored file
+ Expected format: article;nr_art;serial;template_type;count
+ template_type: 0=OK, 1=NOK
+ count: number of labels to print (default=1)
+ """
if not self.monitored_file or not os.path.exists(self.monitored_file):
- return None, None, None
+ return None, None, None, 0, 1
try:
with open(self.monitored_file, 'r', encoding='utf-8') as f:
@@ -662,14 +780,16 @@ class LabelPrinterApp(App):
# Skip if file is empty or only contains "-" (cleared marker)
if not content or content == '-':
- return None, None, None
+ return None, None, None, 0, 1
- # Parse file content - expecting format: article;nr_art;serial
+ # Parse file content - expecting format: article;nr_art;serial;template_type;count
# or key=value pairs on separate lines
lines = content.split('\n')
article = ""
nr_art = ""
serial = ""
+ template_type = 0 # Default to OK template
+ count = 1 # Default to 1 label
# Try semicolon-separated format first
if ';' in content:
@@ -680,6 +800,20 @@ class LabelPrinterApp(App):
nr_art = parts[1].strip()
if len(parts) >= 3:
serial = parts[2].strip()
+ if len(parts) >= 4:
+ try:
+ template_type = int(parts[3].strip())
+ except ValueError:
+ template_type = 0
+ if len(parts) >= 5:
+ try:
+ count = int(parts[4].strip())
+ if count < 1:
+ count = 1
+ if count > 100: # Safety limit
+ count = 100
+ except ValueError:
+ count = 1
else:
# Try key=value format
for line in lines:
@@ -694,16 +828,30 @@ class LabelPrinterApp(App):
nr_art = value
elif key in ['serial', 'serial_no', 'serial-no', 'serialno']:
serial = value
+ elif key in ['template', 'template_type', 'type']:
+ try:
+ template_type = int(value)
+ except ValueError:
+ template_type = 0
+ elif key in ['count', 'quantity', 'copies']:
+ try:
+ count = int(value)
+ if count < 1:
+ count = 1
+ if count > 100:
+ count = 100
+ except ValueError:
+ count = 1
- return article, nr_art, serial
+ return article, nr_art, serial, template_type, count
except Exception as e:
print(f"Error reading file: {e}")
return None, None, None
def print_label(self, instance):
"""Handle print button press - read from file and print"""
- # Read variables from file
- article, nr_art, serial = self.read_file_variables()
+ # Read variables from file including template type and count
+ article, nr_art, serial, template_type, count = self.read_file_variables()
# Resolve display name to full printer name
printer = self._get_full_printer_name(self.printer_spinner.text)
@@ -713,10 +861,23 @@ class LabelPrinterApp(App):
self.show_popup("Error", "No data in file or file not set", auto_dismiss_after=3)
return
+ # Select template based on template_type
+ if template_type == 1:
+ template_path = os.path.join('conf', 'label_template_nok.svg')
+ template_name = "NOK"
+ else:
+ template_path = os.path.join('conf', 'label_template_ok.svg')
+ template_name = "OK"
+
+ # Verify template exists, fallback to default if not
+ if not os.path.exists(template_path):
+ template_path = os.path.join('conf', 'label_template.svg')
+ template_name = "DEFAULT"
+
# Create combined label text using semicolon separator
label_text = f"{article};{nr_art};{serial}"
- # Show loading popup
+ # Show loading popup with count info
popup = Popup(
title='Printing',
content=BoxLayout(
@@ -727,15 +888,32 @@ class LabelPrinterApp(App):
size_hint=(0.8, 0.3)
)
- popup.content.add_widget(Label(text='Processing label...\nPlease wait'))
+ popup.content.add_widget(Label(text=f'Processing {count} label(s)...\nTemplate: {template_name}\nPlease wait'))
popup.open()
# Print in background thread (using PDF by default)
def print_thread():
pdf_filename = None
success = False
+ all_success = True
try:
- success = print_label_standalone(label_text, printer, preview=0, use_pdf=True)
+ # Print multiple copies if count > 1
+ for i in range(count):
+ if count > 1:
+ Clock.schedule_once(lambda dt, idx=i: popup.content.children[0].text.replace(
+ f'Processing {count} label(s)...',
+ f'Printing {idx+1} of {count}...'
+ ), 0)
+
+ success = print_label_standalone(label_text, printer, preview=0, use_pdf=True, svg_template=template_path)
+
+ if not success:
+ all_success = False
+ break
+
+ # Small delay between prints for multiple copies
+ if i < count - 1:
+ time.sleep(0.5)
# Get the PDF filename that was created
# Files are saved to pdf_backup/ with timestamp
@@ -744,7 +922,7 @@ class LabelPrinterApp(App):
# Get the most recently created PDF file
pdf_filename = max(pdf_files, key=os.path.getctime)
- if success:
+ if all_success:
# Log the successful print action
self.log_print_action(article, nr_art, serial, printer, pdf_filename or "unknown", True)
@@ -753,7 +931,7 @@ class LabelPrinterApp(App):
# Use Clock.schedule_once to update UI from main thread
Clock.schedule_once(lambda dt: popup.dismiss(), 0)
- Clock.schedule_once(lambda dt: self.show_popup("Success", "Label printed successfully!", auto_dismiss_after=3), 0.1)
+ Clock.schedule_once(lambda dt: self.show_popup("Success", f"{count} label(s) printed successfully!", auto_dismiss_after=3), 0.1)
else:
# Log the failed print action
self.log_print_action(article, nr_art, serial, printer, pdf_filename or "unknown", False)
diff --git a/print_label.py b/print_label.py
index 3cf6c17..7e8d57f 100644
--- a/print_label.py
+++ b/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=" 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}")
diff --git a/print_label_pdf.py b/print_label_pdf.py
index 75481a9..d93caf2 100644
--- a/print_label_pdf.py
+++ b/print_label_pdf.py
@@ -38,17 +38,19 @@ except (ImportError, OSError) as e:
class PDFLabelGenerator:
"""Generate high-quality PDF labels with image and text"""
- def __init__(self, label_width=3.5, label_height=2.5, dpi=600):
+ def __init__(self, label_width=3.5, label_height=2.5, dpi=1200):
"""
Initialize PDF label generator.
Args:
label_width (float): Width in cm (default 3.5 cm = 35mm)
label_height (float): Height in cm (default 2.5 cm = 25mm)
- dpi (int): DPI for image rendering (default 600 for high quality print)
+ dpi (int): DPI for image rendering (default 1200 for high quality thermal printer)
"""
self.label_width = label_width * cm
self.label_height = label_height * cm
+ # Force landscape: ensure width > height
+ self.page_size = landscape((self.label_height, self.label_width)) if self.label_width > self.label_height else (self.label_width, self.label_height)
self.dpi = dpi
self.margin = 1 * mm # Minimal margin
@@ -68,9 +70,11 @@ class PDFLabelGenerator:
try:
img = Image.open(image_path)
- # Convert to RGB if needed
- if img.mode not in ['RGB', 'L']:
+ # Convert to RGB for best quality (don't use grayscale)
+ if img.mode != 'RGB':
img = img.convert('RGB')
+ # Set DPI information for high-quality output
+ img.info['dpi'] = (self.dpi, self.dpi)
return img
except Exception as e:
print(f"Image loading error: {e}")
@@ -130,42 +134,25 @@ class PDFLabelGenerator:
else:
pdf_output = tempfile.NamedTemporaryFile(suffix='.pdf', delete=False).name
- # Try svglib first (more portable, no external dependencies)
- if SVG_AVAILABLE:
- try:
- drawing = svg2rlg(temp_svg_path)
- if drawing:
- # Render at original size - quality depends on PDF rendering
- # The PDF will contain vector graphics for sharp output
- renderPDF.drawToFile(drawing, pdf_output)
-
- # Clean up temp SVG
- try:
- os.remove(temp_svg_path)
- except:
- pass
-
- if filename:
- return pdf_output
- else:
- with open(pdf_output, 'rb') as f:
- pdf_bytes = f.read()
- os.remove(pdf_output)
- return pdf_bytes
- except Exception as svg_err:
- print(f"svglib conversion failed: {svg_err}, trying cairosvg...")
-
- # Fallback: Try cairosvg (requires system Cairo library)
+ # Use cairosvg FIRST as it handles fonts and complex SVGs better
if CAIROSVG_AVAILABLE:
try:
- # Render at high DPI for sharp output
- cairosvg.svg2pdf(url=temp_svg_path, write_to=pdf_output, dpi=self.dpi)
+ print("Converting SVG to PDF using CairoSVG (high quality)...")
+ # CRITICAL: Let CairoSVG read dimensions from SVG file (width="35mm" height="25mm")
+ # DO NOT specify output_width/output_height as they control raster size, not PDF page size
+ # The SVG already has the correct dimensions, just render at high DPI
+ cairosvg.svg2pdf(
+ url=temp_svg_path,
+ write_to=pdf_output,
+ dpi=300 # High DPI for sharp output, page size comes from SVG
+ )
# Clean up temp SVG
try:
os.remove(temp_svg_path)
except:
pass
+ print(f"✅ PDF created from SVG template: {pdf_output}")
if filename:
return pdf_output
else:
@@ -174,9 +161,54 @@ class PDFLabelGenerator:
os.remove(pdf_output)
return pdf_bytes
except Exception as cairo_err:
- print(f"CairoSVG conversion failed: {cairo_err}")
+ print(f"CairoSVG conversion failed: {cairo_err}, trying svglib...")
- print("SVG conversion failed. svglib and cairosvg both unavailable or failed.")
+ # Fallback: Try svglib (generates many warnings but works)
+ if SVG_AVAILABLE:
+ try:
+ print("Converting SVG to PDF using svglib (fallback)...")
+ drawing = svg2rlg(temp_svg_path)
+ if drawing:
+ # CRITICAL: Force exact label dimensions (35mm x 25mm landscape)
+ # Convert to points: 1mm = 2.834645669 points
+ from reportlab.lib.units import mm
+ from reportlab.pdfgen import canvas as pdf_canvas
+
+ target_width = 35 * mm
+ target_height = 25 * mm
+
+ # Scale drawing to exact size
+ if drawing.width > 0 and drawing.height > 0:
+ scale_x = target_width / drawing.width
+ scale_y = target_height / drawing.height
+ drawing.width = target_width
+ drawing.height = target_height
+ drawing.scale(scale_x, scale_y)
+
+ # Create PDF with explicit landscape page size
+ c = pdf_canvas.Canvas(pdf_output, pagesize=(target_width, target_height))
+ c.setPageCompression(0) # No compression for quality
+ renderPDF.draw(drawing, c, 0, 0)
+ c.save()
+
+ # Clean up temp SVG
+ try:
+ os.remove(temp_svg_path)
+ except:
+ pass
+
+ print(f"✅ PDF created from SVG template: {pdf_output}")
+ if filename:
+ return pdf_output
+ else:
+ with open(pdf_output, 'rb') as f:
+ pdf_bytes = f.read()
+ os.remove(pdf_output)
+ return pdf_bytes
+ except Exception as svg_err:
+ print(f"svglib conversion failed: {svg_err}")
+
+ print("❌ SVG conversion failed. Both cairosvg and svglib unavailable or failed.")
return None
except Exception as e:
@@ -226,11 +258,15 @@ class PDFLabelGenerator:
else:
pdf_buffer = io.BytesIO()
- # Create canvas with label dimensions
- c = canvas.Canvas(pdf_buffer, pagesize=(self.label_width, self.label_height))
+ # Create canvas with label dimensions - explicitly landscape
+ c = canvas.Canvas(pdf_buffer, pagesize=self.page_size)
- # Set higher resolution for better quality
- c.setPageCompression(1) # Enable compression
+ # CRITICAL: Disable compression for maximum print quality
+ c.setPageCompression(0) # Disable compression for best quality
+
+ # Set high resolution for crisp output on thermal printers
+ # Page size already set to landscape orientation
+ c._pagesize = self.page_size
# Calculate dimensions
usable_width = self.label_width - 2 * self.margin
@@ -257,11 +293,11 @@ class PDFLabelGenerator:
temp_img_path = temp_img_file.name
temp_img_file.close()
- # Convert to grayscale for black and white
- img_bw = img.convert('L')
- img_bw.save(temp_img_path, 'PNG')
+ # Keep as RGB for better quality (thermal printers handle conversion)
+ # Save at high DPI for sharp output
+ img.save(temp_img_path, 'PNG', dpi=(self.dpi, self.dpi), optimize=False)
- # Draw image maintaining aspect ratio
+ # Draw image maintaining aspect ratio with high quality
c.drawImage(
temp_img_path,
image_x,
@@ -269,7 +305,8 @@ class PDFLabelGenerator:
width=image_width,
height=image_height,
preserveAspectRatio=True,
- anchor='c'
+ anchor='c',
+ mask='auto' # Better quality rendering
)
# Clean up
@@ -291,10 +328,15 @@ class PDFLabelGenerator:
else:
text = f"{label_name} -"
- # Use appropriate font size to fit (6pt = ~2.1mm height)
- font_size = 6
+ # IMPROVED: Larger font size for better readability (8pt = ~2.8mm height)
+ # This is critical for thermal printers - text must be crisp and readable
+ font_size = 8
c.setFont("Helvetica-Bold", font_size)
+ # Enable text rendering mode for crisp output
+ c.setStrokeColorRGB(0, 0, 0)
+ c.setFillColorRGB(0, 0, 0)
+
try:
c.drawString(text_area_x, y_position, text)
except Exception as e:
diff --git a/requirements_windows.txt b/requirements_windows.txt
index 9d16dae..dc20ee8 100644
--- a/requirements_windows.txt
+++ b/requirements_windows.txt
@@ -1,6 +1,6 @@
python-barcode
pillow
-kivy>=2.1.0
+kivy>=2.3.0
reportlab
pyinstaller>=6.0.0
pywin32
diff --git a/test_conf_generation.py b/test_conf_generation.py
new file mode 100644
index 0000000..cd0325a
--- /dev/null
+++ b/test_conf_generation.py
@@ -0,0 +1,63 @@
+"""
+Test script to verify conf folder auto-generation
+Run this to test that conf/ folder is created with default templates
+"""
+
+import os
+import shutil
+
+# Temporarily rename existing conf folder to test auto-generation
+if os.path.exists('conf'):
+ print("Backing up existing conf folder to conf_backup...")
+ if os.path.exists('conf_backup'):
+ shutil.rmtree('conf_backup')
+ shutil.move('conf', 'conf_backup')
+ print("✓ Existing conf folder backed up")
+
+print("\nTesting conf folder auto-generation...")
+print("Starting label_printer_gui initialization...")
+
+# Import the app (this will trigger initialization)
+from label_printer_gui import LabelPrinterApp
+
+# Create app instance (will auto-create conf folder)
+app = LabelPrinterApp()
+
+# Check if conf folder was created
+if os.path.exists('conf'):
+ print("\n✓ SUCCESS: conf folder created")
+
+ # Check for required files
+ required_files = [
+ 'conf/app.conf',
+ 'conf/label_template.svg',
+ 'conf/label_template_ok.svg',
+ 'conf/label_template_nok.svg'
+ ]
+
+ missing_files = []
+ for file in required_files:
+ if os.path.exists(file):
+ print(f" ✓ {file}")
+ else:
+ print(f" ✗ {file} MISSING")
+ missing_files.append(file)
+
+ if not missing_files:
+ print("\n✓ All required files created successfully!")
+ else:
+ print(f"\n✗ Missing files: {missing_files}")
+else:
+ print("\n✗ FAILED: conf folder not created")
+
+# Restore original conf folder
+print("\nRestoring original conf folder...")
+if os.path.exists('conf_backup'):
+ if os.path.exists('conf'):
+ shutil.rmtree('conf')
+ shutil.move('conf_backup', 'conf')
+ print("✓ Original conf folder restored")
+
+print("\n" + "="*50)
+print("Test complete!")
+print("="*50)