Files
pqAutomationApp/app/views/panels/pantone_baseline_panel.py
2026-05-22 11:31:36 +08:00

563 lines
19 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Pantone 认证摸底测试面板。"""
from __future__ import annotations
import datetime
import os
import sys
import threading
import tkinter as tk
from tkinter import filedialog, messagebox
import ttkbootstrap as ttk
_TEMPLATE_FILE = "pantone_2670_colors.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.3")
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 _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")
def _load_patterns(self):
path = os.path.join(_get_settings_dir(self), _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(_get_settings_dir(self), _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)