内容部分修改
This commit is contained in:
@ -13,11 +13,24 @@ from src.gui.components.custom_widgets import FileSelectWidget
|
||||
from src.gui.styles import ModernStylesheet
|
||||
|
||||
def get_resource_path(relative_path: str) -> str:
|
||||
"""适配开发与 PyInstaller 环境的路径获取逻辑"""
|
||||
"""适配开发与 PyInstaller 环境的路径获取逻辑。
|
||||
支持两种打包模式:
|
||||
1. --onedir 模式:文件在 exe_root/_internal/ 下 → 检查 _internal 目录
|
||||
2. --onefile 模式:文件在 sys._MEIPASS 平铺目录
|
||||
"""
|
||||
# 优先检查 PyInstaller onefile 模式(文件平铺在 _MEIPASS 下)
|
||||
if hasattr(sys, '_MEIPASS'):
|
||||
# 打包后,文件会被平铺或按 tree 结构放入临时目录
|
||||
internal_path = os.path.join(sys._MEIPASS, '_internal', relative_path)
|
||||
if os.path.exists(internal_path):
|
||||
return internal_path
|
||||
return os.path.join(sys._MEIPASS, relative_path)
|
||||
|
||||
# 兼容 PyInstaller onedir 模式的 _internal 目录(exe 同级目录下)
|
||||
exe_dir = os.path.dirname(sys.executable)
|
||||
internal_path = os.path.join(exe_dir, '_internal', relative_path)
|
||||
if os.path.exists(internal_path):
|
||||
return internal_path
|
||||
|
||||
# 开发环境下:基于当前文件 (step5_5_panel.py) 的绝对路径进行回溯
|
||||
# 当前在 src/gui/panels/,目标在 src/gui/model/
|
||||
base_dir = Path(__file__).resolve().parent.parent / "model"
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user