示例#1
0
    def _read(self, file_name: str) -> Union["SceneNode", List["SceneNode"]]:
        """Reads a file using Trimesh.

        :param file_name: The file path. This is assumed to be one of the file
        types that Trimesh can read. It will not be checked again.
        :return: A scene node that contains the file's contents.
        """
        # CURA-6739
        # GLTF files are essentially JSON files. If you directly give a file name to trimesh.load(), it will
        # try to figure out the format, but for GLTF, it loads it as a binary file with flags "rb", and the json.load()
        # doesn't like it. For some reason, this seems to happen with 3.5.7, but not 3.7.1. Below is a workaround to
        # pass a file object that has been opened with "r" instead "rb" to load a GLTF file.
        if file_name.lower().endswith(".gltf"):
            mesh_or_scene = trimesh.load(open(file_name, "r",
                                              encoding="utf-8"),
                                         file_type="gltf")
        else:
            mesh_or_scene = trimesh.load(file_name)

        meshes = []  # type: List[Union[trimesh.Trimesh, trimesh.Scene, Any]]
        if isinstance(mesh_or_scene, trimesh.Trimesh):
            meshes = [mesh_or_scene]
        elif isinstance(mesh_or_scene, trimesh.Scene):
            meshes = [mesh for mesh in mesh_or_scene.geometry.values()]

        active_build_plate = CuraApplication.getInstance(
        ).getMultiBuildPlateModel().activeBuildPlate
        nodes = []  # type: List[SceneNode]
        for mesh in meshes:
            if not isinstance(
                    mesh, trimesh.Trimesh
            ):  # Trimesh can also receive point clouds, 2D paths, 3D paths or metadata. Skip those.
                continue
            mesh.merge_vertices()
            mesh.remove_unreferenced_vertices()
            mesh.fix_normals()
            mesh_data = self._toMeshData(mesh, file_name)

            file_base_name = os.path.basename(file_name)
            new_node = CuraSceneNode()
            new_node.setMeshData(mesh_data)
            new_node.setSelectable(True)
            new_node.setName(file_base_name if len(
                meshes) == 1 else "{file_base_name} {counter}".format(
                    file_base_name=file_base_name, counter=str(len(nodes) +
                                                               1)))
            new_node.addDecorator(BuildPlateDecorator(active_build_plate))
            new_node.addDecorator(SliceableObjectDecorator())
            nodes.append(new_node)

        if len(nodes) == 1:
            return nodes[0]
        # Add all nodes to a group so they stay together.
        group_node = CuraSceneNode()
        group_node.addDecorator(GroupDecorator())
        group_node.addDecorator(ConvexHullDecorator())
        group_node.addDecorator(BuildPlateDecorator(active_build_plate))
        for node in nodes:
            node.setParent(group_node)
        return group_node
示例#2
0
    def _replaceSceneNode(self, existing_node, trimeshes):
        name = existing_node.getName()
        file_name = existing_node.getMeshData().getFileName()
        transformation = existing_node.getWorldTransformation()
        parent = existing_node.getParent()
        extruder_id = existing_node.callDecoration("getActiveExtruder")
        build_plate = existing_node.callDecoration("getBuildPlateNumber")
        selected = Selection.isSelected(existing_node)

        op = GroupedOperation()
        op.addOperation(RemoveSceneNodeOperation(existing_node))

        for i, tri_node in enumerate(trimeshes):
            mesh_data = self._toMeshData(tri_node)

            new_node = CuraSceneNode()
            new_node.setSelectable(True)
            new_node.setMeshData(mesh_data)
            new_node.setName(name if i == 0 else "%s %d" % (name, i))
            new_node.callDecoration("setActiveExtruder", extruder_id)
            new_node.addDecorator(BuildPlateDecorator(build_plate))
            new_node.addDecorator(SliceableObjectDecorator())

            op.addOperation(AddSceneNodeOperation(new_node, parent))
            op.addOperation(
                SetTransformMatrixOperation(new_node, transformation))

            if selected:
                Selection.add(new_node)
        op.push()
示例#3
0
    def _addShape(self, mesh_data: MeshData) -> None:
        application = CuraApplication.getInstance()
        global_stack = application.getGlobalContainerStack()
        if not global_stack:
            return

        node = CuraSceneNode()

        node.setMeshData(mesh_data)
        node.setSelectable(True)
        node.setName("SimpleShape" + str(id(mesh_data)))

        scene = self._controller.getScene()
        op = AddSceneNodeOperation(node, scene.getRoot())
        op.push()

        default_extruder_position = application.getMachineManager(
        ).defaultExtruderPosition
        default_extruder_id = global_stack.extruders[
            default_extruder_position].getId()
        node.callDecoration("setActiveExtruder", default_extruder_id)

        active_build_plate = application.getMultiBuildPlateModel(
        ).activeBuildPlate
        node.addDecorator(BuildPlateDecorator(active_build_plate))

        node.addDecorator(SliceableObjectDecorator())

        application.getController().getScene().sceneChanged.emit(node)
示例#4
0
    def _createEraserMesh(self, parent: CuraSceneNode, position: Vector):
        node = CuraSceneNode()

        node.setName("Eraser")
        node.setSelectable(True)
        mesh = MeshBuilder()
        mesh.addCube(10,10,10)
        node.setMeshData(mesh.build())

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

        stack = node.callDecoration("getStack") # created by SettingOverrideDecorator that is automatically added to CuraSceneNode
        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 = AddSceneNodeOperation(node, parent)
        op.push()
        node.setPosition(position, CuraSceneNode.TransformSpace.World)

        Application.getInstance().getController().getScene().sceneChanged.emit(node)
示例#5
0
    def _createEraserMesh(self, parent: CuraSceneNode, position: Vector):
        node = CuraSceneNode()

        node.setName("Eraser")
        node.setSelectable(True)
        node.setCalculateBoundingBox(True)
        mesh = self._createCube(10)
        node.setMeshData(mesh.build())
        node.calculateBoundingBoxMesh()

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

        stack = node.callDecoration("getStack") # created by SettingOverrideDecorator that is automatically added to CuraSceneNode
        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, CuraSceneNode.TransformSpace.World)

        CuraApplication.getInstance().getController().getScene().sceneChanged.emit(node)
示例#6
0
    def _read(self, file_name):
        try:
            self.defs = {}
            self.shapes = []

            tree = ET.parse(file_name)
            xml_root = tree.getroot()

            if xml_root.tag != "X3D":
                return None

            scale = 1000  # Default X3D unit it one meter, while Cura's is one millimeters
            if xml_root[0].tag == "head":
                for head_node in xml_root[0]:
                    if head_node.tag == "unit" and head_node.attrib.get(
                            "category") == "length":
                        scale *= float(head_node.attrib["conversionFactor"])
                        break
                xml_scene = xml_root[1]
            else:
                xml_scene = xml_root[0]

            if xml_scene.tag != "Scene":
                return None

            self.transform = Matrix()
            self.transform.setByScaleFactor(scale)
            self.index_base = 0

            # Traverse the scene tree, populate the shapes list
            self.processChildNodes(xml_scene)

            if self.shapes:
                builder = MeshBuilder()
                builder.setVertices(
                    numpy.concatenate([shape.verts for shape in self.shapes]))
                builder.setIndices(
                    numpy.concatenate([shape.faces for shape in self.shapes]))
                builder.calculateNormals()
                builder.setFileName(file_name)
                mesh_data = builder.build()

                # Manually try and get the extents of the mesh_data. This should prevent nasty NaN issues from
                # leaving the reader.
                mesh_data.getExtents()

                node = SceneNode()
                node.setMeshData(mesh_data)
                node.setSelectable(True)
                node.setName(file_name)

            else:
                return None

        except Exception:
            Logger.logException("e", "Exception in X3D reader")
            return None

        return node
示例#7
0
    def _replaceSceneNode(self, existing_node, trimeshes) -> None:
        name = existing_node.getName()
        file_name = existing_node.getMeshData().getFileName()
        transformation = existing_node.getWorldTransformation()
        parent = existing_node.getParent()
        extruder_id = existing_node.callDecoration("getActiveExtruder")
        build_plate = existing_node.callDecoration("getBuildPlateNumber")
        selected = Selection.isSelected(existing_node)

        children = existing_node.getChildren()
        new_nodes = []

        op = GroupedOperation()
        op.addOperation(RemoveSceneNodeOperation(existing_node))

        for i, tri_node in enumerate(trimeshes):
            mesh_data = self._toMeshData(tri_node, file_name)

            new_node = CuraSceneNode()
            new_node.setSelectable(True)
            new_node.setMeshData(mesh_data)
            new_node.setName(name if i==0 else "%s %d" % (name, i))
            new_node.callDecoration("setActiveExtruder", extruder_id)
            new_node.addDecorator(BuildPlateDecorator(build_plate))
            new_node.addDecorator(SliceableObjectDecorator())

            op.addOperation(AddSceneNodeOperation(new_node, parent))
            op.addOperation(SetTransformMatrixOperation(new_node, transformation))

            new_nodes.append(new_node)

            if selected:
                Selection.add(new_node)

        for child in children:
            mesh_data = child.getMeshData()
            if not mesh_data:
                continue
            child_bounding_box = mesh_data.getTransformed(child.getWorldTransformation()).getExtents()
            if not child_bounding_box:
                continue
            new_parent = None
            for potential_parent in new_nodes:
                parent_mesh_data = potential_parent.getMeshData()
                if not parent_mesh_data:
                    continue
                parent_bounding_box = parent_mesh_data.getTransformed(potential_parent.getWorldTransformation()).getExtents()
                if not parent_bounding_box:
                    continue
                intersection = child_bounding_box.intersectsBox(parent_bounding_box)
                if intersection != AxisAlignedBox.IntersectionResult.NoIntersection:
                    new_parent = potential_parent
                    break
            if not new_parent:
                new_parent = new_nodes[0]
            op.addOperation(SetParentOperationSimplified(child, new_parent))

        op.push()
示例#8
0
    def _read(self, file_name):
        try:
            self.defs = {}
            self.shapes = []

            tree = ET.parse(file_name)
            xml_root = tree.getroot()

            if xml_root.tag != "X3D":
                return None

            scale = 1000 # Default X3D unit it one meter, while Cura's is one millimeters
            if xml_root[0].tag == "head":
                for head_node in xml_root[0]:
                    if head_node.tag == "unit" and head_node.attrib.get("category") == "length":
                        scale *= float(head_node.attrib["conversionFactor"])
                        break
                xml_scene = xml_root[1]
            else:
                xml_scene = xml_root[0]

            if xml_scene.tag != "Scene":
                return None

            self.transform = Matrix()
            self.transform.setByScaleFactor(scale)
            self.index_base = 0

            # Traverse the scene tree, populate the shapes list
            self.processChildNodes(xml_scene)

            if self.shapes:
                builder = MeshBuilder()
                builder.setVertices(numpy.concatenate([shape.verts for shape in self.shapes]))
                builder.setIndices(numpy.concatenate([shape.faces for shape in self.shapes]))
                builder.calculateNormals()
                builder.setFileName(file_name)
                mesh_data = builder.build()

                # Manually try and get the extents of the mesh_data. This should prevent nasty NaN issues from
                # leaving the reader.
                mesh_data.getExtents()

                node = SceneNode()
                node.setMeshData(mesh_data)
                node.setSelectable(True)
                node.setName(file_name)

            else:
                return None

        except Exception:
            Logger.logException("e", "Exception in X3D reader")
            return None

        return node
示例#9
0
    def _read(self, file_name: str) -> Union["SceneNode", List["SceneNode"]]:
        mesh_or_scene = trimesh.load(file_name)
        meshes = []  # type: List[Union[trimesh.Trimesh, trimesh.Scene, Any]]
        if isinstance(mesh_or_scene, trimesh.Trimesh):
            meshes = [mesh_or_scene]
        elif isinstance(mesh_or_scene, trimesh.Scene):
            meshes = [mesh for mesh in mesh_or_scene.geometry.values()]

        active_build_plate = CuraApplication.getInstance(
        ).getMultiBuildPlateModel().activeBuildPlate
        nodes = []  # type: List[SceneNode]
        for mesh in meshes:
            if not isinstance(
                    mesh, trimesh.Trimesh
            ):  # Trimesh can also receive point clouds, 2D paths, 3D paths or metadata. Skip those.
                continue
            mesh.merge_vertices()
            mesh.remove_unreferenced_vertices()
            mesh.fix_normals()
            mesh_data = self._toMeshData(mesh)

            file_base_name = os.path.basename(file_name)
            new_node = CuraSceneNode()
            new_node.setMeshData(mesh_data)
            new_node.setSelectable(True)
            new_node.setName(file_base_name if len(
                meshes) == 1 else "{file_base_name} {counter}".format(
                    file_base_name=file_base_name, counter=str(len(nodes) +
                                                               1)))
            new_node.addDecorator(BuildPlateDecorator(active_build_plate))
            new_node.addDecorator(SliceableObjectDecorator())
            nodes.append(new_node)

        if len(nodes) == 1:
            return nodes[0]
        # Add all nodes to a group so they stay together.
        group_node = CuraSceneNode()
        group_node.addDecorator(GroupDecorator())
        group_node.addDecorator(ConvexHullDecorator())
        group_node.addDecorator(BuildPlateDecorator(active_build_plate))
        for node in nodes:
            node.setParent(group_node)
        return group_node
示例#10
0
    def _createEraserMesh(self):
        node = CuraSceneNode()

        node.setName("Eraser")
        node.setSelectable(True)
        mesh = MeshBuilder()
        mesh.addCube(10, 10, 10)
        node.setMeshData(mesh.build())
        # Place the cube in the platform. Do it manually so it works if the "automatic drop models" is OFF
        move_vector = Vector(0, 5, 0)
        node.setPosition(move_vector)

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

        node.addDecorator(SettingOverrideDecorator())
        node.addDecorator(BuildPlateDecorator(active_build_plate))
        node.addDecorator(SliceableObjectDecorator())

        stack = node.callDecoration(
            "getStack"
        )  #Don't try to get the active extruder since it may be None anyway.
        if not stack:
            node.addDecorator(SettingOverrideDecorator())
            stack = node.callDecoration("getStack")

        settings = stack.getTop()

        if not (settings.getInstance("anti_overhang_mesh")
                and settings.getProperty("anti_overhang_mesh", "value")):
            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)

        scene = self._controller.getScene()
        op = AddSceneNodeOperation(node, scene.getRoot())
        op.push()
        Application.getInstance().getController().getScene().sceneChanged.emit(
            node)
    def _addShape(self, name, mesh_data: MeshData, ext_pos=0) -> None:
        application = CuraApplication.getInstance()
        global_stack = application.getGlobalContainerStack()
        if not global_stack:
            return

        node = CuraSceneNode()

        node.setMeshData(mesh_data)
        node.setSelectable(True)
        if len(name) == 0:
            node.setName("TestPart" + str(id(mesh_data)))
        else:
            node.setName(str(name))

        scene = self._controller.getScene()
        op = AddSceneNodeOperation(node, scene.getRoot())
        op.push()

        extruder_nr = len(global_stack.extruders)
        # Logger.log("d", "extruder_nr= %d", extruder_nr)
        # default_extruder_position  : <class 'str'>
        if ext_pos > 0 and ext_pos <= extruder_nr:
            default_extruder_position = str(ext_pos - 1)
        else:
            default_extruder_position = application.getMachineManager(
            ).defaultExtruderPosition
        # Logger.log("d", "default_extruder_position= %s", type(default_extruder_position))
        # default_extruder_id = global_stack.extruders[default_extruder_position].getId()
        default_extruder_id = global_stack.extruders[
            default_extruder_position].getId()
        # Logger.log("d", "default_extruder_id= %s", default_extruder_id)
        node.callDecoration("setActiveExtruder", default_extruder_id)

        active_build_plate = application.getMultiBuildPlateModel(
        ).activeBuildPlate
        node.addDecorator(BuildPlateDecorator(active_build_plate))

        node.addDecorator(SliceableObjectDecorator())

        application.getController().getScene().sceneChanged.emit(node)
示例#12
0
    def _createEraserMesh(self, parent: CuraSceneNode, position: Vector):
        node = CuraSceneNode()

        node.setName("Eraser")
        node.setSelectable(True)
        mesh = MeshBuilder()
        mesh.addCube(10, 10, 10)
        node.setMeshData(mesh.build())
        node.setPosition(position)

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

        node.addDecorator(SettingOverrideDecorator())
        node.addDecorator(BuildPlateDecorator(active_build_plate))
        node.addDecorator(SliceableObjectDecorator())

        stack = node.callDecoration(
            "getStack")  # created by SettingOverrideDecorator
        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)

        root = self._controller.getScene().getRoot()

        op = GroupedOperation()
        # First add the node to the scene, so it gets the expected transform
        op.addOperation(AddSceneNodeOperation(node, root))
        op.addOperation(SetParentOperation(node, parent))
        op.push()

        Application.getInstance().getController().getScene().sceneChanged.emit(
            node)
    def _createSupportMesh(self, parent: CuraSceneNode, position: Vector):
        node = CuraSceneNode()
        node.setSelectable(True)
        
        if self._SupportType == 'cylinder':
            height = position.y
            node.setName("CustomSupportCylinder")
            mesh = self._createCylinder(self._SupportSize,22.5,height)
            node_position = Vector(position.x,position.y,position.z)
        else:
            node.setName("CustomSupportCube")
            height = position.y-self._SupportSize/2+self._SupportSize*0.1
            mesh =  self._createCube(self._SupportSize,height)
            node_position = Vector(position.x,position.y-self._SupportSize/2+self._SupportSize*0.1,position.z)
        node.setMeshData(mesh.build())

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

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

        for key in ["support_mesh", "support_mesh_drop_down"]:
            definition = stack.getSettingDefinition(key)
            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 support 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(node_position, CuraSceneNode.TransformSpace.World)

        CuraApplication.getInstance().getController().getScene().sceneChanged.emit(node)
示例#14
0
    def _node(self, mesh, settings):
        node = CuraSceneNode()
        node.setMeshData(mesh)
        node.setSelectable(True)
        node.addDecorator(SliceableObjectDecorator())

        if len(settings) > 0:
            node.addDecorator(SettingOverrideDecorator())
            stack = node.callDecoration('getStack')
            top = stack.getTop()

            for k, v in settings.items():
                if k == 'extruder':
                    node.callDecoration('setActiveExtruder', v)
                else:
                    definition = stack.getSettingDefinition(k)
                    instance = SettingInstance(definition, top)
                    instance.setProperty("value", v)
                    instance.resetState()
                    top.addInstance(instance)

        Logger.log('d', 'node: {0}'.format(node))
        return node
示例#15
0
    def _generateSceneNode(self, file_name, xz_size, peak_height, base_height,
                           blur_iterations, max_size, lighter_is_higher):
        scene_node = SceneNode()

        mesh = MeshBuilder()

        img = QImage(file_name)

        if img.isNull():
            Logger.log("e", "Image is corrupt.")
            return None

        width = max(img.width(), 2)
        height = max(img.height(), 2)
        aspect = height / width

        if img.width() < 2 or img.height() < 2:
            img = img.scaled(width, height, Qt.IgnoreAspectRatio)

        base_height = max(base_height, 0)
        peak_height = max(peak_height, -base_height)

        xz_size = max(xz_size, 1)
        scale_vector = Vector(xz_size, peak_height, xz_size)

        if width > height:
            scale_vector = scale_vector.set(z=scale_vector.z * aspect)
        elif height > width:
            scale_vector = scale_vector.set(x=scale_vector.x / aspect)

        if width > max_size or height > max_size:
            scale_factor = max_size / width
            if height > width:
                scale_factor = max_size / height

            width = int(max(round(width * scale_factor), 2))
            height = int(max(round(height * scale_factor), 2))
            img = img.scaled(width, height, Qt.IgnoreAspectRatio)

        width_minus_one = width - 1
        height_minus_one = height - 1

        Job.yieldThread()

        texel_width = 1.0 / (width_minus_one) * scale_vector.x
        texel_height = 1.0 / (height_minus_one) * scale_vector.z

        height_data = numpy.zeros((height, width), dtype=numpy.float32)

        for x in range(0, width):
            for y in range(0, height):
                qrgb = img.pixel(x, y)
                avg = float(qRed(qrgb) + qGreen(qrgb) + qBlue(qrgb)) / (3 *
                                                                        255)
                height_data[y, x] = avg

        Job.yieldThread()

        if not lighter_is_higher:
            height_data = 1 - height_data

        for _ in range(0, blur_iterations):
            copy = numpy.pad(height_data, ((1, 1), (1, 1)), mode="edge")

            height_data += copy[1:-1, 2:]
            height_data += copy[1:-1, :-2]
            height_data += copy[2:, 1:-1]
            height_data += copy[:-2, 1:-1]

            height_data += copy[2:, 2:]
            height_data += copy[:-2, 2:]
            height_data += copy[2:, :-2]
            height_data += copy[:-2, :-2]

            height_data /= 9

            Job.yieldThread()

        height_data *= scale_vector.y
        height_data += base_height

        heightmap_face_count = 2 * height_minus_one * width_minus_one
        total_face_count = heightmap_face_count + (width_minus_one * 2) * (
            height_minus_one * 2) + 2

        mesh.reserveFaceCount(total_face_count)

        # initialize to texel space vertex offsets.
        # 6 is for 6 vertices for each texel quad.
        heightmap_vertices = numpy.zeros(
            (width_minus_one * height_minus_one, 6, 3), dtype=numpy.float32)
        heightmap_vertices = heightmap_vertices + numpy.array(
            [[[0, base_height, 0], [0, base_height, texel_height],
              [texel_width, base_height, texel_height],
              [texel_width, base_height, texel_height],
              [texel_width, base_height, 0], [0, base_height, 0]]],
            dtype=numpy.float32)

        offsetsz, offsetsx = numpy.mgrid[0:height_minus_one, 0:width - 1]
        offsetsx = numpy.array(offsetsx, numpy.float32).reshape(
            -1, 1) * texel_width
        offsetsz = numpy.array(offsetsz, numpy.float32).reshape(
            -1, 1) * texel_height

        # offsets for each texel quad
        heightmap_vertex_offsets = numpy.concatenate([
            offsetsx,
            numpy.zeros((offsetsx.shape[0], offsetsx.shape[1]),
                        dtype=numpy.float32), offsetsz
        ], 1)
        heightmap_vertices += heightmap_vertex_offsets.repeat(6, 0).reshape(
            -1, 6, 3)

        # apply height data to y values
        heightmap_vertices[:, 0,
                           1] = heightmap_vertices[:, 5,
                                                   1] = height_data[:-1, :
                                                                    -1].reshape(
                                                                        -1)
        heightmap_vertices[:, 1, 1] = height_data[1:, :-1].reshape(-1)
        heightmap_vertices[:, 2,
                           1] = heightmap_vertices[:, 3, 1] = height_data[
                               1:, 1:].reshape(-1)
        heightmap_vertices[:, 4, 1] = height_data[:-1, 1:].reshape(-1)

        heightmap_indices = numpy.array(numpy.mgrid[0:heightmap_face_count *
                                                    3],
                                        dtype=numpy.int32).reshape(-1, 3)

        mesh._vertices[0:(heightmap_vertices.size //
                          3), :] = heightmap_vertices.reshape(-1, 3)
        mesh._indices[0:(heightmap_indices.size // 3), :] = heightmap_indices

        mesh._vertex_count = heightmap_vertices.size // 3
        mesh._face_count = heightmap_indices.size // 3

        geo_width = width_minus_one * texel_width
        geo_height = height_minus_one * texel_height

        # bottom
        mesh.addFaceByPoints(0, 0, 0, 0, 0, geo_height, geo_width, 0,
                             geo_height)
        mesh.addFaceByPoints(geo_width, 0, geo_height, geo_width, 0, 0, 0, 0,
                             0)

        # north and south walls
        for n in range(0, width_minus_one):
            x = n * texel_width
            nx = (n + 1) * texel_width

            hn0 = height_data[0, n]
            hn1 = height_data[0, n + 1]

            hs0 = height_data[height_minus_one, n]
            hs1 = height_data[height_minus_one, n + 1]

            mesh.addFaceByPoints(x, 0, 0, nx, 0, 0, nx, hn1, 0)
            mesh.addFaceByPoints(nx, hn1, 0, x, hn0, 0, x, 0, 0)

            mesh.addFaceByPoints(x, 0, geo_height, nx, 0, geo_height, nx, hs1,
                                 geo_height)
            mesh.addFaceByPoints(nx, hs1, geo_height, x, hs0, geo_height, x, 0,
                                 geo_height)

        # west and east walls
        for n in range(0, height_minus_one):
            y = n * texel_height
            ny = (n + 1) * texel_height

            hw0 = height_data[n, 0]
            hw1 = height_data[n + 1, 0]

            he0 = height_data[n, width_minus_one]
            he1 = height_data[n + 1, width_minus_one]

            mesh.addFaceByPoints(0, 0, y, 0, 0, ny, 0, hw1, ny)
            mesh.addFaceByPoints(0, hw1, ny, 0, hw0, y, 0, 0, y)

            mesh.addFaceByPoints(geo_width, 0, y, geo_width, 0, ny, geo_width,
                                 he1, ny)
            mesh.addFaceByPoints(geo_width, he1, ny, geo_width, he0, y,
                                 geo_width, 0, y)

        mesh.calculateNormals(fast=True)

        scene_node.setMeshData(mesh.build())

        return scene_node
示例#16
0
    def _read(self, file_name):
        base_name = os.path.basename(file_name)
        try:
            zipped_file = zipfile.ZipFile(file_name)
            xml_document = zipped_file.read(zipped_file.namelist()[0])
            zipped_file.close()
        except zipfile.BadZipfile:
            raw_file = open(file_name, "r")
            xml_document = raw_file.read()
            raw_file.close()

        try:
            amf_document = ET.fromstring(xml_document)
        except ET.ParseError:
            Logger.log("e", "Could not parse XML in file %s" % base_name)
            return None

        if "unit" in amf_document.attrib:
            unit = amf_document.attrib["unit"].lower()
        else:
            unit = "millimeter"
        if unit == "millimeter":
            scale = 1.0
        elif unit == "meter":
            scale = 1000.0
        elif unit == "inch":
            scale = 25.4
        elif unit == "feet":
            scale = 304.8
        elif unit == "micron":
            scale = 0.001
        else:
            Logger.log("w",
                       "Unknown unit in amf: %s. Using mm instead." % unit)
            scale = 1.0

        nodes = []
        for amf_object in amf_document.iter("object"):
            for amf_mesh in amf_object.iter("mesh"):
                amf_mesh_vertices = []
                for vertices in amf_mesh.iter("vertices"):
                    for vertex in vertices.iter("vertex"):
                        for coordinates in vertex.iter("coordinates"):
                            v = [0.0, 0.0, 0.0]
                            for t in coordinates:
                                if t.tag == "x":
                                    v[0] = float(t.text) * scale
                                elif t.tag == "y":
                                    v[2] = -float(t.text) * scale
                                elif t.tag == "z":
                                    v[1] = float(t.text) * scale
                            amf_mesh_vertices.append(v)
                if not amf_mesh_vertices:
                    continue

                indices = []
                for volume in amf_mesh.iter("volume"):
                    for triangle in volume.iter("triangle"):
                        f = [0, 0, 0]
                        for t in triangle:
                            if t.tag == "v1":
                                f[0] = int(t.text)
                            elif t.tag == "v2":
                                f[1] = int(t.text)
                            elif t.tag == "v3":
                                f[2] = int(t.text)
                        indices.append(f)

                    mesh = trimesh.base.Trimesh(
                        vertices=numpy.array(amf_mesh_vertices,
                                             dtype=numpy.float32),
                        faces=numpy.array(indices, dtype=numpy.int32))
                    mesh.merge_vertices()
                    mesh.remove_unreferenced_vertices()
                    mesh.fix_normals()
                    mesh_data = self._toMeshData(mesh, file_name)

                    new_node = CuraSceneNode()
                    new_node.setSelectable(True)
                    new_node.setMeshData(mesh_data)
                    new_node.setName(base_name if len(nodes) ==
                                     0 else "%s %d" % (base_name, len(nodes)))
                    new_node.addDecorator(
                        BuildPlateDecorator(CuraApplication.getInstance(
                        ).getMultiBuildPlateModel().activeBuildPlate))
                    new_node.addDecorator(SliceableObjectDecorator())

                    nodes.append(new_node)

        if not nodes:
            Logger.log("e", "No meshes in file %s" % base_name)
            return None

        if len(nodes) == 1:
            return nodes[0]

        # Add all scenenodes to a group so they stay together
        group_node = CuraSceneNode()
        group_node.addDecorator(GroupDecorator())
        group_node.addDecorator(ConvexHullDecorator())
        group_node.addDecorator(
            BuildPlateDecorator(CuraApplication.getInstance().
                                getMultiBuildPlateModel().activeBuildPlate))

        for node in nodes:
            node.setParent(group_node)

        return group_node
示例#17
0
    def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, image_color_invert):
        scene_node = SceneNode()

        mesh = MeshBuilder()

        img = QImage(file_name)

        if img.isNull():
            Logger.log("e", "Image is corrupt.")
            return None

        width = max(img.width(), 2)
        height = max(img.height(), 2)
        aspect = height / width

        if img.width() < 2 or img.height() < 2:
            img = img.scaled(width, height, Qt.IgnoreAspectRatio)

        base_height = max(base_height, 0)
        peak_height = max(peak_height, -base_height)

        xz_size = max(xz_size, 1)
        scale_vector = Vector(xz_size, peak_height, xz_size)

        if width > height:
            scale_vector = scale_vector.set(z=scale_vector.z * aspect)
        elif height > width:
            scale_vector = scale_vector.set(x=scale_vector.x / aspect)

        if width > max_size or height > max_size:
            scale_factor = max_size / width
            if height > width:
                scale_factor = max_size / height

            width = int(max(round(width * scale_factor), 2))
            height = int(max(round(height * scale_factor), 2))
            img = img.scaled(width, height, Qt.IgnoreAspectRatio)

        width_minus_one = width - 1
        height_minus_one = height - 1

        Job.yieldThread()

        texel_width = 1.0 / (width_minus_one) * scale_vector.x
        texel_height = 1.0 / (height_minus_one) * scale_vector.z

        height_data = numpy.zeros((height, width), dtype=numpy.float32)

        for x in range(0, width):
            for y in range(0, height):
                qrgb = img.pixel(x, y)
                avg = float(qRed(qrgb) + qGreen(qrgb) + qBlue(qrgb)) / (3 * 255)
                height_data[y, x] = avg

        Job.yieldThread()

        if image_color_invert:
            height_data = 1 - height_data

        for _ in range(0, blur_iterations):
            copy = numpy.pad(height_data, ((1, 1), (1, 1)), mode= "edge")

            height_data += copy[1:-1, 2:]
            height_data += copy[1:-1, :-2]
            height_data += copy[2:, 1:-1]
            height_data += copy[:-2, 1:-1]

            height_data += copy[2:, 2:]
            height_data += copy[:-2, 2:]
            height_data += copy[2:, :-2]
            height_data += copy[:-2, :-2]

            height_data /= 9

            Job.yieldThread()

        height_data *= scale_vector.y
        height_data += base_height

        heightmap_face_count = 2 * height_minus_one * width_minus_one
        total_face_count = heightmap_face_count + (width_minus_one * 2) * (height_minus_one * 2) + 2

        mesh.reserveFaceCount(total_face_count)

        # initialize to texel space vertex offsets.
        # 6 is for 6 vertices for each texel quad.
        heightmap_vertices = numpy.zeros((width_minus_one * height_minus_one, 6, 3), dtype = numpy.float32)
        heightmap_vertices = heightmap_vertices + numpy.array([[
            [0, base_height, 0],
            [0, base_height, texel_height],
            [texel_width, base_height, texel_height],
            [texel_width, base_height, texel_height],
            [texel_width, base_height, 0],
            [0, base_height, 0]
        ]], dtype = numpy.float32)

        offsetsz, offsetsx = numpy.mgrid[0: height_minus_one, 0: width - 1]
        offsetsx = numpy.array(offsetsx, numpy.float32).reshape(-1, 1) * texel_width
        offsetsz = numpy.array(offsetsz, numpy.float32).reshape(-1, 1) * texel_height

        # offsets for each texel quad
        heightmap_vertex_offsets = numpy.concatenate([offsetsx, numpy.zeros((offsetsx.shape[0], offsetsx.shape[1]), dtype=numpy.float32), offsetsz], 1)
        heightmap_vertices += heightmap_vertex_offsets.repeat(6, 0).reshape(-1, 6, 3)

        # apply height data to y values
        heightmap_vertices[:, 0, 1] = heightmap_vertices[:, 5, 1] = height_data[:-1, :-1].reshape(-1)
        heightmap_vertices[:, 1, 1] = height_data[1:, :-1].reshape(-1)
        heightmap_vertices[:, 2, 1] = heightmap_vertices[:, 3, 1] = height_data[1:, 1:].reshape(-1)
        heightmap_vertices[:, 4, 1] = height_data[:-1, 1:].reshape(-1)

        heightmap_indices = numpy.array(numpy.mgrid[0:heightmap_face_count * 3], dtype=numpy.int32).reshape(-1, 3)

        mesh._vertices[0:(heightmap_vertices.size // 3), :] = heightmap_vertices.reshape(-1, 3)
        mesh._indices[0:(heightmap_indices.size // 3), :] = heightmap_indices

        mesh._vertex_count = heightmap_vertices.size // 3
        mesh._face_count = heightmap_indices.size // 3

        geo_width = width_minus_one * texel_width
        geo_height = height_minus_one * texel_height

        # bottom
        mesh.addFaceByPoints(0, 0, 0, 0, 0, geo_height, geo_width, 0, geo_height)
        mesh.addFaceByPoints(geo_width, 0, geo_height, geo_width, 0, 0, 0, 0, 0)

        # north and south walls
        for n in range(0, width_minus_one):
            x = n * texel_width
            nx = (n + 1) * texel_width

            hn0 = height_data[0, n]
            hn1 = height_data[0, n + 1]

            hs0 = height_data[height_minus_one, n]
            hs1 = height_data[height_minus_one, n + 1]

            mesh.addFaceByPoints(x, 0, 0, nx, 0, 0, nx, hn1, 0)
            mesh.addFaceByPoints(nx, hn1, 0, x, hn0, 0, x, 0, 0)

            mesh.addFaceByPoints(x, 0, geo_height, nx, 0, geo_height, nx, hs1, geo_height)
            mesh.addFaceByPoints(nx, hs1, geo_height, x, hs0, geo_height, x, 0, geo_height)

        # west and east walls
        for n in range(0, height_minus_one):
            y = n * texel_height
            ny = (n + 1) * texel_height

            hw0 = height_data[n, 0]
            hw1 = height_data[n + 1, 0]

            he0 = height_data[n, width_minus_one]
            he1 = height_data[n + 1, width_minus_one]

            mesh.addFaceByPoints(0, 0, y, 0, 0, ny, 0, hw1, ny)
            mesh.addFaceByPoints(0, hw1, ny, 0, hw0, y, 0, 0, y)

            mesh.addFaceByPoints(geo_width, 0, y, geo_width, 0, ny, geo_width, he1, ny)
            mesh.addFaceByPoints(geo_width, he1, ny, geo_width, he0, y, geo_width, 0, y)

        mesh.calculateNormals(fast=True)

        scene_node.setMeshData(mesh.build())

        return scene_node
示例#18
0
    def _createSupportMesh(self, parent: CuraSceneNode, position: Vector , position2: Vector):
        node = CuraSceneNode()

        if self._SType == 'cylinder':
            node.setName("CustomSupportCylinder")
        elif self._SType == 'tube':
            node.setName("CustomSupportTube")
        elif self._SType == 'cube':
            node.setName("CustomSupportCube")
        elif self._SType == 'abutment':
            node.setName("CustomSupportAbutment")
        elif self._SType == 'freeform':
            node.setName("CustomSupportFreeForm")            
        else:
            node.setName("CustomSupportCustom")
            
        node.setSelectable(True)
        
        # long=Support Height
        long=position.y
                
                
        if self._SType == 'cylinder':
            # Cylinder creation Diameter , Increment angle 2°, length
            mesh = self._createCylinder(self._UseSize,self._MaxSize,2,long,self._UseAngle)
        elif self._SType == 'tube':
            # Tube creation Diameter , Diameter Int, Increment angle 2°, length
            mesh =  self._createTube(self._UseSize,self._MaxSize,self._UseISize,2,long,self._UseAngle)
        elif self._SType == 'cube':
            # Cube creation Size , length
            mesh =  self._createCube(self._UseSize,self._MaxSize,long,self._UseAngle)
        elif self._SType == 'freeform':
            # Cube creation Size , length
            mesh = MeshBuilder()  
            MName = self._SubType + ".stl"
            model_definition_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "models", MName)
            # Logger.log('d', 'Model_definition_path : ' + str(model_definition_path)) 
            load_mesh = trimesh.load(model_definition_path)
            origin = [0, 0, 0]
            DirX = [1, 0, 0]
            DirY = [0, 1, 0]
            DirZ = [0, 0, 1]
            load_mesh.apply_transform(trimesh.transformations.scale_matrix(self._UseSize, origin, DirX))
            load_mesh.apply_transform(trimesh.transformations.scale_matrix(self._UseSize, origin, DirY))   
            load_mesh.apply_transform(trimesh.transformations.scale_matrix(long, origin, DirZ)) 
            if self._MirrorSupport == True :   
                load_mesh.apply_transform(trimesh.transformations.rotation_matrix(math.radians(180), [0, 0, 1]))
            if self._UseYDirection == True :
                load_mesh.apply_transform(trimesh.transformations.rotation_matrix(math.radians(90), [0, 0, 1]))

            mesh =  self._toMeshData(load_mesh)
            
        elif self._SType == 'abutment':
            # Abutement creation Size , length , top
            if self._EqualizeHeights == True :
                Logger.log('d', 'SHeights : ' + str(self._SHeights)) 
                if self._SHeights==0 :
                    self._SHeights=position.y 
                top=self._UseSize+(self._SHeights-position.y)

            else:
                top=self._UseSize
                self._SHeights=0
            
            # Logger.log('d', 'top : ' + str(top))
            mesh =  self._createAbutment(self._UseSize,self._MaxSize,long,top,self._UseAngle,self._UseYDirection)
        else:           
            # Custom creation Size , P1 as vector P2 as vector
            # Get support_interface_height as extra distance 
            extruder_stack = self._application.getExtruderManager().getActiveExtruderStacks()[0]
            extra_top=extruder_stack.getProperty("support_interface_height", "value")            
            mesh =  self._createCustom(self._UseSize,self._MaxSize,position,position2,self._UseAngle,extra_top)

        # Mesh Freeform are loaded via trimesh doesn't aheve the Build method
        if self._SType != 'freeform':
            node.setMeshData(mesh.build())
        else:
            node.setMeshData(mesh)

        # test for init position
        node_transform = Matrix()
        node_transform.setToIdentity()
        node.setTransformation(node_transform)
        
        active_build_plate = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
        node.addDecorator(BuildPlateDecorator(active_build_plate))
        node.addDecorator(SliceableObjectDecorator())
              
        stack = node.callDecoration("getStack") # created by SettingOverrideDecorator that is automatically added to CuraSceneNode

        settings = stack.getTop()

        # Define the new mesh as "support_mesh" or "support_mesh_drop_down"
        # Must be set for this 2 types
        # for key in ["support_mesh", "support_mesh_drop_down"]:
        # Don't fix
        
        definition = stack.getSettingDefinition("support_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)

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

        global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()    
        
        s_p = global_container_stack.getProperty("support_type", "value")
        if s_p ==  'buildplate' :
            Message(text = "Info modification support_type new value : everywhere", title = catalog.i18nc("@info:title", "Custom Supports Cylinder")).show()
            Logger.log('d', 'support_type different : ' + str(s_p))
            # Define support_type=everywhere
            global_container_stack.setProperty("support_type", "value", 'everywhere')
            
        op = GroupedOperation()
        # First add node to the scene at the correct position/scale, before parenting, so the support 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, CuraSceneNode.TransformSpace.World)

        CuraApplication.getInstance().getController().getScene().sceneChanged.emit(node)
示例#19
0
    def _constructSupport(self, buffer: QImage) -> None:
        depth_pass = PickingPass(
            buffer.width(), buffer.height()
        )  #Instead of using the picking pass to pick for us, we need to bulk-pick digits so do this in Numpy.
        depth_pass.render()
        depth_image = depth_pass.getOutput()
        camera = CuraApplication.getInstance().getController().getScene(
        ).getActiveCamera()

        #to_support = qimage2ndarray.raw_view(buffer)
        #to_support= _qimageview(_qt.QImage(buffer))
        to_support = self._raw_view(buffer)

        #depth = qimage2ndarray.recarray_view(depth_image)
        depth = self._recarray_view(depth_image)

        depth.a = 0  #Discard alpha channel.
        depth = depth.view(dtype=_np.int32).astype(
            _np.float32
        ) / 1000  #Conflate the R, G and B channels to one 24-bit (cast to 32) float. Divide by 1000 to get mm.
        support_positions_2d = _np.array(
            _np.where(_np.bitwise_and(to_support == 255, depth < 16777))
        )  #All the 2D coordinates on the screen where we want support. The 16777 is for points that don't land on a model.
        support_depths = _np.take(
            depth, support_positions_2d[0, :] * depth.shape[1] +
            support_positions_2d[1, :])  #The depth at those pixels.
        support_positions_2d = support_positions_2d.transpose(
        )  #We want rows with pixels, not columns with pixels.
        if len(support_positions_2d) == 0:
            Logger.log(
                "i",
                "Support was not drawn on the surface of any objects. Not creating support."
            )
            return
        support_positions_2d[:, [0, 1]] = support_positions_2d[:, [
            1, 0
        ]]  #Swap columns to get OpenGL's coordinate system.
        camera_viewport = _np.array(
            [camera.getViewportWidth(),
             camera.getViewportHeight()])
        support_positions_2d = support_positions_2d * 2.0 / camera_viewport - 1.0  #Scale to view coordinates (range -1 to 1).
        inverted_projection = _np.linalg.inv(
            camera.getProjectionMatrix().getData())
        transformation = camera.getWorldTransformation().getData()
        transformation[:,
                       1] = -transformation[:,
                                            1]  #Invert Z to get OpenGL's coordinate system.

        #For each pixel, get the near and far plane.
        near = _np.ndarray((support_positions_2d.shape[0], 4))
        near.fill(1)
        near[0:support_positions_2d.shape[0],
             0:support_positions_2d.shape[1]] = support_positions_2d
        near[:, 2].fill(-1)
        near = _np.dot(inverted_projection, near.transpose())
        near = _np.dot(transformation, near)
        near = near[0:3] / near[3]
        far = _np.ndarray((support_positions_2d.shape[0], 4))
        far.fill(1)
        far[0:support_positions_2d.shape[0],
            0:support_positions_2d.shape[1]] = support_positions_2d
        far = _np.dot(inverted_projection, far.transpose())
        far = _np.dot(transformation, far)
        far = far[0:3] / far[3]

        #Direction is from near plane pixel to far plane pixel, normalised.
        direction = near - far
        direction /= _np.linalg.norm(direction, axis=0)

        #Final position is in the direction of the pixel, moving with <depth> mm away from the camera position.
        support_positions_3d = (
            support_depths - 1
        ) * direction  #We want the support to appear just before the surface, not behind the surface, so - 1.
        support_positions_3d = support_positions_3d.transpose()
        camera_position_data = camera.getPosition().getData()
        support_positions_3d = support_positions_3d + camera_position_data

        #Create the vertices for the 3D mesh.
        #This mesh consists of a diamond-shape for each position that we traced.
        n = support_positions_3d.shape[0]
        Logger.log(
            "i",
            "Adding support in {num_pixels} locations.".format(num_pixels=n))
        vertices = support_positions_3d.copy().astype(_np.float32)
        vertices = _np.resize(vertices,
                              (n * 6, support_positions_3d.shape[1]
                               ))  #Resize will repeat all coordinates 6 times.
        #For each position, create a diamond shape around the position with 6 vertices.
        vertices[
            n * 0:n * 1,
            0] -= support_depths * 0.001 * self.globule_size  #First corner (-x, +y).
        vertices[n * 0:n * 1, 2] += support_depths * 0.001 * self.globule_size
        vertices[
            n * 1:n * 2,
            0] += support_depths * 0.001 * self.globule_size  #Second corner (+x, +y).
        vertices[n * 1:n * 2, 2] += support_depths * 0.001 * self.globule_size
        vertices[
            n * 2:n * 3,
            0] -= support_depths * 0.001 * self.globule_size  #Third corner (-x, -y).
        vertices[n * 2:n * 3, 2] -= support_depths * 0.001 * self.globule_size
        vertices[
            n * 3:n * 4,
            0] += support_depths * 0.001 * self.globule_size  #Fourth corner (+x, -y)
        vertices[n * 3:n * 4, 2] -= support_depths * 0.001 * self.globule_size
        vertices[n * 4:n * 5,
                 1] += support_depths * 0.001 * self.globule_size  #Top side.
        vertices[
            n * 5:n * 6,
            1] -= support_depths * 0.001 * self.globule_size  #Bottom side.

        #Create the faces of the diamond.
        indices = _np.arange(n, dtype=_np.int32)
        indices = _np.kron(indices, _np.ones(
            (3, 1))).astype(_np.int32).transpose()
        indices = _np.resize(
            indices, (n * 8, 3)
        )  #Creates 8 triangles using 3 times the same vertex, for each position: [[0, 0, 0], [1, 1, 1], ... , [0, 0, 0], [1, 1, 1], ... ]

        #indices[n * 0: n * 1, 0] += n * 0 #First corner.
        indices[n * 0:n * 1, 1] += n * 1  #Second corner.
        indices[n * 0:n * 1, 2] += n * 4  #Top side.

        indices[n * 1:n * 2, 0] += n * 1  #Second corner.
        indices[n * 1:n * 2, 1] += n * 3  #Fourth corner.
        indices[n * 1:n * 2, 2] += n * 4  #Top side.

        indices[n * 2:n * 3, 0] += n * 3  #Fourth corner.
        indices[n * 2:n * 3, 1] += n * 2  #Third corner.
        indices[n * 2:n * 3, 2] += n * 4  #Top side.

        indices[n * 3:n * 4, 0] += n * 2  #Third corner.
        #indices[n * 3: n * 4, 1] += n * 0 #First corner.
        indices[n * 3:n * 4, 2] += n * 4  #Top side.

        indices[n * 4:n * 5, 0] += n * 1  #Second corner.
        #indices[n * 4: n * 5, 1] += n * 0 #First corner.
        indices[n * 4:n * 5, 2] += n * 5  #Bottom side.

        indices[n * 5:n * 6, 0] += n * 3  #Fourth corner.
        indices[n * 5:n * 6, 1] += n * 1  #Second corner.
        indices[n * 5:n * 6, 2] += n * 5  #Bottom side.

        indices[n * 6:n * 7, 0] += n * 2  #Third corner.
        indices[n * 6:n * 7, 1] += n * 3  #Fourth corner.
        indices[n * 6:n * 7, 2] += n * 5  #Bottom side.

        #indices[n * 7: n * 8, 0] += n * 0 #First corner.
        indices[n * 7:n * 8, 1] += n * 2  #Third corner.
        indices[n * 7:n * 8, 2] += n * 5  #Bottom side.

        builder = MeshBuilder()
        builder.addVertices(vertices)
        builder.addIndices(indices)

        #Create the scene node.
        scene = CuraApplication.getInstance().getController().getScene()
        new_node = CuraSceneNode(parent=scene.getRoot(), name="BrushSupport")
        new_node.setSelectable(False)
        new_node.setMeshData(builder.build())
        new_node.addDecorator(
            BuildPlateDecorator(CuraApplication.getInstance().
                                getMultiBuildPlateModel().activeBuildPlate))
        new_node.addDecorator(SliceableObjectDecorator())
        operation = GroupedOperation()

        #Figure out which mesh this piece of support belongs to.
        #TODO: You can draw support in one stroke over multiple meshes. The support would belong to an arbitrary one of these.
        selection_pass = CuraApplication.getInstance().getRenderer(
        ).getRenderPass("selection")
        parent_id = selection_pass.getIdAtPosition(
            support_positions_2d[0][0], support_positions_2d[0]
            [1])  #Find the selection under the first support pixel.
        parent_node = scene.getRoot()
        if not parent_id:
            Logger.log("d", "Can't link custom support to any scene node.")
        else:
            for node in BreadthFirstIterator(scene.getRoot()):
                if id(node) == parent_id:
                    parent_node = node
                    break

        #Add the appropriate per-object settings.
        stack = new_node.callDecoration(
            "getStack"
        )  #Created by SettingOverrideDecorator that is automatically added to CuraSceneNode.
        settings = stack.getTop()
        support_mesh_instance = SettingInstance(
            stack.getSettingDefinition("support_mesh"), settings)
        support_mesh_instance.setProperty("value", True)
        support_mesh_instance.resetState()
        settings.addInstance(support_mesh_instance)
        drop_down_instance = SettingInstance(
            stack.getSettingDefinition("support_mesh_drop_down"), settings)
        drop_down_instance.setProperty("value", True)
        drop_down_instance.resetState()
        settings.addInstance(drop_down_instance)

        #Add the scene node to the scene (and allow for undo).
        operation.addOperation(
            AddSceneNodeOperation(new_node, scene.getRoot())
        )  #Set the parent to root initially, then change the parent, so that we don't have to alter the transformation.
        operation.addOperation(SetParentOperation(new_node, parent_node))
        operation.push()

        scene.sceneChanged.emit(new_node)
示例#20
0
    def run(self):
        Logger.log(
            "d", "Processing new layer for build plate %s..." %
            self._build_plate_number)
        start_time = time()
        view = Application.getInstance().getController().getActiveView()
        if view.getPluginId() == "SimulationView":
            view.resetLayerData()
            self._progress_message.show()
            Job.yieldThread()
            if self._abort_requested:
                if self._progress_message:
                    self._progress_message.hide()
                return

        Application.getInstance().getController().activeViewChanged.connect(
            self._onActiveViewChanged)

        # The no_setting_override is here because adding the SettingOverrideDecorator will trigger a reslice
        new_node = CuraSceneNode(no_setting_override=True)
        new_node.addDecorator(BuildPlateDecorator(self._build_plate_number))

        # Force garbage collection.
        # For some reason, Python has a tendency to keep the layer data
        # in memory longer than needed. Forcing the GC to run here makes
        # sure any old layer data is really cleaned up before adding new.
        gc.collect()

        mesh = MeshData()
        layer_data = LayerDataBuilder.LayerDataBuilder()
        layer_count = len(self._layers)

        # Find the minimum layer number
        # When using a raft, the raft layers are sent as layers < 0. Instead of allowing layers < 0, we
        # instead simply offset all other layers so the lowest layer is always 0. It could happens that
        # the first raft layer has value -8 but there are just 4 raft (negative) layers.
        min_layer_number = 0
        negative_layers = 0
        for layer in self._layers:
            if layer.id < min_layer_number:
                min_layer_number = layer.id
            if layer.id < 0:
                negative_layers += 1

        current_layer = 0

        for layer in self._layers:
            # Negative layers are offset by the minimum layer number, but the positive layers are just
            # offset by the number of negative layers so there is no layer gap between raft and model
            abs_layer_number = layer.id + abs(
                min_layer_number
            ) if layer.id < 0 else layer.id + negative_layers

            layer_data.addLayer(abs_layer_number)
            this_layer = layer_data.getLayer(abs_layer_number)
            layer_data.setLayerHeight(abs_layer_number, layer.height)
            layer_data.setLayerThickness(abs_layer_number, layer.thickness)

            for p in range(layer.repeatedMessageCount("path_segment")):
                polygon = layer.getRepeatedMessage("path_segment", p)

                extruder = polygon.extruder

                line_types = numpy.fromstring(
                    polygon.line_type,
                    dtype="u1")  # Convert bytearray to numpy array
                line_types = line_types.reshape((-1, 1))

                points = numpy.fromstring(
                    polygon.points,
                    dtype="f4")  # Convert bytearray to numpy array
                if polygon.point_type == 0:  # Point2D
                    points = points.reshape(
                        (-1, 2)
                    )  # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
                else:  # Point3D
                    points = points.reshape((-1, 3))

                line_widths = numpy.fromstring(
                    polygon.line_width,
                    dtype="f4")  # Convert bytearray to numpy array
                line_widths = line_widths.reshape(
                    (-1, 1)
                )  # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.

                line_thicknesses = numpy.fromstring(
                    polygon.line_thickness,
                    dtype="f4")  # Convert bytearray to numpy array
                line_thicknesses = line_thicknesses.reshape(
                    (-1, 1)
                )  # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.

                line_feedrates = numpy.fromstring(
                    polygon.line_feedrate,
                    dtype="f4")  # Convert bytearray to numpy array
                line_feedrates = line_feedrates.reshape(
                    (-1, 1)
                )  # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.

                global_container_stack = Application.getInstance(
                ).getGlobalContainerStack()
                half_outer_wall_thickness = global_container_stack.getProperty(
                    "wall_line_width_0", "value") / 2

                # Adjust layer data to show Raft line type, if it is enabled
                if global_container_stack.getProperty("blackbelt_raft",
                                                      "value"):
                    raft_thickness = global_container_stack.getProperty(
                        "blackbelt_raft_thickness", "value")

                    extrusion_started = False
                    for index, segment_type in enumerate(line_types):
                        if points[index + 1][
                                1] <= half_outer_wall_thickness + raft_thickness:
                            if segment_type in [
                                    LayerPolygon.LayerPolygon.Inset0Type,
                                    LayerPolygon.LayerPolygon.InsetXType
                            ]:
                                line_types[
                                    index] = LayerPolygon.LayerPolygon.SkirtType
                                extrusion_started = True
                            elif extrusion_started:
                                break

                # Adjust layer data to show Belt Wall feed rate, if it is enabled
                if global_container_stack.getProperty(
                        "blackbelt_belt_wall_enabled", "value"):
                    belt_wall_feedrate = global_container_stack.getProperty(
                        "blackbelt_belt_wall_speed", "value")

                    belt_wall_indices = []
                    for index, point in enumerate(points):
                        if point[1] <= half_outer_wall_thickness:
                            if last_point_hit_wall and line_feedrates[
                                    index - 1] > belt_wall_feedrate:
                                belt_wall_indices.append(index)
                            last_point_hit_wall = True
                        else:
                            last_point_hit_wall = False

                    dimensionality = points.shape[1]
                    edited_points = points.flatten()
                    line_types = line_types.flatten()
                    line_widths = line_widths.flatten()
                    line_thicknesses = line_thicknesses.flatten()
                    line_feedrates = line_feedrates.flatten()
                    for index in reversed(belt_wall_indices):
                        edited_points = numpy.insert(
                            edited_points, dimensionality * (index),
                            numpy.append(points[index - 1], points[index]))
                        line_types = numpy.insert(line_types, index,
                                                  [line_types[index - 1]] * 2)
                        line_widths = numpy.insert(
                            line_widths, index, [line_widths[index - 1]] * 2)
                        line_thicknesses = numpy.insert(
                            line_thicknesses, index,
                            [line_thicknesses[index - 1]] * 2)
                        line_feedrates = numpy.insert(line_feedrates,
                                                      index - 1,
                                                      [belt_wall_feedrate] * 2)

                    # Fix shape of adjusted data
                    if polygon.point_type == 0:
                        points = edited_points.reshape(
                            (-1, 2)
                        )  # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
                    else:
                        points = edited_points.reshape((-1, 3))

                    line_types = line_types.reshape((-1, 1))
                    line_widths = line_widths.reshape((-1, 1))
                    line_thicknesses = line_thicknesses.reshape((-1, 1))
                    line_feedrates = line_feedrates.reshape((-1, 1))

                # Create a new 3D-array, copy the 2D points over and insert the right height.
                # This uses manual array creation + copy rather than numpy.insert since this is
                # faster.
                new_points = numpy.empty((len(points), 3), numpy.float32)
                if polygon.point_type == 0:  # Point2D
                    new_points[:, 0] = points[:, 0]
                    new_points[:,
                               1] = layer.height / 1000  # layer height value is in backend representation
                    new_points[:, 2] = -points[:, 1]
                else:  # Point3D
                    new_points[:, 0] = points[:, 0]
                    new_points[:, 1] = points[:, 2]
                    new_points[:, 2] = -points[:, 1]

                this_poly = LayerPolygon.LayerPolygon(extruder, line_types,
                                                      new_points, line_widths,
                                                      line_thicknesses,
                                                      line_feedrates)
                this_poly.buildCache()

                this_layer.polygons.append(this_poly)

                Job.yieldThread()
            Job.yieldThread()
            current_layer += 1
            progress = (current_layer / layer_count) * 99
            # TODO: Rebuild the layer data mesh once the layer has been processed.
            # This needs some work in LayerData so we can add the new layers instead of recreating the entire mesh.

            if self._abort_requested:
                if self._progress_message:
                    self._progress_message.hide()
                return
            if self._progress_message:
                self._progress_message.setProgress(progress)

        # We are done processing all the layers we got from the engine, now create a mesh out of the data

        # Find out colors per extruder
        global_container_stack = Application.getInstance(
        ).getGlobalContainerStack()
        manager = ExtruderManager.getInstance()
        extruders = list(
            manager.getMachineExtruders(global_container_stack.getId()))
        if extruders:
            material_color_map = numpy.zeros((len(extruders), 4),
                                             dtype=numpy.float32)
            for extruder in extruders:
                position = int(
                    extruder.getMetaDataEntry("position",
                                              default="0"))  # Get the position
                try:
                    default_color = ExtrudersModel.defaultColors[position]
                except IndexError:
                    default_color = "#e0e000"
                color_code = extruder.material.getMetaDataEntry(
                    "color_code", default=default_color)
                color = colorCodeToRGBA(color_code)
                material_color_map[position, :] = color
        else:
            # Single extruder via global stack.
            material_color_map = numpy.zeros((1, 4), dtype=numpy.float32)
            color_code = global_container_stack.material.getMetaDataEntry(
                "color_code", default="#e0e000")
            color = colorCodeToRGBA(color_code)
            material_color_map[0, :] = color

        # We have to scale the colors for compatibility mode
        if OpenGLContext.isLegacyOpenGL() or bool(Preferences.getInstance(
        ).getValue("view/force_layer_view_compatibility_mode")):
            line_type_brightness = 0.5  # for compatibility mode
        else:
            line_type_brightness = 1.0
        layer_mesh = layer_data.build(material_color_map, line_type_brightness)

        if self._abort_requested:
            if self._progress_message:
                self._progress_message.hide()
            return

        # Add LayerDataDecorator to scene node to indicate that the node has layer data
        decorator = LayerDataDecorator.LayerDataDecorator()
        decorator.setLayerData(layer_mesh)
        new_node.addDecorator(decorator)

        new_node.setMeshData(mesh)
        # Set build volume as parent, the build volume can move as a result of raft settings.
        # It makes sense to set the build volume as parent: the print is actually printed on it.
        new_node_parent = Application.getInstance().getBuildVolume()
        new_node.setParent(
            new_node_parent)  # Note: After this we can no longer abort!

        settings = Application.getInstance().getGlobalContainerStack()
        if not settings.getProperty("machine_center_is_zero", "value"):
            new_node.setPosition(
                Vector(-settings.getProperty("machine_width", "value") / 2,
                       0.0,
                       settings.getProperty("machine_depth", "value") / 2))

        transform = self._scene.getRoot().callDecoration("getTransformMatrix")
        if transform and transform != Matrix():
            transform_matrix = new_node.getLocalTransformation().preMultiply(
                transform.getInverse())
            new_node.setTransformation(transform_matrix)
            front_offset = self._scene.getRoot().callDecoration(
                "getSceneFrontOffset")
            if global_container_stack.getProperty("blackbelt_raft", "value"):
                front_offset = front_offset - global_container_stack.getProperty("blackbelt_raft_margin", "value") \
                                            - global_container_stack.getProperty("blackbelt_raft_thickness", "value")
            new_node.translate(Vector(0, 0, front_offset),
                               SceneNode.TransformSpace.World)

        if self._progress_message:
            self._progress_message.setProgress(100)

        if self._progress_message:
            self._progress_message.hide()

        # Clear the unparsed layers. This saves us a bunch of memory if the Job does not get destroyed.
        self._layers = None

        Logger.log("d", "Processing layers took %s seconds",
                   time() - start_time)
示例#21
0
    def run(self):
        Logger.log("d", "Processing new layer for build plate %s..." % self._build_plate_number)
        start_time = time()
        view = Application.getInstance().getController().getActiveView()
        if view.getPluginId() == "SimulationView":
            view.resetLayerData()
            self._progress_message.show()
            Job.yieldThread()
            if self._abort_requested:
                if self._progress_message:
                    self._progress_message.hide()
                return

        Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged)

        # The no_setting_override is here because adding the SettingOverrideDecorator will trigger a reslice
        new_node = CuraSceneNode(no_setting_override = True)
        new_node.addDecorator(BuildPlateDecorator(self._build_plate_number))

        # Force garbage collection.
        # For some reason, Python has a tendency to keep the layer data
        # in memory longer than needed. Forcing the GC to run here makes
        # sure any old layer data is really cleaned up before adding new.
        gc.collect()

        mesh = MeshData()
        layer_data = LayerDataBuilder.LayerDataBuilder()
        layer_count = len(self._layers)

        # Find the minimum layer number
        # When disabling the remove empty first layers setting, the minimum layer number will be a positive
        # value. In that case the first empty layers will be discarded and start processing layers from the
        # first layer with data.
        # When using a raft, the raft layers are sent as layers < 0. Instead of allowing layers < 0, we
        # simply offset all other layers so the lowest layer is always 0. It could happens that the first
        # raft layer has value -8 but there are just 4 raft (negative) layers.
        min_layer_number = sys.maxsize
        negative_layers = 0
        for layer in self._layers:
            if layer.repeatedMessageCount("path_segment") > 0:
                if layer.id < min_layer_number:
                    min_layer_number = layer.id
                if layer.id < 0:
                    negative_layers += 1

        current_layer = 0

        for layer in self._layers:
            # If the layer is below the minimum, it means that there is no data, so that we don't create a layer
            # data. However, if there are empty layers in between, we compute them.
            if layer.id < min_layer_number:
                continue

            # Layers are offset by the minimum layer number. In case the raft (negative layers) is being used,
            # then the absolute layer number is adjusted by removing the empty layers that can be in between raft
            # and the model
            abs_layer_number = layer.id - min_layer_number
            if layer.id >= 0 and negative_layers != 0:
                abs_layer_number += (min_layer_number + negative_layers)

            layer_data.addLayer(abs_layer_number)
            this_layer = layer_data.getLayer(abs_layer_number)
            layer_data.setLayerHeight(abs_layer_number, layer.height)
            layer_data.setLayerThickness(abs_layer_number, layer.thickness)

            for p in range(layer.repeatedMessageCount("path_segment")):
                polygon = layer.getRepeatedMessage("path_segment", p)

                extruder = polygon.extruder

                line_types = numpy.fromstring(polygon.line_type, dtype="u1")  # Convert bytearray to numpy array

                line_types = line_types.reshape((-1,1))

                points = numpy.fromstring(polygon.points, dtype="f4")  # Convert bytearray to numpy array
                if polygon.point_type == 0: # Point2D
                    points = points.reshape((-1,2))  # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
                else:  # Point3D
                    points = points.reshape((-1,3))

                line_widths = numpy.fromstring(polygon.line_width, dtype="f4")  # Convert bytearray to numpy array
                line_widths = line_widths.reshape((-1,1))  # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.

                line_thicknesses = numpy.fromstring(polygon.line_thickness, dtype="f4")  # Convert bytearray to numpy array
                line_thicknesses = line_thicknesses.reshape((-1,1))  # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.

                line_feedrates = numpy.fromstring(polygon.line_feedrate, dtype="f4")  # Convert bytearray to numpy array
                line_feedrates = line_feedrates.reshape((-1,1))  # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.

                # Create a new 3D-array, copy the 2D points over and insert the right height.
                # This uses manual array creation + copy rather than numpy.insert since this is
                # faster.
                new_points = numpy.empty((len(points), 3), numpy.float32)
                if polygon.point_type == 0:  # Point2D
                    new_points[:, 0] = points[:, 0]
                    new_points[:, 1] = layer.height / 1000  # layer height value is in backend representation
                    new_points[:, 2] = -points[:, 1]
                else: # Point3D
                    new_points[:, 0] = points[:, 0]
                    new_points[:, 1] = points[:, 2]
                    new_points[:, 2] = -points[:, 1]

                this_poly = LayerPolygon.LayerPolygon(extruder, line_types, new_points, line_widths, line_thicknesses, line_feedrates)
                this_poly.buildCache()

                this_layer.polygons.append(this_poly)

                Job.yieldThread()
            Job.yieldThread()
            current_layer += 1
            progress = (current_layer / layer_count) * 99
            # TODO: Rebuild the layer data mesh once the layer has been processed.
            # This needs some work in LayerData so we can add the new layers instead of recreating the entire mesh.

            if self._abort_requested:
                if self._progress_message:
                    self._progress_message.hide()
                return
            if self._progress_message:
                self._progress_message.setProgress(progress)

        # We are done processing all the layers we got from the engine, now create a mesh out of the data

        # Find out colors per extruder
        global_container_stack = Application.getInstance().getGlobalContainerStack()
        manager = ExtruderManager.getInstance()
        extruders = manager.getActiveExtruderStacks()
        if extruders:
            material_color_map = numpy.zeros((len(extruders), 4), dtype=numpy.float32)
            for extruder in extruders:
                position = int(extruder.getMetaDataEntry("position", default = "0"))
                try:
                    default_color = ExtrudersModel.defaultColors[position]
                except IndexError:
                    default_color = "#e0e000"
                color_code = extruder.material.getMetaDataEntry("color_code", default=default_color)
                color = colorCodeToRGBA(color_code)
                material_color_map[position, :] = color
        else:
            # Single extruder via global stack.
            material_color_map = numpy.zeros((1, 4), dtype=numpy.float32)
            color_code = global_container_stack.material.getMetaDataEntry("color_code", default="#e0e000")
            color = colorCodeToRGBA(color_code)
            material_color_map[0, :] = color

        # We have to scale the colors for compatibility mode
        if OpenGLContext.isLegacyOpenGL() or bool(Application.getInstance().getPreferences().getValue("view/force_layer_view_compatibility_mode")):
            line_type_brightness = 0.5  # for compatibility mode
        else:
            line_type_brightness = 1.0
        layer_mesh = layer_data.build(material_color_map, line_type_brightness)

        if self._abort_requested:
            if self._progress_message:
                self._progress_message.hide()
            return

        # Add LayerDataDecorator to scene node to indicate that the node has layer data
        decorator = LayerDataDecorator.LayerDataDecorator()
        decorator.setLayerData(layer_mesh)
        new_node.addDecorator(decorator)

        new_node.setMeshData(mesh)
        # Set build volume as parent, the build volume can move as a result of raft settings.
        # It makes sense to set the build volume as parent: the print is actually printed on it.
        new_node_parent = Application.getInstance().getBuildVolume()
        new_node.setParent(new_node_parent)  # Note: After this we can no longer abort!

        settings = Application.getInstance().getGlobalContainerStack()
        if not settings.getProperty("machine_center_is_zero", "value"):
            new_node.setPosition(Vector(-settings.getProperty("machine_width", "value") / 2, 0.0, settings.getProperty("machine_depth", "value") / 2))

        if self._progress_message:
            self._progress_message.setProgress(100)

        if self._progress_message:
            self._progress_message.hide()

        # Clear the unparsed layers. This saves us a bunch of memory if the Job does not get destroyed.
        self._layers = None

        Logger.log("d", "Processing layers took %s seconds", time() - start_time)
示例#22
0
    def _convertSavitarNodeToUMNode(
            self,
            savitar_node: Savitar.SceneNode,
            file_name: str = "") -> Optional[SceneNode]:
        """Convenience function that converts a SceneNode object (as obtained from libSavitar) to a scene node.

        :returns: Scene node.
        """
        try:
            node_name = savitar_node.getName()
            node_id = savitar_node.getId()
        except AttributeError:
            Logger.log(
                "e",
                "Outdated version of libSavitar detected! Please update to the newest version!"
            )
            node_name = ""
            node_id = ""

        if node_name == "":
            if file_name != "":
                node_name = os.path.basename(file_name)
            else:
                node_name = "Object {}".format(node_id)

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

        um_node = CuraSceneNode()  # This adds a SettingOverrideDecorator
        um_node.addDecorator(BuildPlateDecorator(active_build_plate))
        try:
            um_node.addDecorator(ConvexHullDecorator())
        except:
            pass
        um_node.setName(node_name)
        um_node.setId(node_id)
        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)
        if file_name:
            # The filename is used to give the user the option to reload the file if it is changed on disk
            # It is only set for the root node of the 3mf file
            mesh_builder.setFileName(file_name)
        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 = CuraApplication.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 = ContainerTree.getInstance().machines[
                    global_container_stack.definition.getId(
                    )].quality_definition
                um_node.callDecoration("getStack").getTop().setDefinition(
                    definition_id)

            setting_container = um_node.callDecoration("getStack").getTop()
            known_setting_keys = um_node.callDecoration(
                "getStack").getAllKeys()
            for key in settings:
                setting_value = settings[key].value

                # 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
                if key in known_setting_keys:
                    setting_container.setProperty(key, "value", setting_value)
                else:
                    um_node.metadata[key] = settings[key]

        if len(um_node.getChildren()) > 0 and um_node.getMeshData() is None:
            if len(um_node.getAllChildren()) == 1:
                # We don't want groups of one, so move the node up one "level"
                child_node = um_node.getChildren()[0]
                parent_transformation = um_node.getLocalTransformation()
                child_transformation = child_node.getLocalTransformation()
                child_node.setTransformation(
                    parent_transformation.multiply(child_transformation))
                um_node = cast(CuraSceneNode, um_node.getChildren()[0])
            else:
                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
示例#23
0
    def _createSupportMesh(self, parent: CuraSceneNode, position: Vector):
        node = CuraSceneNode()

        node.setName("RoundTab")
            
        node.setSelectable(True)
        
        # long=Support Height
        _long=position.y

        # get layer_height_0 used to define pastille height
        _id_ex=0
        
        # This function can be triggered in the middle of a machine change, so do not proceed if the machine change
        # has not done yet.
        global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
        #extruder = global_container_stack.extruderList[int(_id_ex)] 
        extruder_stack = CuraApplication.getInstance().getExtruderManager().getActiveExtruderStacks()[0]        
        _layer_h_i = extruder_stack.getProperty("layer_height_0", "value")
        _layer_height = extruder_stack.getProperty("layer_height", "value")
        _line_w = extruder_stack.getProperty("line_width", "value")
        # Logger.log('d', 'layer_height_0 : ' + str(_layer_h_i))
        _layer_h = (_layer_h_i * 1.2) + (_layer_height * (self._Nb_Layer -1) )
        _line_w = _line_w * 1.2 
        
        if self._AsCapsule:
             # Capsule creation Diameter , Increment angle 4°, length, layer_height_0*1.2 , line_width
            mesh = self._createCapsule(self._UseSize,4,_long,_layer_h,_line_w)       
        else:
            # Cylinder creation Diameter , Increment angle 4°, length, layer_height_0*1.2
            mesh = self._createPastille(self._UseSize,4,_long,_layer_h)
        
        node.setMeshData(mesh.build())

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

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

        # support_mesh type
        definition = stack.getSettingDefinition("support_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)

        definition = stack.getSettingDefinition("support_mesh_drop_down")
        new_instance = SettingInstance(definition, settings)
        new_instance.setProperty("value", False)
        new_instance.resetState()  # Ensure that the state is not seen as a user state.
        settings.addInstance(new_instance)
 
        if self._AsCapsule:
            s_p = global_container_stack.getProperty("support_type", "value")
            if s_p ==  'buildplate' :
                Message(text = "Info modification current profile support_type parameter\nNew value : everywhere", title = catalog.i18nc("@info:title", "Warning ! Tab Anti Warping")).show()
                Logger.log('d', 'support_type different : ' + str(s_p))
                # Define support_type=everywhere
                global_container_stack.setProperty("support_type", "value", 'everywhere')
                
            
        # Define support_xy_distance
        definition = stack.getSettingDefinition("support_xy_distance")
        new_instance = SettingInstance(definition, settings)
        new_instance.setProperty("value", self._UseOffset)
        # new_instance.resetState()  # Ensure that the state is not seen as a user state.
        settings.addInstance(new_instance)

        # Fix some settings in Cura to get a better result
        id_ex=0
        global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
        extruder_stack = CuraApplication.getInstance().getExtruderManager().getActiveExtruderStacks()[0]
        #extruder = global_container_stack.extruderList[int(id_ex)]    
        
        # hop to fix it in a futur release
        # https://github.com/Ultimaker/Cura/issues/9882
        # if self.Major < 5 or ( self.Major == 5 and self.Minor < 1 ) :
        _xy_distance = extruder_stack.getProperty("support_xy_distance", "value")
        if self._UseOffset !=  _xy_distance :
            _msg = "New value : %8.3f" % (self._UseOffset) 
            Message(text = "Info modification current profile support_xy_distance parameter\nNew value : %8.3f" % (self._UseOffset), title = catalog.i18nc("@info:title", "Warning ! Tab Anti Warping")).show()
            Logger.log('d', 'support_xy_distance different : ' + str(_xy_distance))
            # Define support_xy_distance
            extruder_stack.setProperty("support_xy_distance", "value", self._UseOffset)
 
        if self._Nb_Layer >1 :
            s_p = int(extruder_stack.getProperty("support_infill_rate", "value"))
            Logger.log('d', 'support_infill_rate actual : ' + str(s_p))
            if s_p < 99 :
                Message(text = "Info modification current profile support_infill_rate parameter\nNew value : 100%", title = catalog.i18nc("@info:title", "Warning ! Tab Anti Warping")).show()
                Logger.log('d', 'support_infill_rate different : ' + str(s_p))
                # Define support_infill_rate=100%
                extruder_stack.setProperty("support_infill_rate", "value", 100)
                
        
        
        op = GroupedOperation()
        # First add node to the scene at the correct position/scale, before parenting, so the support 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, CuraSceneNode.TransformSpace.World)

        CuraApplication.getInstance().getController().getScene().sceneChanged.emit(node)
示例#24
0
    def _readMeshFinished(self, job):
        Logger.log("d", "read mesh finisihed!")

        ### START PATCH: detect belt printer
        global_container_stack = self._application.getGlobalContainerStack()
        if not global_container_stack:
            return

        is_belt_printer = self._preferences.getValue("BeltPlugin/on_plugin")
        ### END PATCH

        nodes = job.getResult()
        file_name = job.getFileName()
        file_name_lower = file_name.lower()
        file_extension = file_name_lower.split(".")[-1]
        self._application._currently_loading_files.remove(file_name)

        self._application.fileLoaded.emit(file_name)
        target_build_plate = self._application.getMultiBuildPlateModel(
        ).activeBuildPlate

        root = self._application.getController().getScene().getRoot()
        fixed_nodes = []
        for node_ in DepthFirstIterator(root):
            if node_.callDecoration("isSliceable") and node_.callDecoration(
                    "getBuildPlateNumber") == target_build_plate:
                fixed_nodes.append(node_)
        global_container_stack = self._application.getGlobalContainerStack()
        machine_width = global_container_stack.getProperty(
            "machine_width", "value")
        machine_depth = global_container_stack.getProperty(
            "machine_depth", "value")
        arranger = Arrange.create(x=machine_width,
                                  y=machine_depth,
                                  fixed_nodes=fixed_nodes)
        min_offset = 8
        default_extruder_position = self._application.getMachineManager(
        ).defaultExtruderPosition
        default_extruder_id = self._application._global_container_stack.extruders[
            default_extruder_position].getId()

        select_models_on_load = self._application.getPreferences().getValue(
            "cura/select_models_on_load")

        for original_node in nodes:

            # Create a CuraSceneNode just if the original node is not that type
            if isinstance(original_node, CuraSceneNode):
                node = original_node
            else:
                node = CuraSceneNode()
                node.setMeshData(original_node.getMeshData())

                #Setting meshdata does not apply scaling.
                if (original_node.getScale() != Vector(1.0, 1.0, 1.0)):
                    node.scale(original_node.getScale())

            node.setSelectable(True)
            node.setName(os.path.basename(file_name))
            self._application.getBuildVolume().checkBoundsAndUpdate(node)

            is_non_sliceable = "." + file_extension in self._application._non_sliceable_extensions

            if is_non_sliceable:
                self._application.callLater(
                    lambda: self._application.getController().setActiveView(
                        "SimulationView"))

                block_slicing_decorator = BlockSlicingDecorator()
                node.addDecorator(block_slicing_decorator)
            else:
                sliceable_decorator = SliceableObjectDecorator()
                node.addDecorator(sliceable_decorator)

            scene = self._application.getController().getScene()

            # If there is no convex hull for the node, start calculating it and continue.
            if not node.getDecorator(ConvexHullDecorator):
                node.addDecorator(ConvexHullDecorator())
            for child in node.getAllChildren():
                if not child.getDecorator(ConvexHullDecorator):
                    child.addDecorator(ConvexHullDecorator())

            ### START PATCH: don't do standard arrange on load for belt printers
            ###              but place in a line instead
            if is_belt_printer:
                half_node_depth = node.getBoundingBox().depth / 2
                build_plate_empty = True
                leading_edge = self._application.getBuildVolume(
                ).getBoundingBox().front

                for existing_node in DepthFirstIterator(root):
                    if (not issubclass(type(existing_node), CuraSceneNode) or
                        (not existing_node.getMeshData()
                         and not existing_node.callDecoration("getLayerData"))
                            or
                        (existing_node.callDecoration("getBuildPlateNumber") !=
                         target_build_plate)):

                        continue

                    build_plate_empty = False
                    leading_edge = min(leading_edge,
                                       existing_node.getBoundingBox().back)

                if not build_plate_empty or leading_edge < half_node_depth:
                    node.setPosition(
                        Vector(
                            0, 0, leading_edge - half_node_depth -
                            self._margin_between_models))

            if file_extension != "3mf" and not is_belt_printer:
                ### END PATCH
                if node.callDecoration("isSliceable"):
                    # Only check position if it's not already blatantly obvious that it won't fit.
                    if node.getBoundingBox(
                    ) is None or self._application._volume.getBoundingBox(
                    ) is None or node.getBoundingBox(
                    ).width < self._application._volume.getBoundingBox(
                    ).width or node.getBoundingBox(
                    ).depth < self._application._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._application._i18n_catalog.i18nc(
                                "@info:status",
                                "The selected model was too small to load."),
                                    title=self._application._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
                        arranger.findNodePlacement(node,
                                                   offset_shape_arr,
                                                   hull_shape_arr,
                                                   step=10)

            # This node is deep copied from some other node which already has a BuildPlateDecorator, but the deepcopy
            # of BuildPlateDecorator produces one that's associated with build plate -1. So, here we need to check if
            # the BuildPlateDecorator exists or not and always set the correct build plate number.
            build_plate_decorator = node.getDecorator(BuildPlateDecorator)
            if build_plate_decorator is None:
                build_plate_decorator = BuildPlateDecorator(target_build_plate)
                node.addDecorator(build_plate_decorator)
            build_plate_decorator.setBuildPlateNumber(target_build_plate)

            op = AddSceneNodeOperation(node, scene.getRoot())
            op.push()

            node.callDecoration("setActiveExtruder", default_extruder_id)
            scene.sceneChanged.emit(node)

            if select_models_on_load:
                Selection.add(node)

        self._application.fileCompleted.emit(file_name)
示例#25
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 = CuraSceneNode()  # 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
示例#26
0
    def _read(self, file_name):
        base_name = os.path.basename(file_name)
        try:
            zipped_file = zipfile.ZipFile(file_name)
            xml_document = zipped_file.read(zipped_file.namelist()[0])
            zipped_file.close()
        except zipfile.BadZipfile:
            raw_file = open(file_name, "r")
            xml_document = raw_file.read()
            raw_file.close()

        try:
            amf_document = ET.fromstring(xml_document)
        except ET.ParseError:
            Logger.log("e", "Could not parse XML in file %s" % base_name)
            return None

        if "unit" in amf_document.attrib:
            unit = amf_document.attrib["unit"].lower()
        else:
            unit = "millimeter"
        if unit == "millimeter":
            scale = 1.0
        elif unit == "meter":
            scale = 1000.0
        elif unit == "inch":
            scale = 25.4
        elif unit == "feet":
            scale = 304.8
        elif unit == "micron":
            scale = 0.001
        else:
            Logger.log("w", "Unknown unit in amf: %s. Using mm instead." % unit)
            scale = 1.0

        nodes = []
        for amf_object in amf_document.iter("object"):
            for amf_mesh in amf_object.iter("mesh"):
                amf_mesh_vertices = []
                for vertices in amf_mesh.iter("vertices"):
                    for vertex in vertices.iter("vertex"):
                        for coordinates in vertex.iter("coordinates"):
                            v = [0.0, 0.0, 0.0]
                            for t in coordinates:
                                if t.tag == "x":
                                    v[0] = float(t.text) * scale
                                elif t.tag == "y":
                                    v[2] = float(t.text) * scale
                                elif t.tag == "z":
                                    v[1] = float(t.text) * scale
                            amf_mesh_vertices.append(v)
                if not amf_mesh_vertices:
                    continue

                indices = []
                for volume in amf_mesh.iter("volume"):
                    for triangle in volume.iter("triangle"):
                        f = [0, 0, 0]
                        for t in triangle:
                            if t.tag == "v1":
                                f[0] = int(t.text)
                            elif t.tag == "v2":
                                f[1] = int(t.text)
                            elif t.tag == "v3":
                                f[2] = int(t.text)
                        indices.append(f)

                    mesh = trimesh.base.Trimesh(vertices=numpy.array(amf_mesh_vertices, dtype=numpy.float32), faces=numpy.array(indices, dtype=numpy.int32))
                    mesh.merge_vertices()
                    mesh.remove_unreferenced_vertices()
                    mesh.fix_normals()
                    mesh_data = self._toMeshData(mesh)

                    new_node = CuraSceneNode()
                    new_node.setSelectable(True)
                    new_node.setMeshData(mesh_data)
                    new_node.setName(base_name if len(nodes)==0 else "%s %d" % (base_name, len(nodes)))
                    new_node.addDecorator(BuildPlateDecorator(CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate))
                    new_node.addDecorator(SliceableObjectDecorator())

                    nodes.append(new_node)

        if not nodes:
            Logger.log("e", "No meshes in file %s" % base_name)
            return None

        if len(nodes) == 1:
            return nodes[0]

        # Add all scenenodes to a group so they stay together
        group_node = CuraSceneNode()
        group_node.addDecorator(GroupDecorator())
        group_node.addDecorator(ConvexHullDecorator())
        group_node.addDecorator(BuildPlateDecorator(CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate))

        for node in nodes:
            node.setParent(group_node)

        return group_node
    def updateSceneFromOptimizationResult(
            self, analysis: pywim.smartslice.result.Analysis):

        type_map = {
            'int': int,
            'float': float,
            'str': str,
            'enum': str,
            'bool': bool
        }

        our_only_node = getPrintableNodes()[0]
        active_extruder = getNodeActiveExtruder(our_only_node)

        # TODO - Move this into a common class or function to apply an am.Config to GlobalStack/ExtruderStack
        if analysis.print_config.infill:

            infill_density = analysis.print_config.infill.density
            infill_pattern = analysis.print_config.infill.pattern

            if infill_pattern is None or infill_pattern == pywim.am.InfillType.unknown:
                infill_pattern = pywim.am.InfillType.grid

            infill_pattern_name = SmartSliceJobHandler.INFILL_SMARTSLICE_CURA[
                infill_pattern]

            extruder_dict = {
                "wall_line_count": analysis.print_config.walls,
                "top_layers": analysis.print_config.top_layers,
                "bottom_layers": analysis.print_config.bottom_layers,
                "infill_sparse_density": analysis.print_config.infill.density,
                "infill_pattern": infill_pattern_name
            }

            Logger.log("d",
                       "Optimized extruder settings: {}".format(extruder_dict))

            for key, value in extruder_dict.items():
                if value is not None:
                    property_type = type_map.get(
                        active_extruder.getProperty(key, "type"))
                    if property_type:
                        active_extruder.setProperty(key,
                                                    "value",
                                                    property_type(value),
                                                    set_from_cache=True)
                        active_extruder.setProperty(key,
                                                    "state",
                                                    InstanceState.User,
                                                    set_from_cache=True)

            Application.getInstance().getMachineManager(
            ).forceUpdateAllSettings()
            self.optimizationResultAppliedToScene.emit()

        # Remove any modifier meshes which are present from a previous result
        mod_meshes = getModifierMeshes()
        if len(mod_meshes) > 0:
            for node in mod_meshes:
                node.addDecorator(SmartSliceRemovedDecorator())
                our_only_node.removeChild(node)
            Application.getInstance().getController().getScene(
            ).sceneChanged.emit(node)

        # Add in the new modifier meshes
        for modifier_mesh in analysis.modifier_meshes:
            # Building the scene node
            modifier_mesh_node = CuraSceneNode()
            modifier_mesh_node.setName("SmartSliceMeshModifier")
            modifier_mesh_node.setSelectable(True)
            modifier_mesh_node.setCalculateBoundingBox(True)

            # Use the data from the SmartSlice engine to translate / rotate / scale the mod mesh
            modifier_mesh_node.setTransformation(
                Matrix(modifier_mesh.transform))

            # Building the mesh

            # # Preparing the data from pywim for MeshBuilder
            modifier_mesh_vertices = [[v.x, v.y, v.z]
                                      for v in modifier_mesh.vertices]
            modifier_mesh_indices = [[triangle.v1, triangle.v2, triangle.v3]
                                     for triangle in modifier_mesh.triangles]

            # Doing the actual build
            modifier_mesh_data = MeshBuilder()
            modifier_mesh_data.setVertices(
                numpy.asarray(modifier_mesh_vertices, dtype=numpy.float32))
            modifier_mesh_data.setIndices(
                numpy.asarray(modifier_mesh_indices, dtype=numpy.int32))
            modifier_mesh_data.calculateNormals()

            modifier_mesh_node.setMeshData(modifier_mesh_data.build())
            modifier_mesh_node.calculateBoundingBoxMesh()

            active_build_plate = Application.getInstance(
            ).getMultiBuildPlateModel().activeBuildPlate
            modifier_mesh_node.addDecorator(
                BuildPlateDecorator(active_build_plate))
            modifier_mesh_node.addDecorator(SliceableObjectDecorator())
            modifier_mesh_node.addDecorator(SmartSliceAddedDecorator())

            bottom = modifier_mesh_node.getBoundingBox().bottom

            z_offset_decorator = ZOffsetDecorator()
            z_offset_decorator.setZOffset(bottom)
            modifier_mesh_node.addDecorator(z_offset_decorator)

            stack = modifier_mesh_node.callDecoration("getStack")
            settings = stack.getTop()

            modifier_mesh_node_infill_pattern = SmartSliceJobHandler.INFILL_SMARTSLICE_CURA[
                modifier_mesh.print_config.infill.pattern]
            definition_dict = {
                "infill_mesh": True,
                "infill_pattern": modifier_mesh_node_infill_pattern,
                "infill_sparse_density":
                modifier_mesh.print_config.infill.density,
                "wall_line_count": modifier_mesh.print_config.walls,
                "top_layers": modifier_mesh.print_config.top_layers,
                "bottom_layers": modifier_mesh.print_config.bottom_layers,
            }
            Logger.log(
                "d",
                "Optimized modifier mesh settings: {}".format(definition_dict))

            for key, value in definition_dict.items():
                if value is not None:
                    definition = stack.getSettingDefinition(key)
                    property_type = type_map.get(stack.getProperty(
                        key, "type"))
                    if property_type:
                        new_instance = SettingInstance(definition, settings)

                        new_instance.setProperty("value", property_type(value))

                        new_instance.resetState(
                        )  # Ensure that the state is not seen as a user state.
                        settings.addInstance(new_instance)

            our_only_node.addChild(modifier_mesh_node)

            # emit changes and connect error tracker
            Application.getInstance().getController().getScene(
            ).sceneChanged.emit(modifier_mesh_node)
示例#28
0
    def run(self):
        Logger.log(
            "d", "Processing new layer for build plate %s..." %
            self._build_plate_number)
        start_time = time()
        view = Application.getInstance().getController().getActiveView()
        if view.getPluginId() == "SimulationView":
            view.resetLayerData()
            self._progress_message.show()
            Job.yieldThread()
            if self._abort_requested:
                if self._progress_message:
                    self._progress_message.hide()
                return

        Application.getInstance().getController().activeViewChanged.connect(
            self._onActiveViewChanged)

        # The no_setting_override is here because adding the SettingOverrideDecorator will trigger a reslice
        new_node = CuraSceneNode(no_setting_override=True)
        new_node.addDecorator(BuildPlateDecorator(self._build_plate_number))

        # Force garbage collection.
        # For some reason, Python has a tendency to keep the layer data
        # in memory longer than needed. Forcing the GC to run here makes
        # sure any old layer data is really cleaned up before adding new.
        gc.collect()

        mesh = MeshData()
        layer_data = LayerDataBuilder.LayerDataBuilder()
        layer_count = len(self._layers)

        # Find the minimum layer number
        # When disabling the remove empty first layers setting, the minimum layer number will be a positive
        # value. In that case the first empty layers will be discarded and start processing layers from the
        # first layer with data.
        # When using a raft, the raft layers are sent as layers < 0. Instead of allowing layers < 0, we
        # simply offset all other layers so the lowest layer is always 0. It could happens that the first
        # raft layer has value -8 but there are just 4 raft (negative) layers.
        min_layer_number = sys.maxsize
        negative_layers = 0
        for layer in self._layers:
            if layer.repeatedMessageCount("path_segment") > 0:
                if layer.id < min_layer_number:
                    min_layer_number = layer.id
                if layer.id < 0:
                    negative_layers += 1

        current_layer = 0

        for layer in self._layers:
            # If the layer is below the minimum, it means that there is no data, so that we don't create a layer
            # data. However, if there are empty layers in between, we compute them.
            if layer.id < min_layer_number:
                continue

            # Layers are offset by the minimum layer number. In case the raft (negative layers) is being used,
            # then the absolute layer number is adjusted by removing the empty layers that can be in between raft
            # and the model
            abs_layer_number = layer.id - min_layer_number
            if layer.id >= 0 and negative_layers != 0:
                abs_layer_number += (min_layer_number + negative_layers)

            layer_data.addLayer(abs_layer_number)
            this_layer = layer_data.getLayer(abs_layer_number)
            layer_data.setLayerHeight(abs_layer_number, layer.height)
            layer_data.setLayerThickness(abs_layer_number, layer.thickness)

            for p in range(layer.repeatedMessageCount("path_segment")):
                polygon = layer.getRepeatedMessage("path_segment", p)

                extruder = polygon.extruder

                line_types = numpy.fromstring(
                    polygon.line_type,
                    dtype="u1")  # Convert bytearray to numpy array

                line_types = line_types.reshape((-1, 1))

                points = numpy.fromstring(
                    polygon.points,
                    dtype="f4")  # Convert bytearray to numpy array
                if polygon.point_type == 0:  # Point2D
                    points = points.reshape(
                        (-1, 2)
                    )  # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
                else:  # Point3D
                    points = points.reshape((-1, 3))

                line_widths = numpy.fromstring(
                    polygon.line_width,
                    dtype="f4")  # Convert bytearray to numpy array
                line_widths = line_widths.reshape(
                    (-1, 1)
                )  # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.

                line_thicknesses = numpy.fromstring(
                    polygon.line_thickness,
                    dtype="f4")  # Convert bytearray to numpy array
                line_thicknesses = line_thicknesses.reshape(
                    (-1, 1)
                )  # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.

                line_feedrates = numpy.fromstring(
                    polygon.line_feedrate,
                    dtype="f4")  # Convert bytearray to numpy array
                line_feedrates = line_feedrates.reshape(
                    (-1, 1)
                )  # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.

                # Create a new 3D-array, copy the 2D points over and insert the right height.
                # This uses manual array creation + copy rather than numpy.insert since this is
                # faster.
                new_points = numpy.empty((len(points), 3), numpy.float32)
                if polygon.point_type == 0:  # Point2D
                    new_points[:, 0] = points[:, 0]
                    new_points[:,
                               1] = layer.height / 1000  # layer height value is in backend representation
                    new_points[:, 2] = -points[:, 1]
                else:  # Point3D
                    new_points[:, 0] = points[:, 0]
                    new_points[:, 1] = points[:, 2]
                    new_points[:, 2] = -points[:, 1]

                this_poly = LayerPolygon.LayerPolygon(extruder, line_types,
                                                      new_points, line_widths,
                                                      line_thicknesses,
                                                      line_feedrates)
                this_poly.buildCache()

                this_layer.polygons.append(this_poly)

                Job.yieldThread()
            Job.yieldThread()
            current_layer += 1
            progress = (current_layer / layer_count) * 99
            # TODO: Rebuild the layer data mesh once the layer has been processed.
            # This needs some work in LayerData so we can add the new layers instead of recreating the entire mesh.

            if self._abort_requested:
                if self._progress_message:
                    self._progress_message.hide()
                return
            if self._progress_message:
                self._progress_message.setProgress(progress)

        # We are done processing all the layers we got from the engine, now create a mesh out of the data

        # Find out colors per extruder
        global_container_stack = Application.getInstance(
        ).getGlobalContainerStack()
        manager = ExtruderManager.getInstance()
        extruders = manager.getActiveExtruderStacks()
        if extruders:
            material_color_map = numpy.zeros((len(extruders), 4),
                                             dtype=numpy.float32)
            for extruder in extruders:
                position = int(
                    extruder.getMetaDataEntry("position", default="0"))
                try:
                    default_color = ExtrudersModel.defaultColors[position]
                except IndexError:
                    default_color = "#e0e000"
                color_code = extruder.material.getMetaDataEntry(
                    "color_code", default=default_color)
                color = colorCodeToRGBA(color_code)
                material_color_map[position, :] = color
        else:
            # Single extruder via global stack.
            material_color_map = numpy.zeros((1, 4), dtype=numpy.float32)
            color_code = global_container_stack.material.getMetaDataEntry(
                "color_code", default="#e0e000")
            color = colorCodeToRGBA(color_code)
            material_color_map[0, :] = color

        # We have to scale the colors for compatibility mode
        if OpenGLContext.isLegacyOpenGL() or bool(
                Application.getInstance().getPreferences().getValue(
                    "view/force_layer_view_compatibility_mode")):
            line_type_brightness = 0.5  # for compatibility mode
        else:
            line_type_brightness = 1.0
        layer_mesh = layer_data.build(material_color_map, line_type_brightness)

        if self._abort_requested:
            if self._progress_message:
                self._progress_message.hide()
            return

        # Add LayerDataDecorator to scene node to indicate that the node has layer data
        decorator = LayerDataDecorator.LayerDataDecorator()
        decorator.setLayerData(layer_mesh)
        new_node.addDecorator(decorator)

        new_node.setMeshData(mesh)
        # Set build volume as parent, the build volume can move as a result of raft settings.
        # It makes sense to set the build volume as parent: the print is actually printed on it.
        new_node_parent = Application.getInstance().getBuildVolume()
        new_node.setParent(
            new_node_parent)  # Note: After this we can no longer abort!

        settings = Application.getInstance().getGlobalContainerStack()
        if not settings.getProperty("machine_center_is_zero", "value"):
            new_node.setPosition(
                Vector(-settings.getProperty("machine_width", "value") / 2,
                       0.0,
                       settings.getProperty("machine_depth", "value") / 2))

        if self._progress_message:
            self._progress_message.setProgress(100)

        if self._progress_message:
            self._progress_message.hide()

        # Clear the unparsed layers. This saves us a bunch of memory if the Job does not get destroyed.
        self._layers = None

        Logger.log("d", "Processing layers took %s seconds",
                   time() - start_time)
示例#29
0
    def _convertSavitarNodeToUMNode(
            self,
            savitar_node: Savitar.SceneNode,
            file_name: str = "") -> Optional[SceneNode]:
        node_name = savitar_node.getName()
        node_id = savitar_node.getId()
        if node_name == "":
            if file_name != "":
                node_name = os.path.basename(file_name)
            else:
                node_name = "Object {}".format(node_id)

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

        um_node = CuraSceneNode()  # This adds a SettingOverrideDecorator
        um_node.addDecorator(BuildPlateDecorator(active_build_plate))
        um_node.setName(node_name)
        um_node.setId(node_id)
        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)
        if file_name:
            # The filename is used to give the user the option to reload the file if it is changed on disk
            # It is only set for the root node of the 3mf file
            mesh_builder.setFileName(file_name)
        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 = CuraApplication.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 = ContainerTree.getInstance().machines[
                    global_container_stack.definition.getId(
                    )].quality_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
示例#30
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 = CuraSceneNode() # 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
示例#31
0
    def _generateSceneNode(self, file_name, xz_size, peak_height, base_height,
                           blur_iterations, max_size, lighter_is_higher,
                           use_transparency_model, transmittance_1mm):
        scene_node = SceneNode()

        mesh = MeshBuilder()

        img = QImage(file_name)

        if img.isNull():
            Logger.log("e", "Image is corrupt.")
            return None

        width = max(img.width(), 2)
        height = max(img.height(), 2)
        aspect = height / width

        if img.width() < 2 or img.height() < 2:
            img = img.scaled(width, height, Qt.IgnoreAspectRatio)

        base_height = max(base_height, 0)
        peak_height = max(peak_height, -base_height)

        xz_size = max(xz_size, 1)
        scale_vector = Vector(xz_size, peak_height, xz_size)

        if width > height:
            scale_vector = scale_vector.set(z=scale_vector.z * aspect)
        elif height > width:
            scale_vector = scale_vector.set(x=scale_vector.x / aspect)

        if width > max_size or height > max_size:
            scale_factor = max_size / width
            if height > width:
                scale_factor = max_size / height

            width = int(max(round(width * scale_factor), 2))
            height = int(max(round(height * scale_factor), 2))
            img = img.scaled(width, height, Qt.IgnoreAspectRatio)

        width_minus_one = width - 1
        height_minus_one = height - 1

        Job.yieldThread()

        texel_width = 1.0 / (width_minus_one) * scale_vector.x
        texel_height = 1.0 / (height_minus_one) * scale_vector.z

        height_data = numpy.zeros((height, width), dtype=numpy.float32)

        for x in range(0, width):
            for y in range(0, height):
                qrgb = img.pixel(x, y)
                if use_transparency_model:
                    height_data[y, x] = (
                        0.299 * math.pow(qRed(qrgb) / 255.0, 2.2) +
                        0.587 * math.pow(qGreen(qrgb) / 255.0, 2.2) +
                        0.114 * math.pow(qBlue(qrgb) / 255.0, 2.2))
                else:
                    height_data[y, x] = (
                        0.212655 * qRed(qrgb) + 0.715158 * qGreen(qrgb) +
                        0.072187 * qBlue(qrgb)
                    ) / 255  # fast computation ignoring gamma and degamma

        Job.yieldThread()

        if lighter_is_higher == use_transparency_model:
            height_data = 1 - height_data

        for _ in range(0, blur_iterations):
            copy = numpy.pad(height_data, ((1, 1), (1, 1)), mode="edge")

            height_data += copy[1:-1, 2:]
            height_data += copy[1:-1, :-2]
            height_data += copy[2:, 1:-1]
            height_data += copy[:-2, 1:-1]

            height_data += copy[2:, 2:]
            height_data += copy[:-2, 2:]
            height_data += copy[2:, :-2]
            height_data += copy[:-2, :-2]

            height_data /= 9

            Job.yieldThread()

        if use_transparency_model:
            divisor = 1.0 / math.log(
                transmittance_1mm / 100.0
            )  # log-base doesn't matter here. Precompute this value for faster computation of each pixel.
            min_luminance = (transmittance_1mm / 100.0)**(peak_height -
                                                          base_height)
            for (y, x) in numpy.ndindex(height_data.shape):
                mapped_luminance = min_luminance + (
                    1.0 - min_luminance) * height_data[y, x]
                height_data[y, x] = base_height + divisor * math.log(
                    mapped_luminance
                )  # use same base as a couple lines above this
        else:
            height_data *= scale_vector.y
            height_data += base_height

        if img.hasAlphaChannel():
            for x in range(0, width):
                for y in range(0, height):
                    height_data[y, x] *= qAlpha(img.pixel(x, y)) / 255.0

        heightmap_face_count = 2 * height_minus_one * width_minus_one
        total_face_count = heightmap_face_count + (width_minus_one * 2) * (
            height_minus_one * 2) + 2

        mesh.reserveFaceCount(total_face_count)

        # initialize to texel space vertex offsets.
        # 6 is for 6 vertices for each texel quad.
        heightmap_vertices = numpy.zeros(
            (width_minus_one * height_minus_one, 6, 3), dtype=numpy.float32)
        heightmap_vertices = heightmap_vertices + numpy.array(
            [[[0, base_height, 0], [0, base_height, texel_height],
              [texel_width, base_height, texel_height],
              [texel_width, base_height, texel_height],
              [texel_width, base_height, 0], [0, base_height, 0]]],
            dtype=numpy.float32)

        offsetsz, offsetsx = numpy.mgrid[0:height_minus_one, 0:width - 1]
        offsetsx = numpy.array(offsetsx, numpy.float32).reshape(
            -1, 1) * texel_width
        offsetsz = numpy.array(offsetsz, numpy.float32).reshape(
            -1, 1) * texel_height

        # offsets for each texel quad
        heightmap_vertex_offsets = numpy.concatenate([
            offsetsx,
            numpy.zeros((offsetsx.shape[0], offsetsx.shape[1]),
                        dtype=numpy.float32), offsetsz
        ], 1)
        heightmap_vertices += heightmap_vertex_offsets.repeat(6, 0).reshape(
            -1, 6, 3)

        # apply height data to y values
        heightmap_vertices[:, 0,
                           1] = heightmap_vertices[:, 5,
                                                   1] = height_data[:-1, :
                                                                    -1].reshape(
                                                                        -1)
        heightmap_vertices[:, 1, 1] = height_data[1:, :-1].reshape(-1)
        heightmap_vertices[:, 2,
                           1] = heightmap_vertices[:, 3, 1] = height_data[
                               1:, 1:].reshape(-1)
        heightmap_vertices[:, 4, 1] = height_data[:-1, 1:].reshape(-1)

        heightmap_indices = numpy.array(numpy.mgrid[0:heightmap_face_count *
                                                    3],
                                        dtype=numpy.int32).reshape(-1, 3)

        mesh._vertices[0:(heightmap_vertices.size //
                          3), :] = heightmap_vertices.reshape(-1, 3)
        mesh._indices[0:(heightmap_indices.size // 3), :] = heightmap_indices

        mesh._vertex_count = heightmap_vertices.size // 3
        mesh._face_count = heightmap_indices.size // 3

        geo_width = width_minus_one * texel_width
        geo_height = height_minus_one * texel_height

        # bottom
        mesh.addFaceByPoints(0, 0, 0, 0, 0, geo_height, geo_width, 0,
                             geo_height)
        mesh.addFaceByPoints(geo_width, 0, geo_height, geo_width, 0, 0, 0, 0,
                             0)

        # north and south walls
        for n in range(0, width_minus_one):
            x = n * texel_width
            nx = (n + 1) * texel_width

            hn0 = height_data[0, n]
            hn1 = height_data[0, n + 1]

            hs0 = height_data[height_minus_one, n]
            hs1 = height_data[height_minus_one, n + 1]

            mesh.addFaceByPoints(x, 0, 0, nx, 0, 0, nx, hn1, 0)
            mesh.addFaceByPoints(nx, hn1, 0, x, hn0, 0, x, 0, 0)

            mesh.addFaceByPoints(x, 0, geo_height, nx, 0, geo_height, nx, hs1,
                                 geo_height)
            mesh.addFaceByPoints(nx, hs1, geo_height, x, hs0, geo_height, x, 0,
                                 geo_height)

        # west and east walls
        for n in range(0, height_minus_one):
            y = n * texel_height
            ny = (n + 1) * texel_height

            hw0 = height_data[n, 0]
            hw1 = height_data[n + 1, 0]

            he0 = height_data[n, width_minus_one]
            he1 = height_data[n + 1, width_minus_one]

            mesh.addFaceByPoints(0, 0, y, 0, 0, ny, 0, hw1, ny)
            mesh.addFaceByPoints(0, hw1, ny, 0, hw0, y, 0, 0, y)

            mesh.addFaceByPoints(geo_width, 0, y, geo_width, 0, ny, geo_width,
                                 he1, ny)
            mesh.addFaceByPoints(geo_width, he1, ny, geo_width, he0, y,
                                 geo_width, 0, y)

        mesh.calculateNormals(fast=True)

        scene_node.setMeshData(mesh.build())

        return scene_node