907 lines
38 KiB
Python
907 lines
38 KiB
Python
|
|
#!/usr/bin/env python3
|
|||
|
|
"""
|
|||
|
|
主窗口
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import customtkinter as ctk
|
|||
|
|
from tkinter import filedialog, messagebox
|
|||
|
|
import threading
|
|||
|
|
import yaml
|
|||
|
|
import logging
|
|||
|
|
from logging.handlers import QueueHandler
|
|||
|
|
import queue
|
|||
|
|
from pathlib import Path
|
|||
|
|
|
|||
|
|
from gui.tabs.main_config import MainConfigTab
|
|||
|
|
from gui.tabs.result_preview import ResultPreviewTab
|
|||
|
|
from gui.tabs.hardware_control import HardwareControlTab
|
|||
|
|
from core.simulator import EventSimulatorV4
|
|||
|
|
from core.data_writer import DataWriterV4
|
|||
|
|
from utils.config_manager import ConfigManager
|
|||
|
|
from utils.logger import get_logger, LogLevel
|
|||
|
|
|
|||
|
|
# 配置日志队列
|
|||
|
|
log_queue = queue.Queue()
|
|||
|
|
|
|||
|
|
class QueueListener(logging.handlers.QueueListener):
|
|||
|
|
def __init__(self, queue, gui_callback):
|
|||
|
|
self.gui_callback = gui_callback
|
|||
|
|
super().__init__(queue, *[])
|
|||
|
|
|
|||
|
|
def handle(self, record):
|
|||
|
|
# 调用GUI回调函数
|
|||
|
|
if self.gui_callback:
|
|||
|
|
self.gui_callback(record)
|
|||
|
|
|
|||
|
|
class MainWindow(ctk.CTk):
|
|||
|
|
def __init__(self):
|
|||
|
|
super().__init__()
|
|||
|
|
|
|||
|
|
# 配置日志
|
|||
|
|
self._setup_logging()
|
|||
|
|
|
|||
|
|
self.title("探测器信号模拟器 V4.2")
|
|||
|
|
self.geometry("1400x900")
|
|||
|
|
|
|||
|
|
ctk.set_appearance_mode("dark")
|
|||
|
|
ctk.set_default_color_theme("blue")
|
|||
|
|
|
|||
|
|
# 初始化after任务列表
|
|||
|
|
self.after_tasks = []
|
|||
|
|
|
|||
|
|
self.grid_columnconfigure(0, weight=1)
|
|||
|
|
self.grid_rowconfigure(0, weight=1)
|
|||
|
|
|
|||
|
|
# 创建选项卡视图
|
|||
|
|
self.tabview = ctk.CTkTabview(self)
|
|||
|
|
self.tabview.grid(row=0, column=0, sticky="nsew", padx=10, pady=10)
|
|||
|
|
|
|||
|
|
self.tabview.add("硬件控制")
|
|||
|
|
self.tabview.add("探测器信号谱模拟")
|
|||
|
|
|
|||
|
|
# 绑定选项卡切换事件
|
|||
|
|
self.tabview.configure(command=self._on_tab_change)
|
|||
|
|
|
|||
|
|
# 创建探测器信号谱模拟布局
|
|||
|
|
main_tab = self.tabview.tab("探测器信号谱模拟")
|
|||
|
|
# 固定左右面板的宽度,避免布局变化
|
|||
|
|
main_tab.grid_columnconfigure(0, weight=0, minsize=600) # 左侧固定宽度(增加1/4)
|
|||
|
|
main_tab.grid_columnconfigure(1, weight=1) # 右侧自适应
|
|||
|
|
main_tab.grid_rowconfigure(0, weight=1)
|
|||
|
|
|
|||
|
|
# 左侧配置面板(带滚动条)
|
|||
|
|
left_frame = ctk.CTkFrame(main_tab, fg_color="#2a2a2a") # 设置黑色背景
|
|||
|
|
left_frame.grid(row=0, column=0, sticky="nsew", padx=10, pady=10)
|
|||
|
|
# 固定左侧面板宽度(增加1/4)
|
|||
|
|
left_frame.configure(width=600)
|
|||
|
|
left_frame.grid_propagate(False)
|
|||
|
|
|
|||
|
|
# 创建滚动容器
|
|||
|
|
canvas = ctk.CTkCanvas(left_frame, bg="#2a2a2a", highlightthickness=0)
|
|||
|
|
canvas.pack(fill="both", expand=True)
|
|||
|
|
|
|||
|
|
# 不添加可见的滚动条,只保留鼠标滚轮滚动功能
|
|||
|
|
# 这样可以避免滚动条显示/隐藏时影响布局
|
|||
|
|
|
|||
|
|
# 创建滚动内容框架
|
|||
|
|
scrollable_frame = ctk.CTkFrame(canvas, fg_color="#2a2a2a")
|
|||
|
|
scrollable_frame.bind("<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
|
|||
|
|
window_id = canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
|
|||
|
|
|
|||
|
|
# 调整宽度当窗口大小改变时
|
|||
|
|
def _on_configure(event):
|
|||
|
|
canvas.itemconfig(window_id, width=left_frame.winfo_width())
|
|||
|
|
|
|||
|
|
left_frame.bind("<Configure>", _on_configure)
|
|||
|
|
|
|||
|
|
# 在滚动框架中添加分页控件
|
|||
|
|
self.left_tabview = ctk.CTkTabview(scrollable_frame)
|
|||
|
|
self.left_tabview.pack(fill="both", expand=True, padx=5, pady=5)
|
|||
|
|
|
|||
|
|
# 添加"设置"标签页
|
|||
|
|
settings_tab = self.left_tabview.add("设置")
|
|||
|
|
|
|||
|
|
# 在"设置"标签页中添加主配置选项卡
|
|||
|
|
self.main_config_tab = MainConfigTab(settings_tab)
|
|||
|
|
self.main_config_tab.pack(fill="both", expand=True, padx=5, pady=5)
|
|||
|
|
|
|||
|
|
# 添加"日志"标签页
|
|||
|
|
log_tab = self.left_tabview.add("日志")
|
|||
|
|
|
|||
|
|
# 配置日志标签页的布局
|
|||
|
|
log_tab.grid_rowconfigure(1, weight=1)
|
|||
|
|
log_tab.grid_columnconfigure(0, weight=1)
|
|||
|
|
|
|||
|
|
# ctk.CTkLabel(
|
|||
|
|
# log_tab,
|
|||
|
|
# text="生成日志",
|
|||
|
|
# font=ctk.CTkFont(size=14, weight="bold")
|
|||
|
|
# ).grid(row=0, column=0, sticky="w", padx=10, pady=5)
|
|||
|
|
|
|||
|
|
# 创建日志文本框容器框架,用于动态调整高度
|
|||
|
|
log_container = ctk.CTkFrame(log_tab, fg_color="transparent")
|
|||
|
|
log_container.grid(row=1, column=0, sticky="nsew", padx=10, pady=5)
|
|||
|
|
log_container.grid_rowconfigure(0, weight=1)
|
|||
|
|
log_container.grid_columnconfigure(0, weight=1)
|
|||
|
|
log_container.grid_propagate(False) # 防止子组件收缩框架
|
|||
|
|
|
|||
|
|
self.log_textbox = ctk.CTkTextbox(
|
|||
|
|
log_container,
|
|||
|
|
fg_color="#1e1e1e",
|
|||
|
|
text_color="white"
|
|||
|
|
)
|
|||
|
|
self.log_textbox.grid(row=0, column=0, sticky="nsew")
|
|||
|
|
|
|||
|
|
# 动态调整日志容器高度
|
|||
|
|
def _update_log_container_height(event=None):
|
|||
|
|
# 计算标签页标题和其他元素占用的总高度
|
|||
|
|
other_height = 60 # 标题栏和边距的估算值
|
|||
|
|
# 计算可用高度
|
|||
|
|
available_height = max(400, left_frame.winfo_height() - other_height )
|
|||
|
|
log_container.configure(height=available_height)
|
|||
|
|
|
|||
|
|
# 绑定到窗口大小变化事件
|
|||
|
|
left_frame.bind("<Configure>", lambda e: self.after(100, _update_log_container_height), add="+")
|
|||
|
|
|
|||
|
|
# 加载默认配置
|
|||
|
|
self.load_default_config()
|
|||
|
|
|
|||
|
|
# 在所有组件创建完成后,递归绑定滚轮事件
|
|||
|
|
def _on_mousewheel(event):
|
|||
|
|
canvas.yview_scroll(int(-1*(event.delta/120)), "units")
|
|||
|
|
|
|||
|
|
def _bind_mousewheel(widget):
|
|||
|
|
try:
|
|||
|
|
widget.bind("<MouseWheel>", _on_mousewheel)
|
|||
|
|
except NotImplementedError:
|
|||
|
|
pass # 忽略不支持bind方法的组件
|
|||
|
|
for child in widget.winfo_children():
|
|||
|
|
_bind_mousewheel(child)
|
|||
|
|
|
|||
|
|
_bind_mousewheel(scrollable_frame)
|
|||
|
|
|
|||
|
|
# 右侧结果预览
|
|||
|
|
right_frame = ctk.CTkFrame(main_tab)
|
|||
|
|
right_frame.grid(row=0, column=1, sticky="nsew", padx=10, pady=10)
|
|||
|
|
right_frame.grid_rowconfigure(0, weight=1)
|
|||
|
|
right_frame.grid_columnconfigure(0, weight=1)
|
|||
|
|
# 固定右侧面板最小宽度
|
|||
|
|
right_frame.grid_propagate(False)
|
|||
|
|
|
|||
|
|
self.result_preview_tab = ResultPreviewTab(right_frame)
|
|||
|
|
self.result_preview_tab.grid(row=0, column=0, sticky="nsew", padx=5, pady=5)
|
|||
|
|
|
|||
|
|
# 硬件控制选项卡
|
|||
|
|
hardware_tab = self.tabview.tab("硬件控制")
|
|||
|
|
# 配置硬件控制选项卡的布局
|
|||
|
|
hardware_tab.grid_columnconfigure(0, weight=1)
|
|||
|
|
hardware_tab.grid_rowconfigure(0, weight=1)
|
|||
|
|
|
|||
|
|
self.hardware_control_tab = HardwareControlTab(hardware_tab)
|
|||
|
|
self.hardware_control_tab.grid(row=0, column=0, sticky="nsew", padx=10, pady=10)
|
|||
|
|
|
|||
|
|
# 加载硬件控制配置
|
|||
|
|
from utils.config_manager import ConfigManager
|
|||
|
|
config = ConfigManager.load_default_config()
|
|||
|
|
if config and "hardware_control" in config:
|
|||
|
|
self.hardware_control_tab.set_config(config["hardware_control"])
|
|||
|
|
|
|||
|
|
# 创建控制按钮框架
|
|||
|
|
self.control_frame = ctk.CTkFrame(self)
|
|||
|
|
self.control_frame.grid(row=1, column=0, sticky="ew", padx=10, pady=(0, 10))
|
|||
|
|
self.control_frame.grid_columnconfigure(0, weight=1)
|
|||
|
|
self.control_frame.grid_columnconfigure(1, weight=0)
|
|||
|
|
self.control_frame.grid_columnconfigure(2, weight=0)
|
|||
|
|
self.control_frame.grid_columnconfigure(3, weight=0)
|
|||
|
|
self.control_frame.grid_columnconfigure(4, weight=0)
|
|||
|
|
self.control_frame.grid_columnconfigure(5, weight=0)
|
|||
|
|
self.control_frame.grid_columnconfigure(6, weight=0)
|
|||
|
|
|
|||
|
|
# 状态栏
|
|||
|
|
self.status_var = ctk.StringVar(value="就绪")
|
|||
|
|
self.status_label = ctk.CTkLabel(
|
|||
|
|
self.control_frame,
|
|||
|
|
textvariable=self.status_var,
|
|||
|
|
font=ctk.CTkFont(size=12)
|
|||
|
|
)
|
|||
|
|
self.status_label.grid(row=0, column=0, sticky="w", padx=10, pady=10)
|
|||
|
|
|
|||
|
|
# 清除DDR内存按钮(红色)- 硬件控制页面专用
|
|||
|
|
self.clear_ddr_button = ctk.CTkButton(
|
|||
|
|
self.control_frame,
|
|||
|
|
text="清除DDR内存",
|
|||
|
|
command=self.clear_ddr_memory,
|
|||
|
|
width=120,
|
|||
|
|
fg_color=["#ff6b6b", "#c0392b"],
|
|||
|
|
hover_color=["#e74c3c", "#a93226"]
|
|||
|
|
)
|
|||
|
|
self.clear_ddr_button.grid(row=0, column=1, padx=5, pady=10)
|
|||
|
|
|
|||
|
|
# 加载配置按钮
|
|||
|
|
self.load_button = ctk.CTkButton(
|
|||
|
|
self.control_frame,
|
|||
|
|
text="加载配置",
|
|||
|
|
command=self.load_config,
|
|||
|
|
width=100
|
|||
|
|
)
|
|||
|
|
self.load_button.grid(row=0, column=2, padx=5, pady=10)
|
|||
|
|
|
|||
|
|
# 保存配置按钮
|
|||
|
|
self.save_button = ctk.CTkButton(
|
|||
|
|
self.control_frame,
|
|||
|
|
text="保存配置",
|
|||
|
|
command=self.save_config,
|
|||
|
|
width=100
|
|||
|
|
)
|
|||
|
|
self.save_button.grid(row=0, column=3, padx=5, pady=10)
|
|||
|
|
|
|||
|
|
# 生成样本空间按钮
|
|||
|
|
self.generate_sample_button = ctk.CTkButton(
|
|||
|
|
self.control_frame,
|
|||
|
|
text="生成样本空间",
|
|||
|
|
command=self.generate_sample_space,
|
|||
|
|
width=120,
|
|||
|
|
fg_color="#3498db",
|
|||
|
|
hover_color="#2980b9"
|
|||
|
|
)
|
|||
|
|
self.generate_sample_button.grid(row=0, column=4, padx=5, pady=10)
|
|||
|
|
|
|||
|
|
# 生成探测器信号按钮(原开始模拟按钮)
|
|||
|
|
self.start_button = ctk.CTkButton(
|
|||
|
|
self.control_frame,
|
|||
|
|
text="生成探测器信号",
|
|||
|
|
command=self.start_simulation,
|
|||
|
|
width=140,
|
|||
|
|
fg_color="#2ecc71",
|
|||
|
|
hover_color="#27ae60"
|
|||
|
|
)
|
|||
|
|
self.start_button.grid(row=0, column=5, padx=10, pady=10)
|
|||
|
|
|
|||
|
|
# 停止模拟器按钮(黄色)- 硬件控制页面专用
|
|||
|
|
self.stop_simulator_button = ctk.CTkButton(
|
|||
|
|
self.control_frame,
|
|||
|
|
text="停止模拟器",
|
|||
|
|
command=self.stop_hardware_simulator,
|
|||
|
|
width=120,
|
|||
|
|
fg_color=["#f1c40f", "#f39c12"],
|
|||
|
|
hover_color=["#d4ac0d", "#d68910"]
|
|||
|
|
)
|
|||
|
|
self.stop_simulator_button.grid(row=0, column=6, padx=5, pady=10)
|
|||
|
|
|
|||
|
|
# 初始化变量
|
|||
|
|
self.simulator = None
|
|||
|
|
self.simulation_thread = None
|
|||
|
|
self.all_events = []
|
|||
|
|
self.data_writer = None
|
|||
|
|
self.is_closing = False
|
|||
|
|
self.sample_spaces = {} # 存储预生成的样本空间
|
|||
|
|
self.safe_after(0, self._update_result_preview_thread_safe)
|
|||
|
|
self.safe_after(0, lambda: self.start_button.configure(state="normal"))
|
|||
|
|
|
|||
|
|
# 初始化当前选项卡为硬件控制
|
|||
|
|
self._current_tab = "硬件控制"
|
|||
|
|
# 设置默认选中硬件控制选项卡
|
|||
|
|
self.tabview.set("硬件控制")
|
|||
|
|
# 更新底部按钮
|
|||
|
|
self._update_control_buttons()
|
|||
|
|
|
|||
|
|
def _on_tab_change(self):
|
|||
|
|
"""选项卡切换时的回调"""
|
|||
|
|
current_tab = self.tabview.get()
|
|||
|
|
self._current_tab = current_tab
|
|||
|
|
self._update_control_buttons()
|
|||
|
|
|
|||
|
|
def _update_control_buttons(self):
|
|||
|
|
"""根据当前选项卡更新控制按钮"""
|
|||
|
|
current_tab = self._current_tab
|
|||
|
|
|
|||
|
|
# 隐藏所有特定按钮
|
|||
|
|
self.generate_sample_button.grid_remove()
|
|||
|
|
self.start_button.grid_remove()
|
|||
|
|
self.clear_ddr_button.grid_remove()
|
|||
|
|
self.stop_simulator_button.grid_remove()
|
|||
|
|
|
|||
|
|
# 根据当前选项卡显示相应的按钮
|
|||
|
|
if current_tab == "探测器信号谱模拟":
|
|||
|
|
# 探测器信号谱模拟页面:加载配置、保存配置、生成样本空间、生成探测器信号
|
|||
|
|
self.generate_sample_button.configure(text="生成样本空间", command=self.generate_sample_space)
|
|||
|
|
self.generate_sample_button.grid(row=0, column=4, padx=5, pady=10)
|
|||
|
|
self.start_button.configure(text="生成探测器信号", command=self.start_simulation)
|
|||
|
|
self.start_button.grid(row=0, column=5, padx=10, pady=10)
|
|||
|
|
elif current_tab == "硬件控制":
|
|||
|
|
# 硬件控制页面:清除DDR内存、加载配置、保存配置、发送模拟数据、启动模拟器、停止模拟器
|
|||
|
|
self.clear_ddr_button.grid(row=0, column=1, padx=5, pady=10)
|
|||
|
|
self.generate_sample_button.configure(text="发送模拟数据", command=self.send_simulated_data)
|
|||
|
|
self.generate_sample_button.grid(row=0, column=4, padx=5, pady=10)
|
|||
|
|
self.start_button.configure(text="启动模拟器", command=self.start_hardware_simulator)
|
|||
|
|
self.start_button.grid(row=0, column=5, padx=10, pady=10)
|
|||
|
|
self.stop_simulator_button.grid(row=0, column=6, padx=5, pady=10)
|
|||
|
|
|
|||
|
|
def _setup_logging(self):
|
|||
|
|
"""配置日志"""
|
|||
|
|
# 获取根日志器
|
|||
|
|
root_logger = logging.getLogger()
|
|||
|
|
root_logger.setLevel(logging.INFO)
|
|||
|
|
|
|||
|
|
# 清除现有的处理器
|
|||
|
|
for handler in root_logger.handlers[:]:
|
|||
|
|
root_logger.removeHandler(handler)
|
|||
|
|
|
|||
|
|
# 添加队列处理器
|
|||
|
|
queue_handler = QueueHandler(log_queue)
|
|||
|
|
root_logger.addHandler(queue_handler)
|
|||
|
|
|
|||
|
|
# 启动队列监听器
|
|||
|
|
self.log_listener = QueueListener(log_queue, self._handle_log_record)
|
|||
|
|
self.log_listener.start()
|
|||
|
|
|
|||
|
|
def _handle_log_record(self, record):
|
|||
|
|
"""处理日志记录"""
|
|||
|
|
# 格式化日志消息
|
|||
|
|
import datetime
|
|||
|
|
current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|||
|
|
log_message = f"[{current_time}] {record.getMessage()}"
|
|||
|
|
|
|||
|
|
# 在主线程中更新日志文本框
|
|||
|
|
self.after(0, lambda msg=log_message: self._update_log_textbox(msg))
|
|||
|
|
|
|||
|
|
def _update_log_textbox(self, message):
|
|||
|
|
"""更新日志文本框"""
|
|||
|
|
if hasattr(self, 'log_textbox'):
|
|||
|
|
self.log_textbox.insert("end", message + "\n")
|
|||
|
|
self.log_textbox.see("end")
|
|||
|
|
|
|||
|
|
def update_status(self, message):
|
|||
|
|
"""更新状态栏"""
|
|||
|
|
self.status_var.set(message)
|
|||
|
|
|
|||
|
|
# 同时更新日志文本框
|
|||
|
|
if hasattr(self, 'log_textbox'):
|
|||
|
|
# 获取当前时间
|
|||
|
|
import datetime
|
|||
|
|
current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|||
|
|
# 格式化为日志条目
|
|||
|
|
log_entry = f"[{current_time}] {message}\n"
|
|||
|
|
# 插入到日志文本框
|
|||
|
|
self.log_textbox.insert("end", log_entry)
|
|||
|
|
# 自动滚动到底部
|
|||
|
|
self.log_textbox.see("end")
|
|||
|
|
|
|||
|
|
self.update_idletasks()
|
|||
|
|
|
|||
|
|
def generate_sample_space(self):
|
|||
|
|
"""生成样本空间"""
|
|||
|
|
# 禁用生成按钮
|
|||
|
|
self.generate_sample_button.configure(state="disabled")
|
|||
|
|
self.update_status("正在生成样本空间...")
|
|||
|
|
|
|||
|
|
# 获取配置
|
|||
|
|
full_config = self.get_config()
|
|||
|
|
|
|||
|
|
# 启动生成样本空间线程
|
|||
|
|
self.sample_space_thread = threading.Thread(target=self._generate_sample_space_thread, args=(full_config,))
|
|||
|
|
self.sample_space_thread.daemon = True
|
|||
|
|
self.sample_space_thread.start()
|
|||
|
|
|
|||
|
|
def _generate_sample_space_thread(self, full_config):
|
|||
|
|
"""在线程中生成样本空间"""
|
|||
|
|
try:
|
|||
|
|
from core.simulator import SampleSpaceGenerator
|
|||
|
|
sample_generator = SampleSpaceGenerator()
|
|||
|
|
|
|||
|
|
# 从完整配置中提取探测器配置
|
|||
|
|
detector_configs = full_config.get('detectors', {})
|
|||
|
|
|
|||
|
|
# 清空之前的样本空间
|
|||
|
|
self.sample_spaces = {}
|
|||
|
|
|
|||
|
|
# 为每个探测器生成样本空间
|
|||
|
|
for detector_name, detector_config in detector_configs.items():
|
|||
|
|
self.update_status(f"正在生成 {detector_name} 的样本空间...")
|
|||
|
|
detector_config_copy = detector_config.copy()
|
|||
|
|
detector_config_copy['name'] = detector_name
|
|||
|
|
self.sample_spaces[detector_name] = sample_generator.generate_sample_space(detector_config_copy)
|
|||
|
|
|
|||
|
|
self.update_status("样本空间生成完成!")
|
|||
|
|
|
|||
|
|
# 更新可视化预览
|
|||
|
|
if not self.is_closing:
|
|||
|
|
self.safe_after(0, self._update_preview_with_sample_space)
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
self.update_status(f"生成样本空间失败: {str(e)}")
|
|||
|
|
print(f"生成样本空间错误: {e}")
|
|||
|
|
finally:
|
|||
|
|
# 重新启用生成按钮
|
|||
|
|
if not self.is_closing:
|
|||
|
|
self.safe_after(0, lambda: self.generate_sample_button.configure(state="normal"))
|
|||
|
|
|
|||
|
|
def _update_preview_with_sample_space(self):
|
|||
|
|
"""使用生成的样本空间更新预览"""
|
|||
|
|
try:
|
|||
|
|
# 更新可视化预览
|
|||
|
|
basic_config, detector_configs = self.main_config_tab.get_config()
|
|||
|
|
self.result_preview_tab.update_chart_with_sample_space(self.sample_spaces, detector_configs)
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"更新预览失败: {e}")
|
|||
|
|
|
|||
|
|
def start_simulation(self):
|
|||
|
|
"""开始模拟(生成时间序列)"""
|
|||
|
|
# 禁用开始按钮
|
|||
|
|
self.start_button.configure(state="disabled")
|
|||
|
|
self.update_status("正在准备模拟...")
|
|||
|
|
|
|||
|
|
# 获取配置(返回字典:{simulation: {}, detectors: {}, hardware_control: {}})
|
|||
|
|
full_config = self.get_config()
|
|||
|
|
|
|||
|
|
# 启动模拟线程
|
|||
|
|
self.simulation_thread = threading.Thread(target=self._run_time_series_simulation, args=(full_config,))
|
|||
|
|
self.simulation_thread.daemon = True
|
|||
|
|
self.simulation_thread.start()
|
|||
|
|
|
|||
|
|
def _run_time_series_simulation(self, full_config):
|
|||
|
|
"""运行时间序列模拟"""
|
|||
|
|
try:
|
|||
|
|
from core.simulator import SampleSpaceGenerator, DetectorSampler, TimeSeriesGenerator
|
|||
|
|
from core.data_writer import DataWriterV4
|
|||
|
|
|
|||
|
|
# 从完整配置中提取基本配置和探测器配置
|
|||
|
|
config = full_config.get('simulation', {})
|
|||
|
|
detector_configs = full_config.get('detectors', {})
|
|||
|
|
|
|||
|
|
# 获取序列数量
|
|||
|
|
num_sequences = config.get('num_events', 1)
|
|||
|
|
|
|||
|
|
# 使用配置中的输出文件名(如果有),否则自动生成
|
|||
|
|
output_filename = config.get('output_file')
|
|||
|
|
|
|||
|
|
# 如果没有输出文件名,自动生成
|
|||
|
|
if not output_filename:
|
|||
|
|
work_mode = config.get('work_mode', 'CO')
|
|||
|
|
signal_type = config.get('signal_type', 0x0070)
|
|||
|
|
|
|||
|
|
# 编码信号类型
|
|||
|
|
signal_code = 'V' if signal_type == 0x0070 else 'C'
|
|||
|
|
|
|||
|
|
# 编码同步脉冲
|
|||
|
|
sync_pulses = config.get('sync_pulses', [])
|
|||
|
|
pulse_codes = []
|
|||
|
|
for pulse in sync_pulses:
|
|||
|
|
count = pulse.get('count', 0)
|
|||
|
|
period = pulse.get('period', 0)
|
|||
|
|
if count > 0:
|
|||
|
|
pulse_codes.append(f"{count}x{period}")
|
|||
|
|
|
|||
|
|
pulse_str = '_'.join(pulse_codes) if pulse_codes else '0x0'
|
|||
|
|
|
|||
|
|
# 生成文件名: {工作模式}{信号类型}_{脉冲配置}
|
|||
|
|
output_filename = f"{work_mode}{signal_code}_{pulse_str}"
|
|||
|
|
config['output_file'] = output_filename
|
|||
|
|
|
|||
|
|
# 更新UI中的输出文件名显示
|
|||
|
|
if not self.is_closing:
|
|||
|
|
self.safe_after(0, lambda: self._update_output_filename_display(output_filename))
|
|||
|
|
|
|||
|
|
# 1. 生成样本空间
|
|||
|
|
self.update_status("正在生成样本空间...")
|
|||
|
|
sample_generator = SampleSpaceGenerator()
|
|||
|
|
sample_spaces = {}
|
|||
|
|
samplers = {}
|
|||
|
|
|
|||
|
|
for detector_name, detector_config in detector_configs.items():
|
|||
|
|
self.update_status(f"正在生成 {detector_name} 的样本空间...")
|
|||
|
|
detector_config_copy = detector_config.copy()
|
|||
|
|
detector_config_copy['name'] = detector_name
|
|||
|
|
sample_spaces[detector_name] = sample_generator.generate_sample_space(detector_config_copy)
|
|||
|
|
|
|||
|
|
# 创建采样器
|
|||
|
|
allow_replacement = detector_config.get('allow_replacement', True)
|
|||
|
|
samplers[detector_name] = DetectorSampler(sample_spaces[detector_name], allow_replacement)
|
|||
|
|
|
|||
|
|
self.sample_spaces = sample_spaces
|
|||
|
|
|
|||
|
|
# 2. 生成时间序列
|
|||
|
|
self.update_status("正在生成时间序列...")
|
|||
|
|
|
|||
|
|
work_mode = config.get('work_mode', 'CO')
|
|||
|
|
sync_pulses = config.get('sync_pulses', [])
|
|||
|
|
energy_K = config.get('energy_conversion', {}).get('K', 1.0)
|
|||
|
|
energy_B = config.get('energy_conversion', {}).get('B', 0.0)
|
|||
|
|
|
|||
|
|
# 获取同步脉冲探测器信号数配置
|
|||
|
|
sync_pulse_signals = config.get('sync_pulse_signals', {})
|
|||
|
|
|
|||
|
|
time_series_generator = TimeSeriesGenerator(
|
|||
|
|
work_mode=work_mode,
|
|||
|
|
sync_pulses=sync_pulses,
|
|||
|
|
sample_spaces=sample_spaces,
|
|||
|
|
samplers=samplers,
|
|||
|
|
detectors=detector_configs,
|
|||
|
|
energy_K=energy_K,
|
|||
|
|
energy_B=energy_B,
|
|||
|
|
sync_pulse_signals=sync_pulse_signals
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 将config包装为DataWriterV4需要的格式
|
|||
|
|
writer_config = {
|
|||
|
|
"simulation": config,
|
|||
|
|
"detectors": detector_configs
|
|||
|
|
}
|
|||
|
|
data_writer = DataWriterV4(writer_config)
|
|||
|
|
|
|||
|
|
sync_pulse1_count = sync_pulses[0]['count'] if len(sync_pulses) > 0 else 0
|
|||
|
|
sync_pulse1_period = sync_pulses[0]['period'] if len(sync_pulses) > 0 else 0
|
|||
|
|
sync_pulse2_count = sync_pulses[1]['count'] if len(sync_pulses) > 1 else 0
|
|||
|
|
sync_pulse2_period = sync_pulses[1]['period'] if len(sync_pulses) > 1 else 0
|
|||
|
|
sync_pulse3_count = sync_pulses[2]['count'] if len(sync_pulses) > 2 else 0
|
|||
|
|
sync_pulse3_period = sync_pulses[2]['period'] if len(sync_pulses) > 2 else 0
|
|||
|
|
sync_pulse4_count = sync_pulses[3]['count'] if len(sync_pulses) > 3 else 0
|
|||
|
|
sync_pulse4_period = sync_pulses[3]['period'] if len(sync_pulses) > 3 else 0
|
|||
|
|
|
|||
|
|
signal_type = config.get('signal_type', 0x0070)
|
|||
|
|
|
|||
|
|
# 生成多个序列并合并到一个文件
|
|||
|
|
self.update_status(f"正在生成 {num_sequences} 个序列...")
|
|||
|
|
|
|||
|
|
# 收集所有序列的编码信号
|
|||
|
|
all_encoded_signals = []
|
|||
|
|
total_duration = 0
|
|||
|
|
|
|||
|
|
for sequence_idx in range(num_sequences):
|
|||
|
|
self.update_status(f"正在生成序列 {sequence_idx + 1}/{num_sequences}...")
|
|||
|
|
|
|||
|
|
# 生成时间序列
|
|||
|
|
time_series = time_series_generator.generate_time_series()
|
|||
|
|
|
|||
|
|
# 编码时间序列(包含事件结束标志)
|
|||
|
|
encoded_signals = time_series_generator.encode_time_series(time_series)
|
|||
|
|
|
|||
|
|
# 保存最后一个时间序列用于可视化
|
|||
|
|
if sequence_idx == num_sequences - 1:
|
|||
|
|
self.current_time_series = time_series
|
|||
|
|
|
|||
|
|
# 将当前序列的信号添加到总列表
|
|||
|
|
all_encoded_signals.extend(encoded_signals)
|
|||
|
|
|
|||
|
|
# 累加总时长
|
|||
|
|
total_duration += time_series.get('total_duration', 0)
|
|||
|
|
|
|||
|
|
# 计算总信号数(原始数据,未补零)
|
|||
|
|
total_signals = len(all_encoded_signals)
|
|||
|
|
|
|||
|
|
# 写入数据文件
|
|||
|
|
self.update_status(f"正在写入数据文件...")
|
|||
|
|
|
|||
|
|
data_writer.write_time_series_with_instructions(
|
|||
|
|
time_series={"detector1": [], "detector2": [], "detector3": [], "total_duration": total_duration},
|
|||
|
|
output_file=output_filename + ".bin",
|
|||
|
|
signal_type=signal_type,
|
|||
|
|
simulation_mode=config.get('simulation_mode', 'CO'),
|
|||
|
|
sync_pulse1_count=sync_pulse1_count,
|
|||
|
|
sync_pulse1_period=sync_pulse1_period,
|
|||
|
|
sync_pulse2_count=sync_pulse2_count,
|
|||
|
|
sync_pulse2_period=sync_pulse2_period,
|
|||
|
|
sync_pulse3_count=sync_pulse3_count,
|
|||
|
|
sync_pulse3_period=sync_pulse3_period,
|
|||
|
|
sync_pulse4_count=sync_pulse4_count,
|
|||
|
|
sync_pulse4_period=sync_pulse4_period,
|
|||
|
|
encoded_signals=all_encoded_signals
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 更新结果预览
|
|||
|
|
if not self.is_closing:
|
|||
|
|
self.safe_after(0, self._update_time_series_preview_thread_safe)
|
|||
|
|
|
|||
|
|
self.update_status(f"模拟完成! 共生成 {num_sequences} 个序列")
|
|||
|
|
except Exception as e:
|
|||
|
|
self.update_status(f"模拟失败: {str(e)}")
|
|||
|
|
print(f"模拟错误: {e}")
|
|||
|
|
import traceback
|
|||
|
|
traceback.print_exc()
|
|||
|
|
finally:
|
|||
|
|
# 重新启用开始按钮
|
|||
|
|
if not self.is_closing:
|
|||
|
|
self.safe_after(0, self.enable_start_button)
|
|||
|
|
|
|||
|
|
def _update_time_series_preview_thread_safe(self):
|
|||
|
|
"""线程安全地更新时间序列预览(确保在主线程中执行)"""
|
|||
|
|
try:
|
|||
|
|
self._update_time_series_preview()
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"更新时间序列预览失败: {e}")
|
|||
|
|
|
|||
|
|
def _update_time_series_preview(self):
|
|||
|
|
"""更新时间序列预览"""
|
|||
|
|
# 获取时间序列数据和探测器配置
|
|||
|
|
time_series = getattr(self, 'current_time_series', None)
|
|||
|
|
basic_config, detector_configs = self.main_config_tab.get_config()
|
|||
|
|
|
|||
|
|
if time_series:
|
|||
|
|
# 更新图表 - 传入时间序列数据和探测器配置
|
|||
|
|
self.result_preview_tab.update_chart_with_time_series(time_series, detector_configs)
|
|||
|
|
|
|||
|
|
# 计算并输出统计信息到日志
|
|||
|
|
total_signals = sum(len(time_series[det]) for det in ["detector1", "detector2", "detector3"])
|
|||
|
|
self.update_status(f"时间序列生成完成,总信号数: {total_signals}")
|
|||
|
|
|
|||
|
|
# 验证文件并输出到日志
|
|||
|
|
output_file = basic_config["output_file"] + ".bin"
|
|||
|
|
|
|||
|
|
if Path(output_file).exists():
|
|||
|
|
size = Path(output_file).stat().st_size
|
|||
|
|
self.update_status(f"输出文件已生成: {output_file} ({size} 字节)")
|
|||
|
|
|
|||
|
|
def _update_result_preview_thread_safe(self):
|
|||
|
|
"""线程安全地更新结果预览(确保在主线程中执行)"""
|
|||
|
|
try:
|
|||
|
|
self.update_result_preview()
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"更新结果预览失败: {e}")
|
|||
|
|
|
|||
|
|
def update_result_preview(self):
|
|||
|
|
"""更新结果预览"""
|
|||
|
|
if not self.all_events:
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
# 计算统计信息
|
|||
|
|
total_events = len(self.all_events)
|
|||
|
|
total_signals = sum(len(event) for event in self.all_events)
|
|||
|
|
valid_signals = sum(sum(1 for sig in event if sig != 0) for event in self.all_events)
|
|||
|
|
avg_signals = total_signals / total_events if total_events > 0 else 0
|
|||
|
|
valid_rate = (valid_signals / total_signals * 100) if total_signals > 0 else 0
|
|||
|
|
|
|||
|
|
# 将统计信息输出到日志
|
|||
|
|
stats_message = f"统计信息: 总事件数={total_events}, 总信号数={total_signals}, 有效信号数={valid_signals}, 平均每事件={avg_signals:.1f}个信号, 有效率={valid_rate:.1f}%"
|
|||
|
|
self.update_status(stats_message)
|
|||
|
|
|
|||
|
|
# 更新图表
|
|||
|
|
_, detector_configs = self.main_config_tab.get_config()
|
|||
|
|
self.result_preview_tab.update_chart(self.all_events, detector_configs)
|
|||
|
|
|
|||
|
|
# 验证文件并更新左侧面板文件列表
|
|||
|
|
basic_config, _ = self.main_config_tab.get_config()
|
|||
|
|
output_file = basic_config["output_file"]
|
|||
|
|
files = self.result_preview_tab.verify_files(output_file)
|
|||
|
|
|
|||
|
|
# 更新左侧面板文件列表
|
|||
|
|
self.file_list.configure(state="normal")
|
|||
|
|
self.file_list.delete(1.0, "end")
|
|||
|
|
for file_info in files:
|
|||
|
|
filename = file_info.get("name", "")
|
|||
|
|
size = file_info.get("size", 0)
|
|||
|
|
status = "✓" if file_info.get("exists", False) else "✗"
|
|||
|
|
self.file_list.insert("end", f"{status} {filename} ({size} 字节)\n")
|
|||
|
|
self.file_list.configure(state="disabled")
|
|||
|
|
|
|||
|
|
def load_config(self):
|
|||
|
|
"""加载配置文件"""
|
|||
|
|
filename = filedialog.askopenfilename(
|
|||
|
|
filetypes=[("YAML files", "*.yaml"), ("YAML files", "*.yml"), ("All files", "*.*")]
|
|||
|
|
)
|
|||
|
|
if filename:
|
|||
|
|
try:
|
|||
|
|
config = ConfigManager.load_config(filename)
|
|||
|
|
self.set_config(config)
|
|||
|
|
self.update_status(f"配置已加载: {filename}")
|
|||
|
|
except Exception as e:
|
|||
|
|
self.update_status(f"加载配置失败: {str(e)}")
|
|||
|
|
|
|||
|
|
def save_config(self):
|
|||
|
|
"""保存配置文件"""
|
|||
|
|
filename = filedialog.asksaveasfilename(
|
|||
|
|
defaultextension=".yaml",
|
|||
|
|
filetypes=[("YAML files", "*.yaml"), ("YAML files", "*.yml"), ("All files", "*.*")]
|
|||
|
|
)
|
|||
|
|
if filename:
|
|||
|
|
try:
|
|||
|
|
config = self.get_config()
|
|||
|
|
ConfigManager.save_config(config, filename)
|
|||
|
|
self.update_status(f"配置已保存: {filename}")
|
|||
|
|
except Exception as e:
|
|||
|
|
self.update_status(f"保存配置失败: {str(e)}")
|
|||
|
|
|
|||
|
|
def get_config(self):
|
|||
|
|
"""获取当前配置"""
|
|||
|
|
basic_config, detector_configs = self.main_config_tab.get_config()
|
|||
|
|
|
|||
|
|
config = {
|
|||
|
|
"simulation": basic_config,
|
|||
|
|
"detectors": detector_configs
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 添加硬件控制配置
|
|||
|
|
if hasattr(self, 'hardware_control_tab') and self.hardware_control_tab:
|
|||
|
|
config["hardware_control"] = self.hardware_control_tab.get_config()
|
|||
|
|
|
|||
|
|
return config
|
|||
|
|
|
|||
|
|
def send_simulated_data(self):
|
|||
|
|
"""发送模拟数据(硬件控制页面)"""
|
|||
|
|
if hasattr(self, 'hardware_control_tab') and self.hardware_control_tab:
|
|||
|
|
# 调用硬件控制选项卡的发送数据方法
|
|||
|
|
self.hardware_control_tab.send_data()
|
|||
|
|
else:
|
|||
|
|
self.update_status("硬件控制选项卡未初始化")
|
|||
|
|
|
|||
|
|
def start_hardware_simulator(self):
|
|||
|
|
"""启动模拟器(硬件控制页面)"""
|
|||
|
|
if hasattr(self, 'hardware_control_tab') and self.hardware_control_tab:
|
|||
|
|
# 连接到设备并启动
|
|||
|
|
if not self.hardware_control_tab.is_connected:
|
|||
|
|
self.hardware_control_tab.connect_to_device()
|
|||
|
|
if self.hardware_control_tab.is_connected:
|
|||
|
|
self.hardware_control_tab.start_device()
|
|||
|
|
self.update_status("模拟器已启动")
|
|||
|
|
else:
|
|||
|
|
self.update_status("硬件控制选项卡未初始化")
|
|||
|
|
|
|||
|
|
def stop_hardware_simulator(self):
|
|||
|
|
"""停止模拟器(硬件控制页面)"""
|
|||
|
|
if hasattr(self, 'hardware_control_tab') and self.hardware_control_tab:
|
|||
|
|
if self.hardware_control_tab.is_connected:
|
|||
|
|
self.hardware_control_tab.stop_device()
|
|||
|
|
self.update_status("模拟器已停止")
|
|||
|
|
else:
|
|||
|
|
self.update_status("硬件控制选项卡未初始化")
|
|||
|
|
|
|||
|
|
def clear_ddr_memory(self):
|
|||
|
|
"""清除DDR内存(硬件控制页面)"""
|
|||
|
|
if hasattr(self, 'hardware_control_tab') and self.hardware_control_tab:
|
|||
|
|
if self.hardware_control_tab.is_connected:
|
|||
|
|
self.hardware_control_tab.clear_ddr_memory()
|
|||
|
|
self.update_status("DDR内存已清除")
|
|||
|
|
else:
|
|||
|
|
self.update_status("请先连接设备")
|
|||
|
|
else:
|
|||
|
|
self.update_status("硬件控制选项卡未初始化")
|
|||
|
|
|
|||
|
|
def safe_after(self, ms, func):
|
|||
|
|
"""安全的after方法,记录任务ID"""
|
|||
|
|
if self.is_closing:
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
def wrapper():
|
|||
|
|
if not self.is_closing:
|
|||
|
|
try:
|
|||
|
|
func()
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"执行after任务失败: {e}")
|
|||
|
|
# 从任务列表中删除
|
|||
|
|
if aid in self.after_tasks:
|
|||
|
|
self.after_tasks.remove(aid)
|
|||
|
|
|
|||
|
|
aid = self.after(ms, wrapper)
|
|||
|
|
self.after_tasks.append(aid)
|
|||
|
|
return aid
|
|||
|
|
|
|||
|
|
def enable_start_button(self):
|
|||
|
|
"""安全地启用开始按钮"""
|
|||
|
|
if not self.is_closing and self.winfo_exists():
|
|||
|
|
self.start_button.configure(state="normal")
|
|||
|
|
|
|||
|
|
def _update_output_filename_display(self, filename):
|
|||
|
|
"""更新输出文件名显示"""
|
|||
|
|
if hasattr(self, 'main_config_tab') and hasattr(self.main_config_tab, 'output_file_var'):
|
|||
|
|
self.main_config_tab.output_file_var.set(filename)
|
|||
|
|
|
|||
|
|
def set_config(self, config):
|
|||
|
|
"""设置配置"""
|
|||
|
|
simulation_config = config.get("simulation", config)
|
|||
|
|
detector_configs = config.get("detectors", {})
|
|||
|
|
self.main_config_tab.set_config(simulation_config, detector_configs)
|
|||
|
|
|
|||
|
|
def load_default_config(self):
|
|||
|
|
"""加载默认配置"""
|
|||
|
|
from utils.config_manager import ConfigManager
|
|||
|
|
config = ConfigManager.load_default_config()
|
|||
|
|
if config:
|
|||
|
|
# 恢复窗口位置和大小
|
|||
|
|
if "window" in config:
|
|||
|
|
window_config = config["window"]
|
|||
|
|
if "x" in window_config and "y" in window_config:
|
|||
|
|
self.geometry(f"{window_config.get('width', 1400)}x{window_config.get('height', 900)}+{window_config['x']}+{window_config['y']}")
|
|||
|
|
else:
|
|||
|
|
self.geometry(f"{window_config.get('width', 1400)}x{window_config.get('height', 900)}")
|
|||
|
|
|
|||
|
|
# 恢复窗口最大化状态(使用after延迟,确保窗口完全初始化)
|
|||
|
|
if window_config.get("maximized", False):
|
|||
|
|
def _maximize_window():
|
|||
|
|
try:
|
|||
|
|
self.state("zoomed") # Windows系统
|
|||
|
|
except:
|
|||
|
|
try:
|
|||
|
|
self.attributes("-zoomed", True) # 其他系统
|
|||
|
|
except:
|
|||
|
|
pass
|
|||
|
|
self.after(100, _maximize_window)
|
|||
|
|
|
|||
|
|
# 恢复模拟配置
|
|||
|
|
if "simulation" in config or "detectors" in config:
|
|||
|
|
simulation_config = config.get("simulation", {})
|
|||
|
|
detector_configs = config.get("detectors", {})
|
|||
|
|
self.main_config_tab.set_config(simulation_config, detector_configs)
|
|||
|
|
|
|||
|
|
# 恢复硬件控制配置
|
|||
|
|
if "hardware_control" in config:
|
|||
|
|
hardware_config = config.get("hardware_control", {})
|
|||
|
|
if hasattr(self, 'hardware_control_tab') and self.hardware_control_tab:
|
|||
|
|
self.hardware_control_tab.set_config(hardware_config)
|
|||
|
|
|
|||
|
|
def on_closing(self):
|
|||
|
|
"""窗口关闭时的处理"""
|
|||
|
|
self.is_closing = True
|
|||
|
|
|
|||
|
|
# 停止状态栏的时间更新
|
|||
|
|
if hasattr(self, 'status_bar'):
|
|||
|
|
self.status_bar.stop()
|
|||
|
|
|
|||
|
|
# 取消所有after任务
|
|||
|
|
for aid in self.after_tasks:
|
|||
|
|
try:
|
|||
|
|
self.after_cancel(aid)
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"取消 after 任务失败: {e}")
|
|||
|
|
|
|||
|
|
# 保存默认配置
|
|||
|
|
try:
|
|||
|
|
self.save_default_config()
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"保存默认配置失败: {e}")
|
|||
|
|
|
|||
|
|
# 立即销毁窗口
|
|||
|
|
self.destroy()
|
|||
|
|
|
|||
|
|
def save_default_config(self):
|
|||
|
|
"""保存默认配置"""
|
|||
|
|
from utils.config_manager import ConfigManager
|
|||
|
|
print("开始保存默认配置...")
|
|||
|
|
# 获取当前配置
|
|||
|
|
config = self.get_config()
|
|||
|
|
print(f"获取到配置: {config.keys()}")
|
|||
|
|
# 添加窗口位置和大小
|
|||
|
|
geometry = self.geometry()
|
|||
|
|
print(f"窗口几何信息: {geometry}")
|
|||
|
|
if geometry:
|
|||
|
|
# 解析几何信息: widthxheight+x+y
|
|||
|
|
parts = geometry.split('+')
|
|||
|
|
size_part = parts[0]
|
|||
|
|
width, height = map(int, size_part.split('x'))
|
|||
|
|
x, y = 0, 0
|
|||
|
|
if len(parts) > 1:
|
|||
|
|
x = int(parts[1])
|
|||
|
|
if len(parts) > 2:
|
|||
|
|
y = int(parts[2])
|
|||
|
|
# 检查窗口是否最大化
|
|||
|
|
is_maximized = False
|
|||
|
|
try:
|
|||
|
|
# 尝试获取窗口状态
|
|||
|
|
is_maximized = self.state() == "zoomed" # Windows系统
|
|||
|
|
except:
|
|||
|
|
try:
|
|||
|
|
is_maximized = self.attributes("-zoomed") # 其他系统
|
|||
|
|
except:
|
|||
|
|
pass
|
|||
|
|
config["window"] = {
|
|||
|
|
"width": width,
|
|||
|
|
"height": height,
|
|||
|
|
"x": x,
|
|||
|
|
"y": y,
|
|||
|
|
"maximized": is_maximized
|
|||
|
|
}
|
|||
|
|
print(f"添加窗口配置: {config['window']}")
|
|||
|
|
# 保存默认配置
|
|||
|
|
try:
|
|||
|
|
print(f"保存配置到: {ConfigManager.DEFAULT_CONFIG_FILE}")
|
|||
|
|
ConfigManager.save_default_config(config)
|
|||
|
|
print("保存默认配置成功")
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"保存默认配置失败: {e}")
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
app = MainWindow()
|
|||
|
|
app.protocol("WM_DELETE_WINDOW", app.on_closing)
|
|||
|
|
try:
|
|||
|
|
app.mainloop()
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"主循环发生错误: {e}")
|