示例#1
0
    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())
示例#2
0
    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())
示例#3
0
    def processGCodeStream(self, stream: str) -> Optional[CuraSceneNode]:
        Logger.log("d", "Preparing to load GCode")
        self._cancelled = False
        # We obtain the filament diameter from the selected extruder to calculate line widths
        global_stack = CuraApplication.getInstance().getGlobalContainerStack()

        if not global_stack:
            return None

        self._filament_diameter = global_stack.extruders[str(self._extruder_number)].getProperty("material_diameter", "value")

        scene_node = CuraSceneNode()

        gcode_list = []
        self._is_layers_in_file = False

        self._extruder_offsets = self._extruderOffsets()  # dict with index the extruder number. can be empty

        ##############################################################################################
        ##  This part is where the action starts
        ##############################################################################################
        file_lines = 0
        current_line = 0
        for line in stream.split("\n"):
            file_lines += 1
            gcode_list.append(line + "\n")
            if not self._is_layers_in_file and line[:len(self._layer_keyword)] == self._layer_keyword:
                self._is_layers_in_file = True

        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"))

        assert(self._message is not None) # use for typing purposes
        self._message.setProgress(0)
        self._message.show()

        Logger.log("d", "Parsing Gcode...")

        current_position = Position(0, 0, 0, 0, [0])
        current_path = [] #type: List[List[float]]
        min_layer_number = 0
        negative_layers = 0
        previous_layer = 0
        self._previous_extrusion_value = 0.0

        for line in stream.split("\n"):
            if self._cancelled:
                Logger.log("d", "Parsing Gcode file cancelled")
                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
                elif type == "SUPPORT-INTERFACE":
                    self._layer_type = LayerPolygon.SupportInterfaceType
                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 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()
                    # Start the new layer at the end position of the last layer
                    current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveCombingType])

                    # 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()

                    # When changing tool, store the end point of the previous path, then process the code and finally
                    # add another point with the new position of the head.
                    current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveCombingType])
                    current_position = self.processTCode(T, line, current_position, current_path)
                    current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveCombingType])

            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((8, 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]
        material_color_map[2, :] = [0.9, 0.0, 0.7, 1.0]
        material_color_map[3, :] = [0.7, 0.0, 0.0, 1.0]
        material_color_map[4, :] = [0.0, 0.7, 0.0, 1.0]
        material_color_map[5, :] = [0.0, 0.0, 0.7, 1.0]
        material_color_map[6, :] = [0.3, 0.3, 0.3, 1.0]
        material_color_map[7, :] = [0.7, 0.7, 0.7, 1.0]
        layer_mesh = self._layer_data_builder.build(material_color_map)
        decorator = 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)

        # gcode_dict stores gcode_lists for a number of build plates.
        active_build_plate_id = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
        gcode_dict = {active_build_plate_id: gcode_list}
        CuraApplication.getInstance().getController().getScene().gcode_dict = gcode_dict #type: ignore #Because gcode_dict is generated dynamically.

        Logger.log("d", "Finished parsing Gcode")
        self._message.hide()

        if self._layer_number == 0:
            Logger.log("w", "File doesn't contain any valid layers")

        settings = CuraApplication.getInstance().getGlobalContainerStack()
        if not settings.getProperty("machine_center_is_zero", "value"):
            machine_width = settings.getProperty("machine_width", "value")
            machine_depth = settings.getProperty("machine_depth", "value")
            scene_node.setPosition(Vector(-machine_width / 2, 0, machine_depth / 2))

        Logger.log("d", "GCode loading finished")

        if CuraApplication.getInstance().getPreferences().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 = CuraApplication.getInstance().getBackend()
        backend.backendStateChange.emit(Backend.BackendState.Disabled)

        return scene_node
示例#4
0
    def processGCodeStream(self, stream: str) -> Optional[CuraSceneNode]:
        Logger.log("d", "Preparing to load GCode")
        self._cancelled = False
        # We obtain the filament diameter from the selected extruder to calculate line widths
        global_stack = CuraApplication.getInstance().getGlobalContainerStack()

        if not global_stack:
            return None

        self._filament_diameter = global_stack.extruders[str(
            self._extruder_number)].getProperty("material_diameter", "value")

        scene_node = CuraSceneNode()

        gcode_list = []
        self._is_layers_in_file = False

        self._extruder_offsets = self._extruderOffsets(
        )  # dict with index the extruder number. can be empty

        ##############################################################################################
        ##  This part is where the action starts
        ##############################################################################################
        file_lines = 0
        current_line = 0
        for line in stream.split("\n"):
            file_lines += 1
            gcode_list.append(line + "\n")
            if not self._is_layers_in_file and line[:len(
                    self._layer_keyword)] == self._layer_keyword:
                self._is_layers_in_file = True

        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"))

        assert (self._message is not None)  # use for typing purposes
        self._message.setProgress(0)
        self._message.show()

        Logger.log("d", "Parsing Gcode...")

        current_position = Position(0, 0, 0, 0, [0])
        current_path = []  #type: List[List[float]]
        min_layer_number = 0
        negative_layers = 0
        previous_layer = 0

        for line in stream.split("\n"):
            if self._cancelled:
                Logger.log("d", "Parsing Gcode file cancelled")
                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 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()
                    # Start the new layer at the end position of the last layer
                    current_path.append([
                        current_position.x, current_position.y,
                        current_position.z, current_position.f,
                        current_position.e[self._extruder_number],
                        LayerPolygon.MoveCombingType
                    ])

                    # 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()

                    # When changing tool, store the end point of the previous path, then process the code and finally
                    # add another point with the new position of the head.
                    current_path.append([
                        current_position.x, current_position.y,
                        current_position.z, current_position.f,
                        current_position.e[self._extruder_number],
                        LayerPolygon.MoveCombingType
                    ])
                    current_position = self.processTCode(
                        T, line, current_position, current_path)
                    current_path.append([
                        current_position.x, current_position.y,
                        current_position.z, current_position.f,
                        current_position.e[self._extruder_number],
                        LayerPolygon.MoveCombingType
                    ])

            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((8, 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]
        material_color_map[2, :] = [0.9, 0.0, 0.7, 1.0]
        material_color_map[3, :] = [0.7, 0.0, 0.0, 1.0]
        material_color_map[4, :] = [0.0, 0.7, 0.0, 1.0]
        material_color_map[5, :] = [0.0, 0.0, 0.7, 1.0]
        material_color_map[6, :] = [0.3, 0.3, 0.3, 1.0]
        material_color_map[7, :] = [0.7, 0.7, 0.7, 1.0]
        layer_mesh = self._layer_data_builder.build(material_color_map)
        decorator = 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)

        # gcode_dict stores gcode_lists for a number of build plates.
        active_build_plate_id = CuraApplication.getInstance(
        ).getMultiBuildPlateModel().activeBuildPlate
        gcode_dict = {active_build_plate_id: gcode_list}
        CuraApplication.getInstance().getController().getScene(
        ).gcode_dict = gcode_dict  #type: ignore #Because gcode_dict is generated dynamically.

        Logger.log("d", "Finished parsing Gcode")
        self._message.hide()

        if self._layer_number == 0:
            Logger.log("w", "File doesn't contain any valid layers")

        settings = CuraApplication.getInstance().getGlobalContainerStack()
        if not settings.getProperty("machine_center_is_zero", "value"):
            machine_width = settings.getProperty("machine_width", "value")
            machine_depth = settings.getProperty("machine_depth", "value")
            scene_node.setPosition(
                Vector(-machine_width / 2, 0, machine_depth / 2))

        Logger.log("d", "GCode loading finished")

        if CuraApplication.getInstance().getPreferences().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 = CuraApplication.getInstance().getBackend()
        backend.backendStateChange.emit(Backend.BackendState.Disabled)

        return scene_node
示例#5
0
    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 = CuraSceneNode()
        # 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((8, 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]
        material_color_map[2, :] = [0.9, 0.0, 0.7, 1.0]
        material_color_map[3, :] = [0.7, 0.0, 0.0, 1.0]
        material_color_map[4, :] = [0.0, 0.7, 0.0, 1.0]
        material_color_map[5, :] = [0.0, 0.0, 0.7, 1.0]
        material_color_map[6, :] = [0.3, 0.3, 0.3, 1.0]
        material_color_map[7, :] = [0.7, 0.7, 0.7, 1.0]
        layer_mesh = self._layer_data_builder.build(material_color_map)
        decorator = 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)

        # gcode_dict stores gcode_lists for a number of build plates.
        active_build_plate_id = Application.getInstance().getBuildPlateModel().activeBuildPlate
        gcode_dict = {active_build_plate_id: gcode_list}
        Application.getInstance().getController().getScene().gcode_dict = gcode_dict

        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")
        print_speed = settings.getProperty("speed_wall_0", "value")
        travel_speed = settings.getProperty("speed_travel", "value")
        time = (self._total_move_length - self._extrusion_retraction_length) / travel_speed + self._extrusion_retraction_length / print_speed
        radius = Application.getInstance().getGlobalContainerStack().getProperty("material_diameter", "value") / 2

        for i in range(len(self._extrusion_max_amounts)):
            self._extrusion_max_amounts[i] *= (math.pi * (radius ** 2))
        total_extrusion = self._extrusion_max_amounts

        print_statistics_decorator = PrintStatisticsDecorator()
        print_statistics_decorator.setPrintTime(time)
        print_statistics_decorator.setMaterialAmounts(total_extrusion)
        scene_node.addDecorator(print_statistics_decorator)

        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