"""Pantone 认证摸底测试面板。""" from __future__ import annotations import csv import datetime import os import tempfile import threading import tkinter as tk from tkinter import filedialog, messagebox import ttkbootstrap as ttk from PIL import Image from drivers.ucd_helpers import get_current_resolution, send_image_pattern _PATTERN_FILE = "pantone_patterns_2670.csv" _TEMPLATE_FILE = "pantone\xa02670\xa0colors.xlsx" _TARGET_RESULT_COUNT = 2760 def create_pantone_baseline_panel(self): """创建 Pantone 认证摸底测试面板。""" frame = ttk.Frame(self.content_frame) self.pantone_baseline_frame = frame self.pantone_baseline_visible = False self.pantone_patterns = [] self.pantone_results = [] self._pantone_control_event = None self._pantone_running = False self._pantone_paused = False self._pantone_pause_requested = False self._pantone_stop_requested = False self._pantone_next_index = 0 root = ttk.Frame(frame, padding=10) root.pack(fill=tk.BOTH, expand=True) title_row = ttk.Frame(root) title_row.pack(fill=tk.X, pady=(0, 8)) ttk.Label( title_row, text="Pantone认证摸底测试", font=("微软雅黑", 14, "bold"), ).pack(side=tk.LEFT) self.pantone_status_var = tk.StringVar(value="未开始") self.pantone_progress_var = tk.StringVar(value="0 / 2670") self.pantone_settle_var = tk.StringVar(value="0.35") config_row = ttk.LabelFrame(root, text="任务配置", padding=8) config_row.pack(fill=tk.X) ttk.Label(config_row, text=f"Pattern来源: settings/{_PATTERN_FILE}").pack( side=tk.LEFT ) ttk.Label(config_row, text="稳定等待(s):").pack(side=tk.LEFT, padx=(14, 4)) ttk.Entry(config_row, textvariable=self.pantone_settle_var, width=8).pack( side=tk.LEFT ) ttk.Label(config_row, textvariable=self.pantone_progress_var).pack( side=tk.RIGHT, padx=(8, 0) ) ttk.Label(config_row, textvariable=self.pantone_status_var, foreground="#666").pack( side=tk.RIGHT ) btn_row = ttk.Frame(root) btn_row.pack(fill=tk.X, pady=(8, 8)) self.pantone_start_btn = ttk.Button( btn_row, text="开始", bootstyle="primary", command=lambda: _start_pantone_baseline(self), ) self.pantone_start_btn.pack(side=tk.LEFT, padx=(0, 6)) self.pantone_pause_btn = ttk.Button( btn_row, text="暂停", bootstyle="warning-outline", command=lambda: _pause_pantone_baseline(self), state=tk.DISABLED, ) self.pantone_pause_btn.pack(side=tk.LEFT, padx=(0, 6)) self.pantone_resume_btn = ttk.Button( btn_row, text="继续", bootstyle="info-outline", command=lambda: _resume_pantone_baseline(self), state=tk.DISABLED, ) self.pantone_resume_btn.pack(side=tk.LEFT, padx=(0, 6)) self.pantone_end_btn = ttk.Button( btn_row, text="结束", bootstyle="danger-outline", command=lambda: _end_pantone_baseline(self), state=tk.DISABLED, ) self.pantone_end_btn.pack(side=tk.LEFT, padx=(0, 6)) ttk.Button( btn_row, text="清空结果", bootstyle="secondary-outline", command=lambda: _clear_results(self), ).pack(side=tk.LEFT, padx=(0, 6)) ttk.Button( btn_row, text="另存为模板", bootstyle="success", command=lambda: _save_as_template(self), ).pack(side=tk.LEFT) table_frame = ttk.LabelFrame(root, text="测试结果(R,G,B,L,x,y)", padding=8) table_frame.pack(fill=tk.BOTH, expand=True) columns = ("idx", "r", "g", "b", "l", "x", "y", "time") self.pantone_tree = ttk.Treeview( table_frame, columns=columns, show="headings", height=18, ) headings = { "idx": "序号", "r": "R", "g": "G", "b": "B", "l": "L", "x": "x", "y": "y", "time": "时间", } widths = { "idx": 70, "r": 70, "g": 70, "b": 70, "l": 90, "x": 90, "y": 90, "time": 110, } for col in columns: self.pantone_tree.heading(col, text=headings[col]) self.pantone_tree.column(col, width=widths[col], anchor=tk.CENTER) self.pantone_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scrollbar = ttk.Scrollbar(table_frame, orient=tk.VERTICAL, command=self.pantone_tree.yview) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.pantone_tree.configure(yscrollcommand=scrollbar.set) self.register_panel("pantone_baseline", frame, None, "pantone_baseline_visible") _set_button_states(self) def toggle_pantone_baseline_panel(self): """切换 Pantone 认证摸底测试面板。""" self.show_panel("pantone_baseline") def _load_patterns(self): path = os.path.join("settings", _PATTERN_FILE) if not os.path.isfile(path): raise FileNotFoundError(f"未找到 pattern 文件: {path}") patterns = [] with open(path, "r", encoding="utf-8-sig", newline="") as fp: reader = csv.DictReader(fp) for row in reader: try: r = int(row.get("R", "").strip()) g = int(row.get("G", "").strip()) b = int(row.get("B", "").strip()) except Exception: continue if min(r, g, b) < 0 or max(r, g, b) > 255: continue patterns.append((r, g, b)) if not patterns: raise RuntimeError("pattern 文件为空或格式不正确,需包含列 R,G,B") return patterns def _build_temp_patch(self, rgb): width, height = get_current_resolution(self.ucd) temp_dir = os.path.join(tempfile.gettempdir(), "pq_pantone_baseline") os.makedirs(temp_dir, exist_ok=True) file_path = os.path.join(temp_dir, "pantone_current_patch.png") Image.new("RGB", (width, height), rgb).save(file_path, format="PNG") return file_path def _start_pantone_baseline(self): if self._pantone_running: messagebox.showinfo("提示", "Pantone 任务正在执行") return if not getattr(self, "ucd", None) or not self.ucd.status: messagebox.showwarning("警告", "请先连接 UCD323") return if not getattr(self, "ca", None): messagebox.showwarning("警告", "请先连接 CA410") return try: settle = float(self.pantone_settle_var.get().strip()) if settle < 0: raise ValueError() except Exception: messagebox.showerror("参数错误", "稳定等待时间必须是非负数字") return try: self.pantone_patterns = _load_patterns(self) except Exception as exc: messagebox.showerror("读取失败", str(exc)) return if self.pantone_results: if not messagebox.askyesno("确认开始", "开始将清空当前内存中的测试结果,是否继续?"): return self._pantone_running = True self._pantone_paused = False self._pantone_pause_requested = False self._pantone_stop_requested = False self._pantone_control_event = threading.Event() self._pantone_next_index = 0 self.pantone_status_var.set("执行中") self.pantone_progress_var.set(f"0 / {_TARGET_RESULT_COUNT}") self.pantone_results = [] for item in self.pantone_tree.get_children(): self.pantone_tree.delete(item) _set_button_states(self) _launch_worker(self, start_index=0, settle=settle) def _resume_pantone_baseline(self): if self._pantone_running: messagebox.showinfo("提示", "Pantone 任务正在执行") return if not self._pantone_paused: messagebox.showinfo("提示", "当前没有可继续的暂停任务") return if self._pantone_next_index >= _TARGET_RESULT_COUNT: messagebox.showinfo("提示", "任务已完成,无需继续") return if not getattr(self, "ucd", None) or not self.ucd.status: messagebox.showwarning("警告", "请先连接 UCD323") return if not getattr(self, "ca", None): messagebox.showwarning("警告", "请先连接 CA410") return try: settle = float(self.pantone_settle_var.get().strip()) if settle < 0: raise ValueError() except Exception: messagebox.showerror("参数错误", "稳定等待时间必须是非负数字") return try: self.pantone_patterns = _load_patterns(self) except Exception as exc: messagebox.showerror("读取失败", str(exc)) return self._pantone_running = True self._pantone_paused = False self._pantone_pause_requested = False self._pantone_stop_requested = False self._pantone_control_event = threading.Event() self.pantone_status_var.set("执行中") _set_button_states(self) _launch_worker(self, start_index=self._pantone_next_index, settle=settle) def _launch_worker(self, start_index, settle): total = _TARGET_RESULT_COUNT def worker(): end_state = "completed" try: src = self.pantone_patterns src_count = len(src) self._dispatch_ui( self.log_gui.log, f"Pantone 认证摸底启动: source={src_count}, target={total}, start={start_index + 1}", "info", ) for i in range(start_index, total): if self._pantone_stop_requested: end_state = "stopped" break if self._pantone_pause_requested: end_state = "paused" break r, g, b = src[i % src_count] image_path = _build_temp_patch(self, (r, g, b)) if not send_image_pattern(self.ucd, image_path): raise RuntimeError(f"第 {i + 1} 组发送失败") if settle > 0 and self._pantone_control_event is not None: self._pantone_control_event.clear() self._pantone_control_event.wait(timeout=settle) if self._pantone_stop_requested: end_state = "stopped" break if self._pantone_pause_requested: end_state = "paused" break x, y, lv, _X, _Y, _Z = self.ca.readAllDisplay() if lv is None: raise RuntimeError(f"第 {i + 1} 组 CA410 采集失败") record = { "idx": i + 1, "r": r, "g": g, "b": b, "l": float(lv), "x": float(x), "y": float(y), "time": datetime.datetime.now().strftime("%H:%M:%S"), } self.pantone_results.append(record) self._pantone_next_index = i + 1 self._dispatch_ui(_append_result_row, self, record, total) if end_state == "paused": self._pantone_paused = True self._dispatch_ui(self.pantone_status_var.set, "已暂停") self._dispatch_ui( self.log_gui.log, f"Pantone 任务已暂停,断点 {self._pantone_next_index} / {total}", "warning", ) elif end_state == "stopped": self._pantone_paused = False self._pantone_next_index = 0 self._dispatch_ui(self.pantone_status_var.set, "已结束") self._dispatch_ui( self.log_gui.log, f"Pantone 任务已结束,保留 {len(self.pantone_results)} 条内存结果", "warning", ) else: self._pantone_paused = False self._pantone_next_index = total self._dispatch_ui(self.pantone_status_var.set, "已完成") self._dispatch_ui( self.log_gui.log, f"Pantone 任务完成,共 {len(self.pantone_results)} 条数据", "success", ) except Exception as exc: self._pantone_paused = False self._dispatch_ui(self.pantone_status_var.set, "执行失败") self._dispatch_ui(self.log_gui.log, f"Pantone 任务失败: {exc}", "error") self._dispatch_ui(messagebox.showerror, "执行失败", str(exc)) finally: self._pantone_running = False self._pantone_pause_requested = False self._pantone_stop_requested = False self._dispatch_ui(_set_button_states, self) threading.Thread(target=worker, daemon=True).start() def _append_result_row(self, record, total): self.pantone_tree.insert( "", tk.END, values=( record["idx"], record["r"], record["g"], record["b"], f"{record['l']:.2f}", f"{record['x']:.4f}", f"{record['y']:.4f}", record["time"], ), ) self.pantone_progress_var.set(f"{record['idx']} / {total}") # 每次插入都滚到末尾,便于观察采集进度。 children = self.pantone_tree.get_children() if children: self.pantone_tree.see(children[-1]) def _pause_pantone_baseline(self): if not self._pantone_running: messagebox.showinfo("提示", "当前没有运行中的任务") return self._pantone_pause_requested = True self.pantone_status_var.set("暂停中...") if self._pantone_control_event is not None: self._pantone_control_event.set() def _end_pantone_baseline(self): if self._pantone_running: self._pantone_stop_requested = True self.pantone_status_var.set("结束中...") if self._pantone_control_event is not None: self._pantone_control_event.set() return # 非运行态下点击“结束”:清除断点,保留当前内存结果。 self._pantone_paused = False self._pantone_next_index = 0 self.pantone_status_var.set("已结束") _set_button_states(self) def _clear_results(self): if self._pantone_running: messagebox.showinfo("提示", "任务执行中,无法清空") return self.pantone_results = [] self._pantone_paused = False self._pantone_next_index = 0 for item in self.pantone_tree.get_children(): self.pantone_tree.delete(item) self.pantone_progress_var.set(f"0 / {_TARGET_RESULT_COUNT}") self.pantone_status_var.set("结果已清空") _set_button_states(self) def _set_button_states(self): if self._pantone_running: self.pantone_start_btn.configure(state=tk.DISABLED) self.pantone_pause_btn.configure(state=tk.NORMAL) self.pantone_resume_btn.configure(state=tk.DISABLED) self.pantone_end_btn.configure(state=tk.NORMAL) return self.pantone_start_btn.configure(state=tk.NORMAL) self.pantone_pause_btn.configure(state=tk.DISABLED) self.pantone_end_btn.configure(state=tk.NORMAL if (self._pantone_paused or self.pantone_results) else tk.DISABLED) can_resume = self._pantone_paused and self._pantone_next_index < _TARGET_RESULT_COUNT self.pantone_resume_btn.configure(state=tk.NORMAL if can_resume else tk.DISABLED) def _save_as_template(self): if not self.pantone_results: messagebox.showinfo("提示", "暂无可导出的结果") return default_name = "pantone 2670 colors.xlsx" path = filedialog.asksaveasfilename( title="另存为 Pantone 模板", defaultextension=".xlsx", initialfile=default_name, filetypes=[("Excel 文件", "*.xlsx")], ) if not path: return # 优先复制 settings 模板,再覆盖数据区;没有模板时自动创建同结构表。 template_path = os.path.join("settings", _TEMPLATE_FILE) try: from openpyxl import load_workbook, Workbook if os.path.isfile(template_path): wb = load_workbook(template_path) ws = wb.active else: wb = Workbook() ws = wb.active ws.title = "Sheet1" ws.cell(row=1, column=1, value="R") ws.cell(row=1, column=2, value="G") ws.cell(row=1, column=3, value="B") ws.cell(row=1, column=4, value="L") ws.cell(row=1, column=5, value="x") ws.cell(row=1, column=6, value="y") # 清空旧数据 max_row = max(ws.max_row, 2) for row in range(2, max_row + 1): for col in range(1, 7): ws.cell(row=row, column=col, value=None) for idx, item in enumerate(self.pantone_results, start=2): ws.cell(row=idx, column=1, value=int(item["r"])) ws.cell(row=idx, column=2, value=int(item["g"])) ws.cell(row=idx, column=3, value=int(item["b"])) ws.cell(row=idx, column=4, value=float(item["l"])) ws.cell(row=idx, column=5, value=float(item["x"])) ws.cell(row=idx, column=6, value=float(item["y"])) wb.save(path) self.log_gui.log(f"Pantone 模板已保存: {path}", level="success") self.pantone_status_var.set(f"已保存: {os.path.basename(path)}") except Exception as exc: messagebox.showerror("保存失败", f"写入 xlsx 失败: {exc}")