def read(self, file_name): mesh_builder = MeshBuilder() scene_node = SceneNode() if use_numpystl: self._loadWithNumpySTL(file_name, mesh_builder) else: f = open(file_name, "rb") if not self._loadBinary(mesh_builder, f): f.close() f = open(file_name, "rt") try: self._loadAscii(mesh_builder, f) except UnicodeDecodeError: return None f.close() Job.yieldThread() # Yield somewhat to ensure the GUI has time to update a bit. mesh_builder.calculateNormals(fast = True) mesh_builder.setFileName(file_name) mesh = mesh_builder.build() Logger.log("d", "Loaded a mesh with %s vertices", mesh_builder.getVertexCount()) scene_node.setMeshData(mesh) return scene_node
def _read(self, file_name): try: self.defs = {} self.shapes = [] tree = ET.parse(file_name) xml_root = tree.getroot() if xml_root.tag != "X3D": return None scale = 1000 # Default X3D unit it one meter, while Cura's is one millimeters if xml_root[0].tag == "head": for head_node in xml_root[0]: if head_node.tag == "unit" and head_node.attrib.get( "category") == "length": scale *= float(head_node.attrib["conversionFactor"]) break xml_scene = xml_root[1] else: xml_scene = xml_root[0] if xml_scene.tag != "Scene": return None self.transform = Matrix() self.transform.setByScaleFactor(scale) self.index_base = 0 # Traverse the scene tree, populate the shapes list self.processChildNodes(xml_scene) if self.shapes: builder = MeshBuilder() builder.setVertices( numpy.concatenate([shape.verts for shape in self.shapes])) builder.setIndices( numpy.concatenate([shape.faces for shape in self.shapes])) builder.calculateNormals() builder.setFileName(file_name) mesh_data = builder.build() # Manually try and get the extents of the mesh_data. This should prevent nasty NaN issues from # leaving the reader. mesh_data.getExtents() node = SceneNode() node.setMeshData(mesh_data) node.setSelectable(True) node.setName(file_name) else: return None except Exception: Logger.logException("e", "Exception in X3D reader") return None return node
def read(self, file_name): try: self.defs = {} self.shapes = [] tree = ET.parse(file_name) xml_root = tree.getroot() if xml_root.tag != "X3D": return None scale = 1000 # Default X3D unit it one meter, while Cura's is one millimeters if xml_root[0].tag == "head": for head_node in xml_root[0]: if head_node.tag == "unit" and head_node.attrib.get("category") == "length": scale *= float(head_node.attrib["conversionFactor"]) break xml_scene = xml_root[1] else: xml_scene = xml_root[0] if xml_scene.tag != "Scene": return None self.transform = Matrix() self.transform.setByScaleFactor(scale) self.index_base = 0 # Traverse the scene tree, populate the shapes list self.processChildNodes(xml_scene) if self.shapes: builder = MeshBuilder() builder.setVertices(numpy.concatenate([shape.verts for shape in self.shapes])) builder.setIndices(numpy.concatenate([shape.faces for shape in self.shapes])) builder.calculateNormals() builder.setFileName(file_name) mesh_data = builder.build() # Manually try and get the extents of the mesh_data. This should prevent nasty NaN issues from # leaving the reader. mesh_data.getExtents() node = SceneNode() node.setMeshData(mesh_data) node.setSelectable(True) node.setName(file_name) else: return None except Exception: Logger.logException("e", "Exception in X3D reader") return None return node
def _convertSavitarNodeToUMNode( self, savitar_node: Savitar.SceneNode, file_name: str = "") -> Optional[SceneNode]: """Convenience function that converts a SceneNode object (as obtained from libSavitar) to a scene node. :returns: Scene node. """ try: node_name = savitar_node.getName() node_id = savitar_node.getId() except AttributeError: Logger.log( "e", "Outdated version of libSavitar detected! Please update to the newest version!" ) node_name = "" node_id = "" if node_name == "": if file_name != "": node_name = os.path.basename(file_name) else: node_name = "Object {}".format(node_id) active_build_plate = CuraApplication.getInstance( ).getMultiBuildPlateModel().activeBuildPlate um_node = CuraSceneNode() # This adds a SettingOverrideDecorator um_node.addDecorator(BuildPlateDecorator(active_build_plate)) try: um_node.addDecorator(ConvexHullDecorator()) except: pass um_node.setName(node_name) um_node.setId(node_id) transformation = self._createMatrixFromTransformationString( savitar_node.getTransformation()) um_node.setTransformation(transformation) mesh_builder = MeshBuilder() data = numpy.fromstring( savitar_node.getMeshData().getFlatVerticesAsBytes(), dtype=numpy.float32) vertices = numpy.resize(data, (int(data.size / 3), 3)) mesh_builder.setVertices(vertices) mesh_builder.calculateNormals(fast=True) if file_name: # The filename is used to give the user the option to reload the file if it is changed on disk # It is only set for the root node of the 3mf file mesh_builder.setFileName(file_name) mesh_data = mesh_builder.build() if len(mesh_data.getVertices()): um_node.setMeshData(mesh_data) for child in savitar_node.getChildren(): child_node = self._convertSavitarNodeToUMNode(child) if child_node: um_node.addChild(child_node) if um_node.getMeshData() is None and len(um_node.getChildren()) == 0: return None settings = savitar_node.getSettings() # Add the setting override decorator, so we can add settings to this node. if settings: global_container_stack = CuraApplication.getInstance( ).getGlobalContainerStack() # Ensure the correct next container for the SettingOverride decorator is set. if global_container_stack: default_stack = ExtruderManager.getInstance().getExtruderStack( 0) if default_stack: um_node.callDecoration("setActiveExtruder", default_stack.getId()) # Get the definition & set it definition_id = ContainerTree.getInstance().machines[ global_container_stack.definition.getId( )].quality_definition um_node.callDecoration("getStack").getTop().setDefinition( definition_id) setting_container = um_node.callDecoration("getStack").getTop() known_setting_keys = um_node.callDecoration( "getStack").getAllKeys() for key in settings: setting_value = settings[key].value # Extruder_nr is a special case. if key == "extruder_nr": extruder_stack = ExtruderManager.getInstance( ).getExtruderStack(int(setting_value)) if extruder_stack: um_node.callDecoration("setActiveExtruder", extruder_stack.getId()) else: Logger.log("w", "Unable to find extruder in position %s", setting_value) continue if key in known_setting_keys: setting_container.setProperty(key, "value", setting_value) else: um_node.metadata[key] = settings[key] if len(um_node.getChildren()) > 0 and um_node.getMeshData() is None: if len(um_node.getAllChildren()) == 1: # We don't want groups of one, so move the node up one "level" child_node = um_node.getChildren()[0] parent_transformation = um_node.getLocalTransformation() child_transformation = child_node.getLocalTransformation() child_node.setTransformation( parent_transformation.multiply(child_transformation)) um_node = cast(CuraSceneNode, um_node.getChildren()[0]) else: group_decorator = GroupDecorator() um_node.addDecorator(group_decorator) um_node.setSelectable(True) if um_node.getMeshData(): # Assuming that all nodes with mesh data are printable objects # affects (auto) slicing sliceable_decorator = SliceableObjectDecorator() um_node.addDecorator(sliceable_decorator) return um_node
def _createNodeFromObject(self, object, name=""): node = SceneNode() node.setName(name) mesh_builder = MeshBuilder() vertex_list = [] components = object.find(".//3mf:components", self._namespaces) if components: for component in components: id = component.get("objectid") new_object = self._root.find( "./3mf:resources/3mf:object[@id='{0}']".format(id), self._namespaces) new_node = self._createNodeFromObject( new_object, self._base_name + "_" + str(id)) node.addChild(new_node) transform = component.get("transform") if transform is not None: new_node.setTransformation( self._createMatrixFromTransformationString(transform)) # 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() xml_settings = list(object.findall(".//cura:setting", self._namespaces)) # Add the setting override decorator, so we can add settings to this node. if xml_settings: node.addDecorator(SettingOverrideDecorator()) global_container_stack = Application.getInstance( ).getGlobalContainerStack() # Ensure the correct next container for the SettingOverride decorator is set. if global_container_stack: multi_extrusion = global_container_stack.getProperty( "machine_extruder_count", "value") > 1 # Ensure that all extruder data is reset if not multi_extrusion: default_stack_id = global_container_stack.getId() else: default_stack = ExtruderManager.getInstance( ).getExtruderStack(0) if default_stack: default_stack_id = default_stack.getId() else: default_stack_id = global_container_stack.getId() node.callDecoration("setActiveExtruder", default_stack_id) # Get the definition & set it definition = QualityManager.getInstance( ).getParentMachineDefinition( global_container_stack.getBottom()) node.callDecoration("getStack").getTop().setDefinition( definition) setting_container = node.callDecoration("getStack").getTop() for setting in xml_settings: setting_key = setting.get("key") setting_value = setting.text # Extruder_nr is a special case. if setting_key == "extruder_nr": extruder_stack = ExtruderManager.getInstance( ).getExtruderStack(int(setting_value)) if extruder_stack: node.callDecoration("setActiveExtruder", extruder_stack.getId()) else: Logger.log("w", "Unable to find extruder in position %s", setting_value) continue setting_container.setProperty(setting_key, "value", setting_value) if len(node.getChildren()) > 0: group_decorator = GroupDecorator() node.addDecorator(group_decorator) 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() # TODO: We currently do not check for normals and simply recalculate them. mesh_builder.calculateNormals(fast=True) mesh_builder.setFileName(name) mesh_data = mesh_builder.build() if len(mesh_data.getVertices()): node.setMeshData(mesh_data) node.setSelectable(True) return node
def _createNodeFromObject(self, object, name = ""): node = SceneNode() node.setName(name) mesh_builder = MeshBuilder() vertex_list = [] components = object.find(".//3mf:components", self._namespaces) if components: for component in components: id = component.get("objectid") new_object = self._root.find("./3mf:resources/3mf:object[@id='{0}']".format(id), self._namespaces) new_node = self._createNodeFromObject(new_object, self._base_name + "_" + str(id)) node.addChild(new_node) transform = component.get("transform") if transform is not None: new_node.setTransformation(self._createMatrixFromTransformationString(transform)) # 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() xml_settings = list(object.findall(".//cura:setting", self._namespaces)) # Add the setting override decorator, so we can add settings to this node. if xml_settings: node.addDecorator(SettingOverrideDecorator()) global_container_stack = Application.getInstance().getGlobalContainerStack() # Ensure the correct next container for the SettingOverride decorator is set. if global_container_stack: multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1 # Ensure that all extruder data is reset if not multi_extrusion: default_stack_id = global_container_stack.getId() else: default_stack = ExtruderManager.getInstance().getExtruderStack(0) if default_stack: default_stack_id = default_stack.getId() else: default_stack_id = global_container_stack.getId() node.callDecoration("setActiveExtruder", default_stack_id) # Get the definition & set it definition = QualityManager.getInstance().getParentMachineDefinition(global_container_stack.getBottom()) node.callDecoration("getStack").getTop().setDefinition(definition) setting_container = node.callDecoration("getStack").getTop() for setting in xml_settings: setting_key = setting.get("key") setting_value = setting.text # Extruder_nr is a special case. if setting_key == "extruder_nr": extruder_stack = ExtruderManager.getInstance().getExtruderStack(int(setting_value)) if extruder_stack: node.callDecoration("setActiveExtruder", extruder_stack.getId()) else: Logger.log("w", "Unable to find extruder in position %s", setting_value) continue setting_container.setProperty(setting_key,"value", setting_value) if len(node.getChildren()) > 0: group_decorator = GroupDecorator() node.addDecorator(group_decorator) 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() # TODO: We currently do not check for normals and simply recalculate them. mesh_builder.calculateNormals() mesh_builder.setFileName(name) mesh_data = mesh_builder.build() if len(mesh_data.getVertices()): node.setMeshData(mesh_data) node.setSelectable(True) return node
def _read(self, file_name): scene_node = None extension = os.path.splitext(file_name)[1] if extension.lower() in self._supported_extensions: vertex_list = [] normal_list = [] uv_list = [] face_list = [] scene_node = SceneNode() mesh_builder = MeshBuilder() mesh_builder.setFileName(file_name) previous_line_parts = [] f = open(file_name, "rt", encoding="utf-8") for line in f: parts = previous_line_parts + line.split() previous_line_parts = [] if len(parts) < 1: continue if parts[-1] == "\\": del parts[-1] previous_line_parts = parts continue if parts[0] == "f": parts = [i for i in map(lambda p: p.split("/"), parts)] for idx in range(1, len(parts) - 2): data = self._toAbsoluteIndex(len(vertex_list), [ int(parts[1][0]), int(parts[idx + 1][0]), int(parts[idx + 2][0]) ]) if len(parts[1]) > 1 and parts[1][1] and parts[ idx + 1][1] and parts[idx + 2][1]: data += self._toAbsoluteIndex( len(normal_list), [ int(parts[1][1]), int(parts[idx + 1][1]), int(parts[idx + 2][1]) ]) else: data += [0, 0, 0] if len(parts[1]) > 2: data += self._toAbsoluteIndex( len(uv_list), [ int(parts[1][2]), int(parts[idx + 1][2]), int(parts[idx + 2][2]) ]) else: data += [0, 0, 0] face_list.append(data) elif parts[0] == "v": vertex_list.append( [float(parts[1]), float(parts[3]), -float(parts[2])]) elif parts[0] == "vn": normal_list.append( [float(parts[1]), float(parts[3]), -float(parts[2])]) elif parts[0] == "vt": uv_list.append([float(parts[1]), float(parts[2])]) Job.yieldThread() f.close() mesh_builder.reserveVertexCount(3 * len(face_list)) num_vertices = len(vertex_list) for face in face_list: # Substract 1 from index, as obj starts counting at 1 instead of 0 i = face[0] - 1 j = face[1] - 1 k = face[2] - 1 ui = face[3] - 1 uj = face[4] - 1 uk = face[5] - 1 ni = face[6] - 1 nj = face[7] - 1 nk = face[8] - 1 if i < 0 or i >= num_vertices: i = 0 if j < 0 or j >= num_vertices: j = 0 if k < 0 or k >= num_vertices: k = 0 if ni != -1 and nj != -1 and nk != -1: mesh_builder.addFaceWithNormals( vertex_list[i][0], vertex_list[i][1], vertex_list[i][2], normal_list[ni][0], normal_list[ni][1], normal_list[ni][2], vertex_list[j][0], vertex_list[j][1], vertex_list[j][2], normal_list[nj][0], normal_list[nj][1], normal_list[nj][2], vertex_list[k][0], vertex_list[k][1], vertex_list[k][2], normal_list[nk][0], normal_list[nk][1], normal_list[nk][2]) else: mesh_builder.addFaceByPoints( vertex_list[i][0], vertex_list[i][1], vertex_list[i][2], vertex_list[j][0], vertex_list[j][1], vertex_list[j][2], vertex_list[k][0], vertex_list[k][1], vertex_list[k][2]) if ui != -1 and len(uv_list) > ui: mesh_builder.setVertexUVCoordinates( mesh_builder.getVertexCount() - 3, uv_list[ui][0], uv_list[ui][1]) if uj != -1 and len(uv_list) > uj: mesh_builder.setVertexUVCoordinates( mesh_builder.getVertexCount() - 2, uv_list[uj][0], uv_list[uj][1]) if uk != -1 and len(uv_list) > uk: mesh_builder.setVertexUVCoordinates( mesh_builder.getVertexCount() - 1, uv_list[uk][0], uv_list[uk][1]) Job.yieldThread() if not mesh_builder.hasNormals(): mesh_builder.calculateNormals(fast=True) # make sure that the mesh data is not empty if mesh_builder.getVertexCount() == 0: Logger.log("d", "File did not contain valid data, unable to read.") return None # We didn't load anything. scene_node.setMeshData(mesh_builder.build()) return scene_node
def read(self, file_name): scene_node = None extension = os.path.splitext(file_name)[1] if extension.lower() in self._supported_extensions: vertex_list = [] normal_list = [] uv_list = [] face_list = [] scene_node = SceneNode() mesh_builder = MeshBuilder() mesh_builder.setFileName(file_name) f = open(file_name, "rt") for line in f: parts = line.split() if len(parts) < 1: continue if parts[0] == "v": vertex_list.append( [float(parts[1]), float(parts[3]), -float(parts[2])]) if parts[0] == "vn": normal_list.append( [float(parts[1]), float(parts[3]), -float(parts[2])]) if parts[0] == "vt": uv_list.append([float(parts[1]), float(parts[2])]) if parts[0] == "f": parts = [i for i in map(lambda p: p.split("/"), parts)] for idx in range(1, len(parts) - 2): data = [ int(parts[1][0]), int(parts[idx + 1][0]), int(parts[idx + 2][0]) ] if len(parts[1]) > 2: data += [ int(parts[1][2]), int(parts[idx + 1][2]), int(parts[idx + 2][2]) ] if parts[1][1] and parts[idx + 1][1] and parts[idx + 2][1]: data += [ int(parts[1][1]), int(parts[idx + 1][1]), int(parts[idx + 2][1]) ] face_list.append(data) Job.yieldThread() f.close() mesh_builder.reserveVertexCount(3 * len(face_list)) num_vertices = len(vertex_list) num_normals = len(normal_list) for face in face_list: # Substract 1 from index, as obj starts counting at 1 instead of 0 i = face[0] - 1 j = face[1] - 1 k = face[2] - 1 if len(face) > 3: ni = face[3] - 1 nj = face[4] - 1 nk = face[5] - 1 else: ni = -1 nj = -1 nk = -1 if len(face) > 6: ui = face[6] - 1 uj = face[7] - 1 uk = face[8] - 1 else: ui = -1 uj = -1 uk = -1 #TODO: improve this handling, this can cause weird errors (negative indexes are relative indexes, and are not properly handled) if i < 0 or i >= num_vertices: i = 0 if j < 0 or j >= num_vertices: j = 0 if k < 0 or k >= num_vertices: k = 0 if ni != -1 and nj != -1 and nk != -1: mesh_builder.addFaceWithNormals( vertex_list[i][0], vertex_list[i][1], vertex_list[i][2], normal_list[ni][0], normal_list[ni][1], normal_list[ni][2], vertex_list[j][0], vertex_list[j][1], vertex_list[j][2], normal_list[nj][0], normal_list[nj][1], normal_list[nj][2], vertex_list[k][0], vertex_list[k][1], vertex_list[k][2], normal_list[nk][0], normal_list[nk][1], normal_list[nk][2]) else: mesh_builder.addFaceByPoints( vertex_list[i][0], vertex_list[i][1], vertex_list[i][2], vertex_list[j][0], vertex_list[j][1], vertex_list[j][2], vertex_list[k][0], vertex_list[k][1], vertex_list[k][2]) if ui != -1: mesh_builder.setVertexUVCoordinates( mesh_builder.getVertexCount() - 3, uv_list[ui][0], uv_list[ui][1]) if uj != -1: mesh_builder.setVertexUVCoordinates( mesh_builder.getVertexCount() - 2, uv_list[uj][0], uv_list[uj][1]) if uk != -1: mesh_builder.setVertexUVCoordinates( mesh_builder.getVertexCount() - 1, uv_list[uk][0], uv_list[uk][1]) Job.yieldThread() if not mesh_builder.hasNormals(): mesh_builder.calculateNormals(fast=True) scene_node.setMeshData(mesh_builder.build()) return scene_node
def test_getSetFilename(): builder = MeshBuilder() builder.setFileName("HERPDERP") assert builder.getFileName() == "HERPDERP"
def _convertSavitarNodeToUMNode( self, savitar_node: Savitar.SceneNode, file_name: str = "") -> Optional[SceneNode]: node_name = savitar_node.getName() node_id = savitar_node.getId() if node_name == "": if file_name != "": node_name = os.path.basename(file_name) else: node_name = "Object {}".format(node_id) active_build_plate = CuraApplication.getInstance( ).getMultiBuildPlateModel().activeBuildPlate um_node = CuraSceneNode() # This adds a SettingOverrideDecorator um_node.addDecorator(BuildPlateDecorator(active_build_plate)) um_node.setName(node_name) um_node.setId(node_id) transformation = self._createMatrixFromTransformationString( savitar_node.getTransformation()) um_node.setTransformation(transformation) mesh_builder = MeshBuilder() data = numpy.fromstring( savitar_node.getMeshData().getFlatVerticesAsBytes(), dtype=numpy.float32) vertices = numpy.resize(data, (int(data.size / 3), 3)) mesh_builder.setVertices(vertices) mesh_builder.calculateNormals(fast=True) if file_name: # The filename is used to give the user the option to reload the file if it is changed on disk # It is only set for the root node of the 3mf file mesh_builder.setFileName(file_name) mesh_data = mesh_builder.build() if len(mesh_data.getVertices()): um_node.setMeshData(mesh_data) for child in savitar_node.getChildren(): child_node = self._convertSavitarNodeToUMNode(child) if child_node: um_node.addChild(child_node) if um_node.getMeshData() is None and len(um_node.getChildren()) == 0: return None settings = savitar_node.getSettings() # Add the setting override decorator, so we can add settings to this node. if settings: global_container_stack = CuraApplication.getInstance( ).getGlobalContainerStack() # Ensure the correct next container for the SettingOverride decorator is set. if global_container_stack: default_stack = ExtruderManager.getInstance().getExtruderStack( 0) if default_stack: um_node.callDecoration("setActiveExtruder", default_stack.getId()) # Get the definition & set it definition_id = ContainerTree.getInstance().machines[ global_container_stack.definition.getId( )].quality_definition um_node.callDecoration("getStack").getTop().setDefinition( definition_id) setting_container = um_node.callDecoration("getStack").getTop() for key in settings: setting_value = settings[key] # Extruder_nr is a special case. if key == "extruder_nr": extruder_stack = ExtruderManager.getInstance( ).getExtruderStack(int(setting_value)) if extruder_stack: um_node.callDecoration("setActiveExtruder", extruder_stack.getId()) else: Logger.log("w", "Unable to find extruder in position %s", setting_value) continue setting_container.setProperty(key, "value", setting_value) if len(um_node.getChildren()) > 0 and um_node.getMeshData() is None: group_decorator = GroupDecorator() um_node.addDecorator(group_decorator) um_node.setSelectable(True) if um_node.getMeshData(): # Assuming that all nodes with mesh data are printable objects # affects (auto) slicing sliceable_decorator = SliceableObjectDecorator() um_node.addDecorator(sliceable_decorator) return um_node
def read(self, file_name): scene_node = None extension = os.path.splitext(file_name)[1] if extension.lower() in self._supported_extensions: vertex_list = [] normal_list = [] uv_list = [] face_list = [] scene_node = SceneNode() mesh_builder = MeshBuilder() mesh_builder.setFileName(file_name) f = open(file_name, "rt") for line in f: parts = line.split() if len(parts) < 1: continue if parts[0] == "v": vertex_list.append([float(parts[1]), float(parts[3]), -float(parts[2])]) if parts[0] == "vn": normal_list.append([float(parts[1]), float(parts[3]), -float(parts[2])]) if parts[0] == "vt": uv_list.append([float(parts[1]), float(parts[2])]) if parts[0] == "f": parts = [i for i in map(lambda p: p.split("/"), parts)] for idx in range(1, len(parts)-2): data = [int(parts[1][0]), int(parts[idx+1][0]), int(parts[idx+2][0])] if len(parts[1]) > 2: data += [int(parts[1][2]), int(parts[idx+1][2]), int(parts[idx+2][2])] if parts[1][1] and parts[idx+1][1] and parts[idx+2][1]: data += [int(parts[1][1]), int(parts[idx+1][1]), int(parts[idx+2][1])] face_list.append(data) Job.yieldThread() f.close() mesh_builder.reserveVertexCount(3 * len(face_list)) num_vertices = len(vertex_list) num_normals = len(normal_list) for face in face_list: # Substract 1 from index, as obj starts counting at 1 instead of 0 i = face[0] - 1 j = face[1] - 1 k = face[2] - 1 if len(face) > 3: ni = face[3] - 1 nj = face[4] - 1 nk = face[5] - 1 else: ni = -1 nj = -1 nk = -1 if len(face) > 6: ui = face[6] - 1 uj = face[7] - 1 uk = face[8] - 1 else: ui = -1 uj = -1 uk = -1 #TODO: improve this handling, this can cause weird errors (negative indexes are relative indexes, and are not properly handled) if i < 0 or i >= num_vertices: i = 0 if j < 0 or j >= num_vertices: j = 0 if k < 0 or k >= num_vertices: k = 0 if ni != -1 and nj != -1 and nk != -1: mesh_builder.addFaceWithNormals(vertex_list[i][0], vertex_list[i][1], vertex_list[i][2], normal_list[ni][0], normal_list[ni][1], normal_list[ni][2], vertex_list[j][0], vertex_list[j][1], vertex_list[j][2], normal_list[nj][0], normal_list[nj][1], normal_list[nj][2], vertex_list[k][0], vertex_list[k][1], vertex_list[k][2],normal_list[nk][0], normal_list[nk][1], normal_list[nk][2]) else: mesh_builder.addFaceByPoints(vertex_list[i][0], vertex_list[i][1], vertex_list[i][2], vertex_list[j][0], vertex_list[j][1], vertex_list[j][2], vertex_list[k][0], vertex_list[k][1], vertex_list[k][2]) if ui != -1: mesh_builder.setVertexUVCoordinates(mesh_builder.getVertexCount() - 3, uv_list[ui][0], uv_list[ui][1]) if uj != -1: mesh_builder.setVertexUVCoordinates(mesh_builder.getVertexCount() - 2, uv_list[uj][0], uv_list[uj][1]) if uk != -1: mesh_builder.setVertexUVCoordinates(mesh_builder.getVertexCount() - 1, uv_list[uk][0], uv_list[uk][1]) Job.yieldThread() if not mesh_builder.hasNormals(): mesh_builder.calculateNormals(fast = True) scene_node.setMeshData(mesh_builder.build()) return scene_node
def read(self, file_name): 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 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) node.setMeshData(mesh_builder.build().getTransformed(rotation)) 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) 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) except Exception as e: Logger.log("e", "exception occured in 3mf reader: %s", e) return result
def read(self, file_name): 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 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) node.setMeshData(mesh_builder.build().getTransformed(rotation)) 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) 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: result = result.getChildren()[ 0] # Only one object found, return that. except Exception as e: Logger.log("e", "exception occured in 3mf reader: %s", e) try: # Selftest - There might be more functions that should fail boundingBox = result.getBoundingBox() boundingBox.isValid() except: return None return result