修改Calman灰阶中结果图显示、修改UI主题样式应用
This commit is contained in:
@@ -272,6 +272,49 @@ def disconnect_com_connections(self: "PQAutomationApp"):
|
||||
self.connection.disconnect_all()
|
||||
|
||||
|
||||
def _get_ca_measure_lock(self: "PQAutomationApp"):
|
||||
lock = getattr(self, "_ca_measure_lock", None)
|
||||
if lock is None:
|
||||
lock = threading.RLock()
|
||||
self._ca_measure_lock = lock
|
||||
return lock
|
||||
|
||||
|
||||
def _read_ca_display(self: "PQAutomationApp", mode: int):
|
||||
"""在锁内切换 CA410 Display 模式并立即读取,避免模式串扰。"""
|
||||
if getattr(self, "ca", None) is None:
|
||||
raise RuntimeError("请先连接 CA410 色度计")
|
||||
|
||||
with _get_ca_measure_lock(self):
|
||||
self.ca.set_Display(mode)
|
||||
return self.ca.readAllDisplay()
|
||||
|
||||
|
||||
def read_ca_xyLv(self: "PQAutomationApp"):
|
||||
"""读取 xy/Lv/XYZ(Display 0)。"""
|
||||
return _read_ca_display(self, 0)
|
||||
|
||||
|
||||
def read_ca_tcp_duv(self: "PQAutomationApp"):
|
||||
"""读取 Tcp/duv/Lv/XYZ(Display 1)。"""
|
||||
return _read_ca_display(self, 1)
|
||||
|
||||
|
||||
def read_ca_uvLv(self: "PQAutomationApp"):
|
||||
"""读取 u'/v'/Lv/XYZ(Display 5)。"""
|
||||
return _read_ca_display(self, 5)
|
||||
|
||||
|
||||
def read_ca_xyz(self: "PQAutomationApp"):
|
||||
"""读取 XYZ(Display 7)。"""
|
||||
return _read_ca_display(self, 7)
|
||||
|
||||
|
||||
def read_ca_lambda_pe(self: "PQAutomationApp"):
|
||||
"""读取 λd/Pe/Lv/XYZ(Display 8)。"""
|
||||
return _read_ca_display(self, 8)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"ConnectionController",
|
||||
# 兼容层
|
||||
@@ -298,3 +341,10 @@ class DeviceConnectionMixin:
|
||||
check_port_connection = check_port_connection
|
||||
enable_com_widgets = enable_com_widgets
|
||||
disconnect_com_connections = disconnect_com_connections
|
||||
_get_ca_measure_lock = _get_ca_measure_lock
|
||||
_read_ca_display = _read_ca_display
|
||||
read_ca_xyLv = read_ca_xyLv
|
||||
read_ca_tcp_duv = read_ca_tcp_duv
|
||||
read_ca_uvLv = read_ca_uvLv
|
||||
read_ca_xyz = read_ca_xyz
|
||||
read_ca_lambda_pe = read_ca_lambda_pe
|
||||
|
||||
@@ -394,8 +394,7 @@ def send_fix_pattern(self: "PQAutomationApp", mode):
|
||||
# 测量数据
|
||||
if mode == "custom":
|
||||
result = []
|
||||
self.ca.set_Display(1)
|
||||
tcp, duv, lv, X, Y, Z = self.ca.readAllDisplay()
|
||||
tcp, duv, lv, X, Y, Z = self.read_ca_tcp_duv()
|
||||
|
||||
if should_log_detail:
|
||||
self.log_gui.log(
|
||||
@@ -403,8 +402,7 @@ def send_fix_pattern(self: "PQAutomationApp", mode):
|
||||
f"X={X:.4f}, Y={Y:.4f}, Z={Z:.4f}"
|
||||
, level="success")
|
||||
|
||||
self.ca.set_Display(8)
|
||||
lambda_, Pe, lv, X, Y, Z = self.ca.readAllDisplay()
|
||||
lambda_, Pe, lv, X, Y, Z = self.read_ca_lambda_pe()
|
||||
|
||||
if should_log_detail:
|
||||
self.log_gui.log(
|
||||
@@ -449,9 +447,7 @@ def send_fix_pattern(self: "PQAutomationApp", mode):
|
||||
self.log_gui.log(f"第 {i+1} 行实时结果写入失败: {str(e)}", level="error")
|
||||
|
||||
else:
|
||||
self.ca.set_xyLv_Display()
|
||||
|
||||
x, y, lv, X, Y, Z = self.ca.readAllDisplay()
|
||||
x, y, lv, X, Y, Z = self.read_ca_xyLv()
|
||||
results.append([x, y, lv, X, Y, Z])
|
||||
|
||||
if should_log_detail:
|
||||
|
||||
@@ -171,7 +171,7 @@ def _build_ld_result_row(test_item, pattern_label, value, x="--", y="--"):
|
||||
|
||||
def _measure_ld_row(self: "PQAutomationApp", test_item, pattern_label):
|
||||
"""读取一次 CA410 数据并包装为表格行。"""
|
||||
x, y, lv, _X, _Y, _Z = self.ca.readAllDisplay()
|
||||
x, y, lv, _X, _Y, _Z = self.read_ca_xyLv()
|
||||
if lv is None:
|
||||
raise RuntimeError(f"{pattern_label} 采集失败")
|
||||
return _build_ld_result_row(test_item, pattern_label, lv, x, y), lv
|
||||
@@ -549,7 +549,7 @@ def measure_ld_luminance(self: "PQAutomationApp"):
|
||||
|
||||
def measure():
|
||||
try:
|
||||
x, y, lv, _X, _Y, _Z = self.ca.readAllDisplay()
|
||||
x, y, lv, _X, _Y, _Z = self.read_ca_xyLv()
|
||||
except Exception as e:
|
||||
self._dispatch_ui(self.log_gui.log, f"采集异常: {str(e)}")
|
||||
return
|
||||
|
||||
@@ -36,37 +36,188 @@ def _is_dark(color: str) -> bool:
|
||||
return (r * 299 + g * 587 + b * 114) / 1000 < 128
|
||||
|
||||
|
||||
def apply_modern_styles() -> None:
|
||||
"""注册或刷新现代化样式集。可在主题切换后再次调用。"""
|
||||
style = ttk.Style()
|
||||
theme = style.colors # ttkbootstrap.style.Colors
|
||||
def _contrast_text(color: str, *, dark_text: str, light_text: str) -> str:
|
||||
return dark_text if _is_dark(color) else light_text
|
||||
|
||||
bg = theme.bg # 主背景
|
||||
fg = theme.fg # 主前景
|
||||
|
||||
def get_theme_palette() -> dict[str, str]:
|
||||
"""返回当前主题的语义色板,供 ttk / tk 自定义控件共用。"""
|
||||
style = ttk.Style()
|
||||
theme = style.colors
|
||||
|
||||
bg = theme.bg
|
||||
fg = theme.fg
|
||||
primary = theme.primary
|
||||
secondary = theme.secondary
|
||||
success = theme.success
|
||||
info = theme.info
|
||||
warning = theme.warning
|
||||
danger = theme.danger
|
||||
dark = theme.dark
|
||||
border = theme.border
|
||||
inputbg = theme.inputbg
|
||||
inputfg = getattr(theme, "inputfg", fg)
|
||||
|
||||
dark_theme = _is_dark(bg)
|
||||
select_bg = getattr(theme, "selectbg", _mix(primary, bg, 0.30 if dark_theme else 0.12))
|
||||
select_fg = getattr(theme, "selectfg", "#ffffff" if _is_dark(select_bg) else fg)
|
||||
|
||||
# 卡片背景:在主背景上轻微偏移,营造层级感
|
||||
card_bg = _mix(bg, "#ffffff", 0.04) if dark_theme else _mix(bg, "#000000", 0.025)
|
||||
card_border = _mix(bg, fg, 0.18) if dark_theme else _mix(bg, "#000000", 0.10)
|
||||
# 配置项 header 用 secondary 主题色
|
||||
header_bg = secondary
|
||||
header_fg = "#ffffff" if _is_dark(secondary) else "#1a1a1a"
|
||||
if dark_theme:
|
||||
card_bg = _mix(bg, "#ffffff", 0.04)
|
||||
card_border = _mix(bg, fg, 0.18)
|
||||
header_fg = _contrast_text(
|
||||
"#444A51",
|
||||
dark_text="#ffffff",
|
||||
light_text="#1a1a1a",
|
||||
)
|
||||
sidebar_bg = _mix(dark, bg, 0.18)
|
||||
sidebar_hover = _mix(sidebar_bg, "#ffffff", 0.07)
|
||||
sidebar_selected = _mix(sidebar_bg, "#ffffff", 0.14)
|
||||
sidebar_fg = _mix(fg, "#ffffff", 0.04)
|
||||
sidebar_muted = _mix(sidebar_fg, sidebar_bg, 0.45)
|
||||
muted_fg = _mix(fg, bg, 0.32)
|
||||
disabled_fg = _mix(fg, bg, 0.42)
|
||||
disabled_bg = _mix(inputbg, bg, 0.18)
|
||||
disabled_border = _mix(border, fg, 0.22)
|
||||
readonly_bg = _mix(inputbg, "#ffffff", 0.06)
|
||||
success_fg = _mix(success, "#ffffff", 0.08)
|
||||
warning_fg = _mix(warning, "#ffffff", 0.06)
|
||||
info_fg = _mix(info, "#ffffff", 0.06)
|
||||
statusbar_bg = _mix(bg, "#ffffff", 0.06)
|
||||
tooltip_bg = _mix(inputbg, bg, 0.08)
|
||||
tooltip_fg = inputfg
|
||||
tooltip_border = _mix(border, fg, 0.20)
|
||||
surface_alt_bg = _mix(card_bg, "#ffffff", 0.05)
|
||||
surface_hover_bg = _mix(card_bg, "#ffffff", 0.09)
|
||||
badge_bg = _mix(danger, bg, 0.12)
|
||||
badge_fg = "#ffffff"
|
||||
focus = _mix(primary, "#ffffff", 0.18)
|
||||
config_bg = _mix("#444A51", bg, 0.30)
|
||||
else:
|
||||
card_bg = inputbg
|
||||
card_border = border
|
||||
header_fg = bg
|
||||
config_bg = _mix(primary, bg, 0.25)
|
||||
sidebar_bg = _mix(primary, bg, 0.82)
|
||||
sidebar_hover = _mix(primary, bg, 0.72)
|
||||
sidebar_selected = primary
|
||||
sidebar_fg = fg
|
||||
sidebar_muted = _mix(fg, sidebar_bg, 0.35)
|
||||
muted_fg = _mix(fg, bg, 0.38)
|
||||
disabled_fg = _mix(fg, bg, 0.55)
|
||||
disabled_bg = _mix(bg, border, 0.18)
|
||||
disabled_border = _mix(border, bg, 0.18)
|
||||
readonly_bg = _mix(inputbg, primary, 0.04)
|
||||
success_fg = success
|
||||
warning_fg = _mix(warning, fg, 0.18)
|
||||
info_fg = info
|
||||
statusbar_bg = _mix(bg, dark, 0.04)
|
||||
tooltip_bg = inputbg
|
||||
tooltip_fg = inputfg
|
||||
tooltip_border = border
|
||||
surface_alt_bg = _mix(bg, dark, 0.03)
|
||||
surface_hover_bg = _mix(bg, dark, 0.05)
|
||||
badge_bg = danger
|
||||
badge_fg = "#ffffff"
|
||||
focus = _mix(primary, bg, 0.20)
|
||||
|
||||
return {
|
||||
"bg": bg,
|
||||
"fg": fg,
|
||||
"primary": primary,
|
||||
"secondary": secondary,
|
||||
"success": success,
|
||||
"info": info,
|
||||
"warning": warning,
|
||||
"danger": danger,
|
||||
"border": border,
|
||||
"input_bg": inputbg,
|
||||
"input_fg": inputfg,
|
||||
"select_bg": select_bg,
|
||||
"select_fg": select_fg,
|
||||
"card_bg": card_bg,
|
||||
"card_border": card_border,
|
||||
"header_fg": header_fg,
|
||||
"sidebar_bg": sidebar_bg,
|
||||
"sidebar_hover": sidebar_hover,
|
||||
"sidebar_selected": sidebar_selected,
|
||||
"sidebar_fg": sidebar_fg,
|
||||
"sidebar_muted": sidebar_muted,
|
||||
"muted_fg": muted_fg,
|
||||
"disabled_fg": disabled_fg,
|
||||
"disabled_bg": disabled_bg,
|
||||
"disabled_border": disabled_border,
|
||||
"readonly_bg": readonly_bg,
|
||||
"success_fg": success_fg,
|
||||
"warning_fg": warning_fg,
|
||||
"info_fg": info_fg,
|
||||
"statusbar_bg": statusbar_bg,
|
||||
"tooltip_bg": tooltip_bg,
|
||||
"tooltip_fg": tooltip_fg,
|
||||
"tooltip_border": tooltip_border,
|
||||
"surface_alt_bg": surface_alt_bg,
|
||||
"surface_hover_bg": surface_hover_bg,
|
||||
"badge_bg": badge_bg,
|
||||
"badge_fg": badge_fg,
|
||||
"focus": focus,
|
||||
"config_bg": config_bg,
|
||||
}
|
||||
|
||||
|
||||
def apply_listbox_theme(widget) -> None:
|
||||
"""将 tk.Listbox 颜色同步到当前主题。"""
|
||||
palette = get_theme_palette()
|
||||
widget.configure(
|
||||
background=palette["input_bg"],
|
||||
foreground=palette["input_fg"],
|
||||
highlightbackground=palette["border"],
|
||||
highlightcolor=palette["focus"],
|
||||
selectbackground=palette["select_bg"],
|
||||
selectforeground=palette["select_fg"],
|
||||
disabledforeground=palette["disabled_fg"],
|
||||
)
|
||||
|
||||
|
||||
def apply_tooltip_theme(toplevel, label) -> None:
|
||||
"""将 tooltip 的 tk.Toplevel / Label 同步到当前主题。"""
|
||||
palette = get_theme_palette()
|
||||
toplevel.configure(background=palette["tooltip_border"])
|
||||
label.configure(
|
||||
bg=palette["tooltip_bg"],
|
||||
fg=palette["tooltip_fg"],
|
||||
highlightbackground=palette["tooltip_border"],
|
||||
)
|
||||
|
||||
|
||||
def apply_modern_styles() -> None:
|
||||
"""注册或刷新现代化样式集。可在主题切换后再次调用。"""
|
||||
style = ttk.Style()
|
||||
palette = get_theme_palette()
|
||||
|
||||
bg = palette["bg"]
|
||||
fg = palette["fg"]
|
||||
primary = palette["primary"]
|
||||
secondary = palette["secondary"]
|
||||
info = palette["info"]
|
||||
card_bg = palette["card_bg"]
|
||||
card_border = palette["card_border"]
|
||||
header_bg = palette["config_bg"]
|
||||
header_fg = palette["header_fg"]
|
||||
dark_theme = _is_dark(bg)
|
||||
header_hover_bg = _mix(secondary, "#ffffff", 0.08) if _is_dark(secondary) else _mix(secondary, "#000000", 0.08)
|
||||
|
||||
preview_fg = _mix(header_fg, header_bg, 0.35)
|
||||
sidebar_bg = _mix(dark, bg, 0.18) if dark_theme else _mix(primary, "#000000", 0.10)
|
||||
sidebar_hover = _mix(sidebar_bg, "#ffffff", 0.07) if dark_theme else _mix(sidebar_bg, "#000000", 0.06)
|
||||
sidebar_selected = _mix(sidebar_bg, "#ffffff", 0.14) if dark_theme else _mix(sidebar_bg, "#000000", 0.10)
|
||||
# 侧栏背景在浅色主题下也偏深,文字颜色需按侧栏亮度自适应,避免“黑字不明显”。
|
||||
sidebar_fg = "#F4F8FD" if _is_dark(sidebar_bg) else _mix(fg, bg, 0.05)
|
||||
sidebar_muted = _mix(sidebar_fg, sidebar_bg, 0.45)
|
||||
sidebar_bg = palette["sidebar_bg"]
|
||||
sidebar_hover = palette["sidebar_hover"]
|
||||
sidebar_selected = palette["sidebar_selected"]
|
||||
sidebar_fg = palette["sidebar_fg"]
|
||||
sidebar_muted = palette["sidebar_muted"]
|
||||
muted_fg = palette["muted_fg"]
|
||||
disabled_fg = palette["disabled_fg"]
|
||||
disabled_bg = palette["disabled_bg"]
|
||||
disabled_border = palette["disabled_border"]
|
||||
readonly_bg = palette["readonly_bg"]
|
||||
success_fg = palette["success_fg"]
|
||||
warning_fg = palette["warning_fg"]
|
||||
|
||||
# ---------------- 卡片 ----------------
|
||||
style.configure(
|
||||
@@ -134,6 +285,12 @@ def apply_modern_styles() -> None:
|
||||
font=("Segoe UI", 9),
|
||||
)
|
||||
|
||||
# ---------------- 通用文字语义 ----------------
|
||||
style.configure("Muted.TLabel", background=bg, foreground=muted_fg)
|
||||
style.configure("SuccessState.TLabel", background=bg, foreground=success_fg)
|
||||
style.configure("WarningState.TLabel", background=bg, foreground=warning_fg)
|
||||
style.configure("InfoState.TLabel", background=bg, foreground=palette["info_fg"])
|
||||
|
||||
# ---------------- 顶部工具条 ----------------
|
||||
style.configure("Toolbar.TFrame", background=bg, borderwidth=0)
|
||||
# 工具条上的次要按钮(清理配置等)
|
||||
@@ -168,9 +325,17 @@ def apply_modern_styles() -> None:
|
||||
style.configure(
|
||||
"SidebarBrand.TLabel",
|
||||
background=brand_bg,
|
||||
foreground="#ffffff",
|
||||
foreground=palette["badge_fg"],
|
||||
font=("Segoe UI Semibold", 12),
|
||||
)
|
||||
style.configure(
|
||||
"SidebarBadge.TLabel",
|
||||
background=palette["badge_bg"],
|
||||
foreground=palette["badge_fg"],
|
||||
font=("微软雅黑", 8, "bold"),
|
||||
anchor="center",
|
||||
padding=(6, 2),
|
||||
)
|
||||
|
||||
# ---------------- 结果区无边框标题行 ----------------
|
||||
style.configure("ResultHeader.TFrame", background=bg, borderwidth=0)
|
||||
@@ -182,7 +347,7 @@ def apply_modern_styles() -> None:
|
||||
)
|
||||
|
||||
# ---------------- 状态栏 ----------------
|
||||
statusbar_bg = _mix(bg, "#000000", 0.06) if not dark_theme else _mix(bg, "#ffffff", 0.06)
|
||||
statusbar_bg = palette["statusbar_bg"]
|
||||
statusbar_fg = _mix(fg, bg, 0.15)
|
||||
style.configure(
|
||||
"StatusBar.TFrame",
|
||||
@@ -204,6 +369,33 @@ def apply_modern_styles() -> None:
|
||||
padding=(10, 4),
|
||||
)
|
||||
|
||||
# ---------------- 深色禁用态 / 只读态增强 ----------------
|
||||
style.map(
|
||||
"TLabel",
|
||||
foreground=[("disabled", disabled_fg)],
|
||||
)
|
||||
style.map(
|
||||
"TButton",
|
||||
foreground=[("disabled", disabled_fg)],
|
||||
background=[("disabled", disabled_bg)],
|
||||
bordercolor=[("disabled", disabled_border)],
|
||||
darkcolor=[("disabled", disabled_bg)],
|
||||
lightcolor=[("disabled", disabled_bg)],
|
||||
)
|
||||
style.map(
|
||||
"TEntry",
|
||||
foreground=[("disabled", disabled_fg)],
|
||||
fieldbackground=[("disabled", disabled_bg), ("readonly", readonly_bg)],
|
||||
bordercolor=[("disabled", disabled_border), ("readonly", disabled_border)],
|
||||
)
|
||||
style.map(
|
||||
"TCombobox",
|
||||
foreground=[("disabled", disabled_fg), ("readonly", fg)],
|
||||
fieldbackground=[("disabled", disabled_bg), ("readonly", readonly_bg)],
|
||||
bordercolor=[("disabled", disabled_border), ("readonly", disabled_border)],
|
||||
arrowcolor=[("disabled", disabled_fg), ("readonly", muted_fg)],
|
||||
)
|
||||
|
||||
# ---------------- Sidebar 按钮(保留兼容名) ----------------
|
||||
style.configure(
|
||||
"Sidebar.TButton",
|
||||
@@ -225,7 +417,7 @@ def apply_modern_styles() -> None:
|
||||
style.configure(
|
||||
"SidebarSelected.TButton",
|
||||
background=sidebar_selected,
|
||||
foreground="#ffffff",
|
||||
foreground=_contrast_text(sidebar_selected, dark_text=palette["badge_fg"], light_text=sidebar_fg),
|
||||
font=("Segoe UI Semibold", 10),
|
||||
padding=(18, 9),
|
||||
borderwidth=0,
|
||||
|
||||
@@ -15,6 +15,7 @@ import ttkbootstrap as ttk
|
||||
from PIL import Image, ImageTk
|
||||
|
||||
from app.services import ai_image as _svc
|
||||
from app.views.modern_styles import apply_tooltip_theme, get_theme_palette
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
@@ -26,17 +27,19 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _theme_colors():
|
||||
style = ttk.Style()
|
||||
colors = style.colors
|
||||
palette = get_theme_palette()
|
||||
return {
|
||||
"bg": colors.bg,
|
||||
"fg": colors.fg,
|
||||
"muted": colors.secondary,
|
||||
"input_bg": colors.inputbg,
|
||||
"input_fg": colors.inputfg,
|
||||
"select_bg": colors.selectbg,
|
||||
"select_fg": colors.selectfg,
|
||||
"border": colors.border,
|
||||
"bg": palette["bg"],
|
||||
"fg": palette["fg"],
|
||||
"muted": palette["muted_fg"],
|
||||
"input_bg": palette["input_bg"],
|
||||
"input_fg": palette["input_fg"],
|
||||
"select_bg": palette["select_bg"],
|
||||
"select_fg": palette["select_fg"],
|
||||
"border": palette["border"],
|
||||
"tooltip_bg": palette["tooltip_bg"],
|
||||
"tooltip_fg": palette["tooltip_fg"],
|
||||
"tooltip_border": palette["tooltip_border"],
|
||||
}
|
||||
|
||||
|
||||
@@ -95,8 +98,6 @@ def _show_tree_tooltip(self: "PQAutomationApp", text: str, x_root: int, y_root:
|
||||
text="",
|
||||
justify=tk.LEFT,
|
||||
anchor=tk.W,
|
||||
bg="#ffffff",
|
||||
fg="#1f2937",
|
||||
relief=tk.SOLID,
|
||||
bd=1,
|
||||
padx=8,
|
||||
@@ -104,6 +105,7 @@ def _show_tree_tooltip(self: "PQAutomationApp", text: str, x_root: int, y_root:
|
||||
font=("微软雅黑", 9),
|
||||
wraplength=520,
|
||||
)
|
||||
apply_tooltip_theme(tip, label)
|
||||
label.pack(fill=tk.BOTH, expand=True)
|
||||
self._ai_image_tooltip = tip
|
||||
self._ai_image_tooltip_label = label
|
||||
@@ -114,6 +116,7 @@ def _show_tree_tooltip(self: "PQAutomationApp", text: str, x_root: int, y_root:
|
||||
|
||||
self._ai_image_tooltip_item = item_id
|
||||
label.configure(text=text)
|
||||
apply_tooltip_theme(tip, label)
|
||||
tip.geometry(f"+{x_root + 14}+{y_root + 18}")
|
||||
tip.deiconify()
|
||||
tip.lift()
|
||||
@@ -1225,6 +1228,16 @@ def _build_ucd_resized_image(image_path: str, target_w: int, target_h: int) -> s
|
||||
return out_path
|
||||
|
||||
|
||||
def refresh_ai_image_theme(self: "PQAutomationApp"):
|
||||
"""刷新 AI 图片面板中的主题相关控件。"""
|
||||
if hasattr(self, "_apply_ai_image_list_style"):
|
||||
self._apply_ai_image_list_style()
|
||||
tip = getattr(self, "_ai_image_tooltip", None)
|
||||
label = getattr(self, "_ai_image_tooltip_label", None)
|
||||
if tip is not None and label is not None:
|
||||
apply_tooltip_theme(tip, label)
|
||||
|
||||
|
||||
class AIImagePanelMixin:
|
||||
"""由 tools/refactor_to_mixins.py 自动生成。
|
||||
把本模块的自由函数挂到 PQAutomationApp 上,便于 F12 跳转与类型推断。
|
||||
@@ -1249,3 +1262,5 @@ class AIImagePanelMixin:
|
||||
_rename_current = _rename_current
|
||||
_show_list_context_menu = _show_list_context_menu
|
||||
_send_to_ucd = _send_to_ucd
|
||||
_apply_ai_image_list_style = _apply_ai_image_list_style
|
||||
refresh_ai_image_theme = refresh_ai_image_theme
|
||||
|
||||
@@ -22,6 +22,7 @@ from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
||||
from matplotlib.figure import Figure
|
||||
|
||||
from app.tests.color_accuracy import calculate_delta_e_2000
|
||||
from app.views.modern_styles import get_theme_palette
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pqAutomationApp import PQAutomationApp
|
||||
@@ -35,10 +36,6 @@ D65_X = 0.3127
|
||||
D65_Y = 0.3290
|
||||
TARGET_CCT = 6504
|
||||
TARGET_GAMMA = 2.2
|
||||
_DARK_BG = "#2f2f2f"
|
||||
_AX_BG = "#262626"
|
||||
_FG = "#d8d8d8"
|
||||
_GRID = "#5b5b5b"
|
||||
|
||||
DE_FORMULAS = ["2000", "94", "76"]
|
||||
|
||||
@@ -60,7 +57,7 @@ def _contrast_fg(gray_value: int) -> str:
|
||||
def _set_canvas_patch(canvas: tk.Canvas, color: str, text: str) -> None:
|
||||
"""统一设置色块 Canvas 颜色与文字,避免主题对 Label 背景渲染的干扰。"""
|
||||
gray = int(color[1:3], 16)
|
||||
canvas.configure(bg=color, highlightbackground="#666666")
|
||||
canvas.configure(bg=color, highlightbackground=get_theme_palette()["border"])
|
||||
canvas.itemconfigure("patch_bg", fill=color, outline=color)
|
||||
canvas.itemconfigure("patch_text", text=text, fill=_contrast_fg(gray))
|
||||
|
||||
@@ -115,6 +112,7 @@ def _get_calman_palette() -> dict[str, str]:
|
||||
"""根据当前主题生成 Calman 调试面板色板。"""
|
||||
style = ttk.Style()
|
||||
colors = style.colors
|
||||
theme_palette = get_theme_palette()
|
||||
bg = colors.bg
|
||||
fg = colors.fg
|
||||
dark_mode = _is_dark_hex(bg)
|
||||
@@ -131,22 +129,22 @@ def _get_calman_palette() -> dict[str, str]:
|
||||
reading_fg = _mix(fg, "#ffffff", 0.06)
|
||||
status_fg = _mix(fg, bg, 0.35)
|
||||
reading_accent = colors.info
|
||||
xy_series = "#d7dce4"
|
||||
d65_mark = "#ffffff"
|
||||
xy_series = _mix(fg, "#ffffff", 0.10)
|
||||
d65_mark = _mix(fg, "#ffffff", 0.04)
|
||||
else:
|
||||
figure_bg = _mix(bg, "#dfe7ef", 0.45)
|
||||
axes_bg = _mix(bg, "#eff4f9", 0.72)
|
||||
grid = _mix("#5f6f82", axes_bg, 0.55)
|
||||
tree_bg = "#ffffff"
|
||||
tree_even = "#ffffff"
|
||||
tree_bg = theme_palette["input_bg"]
|
||||
tree_even = theme_palette["input_bg"]
|
||||
tree_odd = "#f3f7fb"
|
||||
heading_bg = _mix(colors.primary, "#ffffff", 0.82)
|
||||
reading_bg = _mix(bg, "#e7eef5", 0.58)
|
||||
reading_fg = fg
|
||||
status_fg = _mix(fg, bg, 0.25)
|
||||
reading_accent = _mix(colors.info, "#000000", 0.25)
|
||||
xy_series = "#1f2a36"
|
||||
d65_mark = "#253142"
|
||||
xy_series = _mix(fg, bg, 0.18)
|
||||
d65_mark = _mix(fg, bg, 0.28)
|
||||
|
||||
return {
|
||||
"figure_bg": figure_bg,
|
||||
@@ -166,31 +164,59 @@ def _get_calman_palette() -> dict[str, str]:
|
||||
"tree_heading_bg": heading_bg,
|
||||
"tree_heading_fg": reading_fg,
|
||||
"tree_select": _mix(colors.info, figure_bg, 0.35),
|
||||
"patch_border": theme_palette["border"],
|
||||
"patch_border_alt": _mix(theme_palette["border"], theme_palette["fg"], 0.12),
|
||||
"patch_focus": theme_palette["focus"],
|
||||
"xy_series": xy_series,
|
||||
"d65_mark": d65_mark,
|
||||
}
|
||||
|
||||
|
||||
def _xyY_to_rgb_balance(x: float, y: float, big_y: float) -> tuple[float, float, float]:
|
||||
"""把 xyY 近似映射到 RGB 比例,并归一到平均值 100。"""
|
||||
"""按 D65 同亮度参考计算 RGB Balance(Calman 常见口径)。"""
|
||||
if y <= 0 or big_y <= 0:
|
||||
return float("nan"), float("nan"), float("nan")
|
||||
|
||||
big_x = (x * big_y) / y
|
||||
big_z = ((1.0 - x - y) * big_y) / y
|
||||
def _xyY_to_xyz(cx: float, cy: float, cy_big: float) -> tuple[float, float, float]:
|
||||
if cy <= 0:
|
||||
return float("nan"), float("nan"), float("nan")
|
||||
cx_big = (cx * cy_big) / cy
|
||||
cz_big = ((1.0 - cx - cy) * cy_big) / cy
|
||||
return cx_big, cy_big, cz_big
|
||||
|
||||
r = (3.2406 * big_x) + (-1.5372 * big_y) + (-0.4986 * big_z)
|
||||
g = (-0.9689 * big_x) + (1.8758 * big_y) + (0.0415 * big_z)
|
||||
b = (0.0557 * big_x) + (-0.2040 * big_y) + (1.0570 * big_z)
|
||||
def _xyz_to_linear_rgb(cx_big: float, cy_big: float, cz_big: float) -> tuple[float, float, float]:
|
||||
rr = (3.2406 * cx_big) + (-1.5372 * cy_big) + (-0.4986 * cz_big)
|
||||
gg = (-0.9689 * cx_big) + (1.8758 * cy_big) + (0.0415 * cz_big)
|
||||
bb = (0.0557 * cx_big) + (-0.2040 * cy_big) + (1.0570 * cz_big)
|
||||
return rr, gg, bb
|
||||
|
||||
r = max(r, 0.0)
|
||||
g = max(g, 0.0)
|
||||
b = max(b, 0.0)
|
||||
mx, my, mz = _xyY_to_xyz(x, y, big_y)
|
||||
tx, ty, tz = _xyY_to_xyz(D65_X, D65_Y, big_y)
|
||||
mr, mg, mb = _xyz_to_linear_rgb(mx, my, mz)
|
||||
tr, tg, tb = _xyz_to_linear_rgb(tx, ty, tz)
|
||||
|
||||
avg = (r + g + b) / 3.0
|
||||
if avg <= 0:
|
||||
eps = 1e-9
|
||||
if tr <= eps or tg <= eps or tb <= eps:
|
||||
return float("nan"), float("nan"), float("nan")
|
||||
return (r / avg) * 100.0, (g / avg) * 100.0, (b / avg) * 100.0
|
||||
|
||||
rr = (mr / tr) * 100.0
|
||||
gg = (mg / tg) * 100.0
|
||||
bb = (mb / tb) * 100.0
|
||||
|
||||
# 明显异常值视为无效,避免图表被离群点拉坏。
|
||||
if not (math.isfinite(rr) and math.isfinite(gg) and math.isfinite(bb)):
|
||||
return float("nan"), float("nan"), float("nan")
|
||||
if rr < 0 or gg < 0 or bb < 0:
|
||||
return float("nan"), float("nan"), float("nan")
|
||||
|
||||
return rr, gg, bb
|
||||
|
||||
|
||||
def _target_gamma_loglog_curve(pct: int) -> float:
|
||||
"""Calman风格目标曲线:低灰阶从 1.8 过渡并逐步逼近 2.2。"""
|
||||
if pct <= 0:
|
||||
return 1.8
|
||||
return TARGET_GAMMA - 0.4 * math.exp(-pct / 6.0)
|
||||
|
||||
|
||||
def _style_axes(self: "PQAutomationApp", ax, title: str) -> None:
|
||||
@@ -480,6 +506,7 @@ def create_calman_panel(self: "PQAutomationApp") -> None:
|
||||
self.calman_actual_patch_cells = []
|
||||
self.calman_target_patch_canvases = []
|
||||
self.calman_target_hexes = []
|
||||
patch_palette = _get_calman_palette()
|
||||
for idx, pct in enumerate(self.calman_levels):
|
||||
rgb = _pct_to_gray_rgb(pct)
|
||||
color = _rgb_to_hex(rgb)
|
||||
@@ -493,7 +520,7 @@ def create_calman_panel(self: "PQAutomationApp") -> None:
|
||||
bd=1,
|
||||
relief="solid",
|
||||
highlightthickness=1,
|
||||
highlightbackground="#808080",
|
||||
highlightbackground=patch_palette["patch_border_alt"],
|
||||
cursor="hand2",
|
||||
)
|
||||
actual_cell.grid(row=0, column=idx, padx=1, pady=1, sticky=tk.NSEW)
|
||||
@@ -520,7 +547,7 @@ def create_calman_panel(self: "PQAutomationApp") -> None:
|
||||
bd=1,
|
||||
relief="solid",
|
||||
highlightthickness=1,
|
||||
highlightbackground="#9c9c9c",
|
||||
highlightbackground=patch_palette["patch_border"],
|
||||
cursor="hand2",
|
||||
)
|
||||
cell.grid(row=1, column=idx, padx=1, pady=1, sticky=tk.NSEW)
|
||||
@@ -722,7 +749,7 @@ def send_patch(self: "PQAutomationApp", pct: int) -> None:
|
||||
def _measure_once(self: "PQAutomationApp", pct: int) -> dict | None:
|
||||
"""采集一次 CA410,并组装一条记录(含 CCT/Gamma/ΔE2000)。"""
|
||||
try:
|
||||
x, y, lv, X, Y, Z = self.ca.readAllDisplay()
|
||||
x, y, lv, X, Y, Z = self.read_ca_xyLv()
|
||||
except Exception as exc:
|
||||
self._dispatch_ui(self.log_gui.log, f"CA410 采集异常: {exc}", "error")
|
||||
return None
|
||||
@@ -974,20 +1001,21 @@ def clear_results(self: "PQAutomationApp") -> None:
|
||||
|
||||
def _highlight_patch(self: "PQAutomationApp", pct: int) -> None:
|
||||
"""高亮当前选中色块。"""
|
||||
palette = _get_calman_palette()
|
||||
try:
|
||||
idx = self.calman_levels.index(pct)
|
||||
except ValueError:
|
||||
return
|
||||
for i, cell in enumerate(self.calman_patch_cells):
|
||||
if i == idx:
|
||||
cell.configure(highlightbackground="#1f6fb2", highlightthickness=2)
|
||||
cell.configure(highlightbackground=palette["patch_focus"], highlightthickness=2)
|
||||
else:
|
||||
cell.configure(highlightbackground="#9c9c9c", highlightthickness=1)
|
||||
cell.configure(highlightbackground=palette["patch_border"], highlightthickness=1)
|
||||
for i, cell in enumerate(self.calman_actual_cells):
|
||||
if i == idx:
|
||||
cell.configure(highlightbackground="#1f6fb2", highlightthickness=2)
|
||||
cell.configure(highlightbackground=palette["patch_focus"], highlightthickness=2)
|
||||
else:
|
||||
cell.configure(highlightbackground="#808080", highlightthickness=1)
|
||||
cell.configure(highlightbackground=palette["patch_border_alt"], highlightthickness=1)
|
||||
|
||||
total_cols = len(self.calman_levels) + 1 # 含 metric 列
|
||||
col_index = idx + 1
|
||||
@@ -1082,10 +1110,18 @@ def _redraw_calman_charts(self: "PQAutomationApp") -> None:
|
||||
pcts = [r["pct"] for r in recs]
|
||||
de_vals = [r["de2000"] if r["de2000"] == r["de2000"] else 0 for r in recs]
|
||||
lum_vals = [r["Y"] if r["Y"] == r["Y"] else 0 for r in recs]
|
||||
rgb_r = [r["rgb_r"] for r in recs if r["rgb_r"] == r["rgb_r"]]
|
||||
rgb_g = [r["rgb_g"] for r in recs if r["rgb_g"] == r["rgb_g"]]
|
||||
rgb_b = [r["rgb_b"] for r in recs if r["rgb_b"] == r["rgb_b"]]
|
||||
rgb_pcts = [r["pct"] for r in recs if r["rgb_r"] == r["rgb_r"]]
|
||||
rgb_recs = [
|
||||
r for r in recs
|
||||
if (
|
||||
r.get("rgb_r") == r.get("rgb_r")
|
||||
and r.get("rgb_g") == r.get("rgb_g")
|
||||
and r.get("rgb_b") == r.get("rgb_b")
|
||||
)
|
||||
]
|
||||
rgb_pcts = [r["pct"] for r in rgb_recs]
|
||||
rgb_r = [r["rgb_r"] for r in rgb_recs]
|
||||
rgb_g = [r["rgb_g"] for r in rgb_recs]
|
||||
rgb_b = [r["rgb_b"] for r in rgb_recs]
|
||||
gamma_pcts = [r["pct"] for r in recs if r["gamma"] == r["gamma"]]
|
||||
gamma_vals = [r["gamma"] for r in recs if r["gamma"] == r["gamma"]]
|
||||
cct_vals = [r["cct"] for r in recs if r["cct"] == r["cct"]]
|
||||
@@ -1123,6 +1159,16 @@ def _redraw_calman_charts(self: "PQAutomationApp") -> None:
|
||||
a1.set_ylim(bottom=0)
|
||||
a1.set_xlabel("", fontsize=8)
|
||||
|
||||
rgb_ylim_low = 95.0
|
||||
rgb_ylim_high = 105.0
|
||||
if rgb_recs:
|
||||
rgb_values = rgb_r + rgb_g + rgb_b
|
||||
rgb_min = min(rgb_values + [100.0])
|
||||
rgb_max = max(rgb_values + [100.0])
|
||||
pad = max(0.8, (rgb_max - rgb_min) * 0.15)
|
||||
rgb_ylim_low = min(95.0, rgb_min - pad)
|
||||
rgb_ylim_high = max(105.0, rgb_max + pad)
|
||||
|
||||
# RGB Balance 线图
|
||||
a2 = self.calman_ax_rgb_line
|
||||
a2.clear()
|
||||
@@ -1133,36 +1179,38 @@ def _redraw_calman_charts(self: "PQAutomationApp") -> None:
|
||||
a2.plot(rgb_pcts, rgb_b, "-", color="#4a73ff", linewidth=1.2)
|
||||
a2.axhline(100, color="#9e9e9e", lw=0.8, ls="--")
|
||||
a2.set_xlim(-2, 102)
|
||||
a2.set_ylim(95, 105)
|
||||
a2.set_ylim(rgb_ylim_low, rgb_ylim_high)
|
||||
a2.set_xlabel("", fontsize=8)
|
||||
|
||||
# RGB Balance 条图(用最后一个点)
|
||||
a3 = self.calman_ax_rgb_bar
|
||||
a3.clear()
|
||||
_style_axes(self, a3, "RGB Balance")
|
||||
if recs:
|
||||
last = recs[-1]
|
||||
if rgb_recs:
|
||||
last = rgb_recs[-1]
|
||||
bars = [
|
||||
last["rgb_r"] if last["rgb_r"] == last["rgb_r"] else 100,
|
||||
last["rgb_g"] if last["rgb_g"] == last["rgb_g"] else 100,
|
||||
last["rgb_b"] if last["rgb_b"] == last["rgb_b"] else 100,
|
||||
last["rgb_r"],
|
||||
last["rgb_g"],
|
||||
last["rgb_b"],
|
||||
]
|
||||
a3.bar([0, 1, 2], bars, color=["#ff4d4d", "#4caf50", "#4a73ff"], width=0.7)
|
||||
a3.set_xticks([0, 1, 2], ["R", "G", "B"])
|
||||
else:
|
||||
a3.set_xticks([0, 1, 2], ["R", "G", "B"])
|
||||
a3.set_ylim(95, 105)
|
||||
a3.set_ylim(rgb_ylim_low, rgb_ylim_high)
|
||||
a3.set_xlabel("", fontsize=8)
|
||||
|
||||
# Gamma
|
||||
a4 = self.calman_ax_gamma
|
||||
a4.clear()
|
||||
_style_axes(self, a4, "Gamma Log/Log")
|
||||
target_pcts = list(self.calman_levels)
|
||||
target_vals = [_target_gamma_loglog_curve(p) for p in target_pcts]
|
||||
a4.plot(target_pcts, target_vals, "-", color="#f4ff00", linewidth=1.8)
|
||||
if gamma_pcts:
|
||||
a4.plot(gamma_pcts, gamma_vals, "-", color="#ffe24d", linewidth=1.3)
|
||||
a4.axhline(TARGET_GAMMA, color="#9e9e9e", lw=0.8, ls="--")
|
||||
a4.plot(gamma_pcts, gamma_vals, "-", color="#8f8f8f", linewidth=2.0)
|
||||
a4.set_xlim(-2, 102)
|
||||
a4.set_ylim(1.6, 2.8)
|
||||
a4.set_ylim(1.8, 2.8)
|
||||
a4.set_xlabel("", fontsize=8)
|
||||
|
||||
self.calman_canvas.draw_idle()
|
||||
@@ -1232,14 +1280,20 @@ def _refresh_metric_table(self: "PQAutomationApp") -> None:
|
||||
"""重绘下方矩阵表。"""
|
||||
_apply_calman_tree_style(self)
|
||||
palette = _get_calman_palette()
|
||||
ref_white_y = self.calman_results.get(100, {}).get("Y")
|
||||
|
||||
def _target_y_abs(pctx):
|
||||
if pctx is None:
|
||||
return "-"
|
||||
if ref_white_y is None or ref_white_y != ref_white_y or ref_white_y <= 0:
|
||||
return "-"
|
||||
return _safe_float(ref_white_y * ((pctx / 100.0) ** TARGET_GAMMA), "{:.3f}")
|
||||
|
||||
metrics = [
|
||||
("x CIE31", lambda r: _safe_float(r.get("x")) if r else "-"),
|
||||
("y CIE31", lambda r: _safe_float(r.get("y")) if r else "-"),
|
||||
("Y", lambda r: _safe_float(r.get("Y"), "{:.3f}") if r else "-"),
|
||||
(
|
||||
"Target Y",
|
||||
lambda _r, pctx=None: _safe_float((pctx / 100.0) ** TARGET_GAMMA, "{:.4f}") if pctx and pctx > 0 else ("0.0000" if pctx == 0 else "-"),
|
||||
),
|
||||
("Target Y", lambda _r, pctx=None: _target_y_abs(pctx)),
|
||||
("Gamma Log/Log", lambda r: _safe_float(r.get("gamma"), "{:.3f}") if r else "-"),
|
||||
("CCT", lambda r: _safe_float(r.get("cct"), "{:.0f}") if r else "-"),
|
||||
("dE 2000", lambda r: _safe_float(r.get("de2000"), "{:.3f}") if r else "-"),
|
||||
|
||||
@@ -10,6 +10,7 @@ import colour
|
||||
import numpy as np
|
||||
|
||||
from app.data_range_converter import convert_pattern_params
|
||||
from app.views.modern_styles import get_theme_palette
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
@@ -28,37 +29,12 @@ def create_custom_template_result_panel(self: "PQAutomationApp"):
|
||||
|
||||
table_container = tk.Frame(
|
||||
self.custom_result_frame,
|
||||
bg="#000000",
|
||||
highlightthickness=1,
|
||||
highlightbackground="#5a5a5a",
|
||||
)
|
||||
table_container.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
||||
self.custom_result_table_container = table_container
|
||||
|
||||
style = ttk.Style()
|
||||
style.configure(
|
||||
"CustomResult.Treeview",
|
||||
background="#000000",
|
||||
fieldbackground="#000000",
|
||||
foreground="#ffffff",
|
||||
rowheight=28,
|
||||
borderwidth=0,
|
||||
)
|
||||
style.configure(
|
||||
"CustomResult.Treeview.Heading",
|
||||
background="#2f2f2f",
|
||||
foreground="#f5f5f5",
|
||||
font=("Microsoft YaHei", 10, "bold"),
|
||||
relief="flat",
|
||||
)
|
||||
style.map(
|
||||
"CustomResult.Treeview",
|
||||
background=[("selected", "#1f4e79")],
|
||||
foreground=[("selected", "#ffffff")],
|
||||
)
|
||||
style.map(
|
||||
"CustomResult.Treeview.Heading",
|
||||
background=[("active", "#3b3b3b")],
|
||||
)
|
||||
_apply_custom_result_theme(self)
|
||||
|
||||
columns = (
|
||||
"Pattern",
|
||||
@@ -157,6 +133,70 @@ def create_custom_template_result_panel(self: "PQAutomationApp"):
|
||||
table_container.grid_columnconfigure(0, weight=1)
|
||||
|
||||
|
||||
def _apply_custom_result_theme(self: "PQAutomationApp"):
|
||||
palette = get_theme_palette()
|
||||
container = getattr(self, "custom_result_table_container", None)
|
||||
if container is not None:
|
||||
container.configure(
|
||||
bg=palette["input_bg"],
|
||||
highlightbackground=palette["border"],
|
||||
highlightcolor=palette["border"],
|
||||
)
|
||||
|
||||
style = ttk.Style()
|
||||
style.configure(
|
||||
"CustomResult.Treeview",
|
||||
background=palette["input_bg"],
|
||||
fieldbackground=palette["input_bg"],
|
||||
foreground=palette["input_fg"],
|
||||
rowheight=28,
|
||||
borderwidth=0,
|
||||
)
|
||||
style.configure(
|
||||
"CustomResult.Treeview.Heading",
|
||||
background=palette["surface_alt_bg"],
|
||||
foreground=palette["muted_fg"],
|
||||
font=("Microsoft YaHei", 10, "bold"),
|
||||
relief="flat",
|
||||
)
|
||||
style.map(
|
||||
"CustomResult.Treeview",
|
||||
background=[("selected", palette["select_bg"])],
|
||||
foreground=[("selected", palette["select_fg"])],
|
||||
)
|
||||
style.map(
|
||||
"CustomResult.Treeview.Heading",
|
||||
background=[("active", palette["surface_hover_bg"])],
|
||||
)
|
||||
|
||||
|
||||
def refresh_custom_template_theme(self: "PQAutomationApp"):
|
||||
"""刷新客户模板结果表的主题色。"""
|
||||
_apply_custom_result_theme(self)
|
||||
|
||||
|
||||
def _set_custom_template_tab_visible(self: "PQAutomationApp", visible: bool):
|
||||
"""控制客户模板结果 TAB 的显示与隐藏。"""
|
||||
if not hasattr(self, "chart_notebook") or not hasattr(self, "custom_template_tab_frame"):
|
||||
return
|
||||
|
||||
tab_id = str(self.custom_template_tab_frame)
|
||||
current_tabs = list(self.chart_notebook.tabs())
|
||||
|
||||
if visible:
|
||||
if tab_id not in current_tabs:
|
||||
self.chart_notebook.add(self.custom_template_tab_frame, text="客户模板结果显示")
|
||||
self.chart_notebook.select(self.custom_template_tab_frame)
|
||||
return
|
||||
|
||||
if tab_id in current_tabs:
|
||||
current_selected = self.chart_notebook.select()
|
||||
self.chart_notebook.forget(self.custom_template_tab_frame)
|
||||
remaining_tabs = list(self.chart_notebook.tabs())
|
||||
if current_selected == tab_id and remaining_tabs:
|
||||
self.chart_notebook.select(remaining_tabs[0])
|
||||
|
||||
|
||||
def show_custom_result_context_menu(self: "PQAutomationApp", event):
|
||||
"""显示客户模板结果右键菜单"""
|
||||
if not hasattr(self, "custom_result_tree") or not hasattr(
|
||||
@@ -322,11 +362,9 @@ def _run_custom_row_single_step(self: "PQAutomationApp", item_id, row_no):
|
||||
time.sleep(self.pattern_settle_time)
|
||||
|
||||
# 测量:显示模式1读取 Tcp/duv/Lv,显示模式8读取 λd/Pe/Lv 与 XYZ。
|
||||
self.ca.set_Display(1)
|
||||
tcp, duv, lv, _, _, _ = self.ca.readAllDisplay()
|
||||
tcp, duv, lv, _, _, _ = self.read_ca_tcp_duv()
|
||||
|
||||
self.ca.set_Display(8)
|
||||
lambda_d, pe, lv, X, Y, Z = self.ca.readAllDisplay()
|
||||
lambda_d, pe, lv, X, Y, Z = self.read_ca_lambda_pe()
|
||||
|
||||
xy = colour.XYZ_to_xy(np.array([X, Y, Z]))
|
||||
u_prime, v_prime, _ = colour.XYZ_to_CIE1976UCS(np.array([X, Y, Z]))
|
||||
@@ -532,9 +570,6 @@ def append_custom_template_result(self: "PQAutomationApp", row_no, result_data):
|
||||
def start_custom_template_test(self: "PQAutomationApp"):
|
||||
"""开始客户模板测试(SDR)"""
|
||||
|
||||
if hasattr(self, "chart_notebook") and hasattr(self, "custom_template_tab_frame"):
|
||||
self.chart_notebook.select(self.custom_template_tab_frame)
|
||||
|
||||
if self.ca is None or self.ucd is None:
|
||||
messagebox.showerror("错误", "请先连接CA410和信号发生器")
|
||||
return
|
||||
@@ -569,8 +604,10 @@ def start_custom_template_test(self: "PQAutomationApp"):
|
||||
self.custom_btn.config(state=tk.NORMAL)
|
||||
self.status_var.set("测试已取消")
|
||||
self.set_custom_result_table_locked(False)
|
||||
_set_custom_template_tab_visible(self, False)
|
||||
return
|
||||
|
||||
_set_custom_template_tab_visible(self, True)
|
||||
self.set_custom_result_table_locked(True)
|
||||
|
||||
self.test_thread = threading.Thread(target=self.run_custom_sdr_test, args=([],))
|
||||
@@ -923,6 +960,7 @@ class CustomTemplatePanelMixin:
|
||||
把本模块的自由函数挂到 PQAutomationApp 上,便于 F12 跳转与类型推断。
|
||||
"""
|
||||
create_custom_template_result_panel = create_custom_template_result_panel
|
||||
_set_custom_template_tab_visible = _set_custom_template_tab_visible
|
||||
show_custom_result_context_menu = show_custom_result_context_menu
|
||||
set_custom_result_table_locked = set_custom_result_table_locked
|
||||
start_custom_row_single_step = start_custom_row_single_step
|
||||
@@ -937,3 +975,4 @@ class CustomTemplatePanelMixin:
|
||||
update_custom_button_visibility = update_custom_button_visibility
|
||||
export_custom_template_excel = export_custom_template_excel
|
||||
export_custom_template_charts = export_custom_template_charts
|
||||
refresh_custom_template_theme = refresh_custom_template_theme
|
||||
|
||||
@@ -171,7 +171,7 @@ def create_gamma_pattern_panel(self: "PQAutomationApp"):
|
||||
ttk.Label(
|
||||
title_row,
|
||||
text="(Gamma / CCT / 对比度 / EOTF 共用此列表)",
|
||||
foreground="#888",
|
||||
style="Muted.TLabel",
|
||||
).pack(side=tk.LEFT, padx=(8, 0))
|
||||
|
||||
# ===== 预设管理行 =====
|
||||
@@ -207,7 +207,7 @@ def create_gamma_pattern_panel(self: "PQAutomationApp"):
|
||||
).pack(side=tk.LEFT, padx=2)
|
||||
|
||||
self._gamma_active_label = ttk.Label(
|
||||
preset_row1, text="", foreground="#0a8", font=("微软雅黑", 9, "bold")
|
||||
preset_row1, text="", style="SuccessState.TLabel", font=("微软雅黑", 9, "bold")
|
||||
)
|
||||
self._gamma_active_label.pack(side=tk.LEFT, padx=(10, 0))
|
||||
|
||||
@@ -230,7 +230,7 @@ def create_gamma_pattern_panel(self: "PQAutomationApp"):
|
||||
|
||||
# 描述行
|
||||
self._gamma_meta_label = ttk.Label(
|
||||
preset_box, text="", foreground="#666", font=("微软雅黑", 9)
|
||||
preset_box, text="", style="Muted.TLabel", font=("微软雅黑", 9)
|
||||
)
|
||||
self._gamma_meta_label.pack(anchor=tk.W, pady=(6, 0))
|
||||
|
||||
@@ -350,7 +350,7 @@ def create_gamma_pattern_panel(self: "PQAutomationApp"):
|
||||
paste_frame.pack(fill=tk.X, pady=(10, 0))
|
||||
ttk.Label(
|
||||
paste_frame, text="每行:R,G,B 或 R G B\n或:灰度% (如 50%)",
|
||||
foreground="#888", justify=tk.LEFT,
|
||||
style="Muted.TLabel", justify=tk.LEFT,
|
||||
).pack(anchor=tk.W)
|
||||
ttk.Button(
|
||||
paste_frame, text="从剪贴板导入",
|
||||
@@ -363,7 +363,7 @@ def create_gamma_pattern_panel(self: "PQAutomationApp"):
|
||||
bottom.pack(fill=tk.X, pady=(10, 0))
|
||||
|
||||
self._gamma_validate_label = ttk.Label(
|
||||
bottom, text="", foreground="#666", justify=tk.LEFT
|
||||
bottom, text="", style="Muted.TLabel", justify=tk.LEFT
|
||||
)
|
||||
self._gamma_validate_label.pack(anchor=tk.W)
|
||||
|
||||
@@ -435,16 +435,16 @@ def _update_active_label(self: "PQAutomationApp"):
|
||||
current = self._gamma_current_preset
|
||||
if active and current == active and not self._gamma_dirty:
|
||||
self._gamma_active_label.config(
|
||||
text=f"✔ 当前激活:{active}", foreground="#0a8"
|
||||
text=f"✔ 当前激活:{active}", style="SuccessState.TLabel"
|
||||
)
|
||||
elif active:
|
||||
extra = "(有未保存改动)" if self._gamma_dirty else ""
|
||||
self._gamma_active_label.config(
|
||||
text=f"● 激活:{active} 编辑中:{current or '-'}{extra}",
|
||||
foreground="#a60" if self._gamma_dirty else "#888",
|
||||
style="WarningState.TLabel" if self._gamma_dirty else "Muted.TLabel",
|
||||
)
|
||||
else:
|
||||
self._gamma_active_label.config(text="● 未激活任何预设", foreground="#888")
|
||||
self._gamma_active_label.config(text="● 未激活任何预设", style="Muted.TLabel")
|
||||
|
||||
|
||||
def _on_preset_selected(self: "PQAutomationApp"):
|
||||
@@ -1023,11 +1023,11 @@ def _run_validation(self: "PQAutomationApp"):
|
||||
|
||||
if not msgs:
|
||||
text = f"✔ 校验通过(共 {len(params)} 点)"
|
||||
color = "#0a8"
|
||||
style_name = "SuccessState.TLabel"
|
||||
else:
|
||||
text = f"共 {len(params)} 点 | " + " ".join(msgs)
|
||||
color = "#a60" if any(m.startswith("⚠") for m in msgs) else "#666"
|
||||
self._gamma_validate_label.config(text=text, foreground=color)
|
||||
style_name = "WarningState.TLabel" if any(m.startswith("⚠") for m in msgs) else "Muted.TLabel"
|
||||
self._gamma_validate_label.config(text=text, style=style_name)
|
||||
|
||||
|
||||
# ============================================================
|
||||
|
||||
@@ -757,13 +757,10 @@ def create_test_type_frame(self: "PQAutomationApp"):
|
||||
# 测试版水印标签(版本 x.x.0.0 时显示)
|
||||
from app_version import is_beta_version, APP_VERSION
|
||||
if is_beta_version():
|
||||
beta_lbl = tk.Label(
|
||||
beta_lbl = ttk.Label(
|
||||
self.sidebar_frame,
|
||||
text=f"[测试版] v{APP_VERSION}",
|
||||
foreground="#ffffff",
|
||||
background="#cc3300",
|
||||
font=("微软雅黑", 8, "bold"),
|
||||
anchor="center",
|
||||
style="SidebarBadge.TLabel",
|
||||
)
|
||||
beta_lbl.pack(fill=tk.X, side=tk.BOTTOM, padx=4, pady=(6, 4))
|
||||
|
||||
@@ -809,6 +806,7 @@ def _on_toggle_theme(self: "PQAutomationApp") -> None:
|
||||
"""切换主题:重新应用 ttk 样式并刷新所有自定义样式相关的标签。"""
|
||||
from app.views.theme_manager import toggle_theme
|
||||
toggle_theme()
|
||||
# apply_modern_styles()
|
||||
_refresh_theme_toggle_label(self)
|
||||
if hasattr(self, "apply_result_chart_theme"):
|
||||
try:
|
||||
@@ -820,6 +818,21 @@ def _on_toggle_theme(self: "PQAutomationApp") -> None:
|
||||
self.log_gui.refresh_log_theme()
|
||||
except Exception:
|
||||
pass
|
||||
if hasattr(self, "refresh_ai_image_theme"):
|
||||
try:
|
||||
self.refresh_ai_image_theme()
|
||||
except Exception:
|
||||
pass
|
||||
if hasattr(self, "refresh_single_step_theme"):
|
||||
try:
|
||||
self.refresh_single_step_theme()
|
||||
except Exception:
|
||||
pass
|
||||
if hasattr(self, "refresh_custom_template_theme"):
|
||||
try:
|
||||
self.refresh_custom_template_theme()
|
||||
except Exception:
|
||||
pass
|
||||
if hasattr(self, "refresh_calman_theme"):
|
||||
try:
|
||||
self.refresh_calman_theme()
|
||||
|
||||
@@ -63,7 +63,7 @@ def create_pantone_baseline_panel(self: "PQAutomationApp"):
|
||||
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(
|
||||
ttk.Label(config_row, textvariable=self.pantone_status_var, style="Muted.TLabel").pack(
|
||||
side=tk.RIGHT
|
||||
)
|
||||
|
||||
@@ -336,7 +336,7 @@ def _launch_worker(self: "PQAutomationApp", start_index, settle):
|
||||
end_state = "paused"
|
||||
break
|
||||
|
||||
x, y, lv, _X, _Y, _Z = self.ca.readAllDisplay()
|
||||
x, y, lv, _X, _Y, _Z = self.read_ca_xyLv()
|
||||
if lv is None:
|
||||
raise RuntimeError(f"第 {i + 1} 组 CA410 采集失败")
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ def create_local_dimming_panel(self: "PQAutomationApp"):
|
||||
window_frame,
|
||||
text="点击按钮发送对应百分比的白色窗口(黑色背景 + 居中白色矩形)",
|
||||
font=("", 9),
|
||||
foreground="#28a745",
|
||||
style="SuccessState.TLabel",
|
||||
).pack(pady=(0, 8))
|
||||
|
||||
# 第一行:1%, 2%, 5%, 10%, 18%
|
||||
@@ -96,7 +96,7 @@ def create_local_dimming_panel(self: "PQAutomationApp"):
|
||||
pattern_frame,
|
||||
text="手动发送棋盘格、瞬时峰值、黑场图案,再点击采集当前亮度",
|
||||
font=("", 9),
|
||||
foreground="#28a745",
|
||||
style="SuccessState.TLabel",
|
||||
).pack(pady=(0, 8))
|
||||
|
||||
pattern_row = ttk.Frame(pattern_frame)
|
||||
@@ -155,7 +155,7 @@ def create_local_dimming_panel(self: "PQAutomationApp"):
|
||||
measure_btn_frame,
|
||||
text="亮度: -- cd/m² | x: -- | y: --",
|
||||
font=("Consolas", 10),
|
||||
foreground="#007bff",
|
||||
style="InfoState.TLabel",
|
||||
)
|
||||
self.ld_result_label.pack(side=tk.LEFT, padx=(10, 0))
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ from tkinter import filedialog, messagebox
|
||||
import ttkbootstrap as ttk
|
||||
from PIL import Image
|
||||
|
||||
from app.views.modern_styles import apply_listbox_theme
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -59,7 +61,7 @@ def create_single_step_panel(self: "PQAutomationApp"):
|
||||
ttk.Label(
|
||||
title_row,
|
||||
text="录入目标色块,发送纯色色块并采集 xyY / ΔE2000。",
|
||||
foreground="#666",
|
||||
style="Muted.TLabel",
|
||||
).pack(side=tk.LEFT, padx=(12, 0))
|
||||
|
||||
left = ttk.LabelFrame(root, text="样本列表", padding=8)
|
||||
@@ -73,11 +75,8 @@ def create_single_step_panel(self: "PQAutomationApp"):
|
||||
activestyle="none",
|
||||
font=("微软雅黑", 9),
|
||||
highlightthickness=1,
|
||||
highlightbackground="#d8d8d8",
|
||||
highlightcolor="#4a90e2",
|
||||
selectbackground="#2b6cb0",
|
||||
selectforeground="#ffffff",
|
||||
)
|
||||
apply_listbox_theme(self.single_step_listbox)
|
||||
self.single_step_listbox.pack(fill=tk.BOTH, expand=True)
|
||||
self.single_step_listbox.bind(
|
||||
"<<ListboxSelect>>", lambda e: _on_sample_select(self)
|
||||
@@ -154,7 +153,7 @@ def create_single_step_panel(self: "PQAutomationApp"):
|
||||
ttk.Label(
|
||||
form_frame,
|
||||
textvariable=self.single_step_status_var,
|
||||
foreground="#666",
|
||||
style="Muted.TLabel",
|
||||
).grid(row=2, column=2, columnspan=4, sticky=tk.W, pady=4)
|
||||
|
||||
action_row = ttk.Frame(form_frame)
|
||||
@@ -444,7 +443,7 @@ def _measure_current_sample(self: "PQAutomationApp"):
|
||||
|
||||
def worker():
|
||||
try:
|
||||
x, y, lv, _X, _Y, _Z = self.ca.readAllDisplay()
|
||||
x, y, lv, _X, _Y, _Z = self.read_ca_xyLv()
|
||||
if lv is None:
|
||||
raise RuntimeError("CA410 未返回有效亮度")
|
||||
self._dispatch_ui(self.single_step_measured_x_var.set, f"{x:.4f}")
|
||||
@@ -556,6 +555,12 @@ def _export_results_csv(self: "PQAutomationApp"):
|
||||
messagebox.showerror("导出失败", f"写入 CSV 失败: {exc}")
|
||||
|
||||
|
||||
def refresh_single_step_theme(self: "PQAutomationApp"):
|
||||
"""刷新单步调试中 tk.Listbox 的主题色。"""
|
||||
if hasattr(self, "single_step_listbox"):
|
||||
apply_listbox_theme(self.single_step_listbox)
|
||||
|
||||
|
||||
class SingleStepPanelMixin:
|
||||
"""由 tools/refactor_to_mixins.py 自动生成。
|
||||
把本模块的自由函数挂到 PQAutomationApp 上,便于 F12 跳转与类型推断。
|
||||
@@ -575,3 +580,4 @@ class SingleStepPanelMixin:
|
||||
_commit_result = _commit_result
|
||||
_clear_results = _clear_results
|
||||
_export_results_csv = _export_results_csv
|
||||
refresh_single_step_theme = refresh_single_step_theme
|
||||
|
||||
@@ -802,7 +802,7 @@ class PQDebugPanel:
|
||||
time.sleep(1.5)
|
||||
|
||||
# 测量数据
|
||||
x, y, lv, X, Y, Z = self.app.ca.readAllDisplay()
|
||||
x, y, lv, X, Y, Z = self.app.read_ca_xyLv()
|
||||
|
||||
self.app.log_gui.log(
|
||||
f"测量完成: x={x:.4f}, y={y:.4f}, lv={lv:.2f}, "
|
||||
|
||||
@@ -19,20 +19,44 @@ from app.views.modern_styles import apply_modern_styles
|
||||
|
||||
_PREFS_PATH = Path("settings/ui_preferences.json")
|
||||
|
||||
# 浅色主题:沿用旧的 yeti(首发布兼容)
|
||||
LIGHT_THEME = "yeti"
|
||||
# 浅色主题:自定义轻量蓝灰色板,恢复旧版浅色观感
|
||||
LIGHT_THEME = "calman_light"
|
||||
# 深色主题:自定义 Calman 风格
|
||||
DARK_THEME = "calman_dark"
|
||||
|
||||
_LEGACY_LIGHT_THEMES = {"yeti"}
|
||||
|
||||
|
||||
_CALMAN_LIGHT_COLORS = {
|
||||
"primary": "#1755a6",
|
||||
"secondary": "#2B6CB0",
|
||||
"success": "#2F9E44",
|
||||
"info": "#247BA0",
|
||||
"warning": "#C98700",
|
||||
"danger": "#CC3300",
|
||||
"light": "#F7FAFC",
|
||||
"dark": "#1F2A36",
|
||||
"bg": "#F5F8FB",
|
||||
"fg": "#1F2933",
|
||||
"selectbg": "#2B6CB0",
|
||||
"selectfg": "#FFFFFF",
|
||||
"border": "#C8D4E3",
|
||||
"inputfg": "#243240",
|
||||
"inputbg": "#FFFFFF",
|
||||
"active": "#D9E6F2",
|
||||
}
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Calman 风格深色主题色板(参考实测截图取色)
|
||||
# Calman 风格深色主题色板
|
||||
# ----------------------------------------------------------------------
|
||||
_CALMAN_DARK_COLORS = {
|
||||
"primary": "#343A41", # 主色改为炭灰,避免大面积亮蓝
|
||||
"secondary": "#444A51", # 中性深灰(用于 header / 分组背景)
|
||||
# "primary": "#2A2F36",
|
||||
# "secondary": "#444A51",
|
||||
"primary": "#6FAFCC",
|
||||
"secondary": "#AEAEAE",
|
||||
"success": "#4FB960",
|
||||
"info": "#6FAFCC", # 降低饱和度,只做少量点缀
|
||||
"info": "#6FAFCC",
|
||||
"warning": "#F2A93B",
|
||||
"danger": "#E0524A",
|
||||
"light": "#BFC6CE", # 高亮文本
|
||||
@@ -51,14 +75,27 @@ _CALMAN_DARK_COLORS = {
|
||||
def register_themes() -> None:
|
||||
"""把自定义深色主题注册到 ttkbootstrap(可重复调用,幂等)。"""
|
||||
style = Style()
|
||||
if LIGHT_THEME not in style.theme_names():
|
||||
light_def = ThemeDefinition(
|
||||
name=LIGHT_THEME,
|
||||
themetype="light",
|
||||
colors=_CALMAN_LIGHT_COLORS,
|
||||
)
|
||||
style.register_theme(light_def)
|
||||
if DARK_THEME in style.theme_names():
|
||||
return
|
||||
theme_def = ThemeDefinition(
|
||||
dark_def = ThemeDefinition(
|
||||
name=DARK_THEME,
|
||||
themetype="dark",
|
||||
colors=_CALMAN_DARK_COLORS,
|
||||
)
|
||||
style.register_theme(theme_def)
|
||||
style.register_theme(dark_def)
|
||||
|
||||
|
||||
def _normalize_theme_name(name: Optional[str]) -> str:
|
||||
if not name or name in _LEGACY_LIGHT_THEMES:
|
||||
return LIGHT_THEME
|
||||
return name
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
@@ -101,7 +138,7 @@ def apply_initial_theme() -> str:
|
||||
返回最终生效的主题名。
|
||||
"""
|
||||
register_themes()
|
||||
name = get_saved_theme() or LIGHT_THEME
|
||||
name = _normalize_theme_name(get_saved_theme())
|
||||
style = Style()
|
||||
if name not in style.theme_names():
|
||||
name = LIGHT_THEME
|
||||
@@ -113,6 +150,7 @@ def apply_initial_theme() -> str:
|
||||
def set_theme(name: str) -> str:
|
||||
"""切换到指定主题,持久化偏好,并刷新自定义样式。"""
|
||||
register_themes()
|
||||
name = _normalize_theme_name(name)
|
||||
style = Style()
|
||||
if name not in style.theme_names():
|
||||
name = LIGHT_THEME
|
||||
|
||||
Reference in New Issue
Block a user