Browse Source

Multiple objects are now handled with right transformation

Jaime van Kessel 8 years ago
parent
commit
7c59f8f2f2
1 changed files with 97 additions and 102 deletions
  1. 97 102
      plugins/3MFReader/ThreeMFReader.py

+ 97 - 102
plugins/3MFReader/ThreeMFReader.py

@@ -11,6 +11,8 @@ from UM.Scene.GroupDecorator import GroupDecorator
 import UM.Application
 import UM.Application
 from UM.Job import Job
 from UM.Job import Job
 
 
+from UM.Math.Quaternion import Quaternion
+
 import math
 import math
 import zipfile
 import zipfile
 
 
@@ -24,118 +26,111 @@ class ThreeMFReader(MeshReader):
     def __init__(self):
     def __init__(self):
         super().__init__()
         super().__init__()
         self._supported_extensions = [".3mf"]
         self._supported_extensions = [".3mf"]
-
+        self._root = None
         self._namespaces = {
         self._namespaces = {
             "3mf": "http://schemas.microsoft.com/3dmanufacturing/core/2015/02",
             "3mf": "http://schemas.microsoft.com/3dmanufacturing/core/2015/02",
             "cura": "http://software.ultimaker.com/xml/cura/3mf/2015/10"
             "cura": "http://software.ultimaker.com/xml/cura/3mf/2015/10"
         }
         }
 
 
+    def _createNodeFromObject(self, object, name = ""):
+        mesh_builder = MeshBuilder()
+        node = SceneNode()
+        vertex_list = []
+
+        components = object.find(".//3mf:components", self._namespaces)
+        if components:
+            for component in components:
+                id = component.get("objectid")
+                object = self._root.find("./3mf:resources/3mf:object[@id='{0}']".format(id), self._namespaces)
+                new_node = self._createNodeFromObject(object)
+                node.addChild(new_node)
+                transform = component.get("transform")
+                if transform is not None:
+                    new_node.setTransformation(self._createMatrixFromTransformationString(transform))
+
+        if len(node.getChildren()) > 0:
+            group_decorator = GroupDecorator()
+            node.addDecorator(group_decorator)
+
+        # for vertex in entry.mesh.vertices.vertex:
+        for vertex in object.findall(".//3mf:vertex", self._namespaces):
+            vertex_list.append([vertex.get("x"), vertex.get("y"), vertex.get("z")])
+            Job.yieldThread()
+
+        triangles = object.findall(".//3mf:triangle", self._namespaces)
+        mesh_builder.reserveFaceCount(len(triangles))
+
+        for triangle in triangles:
+            v1 = int(triangle.get("v1"))
+            v2 = int(triangle.get("v2"))
+            v3 = int(triangle.get("v3"))
+
+            mesh_builder.addFaceByPoints(vertex_list[v1][0], vertex_list[v1][1], vertex_list[v1][2],
+                                         vertex_list[v2][0], vertex_list[v2][1], vertex_list[v2][2],
+                                         vertex_list[v3][0], vertex_list[v3][1], vertex_list[v3][2])
+
+            Job.yieldThread()
+
+        # Rotate the model; We use a different coordinate frame.
+        rotation = Matrix()
+        rotation.setByRotationAxis(-0.5 * math.pi, Vector(1, 0, 0))
+
+        # TODO: We currently do not check for normals and simply recalculate them.
+        mesh_builder.calculateNormals()
+        mesh_builder.setFileName(name)
+        mesh_data = mesh_builder.build() #.getTransformed(rotation)
+
+        if len(mesh_data.getVertices()):
+            node.setMeshData(mesh_data)
+
+        node.setSelectable(True)
+        return node
+
+    def _createMatrixFromTransformationString(self, transformation):
+        splitted_transformation = transformation.split()
+        ## Transformation is saved as:
+        ## M00 M01 M02 0.0
+        ## M10 M11 M12 0.0
+        ## M20 M21 M22 0.0
+        ## M30 M31 M32 1.0
+        ## We switch the row & cols as that is how everyone else uses matrices!
+        temp_mat = Matrix()
+        # Rotation & Scale
+        temp_mat._data[0, 0] = splitted_transformation[0]
+        temp_mat._data[1, 0] = splitted_transformation[1]
+        temp_mat._data[2, 0] = splitted_transformation[2]
+        temp_mat._data[0, 1] = splitted_transformation[3]
+        temp_mat._data[1, 1] = splitted_transformation[4]
+        temp_mat._data[2, 1] = splitted_transformation[5]
+        temp_mat._data[0, 2] = splitted_transformation[6]
+        temp_mat._data[1, 2] = splitted_transformation[7]
+        temp_mat._data[2, 2] = splitted_transformation[8]
+
+        # Translation
+        temp_mat._data[0, 3] = splitted_transformation[9]
+        temp_mat._data[1, 3] = splitted_transformation[10]
+        temp_mat._data[2, 3] = splitted_transformation[11]
+        return temp_mat
+
     def read(self, file_name):
     def read(self, file_name):
         result = SceneNode()
         result = SceneNode()
         # The base object of 3mf is a zipped archive.
         # The base object of 3mf is a zipped archive.
         archive = zipfile.ZipFile(file_name, "r")
         archive = zipfile.ZipFile(file_name, "r")
         try:
         try:
-            root = ET.parse(archive.open("3D/3dmodel.model"))
-
-            # There can be multiple objects, try to load all of them.
-            objects = root.findall("./3mf:resources/3mf:object", self._namespaces)
-            if len(objects) == 0:
-                Logger.log("w", "No objects found in 3MF file %s, either the file is corrupt or you are using an outdated format", file_name)
-                return None
-
-            for entry in objects:
-                mesh_builder = MeshBuilder()
-                node = SceneNode()
-                vertex_list = []
-
-                # for vertex in entry.mesh.vertices.vertex:
-                for vertex in entry.findall(".//3mf:vertex", self._namespaces):
-                    vertex_list.append([vertex.get("x"), vertex.get("y"), vertex.get("z")])
-                    Job.yieldThread()
-
-                triangles = entry.findall(".//3mf:triangle", self._namespaces)
-                mesh_builder.reserveFaceCount(len(triangles))
-
-                for triangle in triangles:
-                    v1 = int(triangle.get("v1"))
-                    v2 = int(triangle.get("v2"))
-                    v3 = int(triangle.get("v3"))
-
-                    mesh_builder.addFaceByPoints(vertex_list[v1][0], vertex_list[v1][1], vertex_list[v1][2],
-                                                 vertex_list[v2][0], vertex_list[v2][1], vertex_list[v2][2],
-                                                 vertex_list[v3][0], vertex_list[v3][1], vertex_list[v3][2])
-
-                    Job.yieldThread()
-
-                # Rotate the model; We use a different coordinate frame.
-                rotation = Matrix()
-                rotation.setByRotationAxis(-0.5 * math.pi, Vector(1, 0, 0))
-
-                # TODO: We currently do not check for normals and simply recalculate them.
-                mesh_builder.calculateNormals()
-                mesh_builder.setFileName(file_name)
-                mesh_data = mesh_builder.build().getTransformed(rotation)
-
-                if not len(mesh_data.getVertices()):
-                    Logger.log("d", "One of the objects does not have vertices. Skipping it.")
-                    continue  # This object doesn't have data, so skip it.
-
-                node.setMeshData(mesh_data)
-                node.setSelectable(True)
-
-                transformations = root.findall("./3mf:build/3mf:item[@objectid='{0}']".format(entry.get("id")), self._namespaces)
-                transformation = transformations[0] if transformations else None
-                if transformation is not None and transformation.get("transform"):
-                    splitted_transformation = transformation.get("transform").split()
-                    ## Transformation is saved as:
-                    ## M00 M01 M02 0.0
-                    ## M10 M11 M12 0.0
-                    ## M20 M21 M22 0.0
-                    ## M30 M31 M32 1.0
-                    ## We switch the row & cols as that is how everyone else uses matrices!
-                    temp_mat = Matrix()
-                    # Rotation & Scale
-                    temp_mat._data[0,0] = splitted_transformation[0]
-                    temp_mat._data[1,0] = splitted_transformation[1]
-                    temp_mat._data[2,0] = splitted_transformation[2]
-                    temp_mat._data[0,1] = splitted_transformation[3]
-                    temp_mat._data[1,1] = splitted_transformation[4]
-                    temp_mat._data[2,1] = splitted_transformation[5]
-                    temp_mat._data[0,2] = splitted_transformation[6]
-                    temp_mat._data[1,2] = splitted_transformation[7]
-                    temp_mat._data[2,2] = splitted_transformation[8]
-
-                    # Translation
-                    temp_mat._data[0,3] = splitted_transformation[9]
-                    temp_mat._data[1,3] = splitted_transformation[10]
-                    temp_mat._data[2,3] = splitted_transformation[11]
-
-                    node.setTransformation(temp_mat)
-
-                try:
-                    node.getBoundingBox()  # Selftest - There might be more functions that should fail
-                except:
-                    Logger.log("w", "Bounding box test for object failed. Skipping this object")
-                    continue
-
-                # 3mf defines the front left corner as the 0, so we need to translate to their operating space.
-                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)
-                    node.translate(translation)
-                result.addChild(node)
-
-                Job.yieldThread()
-
-            # If there is more then one object, group them.
-            if len(objects) > 1:
-                group_decorator = GroupDecorator()
-                result.addDecorator(group_decorator)
-            elif len(objects) == 1:
-                if result.getChildren():
-                    result = result.getChildren()[0]  # Only one object found, return that.
-                else: # we failed to load any data
-                    return None
+            self._root = ET.parse(archive.open("3D/3dmodel.model"))
+
+            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)
+                build_item_node = self._createNodeFromObject(object)
+                transform = build_item.get("transform")
+                if transform is not None:
+                    build_item_node.setTransformation(self._createMatrixFromTransformationString(transform))
+                result.addChild(build_item_node)
+                build_item_node.rotate(Quaternion.fromAngleAxis(-0.5 * math.pi, Vector(1, 0, 0)))
+
         except Exception as e:
         except Exception as e:
             Logger.log("e", "exception occured in 3mf reader: %s", e)
             Logger.log("e", "exception occured in 3mf reader: %s", e)
         try:  # Selftest - There might be more functions that should fail
         try:  # Selftest - There might be more functions that should fail