diff --git a/cura/CrashHandler.py b/cura/CrashHandler.py index 0c6740f740..8709461da3 100644 --- a/cura/CrashHandler.py +++ b/cura/CrashHandler.py @@ -1,7 +1,6 @@ # Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -import sys import platform import traceback import faulthandler @@ -14,7 +13,7 @@ import ssl import urllib.request import urllib.error -from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, QCoreApplication +from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QTextEdit, QGroupBox from UM.Application import Application @@ -49,10 +48,11 @@ fatal_exception_types = [ class CrashHandler: crash_url = "https://stats.ultimaker.com/api/cura" - def __init__(self, exception_type, value, tb): + def __init__(self, exception_type, value, tb, has_started = True): self.exception_type = exception_type self.value = value self.traceback = tb + self.has_started = has_started self.dialog = None # Don't create a QDialog before there is a QApplication # While we create the GUI, the information will be stored for sending afterwards @@ -67,10 +67,6 @@ class CrashHandler: if not CuraDebugMode and exception_type not in fatal_exception_types: return - application = QCoreApplication.instance() - if not application: - sys.exit(1) - self.dialog = QDialog() self._createDialog() @@ -79,6 +75,7 @@ class CrashHandler: self.dialog.setMinimumWidth(640) self.dialog.setMinimumHeight(640) self.dialog.setWindowTitle(catalog.i18nc("@title:window", "Crash Report")) + self.dialog.finished.connect(self._close) layout = QVBoxLayout(self.dialog) @@ -89,6 +86,9 @@ class CrashHandler: layout.addWidget(self._userDescriptionWidget()) layout.addWidget(self._buttonsWidget()) + def _close(self): + os._exit(1) + def _messageWidget(self): label = QLabel() label.setText(catalog.i18nc("@label crash message", """

A fatal error has occurred. Please send us this Crash Report to fix the problem

diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 8df9f01e91..301215005e 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -1,5 +1,4 @@ # Copyright (c) 2017 Ultimaker B.V. -# Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from PyQt5.QtNetwork import QLocalServer from PyQt5.QtNetwork import QLocalSocket @@ -113,6 +112,8 @@ class CuraApplication(QtApplication): # changes of the settings. SettingVersion = 4 + Created = False + class ResourceTypes: QmlFiles = Resources.UserType + 1 Firmware = Resources.UserType + 2 @@ -256,7 +257,7 @@ class CuraApplication(QtApplication): self._center_after_select = False self._camera_animation = None self._cura_actions = None - self._started = False + self.started = False self._message_box_callback = None self._message_box_callback_arguments = [] @@ -409,6 +410,8 @@ class CuraApplication(QtApplication): self.getCuraSceneController().setActiveBuildPlate(0) # Initialize + CuraApplication.Created = True + def _onEngineCreated(self): self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider()) @@ -503,7 +506,7 @@ class CuraApplication(QtApplication): # # Note that the AutoSave plugin also calls this method. def saveSettings(self): - if not self._started: # Do not do saving during application start + if not self.started: # Do not do saving during application start return ContainerRegistry.getInstance().saveDirtyContainers() @@ -732,7 +735,7 @@ class CuraApplication(QtApplication): for file_name in self._open_file_queue: #Open all the files that were queued up while plug-ins were loading. self._openFile(file_name) - self._started = True + self.started = True self.exec_() diff --git a/cura_app.py b/cura_app.py index b9afb9bbcc..624228b715 100755 --- a/cura_app.py +++ b/cura_app.py @@ -71,8 +71,40 @@ if "PYTHONPATH" in os.environ.keys(): # If PYTHONPATH is u def exceptHook(hook_type, value, traceback): from cura.CrashHandler import CrashHandler - _crash_handler = CrashHandler(hook_type, value, traceback) - _crash_handler.show() + from cura.CuraApplication import CuraApplication + has_started = False + if CuraApplication.Created: + has_started = CuraApplication.getInstance().started + + # + # When the exception hook is triggered, the QApplication may not have been initialized yet. In this case, we don't + # have an QApplication to handle the event loop, which is required by the Crash Dialog. + # The flag "CuraApplication.Created" is set to True when CuraApplication finishes its constructor call. + # + # Before the "started" flag is set to True, the Qt event loop has not started yet. The event loop is a blocking + # call to the QApplication.exec_(). In this case, we need to: + # 1. Remove all scheduled events so no more unnecessary events will be processed, such as loading the main dialog, + # loading the machine, etc. + # 2. Start the Qt event loop with exec_() and show the Crash Dialog. + # + # If the application has finished its initialization and was running fine, and then something causes a crash, + # we run the old routine to show the Crash Dialog. + # + from PyQt5.Qt import QApplication + if CuraApplication.Created: + _crash_handler = CrashHandler(hook_type, value, traceback, has_started) + if not has_started: + CuraApplication.getInstance().removePostedEvents(None) + _crash_handler.show() + sys.exit(CuraApplication.getInstance().exec_()) + else: + _crash_handler.show() + sys.exit(1) + else: + application = QApplication(sys.argv) + _crash_handler = CrashHandler(hook_type, value, traceback, has_started) + _crash_handler.dialog.show() + sys.exit(application.exec_()) if not known_args["debug"]: sys.excepthook = exceptHook