瀏覽代碼

updating bag1 for label printer

reset-do-picking-order
Fai Luk 6 天之前
父節點
當前提交
087c008e25
共有 3 個文件被更改,包括 201 次插入9 次删除
  1. +191
    -9
      python/Bag1.py
  2. +8
    -0
      python/installAndExe.txt
  3. +2
    -0
      python/requirements.txt

+ 191
- 9
python/Bag1.py 查看文件

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

try:
import win32print # type: ignore[import]
import win32ui # type: ignore[import]
import win32con # type: ignore[import]
import win32gui # type: ignore[import]
except ImportError:
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")
# 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"""


# 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:
"""Send ZPL to DataFlex printer via TCP. Raises on connection/send error."""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -998,17 +1168,29 @@ def main() -> None:
item_id = j.get("itemId")
stock_in_line_id = j.get("stockInLineId")
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)
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} 張標簽 (連續)"
messagebox.showinfo("標簽機", msg)
except Exception as err:


+ 8
- 0
python/installAndExe.txt 查看文件

@@ -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 查看文件

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

Loading…
取消
儲存