内容部分修改

This commit is contained in:
DXC
2026-05-11 17:38:29 +08:00
parent bf4237b160
commit 170d347e21
8 changed files with 284 additions and 47 deletions

View File

@ -5,6 +5,12 @@
GUI for Water Quality Inversion Pipeline
"""
# ==============================================================================
# 🚀 终极防御:必须在全宇宙第一行强制载入 GDAL 底层 DLL绝对杜绝 0xC0000005 内存崩溃
# ==============================================================================
import osgeo
from osgeo import gdal, ogr
import os
import json
import copy
@ -31,8 +37,23 @@ from PyQt5.QtGui import QIcon, QFont, QTextCursor, QPalette, QColor, QPixmap
import sys
import traceback
import multiprocessing
import ctypes
# ==============================================================================
# 🚀 终极防御置顶:在载入任何自定义面板、样式或子模块之前,强制提前创建 QApplication
# 彻底杜绝 import 时期载入类属性 (如 QFont/QIcon/QPixmap) 触发的 QWidget 崩溃
# ==============================================================================
if multiprocessing.current_process().name == 'MainProcess':
if not QApplication.instance():
try:
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
except Exception:
pass
_global_app = QApplication(sys.argv)
# 👇 全局异常钩子(保持不变)
def get_resource_path(relative_path: str) -> str:
"""获取资源的绝对路径,适配 PyInstaller 打包环境。
打包后资源位于 sys._MEIPASS解压临时目录开发环境则基于 __file__ 向上三级。
@ -45,10 +66,31 @@ def get_resource_path(relative_path: str) -> str:
def global_exception_handler(exc_type, exc_value, exc_traceback):
print("\n" + "="*50)
print("【严重错误拦截 - PyQt 崩溃死因】")
traceback.print_exception(exc_type, exc_value, exc_traceback)
print("="*50 + "\n")
err_lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
err_msg = "".join(err_lines)
dump_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "crash_dump.txt")
try:
with open(dump_path, "a", encoding="utf-8") as f:
f.write(f"\n{'='*60}\n[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}]\n")
f.write(err_msg)
except Exception:
pass
try:
from PyQt5.QtWidgets import QMessageBox
from PyQt5.QtCore import Qt
msg = (
"【严重错误 - 程序即将退出】\n\n"
"错误类型: {}\n\n"
"错误信息: {}\n\n"
"详细信息已写入:\n{}".format(
exc_type.__name__,
str(exc_value),
dump_path,
)
)
QMessageBox.critical(None, "程序崩溃", msg)
except Exception:
pass
# 挂载全局异常钩子,阻止 PyQt 静默闪退
sys.excepthook = global_exception_handler
@ -89,9 +131,13 @@ from src.gui.panels.step9_panel import Step9Panel
from src.gui.panels.visualization_panel import VisualizationPanel
from src.gui.panels.report_generation_panel import ReportGenerationPanel
# Matplotlib相关导入
# Matplotlib相关导入 (推迟并加入底层防爆保护)
import matplotlib
matplotlib.use('Qt5Agg')
try:
# 确保只在主线程且安全的环境下绑定后端
matplotlib.use('Qt5Agg', force=False)
except Exception:
pass
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
@ -1278,7 +1324,17 @@ class WaterQualityGUI(QMainWindow):
"""水质参数反演分析系统主窗口"""
def __init__(self):
# 1. 🚀 强制设置任务栏图标(解决任务栏图标默认是 Python 黄蓝图标的问题)
# 为当前进程设置独立的 AppUserModelID
my_appid = u'mycompany.megacube.waterquality.v1'
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(my_appid)
super().__init__()
# 2. 设置窗口图标(指向你的 .ico 文件)
icon_path = get_resource_path("data/icons-1/uitubiao.ico")
self.setWindowIcon(QIcon(icon_path))
self.pipeline = None
self.worker = None
self.config_file = None
@ -3002,8 +3058,23 @@ class WaterQualityGUI(QMainWindow):
def main():
"""主函数"""
import sys
import multiprocessing
from PyQt5.QtWidgets import QApplication
# 离线授权验证拦截(必须在业务窗口创建前执行)
# 1. 多进程 Fork 环境隔离
if multiprocessing.current_process().name != 'MainProcess':
sys.exit(0)
# 2. 🚀 终极防御:必须在全宇宙第一行强制创建 QApplication 实例!
# 绝对杜绝任何后续验签模块或弹窗过早调用 QWidget 导致的崩溃
app = QApplication.instance()
if not app:
app = QApplication(sys.argv)
app.setApplicationName("Mega Water")
app.setOrganizationName("WaterQuality")
# 3. 安全载入离线授权验证拦截
try:
from src.auth.license_manager import verify_license
from src.auth.license_dialog import LicenseDialog
@ -3018,27 +3089,36 @@ def main():
_is_license_valid, _license_msg = verify_license()
if not _is_license_valid:
_license_app = QApplication(sys.argv)
_license_app.setApplicationName("WaterQuality")
_dialog = LicenseDialog()
_dialog.exec_()
_license_app.quit()
sys.exit(0)
# 授权通过,正常载入主程序
app = QApplication(sys.argv)
app.setApplicationName("Mega Water")
app.setOrganizationName("WaterQuality")
# 4. 授权通过,正常载入主程序主界面
window = WaterQualityGUI()
window.show()
sys.exit(app.exec_())
# ==============================================================================
# 全宇宙最底部程序入口
# ==============================================================================
if __name__ == "__main__":
# 必须紧跟在 if __name__ == "__main__": 下面第一行
import sys
import multiprocessing
multiprocessing.freeze_support()
# 1. 极其强硬的底层防御:
# 如果当前进程明确是 PyInstaller 派生的后台计算子进程,强行静默退出!
# 彻底绕过多进程钩子在尝试解包 sys.argv 时引发的 ValueError 崩溃
if multiprocessing.current_process().name != 'MainProcess':
sys.exit(0)
# 2. 安全调用 freeze_support带防爆气囊
try:
multiprocessing.freeze_support()
except Exception:
pass # 哪怕底层钩子参数解包失败,也强行保住主进程平稳过关
# 3. 正常拉起主业务逻辑
main()