refactor: 引入 EventBus 事件总线,实现各步骤面板间的去中心化自动参数传导,完成最终解耦

This commit is contained in:
DXC
2026-06-17 16:27:26 +08:00
parent a58744cfbb
commit bb5c2a50f8
5 changed files with 220 additions and 120 deletions

View File

@ -124,6 +124,8 @@ from src.gui.core.panel_registry import (
get_step_id_by_tab_index,
get_entry,
)
from src.gui.core.event_bus import global_event_bus
from src.gui.core.dependency_subscriber import subscribe_panel_to_dependencies
from src.gui.dialogs import BandConfirmDialog, AISettingsDialog
# Pipeline 核心异常(用于预检弹窗)
@ -155,6 +157,22 @@ from src.gui.core.pipeline_mode_dialog import PipelineModeDialog
from src.gui.core.viz_thread import VisualizationWorkerThread, _viz_training_spectra_csv_path
from src.core.workspace_manager import WorkspaceManager
# pipeline step_id → panel step_id 映射pipeline 内部编号与 GUI 面板编号不同)
PIPELINE_TO_PANEL_STEP = {
'step1': 'step1',
'step2': 'step2',
'step3': 'step3',
'step4': 'step5_clean',
'step5': 'step6_feature',
'step7': 'step7_index',
'step8': 'step8_ml_train',
'step9': 'step10_watercolor',
'step10': 'step4_sampling',
'step11_ml': 'step9_ml_predict',
'step11': 'step11_map',
'step14': 'step11_map',
}
class WaterQualityGUI(QMainWindow):
"""水质参数反演分析系统主窗口"""
@ -181,6 +199,7 @@ class WaterQualityGUI(QMainWindow):
# 工作空间管理器(文件扫描、路径发现、配置裁剪)
self.workspace_manager = WorkspaceManager()
self.workspace_manager.set_step_id_mapping(PIPELINE_TO_PANEL_STEP)
# 面板实例字典step_id → panel instance由 create_content_area 填充
self._panels = {}
@ -669,6 +688,11 @@ class WaterQualityGUI(QMainWindow):
QIcon(self.get_icon_path(icon_name)),
title
)
# ★ 事件驱动:面板根据注册表依赖自动订阅 OutputUpdated 事件
deps = entry.get('dependencies')
if deps:
subscribe_panel_to_dependencies(panel, step_id, deps)
# 连接Tab切换信号实现双向同步必须在step_stack创建后
self.step_stack.currentChanged.connect(self.on_tab_changed)
@ -809,8 +833,6 @@ class WaterQualityGUI(QMainWindow):
tab_index = get_tab_index(item_data)
if tab_index >= 0:
self.step_stack.setCurrentIndex(tab_index)
# 切换到步骤时自动填充输入路径
self.auto_populate_step_inputs(item_data)
def on_tab_changed(self, index):
"""Tab页面切换时同步更新左侧步骤列表"""
@ -911,88 +933,38 @@ class WaterQualityGUI(QMainWindow):
return config
def auto_populate_step_inputs(self, step_id):
"""自动填充指定步骤的输入路径,返回填充的字段数量"""
if step_id not in self.step_dependencies:
return 0 # 该步骤没有依赖关系
# 获取对应的面板
panel = self.get_step_panel(step_id)
if not panel:
return 0
work_dir = getattr(self, 'work_dir', './work_dir')
work_path = Path(work_dir)
dependencies = self.step_dependencies[step_id]
filled_count = 0
ref_img_path = None
step1_panel = self._panels.get('step1')
if step1_panel and hasattr(step1_panel, 'img_file'):
ref_img_path = step1_panel.img_file.get_path()
for input_field, (dep_step, output_type, panel_attr) in dependencies.items():
# 检查面板是否有对应的属性
if not hasattr(panel, panel_attr):
continue
file_widget = getattr(panel, panel_attr)
# ★ 兼容 FileSelectWidget 与原生 QLineEdit
current_text = (
file_widget.get_path().strip()
if hasattr(file_widget, 'get_path')
else file_widget.text().strip()
)
# 如果输入框已经有内容,跳过自动填充
if current_text:
continue
# 查找依赖步骤的输出文件
output_path = self.workspace_manager.find_step_output(work_path, dep_step, output_type, ref_img_path=ref_img_path)
if output_path and Path(output_path).exists():
# ★ 兼容 FileSelectWidget 与原生 QLineEdit
if hasattr(file_widget, 'set_path'):
file_widget.set_path(str(output_path))
else:
file_widget.setText(str(output_path))
self.log_message(f"自动填充 {step_id}.{input_field}: {output_path}", "info")
filled_count += 1
return filled_count
"""自动填充指定步骤的输入路径(事件总线已接管,保留接口兼容)。"""
return 0
def get_step_panel(self, step_id):
"""根据步骤ID获取对应的面板对象从动态注册表查找"""
return self._panels.get(step_id)
def auto_populate_all_steps(self):
"""自动填充所有步骤的输入路径"""
"""扫描工作目录并触发事件总线自动填充所有步骤的输入路径"""
work_dir = getattr(self, 'work_dir', './work_dir')
work_path = Path(work_dir)
if not work_path.exists():
QMessageBox.warning(self, "警告", f"工作目录不存在: {work_dir}\n请先设置正确的工作目录。")
return
# 首先扫描工作目录发现已有的输出文件
# 扫描工作目录 → WorkspaceManager 发布 OutputUpdated 事件 → 面板自动填充
self.workspace_manager.scan_work_directory_for_files(work_path)
# 从注册表推导步骤顺序(跳过 step1从 step2 开始
step_order = [e['step_id'] for e in PANEL_REGISTRY if e['step_id'] != 'step1']
filled_count = 0
for step_id in step_order:
old_count = filled_count
filled_count += self.auto_populate_step_inputs(step_id)
if filled_count > 0:
self.log_message(f"已完成所有步骤的自动路径填充,共填充 {filled_count} 个输入字段", "info")
QMessageBox.information(self, "完成", f"自动填充完成!\n共填充了 {filled_count} 个输入字段。")
else:
self.log_message("未发现可自动填充的路径", "info")
QMessageBox.information(self, "完成", "未发现可自动填充的路径。\n请确保工作目录中有相关的输出文件。")
# 补充发布 reference_imgstep1 的输入影像,非 step1 产出但下游依赖它
step1_panel = self._panels.get('step1')
if step1_panel and hasattr(step1_panel, 'img_file'):
ref_img = step1_panel.img_file.get_path()
if ref_img:
global_event_bus.publish('OutputUpdated', {
'step_id': 'step1',
'output_type': 'reference_img',
'path': ref_img,
})
self.log_message("✓ 工作目录扫描完成,事件总线已通知所有面板自动填充", "info")
QMessageBox.information(self, "完成", "工作目录扫描完成!\n各步骤输入路径已自动填充。")
def add_auto_fill_buttons_to_panels(self):
"""为各个步骤面板添加自动填充按钮(动态遍历所有面板)"""
@ -1409,46 +1381,19 @@ class WaterQualityGUI(QMainWindow):
QMessageBox.critical(self, "失败", f"流程执行失败:\n\n{message[:200]}")
def on_step_completed(self, step_name, success, message):
"""步骤完成回调:记录输出路径并更新后续步骤"""
"""步骤完成回调:记录输出路径WorkspaceManager 自动发布 EventBus 事件。"""
if not success:
return
# 记录步骤输出路径到内存
work_dir = getattr(self, 'work_dir', './work_dir')
work_path = Path(work_dir)
# 根据步骤名称和约定路径,记录实际输出
if step_name not in self.workspace_manager.step_outputs:
self.workspace_manager.step_outputs[step_name] = {}
# 扫描工作目录,更新该步骤的输出路径
# WorkspaceManager.update_step_outputs 内部会发布 OutputUpdated 事件
# 下游面板通过 DependencySubscriber 自动接收并填充
self.workspace_manager.update_step_outputs(step_name, work_path)
# 自动填充依赖该步骤输出的后续步骤
self.auto_populate_dependent_steps(step_name)
def auto_populate_dependent_steps(self, completed_step):
"""自动填充依赖于已完成步骤的后续步骤"""
ref_img_path = None
step1_panel = self._panels.get('step1')
if step1_panel and hasattr(step1_panel, 'img_file'):
ref_img_path = step1_panel.img_file.get_path()
for step_id, dependencies in self.step_dependencies.items():
for input_field, (dep_step, output_type, panel_attr) in dependencies.items():
if dep_step == completed_step:
# 找到依赖于刚完成步骤的后续步骤,尝试自动填充
panel = self.get_step_panel(step_id)
if panel and hasattr(panel, panel_attr):
file_widget = getattr(panel, panel_attr)
# 如果输入框为空,则自动填充
if not file_widget.get_path().strip():
work_dir = getattr(self, 'work_dir', './work_dir')
work_path = Path(work_dir)
output_path = self.workspace_manager.find_step_output(work_path, dep_step, output_type, ref_img_path=ref_img_path)
if output_path and Path(output_path).exists():
file_widget.set_path(output_path)
self.log_message(f"步骤完成后自动填充 {step_id}.{input_field}: {output_path}", "info")
def run_single_step(self, step_name, config):
"""运行单个步骤"""