Exemple #1
0
    def __init__(self, node, hull, thickness, parent = None):
        super().__init__(parent)

        self.setCalculateBoundingBox(False)

        self._original_parent = parent

        # Color of the drawn convex hull
        if Application.getInstance().hasGui():
            self._color = Color(*Application.getInstance().getTheme().getColor("convex_hull").getRgb())
        else:
            self._color = Color(0, 0, 0)

        # The y-coordinate of the convex hull mesh. Must not be 0, to prevent z-fighting.
        self._mesh_height = 0.1

        self._thickness = thickness

        # The node this mesh is "watching"
        self._node = node
        self._convex_hull_head_mesh = None

        self._node.decoratorsChanged.connect(self._onNodeDecoratorsChanged)
        self._onNodeDecoratorsChanged(self._node)

        self._hull = hull
        if self._hull:
            hull_mesh_builder = MeshBuilder()

            if hull_mesh_builder.addConvexPolygonExtrusion(
                self._hull.getPoints()[::-1],  # bottom layer is reversed
                self._mesh_height-thickness, self._mesh_height, color=self._color):

                hull_mesh = hull_mesh_builder.build()
                self.setMeshData(hull_mesh)
Exemple #2
0
    def __init__(self, node, hull, thickness, parent=None):
        super().__init__(parent)

        self.setCalculateBoundingBox(False)

        self._original_parent = parent

        # Color of the drawn convex hull
        self._color = Color(*Application.getInstance().getTheme().getColor(
            "convex_hull").getRgb())

        # The y-coordinate of the convex hull mesh. Must not be 0, to prevent z-fighting.
        self._mesh_height = 0.1

        self._thickness = thickness

        # The node this mesh is "watching"
        self._node = node
        self._convex_hull_head_mesh = None

        self._node.decoratorsChanged.connect(self._onNodeDecoratorsChanged)
        self._onNodeDecoratorsChanged(self._node)

        self._hull = hull
        if self._hull:
            hull_mesh_builder = MeshBuilder()

            if hull_mesh_builder.addConvexPolygonExtrusion(
                    self._hull.getPoints()[::-1],  # bottom layer is reversed
                    self._mesh_height - thickness,
                    self._mesh_height,
                    color=self._color):

                hull_mesh = hull_mesh_builder.build()
                self.setMeshData(hull_mesh)
Exemple #3
0
    def __init__(self, node: SceneNode, hull: Optional[Polygon], thickness: float, parent: Optional[SceneNode] = None) -> None:
        """Convex hull node is a special type of scene node that is used to display an area, to indicate the

        location an object uses on the buildplate. This area (or area's in case of one at a time printing) is
        then displayed as a transparent shadow. If the adhesion type is set to raft, the area is extruded
        to represent the raft as well.
        """
        super().__init__(parent)

        self.setCalculateBoundingBox(False)

        self._original_parent = parent

        # Color of the drawn convex hull
        if not Application.getInstance().getIsHeadLess():
            theme = QtApplication.getInstance().getTheme()
            if theme:
                self._color = Color(*theme.getColor("convex_hull").getRgb())
            else:
                self._color = Color(0, 0, 0)
        else:
            self._color = Color(0, 0, 0)

        # The y-coordinate of the convex hull mesh. Must not be 0, to prevent z-fighting.
        self._mesh_height = 0.1

        self._thickness = thickness

        # The node this mesh is "watching"
        self._node = node
        # Area of the head + fans for display as a shadow on the buildplate
        self._convex_hull_head_mesh = None  # type: Optional[MeshData]

        self._node.decoratorsChanged.connect(self._onNodeDecoratorsChanged)
        self._onNodeDecoratorsChanged(self._node)

        self._hull = hull
        if self._hull:
            hull_mesh_builder = MeshBuilder()
            if self._thickness == 0:
                if hull_mesh_builder.addConvexPolygon(
                    self._hull.getPoints()[::],  # bottom layer is reversed
                    self._mesh_height, color = self._color):
                    hull_mesh_builder.resetNormals()

                    hull_mesh = hull_mesh_builder.build()
                    self.setMeshData(hull_mesh)
            else:
                if hull_mesh_builder.addConvexPolygonExtrusion(
                    self._hull.getPoints()[::-1],  # bottom layer is reversed
                    self._mesh_height - thickness, self._mesh_height, color = self._color):
                    hull_mesh_builder.resetNormals()
                    hull_mesh = hull_mesh_builder.build()
                    self.setMeshData(hull_mesh)
Exemple #4
0
    def __init__(self,
                 node: SceneNode,
                 hull: Optional[Polygon],
                 thickness: float,
                 parent: Optional[SceneNode] = None) -> None:
        super().__init__(parent)

        self.setCalculateBoundingBox(False)

        self._original_parent = parent

        # Color of the drawn convex hull
        if not Application.getInstance().getIsHeadLess():
            theme = QtApplication.getInstance().getTheme()
            if theme:
                self._color = Color(*theme.getColor("convex_hull").getRgb())
            else:
                self._color = Color(0, 0, 0)
        else:
            self._color = Color(0, 0, 0)

        # The y-coordinate of the convex hull mesh. Must not be 0, to prevent z-fighting.
        self._mesh_height = 0.1

        self._thickness = thickness

        # The node this mesh is "watching"
        self._node = node
        # Area of the head + fans for display as a shadow on the buildplate
        self._convex_hull_head_mesh = None  # type: Optional[MeshData]

        self._node.decoratorsChanged.connect(self._onNodeDecoratorsChanged)
        self._onNodeDecoratorsChanged(self._node)

        self._hull = hull
        if self._hull:
            hull_mesh_builder = MeshBuilder()

            if hull_mesh_builder.addConvexPolygonExtrusion(
                    self._hull.getPoints()[::-1],  # bottom layer is reversed
                    self._mesh_height - thickness,
                    self._mesh_height,
                    color=self._color):

                hull_mesh = hull_mesh_builder.build()
                self.setMeshData(hull_mesh)
Exemple #5
0
    def run(self) -> None:
        if self._build_plate_number is None:
            self.setResult(StartJobResult.Error)
            return

        stack = CuraApplication.getInstance().getGlobalContainerStack()
        if not stack:
            self.setResult(StartJobResult.Error)
            return

        # Don't slice if there is a setting with an error value.
        if CuraApplication.getInstance().getMachineManager().stacksHaveErrors:
            self.setResult(StartJobResult.SettingError)
            return

        if CuraApplication.getInstance().getBuildVolume().hasErrors():
            self.setResult(StartJobResult.BuildPlateError)
            return

        # Don't slice if the buildplate or the nozzle type is incompatible with the materials
        if not CuraApplication.getInstance().getMachineManager().variantBuildplateCompatible and \
                not CuraApplication.getInstance().getMachineManager().variantBuildplateUsable:
            self.setResult(StartJobResult.MaterialIncompatible)
            return

        for position, extruder_stack in stack.extruders.items():
            material = extruder_stack.findContainer({"type": "material"})
            if not extruder_stack.isEnabled:
                continue
            if material:
                if material.getMetaDataEntry("compatible") == False:
                    self.setResult(StartJobResult.MaterialIncompatible)
                    return

        # Don't slice if there is a per object setting with an error value.
        for node in DepthFirstIterator(
                self._scene.getRoot()
        ):  #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
            if not isinstance(node, CuraSceneNode) or not node.isSelectable():
                continue

            if self._checkStackForErrors(node.callDecoration("getStack")):
                self.setResult(StartJobResult.ObjectSettingError)
                return

        with self._scene.getSceneLock():
            # Remove old layer data.
            for node in DepthFirstIterator(
                    self._scene.getRoot()
            ):  #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
                if node.callDecoration("getLayerData") and node.callDecoration(
                        "getBuildPlateNumber") == self._build_plate_number:
                    node.getParent().removeChild(node)
                    break

            global_enable_support = stack.getProperty("support_enable",
                                                      "value")

            # Get the objects in their groups to print.
            object_groups = []
            if stack.getProperty("print_sequence", "value") == "one_at_a_time":
                # note that one_at_a_time printing is disabled on belt printers due to collission risk
                for node in OneAtATimeIterator(
                        self._scene.getRoot()
                ):  #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
                    temp_list = []

                    # Node can't be printed, so don't bother sending it.
                    if getattr(node, "_outside_buildarea", False):
                        continue

                    # Filter on current build plate
                    build_plate_number = node.callDecoration(
                        "getBuildPlateNumber")
                    if build_plate_number is not None and build_plate_number != self._build_plate_number:
                        continue

                    children = node.getAllChildren()
                    children.append(node)
                    for child_node in children:
                        if 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 = []
                has_printing_mesh = False
                # print convex hull nodes as "faux-raft"
                print_convex_hulls = self._preferences.getValue(
                    "BeltPlugin/raft")
                for node in DepthFirstIterator(
                        self._scene.getRoot()
                ):  #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
                    slice_node = (print_convex_hulls
                                  and type(node) is ConvexHullNode
                                  ) or node.callDecoration("isSliceable")
                    if slice_node and node.getMeshData() and node.getMeshData(
                    ).getVertices() is not None:
                        per_object_stack = node.callDecoration("getStack")
                        is_non_printing_mesh = False
                        if per_object_stack:
                            is_non_printing_mesh = any(
                                per_object_stack.getProperty(key, "value")
                                for key in NON_PRINTING_MESH_SETTINGS)

                        # Find a reason not to add the node
                        if node.callDecoration(
                                "getBuildPlateNumber"
                        ) != self._build_plate_number and type(
                                node) is not ConvexHullNode:
                            # NB: ConvexHullNodes get none of the usual decorators, so skip checking for them
                            continue
                        if getattr(node, "_outside_buildarea",
                                   False) and not is_non_printing_mesh:
                            continue

                        temp_list.append(node)
                        if not is_non_printing_mesh:
                            has_printing_mesh = True

                    Job.yieldThread()

                #If the list doesn't have any model with suitable settings then clean the list
                # otherwise CuraEngine will crash
                if not has_printing_mesh:
                    temp_list.clear()

                if temp_list:
                    object_groups.append(temp_list)

            global_stack = CuraApplication.getInstance(
            ).getGlobalContainerStack()
            if not global_stack:
                return
            extruders_enabled = {
                position: stack.isEnabled
                for position, stack in global_stack.extruders.items()
            }
            filtered_object_groups = []
            has_model_with_disabled_extruders = False
            associated_disabled_extruders = set()
            for group in object_groups:
                stack = global_stack
                skip_group = False
                for node in group:
                    # Only check if the printing extruder is enabled for printing meshes
                    is_non_printing_mesh = node.callDecoration(
                        "evaluateIsNonPrintingMesh")
                    extruder_position = node.callDecoration(
                        "getActiveExtruderPosition")
                    if extruder_position is None:  # raft meshes may not have an extruder position (yet)
                        extruder_position = "0"
                    if not is_non_printing_mesh and not extruders_enabled[
                            extruder_position]:
                        skip_group = True
                        has_model_with_disabled_extruders = True
                        associated_disabled_extruders.add(extruder_position)
                if not skip_group:
                    filtered_object_groups.append(group)

            if has_model_with_disabled_extruders:
                self.setResult(StartJobResult.ObjectsWithDisabledExtruder)
                associated_disabled_extruders = {
                    str(c)
                    for c in sorted(
                        [int(p) + 1 for p in associated_disabled_extruders])
                }
                self.setMessage(", ".join(associated_disabled_extruders))
                return

            # There are cases when there is nothing to slice. This can happen due to one at a time slicing not being
            # able to find a possible sequence or because there are no objects on the build plate (or they are outside
            # the build volume)
            if not filtered_object_groups:
                self.setResult(StartJobResult.NothingToSlice)
                return

            container_registry = ContainerRegistry.getInstance()
            stack_id = stack.getId()

            # Adapt layer_height and material_flow for a slanted gantry
            gantry_angle = self._scene.getRoot().callDecoration(
                "getGantryAngle")
            #gantry_angle = float(self._preferences.getValue("BeltPlugin/gantry_angle"))
            if gantry_angle:  # not 0 or None
                # Act on a copy of the stack, so these changes don't cause a reslice
                _stack = CuraContainerStack(stack_id + "_temp")
                for index, container in enumerate(stack.getContainers()):
                    if container_registry.isReadOnly(container.getId()):
                        _stack.replaceContainer(index, container)
                    else:
                        _stack.replaceContainer(index,
                                                copy.deepcopy(container))
                stack = _stack

                # Make sure CuraEngine does not create any supports
                # support_enable is set in the frontend so support options are settable,
                # but CuraEngine support structures don't work for slanted gantry
                stack.setProperty("support_enable", "value", False)
                # Make sure CuraEngine does not create a raft (we create one manually)
                # Adhesion type is used in the frontend to show the raft in the viewport
                stack.setProperty("adhesion_type", "value", "none")

                for key in ["layer_height", "layer_height_0"]:
                    current_value = stack.getProperty(key, "value")
                    stack.setProperty(key, "value",
                                      current_value / math.sin(gantry_angle))

            self._buildGlobalSettingsMessage(stack)
            self._buildGlobalInheritsStackMessage(stack)

            # Build messages for extruder stacks
            # Send the extruder settings in the order of extruder positions. Somehow, if you send e.g. extruder 3 first,
            # then CuraEngine can slice with the wrong settings. This I think should be fixed in CuraEngine as well.
            extruder_stack_list = sorted(list(global_stack.extruders.items()),
                                         key=lambda item: int(item[0]))
            for _, extruder_stack in extruder_stack_list:
                if gantry_angle:  # not 0 or None
                    # Act on a copy of the stack, so these changes don't cause a reslice
                    _extruder_stack = CuraContainerStack(
                        extruder_stack.getId() + "_temp")
                    for index, container in enumerate(
                            extruder_stack.getContainers()):
                        if container_registry.isReadOnly(container.getId()):
                            _extruder_stack.replaceContainer(index, container)
                        else:
                            _extruder_stack.replaceContainer(
                                index, copy.deepcopy(container))
                    extruder_stack = _extruder_stack
                    extruder_stack.setNextStack(stack)
                    for key in [
                            "material_flow", "prime_tower_flow",
                            "spaghetti_flow"
                    ]:
                        if extruder_stack.hasProperty(key, "value"):
                            current_value = extruder_stack.getProperty(
                                key, "value")
                            extruder_stack.setProperty(
                                key, "value",
                                current_value * math.sin(gantry_angle))
                self._buildExtruderMessage(extruder_stack)

            bottom_cutting_meshes = []
            raft_meshes = []
            support_meshes = []
            if gantry_angle:  # not 0 or None
                for group in filtered_object_groups:
                    added_meshes = []
                    for object in group:

                        is_non_printing_mesh = False
                        per_object_stack = object.callDecoration("getStack")

                        # ConvexHullNodes get none of the usual decorators. If it made it here, it is meant to be printed
                        if type(object) is ConvexHullNode:
                            raft_thickness = self._preferences.getValue(
                                "BeltPlugin/raft_thickness")
                            raft_margin = self._preferences.getValue(
                                "BeltPlugin/raft_margin")

                            mb = MeshBuilder()
                            hull_polygon = object.getHull()
                            if raft_margin > 0:
                                hull_polygon = hull_polygon.getMinkowskiHull(
                                    Polygon.approximatedCircle(raft_margin))
                            mb.addConvexPolygonExtrusion(
                                hull_polygon.getPoints()[::-1], 0,
                                raft_thickness)

                            new_node = self._addMeshFromBuilder(mb, "raftMesh")
                            added_meshes.append(new_node)
                            raft_meshes.append(new_node.getName())

                        elif not is_non_printing_mesh:
                            # add support mesh if needed
                            belt_support_gantry_angle_bias = None
                            belt_support_minimum_island_area = None
                            if per_object_stack:
                                is_non_printing_mesh = any(
                                    per_object_stack.getProperty(key, "value")
                                    for key in NON_PRINTING_MESH_SETTINGS)

                                node_enable_support = per_object_stack.getProperty(
                                    "support_enable", "value")
                                if per_object_stack.getProperty(
                                        "support_mesh", "value"):
                                    node_enable_support = node_enable_support or per_object_stack.getProperty(
                                        "support_mesh_drop_down", "value")
                                add_support_mesh = node_enable_support if node_enable_support is not None else global_enable_support

                                belt_support_gantry_angle_bias = self._preferences.getValue(
                                    "BeltPlugin/support_gantry_angle_bias")
                                belt_support_minimum_island_area = self._preferences.getValue(
                                    "BeltPlugin/support_minimum_island_area")
                            else:
                                add_support_mesh = global_enable_support

                            if add_support_mesh:
                                #preferences = Application.getInstance().getPreferences()
                                if belt_support_gantry_angle_bias is None:
                                    belt_support_gantry_angle_bias = self._preferences.getValue(
                                        "BeltPlugin/support_gantry_angle_bias")
                                biased_down_angle = math.radians(
                                    belt_support_gantry_angle_bias)
                                if belt_support_minimum_island_area is None:
                                    belt_support_minimum_island_area = self._preferences.getValue(
                                        "BeltPlugin/support_minimum_island_area"
                                    )
                                support_mesh_data = SupportMeshCreator(
                                    down_vector=numpy.array([
                                        0, -math.cos(
                                            math.radians(biased_down_angle)),
                                        -math.sin(biased_down_angle)
                                    ]),
                                    bottom_cut_off=stack.getProperty(
                                        "wall_line_width_0", "value") / 2,
                                    minimum_island_area=
                                    belt_support_minimum_island_area
                                ).createSupportMeshForNode(object)
                                if support_mesh_data:
                                    new_node = self._addMeshFromData(
                                        support_mesh_data,
                                        "generatedSupportMesh")
                                    added_meshes.append(new_node)
                                    support_meshes.append(new_node.getName())

                            # check if the bottom needs to be cut off
                            aabb = object.getBoundingBox()

                            if aabb.bottom < 0:
                                # mesh extends below the belt; add a cutting mesh to cut off the part below the bottom
                                height = -aabb.bottom
                                center = Vector(aabb.center.x, -height / 2,
                                                aabb.center.z)

                                mb = MeshBuilder()
                                mb.addCube(width=aabb.width,
                                           height=height,
                                           depth=aabb.depth,
                                           center=center)

                                new_node = self._addMeshFromBuilder(
                                    mb, "bottomCuttingMesh")
                                added_meshes.append(new_node)
                                bottom_cutting_meshes.append(
                                    new_node.getName())

                    if added_meshes:
                        group += added_meshes

            transform_matrix = self._scene.getRoot().callDecoration(
                "getTransformMatrix")
            front_offset = None

            raft_offset = 0
            raft_speed = None
            raft_flow = 1.0

            if self._preferences.getValue("BeltPlugin/raft"):
                raft_offset = self._preferences.getValue(
                    "BeltPlugin/raft_thickness")
                raft_speed = self._preferences.getValue(
                    "BeltPlugin/raft_speed")
                raft_flow = self._preferences.getValue(
                    "BeltPlugin/raft_flow") * math.sin(gantry_angle)

            adhesion_extruder_nr = stack.getProperty("adhesion_extruder_nr",
                                                     "value")
            support_extruder_nr = stack.getProperty("support_extruder_nr",
                                                    "value")

            for group in filtered_object_groups:
                group_message = self._slice_message.addRepeatedMessage(
                    "object_lists")
                if group[0].getParent() is not None and group[0].getParent(
                ).callDecoration("isGroup"):
                    self._handlePerObjectSettings(group[0].getParent(),
                                                  group_message)

                if transform_matrix:
                    scene_front = None
                    for object in group:
                        if type(object) is ConvexHullNode:
                            continue

                        is_non_printing_mesh = object.getName(
                        ) in bottom_cutting_meshes or object.getName(
                        ) in raft_meshes
                        if not is_non_printing_mesh:
                            per_object_stack = object.callDecoration(
                                "getStack")
                            if per_object_stack:
                                is_non_printing_mesh = any(
                                    per_object_stack.getProperty(key, "value")
                                    for key in NON_PRINTING_MESH_SETTINGS)

                        if not is_non_printing_mesh:
                            _front = object.getBoundingBox().back
                            if scene_front is None or _front < scene_front:
                                scene_front = _front

                    if scene_front is not None:
                        front_offset = transformVertices(
                            numpy.array([[0, 0, scene_front]]),
                            transform_matrix)[0][1]

                for object in group:
                    if type(object) is ConvexHullNode:
                        continue

                    mesh_data = object.getMeshData()
                    rot_scale = object.getWorldTransformation().getTransposed(
                    ).getData()[0:3, 0:3]
                    translate = object.getWorldTransformation().getData()[:3,
                                                                          3]
                    # offset all non-raft objects if rafts are enabled
                    # air gap is applied here to vertically offset objects from the raft
                    if object.getName() not in raft_meshes:
                        translate[1] += raft_offset
                    if front_offset:
                        translate[2] -= front_offset

                    # This effectively performs a limited form of MeshData.getTransformed that ignores normals.
                    verts = mesh_data.getVertices()
                    verts = verts.dot(rot_scale)
                    verts += translate

                    if transform_matrix:
                        verts = transformVertices(verts, transform_matrix)

                    # Convert from Y up axes to Z up axes. Equals a 90 degree rotation.
                    verts[:, [1, 2]] = verts[:, [2, 1]]
                    verts[:, 1] *= -1

                    obj = group_message.addRepeatedMessage("objects")
                    obj.id = id(object)

                    obj.name = object.getName()
                    indices = mesh_data.getIndices()
                    if indices is not None:
                        flat_verts = numpy.take(verts,
                                                indices.flatten(),
                                                axis=0)
                    else:
                        flat_verts = numpy.array(verts)

                    obj.vertices = flat_verts

                    if object.getName() in raft_meshes:
                        self._addSettingsMessage(
                            obj, {
                                "wall_line_count": 99999999,
                                "speed_wall_0": raft_speed,
                                "speed_wall_x": raft_speed,
                                "material_flow": raft_flow,
                                "extruder_nr": adhesion_extruder_nr
                            })

                    elif object.getName() in support_meshes:
                        self._addSettingsMessage(
                            obj, {
                                "support_mesh": "True",
                                "support_mesh_drop_down": "False",
                                "extruder_nr": support_extruder_nr
                            })

                    elif object.getName() in bottom_cutting_meshes:
                        self._addSettingsMessage(
                            obj, {
                                "cutting_mesh": True,
                                "wall_line_count": 0,
                                "top_layers": 0,
                                "bottom_layers": 0,
                                "infill_line_distance": 0,
                                "extruder_nr": 0
                            })

                    else:
                        self._handlePerObjectSettings(object, obj)
                    Job.yieldThread()

                # Store the front-most coordinate of the scene so the scene can be moved back into place post slicing
                # TODO: this should be handled per mesh-group instead of per scene
                # One-at-a-time printing should be disabled for slanted gantry printers for now

                self._scene.getRoot().callDecoration("setSceneFrontOffset",
                                                     front_offset)

        self.setResult(StartJobResult.Finished)
Exemple #6
0
    def run(self):
        if self._build_plate_number is None:
            self.setResult(StartJobResult.Error)
            return

        stack = Application.getInstance().getGlobalContainerStack()
        if not stack:
            self.setResult(StartJobResult.Error)
            return

        # Don't slice if there is a setting with an error value.
        if Application.getInstance().getMachineManager().stacksHaveErrors:
            self.setResult(StartJobResult.SettingError)
            return

        if Application.getInstance().getBuildVolume().hasErrors():
            self.setResult(StartJobResult.BuildPlateError)
            return

        # Don't slice if the buildplate or the nozzle type is incompatible with the materials
        if not Application.getInstance().getMachineManager().variantBuildplateCompatible and \
                not Application.getInstance().getMachineManager().variantBuildplateUsable:
            self.setResult(StartJobResult.MaterialIncompatible)
            return

        for position, extruder_stack in stack.extruders.items():
            material = extruder_stack.findContainer({"type": "material"})
            if not extruder_stack.isEnabled:
                continue
            if material:
                if material.getMetaDataEntry("compatible") == False:
                    self.setResult(StartJobResult.MaterialIncompatible)
                    return

        # Don't slice if there is a per object setting with an error value.
        for node in DepthFirstIterator(self._scene.getRoot()):
            if not isinstance(node, CuraSceneNode) or not node.isSelectable():
                continue

            if self._checkStackForErrors(node.callDecoration("getStack")):
                self.setResult(StartJobResult.ObjectSettingError)
                return

        with self._scene.getSceneLock():
            # Remove old layer data.
            for node in DepthFirstIterator(self._scene.getRoot()):
                if node.callDecoration("getLayerData") and node.callDecoration(
                        "getBuildPlateNumber") == self._build_plate_number:
                    node.getParent().removeChild(node)
                    break

            # Get the objects in their groups to print.
            object_groups = []
            if stack.getProperty("print_sequence", "value") == "one_at_a_time":
                for node in OneAtATimeIterator(self._scene.getRoot()):
                    temp_list = []

                    # Node can't be printed, so don't bother sending it.
                    if getattr(node, "_outside_buildarea", False):
                        continue

                    # Filter on current build plate
                    build_plate_number = node.callDecoration(
                        "getBuildPlateNumber")
                    if build_plate_number is not None and build_plate_number != self._build_plate_number:
                        continue

                    children = node.getAllChildren()
                    children.append(node)
                    for child_node in children:
                        if 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 = []
                has_printing_mesh = False
                # print convex hull nodes as "faux-raft"
                print_convex_hulls = stack.getProperty("blackbelt_raft",
                                                       "value")
                for node in DepthFirstIterator(self._scene.getRoot()):
                    slice_node = (print_convex_hulls
                                  and type(node) is ConvexHullNode
                                  ) or node.callDecoration("isSliceable")
                    if slice_node and node.getMeshData() and node.getMeshData(
                    ).getVertices() is not None:
                        per_object_stack = node.callDecoration("getStack")
                        is_non_printing_mesh = False
                        if per_object_stack:
                            is_non_printing_mesh = any(
                                per_object_stack.getProperty(key, "value")
                                for key in NON_PRINTING_MESH_SETTINGS)

                        # Find a reason not to add the node
                        if node.callDecoration(
                                "getBuildPlateNumber"
                        ) != self._build_plate_number and type(
                                node) is not ConvexHullNode:
                            # NB: ConvexHullNodes get none of the usual decorators, so skip checking for them
                            continue
                        if getattr(node, "_outside_buildarea",
                                   False) and not is_non_printing_mesh:
                            continue

                        temp_list.append(node)
                        if not is_non_printing_mesh:
                            has_printing_mesh = True

                    Job.yieldThread()

                #If the list doesn't have any model with suitable settings then clean the list
                # otherwise CuraEngine will crash
                if not has_printing_mesh:
                    temp_list.clear()

                if temp_list:
                    object_groups.append(temp_list)

            extruders_enabled = {
                position: stack.isEnabled
                for position, stack in Application.getInstance().
                getGlobalContainerStack().extruders.items()
            }
            filtered_object_groups = []
            for group in object_groups:
                stack = Application.getInstance().getGlobalContainerStack()
                skip_group = False
                for node in group:
                    # ConvexHullNodes get none of the usual decorators. If it made it here, it is meant to be printed
                    if type(
                            node
                    ) is not ConvexHullNode and not extruders_enabled[
                            node.callDecoration("getActiveExtruderPosition")]:
                        skip_group = True
                        break
                if not skip_group:
                    filtered_object_groups.append(group)

            # There are cases when there is nothing to slice. This can happen due to one at a time slicing not being
            # able to find a possible sequence or because there are no objects on the build plate (or they are outside
            # the build volume)
            if not filtered_object_groups:
                self.setResult(StartJobResult.NothingToSlice)
                return

            container_registry = ContainerRegistry.getInstance()
            stack_id = stack.getId()

            # Adapt layer_height and material_flow for a slanted gantry
            gantry_angle = self._scene.getRoot().callDecoration(
                "getGantryAngle")
            if gantry_angle:  # not 0 or None
                # Act on a copy of the stack, so these changes don't cause a reslice
                _stack = CuraContainerStack(stack_id + "_temp")
                index = 0
                for container in stack.getContainers():
                    if container_registry.isReadOnly(container.getId()):
                        _stack.replaceContainer(index, container)
                    else:
                        _stack.replaceContainer(index,
                                                copy.deepcopy(container))
                    index = index + 1
                stack = _stack

                # Make sure CuraEngine does not create any supports
                # support_enable is set in the frontend so support options are settable,
                # but CuraEngine support structures don't work for slanted gantry
                stack.setProperty("support_enable", "value", False)
                # Make sure CuraEngine does not create a raft (we create one manually)
                # Adhsion type is used in the frontend to show the raft in the viewport
                stack.setProperty("adhesion_type", "value", "none")

                for key in ["layer_height", "layer_height_0"]:
                    current_value = stack.getProperty(key, "value")
                    stack.setProperty(key, "value",
                                      current_value / math.sin(gantry_angle))

            self._buildGlobalSettingsMessage(stack)
            self._buildGlobalInheritsStackMessage(stack)

            # Build messages for extruder stacks
            for position, extruder_stack in Application.getInstance(
            ).getGlobalContainerStack().extruders.items():
                if gantry_angle:  # not 0 or None
                    # Act on a copy of the stack, so these changes don't cause a reslice
                    _extruder_stack = CuraContainerStack(
                        extruder_stack.getId() + "_temp")
                    index = 0
                    for container in extruder_stack.getContainers():
                        if container_registry.isReadOnly(container.getId()):
                            _extruder_stack.replaceContainer(index, container)
                        else:
                            _extruder_stack.replaceContainer(
                                index, copy.deepcopy(container))
                        index = index + 1
                    extruder_stack = _extruder_stack
                    extruder_stack.setNextStack(stack)
                    for key in [
                            "material_flow", "prime_tower_flow",
                            "spaghetti_flow"
                    ]:
                        if extruder_stack.hasProperty(key, "value"):
                            current_value = extruder_stack.getProperty(
                                key, "value")
                            extruder_stack.setProperty(
                                key, "value",
                                current_value * math.sin(gantry_angle))
                self._buildExtruderMessage(extruder_stack)

            bottom_cutting_meshes = []
            raft_meshes = []
            if gantry_angle:  # not 0 or None
                # Add a modifier mesh to all printable meshes touching the belt
                for group in filtered_object_groups:
                    added_meshes = []
                    for object in group:

                        is_non_printing_mesh = False
                        per_object_stack = object.callDecoration("getStack")
                        if per_object_stack:
                            is_non_printing_mesh = any(
                                per_object_stack.getProperty(key, "value")
                                for key in NON_PRINTING_MESH_SETTINGS)

                        # ConvexHullNodes get none of the usual decorators. If it made it here, it is meant to be printed
                        if type(object) is ConvexHullNode:
                            raft_thickness = stack.getProperty(
                                "blackbelt_raft_thickness", "value")
                            raft_margin = stack.getProperty(
                                "blackbelt_raft_margin", "value")

                            mb = MeshBuilder()
                            hull_polygon = object.getHull()
                            if raft_margin > 0:
                                hull_polygon = hull_polygon.getMinkowskiHull(
                                    Polygon.approximatedCircle(raft_margin))
                            mb.addConvexPolygonExtrusion(
                                hull_polygon.getPoints()[::-1], 0,
                                raft_thickness)

                            new_node = self._addMesh(mb, "raftMesh")
                            added_meshes.append(new_node)
                            raft_meshes.append(new_node.getName())

                        elif not is_non_printing_mesh:
                            extruder_stack_index = object.callDecoration(
                                "getActiveExtruderPosition")
                            if not extruder_stack_index:
                                extruder_stack_index = 0
                            extruder_stack = ExtruderManager.getInstance(
                            ).getMachineExtruders(Application.getInstance(
                            ).getGlobalContainerStack().getId())[int(
                                extruder_stack_index)]

                            aabb = object.getBoundingBox()

                            if aabb.bottom < 0:
                                # mesh extends below the belt; add a cutting mesh to cut off the part below the bottom
                                height = -aabb.bottom
                                center = Vector(aabb.center.x, -height / 2,
                                                aabb.center.z)

                                mb = MeshBuilder()
                                mb.addCube(width=aabb.width,
                                           height=height,
                                           depth=aabb.depth,
                                           center=center)

                                new_node = self._addMesh(
                                    mb, "bottomCuttingMesh")
                                added_meshes.append(new_node)
                                bottom_cutting_meshes.append(
                                    new_node.getName())

                    if added_meshes:
                        group += added_meshes

            transform_matrix = self._scene.getRoot().callDecoration(
                "getTransformMatrix")
            front_offset = None

            raft_offset = 0
            raft_speed = None
            raft_flow = 1.0

            if stack.getProperty("blackbelt_raft", "value"):
                raft_offset = stack.getProperty(
                    "blackbelt_raft_thickness", "value") + stack.getProperty(
                        "blackbelt_raft_gap", "value")
                raft_speed = stack.getProperty("blackbelt_raft_speed", "value")
                raft_flow = stack.getProperty("blackbelt_raft_flow",
                                              "value") * math.sin(gantry_angle)

            for group in filtered_object_groups:
                group_message = self._slice_message.addRepeatedMessage(
                    "object_lists")
                if group[0].getParent() is not None and group[0].getParent(
                ).callDecoration("isGroup"):
                    self._handlePerObjectSettings(group[0].getParent(),
                                                  group_message)
                for object in group:
                    if type(object) is ConvexHullNode:
                        continue

                    mesh_data = object.getMeshData()
                    rot_scale = object.getWorldTransformation().getTransposed(
                    ).getData()[0:3, 0:3]
                    translate = object.getWorldTransformation().getData()[:3,
                                                                          3]
                    # offset all non-raft objects if rafts are enabled
                    # air gap is applied here to vertically offset objects from the raft
                    if object.getName() not in raft_meshes:
                        translate[1] = translate[1] + raft_offset

                    # This effectively performs a limited form of MeshData.getTransformed that ignores normals.
                    verts = mesh_data.getVertices()
                    verts = verts.dot(rot_scale)
                    verts += translate

                    if transform_matrix:
                        verts = transformVertices(verts, transform_matrix)

                        is_non_printing_mesh = object.getName(
                        ) in bottom_cutting_meshes or object.getName(
                        ) in raft_meshes
                        if not is_non_printing_mesh:
                            per_object_stack = object.callDecoration(
                                "getStack")
                            if per_object_stack:
                                is_non_printing_mesh = any(
                                    per_object_stack.getProperty(key, "value")
                                    for key in NON_PRINTING_MESH_SETTINGS)

                        if not is_non_printing_mesh:
                            _front_offset = verts[:, 1].min()
                            if front_offset is None or _front_offset < front_offset:
                                front_offset = _front_offset

                    # Convert from Y up axes to Z up axes. Equals a 90 degree rotation.
                    verts[:, [1, 2]] = verts[:, [2, 1]]
                    verts[:, 1] *= -1

                    obj = group_message.addRepeatedMessage("objects")
                    obj.id = id(object)

                    indices = mesh_data.getIndices()
                    if indices is not None:
                        flat_verts = numpy.take(verts,
                                                indices.flatten(),
                                                axis=0)
                    else:
                        flat_verts = numpy.array(verts)

                    obj.vertices = flat_verts

                    if object.getName() in raft_meshes:
                        self._addSettingsMessage(
                            obj, {
                                "wall_line_count": 99999999,
                                "speed_wall_0": raft_speed,
                                "speed_wall_x": raft_speed,
                                "material_flow": raft_flow
                            })

                    elif object.getName() in bottom_cutting_meshes:
                        self._addSettingsMessage(
                            obj, {
                                "cutting_mesh": True,
                                "wall_line_count": 0,
                                "top_layers": 0,
                                "bottom_layers": 0,
                                "infill_line_distance": 0
                            })

                    else:
                        self._handlePerObjectSettings(object, obj)
                        Job.yieldThread()

                # Store the front-most coordinate of the scene so the scene can be moved back into place post slicing
                # TODO: this should be handled per mesh-group instead of per scene
                # One-at-a-time printing should be disabled for slanted gantry printers for now
                self._scene.getRoot().callDecoration("setSceneFrontOffset",
                                                     front_offset)

        self.setResult(StartJobResult.Finished)