Пример #1
0
    def _handlePerObjectSettings(self, node: SteSlicerSceneNode,
                                 message: Arcus.PythonMessage):
        stack = node.callDecoration("getStack")

        # Check if the node has a stack attached to it and the stack has any settings in the top container.
        if not stack:
            return

        # Check all settings for relations, so we can also calculate the correct values for dependent settings.
        top_of_stack = stack.getTop()  # Cache for efficiency.
        changed_setting_keys = top_of_stack.getAllKeys()

        # Add all relations to changed settings as well.
        for key in top_of_stack.getAllKeys():
            instance = top_of_stack.getInstance(key)
            self._addRelations(changed_setting_keys,
                               instance.definition.relations)
            Job.yieldThread()

        # Ensure that the engine is aware what the build extruder is.
        changed_setting_keys.add("extruder_nr")

        # Get values for all changed settings
        for key in changed_setting_keys:
            setting = message.addRepeatedMessage("settings")
            setting.name = key
            extruder = int(
                round(float(stack.getProperty(key, "limit_to_extruder"))))

            # Check if limited to a specific extruder, but not overridden by per-object settings.
            if extruder >= 0 and key not in changed_setting_keys:
                limited_stack = ExtruderManager.getInstance(
                ).getActiveExtruderStacks()[extruder]
            else:
                limited_stack = stack

            setting.value = str(limited_stack.getProperty(
                key, "value")).encode("utf-8")

            Job.yieldThread()
Пример #2
0
    def _createEraserMesh(self, parent: SteSlicerSceneNode, position: Vector):
        node = SteSlicerSceneNode()

        node.setName("SplittingPlane")
        node.setSelectable(True)
        mesh = self._createPlane(self._plane_size)
        node.setMeshData(mesh.build())

        active_build_plate = SteSlicerApplication.getInstance(
        ).getMultiBuildPlateModel().activeBuildPlate
        node.addDecorator(BuildPlateDecorator(active_build_plate))
        node.addDecorator(SliceableObjectDecorator())
        node.addDecorator(SplittingPlaneDecorator())

        stack = node.callDecoration(
            "getStack"
        )  # created by SettingOverrideDecorator that is automatically added to SteSlicerSceneNode
        settings = stack.getTop()

        definition = stack.getSettingDefinition("anti_overhang_mesh")
        new_instance = SettingInstance(definition, settings)
        new_instance.setProperty("value", True)
        new_instance.resetState(
        )  # Ensure that the state is not seen as a user state.
        settings.addInstance(new_instance)

        op = GroupedOperation()
        # First add node to the scene at the correct position/scale, before parenting, so the eraser mesh does not get scaled with the parent
        op.addOperation(
            AddSceneNodeOperation(node,
                                  self._controller.getScene().getRoot()))
        op.addOperation(SetParentOperation(node, parent))
        op.push()
        node.setPosition(position, SteSlicerSceneNode.TransformSpace.World)

        SteSlicerApplication.getInstance().getController().getScene(
        ).sceneChanged.emit(node)
Пример #3
0
    def run(self) -> None:
        if self._build_plate_number is None:
            self.setResult(StartJobResult.Error)
            return

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

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

        if SteSlicerApplication.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 SteSlicerApplication.getInstance().getMachineManager().variantBuildplateCompatible and \
                not SteSlicerApplication.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.
        # type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
        for node in DepthFirstIterator(self._scene.getRoot()):
            if not isinstance(node,
                              SteSlicerSceneNode) or not node.isSelectable():
                continue

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

        with self._scene.getSceneLock():
            # Remove old layer data.
            # type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
            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 = []
            printing_mode = stack.getProperty("printing_mode", "value")
            if printing_mode == "classic":
                if stack.getProperty("print_sequence",
                                     "value") == "one_at_a_time":
                    # type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
                    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
                    # type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
                    for node in DepthFirstIterator(self._scene.getRoot()):
                        if node.callDecoration(
                                "isSliceable") 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:
                                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)
            elif printing_mode in ["cylindrical_full", "spherical_full"]:
                temp_list = []
                has_printing_mesh = False

                for node in DepthFirstIterator(
                        self._scene.getRoot()
                ):  # type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
                    if node.callDecoration("isSliceable") 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:
                            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()

            else:
                self.setResult(StartJobResult.ObjectSettingError)
                return

        if temp_list and printing_mode in [
                "cylindrical_full", "spherical_full"
        ]:
            cut_list = []
            for node in temp_list:
                if printing_mode == "cylindrical_full":
                    radius = SteSlicerApplication.getInstance(
                    ).getGlobalContainerStack().getProperty(
                        "cylindrical_mode_base_diameter", "value") / 2
                    height = node.getBoundingBox().height * 2
                    cutting_mesh = trimesh.primitives.Cylinder(radius=radius,
                                                               height=height,
                                                               sections=64)
                    cutting_mesh.apply_transform(
                        trimesh.transformations.rotation_matrix(
                            numpy.pi / 2, [1, 0, 0]))
                elif printing_mode == "spherical_full":
                    radius = SteSlicerApplication.getInstance(
                    ).getGlobalContainerStack().getProperty(
                        "spherical_mode_base_radius", "value")
                    cutting_mesh = trimesh.primitives.Sphere(radius=radius,
                                                             subdivisions=3)
                else:
                    cutting_mesh = None

                mesh_data = node.getMeshData()
                if mesh_data.hasIndices():
                    faces = mesh_data.getIndices()
                else:
                    num_verts = mesh_data.getVertexCount()
                    faces = numpy.empty((int(num_verts / 3 + 1), 3),
                                        numpy.int32)
                    for i in range(0, num_verts - 2, 3):
                        faces[int(i / 3):] = [i, i + 1, i + 2]
                verts = mesh_data.getVertices()
                rot_scale = node.getWorldTransformation().getTransposed(
                ).getData()[0:3, 0:3]
                translate = node.getWorldTransformation().getData()[:3, 3]
                verts = verts.dot(rot_scale)
                verts += translate
                mesh = trimesh.Trimesh(vertices=verts, faces=faces)
                try:
                    mesh.fill_holes()
                    mesh.fix_normals()
                    cutting_result = mesh.intersection(cutting_mesh,
                                                       engine="scad")
                    if cutting_result:
                        cutting_result.fill_holes()
                        cutting_result.fix_normals()

                        data = MeshData.MeshData(
                            vertices=cutting_result.vertices.astype('float32'),
                            normals=cutting_result.face_normals.astype(
                                'float32'),
                            indices=cutting_result.faces.astype('int64'))
                        cutting_node = SteSlicerSceneNode(
                            node.getParent(), no_setting_override=True)
                        cutting_node.addDecorator(
                            node.getDecorator(SettingOverrideDecorator))
                except Exception as e:
                    Logger.log("e", "Failed to intersect model! %s", e)
                    cutting_result = cutting_mesh
                    if cutting_result:
                        cutting_result.fill_holes()
                        cutting_result.fix_normals()

                        data = MeshData.MeshData(
                            vertices=cutting_result.vertices.astype('float32'),
                            normals=cutting_result.face_normals.astype(
                                'float32'),
                            indices=cutting_result.faces.astype('int64'))
                        cutting_node = SteSlicerSceneNode(
                            node.getParent(), no_setting_override=True)
                        stack = cutting_node.callDecoration(
                            "getStack"
                        )  # Don't try to get the active extruder since it may be None anyway.
                        if not stack:
                            cutting_node.addDecorator(
                                SettingOverrideDecorator())
                            stack = cutting_node.callDecoration("getStack")
                        settings = stack.getTop()
                        if not (settings.getInstance("support_mesh") and
                                settings.getProperty("support_mesh", "value")):
                            definition = stack.getSettingDefinition(
                                "support_mesh")
                            new_instance = SettingInstance(
                                definition, settings)
                            new_instance.setProperty("value",
                                                     True,
                                                     emit_signals=False)
                            # Ensure that the state is not seen as a user state.
                            new_instance.resetState()
                            settings.addInstance(new_instance)
                if cutting_node is not None:
                    cutting_node.setName("cut_" + node.getName())
                    cutting_node.setMeshData(data)

                    cut_list.append(cutting_node)

            object_groups.append(cut_list)

        global_stack = SteSlicerApplication.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 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

        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:
            self._buildExtruderMessage(extruder_stack)

        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:
                mesh_data = object.getMeshData()
                rot_scale = object.getWorldTransformation().getTransposed(
                ).getData()[0:3, 0:3]
                translate = object.getWorldTransformation().getData()[:3, 3]

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

                # 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

                self._handlePerObjectSettings(object, obj)

                Job.yieldThread()

        self.setResult(StartJobResult.Finished)
Пример #4
0
    def _convertSavitarNodeToUMNode(self, savitar_node):
        self._object_count += 1
        node_name = "Object %s" % self._object_count

        active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate

        um_node = SteSlicerSceneNode() # This adds a SettingOverrideDecorator
        um_node.addDecorator(BuildPlateDecorator(active_build_plate))
        um_node.setName(node_name)
        transformation = self._createMatrixFromTransformationString(savitar_node.getTransformation())
        um_node.setTransformation(transformation)
        mesh_builder = MeshBuilder()

        data = numpy.fromstring(savitar_node.getMeshData().getFlatVerticesAsBytes(), dtype=numpy.float32)

        vertices = numpy.resize(data, (int(data.size / 3), 3))
        mesh_builder.setVertices(vertices)
        mesh_builder.calculateNormals(fast=True)
        mesh_data = mesh_builder.build()

        if len(mesh_data.getVertices()):
            um_node.setMeshData(mesh_data)

        for child in savitar_node.getChildren():
            child_node = self._convertSavitarNodeToUMNode(child)
            if child_node:
                um_node.addChild(child_node)

        if um_node.getMeshData() is None and len(um_node.getChildren()) == 0:
            return None

        settings = savitar_node.getSettings()

        # Add the setting override decorator, so we can add settings to this node.
        if settings:
            global_container_stack = Application.getInstance().getGlobalContainerStack()

            # Ensure the correct next container for the SettingOverride decorator is set.
            if global_container_stack:
                default_stack = ExtruderManager.getInstance().getExtruderStack(0)

                if default_stack:
                    um_node.callDecoration("setActiveExtruder", default_stack.getId())

                # Get the definition & set it
                definition_id = getMachineDefinitionIDForQualitySearch(global_container_stack.definition)
                um_node.callDecoration("getStack").getTop().setDefinition(definition_id)

            setting_container = um_node.callDecoration("getStack").getTop()

            for key in settings:
                setting_value = settings[key]

                # Extruder_nr is a special case.
                if key == "extruder_nr":
                    extruder_stack = ExtruderManager.getInstance().getExtruderStack(int(setting_value))
                    if extruder_stack:
                        um_node.callDecoration("setActiveExtruder", extruder_stack.getId())
                    else:
                        Logger.log("w", "Unable to find extruder in position %s", setting_value)
                    continue
                setting_container.setProperty(key, "value", setting_value)

        if len(um_node.getChildren()) > 0 and um_node.getMeshData() is None:
            group_decorator = GroupDecorator()
            um_node.addDecorator(group_decorator)
        um_node.setSelectable(True)
        if um_node.getMeshData():
            # Assuming that all nodes with mesh data are printable objects
            # affects (auto) slicing
            sliceable_decorator = SliceableObjectDecorator()
            um_node.addDecorator(sliceable_decorator)
        return um_node