Files
pqAutomationApp/app/views/panels/pantone_baseline_panel.py

548 lines
19 KiB
Python
Raw Normal View History

"""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)