Fix reset print job after new file selected.
This commit is contained in:
		| @@ -0,0 +1,2 @@ | ||||
| __author__ = "Gina Häußge <osd@foosel.net>" | ||||
| __license__ = "GNU Affero General Public License http://www.gnu.org/licenses/agpl.html" | ||||
|   | ||||
| @@ -1,6 +1,4 @@ | ||||
| __author__ = "Gina Häußge <osd@foosel.net>" | ||||
| __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: | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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}") | ||||
|   | ||||
| @@ -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") | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|             ) | ||||
|   | ||||
| @@ -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): | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user