def getTransformed(self, transformation: Matrix) -> "MeshData": """Transform the meshdata, center and zero position by given Matrix :param transformation: 4x4 homogeneous transformation matrix """ if self._vertices is not None: transformed_vertices = transformVertices(self._vertices, transformation) transformed_normals = transformNormals( self._normals, transformation) if self._normals is not None else None transformation_matrix = transformation.getTransposed() if self._center_position is not None: center_position = self._center_position.multiply( transformation_matrix) else: center_position = Reuse zero_position = self._zero_position.multiply(transformation_matrix) return self.set(vertices=transformed_vertices, normals=transformed_normals, center_position=center_position, zero_position=zero_position) else: return MeshData(vertices=self._vertices)
def transformNormals(normals: numpy.ndarray, transformation: Matrix) -> numpy.ndarray: """Transform an array of normals using a matrix :param normals: :type{numpy.ndarray} array of 3D normals :param transformation: a 4x4 matrix :return: :type{numpy.ndarray} the transformed normals :note This assumes the normals are untranslated unit normals, and returns the same. """ data = numpy.pad(normals, ((0, 0), (0, 1)), "constant", constant_values=(0.0, 0.0)) # Get the translation from the transformation so we can cancel it later. translation = transformation.getTranslation() # Transform the normals so they get the proper rotation data = data.dot(transformation.getTransposed().getData()) data += transformation.getData()[:, 3] data = data[:, 0:3] # Cancel the translation since normals should always go from origin to a point on the unit sphere. data[:] -= translation.getData() # Re-normalize the normals, since the transformation can contain scaling. lengths = numpy.linalg.norm(data, axis=1) lengths[lengths == 0] = 1 data[:, 0] /= lengths data[:, 1] /= lengths data[:, 2] /= lengths return data
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 transformVertices(vertices: numpy.ndarray, transformation: Matrix) -> numpy.ndarray: data = numpy.pad(vertices, ((0, 0), (0, 1)), "constant", constant_values=(0.0, 0.0)) data = data.dot(transformation.getTransposed().getData()) data += transformation.getData()[:, 3] data = data[:, 0:3] return data
def transformVertices(vertices: numpy.ndarray, transformation: Matrix) -> numpy.ndarray: """Transform an array of vertices using a matrix :param vertices: :type{numpy.ndarray} array of 3D vertices :param transformation: a 4x4 matrix :return: :type{numpy.ndarray} the transformed vertices """ data = numpy.pad(vertices, ((0, 0), (0, 1)), "constant", constant_values=(0.0, 0.0)) data = data.dot(transformation.getTransposed().getData()) data += transformation.getData()[:, 3] data = data[:, 0:3] return data
def getTransformed(self, transformation: Matrix) -> "MeshData": if self._vertices is not None: transformed_vertices = transformVertices(self._vertices, transformation) transformed_normals = transformNormals(self._normals, transformation) if self._normals is not None else None transformation_matrix = transformation.getTransposed() if self._center_position is not None: center_position = self._center_position.multiply(transformation_matrix) else: center_position = Reuse zero_position = self._zero_position.multiply(transformation_matrix) return self.set(vertices=transformed_vertices, normals=transformed_normals, center_position=center_position, zero_position=zero_position) else: return MeshData(vertices = self._vertices)
def transformNormals(normals: numpy.ndarray, transformation: Matrix) -> numpy.ndarray: data = numpy.pad(normals, ((0, 0), (0, 1)), "constant", constant_values=(0.0, 0.0)) # Get the translation from the transformation so we can cancel it later. translation = transformation.getTranslation() # Transform the normals so they get the proper rotation data = data.dot(transformation.getTransposed().getData()) data += transformation.getData()[:, 3] data = data[:, 0:3] # Cancel the translation since normals should always go from origin to a point on the unit sphere. data[:] -= translation.getData() # Re-normalize the normals, since the transformation can contain scaling. lengths = numpy.linalg.norm(data, axis = 1) data[:, 0] /= lengths data[:, 1] /= lengths data[:, 2] /= lengths return data
def read(self, file_name): result = None extension = os.path.splitext(file_name)[1] if extension.lower() == self._supported_extension: result = SceneNode() # The base object of 3mf is a zipped archive. archive = zipfile.ZipFile(file_name, 'r') 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) for object in objects: mesh = MeshData() node = SceneNode() vertex_list = [] #for vertex in object.mesh.vertices.vertex: for vertex in object.findall(".//3mf:vertex", self._namespaces): vertex_list.append([vertex.get("x"), vertex.get("y"), vertex.get("z")]) triangles = object.findall(".//3mf:triangle", self._namespaces) mesh.reserveFaceCount(len(triangles)) #for triangle in object.mesh.triangles.triangle: for triangle in triangles: v1 = int(triangle.get("v1")) v2 = int(triangle.get("v2")) v3 = int(triangle.get("v3")) mesh.addFace(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]) #TODO: We currently do not check for normals and simply recalculate them. mesh.calculateNormals() node.setMeshData(mesh) node.setSelectable(True) transformation = root.findall("./3mf:build/3mf:item[@objectid='{0}']".format(object.get("id")), self._namespaces) if transformation: transformation = transformation[0] if 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.setPosition(Vector(temp_mat.at(0,3), temp_mat.at(1,3), temp_mat.at(2,3))) temp_quaternion = Quaternion() temp_quaternion.setByMatrix(temp_mat) node.setOrientation(temp_quaternion) # Magical scale extraction S2 = temp_mat.getTransposed().multiply(temp_mat) scale_x = math.sqrt(S2.at(0,0)) scale_y = math.sqrt(S2.at(1,1)) scale_z = math.sqrt(S2.at(2,2)) node.setScale(Vector(scale_x,scale_y,scale_z)) # We use a different coordinate frame, so rotate. rotation = Quaternion.fromAngleAxis(-0.5 * math.pi, Vector(1,0,0)) node.rotate(rotation) result.addChild(node) #If there is more then one object, group them. try: if len(objects) > 1: group_decorator = GroupDecorator() result.addDecorator(group_decorator) except: pass except Exception as e: Logger.log("e" ,"exception occured in 3mf reader: %s" , e) return result
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 read(self, file_name): result = None extension = os.path.splitext(file_name)[1] if extension.lower() == self._supported_extension: result = SceneNode() # The base object of 3mf is a zipped archive. archive = zipfile.ZipFile(file_name, "r") 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 object in objects: mesh = MeshData() node = SceneNode() vertex_list = [] #for vertex in object.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.reserveFaceCount(len(triangles)) #for triangle in object.mesh.triangles.triangle: for triangle in triangles: v1 = int(triangle.get("v1")) v2 = int(triangle.get("v2")) v3 = int(triangle.get("v3")) mesh.addFace(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() #TODO: We currently do not check for normals and simply recalculate them. mesh.calculateNormals() node.setMeshData(mesh) node.setSelectable(True) transformation = root.findall( "./3mf:build/3mf:item[@objectid='{0}']".format( object.get("id")), self._namespaces) if transformation: transformation = transformation[0] if 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.setPosition( Vector(temp_mat.at(0, 3), temp_mat.at(1, 3), temp_mat.at(2, 3))) temp_quaternion = Quaternion() temp_quaternion.setByMatrix(temp_mat) node.setOrientation(temp_quaternion) # Magical scale extraction scale = temp_mat.getTransposed().multiply(temp_mat) scale_x = math.sqrt(scale.at(0, 0)) scale_y = math.sqrt(scale.at(1, 1)) scale_z = math.sqrt(scale.at(2, 2)) node.setScale(Vector(scale_x, scale_y, scale_z)) # We use a different coordinate frame, so rotate. #rotation = Quaternion.fromAngleAxis(-0.5 * math.pi, Vector(1,0,0)) #node.rotate(rotation) result.addChild(node) Job.yieldThread() #If there is more then one object, group them. try: if len(objects) > 1: group_decorator = GroupDecorator() result.addDecorator(group_decorator) except: pass except Exception as e: Logger.log("e", "exception occured in 3mf reader: %s", e) return result