diff --git a/app/views/panels/cct_panel.py b/app/views/panels/cct_panel.py index b23ddf4..c0df096 100644 --- a/app/views/panels/cct_panel.py +++ b/app/views/panels/cct_panel.py @@ -730,26 +730,20 @@ def toggle_cct_params_frame(self): # 屏模组:只有色度参数 if "cct" in selected_items: self.cct_params_frame.pack(fill=tk.X, padx=5, pady=5) - if hasattr(self, "log_gui"): - self.log_gui.log("✓ 显示屏模组色度参数设置") elif current_test_type == "sdr_movie": # SDR:只有色度参数(色准不需要参数设置框) if "cct" in selected_items: self.sdr_cct_params_frame.pack(fill=tk.X, padx=5, pady=5) - if hasattr(self, "log_gui"): - self.log_gui.log("✓ 显示 SDR 色度参数设置") elif current_test_type == "hdr_movie": # HDR:只有色度参数(色准不需要参数设置框) if "cct" in selected_items: if hasattr(self, "hdr_cct_params_frame"): self.hdr_cct_params_frame.pack(fill=tk.X, padx=5, pady=5) - if hasattr(self, "log_gui"): - self.log_gui.log("✓ 显示 HDR 色度参数设置") else: if hasattr(self, "log_gui"): - self.log_gui.log("⚠️ HDR 色度参数框尚未创建") + self.log_gui.log("[ERROR] HDR 色度参数框尚未创建") # ---- gamut 参考标准改变回调(统一实现) ---- diff --git a/app/views/panels/main_layout.py b/app/views/panels/main_layout.py index 5dbffad..0ec0fab 100644 --- a/app/views/panels/main_layout.py +++ b/app/views/panels/main_layout.py @@ -497,8 +497,6 @@ def create_operation_frame(self): self.update_custom_button_visibility() - - def on_screen_module_timing_changed(self, event=None): """屏模组信号格式改变时的回调""" try: @@ -535,3 +533,61 @@ def on_screen_module_timing_changed(self, event=None): except Exception as e: self.log_gui.log(f"❌ 屏模组信号格式更改失败: {str(e)}") + + +def update_test_items(self): + """根据当前测试类型更新测试项目复选框""" + # 先隐藏所有测试项目框架 + for config in self.test_items.values(): + config["frame"].pack_forget() + + current_test_type = self.config.current_test_type + self.test_vars = {} + + if current_test_type in self.test_items: + config = self.test_items[current_test_type] + frame = config["frame"] + frame.pack(fill=tk.X, padx=5, pady=5) + + # 添加测试类型标签 + type_label = ttk.Label( + frame, + text=self.get_test_type_name(current_test_type), + style="primary.TLabel", + ) + type_label.grid(row=0, column=0, columnspan=2, sticky=tk.W, padx=5, pady=3) + + # 从配置中读取保存的选择状态 + saved_test_items = self.config.current_test_types[current_test_type].get( + "test_items", [] + ) + + # 添加复选框 + for i, (text, var_name) in enumerate(config["items"]): + is_checked = var_name in saved_test_items + var = tk.BooleanVar(value=is_checked) + + self.test_vars[f"{current_test_type}_{var_name}"] = var + ttk.Checkbutton( + frame, + text=text, + variable=var, + bootstyle="round-toggle", + command=self.update_config_and_tabs, + ).grid(row=i // 2 + 1, column=i % 2, sticky=tk.W, padx=10, pady=5) + + if hasattr(self, "chart_notebook"): + self.update_chart_tabs_state() + if hasattr(self, "cct_params_frame"): + self.toggle_cct_params_frame() + + +def on_test_type_change(self): + """根据测试类型更新内容区域""" + # 更新配置信息显示 + if hasattr(self, "config") and hasattr(self.config, "get_current_config"): + self.update_config_info_display() + + # SDR 选中时显示客户模版按钮 + self.update_custom_button_visibility() + diff --git a/pqAutomationApp.py b/pqAutomationApp.py index b4156f2..16b4a29 100644 --- a/pqAutomationApp.py +++ b/pqAutomationApp.py @@ -6,9 +6,7 @@ import threading import time import os import datetime -import re import traceback -import numpy as np import matplotlib.pyplot as plt from app_version import APP_NAME, APP_VERSION, get_app_title from drivers.UCD323_Function import UCDController @@ -24,6 +22,7 @@ from app.views.panels import custom_template_panel as _ctp from app.views.panels import side_panels as _sp from app.views.panels import cct_panel as _ccp from app.views.panels import main_layout as _main +from app.views import panel_manager as PM # Step 0/1 重构:资源工具和纯算法已迁移到 app/ 包,这里重新导入以保持 # 对原函数名/方法名的向后兼容(老代码内部仍用 self.calculate_* 调用)。 @@ -107,7 +106,6 @@ from app.runner.test_runner import ( plt.rcParams["font.family"] = ["sans-serif"] plt.rcParams["font.sans-serif"] = ["Microsoft YaHei"] - class PQAutomationApp: def __init__(self, root): self.root = root @@ -133,9 +131,7 @@ class PQAutomationApp: # 创建主框架 self.main_frame = ttk.Frame(root) - self.main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) - backgroud_style_set() # 创建配置对象 @@ -242,13 +238,9 @@ class PQAutomationApp: pass def initialize_default_test_type(self): - """初始化默认测试类型(在所有控件创建完成后调用)""" try: - # 强制切换到屏模组 + # 初始设置为屏模组 self.change_test_type("screen_module") - - if hasattr(self, "log_gui"): - self.log_gui.log("✓ 默认测试类型已设置为屏模组") except Exception as e: if hasattr(self, "log_gui"): self.log_gui.log(f"初始化默认测试类型失败: {str(e)}") @@ -257,6 +249,10 @@ class PQAutomationApp: load_pq_config = _cfg_load_pq_config save_pq_config = _cfg_save_pq_config + register_panel = PM.register_panel + show_panel = PM.show_panel + hide_all_panels = PM.hide_all_panels + init_gamut_chart = _cf_init_gamut_chart init_gamma_chart = _cf_init_gamma_chart init_eotf_chart = _cf_init_eotf_chart @@ -275,6 +271,8 @@ class PQAutomationApp: create_test_type_frame = _main.create_test_type_frame update_config_info_display = _main.update_config_info_display on_screen_module_timing_changed = _main.on_screen_module_timing_changed + update_test_items = _main.update_test_items + on_test_type_change = _main.on_test_type_change create_cct_params_frame = _ccp.create_cct_params_frame _get_cct_var_dict = _ccp._get_cct_var_dict @@ -340,598 +338,402 @@ class PQAutomationApp: clear_ld_records = _ld_clear_ld_records save_local_dimming_results = _ld_save_local_dimming_results + def _save_current_cct_params(self, swallow_errors=True): + """按当前测试类型分发保存对应的 CCT 参数。""" + try: + current_type = self.config.current_test_type + if current_type == "screen_module": + self.save_cct_params() + elif current_type == "sdr_movie": + self.save_sdr_cct_params() + elif current_type == "hdr_movie" and hasattr(self, "save_hdr_cct_params"): + self.save_hdr_cct_params() + except Exception as e: + if not swallow_errors: + raise + if hasattr(self, "log_gui"): + self.log_gui.log(f"保存参数失败: {str(e)}") - def update_test_items(self): - """根据当前测试类型更新测试项目复选框""" - # 先隐藏所有测试项目框架 - for config in self.test_items.values(): - config["frame"].pack_forget() - - current_test_type = self.config.current_test_type - self.test_vars = {} - - if current_test_type in self.test_items: - config = self.test_items[current_test_type] - frame = config["frame"] - frame.pack(fill=tk.X, padx=5, pady=5) - - # 添加测试类型标签 - type_label = ttk.Label( - frame, - text=self.get_test_type_display_name(current_test_type), - style="primary.TLabel", - ) - type_label.grid(row=0, column=0, columnspan=2, sticky=tk.W, padx=5, pady=3) - - # 从配置中读取保存的选择状态 - saved_test_items = self.config.current_test_types[current_test_type].get( - "test_items", [] - ) - - # 添加复选框 - for i, (text, var_name) in enumerate(config["items"]): - # 修改:根据配置决定是否勾选 - # 如果配置中有该测试项,则勾选;否则不勾选 - is_checked = var_name in saved_test_items - var = tk.BooleanVar(value=is_checked) - - self.test_vars[f"{current_test_type}_{var_name}"] = var - ttk.Checkbutton( - frame, - text=text, - variable=var, - bootstyle="round-toggle", - command=self.update_config_and_tabs, - ).grid(row=i // 2 + 1, column=i % 2, sticky=tk.W, padx=10, pady=5) - - # 只有在 chart_notebook 已创建后才更新状态 - if hasattr(self, "chart_notebook"): - self.update_chart_tabs_state() - - # 更新色度参数框的显示状态 - if hasattr(self, "cct_params_frame"): - self.toggle_cct_params_frame() - - def get_test_type_display_name(self, test_type): - """获取测试类型的显示名称""" - display_names = { - "screen_module": "屏模组性能测试", - "sdr_movie": "SDR Movie测试", - "hdr_movie": "HDR Movie测试", - } - return display_names.get(test_type, test_type) - - def register_panel(self, panel_name, frame, button, visible_attr): - """注册一个面板到管理系统""" - self.panels[panel_name] = { - "frame": frame, - "button": button, - "visible_attr": visible_attr, - } - - def show_panel(self, panel_name): - """显示指定面板,隐藏其他所有面板""" - if panel_name not in self.panels: + def _save_cct_params_before_test_type_switch(self): + """切换测试类型前,按当前类型保存色度参数。""" + if not hasattr(self, "cct_x_ideal_var"): return + self._save_current_cct_params() - # 如果当前面板就是要显示的面板,则隐藏它 - if self.current_panel == panel_name: - self.hide_all_panels() - return + def _set_gamut_combos_state(self, state, success_msg=None, error_prefix="色域参考标准状态切换失败"): + """统一切换三个色域参考下拉框的状态。""" + try: + for attr in ("screen_gamut_combo", "sdr_gamut_combo", "hdr_gamut_combo"): + combo = getattr(self, attr, None) + if combo is not None: + combo.configure(state=state) + if success_msg and hasattr(self, "log_gui"): + self.log_gui.log(success_msg) + except Exception as e: + if hasattr(self, "log_gui"): + self.log_gui.log(f"{error_prefix}: {str(e)}") - # 隐藏所有面板 - self.hide_all_panels() - - # 显示指定面板 - panel_info = self.panels[panel_name] - - # 隐藏主内容区域 - self.control_frame_top.pack_forget() - self.control_frame_middle.pack_forget() - self.control_frame_bottom.pack_forget() - - # 显示目标面板 - panel_info["frame"].pack(side=tk.TOP, fill=tk.BOTH, expand=True, padx=5, pady=5) - - # 更新按钮样式 - if panel_info["button"]: - panel_info["button"].configure(style="SidebarSelected.TButton") - - # 更新状态 - setattr(self, panel_info["visible_attr"], True) - self.current_panel = panel_name - - def hide_all_panels(self): - """隐藏所有面板,显示主内容区域""" - # 隐藏所有注册的面板 - for panel_name, panel_info in self.panels.items(): - panel_info["frame"].pack_forget() - if panel_info["button"]: - panel_info["button"].configure(style="Sidebar.TButton") - setattr(self, panel_info["visible_attr"], False) - - # 显示主内容区域 - self.control_frame_top.pack( - side=tk.TOP, fill=tk.BOTH, expand=True, padx=0, pady=5 - ) - self.control_frame_middle.pack( - side=tk.TOP, fill=tk.BOTH, expand=True, padx=0, pady=5 - ) - self.control_frame_bottom.pack( - side=tk.TOP, fill=tk.BOTH, expand=True, padx=0, pady=5 - ) - - self.current_panel = None - - def change_test_type(self, test_type): - """切换测试类型""" - # 切换测试类型时,自动隐藏日志面板和 Local Dimming 面板 - if self.current_panel in ("log", "local_dimming"): - self.hide_all_panels() - # 先保存当前测试类型的色度参数 - if hasattr(self, "cct_x_ideal_var"): + def _hide_recalc_buttons(self, include_gamut=False): + """隐藏重新计算按钮。include_gamut=True 时同时隐藏色域重算按钮。""" + attrs = ["recalc_cct_btn", "sdr_recalc_cct_btn", "hdr_recalc_cct_btn"] + if include_gamut: + attrs += ["recalc_gamut_btn", "sdr_recalc_gamut_btn", "hdr_recalc_gamut_btn"] + hidden = 0 + for attr in attrs: + btn = getattr(self, attr, None) + if btn is None: + continue try: - current_type = self.config.current_test_type - if current_type == "screen_module": - self.save_cct_params() - elif current_type == "sdr_movie": - self.save_sdr_cct_params() - elif current_type == "hdr_movie": - if hasattr(self, "save_hdr_cct_params"): - self.save_hdr_cct_params() - except Exception as e: - if hasattr(self, "log_gui"): - self.log_gui.log(f"保存参数失败: {str(e)}") + btn.grid_remove() + hidden += 1 + except Exception: + pass + return hidden - # 更新测试类型 + def _disable_debug_panel(self): + """禁用并隐藏单步调试面板(统一实现)。""" + if hasattr(self, "debug_panel"): + try: + self.debug_panel.disable_all_debug() + self.log_gui.log("✓ 单步调试已禁用") + except Exception as e: + self.log_gui.log(f"[Error] 禁用单步调试失败: {str(e)}") + if hasattr(self, "debug_container"): + try: + self.debug_container.pack_forget() + self.log_gui.log("✓ 单步调试面板已隐藏") + except Exception as e: + self.log_gui.log(f"[Error] 隐藏调试面板失败: {str(e)}") + + def _set_config_panel_btn_state(self, state): + """统一设置配置面板按钮状态(disabled/normal)。""" + if not hasattr(self, "config_panel_frame"): + return + try: + self.config_panel_frame.btn.configure(state=state) + except Exception: + pass + + def _apply_current_test_type(self, test_type): + """更新 UI 变量与配置中的当前测试类型。""" self.test_type_var.set(test_type) if hasattr(self, "config") and hasattr(self.config, "set_current_test_type"): success = self.config.set_current_test_type(test_type) if not success and hasattr(self, "log_gui"): self.log_gui.log(f"切换测试类型失败: {test_type}") + def _switch_signal_format_tabs(self, test_type): + """切换信号格式 Tab 到目标测试类型。""" + if not hasattr(self, "signal_tabs"): + if hasattr(self, "log_gui"): + self.log_gui.log("[Error] signal_tabs 尚未创建") + return + + try: + tab_mapping = { + "screen_module": 0, + "sdr_movie": 1, + "hdr_movie": 2, + } + target_tab = tab_mapping.get(test_type, 0) + + for i in range(3): + self.signal_tabs.tab(i, state="normal") + + self.signal_tabs.select(target_tab) + self.signal_tabs.update() + self.root.update_idletasks() + + if target_tab == 0: + self.screen_module_signal_frame.tkraise() + elif target_tab == 1: + self.sdr_signal_frame.tkraise() + elif target_tab == 2: + self.hdr_signal_frame.tkraise() + + for i in range(3): + if i != target_tab: + self.signal_tabs.tab(i, state="disabled") + + if hasattr(self, "log_gui"): + tab_names = ["屏模组测试", "SDR测试", "HDR"] + self.log_gui.log(f"✓ 已切换到 {tab_names[target_tab]} 信号格式") + except Exception as e: + if hasattr(self, "log_gui"): + self.log_gui.log(f"切换信号格式失败: {str(e)}") + + def _switch_chart_tabs_by_test_type(self, test_type): + """按测试类型切换 Gamma/EOTF 与客户模板结果 Tab。""" + if not hasattr(self, "chart_notebook"): + return + + try: + current_tabs = list(self.chart_notebook.tabs()) + gamma_tab_id = str(self.gamma_chart_frame) + eotf_tab_id = str(self.eotf_chart_frame) + + if test_type == "hdr_movie": + if gamma_tab_id in current_tabs: + gamma_index = current_tabs.index(gamma_tab_id) + self.chart_notebook.forget(gamma_index) + if hasattr(self, "log_gui"): + self.log_gui.log("✓ 已隐藏 Gamma 曲线 Tab") + + if eotf_tab_id not in current_tabs: + self.chart_notebook.insert(1, self.eotf_chart_frame, text="EOTF 曲线") + if hasattr(self, "log_gui"): + self.log_gui.log("✓ 已显示 EOTF 曲线 Tab") + else: + if eotf_tab_id in current_tabs: + eotf_index = current_tabs.index(eotf_tab_id) + self.chart_notebook.forget(eotf_index) + if hasattr(self, "log_gui"): + self.log_gui.log("✓ 已隐藏 EOTF 曲线 Tab") + + if gamma_tab_id not in current_tabs: + self.chart_notebook.insert(1, self.gamma_chart_frame, text="Gamma 曲线") + if hasattr(self, "log_gui"): + self.log_gui.log("✓ 已显示 Gamma 曲线 Tab") + + custom_tab_id = str(self.custom_template_tab_frame) + current_tabs = list(self.chart_notebook.tabs()) + + if test_type == "sdr_movie": + if custom_tab_id not in current_tabs: + self.chart_notebook.add(self.custom_template_tab_frame, text="客户模板结果显示") + if hasattr(self, "log_gui"): + self.log_gui.log("✓ 已显示客户模板结果 Tab") + else: + if custom_tab_id in current_tabs: + self.chart_notebook.forget(self.custom_template_tab_frame) + if hasattr(self, "log_gui"): + self.log_gui.log("✓ 已隐藏客户模板结果 Tab") + + self.chart_notebook.update_idletasks() + except Exception as e: + if hasattr(self, "log_gui"): + self.log_gui.log(f"切换 Gamma/EOTF Tab 失败: {str(e)}") + + def change_test_type(self, test_type): + """切换测试类型""" + # 切换测试类型时,自动隐藏日志面板和 Local Dimming 面板 + if self.current_panel in ("log", "local_dimming"): + self.hide_all_panels() + self._save_cct_params_before_test_type_switch() + self._apply_current_test_type(test_type) + # 更新测试项目和侧边栏 self.update_test_items() self.update_sidebar_selection() self.on_test_type_change() + self._switch_signal_format_tabs(test_type) + self._switch_chart_tabs_by_test_type(test_type) - # ========== ✅ 1. 切换信号格式 Tab ========== - if hasattr(self, "signal_tabs"): - try: - # 定义测试类型与信号格式 Tab 的映射 - tab_mapping = { - "screen_module": 0, # 屏模组测试 - "sdr_movie": 1, # SDR测试 - "hdr_movie": 2, # HDR - } - - target_tab = tab_mapping.get(test_type, 0) - - # 先启用所有 Tab - for i in range(3): - self.signal_tabs.tab(i, state="normal") - - # 切换到目标 Tab - self.signal_tabs.select(target_tab) - - # 强制刷新显示 - self.signal_tabs.update() - self.root.update_idletasks() - - # 强制显示对应的 Frame - if target_tab == 0: - self.screen_module_signal_frame.tkraise() - elif target_tab == 1: - self.sdr_signal_frame.tkraise() - elif target_tab == 2: - self.hdr_signal_frame.tkraise() - - # 禁用其他 Tab - for i in range(3): - if i != target_tab: - self.signal_tabs.tab(i, state="disabled") - - # 日志记录 - if hasattr(self, "log_gui"): - tab_names = ["屏模组测试", "SDR测试", "HDR"] - self.log_gui.log(f"✓ 已切换到 {tab_names[target_tab]} 信号格式") - - except Exception as e: - if hasattr(self, "log_gui"): - self.log_gui.log(f"切换信号格式失败: {str(e)}") - else: - if hasattr(self, "log_gui"): - self.log_gui.log("⚠️ signal_tabs 尚未创建") - - # ========== 2. 动态切换 Gamma/EOTF Tab ========== - if hasattr(self, "chart_notebook"): - try: - current_tabs = list(self.chart_notebook.tabs()) - - # 获取当前 Tab 的索引 - gamma_tab_id = str(self.gamma_chart_frame) - eotf_tab_id = str(self.eotf_chart_frame) - - if test_type == "hdr_movie": - # ========== HDR 测试:移除 Gamma,添加 EOTF ========== - - # 1. 如果 Gamma Tab 存在,移除它 - if gamma_tab_id in current_tabs: - gamma_index = current_tabs.index(gamma_tab_id) - self.chart_notebook.forget(gamma_index) - if hasattr(self, "log_gui"): - self.log_gui.log("✓ 已隐藏 Gamma 曲线 Tab") - - # 2. 如果 EOTF Tab 不存在,添加它(在色域图之后) - if eotf_tab_id not in current_tabs: - self.chart_notebook.insert( - 1, self.eotf_chart_frame, text="EOTF 曲线" - ) - if hasattr(self, "log_gui"): - self.log_gui.log("✓ 已显示 EOTF 曲线 Tab") - - else: - # ========== SDR/屏模组测试:移除 EOTF,添加 Gamma ========== - - # 1. 如果 EOTF Tab 存在,移除它 - if eotf_tab_id in current_tabs: - eotf_index = current_tabs.index(eotf_tab_id) - self.chart_notebook.forget(eotf_index) - if hasattr(self, "log_gui"): - self.log_gui.log("✓ 已隐藏 EOTF 曲线 Tab") - - # 2. 如果 Gamma Tab 不存在,添加它(在色域图之后) - if gamma_tab_id not in current_tabs: - self.chart_notebook.insert( - 1, self.gamma_chart_frame, text="Gamma 曲线" - ) - if hasattr(self, "log_gui"): - self.log_gui.log("✓ 已显示 Gamma 曲线 Tab") - - # ========== 3. 仅在 SDR 测试显示客户模板结果 Tab ========== - custom_tab_id = str(self.custom_template_tab_frame) - current_tabs = list(self.chart_notebook.tabs()) - - if test_type == "sdr_movie": - if custom_tab_id not in current_tabs: - self.chart_notebook.add( - self.custom_template_tab_frame, - text="客户模板结果显示", - ) - if hasattr(self, "log_gui"): - self.log_gui.log("✓ 已显示客户模板结果 Tab") - else: - if custom_tab_id in current_tabs: - self.chart_notebook.forget(self.custom_template_tab_frame) - if hasattr(self, "log_gui"): - self.log_gui.log("✓ 已隐藏客户模板结果 Tab") - - # 刷新显示 - self.chart_notebook.update_idletasks() - - except Exception as e: - if hasattr(self, "log_gui"): - self.log_gui.log(f"切换 Gamma/EOTF Tab 失败: {str(e)}") - - def on_test_type_change(self): - """根据测试类型更新内容区域""" - test_type = self.test_type_var.get() - - # 获取当前测试类型的配置 - if hasattr(self, "config") and hasattr(self.config, "get_current_config"): - current_config = self.config.get_current_config() - - # 更新配置信息显示 - self.update_config_info_display() - - # SDR 选中时显示客户模版按钮 - self.update_custom_button_visibility() - - def start_test(self): - """开始测试""" - # 检查设备连接状态 + def _check_start_preconditions(self): + """检查开始测试前置条件:设备连接 & 未在测试中。""" if self.ca is None or self.ucd is None: messagebox.showerror("错误", "请先连接CA410和信号发生器") - return - - # 检查是否已经在测试中 + return False if self.testing: messagebox.showinfo("提示", "测试已在进行中") + return False + return True + + def _collapse_config_panel_for_test(self): + """收起配置项并禁用其按钮。""" + if not hasattr(self, "config_panel_frame"): return - - # ✅ 禁用并隐藏单步调试 - if hasattr(self, "debug_panel"): - self.debug_panel.disable_all_debug() - self.log_gui.log("✓ 单步调试已禁用") - - if hasattr(self, "debug_container"): - self.debug_container.pack_forget() - self.log_gui.log("✓ 单步调试面板已隐藏") - - # 获取测试类型和测试项目 - test_type = self.test_type_var.get() - test_items = self.get_selected_test_items() - - if not test_items: - messagebox.showinfo("提示", "请至少选择一个测试项目") - return - - # 自动收起配置项 - if hasattr(self, "config_panel_frame"): - try: - if self.config_panel_frame.winfo_viewable(): - self.config_panel_frame.btn.invoke() - self.root.update_idletasks() - time.sleep(0.2) - except: - pass - - # 禁用配置项按钮 - try: - self.config_panel_frame.btn.configure(state="disabled") - except: - pass - - # ✅ 新增:禁用色域参考标准下拉框 try: - if hasattr(self, "screen_gamut_combo"): - self.screen_gamut_combo.configure(state="disabled") - if hasattr(self, "sdr_gamut_combo"): - self.sdr_gamut_combo.configure(state="disabled") - if hasattr(self, "hdr_gamut_combo"): - self.hdr_gamut_combo.configure(state="disabled") - except Exception as e: - self.log_gui.log(f"禁用色域参考标准失败: {str(e)}") + if self.config_panel_frame.winfo_viewable(): + self.config_panel_frame.btn.invoke() + self.root.update_idletasks() + time.sleep(0.2) + except Exception: + pass + self._set_config_panel_btn_state("disabled") - # 隐藏所有重新计算按钮 - if hasattr(self, "recalc_cct_btn"): - try: - self.recalc_cct_btn.grid_remove() - except: - pass - - if hasattr(self, "sdr_recalc_cct_btn"): - try: - self.sdr_recalc_cct_btn.grid_remove() - except: - pass - - if hasattr(self, "hdr_recalc_cct_btn"): - try: - self.hdr_recalc_cct_btn.grid_remove() - except: - pass - - # 更新UI状态 + def _set_ui_testing_state(self): + """切换主 UI 到测试中状态(按钮禁用、状态栏、清空日志/图表)。""" self.testing = True self.start_btn.config(state=tk.DISABLED) self.stop_btn.config(state=tk.NORMAL) self.save_btn.config(state=tk.DISABLED) self.clear_config_btn.config(state=tk.DISABLED) self.status_var.set("测试进行中...") - - # 清空日志和图表 self.log_gui.clear_log() self.clear_chart() - # 根据测试类型显示不同提示 + def _prepare_ui_for_test_start(self): + """为开始测试准备 UI(禁用调试、收起配置、禁用色域、隐藏重算按钮、切换状态)。""" + self._disable_debug_panel() + self._collapse_config_panel_for_test() + self._set_gamut_combos_state("disabled", error_prefix="禁用色域参考标准失败") + self._hide_recalc_buttons(include_gamut=False) + self._set_ui_testing_state() + + def _rollback_test_start(self): + """用户取消测试时恢复 UI 状态。""" + self.testing = False + self.start_btn.config(state=tk.NORMAL) + self.stop_btn.config(state=tk.DISABLED) + self.clear_config_btn.config(state=tk.NORMAL) + self.status_var.set("测试已取消") + self._set_config_panel_btn_state("normal") + + def _build_test_start_message(self, test_type): + """根据测试类型生成确认弹框提示文案。""" if test_type == "screen_module": - # 屏模组测试:提示 byPass All PQ - message = f"开始屏模组性能测试,请 byPass All PQ" + return "开始屏模组性能测试,请 byPass All PQ" + if test_type == "sdr_movie": + return "开始 SDR Movie 测试,请设置正确的图像模式" + if test_type == "hdr_movie": + return "开始 HDR Movie 测试,请设置正确的图像模式" + return f"开始{self.get_test_type_name(test_type)}测试" - elif test_type == "sdr_movie": - # SDR测试:提示设置正确图像模式 - message = f"开始 SDR Movie 测试,请设置正确的图像模式" - - elif test_type == "hdr_movie": - # HDR测试:提示设置正确图像模式 - message = f"开始 HDR Movie 测试,请设置正确的图像模式" - - else: - message = f"开始{self.get_test_type_name(test_type)}测试" - - confirm = messagebox.askyesno("确认测试", message) - - if not confirm: - self.testing = False - self.start_btn.config(state=tk.NORMAL) - self.stop_btn.config(state=tk.DISABLED) - self.clear_config_btn.config(state=tk.NORMAL) - self.status_var.set("测试已取消") - - # 恢复配置项按钮 - if hasattr(self, "config_panel_frame"): - try: - self.config_panel_frame.btn.configure(state="normal") - except: - pass - return - - # 在新线程中执行测试 + def _launch_test_thread(self, test_type, test_items): + """在新线程中执行测试。""" self.test_thread = threading.Thread( target=self.run_test, args=(test_type, test_items) ) self.test_thread.daemon = True self.test_thread.start() + def start_test(self): + """开始测试""" + if not self._check_start_preconditions(): + return + + test_type = self.test_type_var.get() + test_items = self.get_selected_test_items() + if not test_items: + messagebox.showinfo("提示", "请至少选择一个测试项目") + return + + self._prepare_ui_for_test_start() + + message = self._build_test_start_message(test_type) + if not messagebox.askyesno("确认测试", message): + self._rollback_test_start() + return + + self._launch_test_thread(test_type, test_items) + + def _confirm_stop_test(self): + """弹出确认停止测试对话框。""" + return messagebox.askyesno( + "确认停止测试", + "测试正在进行中,确定要停止吗?\n\n 停止后将放弃本次测试的所有数据,无法保存。", + icon="warning", + ) + + def _signal_stop_and_update_ui(self): + """设置停止标志并立即更新 UI 以反馈给用户。""" + self.testing = False + self.log_gui.log("=" * 50) + self.log_gui.log("正在停止测试...") + self.log_gui.log("=" * 50) + self.stop_btn.config(state=tk.DISABLED) + self.status_var.set("正在停止测试,请稍候...") + self.root.update() + + def _wait_for_test_thread(self, timeout_seconds=5): + """等待测试线程结束,最多 timeout_seconds 秒,同时保持 UI 响应。""" + if not (self.test_thread and self.test_thread.is_alive()): + return + self.log_gui.log("等待测试线程结束...") + for _ in range(int(timeout_seconds * 10)): + if not self.test_thread.is_alive(): + break + time.sleep(0.1) + self.root.update() + if self.test_thread.is_alive(): + self.log_gui.log("[Error] 测试线程未能正常结束,将在后台继续等待") + else: + self.log_gui.log("✓ 测试线程已结束") + + def _clear_test_data(self): + """清空测试结果对象与中间数据缓存。""" + try: + self.log_gui.log("清理测试数据...") + if hasattr(self, "results"): + self.results = None + self.log_gui.log(" ✓ 测试结果对象已清空") + for attr in [ + "gamut_results", + "gamma_results", + "cct_results", + "contrast_results", + "accuracy_results", + ]: + if hasattr(self, attr): + setattr(self, attr, None) + self.log_gui.log(" ✓ 所有中间数据已清空") + except Exception as e: + self.log_gui.log(f"[Error] 清理数据时出错: {str(e)}") + + def _clear_charts_and_tables(self): + """清空图表与客户模板结果表格,并跳转到色域图 Tab。""" + try: + self.clear_chart() + self.log_gui.log("✓ 图表已清空") + except Exception as e: + self.log_gui.log(f"[Error] 清空图表时出错: {str(e)}") + try: + self.clear_custom_template_results() + self.log_gui.log("✓ 客户模板结果表格已清空") + except Exception as e: + self.log_gui.log(f"[Error] 清空客户模板结果表格失败: {str(e)}") + try: + if hasattr(self, "chart_notebook"): + self.chart_notebook.select(self.gamut_chart_frame) + self.root.update_idletasks() + except Exception as e: + self.log_gui.log(f"[Error] 跳转到色域图失败: {str(e)}") + + def _restore_ui_after_stop(self): + """恢复主按钮与状态栏到非测试态。""" + self.set_custom_result_table_locked(False) + self.start_btn.config(state=tk.NORMAL) + self.stop_btn.config(state=tk.DISABLED) + self.save_btn.config(state=tk.DISABLED) + self.clear_config_btn.config(state=tk.NORMAL) + if hasattr(self, "custom_btn"): + self.custom_btn.config(state=tk.NORMAL) + self.status_var.set("测试已停止 - 数据已清空") + if hasattr(self, "config_panel_frame"): + self._set_config_panel_btn_state("normal") + + def _finalize_stop_test(self): + """延迟执行的清理流程总入口(由 root.after 调用)。""" + self._clear_test_data() + self._clear_charts_and_tables() + self._restore_ui_after_stop() + self._set_gamut_combos_state( + "disabled", + success_msg="✓ 色域参考标准已禁用", + error_prefix="禁用色域参考标准失败", + ) + hidden = self._hide_recalc_buttons(include_gamut=True) + if hidden > 0: + self.log_gui.log(f"✓ 已隐藏 {hidden} 个重新计算按钮") + self._disable_debug_panel() + self.log_gui.log("=" * 50) + self.log_gui.log("✓ 测试已停止,所有数据已清空") + self.log_gui.log("=" * 50) + messagebox.showinfo( + "测试已停止", + "测试已停止,本次测试数据已清空。\n\n可以重新开始新的测试。", + ) + def stop_test(self): """停止测试 - 放弃本次所有数据(完全集成版)""" if not self.testing: return - - # ========== 1. 添加确认对话框 ========== - confirm = messagebox.askyesno( - "确认停止测试", - "测试正在进行中,确定要停止吗?\n\n⚠️ 停止后将放弃本次测试的所有数据,无法保存。", - icon="warning", - ) - - if not confirm: - self.log_gui.log("用户取消停止操作") + if not self._confirm_stop_test(): return - - # ========== 2. 立即设置停止标志 ========== - self.testing = False # ← 关键:先设置标志,让测试线程停止 - - self.log_gui.log("=" * 50) - self.log_gui.log("⚠️ 正在停止测试...") - self.log_gui.log("=" * 50) - - # ========== 3. 立即更新UI状态(让用户感知到停止)========== - self.stop_btn.config(state=tk.DISABLED) - self.status_var.set("正在停止测试,请稍候...") - self.root.update() # 立即刷新界面 - - # ========== 4. 等待测试线程结束 ========== - if self.test_thread and self.test_thread.is_alive(): - self.log_gui.log("等待测试线程结束...") - - # 等待最多5秒 - for i in range(50): # 50 * 0.1秒 = 5秒 - if not self.test_thread.is_alive(): - break - time.sleep(0.1) - self.root.update() # 保持界面响应 - - if self.test_thread.is_alive(): - self.log_gui.log("⚠️ 测试线程未能正常结束,将在后台继续等待") - else: - self.log_gui.log("✓ 测试线程已结束") - - # ========== 5. 延迟1秒后执行清理(使用内部函数)========== - def cleanup_and_finish(): - """清理数据并完成停止操作""" - # ========== 5.1 清理测试数据 ========== - try: - self.log_gui.log("清理测试数据...") - - # 清空测试结果对象 - if hasattr(self, "results"): - self.results = None - self.log_gui.log(" ✓ 测试结果对象已清空") - - # 清空中间数据缓存 - for attr in [ - "gamut_results", - "gamma_results", - "cct_results", - "contrast_results", - "accuracy_results", - ]: - if hasattr(self, attr): - setattr(self, attr, None) - - self.log_gui.log(" ✓ 所有中间数据已清空") - - except Exception as e: - self.log_gui.log(f"⚠️ 清理数据时出错: {str(e)}") - - # ========== 5.2 清空图表显示 ========== - try: - self.clear_chart() - self.log_gui.log("✓ 图表已清空") - except Exception as e: - self.log_gui.log(f"⚠️ 清空图表时出错: {str(e)}") - - try: - self.clear_custom_template_results() - self.log_gui.log("✓ 客户模板结果表格已清空") - except Exception as e: - self.log_gui.log(f"⚠️ 清空客户模板结果表格失败: {str(e)}") - - # ========== 5.2.5 跳转到色域图Tab(第一个Tab)========== - try: - if hasattr(self, "chart_notebook"): - self.chart_notebook.select(self.gamut_chart_frame) - self.root.update_idletasks() # ← 刷新界面 - self.log_gui.log("✓ 已跳转到色域图界面") - except Exception as e: - self.log_gui.log(f"⚠️ 跳转到色域图失败: {str(e)}") - - # ========== 5.3 更新UI状态 ========== - self.set_custom_result_table_locked(False) - self.start_btn.config(state=tk.NORMAL) - self.stop_btn.config(state=tk.DISABLED) - self.save_btn.config(state=tk.DISABLED) - self.clear_config_btn.config(state=tk.NORMAL) - if hasattr(self, "custom_btn"): - self.custom_btn.config(state=tk.NORMAL) - self.status_var.set("测试已停止 - 数据已清空") - self.log_gui.log("✓ UI状态已更新") - - # ========== 5.4 恢复配置项按钮 ========== - if hasattr(self, "config_panel_frame"): - try: - self.config_panel_frame.btn.configure(state="normal") - self.log_gui.log("✓ 配置项已恢复") - except: - pass - - # ========== 5.4.5 禁用色域参考标准下拉框 ========== - try: - if hasattr(self, "screen_gamut_combo"): - self.screen_gamut_combo.configure(state="disabled") - if hasattr(self, "sdr_gamut_combo"): - self.sdr_gamut_combo.configure(state="disabled") - if hasattr(self, "hdr_gamut_combo"): - self.hdr_gamut_combo.configure(state="disabled") - self.log_gui.log("✓ 色域参考标准已禁用") - except Exception as e: - self.log_gui.log(f"禁用色域参考标准失败: {str(e)}") - - # ========== 5.5 隐藏所有重新计算按钮 ========== - try: - button_hidden_count = 0 - for btn_attr in [ - "recalc_cct_btn", - "sdr_recalc_cct_btn", - "hdr_recalc_cct_btn", - "recalc_gamut_btn", # ✅ 新增 - "sdr_recalc_gamut_btn", # ✅ 新增 - "hdr_recalc_gamut_btn", # ✅ 新增 - ]: - if hasattr(self, btn_attr): - try: - getattr(self, btn_attr).grid_remove() - button_hidden_count += 1 - except: - pass - - if button_hidden_count > 0: - self.log_gui.log(f"✓ 已隐藏 {button_hidden_count} 个重新计算按钮") - - except Exception as e: - self.log_gui.log(f"⚠️ 隐藏按钮时出错: {str(e)}") - - # ========== 5.6 禁用并隐藏单步调试 ========== - if hasattr(self, "debug_panel"): - try: - self.debug_panel.disable_all_debug() - self.log_gui.log("✓ 单步调试已禁用") - except Exception as e: - self.log_gui.log(f"⚠️ 禁用单步调试失败: {str(e)}") - - # ✅ 隐藏调试面板 - if hasattr(self, "debug_container"): - try: - self.debug_container.pack_forget() - self.log_gui.log("✓ 单步调试面板已隐藏") - except Exception as e: - self.log_gui.log(f"⚠️ 隐藏调试面板失败: {str(e)}") - - # ========== 5.7 最终日志 ========== - self.log_gui.log("=" * 50) - self.log_gui.log("✓ 测试已停止,所有数据已清空") - self.log_gui.log("=" * 50) - - # ========== 5.8 显示提示信息 ========== - messagebox.showinfo( - "测试已停止", - "测试已停止,本次测试数据已清空。\n\n可以重新开始新的测试。", - ) - - # ========== 延迟1秒后执行清理 ========== - self.root.after(1000, cleanup_and_finish) + self._signal_stop_and_update_ui() + self._wait_for_test_thread(timeout_seconds=5) + self.root.after(1000, self._finalize_stop_test) # ==================== 保存测试结果 ==================== def save_results(self): @@ -970,12 +772,12 @@ class PQAutomationApp: # 3) 成功提示 log("=" * 50) - log(f"✅ 测试结果已保存到目录: {result_dir}") + log(f"测试结果已保存到目录: {result_dir}") log("=" * 50) messagebox.showinfo("成功", f"测试结果已保存到目录:\n{result_dir}") except Exception as e: - self.log_gui.log(f"❌ 保存测试结果失败: {str(e)}") + self.log_gui.log(f"[Error] 保存测试结果失败: {str(e)}") import traceback self.log_gui.log(traceback.format_exc()) @@ -1052,7 +854,7 @@ class PQAutomationApp: self.save_pq_config() except Exception as e: - self.log_gui.log(f"更新配置失败: {str(e)}") + self.log_gui.log(f"[Error] 更新配置失败: {str(e)}") def update_config_and_tabs(self): """更新配置并同步Tab状态""" @@ -1060,21 +862,8 @@ class PQAutomationApp: self.update_chart_tabs_state() # 根据当前测试类型保存对应参数 - current_test_type = self.config.current_test_type - selected_items = self.get_selected_test_items() - - if current_test_type == "screen_module": - if "cct" in selected_items: - self.save_cct_params() - - elif current_test_type == "sdr_movie": - if "cct" in selected_items: - self.save_sdr_cct_params() - - elif current_test_type == "hdr_movie": - if "cct" in selected_items: - if hasattr(self, "save_hdr_cct_params"): - self.save_hdr_cct_params() + if "cct" in self.get_selected_test_items(): + self._save_current_cct_params() # 控制参数框的显示 self.toggle_cct_params_frame() diff --git a/settings/pq_config.json b/settings/pq_config.json index d7b733a..02b8611 100644 --- a/settings/pq_config.json +++ b/settings/pq_config.json @@ -1,5 +1,5 @@ { - "current_test_type": "screen_module", + "current_test_type": "sdr_movie", "test_types": { "screen_module": { "name": "屏模组性能测试",