2026-05-07 11:21:17 +08:00
|
|
|
|
"""Pantone 认证摸底测试面板。"""
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
import datetime
|
|
|
|
|
|
import os
|
2026-05-22 11:31:36 +08:00
|
|
|
|
import sys
|
2026-05-07 11:21:17 +08:00
|
|
|
|
import threading
|
|
|
|
|
|
import tkinter as tk
|
|
|
|
|
|
from tkinter import filedialog, messagebox
|
|
|
|
|
|
|
|
|
|
|
|
import ttkbootstrap as ttk
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-22 11:31:36 +08:00
|
|
|
|
_TEMPLATE_FILE = "pantone_2670_colors.xlsx"
|
2026-05-07 11:21:17 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
2026-05-13 17:17:13 +08:00
|
|
|
|
self._pantone_target_count = 0
|
2026-05-07 11:21:17 +08:00
|
|
|
|
|
|
|
|
|
|
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="未开始")
|
2026-05-13 17:17:13 +08:00
|
|
|
|
self.pantone_progress_var = tk.StringVar(value="0 / 0")
|
2026-05-19 10:06:02 +08:00
|
|
|
|
self.pantone_settle_var = tk.StringVar(value="0.3")
|
2026-05-07 11:21:17 +08:00
|
|
|
|
|
|
|
|
|
|
config_row = ttk.LabelFrame(root, text="任务配置", padding=8)
|
|
|
|
|
|
config_row.pack(fill=tk.X)
|
2026-05-13 17:17:13 +08:00
|
|
|
|
ttk.Label(config_row, text=f"Pattern来源: settings/{_TEMPLATE_FILE}").pack(
|
2026-05-07 11:21:17 +08:00
|
|
|
|
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")
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-22 11:31:36 +08:00
|
|
|
|
def _get_settings_dir(self):
|
|
|
|
|
|
"""返回 settings 绝对目录,避免依赖当前工作目录。"""
|
|
|
|
|
|
if getattr(self, "config_file", None):
|
|
|
|
|
|
return os.path.dirname(self.config_file)
|
|
|
|
|
|
|
|
|
|
|
|
if getattr(sys, "frozen", False):
|
|
|
|
|
|
base_dir = os.path.dirname(sys.executable)
|
|
|
|
|
|
else:
|
|
|
|
|
|
base_dir = os.path.dirname(
|
|
|
|
|
|
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
|
|
)
|
|
|
|
|
|
return os.path.join(base_dir, "settings")
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-07 11:21:17 +08:00
|
|
|
|
def _load_patterns(self):
|
2026-05-22 11:31:36 +08:00
|
|
|
|
path = os.path.join(_get_settings_dir(self), _TEMPLATE_FILE)
|
2026-05-07 11:21:17 +08:00
|
|
|
|
if not os.path.isfile(path):
|
2026-05-13 17:17:13 +08:00
|
|
|
|
raise FileNotFoundError(f"未找到模板文件: {path}")
|
|
|
|
|
|
|
|
|
|
|
|
from openpyxl import load_workbook
|
2026-05-07 11:21:17 +08:00
|
|
|
|
|
|
|
|
|
|
patterns = []
|
2026-05-13 17:17:13 +08:00
|
|
|
|
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
|
2026-05-07 11:21:17 +08:00
|
|
|
|
try:
|
2026-05-13 17:17:13 +08:00
|
|
|
|
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
|
2026-05-07 11:21:17 +08:00
|
|
|
|
except Exception:
|
|
|
|
|
|
continue
|
2026-05-13 17:17:13 +08:00
|
|
|
|
if r is None or g is None or b is None:
|
|
|
|
|
|
continue
|
2026-05-07 11:21:17 +08:00
|
|
|
|
if min(r, g, b) < 0 or max(r, g, b) > 255:
|
|
|
|
|
|
continue
|
|
|
|
|
|
patterns.append((r, g, b))
|
2026-05-13 17:17:13 +08:00
|
|
|
|
finally:
|
|
|
|
|
|
wb.close()
|
2026-05-07 11:21:17 +08:00
|
|
|
|
|
|
|
|
|
|
if not patterns:
|
2026-05-13 17:17:13 +08:00
|
|
|
|
raise RuntimeError("模板中未找到有效 RGB 列表(需包含 R/G/B 三列)")
|
2026-05-07 11:21:17 +08:00
|
|
|
|
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)
|
2026-05-13 17:17:13 +08:00
|
|
|
|
self._pantone_target_count = len(self.pantone_patterns)
|
2026-05-07 11:21:17 +08:00
|
|
|
|
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("执行中")
|
2026-05-13 17:17:13 +08:00
|
|
|
|
self.pantone_progress_var.set(f"0 / {self._pantone_target_count}")
|
2026-05-07 11:21:17 +08:00
|
|
|
|
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)
|
2026-05-13 17:17:13 +08:00
|
|
|
|
self._pantone_target_count = len(self.pantone_patterns)
|
2026-05-07 11:21:17 +08:00
|
|
|
|
except Exception as exc:
|
|
|
|
|
|
messagebox.showerror("读取失败", str(exc))
|
|
|
|
|
|
return
|
|
|
|
|
|
|
2026-05-13 17:17:13 +08:00
|
|
|
|
if self._pantone_next_index >= self._pantone_target_count:
|
|
|
|
|
|
messagebox.showinfo("提示", "任务已完成,无需继续")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
2026-05-07 11:21:17 +08:00
|
|
|
|
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):
|
2026-05-13 17:17:13 +08:00
|
|
|
|
total = self._pantone_target_count or len(self.pantone_patterns)
|
2026-05-07 11:21:17 +08:00
|
|
|
|
|
|
|
|
|
|
def worker():
|
|
|
|
|
|
end_state = "completed"
|
|
|
|
|
|
try:
|
|
|
|
|
|
src = self.pantone_patterns
|
|
|
|
|
|
src_count = len(src)
|
2026-05-13 17:17:13 +08:00
|
|
|
|
rgb_session = self.pattern_service.prepare_session("rgb", log_details=False)
|
2026-05-07 11:21:17 +08:00
|
|
|
|
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]
|
2026-05-13 17:17:13 +08:00
|
|
|
|
try:
|
|
|
|
|
|
self.pattern_service.send_rgb((r, g, b), session=rgb_session)
|
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
|
raise RuntimeError(f"第 {i + 1} 组发送失败: {exc}") from exc
|
2026-05-07 11:21:17 +08:00
|
|
|
|
|
|
|
|
|
|
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",
|
|
|
|
|
|
)
|
2026-05-13 17:17:13 +08:00
|
|
|
|
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",
|
|
|
|
|
|
)
|
2026-05-07 11:21:17 +08:00
|
|
|
|
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)
|
2026-05-13 17:17:13 +08:00
|
|
|
|
self._pantone_target_count = 0
|
|
|
|
|
|
self.pantone_progress_var.set("0 / 0")
|
2026-05-07 11:21:17 +08:00
|
|
|
|
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)
|
|
|
|
|
|
|
2026-05-13 17:17:13 +08:00
|
|
|
|
can_resume = self._pantone_paused and self._pantone_next_index < self._pantone_target_count
|
2026-05-07 11:21:17 +08:00
|
|
|
|
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
|
|
|
|
|
|
|
2026-05-13 17:17:13 +08:00
|
|
|
|
default_name = _TEMPLATE_FILE.replace("\xa0", " ")
|
2026-05-07 11:21:17 +08:00
|
|
|
|
path = filedialog.asksaveasfilename(
|
|
|
|
|
|
title="另存为 Pantone 模板",
|
|
|
|
|
|
defaultextension=".xlsx",
|
|
|
|
|
|
initialfile=default_name,
|
|
|
|
|
|
filetypes=[("Excel 文件", "*.xlsx")],
|
|
|
|
|
|
)
|
|
|
|
|
|
if not path:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
2026-05-13 17:17:13 +08:00
|
|
|
|
_write_template_xlsx(self, path)
|
2026-05-07 11:21:17 +08:00
|
|
|
|
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}")
|
2026-05-13 17:17:13 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 模板,再覆盖数据区;没有模板时自动创建同结构表。
|
2026-05-22 11:31:36 +08:00
|
|
|
|
template_path = os.path.join(_get_settings_dir(self), _TEMPLATE_FILE)
|
2026-05-13 17:17:13 +08:00
|
|
|
|
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)
|