1.2.1
This commit is contained in:
@ -67,8 +67,7 @@ docs = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
water-quality-gui = "gui.water_quality_gui:main"
|
water-quality-gui = "src.gui.water_quality_gui_v2:main"
|
||||||
water-quality-pipeline = "core.water_quality_inversion_pipeline:main"
|
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
Homepage = "https://github.com/waterquality/water-quality-inversion"
|
Homepage = "https://github.com/waterquality/water-quality-inversion"
|
||||||
|
|||||||
@ -2,12 +2,12 @@
|
|||||||
chcp 65001 >nul
|
chcp 65001 >nul
|
||||||
echo.
|
echo.
|
||||||
echo ================================================
|
echo ================================================
|
||||||
echo 水质参数反演分析系统 - 打包工具
|
echo 水质参数反演分析系统 - 打包工具 (V2)
|
||||||
echo ================================================
|
echo ================================================
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
:: 检查是否在正确目录
|
:: 检查是否在正确目录 (V2 入口)
|
||||||
if not exist "src\gui\water_quality_gui.py" (
|
if not exist "src\gui\water_quality_gui_v2.py" (
|
||||||
echo [错误] 请在项目根目录下运行此脚本!
|
echo [错误] 请在项目根目录下运行此脚本!
|
||||||
pause
|
pause
|
||||||
exit /b 1
|
exit /b 1
|
||||||
@ -22,19 +22,14 @@ python -m pip install -r requirements.txt --quiet
|
|||||||
python -m pip install pyinstaller --quiet
|
python -m pip install pyinstaller --quiet
|
||||||
|
|
||||||
echo [3/4] 开始打包(首次可能需要 5-15 分钟,请耐心等待)...
|
echo [3/4] 开始打包(首次可能需要 5-15 分钟,请耐心等待)...
|
||||||
pyinstaller --clean scripts/water_quality_gui.spec
|
pyinstaller --clean water_quality_app.spec
|
||||||
|
|
||||||
echo.
|
|
||||||
echo [打包提示] 如果仍然出现 "No module named 'styles'",请检查:
|
|
||||||
echo 1. dist\water_quality_gui\styles.py 是否存在
|
|
||||||
echo 2. 是否需要添加 --collect-all styles 参数
|
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo [4/4] 打包完成!
|
echo [4/4] 打包完成!
|
||||||
echo.
|
echo.
|
||||||
echo 输出位置:
|
echo 输出位置:
|
||||||
echo dist\water_quality_gui\water_quality_gui.exe
|
echo dist\MegaWater\MegaWater.exe
|
||||||
echo dist\water_quality_gui\_internal\
|
echo dist\MegaWater\_internal\
|
||||||
echo.
|
echo.
|
||||||
echo 建议:
|
echo 建议:
|
||||||
echo 1. 将 dist 文件夹整个复制给用户(包含所有依赖)
|
echo 1. 将 dist 文件夹整个复制给用户(包含所有依赖)
|
||||||
|
|||||||
2
setup.py
2
setup.py
@ -68,7 +68,7 @@ setup(
|
|||||||
},
|
},
|
||||||
entry_points={
|
entry_points={
|
||||||
"console_scripts": [
|
"console_scripts": [
|
||||||
"water-quality-gui=gui.water_quality_gui:main",
|
"water-quality-gui=src.gui.water_quality_gui_v2:main",
|
||||||
"water-quality-pipeline=core.water_quality_inversion_pipeline:main",
|
"water-quality-pipeline=core.water_quality_inversion_pipeline:main",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@ -266,6 +266,13 @@ class PanelFactory:
|
|||||||
# ★ 重新禁用滚轮:update_from_config 可能动态创建了新的 SpinBox/ComboBox
|
# ★ 重新禁用滚轮:update_from_config 可能动态创建了新的 SpinBox/ComboBox
|
||||||
_disable_wheel_for_widget(panel)
|
_disable_wheel_for_widget(panel)
|
||||||
|
|
||||||
|
def replay_live_panel_inputs(self):
|
||||||
|
"""公开方法:重播所有已加载面板的输入值(供主窗口切页时调用)。
|
||||||
|
|
||||||
|
内部委托给 _replay_live_panel_inputs。
|
||||||
|
"""
|
||||||
|
self._replay_live_panel_inputs()
|
||||||
|
|
||||||
def _replay_live_panel_inputs(self):
|
def _replay_live_panel_inputs(self):
|
||||||
"""遍历 PANEL_REGISTRY 依赖声明,从已加载面板实时读取属性值并强制广播。
|
"""遍历 PANEL_REGISTRY 依赖声明,从已加载面板实时读取属性值并强制广播。
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -16,9 +16,6 @@ WaterQualityGUI 只负责窗口框架(标题栏、菜单栏、状态栏、QTab
|
|||||||
- TrainingModeManager → 训练模式切换(发布 TrainingModeChanged 事件)
|
- TrainingModeManager → 训练模式切换(发布 TrainingModeChanged 事件)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import osgeo # noqa: F401
|
|
||||||
from osgeo import gdal, ogr # noqa: F401
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import ctypes
|
import ctypes
|
||||||
@ -450,7 +447,9 @@ class WaterQualityGUI(QMainWindow):
|
|||||||
self._log_manager.progress_bar.setValue(100)
|
self._log_manager.progress_bar.setValue(100)
|
||||||
QMessageBox.information(self, "完成", "流程执行成功!\n\n请查看工作目录中的结果文件。")
|
QMessageBox.information(self, "完成", "流程执行成功!\n\n请查看工作目录中的结果文件。")
|
||||||
else:
|
else:
|
||||||
QMessageBox.critical(self, "失败", f"流程执行失败:\n\n{message[:200]}")
|
QMessageBox.critical(self, "失败",
|
||||||
|
f"流程执行失败:\n\n{message[:600]}"
|
||||||
|
+ ("\n...(已截断)" if len(message) > 600 else ""))
|
||||||
|
|
||||||
def _on_pipeline_stopped(self, data):
|
def _on_pipeline_stopped(self, data):
|
||||||
self._stop_btn.setEnabled(False)
|
self._stop_btn.setEnabled(False)
|
||||||
@ -464,18 +463,20 @@ class WaterQualityGUI(QMainWindow):
|
|||||||
work_dir = data.get('work_dir', '')
|
work_dir = data.get('work_dir', '')
|
||||||
self.statusBar().showMessage(f"工作目录: {work_dir}")
|
self.statusBar().showMessage(f"工作目录: {work_dir}")
|
||||||
|
|
||||||
# 遍历已加载面板,调用 update_from_config 重建内存级参数和默认输出路径
|
# 让当前激活的 Tab 立即基于新目录重算默认路径,覆盖掉旧值,绕过非空保护
|
||||||
for step_id, panel in self._panel_factory.get_loaded_panels().items():
|
from src.gui.core.panel_registry import PANEL_REGISTRY
|
||||||
if not hasattr(panel, 'update_from_config'):
|
active_idx = self._tab_widget.currentIndex()
|
||||||
continue
|
if 0 <= active_idx < len(PANEL_REGISTRY):
|
||||||
|
active_step_id = PANEL_REGISTRY[active_idx]['step_id']
|
||||||
|
panel = self._panel_factory.get_panel(active_step_id)
|
||||||
|
if panel is not None and hasattr(panel, 'update_from_config'):
|
||||||
try:
|
try:
|
||||||
panel.update_from_config(work_dir=work_dir, pipeline=None)
|
panel.update_from_config(
|
||||||
except Exception as e:
|
work_dir=self._workspace_initializer.work_dir,
|
||||||
# 2026-06-30:不再静默吞异常,至少通过 EventBus 输出日志
|
pipeline=None
|
||||||
self._event_bus.publish('LogMessage', {
|
)
|
||||||
'message': f'[警告] 面板 {step_id} 的 update_from_config 失败: {e}',
|
except Exception:
|
||||||
'level': 'warning',
|
pass
|
||||||
})
|
|
||||||
|
|
||||||
# ================================================================
|
# ================================================================
|
||||||
# 导航 → Tab 单向路由(左侧 List 驱动右侧 Tab,Tab 头部已隐藏)
|
# 导航 → Tab 单向路由(左侧 List 驱动右侧 Tab,Tab 头部已隐藏)
|
||||||
@ -528,21 +529,16 @@ class WaterQualityGUI(QMainWindow):
|
|||||||
"如果当前有流程正在运行,请勿修改此页面的参数。"
|
"如果当前有流程正在运行,请勿修改此页面的参数。"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 【新增修复】:在跳之前,再核实一遍要去的 tab_index 和 item_data 的 step_id 是否吻合!
|
# 【防御】:用已加载 panel 实例反查真实 Tab 索引,防止索引错位
|
||||||
# 如果因为删除了死文件导致索引错位,这里强行用真实 step_id 再找一遍!
|
|
||||||
try:
|
try:
|
||||||
self._panel_factory.get_panel(item_data)
|
|
||||||
# 遍历右侧所有已加载的 Tab,找到属于当前 step_id 的那个正确的 Tab 索引
|
|
||||||
for i in range(self._tab_widget.count()):
|
for i in range(self._tab_widget.count()):
|
||||||
scroll_area = self._tab_widget.widget(i)
|
scroll_area = self._tab_widget.widget(i)
|
||||||
if scroll_area and hasattr(scroll_area, 'widget'):
|
if scroll_area and hasattr(scroll_area, 'widget'):
|
||||||
real_panel = scroll_area.widget()
|
if scroll_area.widget() == panel:
|
||||||
# 如果这个 panel 就是我们要找的 panel,强制更新 tab_index
|
|
||||||
if real_panel == self._panel_factory.get_panel(item_data):
|
|
||||||
tab_index = i
|
tab_index = i
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._log_manager.error(f"页面实例化失败: {str(e)}")
|
self._log_manager.error(f"页面索引校验失败: {str(e)}")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 2. 强制使用注册表的固定索引进行跳转
|
# 2. 强制使用注册表的固定索引进行跳转
|
||||||
@ -551,7 +547,7 @@ class WaterQualityGUI(QMainWindow):
|
|||||||
# 【新增修复】:每次切页时,强制重播所有面板的输入值!
|
# 【新增修复】:每次切页时,强制重播所有面板的输入值!
|
||||||
# 原理:打破 preload_window 造成的时序差,确保目标页面能拿到源页面最新填写的值
|
# 原理:打破 preload_window 造成的时序差,确保目标页面能拿到源页面最新填写的值
|
||||||
if hasattr(self, '_panel_factory'):
|
if hasattr(self, '_panel_factory'):
|
||||||
self._panel_factory._replay_live_panel_inputs()
|
self._panel_factory.replay_live_panel_inputs()
|
||||||
|
|
||||||
# 3. 防撕裂回弹机制:如果由于某种原因没跳过去,把左边的蓝条强行拽回当前真实页面
|
# 3. 防撕裂回弹机制:如果由于某种原因没跳过去,把左边的蓝条强行拽回当前真实页面
|
||||||
if self._tab_widget.currentIndex() != tab_index:
|
if self._tab_widget.currentIndex() != tab_index:
|
||||||
@ -695,10 +691,6 @@ class WaterQualityGUI(QMainWindow):
|
|||||||
def work_dir(self, value):
|
def work_dir(self, value):
|
||||||
self._workspace_initializer.work_dir = value
|
self._workspace_initializer.work_dir = value
|
||||||
|
|
||||||
@property
|
|
||||||
def pipeline(self):
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def worker(self):
|
def worker(self):
|
||||||
return self._pipeline_executor.worker
|
return self._pipeline_executor.worker
|
||||||
@ -718,6 +710,22 @@ def main():
|
|||||||
if multiprocessing.current_process().name != 'MainProcess':
|
if multiprocessing.current_process().name != 'MainProcess':
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
# ★ GDAL 依赖检查(移至此处以利用 global_exception_handler 展示友好弹窗)
|
||||||
|
try:
|
||||||
|
import osgeo # noqa: F401
|
||||||
|
from osgeo import gdal, ogr # noqa: F401
|
||||||
|
except ImportError:
|
||||||
|
from PyQt5.QtWidgets import QMessageBox, QApplication as _QA
|
||||||
|
_qa = _QA.instance() or _QA(sys.argv)
|
||||||
|
QMessageBox.critical(
|
||||||
|
None, "依赖缺失",
|
||||||
|
"GDAL (osgeo) 未安装,程序无法运行。\n\n"
|
||||||
|
"请运行以下命令安装:\n"
|
||||||
|
" conda install -c conda-forge gdal\n"
|
||||||
|
"或访问 https://gdal.org/download.html"
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
app = QApplication.instance()
|
app = QApplication.instance()
|
||||||
if not app:
|
if not app:
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
|
|||||||
Reference in New Issue
Block a user