From 55b78cea05cb987592542b1e38da2066f7a488d8 Mon Sep 17 00:00:00 2001 From: Anton Skrypnyk Date: Wed, 24 Jul 2024 17:15:47 +0300 Subject: [PATCH] Fix reset print job after new file selected. --- octoprint_bambu_printer/printer/__init__.py | 2 + .../printer/bambu_virtual_printer.py | 67 ++++++------- octoprint_bambu_printer/printer/print_job.py | 12 +-- .../printer/remote_sd_card_file_list.py | 95 +++++++++---------- .../printer/states/a_printer_state.py | 3 + .../printer/states/idle_state.py | 12 ++- .../printer/states/printing_state.py | 32 ++++--- test/test_gcode_execution.py | 53 +++++++++++ 8 files changed, 166 insertions(+), 110 deletions(-) diff --git a/octoprint_bambu_printer/printer/__init__.py b/octoprint_bambu_printer/printer/__init__.py index e69de29..23aaa95 100644 --- a/octoprint_bambu_printer/printer/__init__.py +++ b/octoprint_bambu_printer/printer/__init__.py @@ -0,0 +1,2 @@ +__author__ = "Gina Häußge " +__license__ = "GNU Affero General Public License http://www.gnu.org/licenses/agpl.html" diff --git a/octoprint_bambu_printer/printer/bambu_virtual_printer.py b/octoprint_bambu_printer/printer/bambu_virtual_printer.py index da58be5..b2072e3 100644 --- a/octoprint_bambu_printer/printer/bambu_virtual_printer.py +++ b/octoprint_bambu_printer/printer/bambu_virtual_printer.py @@ -1,6 +1,4 @@ -__author__ = "Gina Häußge " -__license__ = "GNU Affero General Public License http://www.gnu.org/licenses/agpl.html" - +from __future__ import annotations import collections from dataclasses import dataclass, field @@ -113,10 +111,12 @@ class BambuVirtualPrinter: @property def current_print_job(self): - if self._current_print_job is None: - self.update_print_job_info() return self._current_print_job + @current_print_job.setter + def current_print_job(self, value): + self._current_print_job = value + def change_state(self, new_state: APrinterState): self._state_change_queue.put(new_state) @@ -126,28 +126,9 @@ class BambuVirtualPrinter: elif event_type == "event_printer_data_update": self._update_printer_info() - def update_print_job_info(self): - print_job_info = self.bambu_client.get_device().print_job - task_name: str = print_job_info.subtask_name - project_file_info = self.file_system.get_data_by_suffix( - task_name, [".3mf", ".gcode.3mf"] - ) - if project_file_info is None: - self._log.debug(f"No 3mf file found for {print_job_info}") - self._current_print_job = None - return - - if self.file_system.select_file(project_file_info.file_name): - self.sendOk() - - # fuzzy math here to get print percentage to match BambuStudio - progress = print_job_info.print_percentage - self._current_print_job = PrintJob(project_file_info, 0) - self._current_print_job.progress = progress - def _update_printer_info(self): device_data = self.bambu_client.get_device() - print_job = device_data.print_job + print_job_state = device_data.print_job.gcode_state temperatures = device_data.temperature self.lastTempAt = time.monotonic() @@ -158,17 +139,17 @@ class BambuVirtualPrinter: self._telemetry.chamberTemp = temperatures.chamber_temp if ( - print_job.gcode_state == "IDLE" - or print_job.gcode_state == "FINISH" - or print_job.gcode_state == "FAILED" + print_job_state == "IDLE" + or print_job_state == "FINISH" + or print_job_state == "FAILED" ): self.change_state(self._state_idle) - elif print_job.gcode_state == "RUNNING": + elif print_job_state == "RUNNING": self.change_state(self._state_printing) - elif print_job.gcode_state == "PAUSE": + elif print_job_state == "PAUSE": self.change_state(self._state_paused) else: - self._log.warn(f"Unknown print job state: {print_job.gcode_state}") + self._log.warn(f"Unknown print job state: {print_job_state}") def _update_hms_errors(self): bambu_printer = self.bambu_client.get_device() @@ -312,13 +293,18 @@ class BambuVirtualPrinter: def _select_sd_file(self, data: str) -> bool: filename = data.split(maxsplit=1)[1].strip() self._list_sd() - if self.file_system.select_file(filename): - assert self.file_system.selected_file is not None - self.sendIO( - f"File opened: {self.file_system.selected_file.file_name} " - f"Size: {self.file_system.selected_file.size}" - ) - self.sendIO("File selected") + if not self.file_system.select_file(filename): + return False + + assert self.file_system.selected_file is not None + self._current_state.update_print_job_info() + + self.sendIO( + f"File opened: {self.file_system.selected_file.file_name} " + f"Size: {self.file_system.selected_file.size}" + ) + self.sendIO("File selected") + return True @gcode_executor.register("M26") def _set_sd_position(self, data: str) -> bool: @@ -345,7 +331,6 @@ class BambuVirtualPrinter: else: self._sdstatus_reporter = None - self.update_print_job_info() self.report_print_job_status() return True @@ -528,6 +513,10 @@ class BambuVirtualPrinter: self._state_change_queue.task_done() except queue.Empty: continue + except Exception as e: + self._state_change_queue.task_done() + raise e + self._current_state.finalize() def _trigger_change_state(self, new_state: APrinterState): if self._current_state == new_state: diff --git a/octoprint_bambu_printer/printer/print_job.py b/octoprint_bambu_printer/printer/print_job.py index d6d6f9b..de74d5c 100644 --- a/octoprint_bambu_printer/printer/print_job.py +++ b/octoprint_bambu_printer/printer/print_job.py @@ -7,14 +7,10 @@ from octoprint_bambu_printer.printer.remote_sd_card_file_list import FileInfo @dataclass class PrintJob: file_info: FileInfo - file_position: int + progress: int @property - def progress(self): + def file_position(self): if self.file_info.size is None: - return 100 - return 100 * self.file_position / self.file_info.size - - @progress.setter - def progress(self, value): - self.file_position = int(self.file_info.size * value / 100) + return 0 + return int(self.file_info.size * self.progress / 100) diff --git a/octoprint_bambu_printer/printer/remote_sd_card_file_list.py b/octoprint_bambu_printer/printer/remote_sd_card_file_list.py index cb9d9dd..821ec6e 100644 --- a/octoprint_bambu_printer/printer/remote_sd_card_file_list.py +++ b/octoprint_bambu_printer/printer/remote_sd_card_file_list.py @@ -48,6 +48,52 @@ class RemoteSDCardFileList: def has_selected_file(self): return self._selected_file_info is not None + def remove_file_selection(self): + self._selected_file_info = None + + def get_all_files(self): + self._update_existing_files_info() + self._logger.debug(f"get_all_files return: {self._file_data_cache}") + return list(self._file_data_cache.values()) + + def get_data_by_suffix(self, file_stem: str, allowed_suffixes: list[str]): + if file_stem == "": + return None + + file_data = self._get_cached_data_by_suffix(file_stem, allowed_suffixes) + if file_data is None: + self._update_existing_files_info() + file_data = self._get_cached_data_by_suffix(file_stem, allowed_suffixes) + return file_data + + def select_file(self, file_path: str) -> bool: + self._logger.debug(f"_selectSdFile: {file_path}") + file_name = Path(file_path).name + file_info = self._get_cached_file_data(file_name) + if file_info is None: + self._logger.error(f"{file_name} open failed") + return False + + self._selected_file_info = file_info + return True + + def delete_file(self, file_path: str) -> None: + host = self._settings.get(["host"]) + access_code = self._settings.get(["access_code"]) + + file_info = self._get_cached_file_data(file_path) + if file_info is not None: + ftp = IoTFTPSClient( + f"{host}", 990, "bblp", f"{access_code}", ssl_implicit=True + ) + try: + if ftp.delete_file(str(file_info.path)): + self._logger.debug(f"{file_path} deleted") + else: + raise Exception("delete failed") + except Exception as e: + self._logger.debug(f"Error deleting file {file_path}") + def _get_ftp_file_info( self, ftp: IoTFTPSClient, file_path: Path, existing_files: list[str] ): @@ -106,11 +152,6 @@ class RemoteSDCardFileList: self._logger.debug(f"get file data: {data}") return data - def get_all_files(self): - self._update_existing_files_info() - self._logger.debug(f"get_all_files return: {self._file_data_cache}") - return list(self._file_data_cache.values()) - def _update_existing_files_info(self): file_info_list = self._get_existing_files_info() self._file_alias_cache = { @@ -126,47 +167,3 @@ class RemoteSDCardFileList: if file_data is not None: return file_data return None - - def get_data_by_suffix(self, file_stem: str, allowed_suffixes: list[str]): - file_data = self._get_cached_data_by_suffix(file_stem, allowed_suffixes) - if file_data is None: - self._update_existing_files_info() - file_data = self._get_cached_data_by_suffix(file_stem, allowed_suffixes) - return file_data - - def select_file(self, file_path: str, check_already_open: bool = False) -> bool: - self._logger.debug( - f"_selectSdFile: {file_path}, check_already_open={check_already_open}" - ) - file_name = Path(file_path).name - file_info = self._get_cached_file_data(file_name) - if file_info is None: - self._logger.error(f"{file_name} open failed") - return False - - if ( - self._selected_file_info is not None - and self._selected_file_info.path == file_info.path - and check_already_open - ): - return True - - self._selected_file_info = file_info - return True - - def delete_file(self, file_path: str) -> None: - host = self._settings.get(["host"]) - access_code = self._settings.get(["access_code"]) - - file_info = self._get_cached_file_data(file_path) - if file_info is not None: - ftp = IoTFTPSClient( - f"{host}", 990, "bblp", f"{access_code}", ssl_implicit=True - ) - try: - if ftp.delete_file(str(file_info.path)): - self._logger.debug(f"{file_path} deleted") - else: - raise Exception("delete failed") - except Exception as e: - self._logger.debug(f"Error deleting file {file_path}") diff --git a/octoprint_bambu_printer/printer/states/a_printer_state.py b/octoprint_bambu_printer/printer/states/a_printer_state.py index a2f5dec..e8b7f2d 100644 --- a/octoprint_bambu_printer/printer/states/a_printer_state.py +++ b/octoprint_bambu_printer/printer/states/a_printer_state.py @@ -25,6 +25,9 @@ class APrinterState: def handle_gcode(self, gcode): self._log.debug(f"{self.__class__.__name__} gcode execution disabled") + def update_print_job_info(self): + self._log_skip_state_transition("start_new_print") + def start_new_print(self): self._log_skip_state_transition("start_new_print") diff --git a/octoprint_bambu_printer/printer/states/idle_state.py b/octoprint_bambu_printer/printer/states/idle_state.py index 1c72742..4625912 100644 --- a/octoprint_bambu_printer/printer/states/idle_state.py +++ b/octoprint_bambu_printer/printer/states/idle_state.py @@ -1,15 +1,19 @@ from __future__ import annotations +from octoprint_bambu_printer.printer.print_job import PrintJob from octoprint_bambu_printer.printer.states.a_printer_state import APrinterState class IdleState(APrinterState): + def init(self): + if self._printer.file_system.has_selected_file: + self.update_print_job_info() + def start_new_print(self): selected_file = self._printer.file_system.selected_file if selected_file is None: self._log.warn("Cannot start print job if file was not selected") - self._printer.change_state(self._printer._state_idle) return print_command = self._get_print_command_for_file(selected_file) @@ -51,3 +55,9 @@ class IdleState(APrinterState): } return print_command + + def update_print_job_info(self): + if self._printer.file_system.selected_file is not None: + self._printer.current_print_job = PrintJob( + self._printer.file_system.selected_file, 0 + ) diff --git a/octoprint_bambu_printer/printer/states/printing_state.py b/octoprint_bambu_printer/printer/states/printing_state.py index a285d14..bf3c50f 100644 --- a/octoprint_bambu_printer/printer/states/printing_state.py +++ b/octoprint_bambu_printer/printer/states/printing_state.py @@ -23,16 +23,12 @@ class PrintingState(APrinterState): def __init__(self, printer: BambuVirtualPrinter) -> None: super().__init__(printer) self._is_printing = False - self._print_job: PrintJob | None = None self._sd_printing_thread = None - @property - def print_job(self): - return self._print_job - def init(self): self._is_printing = True - self._printer.update_print_job_info() + self._printer.file_system.remove_file_selection() + self.update_print_job_info() self._start_worker_thread() def finalize(self): @@ -51,10 +47,9 @@ class PrintingState(APrinterState): while ( self._is_printing and self._printer.current_print_job is not None - and self._printer.current_print_job.file_position - < self._printer.current_print_job.file_info.size + and self._printer.current_print_job.progress < 100 ): - self._printer.update_print_job_info() + self.update_print_job_info() self._printer.report_print_job_status() time.sleep(3) @@ -63,12 +58,23 @@ class PrintingState(APrinterState): self._log.warn("Printing state was triggered with empty print job") return - if ( - self._printer.current_print_job.file_position - >= self._printer.current_print_job.file_info.size - ): + if self._printer.current_print_job.progress >= 100: self._finish_print() + def update_print_job_info(self): + print_job_info = self._printer.bambu_client.get_device().print_job + task_name: str = print_job_info.subtask_name + project_file_info = self._printer.file_system.get_data_by_suffix( + task_name, [".3mf", ".gcode.3mf"] + ) + if project_file_info is None: + self._log.debug(f"No 3mf file found for {print_job_info}") + self._current_print_job = None + return + + progress = print_job_info.print_percentage + self._printer.current_print_job = PrintJob(project_file_info, progress) + def pause_print(self): if self._printer.bambu_client.connected: if self._printer.bambu_client.publish(pybambu.commands.PAUSE): diff --git a/test/test_gcode_execution.py b/test/test_gcode_execution.py index 8cedaab..b3cf414 100644 --- a/test/test_gcode_execution.py +++ b/test/test_gcode_execution.py @@ -318,3 +318,56 @@ def test_regular_move(printer: BambuVirtualPrinter, bambu_client_mock): gcode_command["print"]["param"] = "G1 X10 Y10\n" bambu_client_mock.publish.assert_called_with(gcode_command) + + +def test_file_selection_does_not_affect_current_print( + printer: BambuVirtualPrinter, print_job_mock +): + print_job_mock.subtask_name = "print.3mf" + + printer.write(b"M23 print.3mf\nM24\n") + printer.flush() + printer.readlines() + assert isinstance(printer.current_state, PrintingState) + assert printer.current_print_job is not None + assert printer.current_print_job.file_info.file_name == "print.3mf" + assert printer.current_print_job.progress == 0 + + printer.write(b"M23 print2.3mf\n") + printer.flush() + assert printer.current_print_job is not None + assert printer.current_print_job.file_info.file_name == "print.3mf" + assert printer.current_print_job.progress == 0 + + +def test_finished_print_job_reset_after_new_file_selected( + printer: BambuVirtualPrinter, print_job_mock +): + print_job_mock.subtask_name = "print.3mf" + + printer.write(b"M23 print.3mf\nM24\n") + printer.flush() + printer.readlines() + assert isinstance(printer.current_state, PrintingState) + assert printer.current_print_job is not None + assert printer.current_print_job.file_info.file_name == "print.3mf" + assert printer.current_print_job.progress == 0 + + print_job_mock.print_percentage = 100 + printer.current_state.update_print_job_info() + assert isinstance(printer.current_state, PrintingState) + assert printer.current_print_job.progress == 100 + + print_job_mock.gcode_state = "FINISH" + printer.new_update("event_printer_data_update") + printer.flush() + assert isinstance(printer.current_state, IdleState) + assert printer.current_print_job is not None + assert printer.current_print_job.file_info.file_name == "print.3mf" + assert printer.current_print_job.progress == 100 + + printer.write(b"M23 print2.3mf\n") + printer.flush() + assert printer.current_print_job is not None + assert printer.current_print_job.file_info.file_name == "print2.3mf" + assert printer.current_print_job.progress == 0