def test_getConvexHullBoundaryNotPrintingMesh(convex_hull_decorator): node = SceneNode() node.addDecorator(NonPrintingDecorator()) with patch("UM.Application.Application.getInstance", MagicMock(return_value=mocked_application)): convex_hull_decorator.setNode(node) assert convex_hull_decorator.getConvexHullBoundary() is None
def test_getConvexHullPrintingMesh(convex_hull_decorator): node = SceneNode() node.addDecorator(PrintingDecorator()) with patch("UM.Application.Application.getInstance", MagicMock(return_value=mocked_application)): convex_hull_decorator.setNode(node) convex_hull_decorator._compute2DConvexHull = MagicMock(return_value = Polygon.approximatedCircle(10)) assert convex_hull_decorator.getConvexHull() == Polygon.approximatedCircle(10)
def test_getConvexHulLBoundaryPrintingMesh(convex_hull_decorator): node = SceneNode() node.addDecorator(PrintingDecorator()) with patch("UM.Application.Application.getInstance", MagicMock(return_value=mocked_application)): convex_hull_decorator.setNode(node) # Should still be None, since print sequence is not one at a time assert convex_hull_decorator.getConvexHullBoundary() is None
def test_SceneNodeDecorator(): test_node = SceneNode() test_decorator = SceneNodeDecorator() amazing_decorator = TheAmazingTestDecorator() less_amazing_decorator = TheLessAmazingTestDecorator() not_amazing_decorator = TheNotSoAmazingTestDecorator() # Replace emit with mock object test_node.decoratorsChanged.emit = MagicMock() test_decorator.clear = MagicMock() # First actual change test_node.addDecorator(test_decorator) assert len(test_node.getDecorators()) == 1 assert test_node.decoratorsChanged.emit.call_count == 1 # Adding a decorator of the same type (SceneNodeDecorator) again should not do anything. test_node.addDecorator(test_decorator) assert len(test_node.getDecorators()) == 1 assert test_node.decoratorsChanged.emit.call_count == 1 assert test_node.getDecorator(type(test_decorator)) == test_decorator # Remove the decorator again! test_node.removeDecorator(SceneNodeDecorator) assert len(test_node.getDecorators()) == 0 assert test_node.decoratorsChanged.emit.call_count == 2 assert test_decorator.clear.call_count == 1 # Ensure that the clear of the test decorator is called. # Add a number of decorators! test_node.addDecorator(amazing_decorator) test_node.addDecorator(less_amazing_decorator) test_node.addDecorator(not_amazing_decorator) assert test_node.decoratorsChanged.emit.call_count == 5 assert len(test_node.getDecorators()) == 3 assert test_node.hasDecoration("zomg") == False assert test_node.hasDecoration("theOkayDecoration") assert test_node.hasDecoration("theAmazingDecoration") # Calling the decorations with args / kwargs assert test_node.callDecoration("theOkayDecoration", None) is None assert test_node.callDecoration("theEvenMoreAmazingDecoration", "beep") == ("beep", "Wow", "so wow") assert test_node.callDecoration("theEvenMoreAmazingDecoration", "beep", much_test="Wow") == ("beep", "Wow", "Wow") # Calling decoration that is "double" assert test_node.callDecoration("theAmazingDecoration") == "Amazing!" test_node.removeDecorator(TheAmazingTestDecorator) assert test_node.callDecoration("theAmazingDecoration") == "amazing" not_amazing_decorator.clear = MagicMock() test_node.removeDecorators() # Also assure that removing all decorators also triggers the clear assert not_amazing_decorator.clear.call_count == 1 assert len(test_node.getDecorators()) == 0 assert test_node.decoratorsChanged.emit.call_count == 7 assert test_node.getDecorator(type(test_decorator)) is None
def read(self, file_name): extension = os.path.splitext(file_name)[1] if extension.lower() == self._supported_extension: layer_data = LayerData() with open(file_name, "rt") as f: layer = "" current_path_type = "" current_layer_nr = 0 poly_list = [] old_position = [0, 0, 0] current_z = 0 for line in f: if line.startswith(';TYPE:'): current_path_type = line[6:].strip() #layer_data.addPolygon(current_layer_nr,3 ,None ,5 ) elif line.startswith(';LAYER:'): current_layer_nr = int(line[7:].strip()) layer_data.addLayer(int(line[7:].strip())) elif line.startswith(';'): pass # Ignore comments else: command_type = self.getCodeInt(line, 'G') if command_type == 0 or command_type == 1: #Move command x = self.getCodeFloat(line, 'X') y = self.getCodeFloat(line, 'Y') z = self.getCodeFloat(line, 'Z') if z: current_z = z if x and y: polygon_data = numpy.zeros((4, 3)) #Square :) polygon_data[0, :] = old_position polygon_data[1, :] = old_position polygon_data[2, :] = [x, current_z, y] polygon_data[3, :] = [x, current_z, y] old_position = [x, current_z, y] if current_path_type == "SKIRT": layer_data.addPolygon( current_layer_nr, 5, polygon_data, 5) elif current_path_type == "WALL-INNER": layer_data.addPolygon( current_layer_nr, 3, polygon_data, 5) elif current_path_type == "WALL-OUTER": layer_data.addPolygon( current_layer_nr, 1, polygon_data, 5) else: layer_data.addPolygon( current_layer_nr, 2, polygon_data, 5) #e = self.getCodeFloat(line, 'E') #print(x , " ", y , " ", z, " " , e) pass layer_data.build() decorator = LayerDataDecorator() decorator.setLayerData(layer_data) new_node = SceneNode() new_node.setMeshData(MeshData()) new_node.addDecorator(decorator) new_node.setParent(self._scene.getRoot())
def test_SceneNodeDecorator(): test_node = SceneNode() test_decorator = SceneNodeDecorator() amazing_decorator = TheAmazingTestDecorator() less_amazing_decorator = TheLessAmazingTestDecorator() not_amazing_decorator = TheNotSoAmazingTestDecorator() # Replace emit with mock object test_node.decoratorsChanged.emit = MagicMock() test_decorator.clear = MagicMock() # First actual change test_node.addDecorator(test_decorator) assert len(test_node.getDecorators()) == 1 assert test_node.decoratorsChanged.emit.call_count == 1 # Adding a decorator of the same type (SceneNodeDecorator) again should not do anything. test_node.addDecorator(test_decorator) assert len(test_node.getDecorators()) == 1 assert test_node.decoratorsChanged.emit.call_count == 1 assert test_node.getDecorator(type(test_decorator)) == test_decorator # Remove the decorator again! test_node.removeDecorator(SceneNodeDecorator) assert len(test_node.getDecorators()) == 0 assert test_node.decoratorsChanged.emit.call_count == 2 assert test_decorator.clear.call_count == 1 # Ensure that the clear of the test decorator is called. # Add a number of decorators! test_node.addDecorator(amazing_decorator) test_node.addDecorator(less_amazing_decorator) test_node.addDecorator(not_amazing_decorator) assert test_node.decoratorsChanged.emit.call_count == 5 assert len(test_node.getDecorators()) == 3 assert test_node.hasDecoration("zomg") == False assert test_node.hasDecoration("theOkayDecoration") assert test_node.hasDecoration("theAmazingDecoration") # Calling the decorations with args / kwargs assert test_node.callDecoration("theOkayDecoration", None) is None assert test_node.callDecoration("theEvenMoreAmazingDecoration", "beep") == ("beep", "Wow", "so wow") assert test_node.callDecoration("theEvenMoreAmazingDecoration", "beep", much_test = "Wow") == ("beep", "Wow", "Wow") # Calling decoration that is "double" assert test_node.callDecoration("theAmazingDecoration") == "Amazing!" test_node.removeDecorator(TheAmazingTestDecorator) assert test_node.callDecoration("theAmazingDecoration") == "amazing" not_amazing_decorator.clear = MagicMock() test_node.removeDecorators() # Also assure that removing all decorators also triggers the clear assert not_amazing_decorator.clear.call_count == 1 assert len(test_node.getDecorators()) == 0 assert test_node.decoratorsChanged.emit.call_count == 7 assert test_node.getDecorator(type(test_decorator)) is None
def test_getConvexHullPrintingMesh(convex_hull_decorator): node = SceneNode() node.addDecorator(PrintingDecorator()) with patch("UM.Application.Application.getInstance", MagicMock(return_value=mocked_application)): convex_hull_decorator.setNode(node) convex_hull_decorator._compute2DConvexHull = MagicMock( return_value=Polygon.approximatedCircle(10)) assert convex_hull_decorator.getConvexHull() == Polygon.approximatedCircle( 10)
def read(self, file_name): extension = os.path.splitext(file_name)[1] if extension.lower() == self._supported_extension: layer_data = LayerData() with open (file_name,"rt") as f: layer = "" current_path_type = "" current_layer_nr = 0 poly_list = [] old_position = [0,0,0] current_z = 0 for line in f: if line.startswith(';TYPE:'): current_path_type = line[6:].strip() #layer_data.addPolygon(current_layer_nr,3 ,None ,5 ) elif line.startswith(';LAYER:'): current_layer_nr = int(line[7:].strip()) layer_data.addLayer(int(line[7:].strip())) elif line.startswith(';'): pass # Ignore comments else: command_type = self.getCodeInt(line, 'G') if command_type == 0 or command_type == 1: #Move command x = self.getCodeFloat(line, 'X') y = self.getCodeFloat(line, 'Y') z = self.getCodeFloat(line, 'Z') if z: current_z = z if x and y: polygon_data = numpy.zeros((4,3)) #Square :) polygon_data[0,:] = old_position polygon_data[1,:] = old_position polygon_data[2,:] = [x,current_z,y] polygon_data[3,:] = [x,current_z,y] old_position = [x,current_z,y] if current_path_type == "SKIRT": layer_data.addPolygon(current_layer_nr,5 ,polygon_data ,5 ) elif current_path_type == "WALL-INNER": layer_data.addPolygon(current_layer_nr,3 ,polygon_data ,5 ) elif current_path_type == "WALL-OUTER": layer_data.addPolygon(current_layer_nr,1 ,polygon_data ,5 ) else: layer_data.addPolygon(current_layer_nr,2 ,polygon_data ,5 ) #e = self.getCodeFloat(line, 'E') #print(x , " ", y , " ", z, " " , e) pass layer_data.build() decorator = LayerDataDecorator() decorator.setLayerData(layer_data) new_node = SceneNode() new_node.setMeshData(MeshData()) new_node.addDecorator(decorator) new_node.setParent(self._scene.getRoot())
def test_getConvexHulLBoundaryPrintingMeshOneAtATime(convex_hull_decorator): node = SceneNode() node.addDecorator(PrintingDecorator()) with patch("UM.Application.Application.getInstance", MagicMock(return_value=mocked_application)): convex_hull_decorator.setNode(node) convex_hull_decorator._global_stack = MagicMock() convex_hull_decorator._global_stack.getProperty = MagicMock(return_value = "one_at_a_time") # In this test we don't care for the result of the function, just that the convex hull computation is called. convex_hull_decorator._compute2DConvexHull = MagicMock() convex_hull_decorator.getConvexHullBoundary() convex_hull_decorator._compute2DConvexHull.assert_called_once_with()
def groupSelected(self): group_node = SceneNode() group_decorator = GroupDecorator() group_node.addDecorator(group_decorator) group_node.setParent(self.getController().getScene().getRoot()) for node in Selection.getAllSelectedObjects(): node.setParent(group_node) for node in group_node.getChildren(): Selection.remove(node) Selection.add(group_node)
def test_getConvexHulLBoundaryPrintingMeshOneAtATime(convex_hull_decorator): node = SceneNode() node.addDecorator(PrintingDecorator()) with patch("UM.Application.Application.getInstance", MagicMock(return_value=mocked_application)): convex_hull_decorator.setNode(node) convex_hull_decorator._global_stack = MagicMock() convex_hull_decorator._global_stack.getProperty = MagicMock( return_value="one_at_a_time") # In this test we don't care for the result of the function, just that the convex hull computation is called. convex_hull_decorator._compute2DConvexHull = MagicMock() convex_hull_decorator.getConvexHullBoundary() convex_hull_decorator._compute2DConvexHull.assert_called_once_with()
def test_deepCopy(self): node_1 = SceneNode() node_2 = SceneNode() node_1.translate(Vector(1, 2, 3)) node_1.scale(Vector(1.5, 1., 1.)) node_1.setMeshData(MeshData()) node_1.addChild(node_2) node_1.addDecorator(GroupDecorator()) copied_node = deepcopy(node_1) assert copied_node.getScale() == Vector(1.5, 1, 1) assert copied_node.getPosition() == Vector(1, 2, 3) assert len(copied_node.getChildren()) == 1 # Ensure that the decorator also got copied assert copied_node.callDecoration("isGroup")
def groupSelected(self): group_node = SceneNode() group_decorator = GroupDecorator() group_node.addDecorator(group_decorator) group_node.setParent(self.getController().getScene().getRoot()) for node in Selection.getAllSelectedObjects(): node.setParent(group_node) group_node.setCenterPosition(group_node.getBoundingBox().center) #group_node.translate(Vector(0,group_node.getBoundingBox().center.y,0)) group_node.translate(group_node.getBoundingBox().center) for node in group_node.getChildren(): Selection.remove(node) Selection.add(group_node)
def groupSelected(self): group_node = SceneNode() group_decorator = GroupDecorator() group_node.addDecorator(group_decorator) group_node.setParent(self.getController().getScene().getRoot()) center = Selection.getSelectionCenter() group_node.setPosition(center) group_node.setCenterPosition(center) for node in Selection.getAllSelectedObjects(): world = node.getWorldPosition() node.setParent(group_node) node.setPosition(world - center) for node in group_node.getChildren(): Selection.remove(node) Selection.add(group_node)
def groupSelected(self): # Create a group-node group_node = SceneNode() group_decorator = GroupDecorator() group_node.addDecorator(group_decorator) group_node.setParent(self.getController().getScene().getRoot()) group_node.setSelectable(True) center = Selection.getSelectionCenter() group_node.setPosition(center) group_node.setCenterPosition(center) # Move selected nodes into the group-node Selection.applyOperation(SetParentOperation, group_node) # Deselect individual nodes and select the group-node instead for node in group_node.getChildren(): Selection.remove(node) Selection.add(group_node)
def test_compute2DConvexHullMeshDataGrouped(convex_hull_decorator): parent_node = SceneNode() parent_node.addDecorator(GroupDecorator()) node = SceneNode() parent_node.addChild(node) mb = MeshBuilder() mb.addCube(10, 10, 10) node.setMeshData(mb.build()) convex_hull_decorator._getSettingProperty = MagicMock(return_value=0) with patch("UM.Application.Application.getInstance", MagicMock(return_value=mocked_application)): convex_hull_decorator.setNode(parent_node) with patch("cura.Settings.ExtruderManager.ExtruderManager.getInstance"): copied_decorator = copy.deepcopy(convex_hull_decorator) copied_decorator._getSettingProperty = MagicMock(return_value=0) node.addDecorator(copied_decorator) assert convex_hull_decorator._compute2DConvexHull() == Polygon([[-5.0,5.0], [5.0,5.0], [5.0,-5.0], [-5.0,-5.0]])
def test_compute2DConvexHullMeshDataGrouped(convex_hull_decorator): parent_node = SceneNode() parent_node.addDecorator(GroupDecorator()) node = SceneNode() parent_node.addChild(node) mb = MeshBuilder() mb.addCube(10, 10, 10) node.setMeshData(mb.build()) convex_hull_decorator._getSettingProperty = MagicMock(return_value=0) with patch("UM.Application.Application.getInstance", MagicMock(return_value=mocked_application)): convex_hull_decorator.setNode(parent_node) with patch( "cura.Settings.ExtruderManager.ExtruderManager.getInstance"): copied_decorator = copy.deepcopy(convex_hull_decorator) copied_decorator._getSettingProperty = MagicMock(return_value=0) node.addDecorator(copied_decorator) assert convex_hull_decorator._compute2DConvexHull() == Polygon( [[-5.0, 5.0], [5.0, 5.0], [5.0, -5.0], [-5.0, -5.0]])
def group_scene_node(): node = SceneNode() node.addDecorator(GroupDecorator()) return node
def read(self, file_name): Logger.log("d", "Preparing to load %s" % file_name) self._cancelled = False scene_node = SceneNode() # Override getBoundingBox function of the sceneNode, as this node should return a bounding box, but there is no # real data to calculate it from. scene_node.getBoundingBox = self._getNullBoundingBox gcode_list = [] self._is_layers_in_file = False Logger.log("d", "Opening file %s" % file_name) self._extruder_offsets = self._extruderOffsets() # dict with index the extruder number. can be empty last_z = 0 with open(file_name, "r") as file: file_lines = 0 current_line = 0 for line in file: file_lines += 1 gcode_list.append(line) if not self._is_layers_in_file and line[:len(self._layer_keyword)] == self._layer_keyword: self._is_layers_in_file = True file.seek(0) file_step = max(math.floor(file_lines / 100), 1) self._clearValues() self._message = Message(catalog.i18nc("@info:status", "Parsing G-code"), lifetime=0) self._message.setProgress(0) self._message.show() Logger.log("d", "Parsing %s..." % file_name) current_position = self._position(0, 0, 0, [0]) current_path = [] for line in file: if self._cancelled: Logger.log("d", "Parsing %s cancelled" % file_name) return None current_line += 1 last_z = current_position.z if current_line % file_step == 0: self._message.setProgress(math.floor(current_line / file_lines * 100)) Job.yieldThread() if len(line) == 0: continue if line.find(self._type_keyword) == 0: type = line[len(self._type_keyword):].strip() if type == "WALL-INNER": self._layer_type = LayerPolygon.InsetXType elif type == "WALL-OUTER": self._layer_type = LayerPolygon.Inset0Type elif type == "SKIN": self._layer_type = LayerPolygon.SkinType elif type == "SKIRT": self._layer_type = LayerPolygon.SkirtType elif type == "SUPPORT": self._layer_type = LayerPolygon.SupportType elif type == "FILL": self._layer_type = LayerPolygon.InfillType else: Logger.log("w", "Encountered a unknown type (%s) while parsing g-code.", type) if self._is_layers_in_file and line[:len(self._layer_keyword)] == self._layer_keyword: try: layer_number = int(line[len(self._layer_keyword):]) self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])) current_path.clear() self._layer_number = layer_number except: pass # This line is a comment. Ignore it (except for the layer_keyword) if line.startswith(";"): continue G = self._getInt(line, "G") if G is not None: current_position = self._processGCode(G, line, current_position, current_path) # < 2 is a heuristic for a movement only, that should not be counted as a layer if current_position.z > last_z and abs(current_position.z - last_z) < 2: if self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])): current_path.clear() if not self._is_layers_in_file: self._layer_number += 1 continue if line.startswith("T"): T = self._getInt(line, "T") if T is not None: self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])) current_path.clear() current_position = self._processTCode(T, line, current_position, current_path) # "Flush" leftovers if not self._is_layers_in_file and len(current_path) > 1: if self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])): self._layer_number += 1 current_path.clear() material_color_map = numpy.zeros((10, 4), dtype = numpy.float32) material_color_map[0, :] = [0.0, 0.7, 0.9, 1.0] material_color_map[1, :] = [0.7, 0.9, 0.0, 1.0] layer_mesh = self._layer_data_builder.build(material_color_map) decorator = LayerDataDecorator.LayerDataDecorator() decorator.setLayerData(layer_mesh) scene_node.addDecorator(decorator) gcode_list_decorator = GCodeListDecorator() gcode_list_decorator.setGCodeList(gcode_list) scene_node.addDecorator(gcode_list_decorator) Application.getInstance().getController().getScene().gcode_list = gcode_list Logger.log("d", "Finished parsing %s" % file_name) self._message.hide() if self._layer_number == 0: Logger.log("w", "File %s doesn't contain any valid layers" % file_name) settings = Application.getInstance().getGlobalContainerStack() machine_width = settings.getProperty("machine_width", "value") machine_depth = settings.getProperty("machine_depth", "value") if not self._center_is_zero: scene_node.setPosition(Vector(-machine_width / 2, 0, machine_depth / 2)) Logger.log("d", "Loaded %s" % file_name) if Preferences.getInstance().getValue("gcodereader/show_caution"): caution_message = Message(catalog.i18nc( "@info:generic", "Make sure the g-code is suitable for your printer and printer configuration before sending the file to it. The g-code representation may not be accurate."), lifetime=0) caution_message.show() # The "save/print" button's state is bound to the backend state. backend = Application.getInstance().getBackend() backend.backendStateChange.emit(Backend.BackendState.Disabled) return scene_node
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
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 object.mesh.triangles.triangle: 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() 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 = 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 run(self): if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView": self._progress = Message(catalog.i18nc("Layers View mode", "Layers"), 0, False, 0) self._progress.show() Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged) objectIdMap = {} new_node = SceneNode() ## Put all nodes in a dict identified by ID for node in DepthFirstIterator(self._scene.getRoot()): if type(node) is SceneNode and node.getMeshData(): if node.callDecoration("getLayerData"): #if hasattr(node.getMeshData(), "layerData"): self._scene.getRoot().removeChild(node) else: objectIdMap[id(node)] = node settings = Application.getInstance().getActiveMachine() layerHeight = settings.getSettingValueByKey("layer_height") center = None if not settings.getSettingValueByKey("machine_center_is_zero"): center = numpy.array([settings.getSettingValueByKey("machine_width") / 2, 0.0, -settings.getSettingValueByKey("machine_depth") / 2]) else: center = numpy.array([0.0, 0.0, 0.0]) if self._progress: self._progress.setProgress(2) mesh = MeshData() layer_data = LayerData.LayerData() for object in self._message.objects: try: node = objectIdMap[object.id] except KeyError: continue for layer in object.layers: layer_data.addLayer(layer.id) layer_data.setLayerHeight(layer.id, layer.height) layer_data.setLayerThickness(layer.id, layer.thickness) for polygon in layer.polygons: points = numpy.fromstring(polygon.points, dtype="i8") # Convert bytearray to numpy array points = points.reshape((-1,2)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly. points = numpy.asarray(points, dtype=numpy.float32) points /= 1000 points = numpy.insert(points, 1, (layer.height / 1000), axis = 1) points[:,2] *= -1 points -= numpy.array(center) layer_data.addPolygon(layer.id, polygon.type, points, polygon.line_width) if self._progress: self._progress.setProgress(50) # We are done processing all the layers we got from the engine, now create a mesh out of the data layer_data.build() if self._progress: self._progress.setProgress(100) #Add layerdata decorator to scene node to indicate that the node has layerdata decorator = LayerDataDecorator.LayerDataDecorator() decorator.setLayerData(layer_data) new_node.addDecorator(decorator) new_node.setMeshData(mesh) new_node.setParent(self._scene.getRoot()) view = Application.getInstance().getController().getActiveView() if view.getPluginId() == "LayerView": view.resetLayerData() if self._progress: self._progress.hide()
def run(self): if Application.getInstance().getController().getActiveView( ).getPluginId() == "LayerView": self._progress = Message( catalog.i18nc("@info:status", "Processing Layers"), 0, False, -1) self._progress.show() Job.yieldThread() if self._abort_requested: if self._progress: self._progress.hide() return Application.getInstance().getController().activeViewChanged.connect( self._onActiveViewChanged) object_id_map = {} new_node = SceneNode() ## Remove old layer data (if any) for node in DepthFirstIterator(self._scene.getRoot()): if type(node) is SceneNode and node.getMeshData(): if node.callDecoration("getLayerData"): self._scene.getRoot().removeChild(node) Job.yieldThread() if self._abort_requested: if self._progress: self._progress.hide() return settings = Application.getInstance().getMachineManager( ).getWorkingProfile() mesh = MeshData() layer_data = LayerData.LayerData() layer_count = len(self._layers) current_layer = 0 for layer in self._layers: layer_data.addLayer(layer.id) layer_data.setLayerHeight(layer.id, layer.height) layer_data.setLayerThickness(layer.id, layer.thickness) for p in range(layer.repeatedMessageCount("polygons")): polygon = layer.getRepeatedMessage("polygons", p) points = numpy.fromstring( polygon.points, dtype="i8") # Convert bytearray to numpy array points = points.reshape( (-1, 2) ) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly. # Create a new 3D-array, copy the 2D points over and insert the right height. # This uses manual array creation + copy rather than numpy.insert since this is # faster. new_points = numpy.empty((len(points), 3), numpy.float32) new_points[:, 0] = points[:, 0] new_points[:, 1] = layer.height new_points[:, 2] = -points[:, 1] new_points /= 1000 layer_data.addPolygon(layer.id, polygon.type, new_points, polygon.line_width) Job.yieldThread() Job.yieldThread() current_layer += 1 progress = (current_layer / layer_count) * 100 # TODO: Rebuild the layer data mesh once the layer has been processed. # This needs some work in LayerData so we can add the new layers instead of recreating the entire mesh. if self._abort_requested: if self._progress: self._progress.hide() return if self._progress: self._progress.setProgress(progress) # We are done processing all the layers we got from the engine, now create a mesh out of the data layer_data.build() if self._abort_requested: if self._progress: self._progress.hide() return #Add layerdata decorator to scene node to indicate that the node has layerdata decorator = LayerDataDecorator.LayerDataDecorator() decorator.setLayerData(layer_data) new_node.addDecorator(decorator) new_node.setMeshData(mesh) new_node.setParent( self._scene.getRoot()) #Note: After this we can no longer abort! if not settings.getSettingValue("machine_center_is_zero"): new_node.setPosition( Vector(-settings.getSettingValue("machine_width") / 2, 0.0, settings.getSettingValue("machine_depth") / 2)) if self._progress: self._progress.setProgress(100) view = Application.getInstance().getController().getActiveView() if view.getPluginId() == "LayerView": view.resetLayerData() if self._progress: self._progress.hide()
def run(self): if Application.getInstance().getController().getActiveView( ).getPluginId() == "LayerView": self._progress = Message( catalog.i18nc("@info:status", "Processing Layers"), 0, False, -1) self._progress.show() Job.yieldThread() if self._abort_requested: if self._progress: self._progress.hide() return Application.getInstance().getController().activeViewChanged.connect( self._onActiveViewChanged) new_node = SceneNode() ## Remove old layer data (if any) for node in DepthFirstIterator(self._scene.getRoot()): if type(node) is SceneNode and node.getMeshData(): if node.callDecoration("getLayerData"): self._scene.getRoot().removeChild(node) Job.yieldThread() if self._abort_requested: if self._progress: self._progress.hide() return mesh = MeshData() layer_data = LayerData.LayerData() layer_count = len(self._layers) # Find the minimum layer number # When using a raft, the raft layers are sent as layers < 0. Instead of allowing layers < 0, we # instead simply offset all other layers so the lowest layer is always 0. min_layer_number = 0 for layer in self._layers: if (layer.id < min_layer_number): min_layer_number = layer.id current_layer = 0 for layer in self._layers: abs_layer_number = layer.id + abs(min_layer_number) layer_data.addLayer(abs_layer_number) layer_data.setLayerHeight(abs_layer_number, layer.height) layer_data.setLayerThickness(abs_layer_number, layer.thickness) for p in range(layer.repeatedMessageCount("polygons")): polygon = layer.getRepeatedMessage("polygons", p) points = numpy.fromstring( polygon.points, dtype="i8") # Convert bytearray to numpy array points = points.reshape( (-1, 2) ) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly. # Create a new 3D-array, copy the 2D points over and insert the right height. # This uses manual array creation + copy rather than numpy.insert since this is # faster. new_points = numpy.empty((len(points), 3), numpy.float32) new_points[:, 0] = points[:, 0] new_points[:, 1] = layer.height new_points[:, 2] = -points[:, 1] new_points /= 1000 layer_data.addPolygon(abs_layer_number, polygon.type, new_points, polygon.line_width) Job.yieldThread() Job.yieldThread() current_layer += 1 progress = (current_layer / layer_count) * 100 # TODO: Rebuild the layer data mesh once the layer has been processed. # This needs some work in LayerData so we can add the new layers instead of recreating the entire mesh. if self._abort_requested: if self._progress: self._progress.hide() return if self._progress: self._progress.setProgress(progress) # We are done processing all the layers we got from the engine, now create a mesh out of the data layer_data.build() if self._abort_requested: if self._progress: self._progress.hide() return # Add LayerDataDecorator to scene node to indicate that the node has layer data decorator = LayerDataDecorator.LayerDataDecorator() decorator.setLayerData(layer_data) new_node.addDecorator(decorator) new_node.setMeshData(mesh) new_node.setParent( self._scene.getRoot()) # Note: After this we can no longer abort! settings = Application.getInstance().getGlobalContainerStack() if not settings.getProperty("machine_center_is_zero", "value"): new_node.setPosition( Vector(-settings.getProperty("machine_width", "value") / 2, 0.0, settings.getProperty("machine_depth", "value") / 2)) if self._progress: self._progress.setProgress(100) view = Application.getInstance().getController().getActiveView() if view.getPluginId() == "LayerView": view.resetLayerData() if self._progress: self._progress.hide() # Clear the unparsed layers. This saves us a bunch of memory if the Job does not get destroyed. self._layers = None
def run(self): if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView": self._progress = Message(catalog.i18nc("@info:status", "Processing Layers"), 0, False, -1) self._progress.show() Job.yieldThread() if self._abort_requested: if self._progress: self._progress.hide() return Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged) new_node = SceneNode() ## Remove old layer data (if any) for node in DepthFirstIterator(self._scene.getRoot()): if type(node) is SceneNode and node.getMeshData(): if node.callDecoration("getLayerData"): self._scene.getRoot().removeChild(node) Job.yieldThread() if self._abort_requested: if self._progress: self._progress.hide() return settings = Application.getInstance().getMachineManager().getWorkingProfile() mesh = MeshData() layer_data = LayerData.LayerData() layer_count = len(self._layers) # Find the minimum layer number # When using a raft, the raft layers are sent as layers < 0. Instead of allowing layers < 0, we # instead simply offset all other layers so the lowest layer is always 0. min_layer_number = 0 for layer in self._layers: if(layer.id < min_layer_number): min_layer_number = layer.id current_layer = 0 for layer in self._layers: abs_layer_number = layer.id + abs(min_layer_number) layer_data.addLayer(abs_layer_number) layer_data.setLayerHeight(abs_layer_number, layer.height) layer_data.setLayerThickness(abs_layer_number, layer.thickness) for p in range(layer.repeatedMessageCount("polygons")): polygon = layer.getRepeatedMessage("polygons", p) points = numpy.fromstring(polygon.points, dtype="i8") # Convert bytearray to numpy array points = points.reshape((-1,2)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly. # Create a new 3D-array, copy the 2D points over and insert the right height. # This uses manual array creation + copy rather than numpy.insert since this is # faster. new_points = numpy.empty((len(points), 3), numpy.float32) new_points[:,0] = points[:,0] new_points[:,1] = layer.height new_points[:,2] = -points[:,1] new_points /= 1000 layer_data.addPolygon(abs_layer_number, polygon.type, new_points, polygon.line_width) Job.yieldThread() Job.yieldThread() current_layer += 1 progress = (current_layer / layer_count) * 100 # TODO: Rebuild the layer data mesh once the layer has been processed. # This needs some work in LayerData so we can add the new layers instead of recreating the entire mesh. if self._abort_requested: if self._progress: self._progress.hide() return if self._progress: self._progress.setProgress(progress) # We are done processing all the layers we got from the engine, now create a mesh out of the data layer_data.build() if self._abort_requested: if self._progress: self._progress.hide() return # Add LayerDataDecorator to scene node to indicate that the node has layer data decorator = LayerDataDecorator.LayerDataDecorator() decorator.setLayerData(layer_data) new_node.addDecorator(decorator) new_node.setMeshData(mesh) new_node.setParent(self._scene.getRoot()) # Note: After this we can no longer abort! if not settings.getSettingValue("machine_center_is_zero"): new_node.setPosition(Vector(-settings.getSettingValue("machine_width") / 2, 0.0, settings.getSettingValue("machine_depth") / 2)) if self._progress: self._progress.setProgress(100) view = Application.getInstance().getController().getActiveView() if view.getPluginId() == "LayerView": view.resetLayerData() if self._progress: self._progress.hide() # Clear the unparsed layers. This saves us a bunch of memory if the Job does not get destroyed. self._layers = None
def run(self): start_time = time() if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView": self._progress = Message(catalog.i18nc("@info:status", "Processing Layers"), 0, False, -1) self._progress.show() Job.yieldThread() if self._abort_requested: if self._progress: self._progress.hide() return Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged) new_node = SceneNode() ## Remove old layer data (if any) for node in DepthFirstIterator(self._scene.getRoot()): if type(node) is SceneNode and node.getMeshData(): if node.callDecoration("getLayerData"): self._scene.getRoot().removeChild(node) Job.yieldThread() if self._abort_requested: if self._progress: self._progress.hide() return mesh = MeshData() layer_data = LayerDataBuilder.LayerDataBuilder() layer_count = len(self._layers) # Find the minimum layer number # When using a raft, the raft layers are sent as layers < 0. Instead of allowing layers < 0, we # instead simply offset all other layers so the lowest layer is always 0. min_layer_number = 0 for layer in self._layers: if(layer.id < min_layer_number): min_layer_number = layer.id current_layer = 0 for layer in self._layers: abs_layer_number = layer.id + abs(min_layer_number) layer_data.addLayer(abs_layer_number) this_layer = layer_data.getLayer(abs_layer_number) layer_data.setLayerHeight(abs_layer_number, layer.height) layer_data.setLayerThickness(abs_layer_number, layer.thickness) for p in range(layer.repeatedMessageCount("path_segment")): polygon = layer.getRepeatedMessage("path_segment", p) extruder = polygon.extruder line_types = numpy.fromstring(polygon.line_type, dtype="u1") # Convert bytearray to numpy array line_types = line_types.reshape((-1,1)) points = numpy.fromstring(polygon.points, dtype="f4") # Convert bytearray to numpy array if polygon.point_type == 0: # Point2D points = points.reshape((-1,2)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly. else: # Point3D points = points.reshape((-1,3)) line_widths = numpy.fromstring(polygon.line_width, dtype="f4") # Convert bytearray to numpy array line_widths = line_widths.reshape((-1,1)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly. # Create a new 3D-array, copy the 2D points over and insert the right height. # This uses manual array creation + copy rather than numpy.insert since this is # faster. new_points = numpy.empty((len(points), 3), numpy.float32) if polygon.point_type == 0: # Point2D new_points[:,0] = points[:,0] new_points[:,1] = layer.height/1000 # layer height value is in backend representation new_points[:,2] = -points[:,1] else: # Point3D new_points[:,0] = points[:,0] new_points[:,1] = points[:,2] new_points[:,2] = -points[:,1] this_poly = LayerPolygon.LayerPolygon(layer_data, extruder, line_types, new_points, line_widths) this_poly.buildCache() this_layer.polygons.append(this_poly) Job.yieldThread() Job.yieldThread() current_layer += 1 progress = (current_layer / layer_count) * 99 # TODO: Rebuild the layer data mesh once the layer has been processed. # This needs some work in LayerData so we can add the new layers instead of recreating the entire mesh. if self._abort_requested: if self._progress: self._progress.hide() return if self._progress: self._progress.setProgress(progress) # We are done processing all the layers we got from the engine, now create a mesh out of the data layer_mesh = layer_data.build() if self._abort_requested: if self._progress: self._progress.hide() return # Add LayerDataDecorator to scene node to indicate that the node has layer data decorator = LayerDataDecorator.LayerDataDecorator() decorator.setLayerData(layer_mesh) new_node.addDecorator(decorator) new_node.setMeshData(mesh) # Set build volume as parent, the build volume can move as a result of raft settings. # It makes sense to set the build volume as parent: the print is actually printed on it. new_node_parent = Application.getInstance().getBuildVolume() new_node.setParent(new_node_parent) # Note: After this we can no longer abort! settings = Application.getInstance().getGlobalContainerStack() if not settings.getProperty("machine_center_is_zero", "value"): new_node.setPosition(Vector(-settings.getProperty("machine_width", "value") / 2, 0.0, settings.getProperty("machine_depth", "value") / 2)) if self._progress: self._progress.setProgress(100) view = Application.getInstance().getController().getActiveView() if view.getPluginId() == "LayerView": view.resetLayerData() if self._progress: self._progress.hide() # Clear the unparsed layers. This saves us a bunch of memory if the Job does not get destroyed. self._layers = None Logger.log("d", "Processing layers took %s seconds", time() - start_time)
def read(self, file_name): Logger.log("d", "Preparing to load %s" % file_name) self._cancelled = False scene_node = SceneNode() scene_node.getBoundingBox = self._getNullBoundingBox # Manually set bounding box, because mesh doesn't have mesh data glist = [] self._is_layers_in_file = False Logger.log("d", "Opening file %s" % file_name) with open(file_name, "r") as file: file_lines = 0 current_line = 0 for line in file: file_lines += 1 glist.append(line) if not self._is_layers_in_file and line[:len( self._layer_keyword)] == self._layer_keyword: self._is_layers_in_file = True file.seek(0) file_step = max(math.floor(file_lines / 100), 1) self._clearValues() self._message = Message(catalog.i18nc("@info:status", "Parsing G-code"), lifetime=0) self._message.setProgress(0) self._message.show() Logger.log("d", "Parsing %s" % file_name) current_position = self._position(0, 0, 0, [0]) current_path = [] for line in file: if self._cancelled: Logger.log("d", "Parsing %s cancelled" % file_name) return None current_line += 1 if current_line % file_step == 0: self._message.setProgress( math.floor(current_line / file_lines * 100)) if len(line) == 0: continue if line.find(self._type_keyword) == 0: type = line[len(self._type_keyword):].strip() if type == "WALL-INNER": self._layer_type = LayerPolygon.InsetXType elif type == "WALL-OUTER": self._layer_type = LayerPolygon.Inset0Type elif type == "SKIN": self._layer_type = LayerPolygon.SkinType elif type == "SKIRT": self._layer_type = LayerPolygon.SkirtType elif type == "SUPPORT": self._layer_type = LayerPolygon.SupportType elif type == "FILL": self._layer_type = LayerPolygon.InfillType if self._is_layers_in_file and line[:len( self._layer_keyword)] == self._layer_keyword: try: layer_number = int(line[len(self._layer_keyword):]) self._createPolygon(current_position[2], current_path) current_path.clear() self._layer = layer_number except: pass if line[0] == ";": continue G = self._getInt(line, "G") if G is not None: current_position = self._processGCode( G, line, current_position, current_path) T = self._getInt(line, "T") if T is not None: current_position = self._processTCode( T, line, current_position, current_path) if not self._is_layers_in_file and len( current_path) > 1 and current_position[2] > 0: if self._createPolygon(current_position[2], current_path): self._layer += 1 current_path.clear() layer_mesh = self._layer_data_builder.build() decorator = LayerDataDecorator.LayerDataDecorator() decorator.setLayerData(layer_mesh) scene_node.addDecorator(decorator) gcode_list_decorator = GCodeListDecorator() gcode_list_decorator.setGCodeList(glist) scene_node.addDecorator(gcode_list_decorator) Logger.log("d", "Finished parsing %s" % file_name) self._message.hide() if self._layer == 0: Logger.log("w", "File %s doesn't contain any valid layers" % file_name) settings = Application.getInstance().getGlobalContainerStack() machine_width = settings.getProperty("machine_width", "value") machine_depth = settings.getProperty("machine_depth", "value") if not self._center_is_zero: scene_node.setPosition( Vector(-machine_width / 2, 0, machine_depth / 2)) Logger.log("d", "Loaded %s" % file_name) return scene_node
def _convertSavitarNodeToUMNode(self, savitar_node): um_node = SceneNode() transformation = self._createMatrixFromTransformationString(savitar_node.getTransformation()) um_node.setTransformation(transformation) mesh_builder = MeshBuilder() data = numpy.fromstring(savitar_node.getMeshData().getFlatVerticesAsBytes(), dtype=numpy.float32) vertices = numpy.resize(data, (int(data.size / 3), 3)) mesh_builder.setVertices(vertices) mesh_builder.calculateNormals(fast=True) mesh_data = mesh_builder.build() if len(mesh_data.getVertices()): um_node.setMeshData(mesh_data) for child in savitar_node.getChildren(): child_node = self._convertSavitarNodeToUMNode(child) if child_node: um_node.addChild(child_node) if um_node.getMeshData() is None and len(um_node.getChildren()) == 0: return None settings = savitar_node.getSettings() # Add the setting override decorator, so we can add settings to this node. if settings: um_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() um_node.callDecoration("setActiveExtruder", default_stack_id) # Get the definition & set it definition = QualityManager.getInstance().getParentMachineDefinition(global_container_stack.getBottom()) um_node.callDecoration("getStack").getTop().setDefinition(definition) 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: 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 slicable_scene_node(): node = SceneNode() node.addDecorator(SliceableObjectDecorator()) 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 run(self): if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView": self._progress = Message(catalog.i18nc("@info:status", "Processing Layers"), 0, False, -1) self._progress.show() Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged) object_id_map = {} new_node = SceneNode() ## Put all nodes in a dict identified by ID for node in DepthFirstIterator(self._scene.getRoot()): if type(node) is SceneNode and node.getMeshData(): if node.callDecoration("getLayerData"): self._scene.getRoot().removeChild(node) else: object_id_map[id(node)] = node Job.yieldThread() settings = Application.getInstance().getMachineManager().getActiveProfile() center = None if not settings.getSettingValue("machine_center_is_zero"): center = numpy.array([settings.getSettingValue("machine_width") / 2, 0.0, -settings.getSettingValue("machine_depth") / 2]) else: center = numpy.array([0.0, 0.0, 0.0]) mesh = MeshData() layer_data = LayerData.LayerData() layer_count = 0 for object in self._message.objects: layer_count += len(object.layers) current_layer = 0 for object in self._message.objects: try: node = object_id_map[object.id] except KeyError: continue for layer in object.layers: layer_data.addLayer(layer.id) layer_data.setLayerHeight(layer.id, layer.height) layer_data.setLayerThickness(layer.id, layer.thickness) for polygon in layer.polygons: points = numpy.fromstring(polygon.points, dtype="i8") # Convert bytearray to numpy array points = points.reshape((-1,2)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly. points = numpy.asarray(points, dtype=numpy.float32) points /= 1000 points = numpy.insert(points, 1, (layer.height / 1000), axis = 1) points[:,2] *= -1 points -= center layer_data.addPolygon(layer.id, polygon.type, points, polygon.line_width) Job.yieldThread() current_layer += 1 progress = (current_layer / layer_count) * 100 # TODO: Rebuild the layer data mesh once the layer has been processed. # This needs some work in LayerData so we can add the new layers instead of recreating the entire mesh. if self._progress: self._progress.setProgress(progress) # We are done processing all the layers we got from the engine, now create a mesh out of the data layer_data.build() #Add layerdata decorator to scene node to indicate that the node has layerdata decorator = LayerDataDecorator.LayerDataDecorator() decorator.setLayerData(layer_data) new_node.addDecorator(decorator) new_node.setMeshData(mesh) new_node.setParent(self._scene.getRoot()) if self._progress: self._progress.setProgress(100) view = Application.getInstance().getController().getActiveView() if view.getPluginId() == "LayerView": view.resetLayerData() if self._progress: self._progress.hide()
def processGCodeFile(self, file_name): Logger.log("d", "Preparing to load %s" % file_name) self._cancelled = False # We obtain the filament diameter from the selected printer to calculate line widths self._filament_diameter = Application.getInstance( ).getGlobalContainerStack().getProperty("material_diameter", "value") scene_node = SceneNode() # Override getBoundingBox function of the sceneNode, as this node should return a bounding box, but there is no # real data to calculate it from. scene_node.getBoundingBox = self._getNullBoundingBox gcode_list = [] self._is_layers_in_file = False Logger.log("d", "Opening file %s" % file_name) self._extruder_offsets = self._extruderOffsets( ) # dict with index the extruder number. can be empty with open(file_name, "r") as file: file_lines = 0 current_line = 0 for line in file: file_lines += 1 gcode_list.append(line) if not self._is_layers_in_file and line[:len( self._layer_keyword)] == self._layer_keyword: self._is_layers_in_file = True file.seek(0) file_step = max(math.floor(file_lines / 100), 1) self._clearValues() self._message = Message(catalog.i18nc("@info:status", "Parsing G-code"), lifetime=0, title=catalog.i18nc( "@info:title", "G-code Details")) self._message.setProgress(0) self._message.show() Logger.log("d", "Parsing %s..." % file_name) current_position = self._position(0, 0, 0, 0, [0]) current_path = [] min_layer_number = 0 negative_layers = 0 previous_layer = 0 for line in file: if self._cancelled: Logger.log("d", "Parsing %s cancelled" % file_name) return None current_line += 1 if current_line % file_step == 0: self._message.setProgress( math.floor(current_line / file_lines * 100)) Job.yieldThread() if len(line) == 0: continue if line.find(self._type_keyword) == 0: type = line[len(self._type_keyword):].strip() if type == "WALL-INNER": self._layer_type = LayerPolygon.InsetXType elif type == "WALL-OUTER": self._layer_type = LayerPolygon.Inset0Type elif type == "SKIN": self._layer_type = LayerPolygon.SkinType elif type == "SKIRT": self._layer_type = LayerPolygon.SkirtType elif type == "SUPPORT": self._layer_type = LayerPolygon.SupportType elif type == "FILL": self._layer_type = LayerPolygon.InfillType else: Logger.log( "w", "Encountered a unknown type (%s) while parsing g-code.", type) # When the layer change is reached, the polygon is computed so we have just one layer per layer per extruder if self._is_layers_in_file and line[:len( self._layer_keyword)] == self._layer_keyword: try: layer_number = int(line[len(self._layer_keyword):]) self._createPolygon( self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])) current_path.clear() # When using a raft, the raft layers are stored as layers < 0, it mimics the same behavior # as in ProcessSlicedLayersJob if layer_number < min_layer_number: min_layer_number = layer_number if layer_number < 0: layer_number += abs(min_layer_number) negative_layers += 1 else: layer_number += negative_layers # In case there is a gap in the layer count, empty layers are created for empty_layer in range(previous_layer + 1, layer_number): self._createEmptyLayer(empty_layer) self._layer_number = layer_number previous_layer = layer_number except: pass # This line is a comment. Ignore it (except for the layer_keyword) if line.startswith(";"): continue G = self._getInt(line, "G") if G is not None: # When find a movement, the new posistion is calculated and added to the current_path, but # don't need to create a polygon until the end of the layer current_position = self.processGCode( G, line, current_position, current_path) continue # When changing the extruder, the polygon with the stored paths is computed if line.startswith("T"): T = self._getInt(line, "T") if T is not None: self._createPolygon( self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])) current_path.clear() current_position = self.processTCode( T, line, current_position, current_path) if line.startswith("M"): M = self._getInt(line, "M") self.processMCode(M, line, current_position, current_path) # "Flush" leftovers. Last layer paths are still stored if len(current_path) > 1: if self._createPolygon( self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])): self._layer_number += 1 current_path.clear() material_color_map = numpy.zeros((10, 4), dtype=numpy.float32) material_color_map[0, :] = [0.0, 0.7, 0.9, 1.0] material_color_map[1, :] = [0.7, 0.9, 0.0, 1.0] layer_mesh = self._layer_data_builder.build(material_color_map) decorator = LayerDataDecorator.LayerDataDecorator() decorator.setLayerData(layer_mesh) scene_node.addDecorator(decorator) gcode_list_decorator = GCodeListDecorator() gcode_list_decorator.setGCodeList(gcode_list) scene_node.addDecorator(gcode_list_decorator) Application.getInstance().getController().getScene( ).gcode_list = gcode_list Logger.log("d", "Finished parsing %s" % file_name) self._message.hide() if self._layer_number == 0: Logger.log("w", "File %s doesn't contain any valid layers" % file_name) settings = Application.getInstance().getGlobalContainerStack() machine_width = settings.getProperty("machine_width", "value") machine_depth = settings.getProperty("machine_depth", "value") if not self._center_is_zero: scene_node.setPosition( Vector(-machine_width / 2, 0, machine_depth / 2)) Logger.log("d", "Loaded %s" % file_name) if Preferences.getInstance().getValue("gcodereader/show_caution"): caution_message = Message(catalog.i18nc( "@info:generic", "Make sure the g-code is suitable for your printer and printer configuration before sending the file to it. The g-code representation may not be accurate." ), lifetime=0, title=catalog.i18nc( "@info:title", "G-code Details")) caution_message.show() # The "save/print" button's state is bound to the backend state. backend = Application.getInstance().getBackend() backend.backendStateChange.emit(Backend.BackendState.Disabled) return scene_node
def read(self, file_name): Logger.log("d", "Preparing to load %s" % file_name) self._cancelled = False scene_node = SceneNode() # Override getBoundingBox function of the sceneNode, as this node should return a bounding box, but there is no # real data to calculate it from. scene_node.getBoundingBox = self._getNullBoundingBox gcode_list = [] self._is_layers_in_file = False Logger.log("d", "Opening file %s" % file_name) self._extruder_offsets = self._extruderOffsets( ) # dict with index the extruder number. can be empty last_z = 0 with open(file_name, "r") as file: file_lines = 0 current_line = 0 for line in file: file_lines += 1 gcode_list.append(line) if not self._is_layers_in_file and line[:len( self._layer_keyword)] == self._layer_keyword: self._is_layers_in_file = True file.seek(0) file_step = max(math.floor(file_lines / 100), 1) self._clearValues() self._message = Message(catalog.i18nc("@info:status", "Parsing G-code"), lifetime=0) self._message.setProgress(0) self._message.show() Logger.log("d", "Parsing %s..." % file_name) current_position = self._position(0, 0, 0, [0]) current_path = [] for line in file: if self._cancelled: Logger.log("d", "Parsing %s cancelled" % file_name) return None current_line += 1 last_z = current_position.z if current_line % file_step == 0: self._message.setProgress( math.floor(current_line / file_lines * 100)) Job.yieldThread() if len(line) == 0: continue if line.find(self._type_keyword) == 0: type = line[len(self._type_keyword):].strip() if type == "WALL-INNER": self._layer_type = LayerPolygon.InsetXType elif type == "WALL-OUTER": self._layer_type = LayerPolygon.Inset0Type elif type == "SKIN": self._layer_type = LayerPolygon.SkinType elif type == "SKIRT": self._layer_type = LayerPolygon.SkirtType elif type == "SUPPORT": self._layer_type = LayerPolygon.SupportType elif type == "FILL": self._layer_type = LayerPolygon.InfillType else: Logger.log( "w", "Encountered a unknown type (%s) while parsing g-code.", type) if self._is_layers_in_file and line[:len( self._layer_keyword)] == self._layer_keyword: try: layer_number = int(line[len(self._layer_keyword):]) self._createPolygon( self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])) current_path.clear() self._layer_number = layer_number except: pass # This line is a comment. Ignore it (except for the layer_keyword) if line.startswith(";"): continue G = self._getInt(line, "G") if G is not None: current_position = self._processGCode( G, line, current_position, current_path) # < 2 is a heuristic for a movement only, that should not be counted as a layer if current_position.z > last_z and abs(current_position.z - last_z) < 2: if self._createPolygon( self._current_layer_thickness, current_path, self._extruder_offsets.get( self._extruder_number, [0, 0])): current_path.clear() if not self._is_layers_in_file: self._layer_number += 1 continue if line.startswith("T"): T = self._getInt(line, "T") if T is not None: self._createPolygon( self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])) current_path.clear() current_position = self._processTCode( T, line, current_position, current_path) # "Flush" leftovers if not self._is_layers_in_file and len(current_path) > 1: if self._createPolygon( self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])): self._layer_number += 1 current_path.clear() material_color_map = numpy.zeros((10, 4), dtype=numpy.float32) material_color_map[0, :] = [0.0, 0.7, 0.9, 1.0] material_color_map[1, :] = [0.7, 0.9, 0.0, 1.0] layer_mesh = self._layer_data_builder.build(material_color_map) decorator = LayerDataDecorator.LayerDataDecorator() decorator.setLayerData(layer_mesh) scene_node.addDecorator(decorator) gcode_list_decorator = GCodeListDecorator() gcode_list_decorator.setGCodeList(gcode_list) scene_node.addDecorator(gcode_list_decorator) Application.getInstance().getController().getScene( ).gcode_list = gcode_list Logger.log("d", "Finished parsing %s" % file_name) self._message.hide() if self._layer_number == 0: Logger.log("w", "File %s doesn't contain any valid layers" % file_name) settings = Application.getInstance().getGlobalContainerStack() machine_width = settings.getProperty("machine_width", "value") machine_depth = settings.getProperty("machine_depth", "value") if not self._center_is_zero: scene_node.setPosition( Vector(-machine_width / 2, 0, machine_depth / 2)) Logger.log("d", "Loaded %s" % file_name) if Preferences.getInstance().getValue("gcodereader/show_caution"): caution_message = Message(catalog.i18nc( "@info:generic", "Make sure the g-code is suitable for your printer and printer configuration before sending the file to it. The g-code representation may not be accurate." ), lifetime=0) caution_message.show() # The "save/print" button's state is bound to the backend state. backend = Application.getInstance().getBackend() backend.backendStateChange.emit(Backend.BackendState.Disabled) return scene_node
def _convertSavitarNodeToUMNode(self, savitar_node): um_node = SceneNode() transformation = self._createMatrixFromTransformationString( savitar_node.getTransformation()) um_node.setTransformation(transformation) mesh_builder = MeshBuilder() data = numpy.fromstring( savitar_node.getMeshData().getFlatVerticesAsBytes(), dtype=numpy.float32) vertices = numpy.resize(data, (int(data.size / 3), 3)) mesh_builder.setVertices(vertices) mesh_builder.calculateNormals(fast=True) mesh_data = mesh_builder.build() if len(mesh_data.getVertices()): um_node.setMeshData(mesh_data) for child in savitar_node.getChildren(): child_node = self._convertSavitarNodeToUMNode(child) if child_node: um_node.addChild(child_node) if um_node.getMeshData() is None and len(um_node.getChildren()) == 0: return None settings = savitar_node.getSettings() # Add the setting override decorator, so we can add settings to this node. if settings: um_node.addDecorator(SettingOverrideDecorator()) global_container_stack = Application.getInstance( ).getGlobalContainerStack() # Ensure the correct next container for the SettingOverride decorator is set. if global_container_stack: default_stack = ExtruderManager.getInstance().getExtruderStack( 0) if default_stack: um_node.callDecoration("setActiveExtruder", default_stack.getId()) # Get the definition & set it definition = QualityManager.getInstance( ).getParentMachineDefinition( global_container_stack.getBottom()) um_node.callDecoration("getStack").getTop().setDefinition( definition.getId()) 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: 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 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
def run(self): if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView": self._progress = Message(catalog.i18nc("@info:status", "Processing Layers"), 0, False, -1) self._progress.show() Job.yieldThread() if self._abort_requested: if self._progress: self._progress.hide() return Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged) object_id_map = {} new_node = SceneNode() ## Put all nodes in a dictionary identified by ID for node in DepthFirstIterator(self._scene.getRoot()): if type(node) is SceneNode and node.getMeshData(): if node.callDecoration("getLayerData"): self._scene.getRoot().removeChild(node) else: object_id_map[id(node)] = node Job.yieldThread() if self._abort_requested: if self._progress: self._progress.hide() return settings = Application.getInstance().getMachineManager().getWorkingProfile() mesh = MeshData() layer_data = LayerData.LayerData() layer_count = 0 for i in range(self._message.repeatedMessageCount("objects")): layer_count += self._message.getRepeatedMessage("objects", i).repeatedMessageCount("layers") current_layer = 0 for object_position in range(self._message.repeatedMessageCount("objects")): current_object = self._message.getRepeatedMessage("objects", object_position) try: node = object_id_map[current_object.id] except KeyError: continue for l in range(current_object.repeatedMessageCount("layers")): layer = current_object.getRepeatedMessage("layers", l) layer_data.addLayer(layer.id) layer_data.setLayerHeight(layer.id, layer.height) layer_data.setLayerThickness(layer.id, layer.thickness) for p in range(layer.repeatedMessageCount("polygons")): polygon = layer.getRepeatedMessage("polygons", p) points = numpy.fromstring(polygon.points, dtype="i8") # Convert bytearray to numpy array points = points.reshape((-1,2)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly. # Create a new 3D-array, copy the 2D points over and insert the right height. # This uses manual array creation + copy rather than numpy.insert since this is # faster. new_points = numpy.empty((len(points), 3), numpy.float32) new_points[:,0] = points[:,0] new_points[:,1] = layer.height new_points[:,2] = -points[:,1] new_points /= 1000 layer_data.addPolygon(layer.id, polygon.type, new_points, polygon.line_width) Job.yieldThread() Job.yieldThread() current_layer += 1 progress = (current_layer / layer_count) * 100 # TODO: Rebuild the layer data mesh once the layer has been processed. # This needs some work in LayerData so we can add the new layers instead of recreating the entire mesh. if self._abort_requested: if self._progress: self._progress.hide() return if self._progress: self._progress.setProgress(progress) # We are done processing all the layers we got from the engine, now create a mesh out of the data layer_data.build() if self._abort_requested: if self._progress: self._progress.hide() return #Add layerdata decorator to scene node to indicate that the node has layerdata decorator = LayerDataDecorator.LayerDataDecorator() decorator.setLayerData(layer_data) new_node.addDecorator(decorator) new_node.setMeshData(mesh) new_node.setParent(self._scene.getRoot()) #Note: After this we can no longer abort! if not settings.getSettingValue("machine_center_is_zero"): new_node.setPosition(Vector(-settings.getSettingValue("machine_width") / 2, 0.0, settings.getSettingValue("machine_depth") / 2)) if self._progress: self._progress.setProgress(100) view = Application.getInstance().getController().getActiveView() if view.getPluginId() == "LayerView": view.resetLayerData() if self._progress: self._progress.hide()
def read(self, file_name): Logger.log("d", "Preparing to load %s" % file_name) self._cancelled = False scene_node = SceneNode() # Override getBoundingBox function of the sceneNode, as this node should return a bounding box, but there is no # real data to calculate it from. scene_node.getBoundingBox = self._getNullBoundingBox gcode_list = [] self._is_layers_in_file = False Logger.log("d", "Opening file %s" % file_name) with open(file_name, "r") as file: file_lines = 0 current_line = 0 for line in file: file_lines += 1 gcode_list.append(line) if not self._is_layers_in_file and line[:len( self._layer_keyword)] == self._layer_keyword: self._is_layers_in_file = True file.seek(0) file_step = max(math.floor(file_lines / 100), 1) self._clearValues() self._message = Message(catalog.i18nc("@info:status", "Parsing G-code"), lifetime=0) self._message.setProgress(0) self._message.show() Logger.log("d", "Parsing %s" % file_name) current_position = self._position(0, 0, 0, [0]) current_path = [] for line in file: if self._cancelled: Logger.log("d", "Parsing %s cancelled" % file_name) return None current_line += 1 if current_line % file_step == 0: self._message.setProgress( math.floor(current_line / file_lines * 100)) if len(line) == 0: continue if line.find(self._type_keyword) == 0: type = line[len(self._type_keyword):].strip() if type == "WALL-INNER": self._layer_type = LayerPolygon.InsetXType elif type == "WALL-OUTER": self._layer_type = LayerPolygon.Inset0Type elif type == "SKIN": self._layer_type = LayerPolygon.SkinType elif type == "SKIRT": self._layer_type = LayerPolygon.SkirtType elif type == "SUPPORT": self._layer_type = LayerPolygon.SupportType elif type == "FILL": self._layer_type = LayerPolygon.InfillType else: Logger.log( "w", "Encountered a unknown type (%s) while parsing g-code.", type) if self._is_layers_in_file and line[:len( self._layer_keyword)] == self._layer_keyword: try: layer_number = int(line[len(self._layer_keyword):]) self._createPolygon(current_position[2], current_path) current_path.clear() self._layer_number = layer_number except: pass # This line is a comment. Ignore it. if line.startswith(";"): continue G = self._getInt(line, "G") if G is not None: current_position = self._processGCode( G, line, current_position, current_path) T = self._getInt(line, "T") if T is not None: current_position = self._processTCode( T, line, current_position, current_path) if not self._is_layers_in_file and len( current_path) > 1 and current_position[2] > 0: if self._createPolygon(current_position[2], current_path): self._layer_number += 1 current_path.clear() material_color_map = numpy.zeros((10, 4), dtype=numpy.float32) material_color_map[0, :] = [0.0, 0.7, 0.9, 1.0] material_color_map[1, :] = [0.7, 0.9, 0.0, 1.0] layer_mesh = self._layer_data_builder.build(material_color_map) decorator = LayerDataDecorator.LayerDataDecorator() decorator.setLayerData(layer_mesh) scene_node.addDecorator(decorator) gcode_list_decorator = GCodeListDecorator() gcode_list_decorator.setGCodeList(gcode_list) scene_node.addDecorator(gcode_list_decorator) Logger.log("d", "Finished parsing %s" % file_name) self._message.hide() if self._layer_number == 0: Logger.log("w", "File %s doesn't contain any valid layers" % file_name) settings = Application.getInstance().getGlobalContainerStack() machine_width = settings.getProperty("machine_width", "value") machine_depth = settings.getProperty("machine_depth", "value") if not self._center_is_zero: scene_node.setPosition( Vector(-machine_width / 2, 0, machine_depth / 2)) Logger.log("d", "Loaded %s" % file_name) return scene_node
def run(self): start_time = time() if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView": self._progress = Message(catalog.i18nc("@info:status", "Processing Layers"), 0, False, -1) self._progress.show() Job.yieldThread() if self._abort_requested: if self._progress: self._progress.hide() return Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged) new_node = SceneNode() ## Remove old layer data (if any) for node in DepthFirstIterator(self._scene.getRoot()): if node.callDecoration("getLayerData"): node.getParent().removeChild(node) break if self._abort_requested: if self._progress: self._progress.hide() return # Force garbage collection. # For some reason, Python has a tendency to keep the layer data # in memory longer than needed. Forcing the GC to run here makes # sure any old layer data is really cleaned up before adding new. gc.collect() mesh = MeshData() layer_data = LayerDataBuilder.LayerDataBuilder() layer_count = len(self._layers) # Find the minimum layer number # When using a raft, the raft layers are sent as layers < 0. Instead of allowing layers < 0, we # instead simply offset all other layers so the lowest layer is always 0. min_layer_number = 0 for layer in self._layers: if layer.id < min_layer_number: min_layer_number = layer.id current_layer = 0 for layer in self._layers: abs_layer_number = layer.id + abs(min_layer_number) layer_data.addLayer(abs_layer_number) this_layer = layer_data.getLayer(abs_layer_number) layer_data.setLayerHeight(abs_layer_number, layer.height) for p in range(layer.repeatedMessageCount("path_segment")): polygon = layer.getRepeatedMessage("path_segment", p) extruder = polygon.extruder line_types = numpy.fromstring(polygon.line_type, dtype="u1") # Convert bytearray to numpy array line_types = line_types.reshape((-1,1)) points = numpy.fromstring(polygon.points, dtype="f4") # Convert bytearray to numpy array if polygon.point_type == 0: # Point2D points = points.reshape((-1,2)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly. else: # Point3D points = points.reshape((-1,3)) line_widths = numpy.fromstring(polygon.line_width, dtype="f4") # Convert bytearray to numpy array line_widths = line_widths.reshape((-1,1)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly. # In the future, line_thicknesses should be given by CuraEngine as well. # Currently the infill layer thickness also translates to line width line_thicknesses = numpy.zeros(line_widths.shape, dtype="f4") line_thicknesses[:] = layer.thickness / 1000 # from micrometer to millimeter # Create a new 3D-array, copy the 2D points over and insert the right height. # This uses manual array creation + copy rather than numpy.insert since this is # faster. new_points = numpy.empty((len(points), 3), numpy.float32) if polygon.point_type == 0: # Point2D new_points[:, 0] = points[:, 0] new_points[:, 1] = layer.height / 1000 # layer height value is in backend representation new_points[:, 2] = -points[:, 1] else: # Point3D new_points[:, 0] = points[:, 0] new_points[:, 1] = points[:, 2] new_points[:, 2] = -points[:, 1] this_poly = LayerPolygon.LayerPolygon(extruder, line_types, new_points, line_widths, line_thicknesses) this_poly.buildCache() this_layer.polygons.append(this_poly) Job.yieldThread() Job.yieldThread() current_layer += 1 progress = (current_layer / layer_count) * 99 # TODO: Rebuild the layer data mesh once the layer has been processed. # This needs some work in LayerData so we can add the new layers instead of recreating the entire mesh. if self._abort_requested: if self._progress: self._progress.hide() return if self._progress: self._progress.setProgress(progress) # We are done processing all the layers we got from the engine, now create a mesh out of the data # Find out colors per extruder global_container_stack = Application.getInstance().getGlobalContainerStack() manager = ExtruderManager.getInstance() extruders = list(manager.getMachineExtruders(global_container_stack.getId())) if extruders: material_color_map = numpy.zeros((len(extruders), 4), dtype=numpy.float32) for extruder in extruders: position = int(extruder.getMetaDataEntry("position", default="0")) # Get the position try: default_color = ExtrudersModel.defaultColors[position] except KeyError: default_color = "#e0e000" color_code = extruder.material.getMetaDataEntry("color_code", default=default_color) color = colorCodeToRGBA(color_code) material_color_map[position, :] = color else: # Single extruder via global stack. material_color_map = numpy.zeros((1, 4), dtype=numpy.float32) color_code = global_container_stack.material.getMetaDataEntry("color_code", default="#e0e000") color = colorCodeToRGBA(color_code) material_color_map[0, :] = color # We have to scale the colors for compatibility mode if OpenGLContext.isLegacyOpenGL() or bool(Preferences.getInstance().getValue("view/force_layer_view_compatibility_mode")): line_type_brightness = 0.5 # for compatibility mode else: line_type_brightness = 1.0 layer_mesh = layer_data.build(material_color_map, line_type_brightness) if self._abort_requested: if self._progress: self._progress.hide() return # Add LayerDataDecorator to scene node to indicate that the node has layer data decorator = LayerDataDecorator.LayerDataDecorator() decorator.setLayerData(layer_mesh) new_node.addDecorator(decorator) new_node.setMeshData(mesh) # Set build volume as parent, the build volume can move as a result of raft settings. # It makes sense to set the build volume as parent: the print is actually printed on it. new_node_parent = Application.getInstance().getBuildVolume() new_node.setParent(new_node_parent) # Note: After this we can no longer abort! settings = Application.getInstance().getGlobalContainerStack() if not settings.getProperty("machine_center_is_zero", "value"): new_node.setPosition(Vector(-settings.getProperty("machine_width", "value") / 2, 0.0, settings.getProperty("machine_depth", "value") / 2)) if self._progress: self._progress.setProgress(100) view = Application.getInstance().getController().getActiveView() if view.getPluginId() == "LayerView": view.resetLayerData() if self._progress: self._progress.hide() # Clear the unparsed layers. This saves us a bunch of memory if the Job does not get destroyed. self._layers = None Logger.log("d", "Processing layers took %s seconds", time() - start_time)
def run(self): if Application.getInstance().getController().getActiveView( ).getPluginId() == "LayerView": self._progress = Message( catalog.i18nc("@info:status", "Processing Layers"), 0, False, -1) self._progress.show() Application.getInstance().getController().activeViewChanged.connect( self._onActiveViewChanged) objectIdMap = {} new_node = SceneNode() ## Put all nodes in a dict identified by ID for node in DepthFirstIterator(self._scene.getRoot()): if type(node) is SceneNode and node.getMeshData(): if node.callDecoration("getLayerData"): self._scene.getRoot().removeChild(node) else: objectIdMap[id(node)] = node Job.yieldThread() settings = Application.getInstance().getMachineManager( ).getActiveProfile() layerHeight = settings.getSettingValue("layer_height") center = None if not settings.getSettingValue("machine_center_is_zero"): center = numpy.array([ settings.getSettingValue("machine_width") / 2, 0.0, -settings.getSettingValue("machine_depth") / 2 ]) else: center = numpy.array([0.0, 0.0, 0.0]) mesh = MeshData() layer_data = LayerData.LayerData() layer_count = 0 for object in self._message.objects: layer_count += len(object.layers) current_layer = 0 for object in self._message.objects: try: node = objectIdMap[object.id] except KeyError: continue for layer in object.layers: layer_data.addLayer(layer.id) layer_data.setLayerHeight(layer.id, layer.height) layer_data.setLayerThickness(layer.id, layer.thickness) for polygon in layer.polygons: points = numpy.fromstring( polygon.points, dtype="i8") # Convert bytearray to numpy array points = points.reshape( (-1, 2) ) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly. points = numpy.asarray(points, dtype=numpy.float32) points /= 1000 points = numpy.insert(points, 1, (layer.height / 1000), axis=1) points[:, 2] *= -1 points -= center layer_data.addPolygon(layer.id, polygon.type, points, polygon.line_width) Job.yieldThread() current_layer += 1 progress = (current_layer / layer_count) * 100 # TODO: Rebuild the layer data mesh once the layer has been processed. # This needs some work in LayerData so we can add the new layers instead of recreating the entire mesh. if self._progress: self._progress.setProgress(progress) # We are done processing all the layers we got from the engine, now create a mesh out of the data layer_data.build() #Add layerdata decorator to scene node to indicate that the node has layerdata decorator = LayerDataDecorator.LayerDataDecorator() decorator.setLayerData(layer_data) new_node.addDecorator(decorator) new_node.setMeshData(mesh) new_node.setParent(self._scene.getRoot()) if self._progress: self._progress.setProgress(100) view = Application.getInstance().getController().getActiveView() if view.getPluginId() == "LayerView": view.resetLayerData() if self._progress: self._progress.hide()
def run(self): start_time = time() view = Application.getInstance().getController().getActiveView() if view.getPluginId() == "SimulationView": view.resetLayerData() self._progress_message.show() Job.yieldThread() if self._abort_requested: if self._progress_message: self._progress_message.hide() return Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged) new_node = SceneNode() ## Remove old layer data (if any) for node in DepthFirstIterator(self._scene.getRoot()): if node.callDecoration("getLayerData"): node.getParent().removeChild(node) break if self._abort_requested: if self._progress_message: self._progress_message.hide() return # Force garbage collection. # For some reason, Python has a tendency to keep the layer data # in memory longer than needed. Forcing the GC to run here makes # sure any old layer data is really cleaned up before adding new. gc.collect() mesh = MeshData() layer_data = LayerDataBuilder.LayerDataBuilder() layer_count = len(self._layers) # Find the minimum layer number # When using a raft, the raft layers are sent as layers < 0. Instead of allowing layers < 0, we # instead simply offset all other layers so the lowest layer is always 0. It could happens that # the first raft layer has value -8 but there are just 4 raft (negative) layers. min_layer_number = 0 negative_layers = 0 for layer in self._layers: if layer.id < min_layer_number: min_layer_number = layer.id if layer.id < 0: negative_layers += 1 current_layer = 0 for layer in self._layers: # Negative layers are offset by the minimum layer number, but the positive layers are just # offset by the number of negative layers so there is no layer gap between raft and model abs_layer_number = layer.id + abs(min_layer_number) if layer.id < 0 else layer.id + negative_layers layer_data.addLayer(abs_layer_number) this_layer = layer_data.getLayer(abs_layer_number) layer_data.setLayerHeight(abs_layer_number, layer.height) layer_data.setLayerThickness(abs_layer_number, layer.thickness) for p in range(layer.repeatedMessageCount("path_segment")): polygon = layer.getRepeatedMessage("path_segment", p) extruder = polygon.extruder line_types = numpy.fromstring(polygon.line_type, dtype="u1") # Convert bytearray to numpy array line_types = line_types.reshape((-1,1)) points = numpy.fromstring(polygon.points, dtype="f4") # Convert bytearray to numpy array if polygon.point_type == 0: # Point2D points = points.reshape((-1,2)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly. else: # Point3D points = points.reshape((-1,3)) line_widths = numpy.fromstring(polygon.line_width, dtype="f4") # Convert bytearray to numpy array line_widths = line_widths.reshape((-1,1)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly. line_thicknesses = numpy.fromstring(polygon.line_thickness, dtype="f4") # Convert bytearray to numpy array line_thicknesses = line_thicknesses.reshape((-1,1)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly. line_feedrates = numpy.fromstring(polygon.line_feedrate, dtype="f4") # Convert bytearray to numpy array line_feedrates = line_feedrates.reshape((-1,1)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly. # Create a new 3D-array, copy the 2D points over and insert the right height. # This uses manual array creation + copy rather than numpy.insert since this is # faster. new_points = numpy.empty((len(points), 3), numpy.float32) if polygon.point_type == 0: # Point2D new_points[:, 0] = points[:, 0] new_points[:, 1] = layer.height / 1000 # layer height value is in backend representation new_points[:, 2] = -points[:, 1] else: # Point3D new_points[:, 0] = points[:, 0] new_points[:, 1] = points[:, 2] new_points[:, 2] = -points[:, 1] this_poly = LayerPolygon.LayerPolygon(extruder, line_types, new_points, line_widths, line_thicknesses, line_feedrates) this_poly.buildCache() this_layer.polygons.append(this_poly) Job.yieldThread() Job.yieldThread() current_layer += 1 progress = (current_layer / layer_count) * 99 # TODO: Rebuild the layer data mesh once the layer has been processed. # This needs some work in LayerData so we can add the new layers instead of recreating the entire mesh. if self._abort_requested: if self._progress_message: self._progress_message.hide() return if self._progress_message: self._progress_message.setProgress(progress) # We are done processing all the layers we got from the engine, now create a mesh out of the data # Find out colors per extruder global_container_stack = Application.getInstance().getGlobalContainerStack() manager = ExtruderManager.getInstance() extruders = list(manager.getMachineExtruders(global_container_stack.getId())) if extruders: material_color_map = numpy.zeros((len(extruders), 4), dtype=numpy.float32) for extruder in extruders: position = int(extruder.getMetaDataEntry("position", default="0")) # Get the position try: default_color = ExtrudersModel.defaultColors[position] except IndexError: default_color = "#e0e000" color_code = extruder.material.getMetaDataEntry("color_code", default=default_color) color = colorCodeToRGBA(color_code) material_color_map[position, :] = color else: # Single extruder via global stack. material_color_map = numpy.zeros((1, 4), dtype=numpy.float32) color_code = global_container_stack.material.getMetaDataEntry("color_code", default="#e0e000") color = colorCodeToRGBA(color_code) material_color_map[0, :] = color # We have to scale the colors for compatibility mode if OpenGLContext.isLegacyOpenGL() or bool(Preferences.getInstance().getValue("view/force_layer_view_compatibility_mode")): line_type_brightness = 0.5 # for compatibility mode else: line_type_brightness = 1.0 layer_mesh = layer_data.build(material_color_map, line_type_brightness) if self._abort_requested: if self._progress_message: self._progress_message.hide() return # Add LayerDataDecorator to scene node to indicate that the node has layer data decorator = LayerDataDecorator.LayerDataDecorator() decorator.setLayerData(layer_mesh) new_node.addDecorator(decorator) new_node.setMeshData(mesh) # Set build volume as parent, the build volume can move as a result of raft settings. # It makes sense to set the build volume as parent: the print is actually printed on it. new_node_parent = Application.getInstance().getBuildVolume() new_node.setParent(new_node_parent) # Note: After this we can no longer abort! settings = Application.getInstance().getGlobalContainerStack() if not settings.getProperty("machine_center_is_zero", "value"): new_node.setPosition(Vector(-settings.getProperty("machine_width", "value") / 2, 0.0, settings.getProperty("machine_depth", "value") / 2)) if self._progress_message: self._progress_message.setProgress(100) if self._progress_message: self._progress_message.hide() # Clear the unparsed layers. This saves us a bunch of memory if the Job does not get destroyed. self._layers = None Logger.log("d", "Processing layers took %s seconds", time() - start_time)