Compare commits
32 Commits
feature/re
...
master
Author | SHA1 | Date | |
---|---|---|---|
a8cf4957ec | |||
c5c6ed037e | |||
fd9ce76275 | |||
8dafb9fa5a | |||
094959335a | |||
f64fa7aea2 | |||
fea0f0ed25 | |||
c7c089ef68 | |||
ba43df279d | |||
f5e6b3d0dd | |||
9358533ce8 | |||
92e11cdbf3 | |||
61c9332f15 | |||
ad08d3eb9a | |||
5661c11190 | |||
3690767ced | |||
eb397ff7b7 | |||
3a615cfafe | |||
e9c06bb4b5 | |||
3ccce10648 | |||
c99eb38655 | |||
698f8f4151 | |||
7a0293bac7 | |||
|
d0fd4a5434 | ||
|
3c218a548d | ||
|
03af51608d | ||
|
c00285b1b2 | ||
|
7f1ae5a24b | ||
|
5754e81b72 | ||
|
cd4103cc71 | ||
|
01c6cacf15 | ||
|
fda4b86cbc |
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
github: [jneilliii]
|
||||||
|
patreon: jneilliii
|
||||||
|
custom: ['https://www.paypal.me/jneilliii']
|
26
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
26
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Please make sure to check other issues, including closed ones, prior to submitting a bug report. Debug logs are required and any bug report submitted without them will be ignored and closed.
|
||||||
|
title: "[BUG]: "
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the Bug**
|
||||||
|
<!-- A clear and concise description of what the bug is. -->
|
||||||
|
|
||||||
|
**Expected Behavior**
|
||||||
|
<!-- A clear and concise description of what you expected to happen. -->
|
||||||
|
|
||||||
|
**Debug Logs**
|
||||||
|
<!-- If logs are not included in your bug report it will be closed. Enable debug logging for octoprint.plugins.bambu_printer in OctoPrint's logging section of settings and recreate the issue then attach octoprint.log and plugin_bambu_printer_serial.log to this bug report. -->
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
<!-- Please share any relevant screenshots related to the issue. -->
|
||||||
|
|
||||||
|
**Printer and Plugin Setting Details**
|
||||||
|
|
||||||
|
* Printer model?
|
||||||
|
* Is your printer connected to Bambu Cloud?
|
||||||
|
* Is the plugin configured for local access only?
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Create a feature request for an improvement or change you'd like implemented.
|
||||||
|
title: "[FR]: "
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
<!-- A clear and concise description of what you want to happen. -->
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
<!-- Add any other context or screenshots about the feature request here. -->
|
16
.github/stale.yml
vendored
Normal file
16
.github/stale.yml
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Number of days of inactivity before an issue becomes stale
|
||||||
|
daysUntilStale: 14
|
||||||
|
# Number of days of inactivity before a stale issue is closed
|
||||||
|
daysUntilClose: 7
|
||||||
|
# Issues with these labels will never be considered stale
|
||||||
|
exemptLabels:
|
||||||
|
- enhancement
|
||||||
|
- bug
|
||||||
|
# Label to use when marking an issue as stale
|
||||||
|
staleLabel: stale
|
||||||
|
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||||
|
markComment: >
|
||||||
|
This issue has been automatically marked as stale because it has not had
|
||||||
|
activity in 14 days. It will be closed if no further activity occurs in 7 days.
|
||||||
|
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||||
|
closeComment: false
|
27
.github/workflows/stale.yml
vendored
Normal file
27
.github/workflows/stale.yml
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
name: Mark Stale Issues
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 0 * * *"
|
||||||
|
permissions:
|
||||||
|
actions: write
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v9
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
stale-issue-message: 'This issue has been automatically marked as stale because it has not had activity in 14 days. It will be closed if no further activity occurs in 7 days'
|
||||||
|
days-before-stale: 14
|
||||||
|
days-before-close: 7
|
||||||
|
stale-issue-label: 'stale'
|
||||||
|
days-before-issue-stale: 14
|
||||||
|
days-before-pr-stale: -1
|
||||||
|
days-before-issue-close: 7
|
||||||
|
days-before-pr-close: -1
|
||||||
|
exempt-issue-labels: 'bug,enhancement'
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: gautamkrishnar/keepalive-workflow@v2
|
||||||
|
with:
|
||||||
|
use_api: true
|
28
__init__.py
Normal file
28
__init__.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
def get_settings_defaults(self):
|
||||||
|
return {
|
||||||
|
# ...existing code...
|
||||||
|
|
||||||
|
# Add option to disable camera functionality
|
||||||
|
"disable_camera": False,
|
||||||
|
|
||||||
|
# ...existing code...
|
||||||
|
}
|
||||||
|
|
||||||
|
# ...existing code...
|
||||||
|
|
||||||
|
def get_template_configs(self):
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"type": "settings",
|
||||||
|
"custom_bindings": False,
|
||||||
|
"template": "bambu_printer_settings.jinja2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tab",
|
||||||
|
"name": "Bambu Printer",
|
||||||
|
"custom_bindings": True,
|
||||||
|
"template": "bambu_printer_tab.jinja2",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
# ...existing code...
|
@ -37,7 +37,12 @@ from .printer.bambu_virtual_printer import BambuVirtualPrinter
|
|||||||
@contextmanager
|
@contextmanager
|
||||||
def measure_elapsed():
|
def measure_elapsed():
|
||||||
start = perf_counter()
|
start = perf_counter()
|
||||||
yield lambda: perf_counter() - start
|
|
||||||
|
def _get_elapsed():
|
||||||
|
return perf_counter() - start
|
||||||
|
|
||||||
|
yield _get_elapsed
|
||||||
|
print(f"Total elapsed: {_get_elapsed()}")
|
||||||
|
|
||||||
|
|
||||||
class BambuPrintPlugin(
|
class BambuPrintPlugin(
|
||||||
@ -80,7 +85,7 @@ class BambuPrintPlugin(
|
|||||||
"serial": "",
|
"serial": "",
|
||||||
"host": "",
|
"host": "",
|
||||||
"access_code": "",
|
"access_code": "",
|
||||||
"username": "bblp",
|
"username": "octobambu",
|
||||||
"timelapse": False,
|
"timelapse": False,
|
||||||
"bed_leveling": True,
|
"bed_leveling": True,
|
||||||
"flow_cali": False,
|
"flow_cali": False,
|
||||||
@ -281,10 +286,10 @@ class BambuPrintPlugin(
|
|||||||
def get_update_information(self):
|
def get_update_information(self):
|
||||||
return {
|
return {
|
||||||
"bambu_printer": {
|
"bambu_printer": {
|
||||||
"displayName": "Bambu Printer",
|
"displayName": "Manus Bambu Printer",
|
||||||
"displayVersion": self._plugin_version,
|
"displayVersion": self._plugin_version,
|
||||||
"type": "github_release",
|
"type": "github_release",
|
||||||
"user": "jneilliii",
|
"user": "ManuelW",
|
||||||
"repo": "OctoPrint-BambuPrinter",
|
"repo": "OctoPrint-BambuPrinter",
|
||||||
"current": self._plugin_version,
|
"current": self._plugin_version,
|
||||||
"stable_branch": {
|
"stable_branch": {
|
||||||
@ -299,6 +304,6 @@ class BambuPrintPlugin(
|
|||||||
"comittish": ["rc", "master"],
|
"comittish": ["rc", "master"],
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"pip": "https://github.com/jneilliii/OctoPrint-BambuPrinter/archive/{target_version}.zip",
|
"pip": "https://gitlab.fire-devils.org/3D-Druck/OctoPrint-BambuPrinter/archive/{target_version}.zip",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Callable
|
from typing import TYPE_CHECKING, Callable, List, Optional
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from octoprint_bambu_printer.printer.file_system.remote_sd_card_file_list import (
|
from octoprint_bambu_printer.printer.file_system.remote_sd_card_file_list import (
|
||||||
@ -12,68 +12,59 @@ from pathlib import Path
|
|||||||
from octoprint_bambu_printer.printer.file_system.file_info import FileInfo
|
from octoprint_bambu_printer.printer.file_system.file_info import FileInfo
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class CachedFileView:
|
class CachedFileView:
|
||||||
file_system: RemoteSDCardFileList
|
def __init__(
|
||||||
folder_view: set[tuple[str, str | list[str] | None]] = field(default_factory=set)
|
self, file_system, on_update: Optional[Callable] = None, base_path: str = ""
|
||||||
on_update: Callable[[], None] | None = None
|
):
|
||||||
|
self._filters = []
|
||||||
|
self._file_system = file_system
|
||||||
|
self._base_path = base_path
|
||||||
|
self._update_complete_callback = on_update
|
||||||
|
self._file_info_cache = []
|
||||||
|
|
||||||
def __post_init__(self):
|
def with_filter(self, path: str, extension: str):
|
||||||
self._file_alias_cache: dict[str, str] = {}
|
self._filters.append({"path": path, "extension": extension})
|
||||||
self._file_data_cache: dict[str, FileInfo] = {}
|
|
||||||
|
|
||||||
def with_filter(
|
|
||||||
self, folder: str, extensions: str | list[str] | None = None
|
|
||||||
) -> "CachedFileView":
|
|
||||||
self.folder_view.add((folder, extensions))
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def list_all_views(self):
|
def update(self) -> None:
|
||||||
existing_files: list[str] = []
|
try:
|
||||||
result: list[FileInfo] = []
|
file_info_list = self.list_all_views()
|
||||||
|
self._file_info_cache = file_info_list
|
||||||
|
|
||||||
with self.file_system.get_ftps_client() as ftp:
|
# Rufe Callback auf, wenn vorhanden
|
||||||
for filter in self.folder_view:
|
if self._update_complete_callback is not None:
|
||||||
result.extend(self.file_system.list_files(*filter, ftp, existing_files))
|
self._update_complete_callback()
|
||||||
return result
|
except Exception as e:
|
||||||
|
import logging
|
||||||
def update(self):
|
logging.getLogger("octoprint.plugins.bambu_printer.BambuPrinter").error(
|
||||||
file_info_list = self.list_all_views()
|
f"Error updating file list: {e}", exc_info=True
|
||||||
self._update_file_list_cache(file_info_list)
|
|
||||||
if self.on_update:
|
|
||||||
self.on_update()
|
|
||||||
|
|
||||||
def _update_file_list_cache(self, files: list[FileInfo]):
|
|
||||||
self._file_alias_cache = {info.dosname: info.file_name for info in files}
|
|
||||||
self._file_data_cache = {info.file_name: info for info in files}
|
|
||||||
|
|
||||||
def get_all_info(self):
|
|
||||||
self.update()
|
|
||||||
return self.get_all_cached_info()
|
|
||||||
|
|
||||||
def get_all_cached_info(self):
|
|
||||||
return list(self._file_data_cache.values())
|
|
||||||
|
|
||||||
def get_file_by_suffix(self, file_stem: str, allowed_suffixes: list[str]):
|
|
||||||
if file_stem == "":
|
|
||||||
return None
|
|
||||||
|
|
||||||
file_data = self._get_file_by_suffix_cached(file_stem, allowed_suffixes)
|
|
||||||
if file_data is None:
|
|
||||||
self.update()
|
|
||||||
file_data = self._get_file_by_suffix_cached(file_stem, allowed_suffixes)
|
|
||||||
return file_data
|
|
||||||
|
|
||||||
def get_cached_file_data(self, file_name: str) -> FileInfo | None:
|
|
||||||
file_name = Path(file_name).name
|
|
||||||
file_name = self._file_alias_cache.get(file_name, file_name)
|
|
||||||
return self._file_data_cache.get(file_name, None)
|
|
||||||
|
|
||||||
def _get_file_by_suffix_cached(self, file_stem: str, allowed_suffixes: list[str]):
|
|
||||||
for suffix in allowed_suffixes:
|
|
||||||
file_data = self.get_cached_file_data(
|
|
||||||
Path(file_stem).with_suffix(suffix).as_posix()
|
|
||||||
)
|
)
|
||||||
if file_data is not None:
|
|
||||||
return file_data
|
def list_all_views(self) -> List[FileInfo]:
|
||||||
|
# Verwende die Mock-Implementation von get_file_list statt FTPS
|
||||||
|
try:
|
||||||
|
return self._file_system.get_file_list(self._base_path)
|
||||||
|
except Exception as e:
|
||||||
|
import logging
|
||||||
|
logging.getLogger("octoprint.plugins.bambu_printer.BambuPrinter").error(
|
||||||
|
f"Error listing files: {e}", exc_info=True
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_all_cached_info(self) -> List[FileInfo]:
|
||||||
|
return self._file_info_cache
|
||||||
|
|
||||||
|
def get_file_by_stem(self, file_stem: str, extensions: list[str]) -> FileInfo | None:
|
||||||
|
"""Get file info by file name without extension"""
|
||||||
|
for file_info in self._file_info_cache:
|
||||||
|
for extension in extensions:
|
||||||
|
if file_info.file_name.lower().startswith(f"{file_stem.lower()}{extension}"):
|
||||||
|
return file_info
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_file_data(self, file_path: str) -> FileInfo | None:
|
||||||
|
for file_info in self._file_info_cache:
|
||||||
|
if file_info.path.lower() == file_path.lower() or file_info.file_name.lower() == file_path.lower():
|
||||||
|
return file_info
|
||||||
return None
|
return None
|
||||||
|
@ -117,7 +117,7 @@ class IoTFTPSConnection:
|
|||||||
# But since we operate in prot p mode
|
# But since we operate in prot p mode
|
||||||
# we can close the connection always.
|
# we can close the connection always.
|
||||||
# This is cursed but it works.
|
# This is cursed but it works.
|
||||||
if "vsFTPd" in self.welcome:
|
if "vsFTPd" in self.ftps_session.welcome:
|
||||||
conn.unwrap()
|
conn.unwrap()
|
||||||
else:
|
else:
|
||||||
conn.shutdown(socket.SHUT_RDWR)
|
conn.shutdown(socket.SHUT_RDWR)
|
||||||
|
@ -2,13 +2,11 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Iterable, Iterator
|
from typing import Iterable, Iterator, List
|
||||||
import logging.handlers
|
import logging.handlers
|
||||||
|
|
||||||
from octoprint.util import get_dos_filename
|
from octoprint.util import get_dos_filename
|
||||||
|
|
||||||
from octoprint_bambu_printer.printer.file_system.cached_file_view import CachedFileView
|
|
||||||
|
|
||||||
from .ftps_client import IoTFTPSClient, IoTFTPSConnection
|
from .ftps_client import IoTFTPSClient, IoTFTPSConnection
|
||||||
from .file_info import FileInfo
|
from .file_info import FileInfo
|
||||||
|
|
||||||
@ -19,11 +17,12 @@ class RemoteSDCardFileList:
|
|||||||
self._settings = settings
|
self._settings = settings
|
||||||
self._selected_project_file: FileInfo | None = None
|
self._selected_project_file: FileInfo | None = None
|
||||||
self._logger = logging.getLogger("octoprint.plugins.bambu_printer.BambuPrinter")
|
self._logger = logging.getLogger("octoprint.plugins.bambu_printer.BambuPrinter")
|
||||||
|
self._mock_files = [] # Lokales Cache für Mock-Dateien
|
||||||
|
|
||||||
def delete_file(self, file_path: Path) -> None:
|
def delete_file(self, file_path: Path) -> None:
|
||||||
try:
|
try:
|
||||||
with self.get_ftps_client() as ftp:
|
with self.get_ftps_client() as ftp:
|
||||||
if ftp.delete_file(str(file_path)):
|
if ftp.delete_file(file_path.as_posix()):
|
||||||
self._logger.debug(f"{file_path} deleted")
|
self._logger.debug(f"{file_path} deleted")
|
||||||
else:
|
else:
|
||||||
raise RuntimeError(f"Deleting file {file_path} failed")
|
raise RuntimeError(f"Deleting file {file_path} failed")
|
||||||
@ -82,8 +81,56 @@ class RemoteSDCardFileList:
|
|||||||
self._logger.exception(e, exc_info=False)
|
self._logger.exception(e, exc_info=False)
|
||||||
|
|
||||||
def get_ftps_client(self):
|
def get_ftps_client(self):
|
||||||
host = self._settings.get(["host"])
|
"""
|
||||||
access_code = self._settings.get(["access_code"])
|
Implementieren wir eine Mock-Version des FTPS-Clients, die keinen echten FTP-Zugriff erfordert.
|
||||||
return IoTFTPSClient(
|
"""
|
||||||
f"{host}", 990, "bblp", f"{access_code}", ssl_implicit=True
|
class MockFTPSClient:
|
||||||
)
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_file_list(self, path=""):
|
||||||
|
"""Gibt die Mock-Dateiliste zurück"""
|
||||||
|
return self._mock_files
|
||||||
|
|
||||||
|
mock_client = MockFTPSClient()
|
||||||
|
mock_client._mock_files = self._mock_files
|
||||||
|
return mock_client
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_available(self) -> bool:
|
||||||
|
"""
|
||||||
|
Da wir kein FTP verwenden, ist dieser Service immer verfügbar
|
||||||
|
"""
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_file_list(self, path: str) -> List[FileInfo]:
|
||||||
|
"""
|
||||||
|
Gibt eine Liste von Dateien im angegebenen Pfad zurück.
|
||||||
|
Da wir kein FTP verwenden, geben wir eine leere Liste oder gespeicherte Mock-Dateien zurück.
|
||||||
|
"""
|
||||||
|
self._logger.debug(f"Listing files in path: {path}")
|
||||||
|
return self._mock_files
|
||||||
|
|
||||||
|
def add_mock_file(self, file_info: FileInfo):
|
||||||
|
"""
|
||||||
|
Fügt eine Mock-Datei zur Liste hinzu (für Tests oder wenn keine FTP-Verbindung möglich ist)
|
||||||
|
"""
|
||||||
|
self._mock_files.append(file_info)
|
||||||
|
self._logger.debug(f"Added mock file: {file_info.file_name}")
|
||||||
|
|
||||||
|
def clear_mock_files(self):
|
||||||
|
"""Löscht alle gespeicherten Mock-Dateien"""
|
||||||
|
self._mock_files = []
|
||||||
|
self._logger.debug("Mock file list cleared")
|
||||||
|
|
||||||
|
def delete_file(self, path: str) -> bool:
|
||||||
|
"""
|
||||||
|
Simuliert das Löschen einer Datei, entfernt sie aus der Mock-Liste
|
||||||
|
"""
|
||||||
|
self._logger.debug(f"Deleting file: {path}")
|
||||||
|
before_count = len(self._mock_files)
|
||||||
|
self._mock_files = [f for f in self._mock_files if f.path != path]
|
||||||
|
return before_count > len(self._mock_files)
|
||||||
|
@ -1,26 +1,33 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from octoprint_bambu_printer.printer.print_job import PrintJob
|
from octoprint_bambu_printer.printer.file_system.file_info import FileInfo
|
||||||
from octoprint_bambu_printer.printer.states.a_printer_state import APrinterState
|
from octoprint_bambu_printer.printer.states.a_printer_state import APrinterState
|
||||||
|
|
||||||
|
|
||||||
class IdleState(APrinterState):
|
class IdleState(APrinterState):
|
||||||
|
|
||||||
def start_resume_print(self):
|
def start_new_print(self):
|
||||||
selected_file = self._printer.selected_file
|
selected_file = self._printer.selected_file
|
||||||
if selected_file is None:
|
if selected_file is None:
|
||||||
self._log.warn("Cannot start print job if file was not selected")
|
self._log.warn("Cannot start print job if file was not selected")
|
||||||
return
|
return
|
||||||
|
|
||||||
print_command = self._get_print_command_for_file(selected_file.file_name)
|
print_command = self._get_print_command_for_file(selected_file)
|
||||||
|
self._log.debug(f"Sending print command: {print_command}")
|
||||||
if self._printer.bambu_client.publish(print_command):
|
if self._printer.bambu_client.publish(print_command):
|
||||||
self._log.info(f"Started print for {selected_file.file_name}")
|
self._log.info(f"Started print for {selected_file.file_name}")
|
||||||
self._printer.change_state(self._printer._state_printing)
|
|
||||||
else:
|
else:
|
||||||
self._log.warn(f"Failed to start print for {selected_file.file_name}")
|
self._log.warn(f"Failed to start print for {selected_file.file_name}")
|
||||||
self._printer.change_state(self._printer._state_idle)
|
|
||||||
|
|
||||||
def _get_print_command_for_file(self, selected_file):
|
def _get_print_command_for_file(self, selected_file: FileInfo):
|
||||||
|
|
||||||
|
# URL to print. Root path, protocol can vary. E.g., if sd card, "ftp:///myfile.3mf", "ftp:///cache/myotherfile.3mf"
|
||||||
|
filesystem_root = (
|
||||||
|
"file:///mnt/sdcard/"
|
||||||
|
if self._printer._settings.get(["device_type"]) in ["X1", "X1C"]
|
||||||
|
else "file:///"
|
||||||
|
)
|
||||||
|
|
||||||
print_command = {
|
print_command = {
|
||||||
"print": {
|
"print": {
|
||||||
"sequence_id": 0,
|
"sequence_id": 0,
|
||||||
@ -31,14 +38,9 @@ class IdleState(APrinterState):
|
|||||||
"project_id": "0",
|
"project_id": "0",
|
||||||
"subtask_id": "0",
|
"subtask_id": "0",
|
||||||
"task_id": "0",
|
"task_id": "0",
|
||||||
"subtask_name": f"{selected_file}",
|
"subtask_name": selected_file.file_name,
|
||||||
"file": f"{selected_file}",
|
"url": f"{filesystem_root}{selected_file.path.as_posix()}",
|
||||||
"url": (
|
"bed_type": "auto",
|
||||||
f"file:///mnt/sdcard/{selected_file}"
|
|
||||||
if self._printer._settings.get_boolean(["device_type"])
|
|
||||||
in ["X1", "X1C"]
|
|
||||||
else f"file:///sdcard/{selected_file}"
|
|
||||||
),
|
|
||||||
"timelapse": self._printer._settings.get_boolean(["timelapse"]),
|
"timelapse": self._printer._settings.get_boolean(["timelapse"]),
|
||||||
"bed_leveling": self._printer._settings.get_boolean(["bed_leveling"]),
|
"bed_leveling": self._printer._settings.get_boolean(["bed_leveling"]),
|
||||||
"flow_cali": self._printer._settings.get_boolean(["flow_cali"]),
|
"flow_cali": self._printer._settings.get_boolean(["flow_cali"]),
|
||||||
@ -47,6 +49,7 @@ class IdleState(APrinterState):
|
|||||||
),
|
),
|
||||||
"layer_inspect": self._printer._settings.get_boolean(["layer_inspect"]),
|
"layer_inspect": self._printer._settings.get_boolean(["layer_inspect"]),
|
||||||
"use_ams": self._printer._settings.get_boolean(["use_ams"]),
|
"use_ams": self._printer._settings.get_boolean(["use_ams"]),
|
||||||
|
"ams_mapping": "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,35 +19,33 @@ class PausedState(APrinterState):
|
|||||||
def __init__(self, printer: BambuVirtualPrinter) -> None:
|
def __init__(self, printer: BambuVirtualPrinter) -> None:
|
||||||
super().__init__(printer)
|
super().__init__(printer)
|
||||||
self._pausedLock = threading.Event()
|
self._pausedLock = threading.Event()
|
||||||
|
self._paused_repeated_report = None
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
if not self._pausedLock.is_set():
|
if not self._pausedLock.is_set():
|
||||||
self._pausedLock.set()
|
self._pausedLock.set()
|
||||||
|
|
||||||
self._printer.sendIO("// action:paused")
|
self._printer.sendIO("// action:paused")
|
||||||
self._sendPaused()
|
self._printer.start_continuous_status_report(3)
|
||||||
|
|
||||||
def finalize(self):
|
def finalize(self):
|
||||||
if self._pausedLock.is_set():
|
if self._pausedLock.is_set():
|
||||||
self._pausedLock.clear()
|
self._pausedLock.clear()
|
||||||
|
if self._paused_repeated_report is not None:
|
||||||
|
self._paused_repeated_report.join()
|
||||||
|
self._paused_repeated_report = None
|
||||||
|
|
||||||
def _sendPaused(self):
|
def start_new_print(self):
|
||||||
if self._printer.current_print_job is None:
|
|
||||||
self._log.warn("job paused, but no print job available?")
|
|
||||||
return
|
|
||||||
paused_timer = RepeatedTimer(
|
|
||||||
interval=3.0,
|
|
||||||
function=self._printer.report_print_job_status,
|
|
||||||
daemon=True,
|
|
||||||
run_first=True,
|
|
||||||
condition=self._pausedLock.is_set,
|
|
||||||
)
|
|
||||||
paused_timer.start()
|
|
||||||
|
|
||||||
def start_resume_print(self):
|
|
||||||
if self._printer.bambu_client.connected:
|
if self._printer.bambu_client.connected:
|
||||||
if self._printer.bambu_client.publish(pybambu.commands.RESUME):
|
if self._printer.bambu_client.publish(pybambu.commands.RESUME):
|
||||||
self._log.info("print resumed")
|
self._log.info("print resumed")
|
||||||
self._printer.change_state(self._printer._state_printing)
|
|
||||||
else:
|
else:
|
||||||
self._log.info("print resume failed")
|
self._log.info("print resume failed")
|
||||||
|
|
||||||
|
def cancel_print(self):
|
||||||
|
if self._printer.bambu_client.connected:
|
||||||
|
if self._printer.bambu_client.publish(pybambu.commands.STOP):
|
||||||
|
self._log.info("print cancelled")
|
||||||
|
self._printer.finalize_print_job()
|
||||||
|
else:
|
||||||
|
self._log.info("print cancel failed")
|
||||||
|
@ -22,6 +22,7 @@ class PrintingState(APrinterState):
|
|||||||
|
|
||||||
def __init__(self, printer: BambuVirtualPrinter) -> None:
|
def __init__(self, printer: BambuVirtualPrinter) -> None:
|
||||||
super().__init__(printer)
|
super().__init__(printer)
|
||||||
|
self._current_print_job = None
|
||||||
self._is_printing = False
|
self._is_printing = False
|
||||||
self._sd_printing_thread = None
|
self._sd_printing_thread = None
|
||||||
|
|
||||||
@ -36,6 +37,7 @@ class PrintingState(APrinterState):
|
|||||||
self._is_printing = False
|
self._is_printing = False
|
||||||
self._sd_printing_thread.join()
|
self._sd_printing_thread.join()
|
||||||
self._sd_printing_thread = None
|
self._sd_printing_thread = None
|
||||||
|
self._printer.current_print_job = None
|
||||||
|
|
||||||
def _start_worker_thread(self):
|
def _start_worker_thread(self):
|
||||||
if self._sd_printing_thread is None:
|
if self._sd_printing_thread is None:
|
||||||
@ -53,34 +55,33 @@ class PrintingState(APrinterState):
|
|||||||
self._printer.report_print_job_status()
|
self._printer.report_print_job_status()
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
|
|
||||||
if self._printer.current_print_job is None:
|
self.update_print_job_info()
|
||||||
|
if (
|
||||||
self._log.warn("Printing state was triggered with empty print job")
|
self._printer.current_print_job is not None
|
||||||
return
|
and self._printer.current_print_job.progress >= 100
|
||||||
|
):
|
||||||
if self._printer.current_print_job.progress >= 100:
|
self._printer.finalize_print_job()
|
||||||
self._finish_print()
|
|
||||||
|
|
||||||
def update_print_job_info(self):
|
def update_print_job_info(self):
|
||||||
print_job_info = self._printer.bambu_client.get_device().print_job
|
print_job_info = self._printer.bambu_client.get_device().print_job
|
||||||
task_name: str = print_job_info.subtask_name
|
task_name: str = print_job_info.subtask_name
|
||||||
project_file_info = self._printer.project_files.get_file_by_suffix(
|
project_file_info = self._printer.project_files.get_file_by_stem(
|
||||||
task_name, [".3mf", ".gcode.3mf"]
|
task_name, [".gcode", ".3mf"]
|
||||||
)
|
)
|
||||||
if project_file_info is None:
|
if project_file_info is None:
|
||||||
self._log.debug(f"No 3mf file found for {print_job_info}")
|
self._log.debug(f"No 3mf file found for {print_job_info}")
|
||||||
self._current_print_job = None
|
self._current_print_job = None
|
||||||
|
self._printer.change_state(self._printer._state_idle)
|
||||||
return
|
return
|
||||||
|
|
||||||
progress = print_job_info.print_percentage
|
progress = print_job_info.print_percentage
|
||||||
self._printer.current_print_job = PrintJob(project_file_info, progress)
|
self._printer.current_print_job = PrintJob(project_file_info, progress)
|
||||||
self._printer.select_project_file(project_file_info.file_name)
|
self._printer.select_project_file(project_file_info.path.as_posix())
|
||||||
|
|
||||||
def pause_print(self):
|
def pause_print(self):
|
||||||
if self._printer.bambu_client.connected:
|
if self._printer.bambu_client.connected:
|
||||||
if self._printer.bambu_client.publish(pybambu.commands.PAUSE):
|
if self._printer.bambu_client.publish(pybambu.commands.PAUSE):
|
||||||
self._log.info("print paused")
|
self._log.info("print paused")
|
||||||
self._printer.change_state(self._printer._state_paused)
|
|
||||||
else:
|
else:
|
||||||
self._log.info("print pause failed")
|
self._log.info("print pause failed")
|
||||||
|
|
||||||
@ -88,17 +89,6 @@ class PrintingState(APrinterState):
|
|||||||
if self._printer.bambu_client.connected:
|
if self._printer.bambu_client.connected:
|
||||||
if self._printer.bambu_client.publish(pybambu.commands.STOP):
|
if self._printer.bambu_client.publish(pybambu.commands.STOP):
|
||||||
self._log.info("print cancelled")
|
self._log.info("print cancelled")
|
||||||
self._finish_print()
|
self._printer.finalize_print_job()
|
||||||
self._printer.change_state(self._printer._state_idle)
|
|
||||||
else:
|
else:
|
||||||
self._log.info("print cancel failed")
|
self._log.info("print cancel failed")
|
||||||
|
|
||||||
def _finish_print(self):
|
|
||||||
if self._printer.current_print_job is not None:
|
|
||||||
self._log.debug(
|
|
||||||
f"SD File Print finishing: {self._printer.current_print_job.file_info.file_name}"
|
|
||||||
)
|
|
||||||
self._printer.sendIO("Done printing file")
|
|
||||||
self._printer.current_print_job = None
|
|
||||||
|
|
||||||
self._printer.change_state(self._printer._state_idle)
|
|
||||||
|
@ -7,3 +7,11 @@
|
|||||||
###
|
###
|
||||||
|
|
||||||
.
|
.
|
||||||
|
|
||||||
|
pytest~=7.4.4
|
||||||
|
pybambu~=1.0.1
|
||||||
|
OctoPrint~=1.10.2
|
||||||
|
setuptools~=70.0.0
|
||||||
|
pyserial~=3.5
|
||||||
|
Flask~=2.2.5
|
||||||
|
paho-mqtt~=2.1.0
|
||||||
|
8
setup.py
8
setup.py
@ -14,20 +14,20 @@ plugin_package = "octoprint_bambu_printer"
|
|||||||
plugin_name = "OctoPrint-BambuPrinter"
|
plugin_name = "OctoPrint-BambuPrinter"
|
||||||
|
|
||||||
# The plugin's version. Can be overwritten within OctoPrint's internal data via __plugin_version__ in the plugin module
|
# The plugin's version. Can be overwritten within OctoPrint's internal data via __plugin_version__ in the plugin module
|
||||||
plugin_version = "0.0.23"
|
plugin_version = "1.0.0"
|
||||||
|
|
||||||
# The plugin's description. Can be overwritten within OctoPrint's internal data via __plugin_description__ in the plugin
|
# The plugin's description. Can be overwritten within OctoPrint's internal data via __plugin_description__ in the plugin
|
||||||
# module
|
# module
|
||||||
plugin_description = """Connects OctoPrint to BambuLabs printers."""
|
plugin_description = """Connects OctoPrint to BambuLabs printers."""
|
||||||
|
|
||||||
# The plugin's author. Can be overwritten within OctoPrint's internal data via __plugin_author__ in the plugin module
|
# The plugin's author. Can be overwritten within OctoPrint's internal data via __plugin_author__ in the plugin module
|
||||||
plugin_author = "jneilliii"
|
plugin_author = "ManuelW"
|
||||||
|
|
||||||
# The plugin's author's mail address.
|
# The plugin's author's mail address.
|
||||||
plugin_author_email = "jneilliii+github@gmail.com"
|
plugin_author_email = "manuelw@example.com"
|
||||||
|
|
||||||
# The plugin's homepage URL. Can be overwritten within OctoPrint's internal data via __plugin_url__ in the plugin module
|
# The plugin's homepage URL. Can be overwritten within OctoPrint's internal data via __plugin_url__ in the plugin module
|
||||||
plugin_url = "https://github.com/jneilliii/OctoPrint-BambuPrinter"
|
plugin_url = "https://gitlab.fire-devils.org/3D-Druck/OctoPrint-BambuPrinter"
|
||||||
|
|
||||||
# The plugin's license. Can be overwritten within OctoPrint's internal data via __plugin_license__ in the plugin module
|
# The plugin's license. Can be overwritten within OctoPrint's internal data via __plugin_license__ in the plugin module
|
||||||
plugin_license = "AGPLv3"
|
plugin_license = "AGPLv3"
|
||||||
|
17
templates/bambu_printer_settings.jinja2
Normal file
17
templates/bambu_printer_settings.jinja2
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label">{{ _('Connection Options') }}</label>
|
||||||
|
<div class="controls">
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" data-bind="checked: settings.plugins.bambu_printer.use_mqtt_bridge"> {{ _('Use MQTT Bridge') }}
|
||||||
|
<span class="help-block">
|
||||||
|
{{ _('Connect via a MQTT broker that bridges communications from the printer. Useful for connecting to a printer on a different network.') }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" data-bind="checked: settings.plugins.bambu_printer.disable_camera"> {{ _('Disable Camera Functionality') }}
|
||||||
|
<span class="help-block">
|
||||||
|
{{ _('Disable camera streaming and image capture to avoid connection errors. Enable this if you see frequent connection refused errors in the logs.') }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -2,8 +2,9 @@ from __future__ import annotations
|
|||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from octoprint_bambu_printer.printer.file_system.cached_file_view import CachedFileView
|
from octoprint_bambu_printer.printer.file_system.cached_file_view import CachedFileView
|
||||||
import pybambu
|
import pybambu
|
||||||
@ -29,7 +30,9 @@ def output_test_folder(output_folder: Path):
|
|||||||
|
|
||||||
@fixture
|
@fixture
|
||||||
def log_test():
|
def log_test():
|
||||||
return logging.getLogger("gcode_unittest")
|
log = logging.getLogger("gcode_unittest")
|
||||||
|
log.setLevel(logging.DEBUG)
|
||||||
|
return log
|
||||||
|
|
||||||
|
|
||||||
class DictGetter:
|
class DictGetter:
|
||||||
@ -89,7 +92,11 @@ def project_files_info_ftp():
|
|||||||
def cache_files_info_ftp():
|
def cache_files_info_ftp():
|
||||||
return {
|
return {
|
||||||
"cache/print.3mf": (1200, _ftp_date_format(datetime(2024, 5, 7))),
|
"cache/print.3mf": (1200, _ftp_date_format(datetime(2024, 5, 7))),
|
||||||
"cache/print2.3mf": (1200, _ftp_date_format(datetime(2024, 5, 7))),
|
"cache/print3.gcode.3mf": (1200, _ftp_date_format(datetime(2024, 5, 7))),
|
||||||
|
"cache/long file path with spaces.gcode.3mf": (
|
||||||
|
1200,
|
||||||
|
_ftp_date_format(datetime(2024, 5, 7)),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -187,8 +194,11 @@ def test_list_sd_card(printer: BambuVirtualPrinter):
|
|||||||
assert result[0] == b"Begin file list"
|
assert result[0] == b"Begin file list"
|
||||||
assert result[1].endswith(b'"print.3mf"')
|
assert result[1].endswith(b'"print.3mf"')
|
||||||
assert result[2].endswith(b'"print2.3mf"')
|
assert result[2].endswith(b'"print2.3mf"')
|
||||||
assert result[3] == b"End file list"
|
assert result[3].endswith(b'"print.3mf"')
|
||||||
assert result[4] == b"ok"
|
assert result[4].endswith(b'"print3.gcode.3mf"')
|
||||||
|
assert result[-3] == b"End file list"
|
||||||
|
assert result[-2] == b"ok"
|
||||||
|
assert result[-1] == b"ok"
|
||||||
|
|
||||||
|
|
||||||
def test_list_ftp_paths_p1s(settings, ftps_session_mock):
|
def test_list_ftp_paths_p1s(settings, ftps_session_mock):
|
||||||
@ -239,6 +249,67 @@ def test_list_ftp_paths_x1(settings, ftps_session_mock):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_delete_sd_file_gcode(printer: BambuVirtualPrinter):
|
||||||
|
with patch(
|
||||||
|
"octoprint_bambu_printer.printer.file_system.ftps_client.IoTFTPSConnection.delete_file"
|
||||||
|
) as delete_function:
|
||||||
|
printer.write(b"M30 print.3mf\n")
|
||||||
|
printer.flush()
|
||||||
|
result = printer.readlines()
|
||||||
|
assert result[-1] == b"ok"
|
||||||
|
delete_function.assert_called_with("print.3mf")
|
||||||
|
|
||||||
|
printer.write(b"M30 cache/print.3mf\n")
|
||||||
|
printer.flush()
|
||||||
|
result = printer.readlines()
|
||||||
|
assert result[-1] == b"ok"
|
||||||
|
delete_function.assert_called_with("cache/print.3mf")
|
||||||
|
|
||||||
|
|
||||||
|
def test_delete_sd_file_by_dosname(printer: BambuVirtualPrinter):
|
||||||
|
with patch(
|
||||||
|
"octoprint_bambu_printer.printer.file_system.ftps_client.IoTFTPSConnection.delete_file"
|
||||||
|
) as delete_function:
|
||||||
|
file_info = printer.project_files.get_file_data("cache/print.3mf")
|
||||||
|
assert file_info is not None
|
||||||
|
|
||||||
|
printer.write(b"M30 " + file_info.dosname.encode() + b"\n")
|
||||||
|
printer.flush()
|
||||||
|
assert printer.readlines()[-1] == b"ok"
|
||||||
|
assert delete_function.call_count == 1
|
||||||
|
delete_function.assert_called_with("cache/print.3mf")
|
||||||
|
|
||||||
|
printer.write(b"M30 cache/print.3mf\n")
|
||||||
|
printer.flush()
|
||||||
|
assert printer.readlines()[-1] == b"ok"
|
||||||
|
assert delete_function.call_count == 2
|
||||||
|
delete_function.assert_called_with("cache/print.3mf")
|
||||||
|
|
||||||
|
|
||||||
|
def test_select_project_file_by_stem(printer: BambuVirtualPrinter):
|
||||||
|
printer.write(b"M23 print3\n")
|
||||||
|
printer.flush()
|
||||||
|
result = printer.readlines()
|
||||||
|
assert printer.selected_file is not None
|
||||||
|
assert printer.selected_file.path == Path("cache/print3.gcode.3mf")
|
||||||
|
assert result[-2] == b"File selected"
|
||||||
|
assert result[-1] == b"ok"
|
||||||
|
|
||||||
|
|
||||||
|
def test_select_project_long_name_file_with_multiple_extensions(
|
||||||
|
printer: BambuVirtualPrinter,
|
||||||
|
):
|
||||||
|
printer.write(b"M23 long file path with spaces.gcode.3mf\n")
|
||||||
|
printer.flush()
|
||||||
|
result = printer.readlines()
|
||||||
|
assert printer.selected_file is not None
|
||||||
|
assert printer.selected_file.path == Path(
|
||||||
|
"cache/long file path with spaces.gcode.3mf"
|
||||||
|
)
|
||||||
|
assert result[-2] == b"File selected"
|
||||||
|
assert result[-1] == b"ok"
|
||||||
|
|
||||||
|
|
||||||
def test_cannot_start_print_without_file(printer: BambuVirtualPrinter):
|
def test_cannot_start_print_without_file(printer: BambuVirtualPrinter):
|
||||||
printer.write(b"M24\n")
|
printer.write(b"M24\n")
|
||||||
printer.flush()
|
printer.flush()
|
||||||
@ -278,9 +349,13 @@ def test_print_started_with_selected_file(printer: BambuVirtualPrinter, print_jo
|
|||||||
|
|
||||||
printer.write(b"M24\n")
|
printer.write(b"M24\n")
|
||||||
printer.flush()
|
printer.flush()
|
||||||
|
|
||||||
result = printer.readlines()
|
result = printer.readlines()
|
||||||
assert result[0] == b"ok"
|
assert result[-1] == b"ok"
|
||||||
|
|
||||||
|
# emulate printer reporting it's status
|
||||||
|
print_job_mock.gcode_state = "RUNNING"
|
||||||
|
printer.new_update("event_printer_data_update")
|
||||||
|
printer.flush()
|
||||||
assert isinstance(printer.current_state, PrintingState)
|
assert isinstance(printer.current_state, PrintingState)
|
||||||
|
|
||||||
|
|
||||||
@ -291,18 +366,26 @@ def test_pause_print(printer: BambuVirtualPrinter, bambu_client_mock, print_job_
|
|||||||
printer.write(b"M23 print.3mf\n")
|
printer.write(b"M23 print.3mf\n")
|
||||||
printer.write(b"M24\n")
|
printer.write(b"M24\n")
|
||||||
printer.flush()
|
printer.flush()
|
||||||
printer.readlines()
|
|
||||||
|
print_job_mock.gcode_state = "RUNNING"
|
||||||
|
printer.new_update("event_printer_data_update")
|
||||||
|
printer.flush()
|
||||||
assert isinstance(printer.current_state, PrintingState)
|
assert isinstance(printer.current_state, PrintingState)
|
||||||
|
|
||||||
bambu_client_mock.publish.return_value = True
|
printer.write(b"M25\n") # pausing the print
|
||||||
printer.write(b"M25\n") # GCode for pausing the print
|
|
||||||
printer.flush()
|
printer.flush()
|
||||||
result = printer.readlines()
|
result = printer.readlines()
|
||||||
assert result[0] == b"ok"
|
assert result[-1] == b"ok"
|
||||||
|
|
||||||
|
print_job_mock.gcode_state = "PAUSE"
|
||||||
|
printer.new_update("event_printer_data_update")
|
||||||
|
printer.flush()
|
||||||
assert isinstance(printer.current_state, PausedState)
|
assert isinstance(printer.current_state, PausedState)
|
||||||
|
bambu_client_mock.publish.assert_called_with(pybambu.commands.PAUSE)
|
||||||
|
|
||||||
|
|
||||||
def test_events_update_printer_state(printer: BambuVirtualPrinter, print_job_mock):
|
def test_events_update_printer_state(printer: BambuVirtualPrinter, print_job_mock):
|
||||||
|
print_job_mock.subtask_name = "print.3mf"
|
||||||
print_job_mock.gcode_state = "RUNNING"
|
print_job_mock.gcode_state = "RUNNING"
|
||||||
printer.new_update("event_printer_data_update")
|
printer.new_update("event_printer_data_update")
|
||||||
printer.flush()
|
printer.flush()
|
||||||
@ -338,10 +421,45 @@ def test_printer_info_check(printer: BambuVirtualPrinter):
|
|||||||
assert isinstance(printer.current_state, IdleState)
|
assert isinstance(printer.current_state, IdleState)
|
||||||
|
|
||||||
|
|
||||||
def test_abort_print(printer: BambuVirtualPrinter):
|
def test_abort_print_during_printing(printer: BambuVirtualPrinter, print_job_mock):
|
||||||
printer.write(b"M26\n") # GCode for aborting the print
|
print_job_mock.subtask_name = "print.3mf"
|
||||||
|
|
||||||
|
printer.write(b"M20\nM23 print.3mf\nM24\n")
|
||||||
|
printer.flush()
|
||||||
|
print_job_mock.gcode_state = "RUNNING"
|
||||||
|
print_job_mock.print_percentage = 50
|
||||||
|
printer.new_update("event_printer_data_update")
|
||||||
|
printer.flush()
|
||||||
|
printer.readlines()
|
||||||
|
assert isinstance(printer.current_state, PrintingState)
|
||||||
|
|
||||||
|
printer.write(b"M26 S0\n")
|
||||||
|
printer.flush()
|
||||||
|
result = printer.readlines()
|
||||||
|
assert result[-1] == b"ok"
|
||||||
|
assert isinstance(printer.current_state, IdleState)
|
||||||
|
|
||||||
|
|
||||||
|
def test_abort_print_during_pause(printer: BambuVirtualPrinter, print_job_mock):
|
||||||
|
print_job_mock.subtask_name = "print.3mf"
|
||||||
|
|
||||||
|
printer.write(b"M20\nM23 print.3mf\nM24\n")
|
||||||
|
printer.flush()
|
||||||
|
print_job_mock.gcode_state = "RUNNING"
|
||||||
|
printer.new_update("event_printer_data_update")
|
||||||
printer.flush()
|
printer.flush()
|
||||||
|
|
||||||
|
printer.write(b"M25\n")
|
||||||
|
printer.flush()
|
||||||
|
print_job_mock.gcode_state = "PAUSE"
|
||||||
|
printer.new_update("event_printer_data_update")
|
||||||
|
printer.flush()
|
||||||
|
|
||||||
|
printer.readlines()
|
||||||
|
assert isinstance(printer.current_state, PausedState)
|
||||||
|
|
||||||
|
printer.write(b"M26 S0\n")
|
||||||
|
printer.flush()
|
||||||
result = printer.readlines()
|
result = printer.readlines()
|
||||||
assert result[-1] == b"ok"
|
assert result[-1] == b"ok"
|
||||||
assert isinstance(printer.current_state, IdleState)
|
assert isinstance(printer.current_state, IdleState)
|
||||||
@ -369,7 +487,9 @@ def test_file_selection_does_not_affect_current_print(
|
|||||||
|
|
||||||
printer.write(b"M23 print.3mf\nM24\n")
|
printer.write(b"M23 print.3mf\nM24\n")
|
||||||
printer.flush()
|
printer.flush()
|
||||||
printer.readlines()
|
print_job_mock.gcode_state = "RUNNING"
|
||||||
|
printer.new_update("event_printer_data_update")
|
||||||
|
printer.flush()
|
||||||
assert isinstance(printer.current_state, PrintingState)
|
assert isinstance(printer.current_state, PrintingState)
|
||||||
assert printer.current_print_job is not None
|
assert printer.current_print_job is not None
|
||||||
assert printer.current_print_job.file_info.file_name == "print.3mf"
|
assert printer.current_print_job.file_info.file_name == "print.3mf"
|
||||||
@ -389,7 +509,9 @@ def test_finished_print_job_reset_after_new_file_selected(
|
|||||||
|
|
||||||
printer.write(b"M23 print.3mf\nM24\n")
|
printer.write(b"M23 print.3mf\nM24\n")
|
||||||
printer.flush()
|
printer.flush()
|
||||||
printer.readlines()
|
print_job_mock.gcode_state = "RUNNING"
|
||||||
|
printer.new_update("event_printer_data_update")
|
||||||
|
printer.flush()
|
||||||
assert isinstance(printer.current_state, PrintingState)
|
assert isinstance(printer.current_state, PrintingState)
|
||||||
assert printer.current_print_job is not None
|
assert printer.current_print_job is not None
|
||||||
assert printer.current_print_job.file_info.file_name == "print.3mf"
|
assert printer.current_print_job.file_info.file_name == "print.3mf"
|
||||||
@ -413,3 +535,28 @@ def test_finished_print_job_reset_after_new_file_selected(
|
|||||||
assert printer.current_print_job is None
|
assert printer.current_print_job is None
|
||||||
assert printer.selected_file is not None
|
assert printer.selected_file is not None
|
||||||
assert printer.selected_file.file_name == "print2.3mf"
|
assert printer.selected_file.file_name == "print2.3mf"
|
||||||
|
|
||||||
|
|
||||||
|
def test_finish_detected_correctly(printer: BambuVirtualPrinter, print_job_mock):
|
||||||
|
print_job_mock.subtask_name = "print.3mf"
|
||||||
|
print_job_mock.gcode_state = "RUNNING"
|
||||||
|
print_job_mock.print_percentage = 99
|
||||||
|
printer.new_update("event_printer_data_update")
|
||||||
|
printer.flush()
|
||||||
|
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 == 99
|
||||||
|
|
||||||
|
print_job_mock.print_percentage = 100
|
||||||
|
print_job_mock.gcode_state = "FINISH"
|
||||||
|
printer.new_update("event_printer_data_update")
|
||||||
|
printer.flush()
|
||||||
|
result = printer.readlines()
|
||||||
|
assert result[-3].endswith(b"1000/1000")
|
||||||
|
assert result[-2] == b"Done printing file"
|
||||||
|
assert result[-1] == b"Not SD printing"
|
||||||
|
assert isinstance(printer.current_state, IdleState)
|
||||||
|
assert printer.current_print_job is None
|
||||||
|
assert printer.selected_file is not None
|
||||||
|
assert printer.selected_file.file_name == "print.3mf"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user