From 62521e93db9d91d9071ac60bdb2a60e0c2e5c070 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Mon, 16 Apr 2018 15:32:10 +0200 Subject: [PATCH] Make sure that project writer runs on Qt thread CURA-5229 - Move @call_on_qt_thread to a separate module - Make sure that project writer runs on Qt thread because itself and the calls it makes can create new QObjects such as InstanceContainers, and this must happen on the Qt thread. --- cura/Utils/Threading.py | 34 +++++++++++++++++++++ plugins/3MFReader/ThreeMFWorkspaceReader.py | 33 +------------------- plugins/3MFWriter/ThreeMFWorkspaceWriter.py | 4 ++- 3 files changed, 38 insertions(+), 33 deletions(-) create mode 100644 cura/Utils/Threading.py 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/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()