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.
This commit is contained in:
Lipu Fei 2018-04-16 15:32:10 +02:00
parent 58e18ded18
commit 62521e93db
3 changed files with 38 additions and 33 deletions

34
cura/Utils/Threading.py Normal file
View file

@ -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

View file

@ -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

View file

@ -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()