Browse Source

Only talk to the CuraEngine socket from the same (Main) thread, and be a lot more careful about handling the StartSliceJob when restarting CuraEngine.

Fixes CURA-1434
Simon Edwards 9 years ago
parent
commit
f92ff3e864

+ 17 - 5
plugins/CuraEngineBackend/CuraEngineBackend.py

@@ -74,6 +74,7 @@ class CuraEngineBackend(Backend):
         self._message_handlers["cura.proto.SlicingFinished"] = self._onSlicingFinishedMessage
 
         self._slicing = False
+        self._start_slice_job = None
         self._restart = False
         self._enabled = True
         self._always_restart = True
@@ -153,14 +154,19 @@ class CuraEngineBackend(Backend):
         self._slicing = True
         self.slicingStarted.emit()
 
-        job = StartSliceJob.StartSliceJob(self._profile, self._socket)
-        job.start()
-        job.finished.connect(self._onStartSliceCompleted)
+        slice_message = self._socket.createMessage("cura.proto.Slice")
+        settings_message = self._socket.createMessage("cura.proto.SettingList");
+        self._start_slice_job = StartSliceJob.StartSliceJob(self._profile, slice_message, settings_message)
+        self._start_slice_job.start()
+        self._start_slice_job.finished.connect(self._onStartSliceCompleted)
 
     def _terminate(self):
         self._slicing = False
         self._restart = True
         self._stored_layer_data = []
+        if self._start_slice_job is not None:
+            self._start_slice_job.cancel()
+
         self.slicingCancelled.emit()
         self.processingProgress.emit(0)
         Logger.log("d", "Attempting to kill the engine process")
@@ -174,13 +180,19 @@ class CuraEngineBackend(Backend):
             except Exception as e: # terminating a process that is already terminating causes an exception, silently ignore this.
                 Logger.log("d", "Exception occured while trying to kill the engine %s", str(e))
 
-
     def _onStartSliceCompleted(self, job):
-        if job.getError() or job.getResult() != True:
+        # Note that cancelled slice jobs can still call this method.
+        if self._start_slice_job is job:
+            self._start_slice_job = None
+        if job.isCancelled() or job.getError() or job.getResult() != True:
             if self._message:
                 self._message.hide()
                 self._message = None
             return
+        else:
+            # Preparation completed, send it to the backend.
+            self._socket.sendMessage(job.getSettingsMessage())
+            self._socket.sendMessage(job.getSliceMessage())
 
     def _onSceneChanged(self, source):
         if type(source) is not SceneNode:

+ 66 - 62
plugins/CuraEngineBackend/StartSliceJob.py

@@ -27,86 +27,93 @@ class GcodeStartEndFormatter(Formatter):
             Logger.log("w", "Incorrectly formatted placeholder '%s' in start/end gcode", key)
             return "{" + str(key) + "}"
 
-##  Job class that handles sending the current scene data to CuraEngine
+##  Job class that builds up the message of scene data to send to CuraEngine.
 class StartSliceJob(Job):
-    def __init__(self, profile, socket):
+    def __init__(self, profile, sliceMessage, settingsMessage):
         super().__init__()
 
         self._scene = Application.getInstance().getController().getScene()
         self._profile = profile
-        self._socket = socket
+        self._slice_message = sliceMessage
+        self._settings_message = settingsMessage
+        self._is_cancelled = False
 
-    def run(self):
-        self._scene.acquireLock()
+    def getSettingsMessage(self):
+        return self._settings_message
 
-        for node in DepthFirstIterator(self._scene.getRoot()):
-            if node.callDecoration("getLayerData"):
-                node.getParent().removeChild(node)
-                break
+    def getSliceMessage(self):
+        return self._slice_message
 
-        object_groups = []
-        if self._profile.getSettingValue("print_sequence") == "one_at_a_time":
-            for node in OneAtATimeIterator(self._scene.getRoot()):
+    def run(self):
+        with self._scene.getSceneLock():
+            for node in DepthFirstIterator(self._scene.getRoot()):
+                if node.callDecoration("getLayerData"):
+                    node.getParent().removeChild(node)
+                    break
+
+            object_groups = []
+            if self._profile.getSettingValue("print_sequence") == "one_at_a_time":
+                for node in OneAtATimeIterator(self._scene.getRoot()):
+                    temp_list = []
+
+                    if getattr(node, "_outside_buildarea", False):
+                        continue
+
+                    children = node.getAllChildren()
+                    children.append(node)
+                    for child_node in children:
+                        if type(child_node) is SceneNode and child_node.getMeshData() and child_node.getMeshData().getVertices() is not None:
+                            temp_list.append(child_node)
+
+                    if temp_list:
+                        object_groups.append(temp_list)
+                    Job.yieldThread()
+                if len(object_groups) == 0:
+                    Logger.log("w", "No objects suitable for one at a time found, or no correct order found")
+            else:
                 temp_list = []
-
-                if getattr(node, "_outside_buildarea", False):
-                    continue
-
-                children = node.getAllChildren()
-                children.append(node)
-                for child_node in children:
-                    if type(child_node) is SceneNode and child_node.getMeshData() and child_node.getMeshData().getVertices() is not None:
-                        temp_list.append(child_node)
+                for node in DepthFirstIterator(self._scene.getRoot()):
+                    if type(node) is SceneNode and node.getMeshData() and node.getMeshData().getVertices() is not None:
+                        if not getattr(node, "_outside_buildarea", False):
+                            temp_list.append(node)
+                    Job.yieldThread()
 
                 if temp_list:
                     object_groups.append(temp_list)
-                Job.yieldThread()
-            if len(object_groups) == 0:
-                Logger.log("w", "No objects suitable for one at a time found, or no correct order found")
-        else:
-            temp_list = []
-            for node in DepthFirstIterator(self._scene.getRoot()):
-                if type(node) is SceneNode and node.getMeshData() and node.getMeshData().getVertices() is not None:
-                    if not getattr(node, "_outside_buildarea", False):
-                        temp_list.append(node)
-                Job.yieldThread()
 
-            if temp_list:
-                object_groups.append(temp_list)
+            if not object_groups:
+                return
 
-        self._scene.releaseLock()
-
-        if not object_groups:
-            return
+            self._buildSettingsMessage(self._profile)
 
-        self._sendSettings(self._profile)
+            for group in object_groups:
+                group_message = self._slice_message.addRepeatedMessage("object_lists")
+                if group[0].getParent().callDecoration("isGroup"):
+                    self._handlePerObjectSettings(group[0].getParent(), group_message)
+                for object in group:
+                    mesh_data = object.getMeshData().getTransformed(object.getWorldTransformation())
 
-        slice_message = self._socket.createMessage("cura.proto.Slice")
+                    obj = group_message.addRepeatedMessage("objects")
+                    obj.id = id(object)
 
-        for group in object_groups:
-            group_message = slice_message.addRepeatedMessage("object_lists")
-            if group[0].getParent().callDecoration("isGroup"):
-                self._handlePerObjectSettings(group[0].getParent(), group_message)
-            for object in group:
-                mesh_data = object.getMeshData().getTransformed(object.getWorldTransformation())
+                    verts = numpy.array(mesh_data.getVertices())
+                    verts[:,[1,2]] = verts[:,[2,1]]
+                    verts[:,1] *= -1
 
-                obj = group_message.addRepeatedMessage("objects")
-                obj.id = id(object)
+                    obj.vertices = verts
 
-                verts = numpy.array(mesh_data.getVertices())
-                verts[:,[1,2]] = verts[:,[2,1]]
-                verts[:,1] *= -1
+                    self._handlePerObjectSettings(object, obj)
 
-                obj.vertices = verts
+                    Job.yieldThread()
 
-                self._handlePerObjectSettings(object, obj)
+        self.setResult(True)
 
-                Job.yieldThread()
+    def cancel(self):
+        super().cancel()
+        self._is_cancelled = True
 
-        Logger.log("d", "Sending data to engine for slicing.")
-        self._socket.sendMessage(slice_message)
-        Logger.log("d", "Sending data to engine is completed")
-        self.setResult(True)
+    def isCancelled(self):
+        return self._is_cancelled
 
     def _expandGcodeTokens(self, key, value, settings):
         try:
@@ -117,22 +124,19 @@ class StartSliceJob(Job):
             Logger.log("w", "Unabled to do token replacement on start/end gcode %s", traceback.format_exc())
             return str(value).encode("utf-8")
 
-    def _sendSettings(self, profile):
-        msg = self._socket.createMessage("cura.proto.SettingList");
+    def _buildSettingsMessage(self, profile):
         settings = profile.getAllSettingValues(include_machine = True)
         start_gcode = settings["machine_start_gcode"]
         settings["material_bed_temp_prepend"] = "{material_bed_temperature}" not in start_gcode
         settings["material_print_temp_prepend"] = "{material_print_temperature}" not in start_gcode
         for key, value in settings.items():
-            s = msg.addRepeatedMessage("settings")
+            s = self._settings_message.addRepeatedMessage("settings")
             s.name = key
             if key == "machine_start_gcode" or key == "machine_end_gcode":
                 s.value = self._expandGcodeTokens(key, value, settings)
             else:
                 s.value = str(value).encode("utf-8")
 
-        self._socket.sendMessage(msg)
-
     def _handlePerObjectSettings(self, node, message):
         profile = node.callDecoration("getProfile")
         if profile: