Quellcode durchsuchen

updating bag1 for label printer

reset-do-picking-order
Fai Luk vor 6 Tagen
Ursprung
Commit
087c008e25
3 geänderte Dateien mit 201 neuen und 9 gelöschten Zeilen
  1. +191
    -9
      python/Bag1.py
  2. +8
    -0
      python/installAndExe.txt
  3. +2
    -0
      python/requirements.txt

+ 191
- 9
python/Bag1.py Datei anzeigen

@@ -12,6 +12,7 @@ import os
import select import select
import socket import socket
import sys import sys
import tempfile
import threading import threading
import time import time
import tkinter as tk import tkinter as tk
@@ -28,8 +29,25 @@ except ImportError:


try: try:
import win32print # type: ignore[import] import win32print # type: ignore[import]
import win32ui # type: ignore[import]
import win32con # type: ignore[import]
import win32gui # type: ignore[import]
except ImportError: except ImportError:
win32print = None # type: ignore[assignment] win32print = None # type: ignore[assignment]
win32ui = None # type: ignore[assignment]
win32con = None # type: ignore[assignment]
win32gui = None # type: ignore[assignment]

try:
from PIL import Image, ImageDraw, ImageFont
import qrcode
_HAS_PIL_QR = True
except ImportError:
Image = None # type: ignore[assignment]
ImageDraw = None # type: ignore[assignment]
ImageFont = None # type: ignore[assignment]
qrcode = None # type: ignore[assignment]
_HAS_PIL_QR = False


DEFAULT_BASE_URL = os.environ.get("FPSMS_BASE_URL", "http://localhost:8090/api") DEFAULT_BASE_URL = os.environ.get("FPSMS_BASE_URL", "http://localhost:8090/api")
# When run as PyInstaller exe, save settings next to the exe; otherwise next to script # When run as PyInstaller exe, save settings next to the exe; otherwise next to script
@@ -240,6 +258,158 @@ def generate_zpl_label_small(
^XZ""" ^XZ"""




# Label image size (pixels) for 標簽機 image printing; words drawn bigger for readability
LABEL_IMAGE_W = 520
LABEL_IMAGE_H = 520
LABEL_PADDING = 20
LABEL_FONT_NAME_SIZE = 38 # item name (bigger)
LABEL_FONT_CODE_SIZE = 44 # item code (bigger)
LABEL_FONT_BATCH_SIZE = 30 # batch/lot line
LABEL_QR_SIZE = 140 # QR module size in pixels


def _get_chinese_font(size: int) -> Optional["ImageFont.FreeTypeFont"]:
"""Return a Chinese-capable font for PIL, or None to use default."""
if ImageFont is None:
return None
# Prefer Traditional Chinese fonts on Windows
for name in ("Microsoft JhengHei UI", "Microsoft JhengHei", "MingLiU", "SimHei", "Microsoft YaHei", "SimSun"):
try:
return ImageFont.truetype(name, size)
except (OSError, IOError):
continue
try:
return ImageFont.load_default()
except Exception:
return None


def render_label_to_image(
batch_no: str,
item_code: str,
item_name: str,
item_id: Optional[int] = None,
stock_in_line_id: Optional[int] = None,
lot_no: Optional[str] = None,
) -> "Image.Image":
"""
Render 標簽機 label as a PIL Image (white bg, black text + QR).
Use this image for printing so Chinese displays correctly; words are drawn bigger.
Requires Pillow and qrcode. Raises RuntimeError if not available.
"""
if not _HAS_PIL_QR or Image is None or qrcode is None:
raise RuntimeError("Pillow and qrcode are required for image labels. Run: pip install Pillow qrcode[pil]")
img = Image.new("RGB", (LABEL_IMAGE_W, LABEL_IMAGE_H), "white")
draw = ImageDraw.Draw(img)
# QR payload (same as ZPL)
if item_id is not None and stock_in_line_id is not None:
qr_data = json.dumps({"itemId": item_id, "stockInLineId": stock_in_line_id})
else:
qr_data = f"QA,{batch_no}"
# Draw QR top-left area
qr = qrcode.QRCode(box_size=4, border=2)
qr.add_data(qr_data)
qr.make(fit=True)
qr_img = qr.make_image(fill_color="black", back_color="white")
_resample = getattr(Image, "Resampling", Image).NEAREST
qr_img = qr_img.resize((LABEL_QR_SIZE, LABEL_QR_SIZE), _resample)
img.paste(qr_img, (LABEL_PADDING, LABEL_PADDING))
# Fonts (bigger for readability)
font_name = _get_chinese_font(LABEL_FONT_NAME_SIZE)
font_code = _get_chinese_font(LABEL_FONT_CODE_SIZE)
font_batch = _get_chinese_font(LABEL_FONT_BATCH_SIZE)
x_right = LABEL_PADDING + LABEL_QR_SIZE + LABEL_PADDING
y_line = LABEL_PADDING
# Line 1: item name (wrap within remaining width)
name_str = (item_name or "—").strip()
max_name_w = LABEL_IMAGE_W - x_right - LABEL_PADDING
if font_name:
# Wrap by text width (Pillow 8+ textbbox) or by char count
def _wrap_text(text: str, font, max_width: int) -> list:
if hasattr(draw, "textbbox"):
words = list(text)
lines, line = [], []
for c in words:
line.append(c)
bbox = draw.textbbox((0, 0), "".join(line), font=font)
if bbox[2] - bbox[0] > max_width and len(line) > 1:
lines.append("".join(line[:-1]))
line = [line[-1]]
if line:
lines.append("".join(line))
return lines
# Fallback: ~12 chars per line for Chinese
chunk = 12
return [text[i : i + chunk] for i in range(0, len(text), chunk)]
lines = _wrap_text(name_str, font_name, max_name_w)
for i, ln in enumerate(lines):
draw.text((x_right, y_line + i * (LABEL_FONT_NAME_SIZE + 4)), ln, font=font_name, fill="black")
y_line += len(lines) * (LABEL_FONT_NAME_SIZE + 4) + 8
else:
draw.text((x_right, y_line), name_str[:30], fill="black")
y_line += LABEL_FONT_NAME_SIZE + 12
# Item code (bigger)
code_str = (item_code or "—").strip()
if font_code:
draw.text((x_right, y_line), code_str, font=font_code, fill="black")
else:
draw.text((x_right, y_line), code_str, fill="black")
y_line += LABEL_FONT_CODE_SIZE + 6
# Batch/lot line
batch_str = (lot_no or batch_no or "—").strip()
if font_batch:
draw.text((x_right, y_line), batch_str, font=font_batch, fill="black")
else:
draw.text((x_right, y_line), batch_str, fill="black")
return img


def send_image_to_label_printer(printer_name: str, pil_image: "Image.Image") -> None:
"""
Send a PIL Image to 標簽機 via Windows GDI (so Chinese and graphics print correctly).
Only supported when target is a Windows printer name (not COM port). Requires pywin32.
"""
dest = (printer_name or "").strip()
if not dest:
raise ValueError("Label printer destination is empty.")
if os.name != "nt" or dest.upper().startswith("COM"):
raise RuntimeError("Image printing is only supported for a Windows printer name (e.g. TSC TTP-246M Pro).")
if win32print is None or win32ui is None or win32con is None or win32gui is None:
raise RuntimeError("pywin32 is required. Run: pip install pywin32")
# Draw image to printer DC via temp BMP (GDI uses BMP)
with tempfile.NamedTemporaryFile(suffix=".bmp", delete=False) as f:
tmp_bmp = f.name
try:
pil_image.save(tmp_bmp, "BMP")
hbm = win32gui.LoadImage(
0, tmp_bmp, win32con.IMAGE_BITMAP, 0, 0,
win32con.LR_LOADFROMFILE | win32con.LR_CREATEDIBSECTION,
)
if hbm == 0:
raise RuntimeError("Failed to load label image as bitmap.")
dc = win32ui.CreateDC()
dc.CreatePrinterDC(dest)
dc.StartDoc("FPSMS Label")
dc.StartPage()
try:
mem_dc = win32ui.CreateDCFromHandle(win32gui.CreateCompatibleDC(dc.GetSafeHdc()))
# PyCBitmap.FromHandle works across pywin32 versions
bmp = getattr(win32ui, "CreateBitmapFromHandle", lambda h: win32ui.PyCBitmap.FromHandle(h))(hbm)
mem_dc.SelectObject(bmp)
bmp_w = pil_image.width
bmp_h = pil_image.height
dc.StretchBlt((0, 0), (bmp_w, bmp_h), mem_dc, (0, 0), (bmp_w, bmp_h), win32con.SRCCOPY)
finally:
win32gui.DeleteObject(hbm)
dc.EndPage()
dc.EndDoc()
finally:
try:
os.unlink(tmp_bmp)
except OSError:
pass


def send_zpl_to_dataflex(ip: str, port: int, zpl: str) -> None: def send_zpl_to_dataflex(ip: str, port: int, zpl: str) -> None:
"""Send ZPL to DataFlex printer via TCP. Raises on connection/send error.""" """Send ZPL to DataFlex printer via TCP. Raises on connection/send error."""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -998,17 +1168,29 @@ def main() -> None:
item_id = j.get("itemId") item_id = j.get("itemId")
stock_in_line_id = j.get("stockInLineId") stock_in_line_id = j.get("stockInLineId")
lot_no = j.get("lotNo") lot_no = j.get("lotNo")
zpl = generate_zpl_label_small(
b, item_code, item_name,
item_id=item_id, stock_in_line_id=stock_in_line_id,
lot_no=lot_no,
)
n = 100 if count == "C" else int(count) n = 100 if count == "C" else int(count)
try: try:
for i in range(n):
send_zpl_to_label_printer(com, zpl)
if i < n - 1:
time.sleep(0.5)
# Prefer image printing so Chinese displays correctly; words are bigger
if _HAS_PIL_QR and os.name == "nt" and not com.upper().startswith("COM"):
label_img = render_label_to_image(
b, item_code, item_name,
item_id=item_id, stock_in_line_id=stock_in_line_id,
lot_no=lot_no,
)
for i in range(n):
send_image_to_label_printer(com, label_img)
if i < n - 1:
time.sleep(0.5)
else:
zpl = generate_zpl_label_small(
b, item_code, item_name,
item_id=item_id, stock_in_line_id=stock_in_line_id,
lot_no=lot_no,
)
for i in range(n):
send_zpl_to_label_printer(com, zpl)
if i < n - 1:
time.sleep(0.5)
msg = f"已送出列印:{n} 張標簽" if count != "C" else f"已送出列印:{n} 張標簽 (連續)" msg = f"已送出列印:{n} 張標簽" if count != "C" else f"已送出列印:{n} 張標簽 (連續)"
messagebox.showinfo("標簽機", msg) messagebox.showinfo("標簽機", msg)
except Exception as err: except Exception as err:


+ 8
- 0
python/installAndExe.txt Datei anzeigen

@@ -0,0 +1,8 @@
py -m pip install pyinstaller
py -m pip install --upgrade pyinstaller
py -m PyInstaller --onefile --windowed --name "Bag1" Bag1.py


python -m pip install pyinstaller
python -m pip install --upgrade pyinstaller
python -m PyInstaller --onefile --windowed --name "Bag1" Bag1.py

+ 2
- 0
python/requirements.txt Datei anzeigen

@@ -1,3 +1,5 @@
# Python dependencies for FPSMS backend integration # Python dependencies for FPSMS backend integration
requests>=2.28.0 requests>=2.28.0
pyserial>=3.5 pyserial>=3.5
Pillow>=9.0.0
qrcode[pil]>=7.0

Laden…
Abbrechen
Speichern