"""Pantone 认证摸底测试面板。""" from __future__ import annotations import datetime import os import threading import tkinter as tk from tkinter import filedialog, messagebox import ttkbootstrap as ttk _TEMPLATE_FILE = "pantone\xa02670\xa0colors.xlsx" 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 self._pantone_target_count = 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 / 0") 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/{_TEMPLATE_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", _TEMPLATE_FILE) if not os.path.isfile(path): raise FileNotFoundError(f"未找到模板文件: {path}") from openpyxl import load_workbook patterns = [] wb = load_workbook(path, read_only=True, data_only=True) ws = wb.active try: for row in ws.iter_rows(min_row=2, values_only=True): if not row: continue try: r = int(row[0]) if row[0] is not None else None g = int(row[1]) if len(row) > 1 and row[1] is not None else None b = int(row[2]) if len(row) > 2 and row[2] is not None else None except Exception: continue if r is None or g is None or b is None: continue if min(r, g, b) < 0 or max(r, g, b) > 255: continue patterns.append((r, g, b)) finally: wb.close() if not patterns: raise RuntimeError("模板中未找到有效 RGB 列表(需包含 R/G/B 三列)") return patterns 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) self._pantone_target_count = len(self.pantone_patterns) 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 / {self._pantone_target_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 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) self._pantone_target_count = len(self.pantone_patterns) except Exception as exc: messagebox.showerror("读取失败", str(exc)) return if self._pantone_next_index >= self._pantone_target_count: messagebox.showinfo("提示", "任务已完成,无需继续") 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 = self._pantone_target_count or len(self.pantone_patterns) def worker(): end_state = "completed" try: src = self.pantone_patterns src_count = len(src) rgb_session = self.pattern_service.prepare_session("rgb", log_details=False) 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] try: self.pattern_service.send_rgb((r, g, b), session=rgb_session) except Exception as exc: raise RuntimeError(f"第 {i + 1} 组发送失败: {exc}") from exc 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", ) try: auto_path = _auto_save_template(self) self._dispatch_ui( self.log_gui.log, f"Pantone 模板已自动保存: {auto_path}", "success", ) except Exception as exc: self._dispatch_ui( self.log_gui.log, f"Pantone 自动保存模板失败: {exc}", "error", ) 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_target_count = 0 self.pantone_progress_var.set("0 / 0") 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 < self._pantone_target_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 = _TEMPLATE_FILE.replace("\xa0", " ") path = filedialog.asksaveasfilename( title="另存为 Pantone 模板", defaultextension=".xlsx", initialfile=default_name, filetypes=[("Excel 文件", "*.xlsx")], ) if not path: return try: _write_template_xlsx(self, 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}") def _resolve_results_dir(self): if getattr(self, "config_file", None): root_dir = os.path.dirname(os.path.dirname(self.config_file)) else: root_dir = os.path.dirname( os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) ) results_dir = os.path.join(root_dir, "results") os.makedirs(results_dir, exist_ok=True) return results_dir def _auto_save_template(self): results_dir = _resolve_results_dir(self) target_count = len(self.pantone_results) filename = ( f"pantone_{target_count}_baseline_" f"{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx" ) path = os.path.join(results_dir, filename) _write_template_xlsx(self, path) return path def _write_template_xlsx(self, path): # 优先复制 settings 模板,再覆盖数据区;没有模板时自动创建同结构表。 template_path = os.path.join("settings", _TEMPLATE_FILE) 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)