Browse Source

CURA-4525 first multi slice + multi layer data, added filter on build plate, added option arrange on load, visuals like convex hull are now correct

Jack Ha 7 years ago
parent
commit
e21acd1a07

+ 1 - 1
cura/Arrange.py

@@ -40,7 +40,7 @@ class Arrange:
     #   \param fixed_nodes  Scene nodes to be placed
     @classmethod
     def create(cls, scene_root = None, fixed_nodes = None, scale = 0.5, x = 220, y = 220):
-        arranger = Arrange(x, y, x / 2, y / 2, scale = scale)
+        arranger = Arrange(x, y, x // 2, y // 2, scale = scale)
         arranger.centerFirst()
 
         if fixed_nodes is None:

+ 0 - 1
cura/ArrangeObjectsAllBuildPlatesJob.py

@@ -112,7 +112,6 @@ class ArrangeObjectsAllBuildPlatesJob(Job):
             #     start_priority = 0
 
             while try_placement:
-                Logger.log("d", "start_priority %s", start_priority)
                 # make sure that current_build_plate_number is not going crazy or you'll have a lot of arrange objects
                 while current_build_plate_number >= arrange_array.count():
                     arrange_array.add()

+ 1 - 2
cura/ConvexHullNode.py

@@ -6,7 +6,6 @@ from UM.Scene.SceneNode import SceneNode
 from UM.Resources import Resources
 from UM.Math.Color import Color
 from UM.Mesh.MeshBuilder import MeshBuilder  # To create a mesh to display the convex hull with.
-
 from UM.View.GL.OpenGL import OpenGL
 
 
@@ -65,7 +64,7 @@ class ConvexHullNode(SceneNode):
             ConvexHullNode.shader.setUniformValue("u_diffuseColor", self._color)
             ConvexHullNode.shader.setUniformValue("u_opacity", 0.6)
 
-        if self.getParent():
+        if self.getParent() and self.getParent().callDecoration("getBuildPlateNumber") == Application.getInstance().activeBuildPlate:
             if self.getMeshData():
                 renderer.queueNode(self, transparent = True, shader = ConvexHullNode.shader, backface_cull = True, sort = -8)
                 if self._convex_hull_head_mesh:

+ 47 - 26
cura/CuraApplication.py

@@ -33,6 +33,7 @@ from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
 from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
 from UM.Operations.GroupedOperation import GroupedOperation
 from UM.Operations.SetTransformOperation import SetTransformOperation
+
 from cura.Arrange import Arrange
 from cura.ShapeArray import ShapeArray
 from cura.ConvexHullDecorator import ConvexHullDecorator
@@ -41,6 +42,7 @@ from cura.SliceableObjectDecorator import SliceableObjectDecorator
 from cura.BlockSlicingDecorator import BlockSlicingDecorator
 # research
 from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
+from cura.Scene.CuraSceneNode import CuraSceneNode
 
 from cura.ArrangeObjectsJob import ArrangeObjectsJob
 from cura.ArrangeObjectsAllBuildPlatesJob import ArrangeObjectsAllBuildPlatesJob
@@ -307,11 +309,13 @@ class CuraApplication(QtApplication):
         preferences.addPreference("cura/asked_dialog_on_project_save", False)
         preferences.addPreference("cura/choice_on_profile_override", "always_ask")
         preferences.addPreference("cura/choice_on_open_project", "always_ask")
+        preferences.addPreference("cura/arrange_objects_on_load", True)
 
         preferences.addPreference("cura/currency", "€")
         preferences.addPreference("cura/material_settings", "{}")
 
         preferences.addPreference("view/invert_zoom", False)
+        preferences.addPreference("view/filter_current_build_plate", False)
 
         self._need_to_show_user_agreement = not Preferences.getInstance().getValue("general/accepted_user_agreement")
 
@@ -896,7 +900,7 @@ class CuraApplication(QtApplication):
         scene_bounding_box = None
         is_block_slicing_node = False
         for node in DepthFirstIterator(self.getController().getScene().getRoot()):
-            if type(node) is not SceneNode or (not node.getMeshData() and not node.callDecoration("getLayerData")):
+            if not issubclass(type(node), SceneNode) or (not node.getMeshData() and not node.callDecoration("getLayerData")):
                 continue
             if node.callDecoration("isBlockSlicing"):
                 is_block_slicing_node = True
@@ -1013,7 +1017,7 @@ class CuraApplication(QtApplication):
 
         Selection.clear()
         for node in DepthFirstIterator(self.getController().getScene().getRoot()):
-            if type(node) is not SceneNode:
+            if not issubclass(type(node), SceneNode):
                 continue
             if not node.getMeshData() and not node.callDecoration("isGroup"):
                 continue  # Node that doesnt have a mesh and is not a group.
@@ -1021,6 +1025,9 @@ class CuraApplication(QtApplication):
                 continue  # Grouped nodes don't need resetting as their parent (the group) is resetted)
             if not node.isSelectable():
                 continue  # i.e. node with layer data
+            if not node.callDecoration("isSliceable"):
+                continue  # i.e. node with layer data
+
             Selection.add(node)
 
     ##  Delete all nodes containing mesh data in the scene.
@@ -1032,7 +1039,7 @@ class CuraApplication(QtApplication):
 
         nodes = []
         for node in DepthFirstIterator(self.getController().getScene().getRoot()):
-            if type(node) is not SceneNode:
+            if not issubclass(type(node), SceneNode):
                 continue
             if (not node.getMeshData() and not node.callDecoration("getLayerData")) and not node.callDecoration("isGroup"):
                 continue  # Node that doesnt have a mesh and is not a group.
@@ -1054,7 +1061,7 @@ class CuraApplication(QtApplication):
         Logger.log("i", "Resetting all scene translations")
         nodes = []
         for node in DepthFirstIterator(self.getController().getScene().getRoot()):
-            if type(node) is not SceneNode:
+            if not issubclass(type(node), SceneNode):
                 continue
             if not node.getMeshData() and not node.callDecoration("isGroup"):
                 continue  # Node that doesnt have a mesh and is not a group.
@@ -1082,13 +1089,13 @@ class CuraApplication(QtApplication):
         Logger.log("i", "Resetting all scene transformations")
         nodes = []
         for node in DepthFirstIterator(self.getController().getScene().getRoot()):
-            if type(node) is not SceneNode:
+            if not issubclass(type(node), SceneNode):
                 continue
             if not node.getMeshData() and not node.callDecoration("isGroup"):
                 continue  # Node that doesnt have a mesh and is not a group.
             if node.getParent() and node.getParent().callDecoration("isGroup"):
                 continue  # Grouped nodes don't need resetting as their parent (the group) is resetted)
-            if not node.isSelectable():
+            if not node.callDecoration("isSliceable"):
                 continue  # i.e. node with layer data
             nodes.append(node)
 
@@ -1109,26 +1116,27 @@ class CuraApplication(QtApplication):
     def arrangeObjectsToAllBuildPlates(self):
         nodes = []
         for node in DepthFirstIterator(self.getController().getScene().getRoot()):
-            if type(node) is not SceneNode:
+            if not issubclass(type(node), SceneNode):
                 continue
             if not node.getMeshData() and not node.callDecoration("isGroup"):
                 continue  # Node that doesnt have a mesh and is not a group.
             if node.getParent() and node.getParent().callDecoration("isGroup"):
                 continue  # Grouped nodes don't need resetting as their parent (the group) is resetted)
-            if not node.isSelectable():
+            if not node.callDecoration("isSliceable"):
                 continue  # i.e. node with layer data
             # Skip nodes that are too big
             if node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
                 nodes.append(node)
         job = ArrangeObjectsAllBuildPlatesJob(nodes)
         job.start()
+        self.setActiveBuildPlate(0)
 
     # Single build plate
     @pyqtSlot()
     def arrangeAll(self):
         nodes = []
         for node in DepthFirstIterator(self.getController().getScene().getRoot()):
-            if type(node) is not SceneNode:
+            if not issubclass(type(node), SceneNode):
                 continue
             if not node.getMeshData() and not node.callDecoration("isGroup"):
                 continue  # Node that doesnt have a mesh and is not a group.
@@ -1136,6 +1144,8 @@ class CuraApplication(QtApplication):
                 continue  # Grouped nodes don't need resetting as their parent (the group) is resetted)
             if not node.isSelectable():
                 continue  # i.e. node with layer data
+            if not node.callDecoration("isSliceable"):
+                continue  # i.e. node with layer data
             if node.callDecoration("getBuildPlateNumber") == self._active_build_plate:
                 # Skip nodes that are too big
                 if node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
@@ -1150,7 +1160,7 @@ class CuraApplication(QtApplication):
         # What nodes are on the build plate and are not being moved
         fixed_nodes = []
         for node in DepthFirstIterator(self.getController().getScene().getRoot()):
-            if type(node) is not SceneNode:
+            if not issubclass(type(node), SceneNode):
                 continue
             if not node.getMeshData() and not node.callDecoration("isGroup"):
                 continue  # Node that doesnt have a mesh and is not a group.
@@ -1158,6 +1168,8 @@ class CuraApplication(QtApplication):
                 continue  # Grouped nodes don't need resetting as their parent (the group) is resetted)
             if not node.isSelectable():
                 continue  # i.e. node with layer data
+            if not node.callDecoration("isSliceable"):
+                continue  # i.e. node with layer data
             if node in nodes:  # exclude selected node from fixed_nodes
                 continue
             fixed_nodes.append(node)
@@ -1176,7 +1188,7 @@ class CuraApplication(QtApplication):
         Logger.log("i", "Reloading all loaded mesh data.")
         nodes = []
         for node in DepthFirstIterator(self.getController().getScene().getRoot()):
-            if type(node) is not SceneNode or not node.getMeshData():
+            if not issubclass(type(node), SceneNode) or not node.getMeshData():
                 continue
 
             nodes.append(node)
@@ -1267,7 +1279,7 @@ class CuraApplication(QtApplication):
     @pyqtSlot()
     def groupSelected(self):
         # Create a group-node
-        group_node = SceneNode()
+        group_node = CuraSceneNode()
         group_decorator = GroupDecorator()
         group_node.addDecorator(group_decorator)
         group_node.addDecorator(ConvexHullDecorator())
@@ -1413,11 +1425,15 @@ class CuraApplication(QtApplication):
         min_offset = 8
 
         self.fileLoaded.emit(filename)
+        arrange_objects_on_load = Preferences.getInstance().getValue("cura/arrange_objects_on_load")
+        target_build_plate = self.activeBuildPlate if arrange_objects_on_load else -1
+
+        for original_node in nodes:
+            node = CuraSceneNode()  # We want our own CuraSceneNode
+            node.setMeshData(original_node.getMeshData())
 
-        for node in nodes:
             node.setSelectable(True)
             node.setName(os.path.basename(filename))
-            node.addDecorator(BuildPlateDecorator())
 
             extension = os.path.splitext(filename)[1]
             if extension.lower() in self._non_sliceable_extensions:
@@ -1442,20 +1458,23 @@ class CuraApplication(QtApplication):
                 if not child.getDecorator(ConvexHullDecorator):
                     child.addDecorator(ConvexHullDecorator())
 
-            if node.callDecoration("isSliceable"):
-                # Only check position if it's not already blatantly obvious that it won't fit.
-                if node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
-                    # Find node location
-                    offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = min_offset)
+            if arrange_objects_on_load:
+                if node.callDecoration("isSliceable"):
+                    # Only check position if it's not already blatantly obvious that it won't fit.
+                    if node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
+                        # Find node location
+                        offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = min_offset)
+
+                        # If a model is to small then it will not contain any points
+                        if offset_shape_arr is None and hull_shape_arr is None:
+                            Message(self._i18n_catalog.i18nc("@info:status", "The selected model was too small to load."),
+                                    title=self._i18n_catalog.i18nc("@info:title", "Warning")).show()
+                            return
 
-                    # If a model is to small then it will not contain any points
-                    if offset_shape_arr is None and hull_shape_arr is None:
-                        Message(self._i18n_catalog.i18nc("@info:status", "The selected model was too small to load."),
-                                title=self._i18n_catalog.i18nc("@info:title", "Warning")).show()
-                        return
+                        # Step is for skipping tests to make it a lot faster. it also makes the outcome somewhat rougher
+                        node, _ = arranger.findNodePlacement(node, offset_shape_arr, hull_shape_arr, step = 10)
 
-                    # Step is for skipping tests to make it a lot faster. it also makes the outcome somewhat rougher
-                    node, _ = arranger.findNodePlacement(node, offset_shape_arr, hull_shape_arr, step = 10)
+            node.addDecorator(BuildPlateDecorator(target_build_plate))
 
             op = AddSceneNodeOperation(node, scene.getRoot())
             op.push()
@@ -1494,6 +1513,8 @@ class CuraApplication(QtApplication):
     #### research - hacky place for these kind of thing
     @pyqtSlot(int)
     def setActiveBuildPlate(self, nr):
+        if nr == self._active_build_plate:
+            return
         Logger.log("d", "Select build plate: %s" % nr)
         self._active_build_plate = nr
 

+ 26 - 3
cura/ObjectManager.py

@@ -7,23 +7,35 @@ from UM.Scene.SceneNode import SceneNode
 from UM.Scene.Selection import Selection
 from PyQt5.QtCore import Qt
 from PyQt5.QtWidgets import QApplication
+#from cura.Scene.CuraSceneNode import CuraSceneNode
+from UM.Preferences import Preferences
 
 
 class ObjectManager(ListModel):
     def __init__(self):
         super().__init__()
         self._last_selected_index = 0
-        Application.getInstance().getController().getScene().sceneChanged.connect(self._update)
+        Application.getInstance().getController().getScene().sceneChanged.connect(self._update_scene_changed)
+        Preferences.getInstance().preferenceChanged.connect(self._update)
+        Application.getInstance().activeBuildPlateChanged.connect(self._update)
 
     def _update(self, *args):
         nodes = []
+        filter_current_build_plate = Preferences.getInstance().getValue("view/filter_current_build_plate")
+        active_build_plate_number = Application.getInstance().activeBuildPlate
         for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()):
-            if type(node) is not SceneNode or (not node.getMeshData() and not node.callDecoration("getLayerData")):
+            if not issubclass(type(node), SceneNode) or (not node.getMeshData() and not node.callDecoration("getLayerData")):
+                continue
+            if not node.callDecoration("isSliceable"):
+                continue
+            node_build_plate_number = node.callDecoration("getBuildPlateNumber")
+            if filter_current_build_plate and node_build_plate_number != active_build_plate_number:
                 continue
             nodes.append({
                 "name": node.getName(),
                 "isSelected": Selection.isSelected(node),
-                "buildPlateNumber": node.callDecoration("getBuildPlateNumber"),
+                "isOutsideBuildArea": node.isOutsideBuildArea(),
+                "buildPlateNumber": node_build_plate_number,
                 "node": node
             })
         nodes = sorted(nodes, key=lambda n: n["name"])
@@ -31,6 +43,12 @@ class ObjectManager(ListModel):
 
         self.itemsChanged.emit()
 
+    def _update_scene_changed(self, *args):
+        # if args and type(args[0]) is not CuraSceneNode:
+        #     Logger.log("d", "   ascdf %s", args)
+        #     return
+        self._update(*args)
+
     ##  Either select or deselect an item
     @pyqtSlot(int)
     def changeSelection(self, index):
@@ -63,6 +81,11 @@ class ObjectManager(ListModel):
 
         self._last_selected_index = index
 
+        # testing
+        for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()):
+            if node.callDecoration("getLayerData"):
+                Logger.log("d", "   ##### NODE: %s", node)
+
     @staticmethod
     def createObjectManager():
         return ObjectManager()

+ 7 - 0
cura/Scene/BuildPlateDecorator.py

@@ -6,11 +6,14 @@ from UM.Logger import Logger
 class BuildPlateDecorator(SceneNodeDecorator):
     def __init__(self, build_plate_number = -1):
         super().__init__()
+        self._build_plate_number = None
+        self._previous_build_plate_number = None
         self.setBuildPlateNumber(build_plate_number)
 
     def setBuildPlateNumber(self, nr):
         # Make sure that groups are set correctly
         # setBuildPlateForSelection in CuraActions makes sure that no single childs are set.
+        self._previous_build_plate_number = self._build_plate_number
         self._build_plate_number = nr
         if self._node and self._node.callDecoration("isGroup"):
             for child in self._node.getChildren():
@@ -19,5 +22,9 @@ class BuildPlateDecorator(SceneNodeDecorator):
     def getBuildPlateNumber(self):
         return self._build_plate_number
 
+    # Used to determine from what build plate the node moved.
+    def getPreviousBuildPlateNumber(self):
+        return self._previous_build_plate_number
+
     def __deepcopy__(self, memo):
         return BuildPlateDecorator()

+ 40 - 0
cura/Scene/CuraSceneNode.py

@@ -0,0 +1,40 @@
+from UM.Application import Application
+from UM.Logger import Logger
+from UM.Scene.SceneNode import SceneNode
+from copy import deepcopy
+
+
+##  Scene nodes that are models are only seen when selecting the corresponding build plate
+#   Note that many other nodes can just be UM SceneNode objects.
+class CuraSceneNode(SceneNode):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self._outside_buildarea = True
+
+    def setOutsideBuildArea(self, new_value):
+        self._outside_buildarea = new_value
+
+    def isOutsideBuildArea(self):
+        return self._outside_buildarea or self.callDecoration("getBuildPlateNumber") < 0
+
+    def isVisible(self):
+        return super().isVisible() and self.callDecoration("getBuildPlateNumber") == Application.getInstance().activeBuildPlate
+
+    def isSelectable(self) -> bool:
+        return super().isSelectable() and self.callDecoration("getBuildPlateNumber") == Application.getInstance().activeBuildPlate
+
+    ##  Taken from SceneNode, but replaced SceneNode with CuraSceneNode
+    def __deepcopy__(self, memo):
+        copy = CuraSceneNode()
+        copy.setTransformation(self.getLocalTransformation())
+        copy.setMeshData(self._mesh_data)
+        copy.setVisible(deepcopy(self._visible, memo))
+        copy._selectable = deepcopy(self._selectable, memo)
+        copy._name = deepcopy(self._name, memo)
+        for decorator in self._decorators:
+            copy.addDecorator(deepcopy(decorator, memo))
+
+        for child in self._children:
+            copy.addChild(deepcopy(child, memo))
+        self.calculateBoundingBoxMesh()
+        return copy

+ 2 - 1
plugins/3MFReader/ThreeMFReader.py

@@ -15,7 +15,8 @@ from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
 from UM.Application import Application
 from cura.Settings.ExtruderManager import ExtruderManager
 from cura.QualityManager import QualityManager
-from UM.Scene.SceneNode import SceneNode
+#from UM.Scene.SceneNode import SceneNode
+from cura.Scene.CuraSceneNode import CuraSceneNode as SceneNode
 from cura.SliceableObjectDecorator import SliceableObjectDecorator
 from cura.ZOffsetDecorator import ZOffsetDecorator
 

+ 104 - 40
plugins/CuraEngineBackend/CuraEngineBackend.py

@@ -69,9 +69,10 @@ class CuraEngineBackend(QObject, Backend):
         # Workaround to disable layer view processing if layer view is not active.
         self._layer_view_active = False
         Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged)
+        Application.getInstance().activeBuildPlateChanged.connect(self._onActiveViewChanged)
         self._onActiveViewChanged()
         self._stored_layer_data = []
-        self._stored_optimized_layer_data = []
+        self._stored_optimized_layer_data = {}  # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob
 
         self._scene = Application.getInstance().getController().getScene()
         self._scene.sceneChanged.connect(self._onSceneChanged)
@@ -104,12 +105,14 @@ class CuraEngineBackend(QObject, Backend):
         self._message_handlers["cura.proto.SlicingFinished"] = self._onSlicingFinishedMessage
 
         self._start_slice_job = None
+        self._start_slice_job_build_plate = None
         self._slicing = False  # Are we currently slicing?
         self._restart = False  # Back-end is currently restarting?
         self._tool_active = False  # If a tool is active, some tasks do not have to do anything
         self._always_restart = True  # Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness.
         self._process_layers_job = None  # The currently active job to process layers, or None if it is not processing layers.
-        self._need_slicing = False
+        # self._need_slicing = False
+        self._build_plates_to_be_sliced = []  # what needs slicing?
         self._engine_is_fresh = True  # Is the newly started engine used before or not?
 
         self._backend_log_max_lines = 20000  # Maximum number of lines to buffer
@@ -189,8 +192,9 @@ class CuraEngineBackend(QObject, Backend):
 
     ##  Perform a slice of the scene.
     def slice(self):
+        Logger.log("d", "starting to slice again!")
         self._slice_start_time = time()
-        if not self._need_slicing:
+        if not self._build_plates_to_be_sliced:
             self.processingProgress.emit(1.0)
             self.backendStateChange.emit(BackendState.Done)
             Logger.log("w", "Slice unnecessary, nothing has changed that needs reslicing.")
@@ -199,7 +203,6 @@ class CuraEngineBackend(QObject, Backend):
             Application.getInstance().getPrintInformation().setToZeroPrintInformation()
 
         self._stored_layer_data = []
-        self._stored_optimized_layer_data = []
 
         if self._process is None:
             self._createSocket()
@@ -215,6 +218,9 @@ class CuraEngineBackend(QObject, Backend):
 
         slice_message = self._socket.createMessage("cura.proto.Slice")
         self._start_slice_job = StartSliceJob.StartSliceJob(slice_message)
+        self._start_slice_job_build_plate = self._build_plates_to_be_sliced.pop(0)
+        self._stored_optimized_layer_data[self._start_slice_job_build_plate] = []
+        self._start_slice_job.setBuildPlate(self._start_slice_job_build_plate)
         self._start_slice_job.start()
         self._start_slice_job.finished.connect(self._onStartSliceCompleted)
 
@@ -223,7 +229,8 @@ class CuraEngineBackend(QObject, Backend):
     def _terminate(self):
         self._slicing = False
         self._stored_layer_data = []
-        self._stored_optimized_layer_data = []
+        if self._start_slice_job_build_plate in self._stored_optimized_layer_data:
+            del self._stored_optimized_layer_data[self._start_slice_job_build_plate]
         if self._start_slice_job is not None:
             self._start_slice_job.cancel()
 
@@ -315,10 +322,13 @@ class CuraEngineBackend(QObject, Backend):
                 self._error_message = Message(catalog.i18nc("@info:status", "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit."),
                                               title = catalog.i18nc("@info:title", "Unable to slice"))
                 self._error_message.show()
-                self.backendStateChange.emit(BackendState.Error)
+                #self.backendStateChange.emit(BackendState.Error)
             else:
-                self.backendStateChange.emit(BackendState.NotStarted)
+                #self.backendStateChange.emit(BackendState.NotStarted)
+                pass
+            self._invokeSlice()
             return
+
         # Preparation completed, send it to the backend.
         self._socket.sendMessage(job.getSliceMessage())
 
@@ -360,27 +370,34 @@ class CuraEngineBackend(QObject, Backend):
     #
     #   \param source The scene node that was changed.
     def _onSceneChanged(self, source):
-        if type(source) is not SceneNode:
+        Logger.log("d", "  ##### scene changed: %s", source)
+        if not issubclass(type(source), SceneNode):
             return
 
         root_scene_nodes_changed = False
+        build_plates_changed = set()
         if source == self._scene.getRoot():
             num_objects = 0
             for node in DepthFirstIterator(self._scene.getRoot()):
                 # Only count sliceable objects
                 if node.callDecoration("isSliceable"):
                     num_objects += 1
+                    build_plates_changed.add(node.callDecoration("getBuildPlateNumber"))
+                    build_plates_changed.add(node.callDecoration("getPreviousBuildPlateNumber"))
             if num_objects != self._last_num_objects:
                 self._last_num_objects = num_objects
                 root_scene_nodes_changed = True
-            else:
-                return
-
-        if not source.callDecoration("isGroup") and not root_scene_nodes_changed:
-            if source.getMeshData() is None:
-                return
-            if source.getMeshData().getVertices() is None:
-                return
+            # else:
+            #     return  # ??
+        build_plates_changed.discard(None)
+        build_plates_changed.discard(-1)  # object not on build plate
+        Logger.log("d", "    #### build plates changed: %s", build_plates_changed)
+
+        # if not source.callDecoration("isGroup") and not root_scene_nodes_changed:
+        #     if source.getMeshData() is None:
+        #         return
+        #     if source.getMeshData().getVertices() is None:
+        #         return
 
         if self._tool_active:
             # do it later, each source only has to be done once
@@ -388,9 +405,24 @@ class CuraEngineBackend(QObject, Backend):
                 self._postponed_scene_change_sources.append(source)
             return
 
-        self.needsSlicing()
-        self.stopSlicing()
-        self._onChanged()
+        if build_plates_changed:
+            Logger.log("d", " going to reslice")
+            self.stopSlicing()
+            for build_plate_number in build_plates_changed:
+                if build_plate_number not in self._build_plates_to_be_sliced:
+                    self._build_plates_to_be_sliced.append(build_plate_number)
+            self.processingProgress.emit(0.0)
+            self.backendStateChange.emit(BackendState.NotStarted)
+            if not self._use_timer:
+                # With manually having to slice, we want to clear the old invalid layer data.
+                self._clearLayerData(build_plates_changed)
+
+            self._invokeSlice()
+
+        # #self.needsSlicing()
+        # self.stopSlicing()
+        # #self._onChanged()
+        # self._invokeSlice()
 
     ##  Called when an error occurs in the socket connection towards the engine.
     #
@@ -410,16 +442,24 @@ class CuraEngineBackend(QObject, Backend):
             Logger.log("w", "A socket error caused the connection to be reset")
 
     ##  Remove old layer data (if any)
-    def _clearLayerData(self):
+    def _clearLayerData(self, build_plate_numbers = set()):
         for node in DepthFirstIterator(self._scene.getRoot()):
             if node.callDecoration("getLayerData"):
-                node.getParent().removeChild(node)
-                break
-
-    ##  Convenient function: set need_slicing, emit state and clear layer data
+                if node.callDecoration("getBuildPlateNumber") in build_plate_numbers or not build_plate_numbers:
+                    node.getParent().removeChild(node)
+
+    def markSliceAll(self):
+        if 0 not in self._build_plates_to_be_sliced:
+            self._build_plates_to_be_sliced.append(0)
+        if 1 not in self._build_plates_to_be_sliced:
+            self._build_plates_to_be_sliced.append(1)
+        if 2 not in self._build_plates_to_be_sliced:
+            self._build_plates_to_be_sliced.append(2)
+
+    ##  Convenient function: mark everything to slice, emit state and clear layer data
     def needsSlicing(self):
         self.stopSlicing()
-        self._need_slicing = True
+        self.markSliceAll()
         self.processingProgress.emit(0.0)
         self.backendStateChange.emit(BackendState.NotStarted)
         if not self._use_timer:
@@ -441,7 +481,7 @@ class CuraEngineBackend(QObject, Backend):
 
     def _onStackErrorCheckFinished(self):
         self._is_error_check_scheduled = False
-        if not self._slicing and self._need_slicing:
+        if not self._slicing and self._build_plates_to_be_sliced:  #self._need_slicing:
             self.needsSlicing()
             self._onChanged()
 
@@ -455,7 +495,7 @@ class CuraEngineBackend(QObject, Backend):
     #
     #   \param message The protobuf message containing sliced layer data.
     def _onOptimizedLayerMessage(self, message):
-        self._stored_optimized_layer_data.append(message)
+        self._stored_optimized_layer_data[self._start_slice_job_build_plate].append(message)
 
     ##  Called when a progress message is received from the engine.
     #
@@ -464,6 +504,16 @@ class CuraEngineBackend(QObject, Backend):
         self.processingProgress.emit(message.amount)
         self.backendStateChange.emit(BackendState.Processing)
 
+    # testing
+    def _invokeSlice(self):
+        if self._use_timer:
+            # if the error check is scheduled, wait for the error check finish signal to trigger auto-slice,
+            # otherwise business as usual
+            if self._is_error_check_scheduled:
+                self._change_timer.stop()
+            else:
+                self._change_timer.start()
+
     ##  Called when the engine sends a message that slicing is finished.
     #
     #   \param message The protobuf message signalling that slicing is finished.
@@ -481,13 +531,20 @@ class CuraEngineBackend(QObject, Backend):
             self._scene.gcode_list[self._scene.gcode_list.index(line)] = replaced
 
         self._slicing = False
-        self._need_slicing = False
+        #self._need_slicing = False
         Logger.log("d", "Slicing took %s seconds", time() - self._slice_start_time )
-        if self._layer_view_active and (self._process_layers_job is None or not self._process_layers_job.isRunning()):
-            self._process_layers_job = ProcessSlicedLayersJob.ProcessSlicedLayersJob(self._stored_optimized_layer_data)
-            self._process_layers_job.finished.connect(self._onProcessLayersFinished)
-            self._process_layers_job.start()
-            self._stored_optimized_layer_data = []
+
+        # See if we need to process the sliced layers job.
+        active_build_plate = Application.getInstance().activeBuildPlate
+        if self._layer_view_active and (self._process_layers_job is None or not self._process_layers_job.isRunning()) and active_build_plate == self._start_slice_job_build_plate:
+            self._startProcessSlicedLayersJob(active_build_plate)
+        self._start_slice_job_build_plate = None
+
+        Logger.log("d", "See if there is more to slice...")
+        # Somehow this results in an Arcus Error
+        # self.slice()
+        # Testing call slice again, allow backend to restart by using the timer
+        self._invokeSlice()
 
     ##  Called when a g-code message is received from the engine.
     #
@@ -584,19 +641,26 @@ class CuraEngineBackend(QObject, Backend):
             source = self._postponed_scene_change_sources.pop(0)
             self._onSceneChanged(source)
 
+    def _startProcessSlicedLayersJob(self, build_plate_number):
+        self._process_layers_job = ProcessSlicedLayersJob.ProcessSlicedLayersJob(self._stored_optimized_layer_data[build_plate_number])
+        self._process_layers_job.setBuildPlate(build_plate_number)
+        self._process_layers_job.finished.connect(self._onProcessLayersFinished)
+        self._process_layers_job.start()
+        del self._stored_optimized_layer_data[build_plate_number]
+
     ##  Called when the user changes the active view mode.
     def _onActiveViewChanged(self):
-        if Application.getInstance().getController().getActiveView():
-            view = Application.getInstance().getController().getActiveView()
+        application = Application.getInstance()
+        view = application.getController().getActiveView()
+        if view:
+            active_build_plate = application.activeBuildPlate
             if view.getPluginId() == "LayerView":  # If switching to layer view, we should process the layers if that hasn't been done yet.
                 self._layer_view_active = True
                 # There is data and we're not slicing at the moment
                 # if we are slicing, there is no need to re-calculate the data as it will be invalid in a moment.
-                if self._stored_optimized_layer_data and not self._slicing:
-                    self._process_layers_job = ProcessSlicedLayersJob.ProcessSlicedLayersJob(self._stored_optimized_layer_data)
-                    self._process_layers_job.finished.connect(self._onProcessLayersFinished)
-                    self._process_layers_job.start()
-                    self._stored_optimized_layer_data = []
+                # TODO: what build plate I am slicing
+                if active_build_plate in self._stored_optimized_layer_data and not self._slicing:
+                    self._startProcessSlicedLayersJob(active_build_plate)
             else:
                 self._layer_view_active = False
 

+ 19 - 10
plugins/CuraEngineBackend/ProcessSlicedLayersJob.py

@@ -17,10 +17,12 @@ from UM.Logger import Logger
 
 from UM.Math.Vector import Vector
 
+from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
 from cura.Settings.ExtruderManager import ExtruderManager
 from cura import LayerDataBuilder
 from cura import LayerDataDecorator
 from cura import LayerPolygon
+# from cura.Scene.CuraSceneNode import CuraSceneNode
 
 import numpy
 from time import time
@@ -49,6 +51,7 @@ class ProcessSlicedLayersJob(Job):
         self._scene = Application.getInstance().getController().getScene()
         self._progress_message = Message(catalog.i18nc("@info:status", "Processing Layers"), 0, False, -1)
         self._abort_requested = False
+        self._build_plate_number = None
 
     ##  Aborts the processing of layers.
     #
@@ -59,7 +62,11 @@ class ProcessSlicedLayersJob(Job):
     def abort(self):
         self._abort_requested = True
 
+    def setBuildPlate(self, new_value):
+        self._build_plate_number = new_value
+
     def run(self):
+        Logger.log("d", "########## Processing new layer for [%s]..." % self._build_plate_number)
         start_time = time()
         if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView":
             self._progress_message.show()
@@ -72,16 +79,18 @@ class ProcessSlicedLayersJob(Job):
         Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged)
 
         new_node = SceneNode()
-
-        ## Remove old layer data (if any)
-        for node in DepthFirstIterator(self._scene.getRoot()):
-            if node.callDecoration("getLayerData"):
-                node.getParent().removeChild(node)
-                break
-            if self._abort_requested:
-                if self._progress_message:
-                    self._progress_message.hide()
-                return
+        new_node.addDecorator(BuildPlateDecorator(self._build_plate_number))
+
+        # ## Remove old layer data (if any)
+        # for node in DepthFirstIterator(self._scene.getRoot()):
+        #     if node.callDecoration("getLayerData") and node.callDecoration("getBuildPlateNumber") == self._build_plate_number:
+        #         Logger.log("d", "   # Removing: %s", node)
+        #         node.getParent().removeChild(node)
+        #         #break
+        #     if self._abort_requested:
+        #         if self._progress_message:
+        #             self._progress_message.hide()
+        #         return
 
         # Force garbage collection.
         # For some reason, Python has a tendency to keep the layer data

Some files were not shown because too many files changed in this diff