예제 #1
0
 def test_preMultiply(self):
     temp_matrix = Matrix()
     temp_matrix.setByTranslation(Vector(10,10,10))
     temp_matrix2 = Matrix()
     temp_matrix2.setByScaleFactor(0.5)
     temp_matrix.preMultiply(temp_matrix2)
     numpy.testing.assert_array_almost_equal(temp_matrix.getData(), numpy.array([[0.5,0,0,5],[0,0.5,0,5],[0,0,0.5,5],[0,0,0,1]]))
예제 #2
0
    def resetMeshOrigin(self) -> None:
        nodes_list = self._getSelectedNodes()
        if not nodes_list:
            return

        op = GroupedOperation()
        for node in nodes_list:
            mesh_data = node.getMeshData()
            if not mesh_data:
                continue

            extents = mesh_data.getExtents()
            center = Vector(extents.center.x, extents.center.y, extents.center.z)

            translation = Matrix()
            translation.setByTranslation(-center)
            transformed_mesh_data = mesh_data.getTransformed(translation).set(zero_position=Vector())

            new_transformation = Matrix(node.getLocalTransformation().getData())  # Matrix.copy() is not available in Cura 3.5-4.0
            new_transformation.translate(center)

            op.addOperation(SetMeshDataAndNameOperation(node, transformed_mesh_data, node.getName()))
            op.addOperation(SetTransformMatrixOperation(node, new_transformation))

        op.push()
예제 #3
0
 def test_setByTranslation(self):
     matrix = Matrix()
     matrix.setByTranslation(Vector(0, 1, 0))
     numpy.testing.assert_array_almost_equal(
         matrix.getData(),
         numpy.array([[1, 0, 0, 0], [0, 1, 0, 1], [0, 0, 1, 0],
                      [0, 0, 0, 1]]))
예제 #4
0
 def setCenterPosition(self, center: Vector):
     if self._mesh_data:
         m = Matrix()
         m.setByTranslation(-center)
         self._mesh_data = self._mesh_data.getTransformed(m).set(center_position=center)
     for child in self._children:
         child.setCenterPosition(center)
예제 #5
0
 def test_preMultiply(self):
     temp_matrix = Matrix()
     temp_matrix.setByTranslation(Vector(10,10,10))
     temp_matrix2 = Matrix()
     temp_matrix2.setByScaleFactor(0.5)
     temp_matrix.preMultiply(temp_matrix2)
     numpy.testing.assert_array_almost_equal(temp_matrix.getData(), numpy.array([[0.5,0,0,5],[0,0.5,0,5],[0,0,0.5,5],[0,0,0,1]]))
예제 #6
0
 def setCenterPosition(self, center: Vector) -> None:
     if self._mesh_data:
         m = Matrix()
         m.setByTranslation(-center)
         self._mesh_data = self._mesh_data.getTransformed(m).set(center_position=center)
     for child in self._children:
         child.setCenterPosition(center)
예제 #7
0
    def read(self, file_name):
        result = []
        # The base object of 3mf is a zipped archive.
        try:
            archive = zipfile.ZipFile(file_name, "r")
            self._base_name = os.path.basename(file_name)
            parser = Savitar.ThreeMFParser()
            scene_3mf = parser.parse(archive.open("3D/3dmodel.model").read())
            self._unit = scene_3mf.getUnit()
            for node in scene_3mf.getSceneNodes():
                um_node = self._convertSavitarNodeToUMNode(node)
                if um_node is None:
                    continue
                # compensate for original center position, if object(s) is/are not around its zero position

                transform_matrix = Matrix()
                mesh_data = um_node.getMeshData()
                if mesh_data is not None:
                    extents = mesh_data.getExtents()
                    center_vector = Vector(extents.center.x, extents.center.y, extents.center.z)
                    transform_matrix.setByTranslation(center_vector)
                transform_matrix.multiply(um_node.getLocalTransformation())
                um_node.setTransformation(transform_matrix)

                global_container_stack = Application.getInstance().getGlobalContainerStack()

                # Create a transformation Matrix to convert from 3mf worldspace into ours.
                # First step: flip the y and z axis.
                transformation_matrix = Matrix()
                transformation_matrix._data[1, 1] = 0
                transformation_matrix._data[1, 2] = 1
                transformation_matrix._data[2, 1] = -1
                transformation_matrix._data[2, 2] = 0

                # Second step: 3MF defines the left corner of the machine as center, whereas cura uses the center of the
                # build volume.
                if global_container_stack:
                    translation_vector = Vector(x=-global_container_stack.getProperty("machine_width", "value") / 2,
                                                y=-global_container_stack.getProperty("machine_depth", "value") / 2,
                                                z=0)
                    translation_matrix = Matrix()
                    translation_matrix.setByTranslation(translation_vector)
                    transformation_matrix.multiply(translation_matrix)

                # Third step: 3MF also defines a unit, wheras Cura always assumes mm.
                scale_matrix = Matrix()
                scale_matrix.setByScaleVector(self._getScaleFromUnit(self._unit))
                transformation_matrix.multiply(scale_matrix)

                # Pre multiply the transformation with the loaded transformation, so the data is handled correctly.
                um_node.setTransformation(um_node.getLocalTransformation().preMultiply(transformation_matrix))

                result.append(um_node)

        except Exception:
            Logger.logException("e", "An exception occurred in 3mf reader.")
            return []

        return result
예제 #8
0
 def test_transposed(self):
     temp_matrix = Matrix()
     temp_matrix.setByTranslation(Vector(10, 10, 10))
     temp_matrix = temp_matrix.getTransposed()
     numpy.testing.assert_array_almost_equal(
         temp_matrix.getData(),
         numpy.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0],
                      [10, 10, 10, 1]]))
예제 #9
0
파일: SceneNode.py 프로젝트: jf---/Uranium
 def setCenterPosition(self, center):
     if self._mesh_data:
         m = Matrix()
         m.setByTranslation(-center)
         self._mesh_data = self._mesh_data.getTransformed(m)
         self._mesh_data.setCenterPosition(center)
     for child in self._children:
         child.setCenterPosition(center)
예제 #10
0
class TestMatrix(unittest.TestCase):
    def setUp(self):
        self._matrix = Matrix()
        # Called before the first testfunction is executed
        pass

    def tearDown(self):
        # Called after the last testfunction was executed
        pass

    def test_setByQuaternion(self):
        pass
    
    def test_multiply(self):
        temp_matrix = Matrix()
        temp_matrix.setByTranslation(Vector(10,10,10))
        temp_matrix2 = Matrix()
        temp_matrix2.setByScaleFactor(0.5)
        temp_matrix.multiply(temp_matrix2)
        numpy.testing.assert_array_almost_equal(temp_matrix.getData(), numpy.array([[0.5,0,0,10],[0,0.5,0,10],[0,0,0.5,10],[0,0,0,1]]))
    
    def test_preMultiply(self):
        temp_matrix = Matrix()
        temp_matrix.setByTranslation(Vector(10,10,10))
        temp_matrix2 = Matrix()
        temp_matrix2.setByScaleFactor(0.5)
        temp_matrix.preMultiply(temp_matrix2)
        numpy.testing.assert_array_almost_equal(temp_matrix.getData(), numpy.array([[0.5,0,0,5],[0,0.5,0,5],[0,0,0.5,5],[0,0,0,1]]))

    def test_setByScaleFactor(self):
        self._matrix.setByScaleFactor(0.5)
        numpy.testing.assert_array_almost_equal(self._matrix.getData(), numpy.array([[0.5,0,0,0],[0,0.5,0,0],[0,0,0.5,0],[0,0,0,1]]))

    def test_setByRotation(self):
        pass

    def test_setByTranslation(self):
        self._matrix.setByTranslation(Vector(0,1,0))
        numpy.testing.assert_array_almost_equal(self._matrix.getData(), numpy.array([[1,0,0,0],[0,1,0,1],[0,0,1,0],[0,0,0,1]]))

    def test_setToIdentity(self):
        pass

    def test_getData(self):
        pass

    def test_transposed(self):
        temp_matrix = Matrix()  
        temp_matrix.setByTranslation(Vector(10,10,10))
        temp_matrix = temp_matrix.getTransposed()
        numpy.testing.assert_array_almost_equal(temp_matrix.getData(), numpy.array([[1,0,0,0],[0,1,0,0],[0,0,1,0],[10,10,10,1]]))

    def test_dot(self):
        pass
예제 #11
0
 def test_preMultiplyCopy(self):
     temp_matrix = Matrix()
     temp_matrix.setByTranslation(Vector(10, 10, 10))
     temp_matrix2 = Matrix()
     temp_matrix2.setByScaleFactor(0.5)
     result = temp_matrix.preMultiply(temp_matrix2, copy=True)
     assert result != temp_matrix
     numpy.testing.assert_array_almost_equal(
         result.getData(),
         numpy.array([[0.5, 0, 0, 5], [0, 0.5, 0, 5], [0, 0, 0.5, 5],
                      [0, 0, 0, 1]]))
예제 #12
0
class TestMatrix(unittest.TestCase):
    def setUp(self):
        self._matrix = Matrix()
        # Called before the first testfunction is executed
        pass

    def tearDown(self):
        # Called after the last testfunction was executed
        pass

    def test_setByQuaternion(self):
        pass
    
    def test_multiply(self):
        temp_matrix = Matrix()
        temp_matrix.setByTranslation(Vector(10,10,10))
        temp_matrix2 = Matrix()
        temp_matrix2.setByScaleFactor(0.5)
        temp_matrix.multiply(temp_matrix2)
        numpy.testing.assert_array_almost_equal(temp_matrix.getData(), numpy.array([[0.5,0,0,10],[0,0.5,0,10],[0,0,0.5,10],[0,0,0,1]]))
    
    def test_preMultiply(self):
        temp_matrix = Matrix()
        temp_matrix.setByTranslation(Vector(10,10,10))
        temp_matrix2 = Matrix()
        temp_matrix2.setByScaleFactor(0.5)
        temp_matrix.preMultiply(temp_matrix2)
        numpy.testing.assert_array_almost_equal(temp_matrix.getData(), numpy.array([[0.5,0,0,5],[0,0.5,0,5],[0,0,0.5,5],[0,0,0,1]]))

    def test_setByScaleFactor(self):
        self._matrix.setByScaleFactor(0.5)
        numpy.testing.assert_array_almost_equal(self._matrix.getData(), numpy.array([[0.5,0,0,0],[0,0.5,0,0],[0,0,0.5,0],[0,0,0,1]]))

    def test_setByRotation(self):
        pass

    def test_setByTranslation(self):
        self._matrix.setByTranslation(Vector(0,1,0))
        numpy.testing.assert_array_almost_equal(self._matrix.getData(), numpy.array([[1,0,0,0],[0,1,0,1],[0,0,1,0],[0,0,0,1]]))

    def test_setToIdentity(self):
        pass

    def test_getData(self):
        pass

    def test_transposed(self):
        temp_matrix = Matrix()  
        temp_matrix.setByTranslation(Vector(10,10,10))
        temp_matrix = temp_matrix.getTransposed()
        numpy.testing.assert_array_almost_equal(temp_matrix.getData(), numpy.array([[1,0,0,0],[0,1,0,0],[0,0,1,0],[10,10,10,1]]))

    def test_dot(self):
        pass
예제 #13
0
 def setUp(self):
     # Called before the first testfunction is executed
     self._scene = Scene()
     self._scene_object = SceneNode()
     self._scene_object2 = SceneNode()
     self._scene_object.addChild(self._scene_object2)
     self._scene.getRoot().addChild(self._scene_object)
     temp_matrix = Matrix()
     temp_matrix.setByTranslation(Vector(10, 10, 10))
     self._scene_object2.setLocalTransformation(deepcopy(temp_matrix))
     temp_matrix.setByScaleFactor(0.5)
     self._scene_object.setLocalTransformation(temp_matrix)
예제 #14
0
def test_transformMeshData():
    transformation_matrix = Matrix()
    transformation_matrix.setByTranslation(Vector(30, 20, 10))

    vertices = numpy.zeros((1, 3), dtype=numpy.float32)
    mesh_data = MeshData(vertices)

    transformed_mesh = mesh_data.getTransformed(transformation_matrix)

    assert transformed_mesh.getVertex(0)[0] == 30.
    assert transformed_mesh.getVertex(0)[1] == 20.
    assert transformed_mesh.getVertex(0)[2] == 10.
예제 #15
0
 def setUp(self):
     # Called before the first testfunction is executed
     self._scene = Scene()
     self._scene_object = SceneNode()
     self._scene_object2 = SceneNode()
     self._scene_object.addChild(self._scene_object2)
     self._scene.getRoot().addChild(self._scene_object)
     temp_matrix = Matrix()
     temp_matrix.setByTranslation(Vector(10,10,10))
     self._scene_object2.setLocalTransformation(deepcopy(temp_matrix))
     temp_matrix.setByScaleFactor(0.5)
     self._scene_object.setLocalTransformation(temp_matrix)
예제 #16
0
def test_transformMeshData():
    transformation_matrix = Matrix()
    transformation_matrix.setByTranslation(Vector(30, 20, 10))

    vertices = numpy.zeros((1, 3), dtype=numpy.float32)
    mesh_data = MeshData(vertices)

    transformed_mesh = mesh_data.getTransformed(transformation_matrix)

    assert transformed_mesh.getVertex(0)[0] == 30.
    assert transformed_mesh.getVertex(0)[1] == 20.
    assert transformed_mesh.getVertex(0)[2] == 10.
예제 #17
0
    def setCenterPosition(self, center: Vector) -> None:
        """Set the center position of this node.

        This is used to modify it's mesh data (and it's children) in such a way that they are centered.
        In most cases this means that we use the center of mass as center (which most objects don't use)
        """

        if self._mesh_data:
            m = Matrix()
            m.setByTranslation(-center)
            self._mesh_data = self._mesh_data.getTransformed(m).set(center_position=center)
        for child in self._children:
            child.setCenterPosition(center)
예제 #18
0
 def translate(self, translation, transform_space = TransformSpace.Local):
     if not self._enabled:
         return
     translation_matrix = Matrix()
     translation_matrix.setByTranslation(translation)
     if transform_space == SceneNode.TransformSpace.Local:
         self._transformation.multiply(translation_matrix)
     elif transform_space == SceneNode.TransformSpace.Parent:
         self._transformation.preMultiply(translation_matrix)
     elif transform_space == SceneNode.TransformSpace.World:
         self._transformation.multiply(self._world_transformation.getInverse())
         self._transformation.multiply(translation_matrix)
         self._transformation.multiply(self._world_transformation)
     self._transformChanged()
예제 #19
0
 def translate(self, translation, transform_space = TransformSpace.Local):
     if not self._enabled:
         return
     translation_matrix = Matrix()
     translation_matrix.setByTranslation(translation)
     if transform_space == SceneNode.TransformSpace.Local:
         self._transformation.multiply(translation_matrix)
     elif transform_space == SceneNode.TransformSpace.Parent:
         self._transformation.preMultiply(translation_matrix)
     elif transform_space == SceneNode.TransformSpace.World:
         self._transformation.multiply(self._world_transformation.getInverse())
         self._transformation.multiply(translation_matrix)
         self._transformation.multiply(self._world_transformation)
     self._transformChanged()
예제 #20
0
def test_getExtentsTransposed():
    # Create a cube mesh at position 0,0,0
    builder = MeshBuilder()
    builder.addCube(20, 20, 20)
    mesh_data = builder.build()

    transformation_matrix = Matrix()
    transformation_matrix.setByTranslation(Vector(10, 10, 10))

    extents = mesh_data.getExtents(transformation_matrix)
    assert extents.width == 20
    assert extents.height == 20
    assert extents.depth == 20

    assert extents.maximum == Vector(20, 20, 20)
    assert extents.minimum == Vector(0, 0, 0)
예제 #21
0
def test_getExtentsTransposed():
    # Create a cube mesh at position 0,0,0
    builder = MeshBuilder()
    builder.addCube(20, 20, 20)
    mesh_data = builder.build()

    transformation_matrix = Matrix()
    transformation_matrix.setByTranslation(Vector(10, 10, 10))

    extents = mesh_data.getExtents(transformation_matrix)
    assert extents.width == 20
    assert extents.height == 20
    assert extents.depth == 20

    assert extents.maximum == Vector(20, 20, 20)
    assert extents.minimum == Vector(0, 0, 0)
예제 #22
0
    def read(self, file_name, storage_device, **kwargs):
        try:
            for reader in self._mesh_readers:
                result = reader.read(file_name, storage_device)
                if(result is not None):
                    if kwargs.get("center", True):
                        # Center the mesh
                        extents = result.getExtents()
                        m = Matrix()
                        m.setByTranslation(-extents.center)
                        result = result.getTransformed(m)

                    result.setFileName(file_name)
                    return result

        except OSError as e:
            Logger.log("e", str(e))

        Logger.log("w", "Unable to read file %s", file_name)
        return None #unable to read
예제 #23
0
    def translate(self, translation: Vector, transform_space: int = TransformSpace.Local) -> None:
        """Translate the scene object (and thus its children) by given amount.

        :param translation: :type{Vector} The amount to translate by.
        :param transform_space: The space relative to which to translate. Can be any one of the constants in SceneNode::TransformSpace.
        """

        if not self._enabled:
            return
        translation_matrix = Matrix()
        translation_matrix.setByTranslation(translation)
        if transform_space == SceneNode.TransformSpace.Local:
            self._transformation.multiply(translation_matrix)
        elif transform_space == SceneNode.TransformSpace.Parent:
            self._transformation.preMultiply(translation_matrix)
        elif transform_space == SceneNode.TransformSpace.World:
            world_transformation = self._world_transformation.copy()
            self._transformation.multiply(self._world_transformation.getInverse())
            self._transformation.multiply(translation_matrix)
            self._transformation.multiply(world_transformation)
        self._transformChanged()
예제 #24
0
    def render(self, renderer):
        if not self._shader:
            # We now misuse the platform shader, as it actually supports textures
            self._shader = OpenGL.getInstance().createShaderProgram(
                Resources.getPath(Resources.Shaders, "platform.shader"))
            # Set the opacity to 0, so that the template is in full control.
            self._shader.setUniformValue("u_opacity", 0)
            self._texture = OpenGL.getInstance().createTexture()
            document = QTextDocument()
            document.setHtml(
                self._getFilledTemplate(self._display_data, self._template))

            texture_image = QImage(self._texture_width, self._texture_height,
                                   QImage.Format_ARGB32)
            texture_image.fill(Qt.transparent)
            painter = QPainter(texture_image)
            document.drawContents(
                painter,
                QRectF(0., 0., self._texture_width, self._texture_height))
            painter.end()
            self._texture.setImage(texture_image)
            self._shader.setTexture(0, self._texture)

        node_position = self._target_node.getWorldPosition()
        position_matrix = Matrix()
        position_matrix.setByTranslation(node_position)
        camera_orientation = self._scene.getActiveCamera().getOrientation(
        ).toMatrix()

        renderer.queueNode(self._scene.getRoot(),
                           shader=self._shader,
                           transparent=True,
                           mesh=self._billboard_mesh.getTransformed(
                               position_matrix.multiply(camera_orientation)),
                           sort=self._target_node.getDepth())

        return True  # This node does it's own rendering.
예제 #25
0
    def write(self, stream, nodes, mode = MeshWriter.OutputMode.BinaryMode):
        self._archive = None # Reset archive
        archive = zipfile.ZipFile(stream, "w", compression = zipfile.ZIP_DEFLATED)
        try:
            model_file = zipfile.ZipInfo("3D/3dmodel.model")
            # Because zipfile is stupid and ignores archive-level compression settings when writing with ZipInfo.
            model_file.compress_type = zipfile.ZIP_DEFLATED

            # Create content types file
            content_types_file = zipfile.ZipInfo("[Content_Types].xml")
            content_types_file.compress_type = zipfile.ZIP_DEFLATED
            content_types = ET.Element("Types", xmlns = self._namespaces["content-types"])
            rels_type = ET.SubElement(content_types, "Default", Extension = "rels", ContentType = "application/vnd.openxmlformats-package.relationships+xml")
            model_type = ET.SubElement(content_types, "Default", Extension = "model", ContentType = "application/vnd.ms-package.3dmanufacturing-3dmodel+xml")

            # Create _rels/.rels file
            relations_file = zipfile.ZipInfo("_rels/.rels")
            relations_file.compress_type = zipfile.ZIP_DEFLATED
            relations_element = ET.Element("Relationships", xmlns = self._namespaces["relationships"])
            model_relation_element = ET.SubElement(relations_element, "Relationship", Target = "/3D/3dmodel.model", Id = "rel0", Type = "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel")

            model = ET.Element("model", unit = "millimeter", xmlns = self._namespaces["3mf"])
            model.set("xmlns:cura", self._namespaces["cura"])

            # Add the version of Cura this was created with. Since there is no "version" or similar metadata name we need
            # to prefix it with the cura namespace, as specified by the 3MF specification.
            version_metadata = ET.SubElement(model, "metadata", name = "cura:version")
            version_metadata.text = Application.getInstance().getVersion()

            resources = ET.SubElement(model, "resources")
            build = ET.SubElement(model, "build")

            added_nodes = []
            index = 0  # Ensure index always exists (even if there are no nodes to write)
            # Write all nodes with meshData to the file as objects inside the resource tag
            for index, n in enumerate(MeshWriter._meshNodes(nodes)):
                added_nodes.append(n)  # Save the nodes that have mesh data
                object = ET.SubElement(resources, "object", id = str(index+1), type = "model")
                mesh = ET.SubElement(object, "mesh")

                mesh_data = n.getMeshData()
                vertices = ET.SubElement(mesh, "vertices")
                verts = mesh_data.getVertices()

                if verts is None:
                    Logger.log("d", "3mf writer can't write nodes without mesh data. Skipping this node.")
                    continue  # No mesh data, nothing to do.
                if mesh_data.hasIndices():
                    for face in mesh_data.getIndices():
                        v1 = verts[face[0]]
                        v2 = verts[face[1]]
                        v3 = verts[face[2]]
                        xml_vertex1 = ET.SubElement(vertices, "vertex", x = str(v1[0]), y = str(v1[1]), z = str(v1[2]))
                        xml_vertex2 = ET.SubElement(vertices, "vertex", x = str(v2[0]), y = str(v2[1]), z = str(v2[2]))
                        xml_vertex3 = ET.SubElement(vertices, "vertex", x = str(v3[0]), y = str(v3[1]), z = str(v3[2]))

                    triangles = ET.SubElement(mesh, "triangles")
                    for face in mesh_data.getIndices():
                        triangle = ET.SubElement(triangles, "triangle", v1 = str(face[0]) , v2 = str(face[1]), v3 = str(face[2]))
                else:
                    triangles = ET.SubElement(mesh, "triangles")
                    for idx, vert in enumerate(verts):
                        xml_vertex = ET.SubElement(vertices, "vertex", x = str(vert[0]), y = str(vert[1]), z = str(vert[2]))

                        # If we have no faces defined, assume that every three subsequent vertices form a face.
                        if idx % 3 == 0:
                            triangle = ET.SubElement(triangles, "triangle", v1 = str(idx), v2 = str(idx + 1), v3 = str(idx + 2))

                # Handle per object settings
                stack = n.callDecoration("getStack")
                if stack is not None:
                    changed_setting_keys = set(stack.getTop().getAllKeys())

                    # Ensure that we save the extruder used for this object.
                    if stack.getProperty("machine_extruder_count", "value") > 1:
                        changed_setting_keys.add("extruder_nr")

                    settings_xml = ET.SubElement(object, "settings", xmlns=self._namespaces["cura"])

                    # Get values for all changed settings & save them.
                    for key in changed_setting_keys:
                        setting_xml = ET.SubElement(settings_xml, "setting", key = key)
                        setting_xml.text = str(stack.getProperty(key, "value"))

            # Add one to the index as we haven't incremented the last iteration.
            index += 1
            nodes_to_add = set()

            for node in added_nodes:
                # Check the parents of the nodes with mesh_data and ensure that they are also added.
                parent_node = node.getParent()
                while parent_node is not None:
                    if parent_node.callDecoration("isGroup"):
                        nodes_to_add.add(parent_node)
                        parent_node = parent_node.getParent()
                    else:
                        parent_node = None

            # Sort all the nodes by depth (so nodes with the highest depth are done first)
            sorted_nodes_to_add = sorted(nodes_to_add, key=lambda node: node.getDepth(), reverse = True)

            # We have already saved the nodes with mesh data, but now we also want to save nodes required for the scene
            for node in sorted_nodes_to_add:
                object = ET.SubElement(resources, "object", id=str(index + 1), type="model")
                components = ET.SubElement(object, "components")
                for child in node.getChildren():
                    if child in added_nodes:
                        component = ET.SubElement(components, "component", objectid = str(added_nodes.index(child) + 1), transform = self._convertMatrixToString(child.getLocalTransformation()))
                index += 1
                added_nodes.append(node)

            # Create a transformation Matrix to convert from our worldspace into 3MF.
            # First step: flip the y and z axis.
            transformation_matrix = Matrix()
            transformation_matrix._data[1, 1] = 0
            transformation_matrix._data[1, 2] = -1
            transformation_matrix._data[2, 1] = 1
            transformation_matrix._data[2, 2] = 0

            global_container_stack = Application.getInstance().getGlobalContainerStack()
            # Second step: 3MF defines the left corner of the machine as center, whereas cura uses the center of the
            # build volume.
            if global_container_stack:
                translation_vector = Vector(x=global_container_stack.getProperty("machine_width", "value") / 2,
                                            y=global_container_stack.getProperty("machine_depth", "value") / 2,
                                            z=0)
                translation_matrix = Matrix()
                translation_matrix.setByTranslation(translation_vector)
                transformation_matrix.preMultiply(translation_matrix)

            # Find out what the final build items are and add them.
            for node in added_nodes:
                if node.getParent().callDecoration("isGroup") is None:
                    node_matrix = node.getLocalTransformation()

                    ET.SubElement(build, "item", objectid = str(added_nodes.index(node) + 1), transform = self._convertMatrixToString(node_matrix.preMultiply(transformation_matrix)))

            archive.writestr(model_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(model))
            archive.writestr(content_types_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(content_types))
            archive.writestr(relations_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(relations_element))
        except Exception as e:
            Logger.logException("e", "Error writing zip file")
            return False
        finally:
            if not self._store_archive:
                archive.close()
            else:
                self._archive = archive

        return True
예제 #26
0
파일: ThreeMFReader.py 프로젝트: mifga/Cura
    def read(self, file_name):
        result = []
        # The base object of 3mf is a zipped archive.
        archive = zipfile.ZipFile(file_name, "r")
        self._base_name = os.path.basename(file_name)
        try:
            self._root = ET.parse(archive.open("3D/3dmodel.model"))
            self._unit = self._root.getroot().get("unit")

            build_items = self._root.findall("./3mf:build/3mf:item", self._namespaces)

            for build_item in build_items:
                id = build_item.get("objectid")
                object = self._root.find("./3mf:resources/3mf:object[@id='{0}']".format(id), self._namespaces)
                if "type" in object.attrib:
                    if object.attrib["type"] == "support" or object.attrib["type"] == "other":
                        # Ignore support objects, as cura does not support these.
                        # We can't guarantee that they wont be made solid.
                        # We also ignore "other", as I have no idea what to do with them.
                        Logger.log("w", "3MF file contained an object of type %s which is not supported by Cura", object.attrib["type"])
                        continue
                    elif object.attrib["type"] == "solidsupport" or object.attrib["type"] == "model":
                        pass  # Load these as normal
                    else:
                        # We should technically fail at this point because it's an invalid 3MF, but try to continue anyway.
                        Logger.log("e", "3MF file contained an object of type %s which is not supported by the 3mf spec",
                                   object.attrib["type"])
                        continue

                build_item_node = self._createNodeFromObject(object, self._base_name + "_" + str(id))

                # compensate for original center position, if object(s) is/are not around its zero position
                transform_matrix = Matrix()
                mesh_data = build_item_node.getMeshData()
                if mesh_data is not None:
                    extents = mesh_data.getExtents()
                    center_vector = Vector(extents.center.x, extents.center.y, extents.center.z)
                    transform_matrix.setByTranslation(center_vector)

                # offset with transform from 3mf
                transform = build_item.get("transform")
                if transform is not None:
                    transform_matrix.multiply(self._createMatrixFromTransformationString(transform))

                build_item_node.setTransformation(transform_matrix)

                global_container_stack = UM.Application.getInstance().getGlobalContainerStack()

                # Create a transformation Matrix to convert from 3mf worldspace into ours.
                # First step: flip the y and z axis.
                transformation_matrix = Matrix()
                transformation_matrix._data[1, 1] = 0
                transformation_matrix._data[1, 2] = 1
                transformation_matrix._data[2, 1] = -1
                transformation_matrix._data[2, 2] = 0

                # Second step: 3MF defines the left corner of the machine as center, whereas cura uses the center of the
                # build volume.
                if global_container_stack:
                    translation_vector = Vector(x = -global_container_stack.getProperty("machine_width", "value") / 2,
                                                y = -global_container_stack.getProperty("machine_depth", "value") / 2,
                                                z = 0)
                    translation_matrix = Matrix()
                    translation_matrix.setByTranslation(translation_vector)
                    transformation_matrix.multiply(translation_matrix)

                # Third step: 3MF also defines a unit, wheras Cura always assumes mm.
                scale_matrix = Matrix()
                scale_matrix.setByScaleVector(self._getScaleFromUnit(self._unit))
                transformation_matrix.multiply(scale_matrix)

                # Pre multiply the transformation with the loaded transformation, so the data is handled correctly.
                build_item_node.setTransformation(build_item_node.getLocalTransformation().preMultiply(transformation_matrix))

                result.append(build_item_node)

        except Exception as e:
            Logger.log("e", "An exception occurred in 3mf reader: %s", e)

        return result
예제 #27
0
 def test_transposed(self):
     temp_matrix = Matrix()  
     temp_matrix.setByTranslation(Vector(10,10,10))
     temp_matrix = temp_matrix.getTransposed()
     numpy.testing.assert_array_almost_equal(temp_matrix.getData(), numpy.array([[1,0,0,0],[0,1,0,0],[0,0,1,0],[10,10,10,1]]))
예제 #28
0
 def test_setByTranslation(self):
     matrix = Matrix()
     matrix.setByTranslation(Vector(0,1,0))
     numpy.testing.assert_array_almost_equal(matrix.getData(), numpy.array([[1,0,0,0],[0,1,0,1],[0,0,1,0],[0,0,0,1]]))
예제 #29
0
    def _read(self, file_name: str) -> Union[SceneNode, List[SceneNode]]:
        result = []
        self._object_count = 0  # Used to name objects as there is no node name yet.
        # The base object of 3mf is a zipped archive.
        try:
            archive = zipfile.ZipFile(file_name, "r")
            self._base_name = os.path.basename(file_name)
            parser = Savitar.ThreeMFParser()
            scene_3mf = parser.parse(archive.open("3D/3dmodel.model").read())
            self._unit = scene_3mf.getUnit()
            for node in scene_3mf.getSceneNodes():
                um_node = self._convertSavitarNodeToUMNode(node)
                if um_node is None:
                    continue
                # compensate for original center position, if object(s) is/are not around its zero position

                transform_matrix = Matrix()
                mesh_data = um_node.getMeshData()
                if mesh_data is not None:
                    extents = mesh_data.getExtents()
                    if extents is not None:
                        center_vector = Vector(extents.center.x, extents.center.y, extents.center.z)
                        transform_matrix.setByTranslation(center_vector)
                transform_matrix.multiply(um_node.getLocalTransformation())
                um_node.setTransformation(transform_matrix)

                global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()

                # Create a transformation Matrix to convert from 3mf worldspace into ours.
                # First step: flip the y and z axis.
                transformation_matrix = Matrix()
                transformation_matrix._data[1, 1] = 0
                transformation_matrix._data[1, 2] = 1
                transformation_matrix._data[2, 1] = -1
                transformation_matrix._data[2, 2] = 0

                # Second step: 3MF defines the left corner of the machine as center, whereas cura uses the center of the
                # build volume.
                if global_container_stack:
                    translation_vector = Vector(x = -global_container_stack.getProperty("machine_width", "value") / 2,
                                                y = -global_container_stack.getProperty("machine_depth", "value") / 2,
                                                z = 0)
                    translation_matrix = Matrix()
                    translation_matrix.setByTranslation(translation_vector)
                    transformation_matrix.multiply(translation_matrix)

                # Third step: 3MF also defines a unit, whereas Cura always assumes mm.
                scale_matrix = Matrix()
                scale_matrix.setByScaleVector(self._getScaleFromUnit(self._unit))
                transformation_matrix.multiply(scale_matrix)

                # Pre multiply the transformation with the loaded transformation, so the data is handled correctly.
                um_node.setTransformation(um_node.getLocalTransformation().preMultiply(transformation_matrix))

                # Check if the model is positioned below the build plate and honor that when loading project files.
                node_meshdata = um_node.getMeshData()
                if node_meshdata is not None:
                    aabb = node_meshdata.getExtents(um_node.getWorldTransformation())
                    if aabb is not None:
                        minimum_z_value = aabb.minimum.y  # y is z in transformation coordinates
                        if minimum_z_value < 0:
                            um_node.addDecorator(ZOffsetDecorator())
                            um_node.callDecoration("setZOffset", minimum_z_value)

                result.append(um_node)

        except Exception:
            Logger.logException("e", "An exception occurred in 3mf reader.")
            return []

        return result
예제 #30
0
    def write(self, stream, nodes, mode = MeshWriter.OutputMode.BinaryMode):
        self._archive = None # Reset archive
        archive = zipfile.ZipFile(stream, "w", compression = zipfile.ZIP_DEFLATED)
        try:
            model_file = zipfile.ZipInfo("3D/3dmodel.model")
            # Because zipfile is stupid and ignores archive-level compression settings when writing with ZipInfo.
            model_file.compress_type = zipfile.ZIP_DEFLATED

            # Create content types file
            content_types_file = zipfile.ZipInfo("[Content_Types].xml")
            content_types_file.compress_type = zipfile.ZIP_DEFLATED
            content_types = ET.Element("Types", xmlns = self._namespaces["content-types"])
            rels_type = ET.SubElement(content_types, "Default", Extension = "rels", ContentType = "application/vnd.openxmlformats-package.relationships+xml")
            model_type = ET.SubElement(content_types, "Default", Extension = "model", ContentType = "application/vnd.ms-package.3dmanufacturing-3dmodel+xml")

            # Create _rels/.rels file
            relations_file = zipfile.ZipInfo("_rels/.rels")
            relations_file.compress_type = zipfile.ZIP_DEFLATED
            relations_element = ET.Element("Relationships", xmlns = self._namespaces["relationships"])
            model_relation_element = ET.SubElement(relations_element, "Relationship", Target = "/3D/3dmodel.model", Id = "rel0", Type = "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel")

            savitar_scene = Savitar.Scene()
            transformation_matrix = Matrix()
            transformation_matrix._data[1, 1] = 0
            transformation_matrix._data[1, 2] = -1
            transformation_matrix._data[2, 1] = 1
            transformation_matrix._data[2, 2] = 0

            global_container_stack = Application.getInstance().getGlobalContainerStack()
            # Second step: 3MF defines the left corner of the machine as center, whereas cura uses the center of the
            # build volume.
            if global_container_stack:
                translation_vector = Vector(x=global_container_stack.getProperty("machine_width", "value") / 2,
                                            y=global_container_stack.getProperty("machine_depth", "value") / 2,
                                            z=0)
                translation_matrix = Matrix()
                translation_matrix.setByTranslation(translation_vector)
                transformation_matrix.preMultiply(translation_matrix)

            root_node = UM.Application.Application.getInstance().getController().getScene().getRoot()
            for node in nodes:
                if node == root_node:
                    for root_child in node.getChildren():
                        savitar_node = self._convertUMNodeToSavitarNode(root_child, transformation_matrix)
                        if savitar_node:
                            savitar_scene.addSceneNode(savitar_node)
                else:
                    savitar_node = self._convertUMNodeToSavitarNode(node, transformation_matrix)
                    if savitar_node:
                        savitar_scene.addSceneNode(savitar_node)

            parser = Savitar.ThreeMFParser()
            scene_string = parser.sceneToString(savitar_scene)

            archive.writestr(model_file, scene_string)
            archive.writestr(content_types_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(content_types))
            archive.writestr(relations_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(relations_element))
        except Exception as e:
            Logger.logException("e", "Error writing zip file")
            return False
        finally:
            if not self._store_archive:
                archive.close()
            else:
                self._archive = archive

        return True
예제 #31
0
    def write(self, stream, nodes, mode=MeshWriter.OutputMode.BinaryMode):
        try:
            MeshWriter._meshNodes(nodes).__next__()
        except StopIteration:
            return False  #Don't write anything if there is no mesh data.

        archive = zipfile.ZipFile(stream,
                                  "w",
                                  compression=zipfile.ZIP_DEFLATED)
        try:
            model_file = zipfile.ZipInfo("3D/3dmodel.model")
            # Because zipfile is stupid and ignores archive-level compression settings when writing with ZipInfo.
            model_file.compress_type = zipfile.ZIP_DEFLATED

            # Create content types file
            content_types_file = zipfile.ZipInfo("[Content_Types].xml")
            content_types_file.compress_type = zipfile.ZIP_DEFLATED
            content_types = ET.Element("Types",
                                       xmlns=self._namespaces["content-types"])
            rels_type = ET.SubElement(
                content_types,
                "Default",
                Extension="rels",
                ContentType=
                "application/vnd.openxmlformats-package.relationships+xml")
            model_type = ET.SubElement(
                content_types,
                "Default",
                Extension="model",
                ContentType=
                "application/vnd.ms-package.3dmanufacturing-3dmodel+xml")

            # Create _rels/.rels file
            relations_file = zipfile.ZipInfo("_rels/.rels")
            relations_file.compress_type = zipfile.ZIP_DEFLATED
            relations_element = ET.Element(
                "Relationships", xmlns=self._namespaces["relationships"])
            model_relation_element = ET.SubElement(
                relations_element,
                "Relationship",
                Target="/3D/3dmodel.model",
                Id="rel0",
                Type=
                "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel")

            model = ET.Element("model",
                               unit="millimeter",
                               xmlns=self._namespaces["3mf"])
            resources = ET.SubElement(model, "resources")
            build = ET.SubElement(model, "build")
            for index, n in enumerate(MeshWriter._meshNodes(nodes)):
                object = ET.SubElement(resources,
                                       "object",
                                       id=str(index + 1),
                                       type="model")
                mesh = ET.SubElement(object, "mesh")

                mesh_data = n.getMeshData()
                vertices = ET.SubElement(mesh, "vertices")
                verts = mesh_data.getVertices()

                if verts is None:
                    Logger.log(
                        "d",
                        "3mf writer can't write nodes without mesh data. Skipping this node."
                    )
                    continue  # No mesh data, nothing to do.

                if mesh_data.hasIndices():
                    for face in mesh_data.getIndices():
                        v1 = verts[face[0]]
                        v2 = verts[face[1]]
                        v3 = verts[face[2]]
                        xml_vertex1 = ET.SubElement(vertices,
                                                    "vertex",
                                                    x=str(v1[0]),
                                                    y=str(v1[2]),
                                                    z=str(v1[1]))
                        xml_vertex2 = ET.SubElement(vertices,
                                                    "vertex",
                                                    x=str(v2[0]),
                                                    y=str(v2[2]),
                                                    z=str(v2[1]))
                        xml_vertex3 = ET.SubElement(vertices,
                                                    "vertex",
                                                    x=str(v3[0]),
                                                    y=str(v3[2]),
                                                    z=str(v3[1]))

                    triangles = ET.SubElement(mesh, "triangles")
                    for face in mesh_data.getIndices():
                        triangle = ET.SubElement(triangles,
                                                 "triangle",
                                                 v1=str(face[0]),
                                                 v2=str(face[1]),
                                                 v3=str(face[2]))
                else:
                    triangles = ET.SubElement(mesh, "triangles")
                    for idx, vert in enumerate(verts):
                        xml_vertex = ET.SubElement(vertices,
                                                   "vertex",
                                                   x=str(vert[0]),
                                                   y=str(vert[2]),
                                                   z=str(vert[1]))

                        # If we have no faces defined, assume that every three subsequent vertices form a face.
                        if idx % 3 == 0:
                            triangle = ET.SubElement(triangles,
                                                     "triangle",
                                                     v1=str(idx),
                                                     v2=str(idx + 1),
                                                     v3=str(idx + 2))
                world_transformation = n.getWorldTransformation()

                # 3MF sees lower left corner of buildplate as zero, so we need to translate a bit first
                global_container_stack = UM.Application.getInstance(
                ).getGlobalContainerStack()
                if global_container_stack:
                    translation = Vector(x=global_container_stack.getProperty(
                        "machine_width", "value") / 2,
                                         y=0,
                                         z=-global_container_stack.getProperty(
                                             "machine_depth", "value") / 2)
                else:
                    translation = Vector(0, 0, 0)
                translation_matrix = Matrix()
                translation_matrix.setByTranslation(translation)
                world_transformation.multiply(translation_matrix)

                # We use a different coordinate frame, so we need to flip the transformation
                flip_matrix = Matrix()
                flip_matrix._data[1, 1] = 0
                flip_matrix._data[1, 2] = 1
                flip_matrix._data[2, 1] = 1
                flip_matrix._data[2, 2] = 0
                world_transformation.multiply(flip_matrix)

                transformation_string = self._convertMatrixToString(
                    world_transformation)
                if transformation_string != self._convertMatrixToString(
                        Matrix()):
                    item = ET.SubElement(build,
                                         "item",
                                         objectid=str(index + 1),
                                         transform=transformation_string)
                else:
                    item = ET.SubElement(
                        build, "item",
                        objectid=str(index +
                                     1))  #, transform = transformation_string)

            archive.writestr(
                model_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' +
                ET.tostring(model))
            archive.writestr(
                content_types_file,
                b'<?xml version="1.0" encoding="UTF-8"?> \n' +
                ET.tostring(content_types))
            archive.writestr(
                relations_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' +
                ET.tostring(relations_element))
        except Exception as e:
            Logger.logException("e", "Error writing zip file")
            return False
        finally:
            archive.close()

        return True
예제 #32
0
파일: ThreeMFWriter.py 프로젝트: mifga/Cura
    def write(self, stream, nodes, mode = MeshWriter.OutputMode.BinaryMode):
        self._archive = None # Reset archive
        archive = zipfile.ZipFile(stream, "w", compression = zipfile.ZIP_DEFLATED)
        try:
            model_file = zipfile.ZipInfo("3D/3dmodel.model")
            # Because zipfile is stupid and ignores archive-level compression settings when writing with ZipInfo.
            model_file.compress_type = zipfile.ZIP_DEFLATED

            # Create content types file
            content_types_file = zipfile.ZipInfo("[Content_Types].xml")
            content_types_file.compress_type = zipfile.ZIP_DEFLATED
            content_types = ET.Element("Types", xmlns = self._namespaces["content-types"])
            rels_type = ET.SubElement(content_types, "Default", Extension = "rels", ContentType = "application/vnd.openxmlformats-package.relationships+xml")
            model_type = ET.SubElement(content_types, "Default", Extension = "model", ContentType = "application/vnd.ms-package.3dmanufacturing-3dmodel+xml")

            # Create _rels/.rels file
            relations_file = zipfile.ZipInfo("_rels/.rels")
            relations_file.compress_type = zipfile.ZIP_DEFLATED
            relations_element = ET.Element("Relationships", xmlns = self._namespaces["relationships"])
            model_relation_element = ET.SubElement(relations_element, "Relationship", Target = "/3D/3dmodel.model", Id = "rel0", Type = "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel")

            model = ET.Element("model", unit = "millimeter", xmlns = self._namespaces["3mf"])

            # Add the version of Cura this was created with. As "CuraVersion" is not a recognised metadata name
            #  by 3mf itself, we place it in our own namespace.
            version_metadata = ET.SubElement(model, "metadata", xmlns = self._namespaces["cura"], name = "CuraVersion")
            version_metadata.text = Application.getInstance().getVersion()

            resources = ET.SubElement(model, "resources")
            build = ET.SubElement(model, "build")

            added_nodes = []
            index = 0  # Ensure index always exists (even if there are no nodes to write)
            # Write all nodes with meshData to the file as objects inside the resource tag
            for index, n in enumerate(MeshWriter._meshNodes(nodes)):
                added_nodes.append(n)  # Save the nodes that have mesh data
                object = ET.SubElement(resources, "object", id = str(index+1), type = "model")
                mesh = ET.SubElement(object, "mesh")

                mesh_data = n.getMeshData()
                vertices = ET.SubElement(mesh, "vertices")
                verts = mesh_data.getVertices()

                if verts is None:
                    Logger.log("d", "3mf writer can't write nodes without mesh data. Skipping this node.")
                    continue  # No mesh data, nothing to do.
                if mesh_data.hasIndices():
                    for face in mesh_data.getIndices():
                        v1 = verts[face[0]]
                        v2 = verts[face[1]]
                        v3 = verts[face[2]]
                        xml_vertex1 = ET.SubElement(vertices, "vertex", x = str(v1[0]), y = str(v1[1]), z = str(v1[2]))
                        xml_vertex2 = ET.SubElement(vertices, "vertex", x = str(v2[0]), y = str(v2[1]), z = str(v2[2]))
                        xml_vertex3 = ET.SubElement(vertices, "vertex", x = str(v3[0]), y = str(v3[1]), z = str(v3[2]))

                    triangles = ET.SubElement(mesh, "triangles")
                    for face in mesh_data.getIndices():
                        triangle = ET.SubElement(triangles, "triangle", v1 = str(face[0]) , v2 = str(face[1]), v3 = str(face[2]))
                else:
                    triangles = ET.SubElement(mesh, "triangles")
                    for idx, vert in enumerate(verts):
                        xml_vertex = ET.SubElement(vertices, "vertex", x = str(vert[0]), y = str(vert[1]), z = str(vert[2]))

                        # If we have no faces defined, assume that every three subsequent vertices form a face.
                        if idx % 3 == 0:
                            triangle = ET.SubElement(triangles, "triangle", v1 = str(idx), v2 = str(idx + 1), v3 = str(idx + 2))

                # Handle per object settings
                stack = n.callDecoration("getStack")
                if stack is not None:
                    changed_setting_keys = set(stack.getTop().getAllKeys())

                    # Ensure that we save the extruder used for this object.
                    if stack.getProperty("machine_extruder_count", "value") > 1:
                        changed_setting_keys.add("extruder_nr")

                    settings_xml = ET.SubElement(object, "settings", xmlns=self._namespaces["cura"])

                    # Get values for all changed settings & save them.
                    for key in changed_setting_keys:
                        setting_xml = ET.SubElement(settings_xml, "setting", key = key)
                        setting_xml.text = str(stack.getProperty(key, "value"))

            # Add one to the index as we haven't incremented the last iteration.
            index += 1
            nodes_to_add = set()

            for node in added_nodes:
                # Check the parents of the nodes with mesh_data and ensure that they are also added.
                parent_node = node.getParent()
                while parent_node is not None:
                    if parent_node.callDecoration("isGroup"):
                        nodes_to_add.add(parent_node)
                        parent_node = parent_node.getParent()
                    else:
                        parent_node = None

            # Sort all the nodes by depth (so nodes with the highest depth are done first)
            sorted_nodes_to_add = sorted(nodes_to_add, key=lambda node: node.getDepth(), reverse = True)

            # We have already saved the nodes with mesh data, but now we also want to save nodes required for the scene
            for node in sorted_nodes_to_add:
                object = ET.SubElement(resources, "object", id=str(index + 1), type="model")
                components = ET.SubElement(object, "components")
                for child in node.getChildren():
                    if child in added_nodes:
                        component = ET.SubElement(components, "component", objectid = str(added_nodes.index(child) + 1), transform = self._convertMatrixToString(child.getLocalTransformation()))
                index += 1
                added_nodes.append(node)

            # Create a transformation Matrix to convert from our worldspace into 3MF.
            # First step: flip the y and z axis.
            transformation_matrix = Matrix()
            transformation_matrix._data[1, 1] = 0
            transformation_matrix._data[1, 2] = -1
            transformation_matrix._data[2, 1] = 1
            transformation_matrix._data[2, 2] = 0

            global_container_stack = UM.Application.getInstance().getGlobalContainerStack()
            # Second step: 3MF defines the left corner of the machine as center, whereas cura uses the center of the
            # build volume.
            if global_container_stack:
                translation_vector = Vector(x=global_container_stack.getProperty("machine_width", "value") / 2,
                                            y=global_container_stack.getProperty("machine_depth", "value") / 2,
                                            z=0)
                translation_matrix = Matrix()
                translation_matrix.setByTranslation(translation_vector)
                transformation_matrix.preMultiply(translation_matrix)

            # Find out what the final build items are and add them.
            for node in added_nodes:
                if node.getParent().callDecoration("isGroup") is None:
                    node_matrix = node.getLocalTransformation()

                    ET.SubElement(build, "item", objectid = str(added_nodes.index(node) + 1), transform = self._convertMatrixToString(node_matrix.preMultiply(transformation_matrix)))

            archive.writestr(model_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(model))
            archive.writestr(content_types_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(content_types))
            archive.writestr(relations_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(relations_element))
        except Exception as e:
            Logger.logException("e", "Error writing zip file")
            return False
        finally:
            if not self._store_archive:
                archive.close()
            else:
                self._archive = archive

        return True
예제 #33
0
    def read(self, file_name):
        result = []
        # The base object of 3mf is a zipped archive.
        archive = zipfile.ZipFile(file_name, "r")
        self._base_name = os.path.basename(file_name)
        try:
            self._root = ET.parse(archive.open("3D/3dmodel.model"))
            self._unit = self._root.getroot().get("unit")

            build_items = self._root.findall("./3mf:build/3mf:item",
                                             self._namespaces)

            for build_item in build_items:
                id = build_item.get("objectid")
                object = self._root.find(
                    "./3mf:resources/3mf:object[@id='{0}']".format(id),
                    self._namespaces)
                if "type" in object.attrib:
                    if object.attrib["type"] == "support" or object.attrib[
                            "type"] == "other":
                        # Ignore support objects, as cura does not support these.
                        # We can't guarantee that they wont be made solid.
                        # We also ignore "other", as I have no idea what to do with them.
                        Logger.log(
                            "w",
                            "3MF file contained an object of type %s which is not supported by Cura",
                            object.attrib["type"])
                        continue
                    elif object.attrib[
                            "type"] == "solidsupport" or object.attrib[
                                "type"] == "model":
                        pass  # Load these as normal
                    else:
                        # We should technically fail at this point because it's an invalid 3MF, but try to continue anyway.
                        Logger.log(
                            "e",
                            "3MF file contained an object of type %s which is not supported by the 3mf spec",
                            object.attrib["type"])
                        continue

                build_item_node = self._createNodeFromObject(
                    object, self._base_name + "_" + str(id))

                # compensate for original center position, if object(s) is/are not around its zero position
                transform_matrix = Matrix()
                mesh_data = build_item_node.getMeshData()
                if mesh_data is not None:
                    extents = mesh_data.getExtents()
                    center_vector = Vector(extents.center.x, extents.center.y,
                                           extents.center.z)
                    transform_matrix.setByTranslation(center_vector)

                # offset with transform from 3mf
                transform = build_item.get("transform")
                if transform is not None:
                    transform_matrix.multiply(
                        self._createMatrixFromTransformationString(transform))

                build_item_node.setTransformation(transform_matrix)

                global_container_stack = UM.Application.getInstance(
                ).getGlobalContainerStack()

                # Create a transformation Matrix to convert from 3mf worldspace into ours.
                # First step: flip the y and z axis.
                transformation_matrix = Matrix()
                transformation_matrix._data[1, 1] = 0
                transformation_matrix._data[1, 2] = 1
                transformation_matrix._data[2, 1] = -1
                transformation_matrix._data[2, 2] = 0

                # Second step: 3MF defines the left corner of the machine as center, whereas cura uses the center of the
                # build volume.
                if global_container_stack:
                    translation_vector = Vector(
                        x=-global_container_stack.getProperty(
                            "machine_width", "value") / 2,
                        y=-global_container_stack.getProperty(
                            "machine_depth", "value") / 2,
                        z=0)
                    translation_matrix = Matrix()
                    translation_matrix.setByTranslation(translation_vector)
                    transformation_matrix.multiply(translation_matrix)

                # Third step: 3MF also defines a unit, wheras Cura always assumes mm.
                scale_matrix = Matrix()
                scale_matrix.setByScaleVector(
                    self._getScaleFromUnit(self._unit))
                transformation_matrix.multiply(scale_matrix)

                # Pre multiply the transformation with the loaded transformation, so the data is handled correctly.
                build_item_node.setTransformation(
                    build_item_node.getLocalTransformation().preMultiply(
                        transformation_matrix))

                result.append(build_item_node)

        except Exception as e:
            Logger.log("e", "An exception occurred in 3mf reader: %s", e)

        return result
예제 #34
0
    def write(self, stream, nodes, mode = MeshWriter.OutputMode.BinaryMode):
        self._archive = None # Reset archive
        archive = zipfile.ZipFile(stream, "w", compression = zipfile.ZIP_DEFLATED)
        try:
            model_file = zipfile.ZipInfo("3D/3dmodel.model")
            # Because zipfile is stupid and ignores archive-level compression settings when writing with ZipInfo.
            model_file.compress_type = zipfile.ZIP_DEFLATED

            # Create content types file
            content_types_file = zipfile.ZipInfo("[Content_Types].xml")
            content_types_file.compress_type = zipfile.ZIP_DEFLATED
            content_types = ET.Element("Types", xmlns = self._namespaces["content-types"])
            rels_type = ET.SubElement(content_types, "Default", Extension = "rels", ContentType = "application/vnd.openxmlformats-package.relationships+xml")
            model_type = ET.SubElement(content_types, "Default", Extension = "model", ContentType = "application/vnd.ms-package.3dmanufacturing-3dmodel+xml")

            # Create _rels/.rels file
            relations_file = zipfile.ZipInfo("_rels/.rels")
            relations_file.compress_type = zipfile.ZIP_DEFLATED
            relations_element = ET.Element("Relationships", xmlns = self._namespaces["relationships"])
            model_relation_element = ET.SubElement(relations_element, "Relationship", Target = "/3D/3dmodel.model", Id = "rel0", Type = "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel")

            savitar_scene = Savitar.Scene()
            transformation_matrix = Matrix()
            transformation_matrix._data[1, 1] = 0
            transformation_matrix._data[1, 2] = -1
            transformation_matrix._data[2, 1] = 1
            transformation_matrix._data[2, 2] = 0

            global_container_stack = Application.getInstance().getGlobalContainerStack()
            # Second step: 3MF defines the left corner of the machine as center, whereas cura uses the center of the
            # build volume.
            if global_container_stack:
                translation_vector = Vector(x=global_container_stack.getProperty("machine_width", "value") / 2,
                                            y=global_container_stack.getProperty("machine_depth", "value") / 2,
                                            z=0)
                translation_matrix = Matrix()
                translation_matrix.setByTranslation(translation_vector)
                transformation_matrix.preMultiply(translation_matrix)

            root_node = UM.Application.Application.getInstance().getController().getScene().getRoot()
            for node in nodes:
                if node == root_node:
                    for root_child in node.getChildren():
                        savitar_node = self._convertUMNodeToSavitarNode(root_child, transformation_matrix)
                        if savitar_node:
                            savitar_scene.addSceneNode(savitar_node)
                else:
                    savitar_node = self._convertUMNodeToSavitarNode(node, transformation_matrix)
                    if savitar_node:
                        savitar_scene.addSceneNode(savitar_node)

            parser = Savitar.ThreeMFParser()
            scene_string = parser.sceneToString(savitar_scene)

            archive.writestr(model_file, scene_string)
            archive.writestr(content_types_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(content_types))
            archive.writestr(relations_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(relations_element))
        except Exception as e:
            Logger.logException("e", "Error writing zip file")
            self.setInformation(catalog.i18nc("@error:zip", "Error writing 3mf file."))
            return False
        finally:
            if not self._store_archive:
                archive.close()
            else:
                self._archive = archive

        return True
예제 #35
0
    def _read(self, file_name: str) -> Union[SceneNode, List[SceneNode]]:
        self._empty_project = False
        result = []
        # The base object of 3mf is a zipped archive.
        try:
            archive = zipfile.ZipFile(file_name, "r")
            self._base_name = os.path.basename(file_name)
            parser = Savitar.ThreeMFParser()
            scene_3mf = parser.parse(archive.open("3D/3dmodel.model").read())
            self._unit = scene_3mf.getUnit()

            for key, value in scene_3mf.getMetadata().items():
                CuraApplication.getInstance().getController().getScene(
                ).setMetaDataEntry(key, value)

            for node in scene_3mf.getSceneNodes():
                um_node = self._convertSavitarNodeToUMNode(node, file_name)
                if um_node is None:
                    continue

                # compensate for original center position, if object(s) is/are not around its zero position
                transform_matrix = Matrix()
                mesh_data = um_node.getMeshData()
                if mesh_data is not None:
                    extents = mesh_data.getExtents()
                    if extents is not None:
                        center_vector = Vector(extents.center.x,
                                               extents.center.y,
                                               extents.center.z)
                        transform_matrix.setByTranslation(center_vector)
                transform_matrix.multiply(um_node.getLocalTransformation())
                um_node.setTransformation(transform_matrix)

                global_container_stack = CuraApplication.getInstance(
                ).getGlobalContainerStack()

                # Create a transformation Matrix to convert from 3mf worldspace into ours.
                # First step: flip the y and z axis.
                transformation_matrix = Matrix()
                transformation_matrix._data[1, 1] = 0
                transformation_matrix._data[1, 2] = 1
                transformation_matrix._data[2, 1] = -1
                transformation_matrix._data[2, 2] = 0

                # Second step: 3MF defines the left corner of the machine as center, whereas cura uses the center of the
                # build volume.
                if global_container_stack:
                    translation_vector = Vector(
                        x=-global_container_stack.getProperty(
                            "machine_width", "value") / 2,
                        y=-global_container_stack.getProperty(
                            "machine_depth", "value") / 2,
                        z=0)
                    translation_matrix = Matrix()
                    translation_matrix.setByTranslation(translation_vector)
                    transformation_matrix.multiply(translation_matrix)

                # Third step: 3MF also defines a unit, whereas Cura always assumes mm.
                scale_matrix = Matrix()
                scale_matrix.setByScaleVector(
                    self._getScaleFromUnit(self._unit))
                transformation_matrix.multiply(scale_matrix)

                # Pre multiply the transformation with the loaded transformation, so the data is handled correctly.
                um_node.setTransformation(
                    um_node.getLocalTransformation().preMultiply(
                        transformation_matrix))

                # Check if the model is positioned below the build plate and honor that when loading project files.
                node_meshdata = um_node.getMeshData()
                if node_meshdata is not None:
                    aabb = node_meshdata.getExtents(
                        um_node.getWorldTransformation())
                    if aabb is not None:
                        minimum_z_value = aabb.minimum.y  # y is z in transformation coordinates
                        if minimum_z_value < 0:
                            um_node.addDecorator(ZOffsetDecorator())
                            um_node.callDecoration("setZOffset",
                                                   minimum_z_value)

                result.append(um_node)

            if len(result) == 0:
                self._empty_project = True

        except Exception:
            Logger.logException("e", "An exception occurred in 3mf reader.")
            return []

        return result
예제 #36
0
    def write(self, stream, nodes, mode = MeshWriter.OutputMode.BinaryMode):
        self._archive = None # Reset archive
        archive = zipfile.ZipFile(stream, "w", compression = zipfile.ZIP_DEFLATED)
        try:
            model_file = zipfile.ZipInfo("3D/3dmodel.model")
            # Because zipfile is stupid and ignores archive-level compression settings when writing with ZipInfo.
            model_file.compress_type = zipfile.ZIP_DEFLATED

            # Create content types file
            content_types_file = zipfile.ZipInfo("[Content_Types].xml")
            content_types_file.compress_type = zipfile.ZIP_DEFLATED
            content_types = ET.Element("Types", xmlns = self._namespaces["content-types"])
            rels_type = ET.SubElement(content_types, "Default", Extension = "rels", ContentType = "application/vnd.openxmlformats-package.relationships+xml")
            model_type = ET.SubElement(content_types, "Default", Extension = "model", ContentType = "application/vnd.ms-package.3dmanufacturing-3dmodel+xml")

            # Create _rels/.rels file
            relations_file = zipfile.ZipInfo("_rels/.rels")
            relations_file.compress_type = zipfile.ZIP_DEFLATED
            relations_element = ET.Element("Relationships", xmlns = self._namespaces["relationships"])
            model_relation_element = ET.SubElement(relations_element, "Relationship", Target = "/3D/3dmodel.model", Id = "rel0", Type = "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel")

            # Attempt to add a thumbnail
            snapshot = self._createSnapshot()
            if snapshot:
                thumbnail_buffer = QBuffer()
                thumbnail_buffer.open(QBuffer.ReadWrite)
                snapshot.save(thumbnail_buffer, "PNG")

                thumbnail_file = zipfile.ZipInfo("Metadata/thumbnail.png")
                # Don't try to compress snapshot file, because the PNG is pretty much as compact as it will get
                archive.writestr(thumbnail_file, thumbnail_buffer.data())

                # Add PNG to content types file
                thumbnail_type = ET.SubElement(content_types, "Default", Extension = "png", ContentType = "image/png")
                # Add thumbnail relation to _rels/.rels file
                thumbnail_relation_element = ET.SubElement(relations_element, "Relationship", Target = "/Metadata/thumbnail.png", Id = "rel1", Type = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail")

            savitar_scene = Savitar.Scene()

            metadata_to_store = CuraApplication.getInstance().getController().getScene().getMetaData()

            for key, value in metadata_to_store.items():
                savitar_scene.setMetaDataEntry(key, value)

            current_time_string = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            if "Application" not in metadata_to_store:
                # This might sound a bit strange, but this field should store the original application that created
                # the 3mf. So if it was already set, leave it to whatever it was.
                savitar_scene.setMetaDataEntry("Application", CuraApplication.getInstance().getApplicationDisplayName())
            if "CreationDate" not in metadata_to_store:
                savitar_scene.setMetaDataEntry("CreationDate", current_time_string)

            savitar_scene.setMetaDataEntry("ModificationDate", current_time_string)

            transformation_matrix = Matrix()
            transformation_matrix._data[1, 1] = 0
            transformation_matrix._data[1, 2] = -1
            transformation_matrix._data[2, 1] = 1
            transformation_matrix._data[2, 2] = 0

            global_container_stack = Application.getInstance().getGlobalContainerStack()
            # Second step: 3MF defines the left corner of the machine as center, whereas cura uses the center of the
            # build volume.
            if global_container_stack:
                translation_vector = Vector(x=global_container_stack.getProperty("machine_width", "value") / 2,
                                            y=global_container_stack.getProperty("machine_depth", "value") / 2,
                                            z=0)
                translation_matrix = Matrix()
                translation_matrix.setByTranslation(translation_vector)
                transformation_matrix.preMultiply(translation_matrix)

            root_node = UM.Application.Application.getInstance().getController().getScene().getRoot()
            for node in nodes:
                if node == root_node:
                    for root_child in node.getChildren():
                        savitar_node = self._convertUMNodeToSavitarNode(root_child, transformation_matrix)
                        if savitar_node:
                            savitar_scene.addSceneNode(savitar_node)
                else:
                    savitar_node = self._convertUMNodeToSavitarNode(node, transformation_matrix)
                    if savitar_node:
                        savitar_scene.addSceneNode(savitar_node)

            parser = Savitar.ThreeMFParser()
            scene_string = parser.sceneToString(savitar_scene)

            archive.writestr(model_file, scene_string)
            archive.writestr(content_types_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(content_types))
            archive.writestr(relations_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(relations_element))
        except Exception as e:
            Logger.logException("e", "Error writing zip file")
            self.setInformation(catalog.i18nc("@error:zip", "Error writing 3mf file."))
            return False
        finally:
            if not self._store_archive:
                archive.close()
            else:
                self._archive = archive

        return True