Browse Source

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.
Lipu Fei 7 years ago
parent
commit
62521e93db

+ 34 - 0
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

+ 1 - 32
plugins/3MFReader/ThreeMFWorkspaceReader.py

@@ -4,7 +4,6 @@
 from configparser import ConfigParser
 from configparser import ConfigParser
 import zipfile
 import zipfile
 import os
 import os
-import threading
 
 
 import xml.etree.ElementTree as ET
 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.GlobalStack import GlobalStack
 from cura.Settings.CuraContainerStack import _ContainerIndexes
 from cura.Settings.CuraContainerStack import _ContainerIndexes
 from cura.CuraApplication import CuraApplication
 from cura.CuraApplication import CuraApplication
+from cura.Utils.Threading import call_on_qt_thread
 
 
 from .WorkspaceDialog import WorkspaceDialog
 from .WorkspaceDialog import WorkspaceDialog
 
 
 i18n_catalog = i18nCatalog("cura")
 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:
 class ContainerInfo:
     def __init__(self, file_name: str, serialized: str, parser: ConfigParser):
     def __init__(self, file_name: str, serialized: str, parser: ConfigParser):
         self.file_name = file_name
         self.file_name = file_name

+ 3 - 1
plugins/3MFWriter/ThreeMFWorkspaceWriter.py

@@ -6,16 +6,18 @@ from io import StringIO
 import zipfile
 import zipfile
 
 
 from UM.Application import Application
 from UM.Application import Application
-from UM.Logger import Logger
 from UM.Preferences import Preferences
 from UM.Preferences import Preferences
 from UM.Settings.ContainerRegistry import ContainerRegistry
 from UM.Settings.ContainerRegistry import ContainerRegistry
 from UM.Workspace.WorkspaceWriter import WorkspaceWriter
 from UM.Workspace.WorkspaceWriter import WorkspaceWriter
 
 
+from cura.Utils.Threading import call_on_qt_thread
+
 
 
 class ThreeMFWorkspaceWriter(WorkspaceWriter):
 class ThreeMFWorkspaceWriter(WorkspaceWriter):
     def __init__(self):
     def __init__(self):
         super().__init__()
         super().__init__()
 
 
+    @call_on_qt_thread
     def write(self, stream, nodes, mode=WorkspaceWriter.OutputMode.BinaryMode):
     def write(self, stream, nodes, mode=WorkspaceWriter.OutputMode.BinaryMode):
         application = Application.getInstance()
         application = Application.getInstance()
         machine_manager = application.getMachineManager()
         machine_manager = application.getMachineManager()