#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 工具运行配置: 运行目录定位 / pattern_slots.json(自定义栏位 + 串口记忆) ================================================================ PyInstaller onefile 打包后 __file__ 指向临时解压目录, 配置必须落在 exe 旁边, 故 frozen 时改用 sys.executable 定位。 """ import json import os import shutil import sys import dlpc_rs485 # 可选 Pillow: 装了则支持 JPEG/BMP/WebP, 没装则用 tkinter 原生(仅 PNG/GIF) # Image/ImageTk 在此导入一次, patterns.py 复用同一份对象 try: from PIL import Image, ImageTk # noqa: F401 HAS_PIL = True except ImportError: Image = ImageTk = None HAS_PIL = False # 测试卡区域常量(栏位配置与投屏共用) PAT_W, PAT_H = 640, 360 AREA_OPTIONS = ('640x360', '800x600', 'full') DEFAULT_AREA = AREA_OPTIONS[0] # ============================================================ # 自定义图案栏位: 配置存于脚本目录 pattern_slots.json, # 图片文件同样保存在脚本目录下 # 注: PyInstaller onefile 打包后 __file__ 指向临时解压目录, # 配置必须落在 exe 旁边, 故 frozen 时改用 sys.executable 定位 # ============================================================ if getattr(sys, 'frozen', False): BASE_DIR = os.path.dirname(os.path.abspath(sys.executable)) else: BASE_DIR = os.path.dirname(os.path.abspath(__file__)) SLOT_COUNT = 10 SLOT_CONFIG = os.path.join(BASE_DIR, 'pattern_slots.json') IMG_EXTS = ['.png', '.gif'] + (['.jpg', '.jpeg', '.bmp', '.webp'] if HAS_PIL else []) # 光机控制串口记忆(与栏位配置同存 pattern_slots.json 的 "serial" 键) SERIAL_CFG = {'port': '', 'baud': dlpc_rs485.DEFAULT_BAUD, 'bytesize': 8, 'parity': 'N', 'stopbits': 1} def load_slots(): """读 pattern_slots.json, 返回 (slots, area)。 新格式为 {'area': 'WxH'|'full', 'slots': [...], 'serial': {...}}; 兼容旧版纯数组格式(无 area)与无 serial 键的中间格式。 """ slots = [{'name': f'自定义{i + 1}', 'file': None} for i in range(SLOT_COUNT)] try: with open(SLOT_CONFIG, encoding='utf-8') as f: data = json.load(f) except Exception: return slots, DEFAULT_AREA if isinstance(data, dict): area = str(data.get('area') or DEFAULT_AREA) items = data.get('slots') or [] ser = data.get('serial') if isinstance(ser, dict): SERIAL_CFG['port'] = str(ser.get('port') or '') try: SERIAL_CFG['baud'] = int(ser.get('baud') or dlpc_rs485.DEFAULT_BAUD) except (TypeError, ValueError): pass try: SERIAL_CFG['bytesize'] = int(ser.get('bytesize') or 8) except (TypeError, ValueError): pass SERIAL_CFG['parity'] = str(ser.get('parity') or 'N').upper()[:1] try: SERIAL_CFG['stopbits'] = int(ser.get('stopbits') or 1) except (TypeError, ValueError): pass else: area, items = DEFAULT_AREA, data for i, item in enumerate(items[:SLOT_COUNT]): if isinstance(item, dict): slots[i]['name'] = str(item.get('name') or slots[i]['name']) slots[i]['file'] = item.get('file') or None return slots, area def save_slots(slots, area): with open(SLOT_CONFIG, 'w', encoding='utf-8') as f: json.dump({'area': area, 'slots': slots, 'serial': SERIAL_CFG}, f, ensure_ascii=False, indent=2) def copy_image_into_script_dir(src_path): """把任意路径的图片复制到脚本目录, 重名自动加序号, 返回文件名""" dst_name = os.path.basename(src_path) dst = os.path.join(BASE_DIR, dst_name) if os.path.normcase(os.path.abspath(src_path)) == os.path.normcase(dst): return dst_name stem, ext = os.path.splitext(dst_name) n = 1 while os.path.exists(dst): dst_name = f'{stem}_{n}{ext}' dst = os.path.join(BASE_DIR, dst_name) n += 1 shutil.copy2(src_path, dst) return dst_name