diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 12ccb6a5b6..c9529f8025 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -501,11 +501,6 @@ class CuraApplication(QtApplication): def getStaticVersion(cls): return CuraVersion - ## Handle removing the unneeded plugins - # \sa PluginRegistry - def _removePlugins(self): - self._plugin_registry.removePlugins() - ## Handle loading of all plugin types (and the backend explicitly) # \sa PluginRegistry def _loadPlugins(self): diff --git a/cura/Utils/Threading.py b/cura/Utils/Threading.py new file mode 100644 index 0000000000..3cd6200513 --- /dev/null +++ b/cura/Utils/Threading.py @@ -0,0 +1,34 @@ +import threading + +from cura.CuraApplication import CuraApplication + + +# +# HACK: +# +# In project loading, when override the existing machine is selected, the stacks and containers that are correctly +# active in the system will be overridden at runtime. Because the project loading is done in a different thread than +# the Qt thread, something else can kick in the middle of the process. One of them is the rendering. It will access +# the current stacks and container, which have not completely been updated yet, so Cura will crash in this case. +# +# This "@call_on_qt_thread" decorator makes sure that a function will always be called on the Qt thread (blocking). +# It is applied to the read() function of project loading so it can be guaranteed that only after the project loading +# process is completely done, everything else that needs to occupy the QT thread will be executed. +# +class InterCallObject: + def __init__(self): + self.finish_event = threading.Event() + self.result = None + + +def call_on_qt_thread(func): + def _call_on_qt_thread_wrapper(*args, **kwargs): + def _handle_call(ico, *args, **kwargs): + ico.result = func(*args, **kwargs) + ico.finish_event.set() + inter_call_object = InterCallObject() + new_args = tuple([inter_call_object] + list(args)[:]) + CuraApplication.getInstance().callLater(_handle_call, *new_args, **kwargs) + inter_call_object.finish_event.wait() + return inter_call_object.result + return _call_on_qt_thread_wrapper diff --git a/cura/Utils/__init__.py b/cura/Utils/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 633142187c..212df59294 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -4,7 +4,6 @@ from configparser import ConfigParser import zipfile import os -import threading import xml.etree.ElementTree as ET @@ -27,43 +26,13 @@ from cura.Settings.ExtruderStack import ExtruderStack from cura.Settings.GlobalStack import GlobalStack from cura.Settings.CuraContainerStack import _ContainerIndexes from cura.CuraApplication import CuraApplication +from cura.Utils.Threading import call_on_qt_thread from .WorkspaceDialog import WorkspaceDialog i18n_catalog = i18nCatalog("cura") -# -# HACK: -# -# In project loading, when override the existing machine is selected, the stacks and containers that are correctly -# active in the system will be overridden at runtime. Because the project loading is done in a different thread than -# the Qt thread, something else can kick in the middle of the process. One of them is the rendering. It will access -# the current stacks and container, which have not completely been updated yet, so Cura will crash in this case. -# -# This "@call_on_qt_thread" decorator makes sure that a function will always be called on the Qt thread (blocking). -# It is applied to the read() function of project loading so it can be guaranteed that only after the project loading -# process is completely done, everything else that needs to occupy the QT thread will be executed. -# -class InterCallObject: - def __init__(self): - self.finish_event = threading.Event() - self.result = None - - -def call_on_qt_thread(func): - def _call_on_qt_thread_wrapper(*args, **kwargs): - def _handle_call(ico, *args, **kwargs): - ico.result = func(*args, **kwargs) - ico.finish_event.set() - inter_call_object = InterCallObject() - new_args = tuple([inter_call_object] + list(args)[:]) - CuraApplication.getInstance().callLater(_handle_call, *new_args, **kwargs) - inter_call_object.finish_event.wait() - return inter_call_object.result - return _call_on_qt_thread_wrapper - - class ContainerInfo: def __init__(self, file_name: str, serialized: str, parser: ConfigParser): self.file_name = file_name diff --git a/plugins/3MFWriter/ThreeMFWorkspaceWriter.py b/plugins/3MFWriter/ThreeMFWorkspaceWriter.py index 3f5e69317e..e948f62337 100644 --- a/plugins/3MFWriter/ThreeMFWorkspaceWriter.py +++ b/plugins/3MFWriter/ThreeMFWorkspaceWriter.py @@ -6,16 +6,18 @@ from io import StringIO import zipfile from UM.Application import Application -from UM.Logger import Logger from UM.Preferences import Preferences from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Workspace.WorkspaceWriter import WorkspaceWriter +from cura.Utils.Threading import call_on_qt_thread + class ThreeMFWorkspaceWriter(WorkspaceWriter): def __init__(self): super().__init__() + @call_on_qt_thread def write(self, stream, nodes, mode=WorkspaceWriter.OutputMode.BinaryMode): application = Application.getInstance() machine_manager = application.getMachineManager() diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index 314983404c..654c1024bb 100755 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -121,7 +121,7 @@ class CuraEngineBackend(QObject, Backend): self._slice_start_time = None self._is_disabled = False - Preferences.getInstance().addPreference("general/auto_slice", True) + Preferences.getInstance().addPreference("general/auto_slice", False) self._use_timer = False # When you update a setting and other settings get changed through inheritance, many propertyChanged signals are fired. diff --git a/plugins/ModelChecker/ModelChecker.py b/plugins/ModelChecker/ModelChecker.py index 65d7c2e35d..f85a7a249a 100644 --- a/plugins/ModelChecker/ModelChecker.py +++ b/plugins/ModelChecker/ModelChecker.py @@ -49,6 +49,13 @@ class ModelChecker(QObject, Extension): warning_size_xy = 150 #The horizontal size of a model that would be too large when dealing with shrinking materials. warning_size_z = 100 #The vertical size of a model that would be too large when dealing with shrinking materials. + # This function can be triggered in the middle of a machine change, so do not proceed if the machine change + # has not done yet. + global_container_stack = Application.getInstance().getGlobalContainerStack() + if global_container_stack is None: + Application.getInstance().callLater(lambda: self.onChanged.emit()) + return False + material_shrinkage = self._getMaterialShrinkage() warning_nodes = [] @@ -56,6 +63,13 @@ class ModelChecker(QObject, Extension): # Check node material shrinkage and bounding box size for node in self.sliceableNodes(): node_extruder_position = node.callDecoration("getActiveExtruderPosition") + + # This function can be triggered in the middle of a machine change, so do not proceed if the machine change + # has not done yet. + if str(node_extruder_position) not in global_container_stack.extruders: + Application.getInstance().callLater(lambda: self.onChanged.emit()) + return False + if material_shrinkage[node_extruder_position] > shrinkage_threshold: bbox = node.getBoundingBox() if bbox.width >= warning_size_xy or bbox.depth >= warning_size_xy or bbox.height >= warning_size_z: @@ -63,11 +77,11 @@ class ModelChecker(QObject, Extension): self._caution_message.setText(catalog.i18nc( "@info:status", - "Some models may not be printed optimally due to object size and chosen material for models: {model_names}.\n" - "Tips that may be useful to improve the print quality:\n" - "1) Use rounded corners.\n" - "2) Turn the fan off (only if there are no tiny details on the model).\n" - "3) Use a different material.").format(model_names = ", ".join([n.getName() for n in warning_nodes]))) + "
One or more 3D models may not print optimally due to the model size and material configuration:
\n" + "{model_names}
\n" + "Find out how to ensure the best possible print quality and reliability.
\n" + "" + ).format(model_names = ", ".join([n.getName() for n in warning_nodes]))) return len(warning_nodes) > 0 @@ -92,9 +106,8 @@ class ModelChecker(QObject, Extension): Logger.log("d", "Model checker view created.") @pyqtProperty(bool, notify = onChanged) - def runChecks(self): + def hasWarnings(self): danger_shrinkage = self.checkObjectsForShrinkage() - return any((danger_shrinkage, )) #If any of the checks fail, show the warning button. @pyqtSlot() diff --git a/plugins/ModelChecker/ModelChecker.qml b/plugins/ModelChecker/ModelChecker.qml index 3db54d4387..98db233bf8 100644 --- a/plugins/ModelChecker/ModelChecker.qml +++ b/plugins/ModelChecker/ModelChecker.qml @@ -18,7 +18,7 @@ Button UM.I18nCatalog{id: catalog; name:"cura"} - visible: manager.runChecks + visible: manager.hasWarnings tooltip: catalog.i18nc("@info:tooltip", "Some things could be problematic in this print. Click to see tips for adjustment.") onClicked: manager.showWarnings() diff --git a/plugins/PluginBrowser/PluginBrowser.qml b/plugins/PluginBrowser/PluginBrowser.qml index ec4c2a9135..6e5f532709 100644 --- a/plugins/PluginBrowser/PluginBrowser.qml +++ b/plugins/PluginBrowser/PluginBrowser.qml @@ -15,6 +15,7 @@ Window { id: base title: catalog.i18nc("@title:tab", "Plugins"); + modality: Qt.ApplicationModal width: 800 * screenScaleFactor height: 640 * screenScaleFactor minimumWidth: 350 * screenScaleFactor diff --git a/resources/qml/Menus/ProfileMenu.qml b/resources/qml/Menus/ProfileMenu.qml index ffd3c556b6..5b9a5a3b73 100644 --- a/resources/qml/Menus/ProfileMenu.qml +++ b/resources/qml/Menus/ProfileMenu.qml @@ -51,7 +51,8 @@ Menu MenuItem { text: model.name - checkable: true + checkable: model.available + enabled: model.available checked: Cura.MachineManager.activeQualityOrQualityChangesName == model.name exclusiveGroup: group onTriggered: Cura.MachineManager.setQualityChangesGroup(model.quality_changes_group)