From e64aebd1452abeeedcfe6dde0e9e7a55e06749a6 Mon Sep 17 00:00:00 2001 From: Gangoke Date: Mon, 15 Jun 2026 22:25:38 -1000 Subject: [PATCH] fix skip object from print dialog --- kobrax_moonraker_bridge.py | 80 +++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/kobrax_moonraker_bridge.py b/kobrax_moonraker_bridge.py index 54d4f82..acccd29 100644 --- a/kobrax_moonraker_bridge.py +++ b/kobrax_moonraker_bridge.py @@ -807,6 +807,9 @@ class KobraXBridge: # Part-Skip: zuletzt vom Drucker gemeldete Skip-Liste (v0.9.10) self._skip_state: dict = {"objects": [], "skipped": [], "ts": 0} + # Pre-Print-Skip: pending until confirmed/applied via skip/start + self._pending_preprint_skip: list[str] = [] + self._pending_preprint_skip_deadline: float = 0.0 # Theme-Name prüfen (keine Sonderzeichen oder Umlaute) raw_theme = (getattr(args, "ui_theme", None) or "default").strip() @@ -1060,11 +1063,21 @@ class KobraXBridge: """ d = payload.get("data") or {} skipped = d.get("objects_skip_parts") or d.get("skipped") or d.get("skipped_parts") or [] - # Liste immer (auch leer) übernehmen – sonst bleibt sie auf alten Stand + # Während ein Pre-Print-Skip noch als pending markiert ist, ignorieren wir + # leere Früh-Reports kurzzeitig, damit die UI nicht sofort zurückspringt. + now = time.time() + if (not skipped and self._pending_preprint_skip + and now <= self._pending_preprint_skip_deadline): + return + + # Liste übernehmen (auch leer), sobald kein Pending-Lock aktiv ist. self._skip_state = { "skipped": list(skipped), "ts": int(time.time()), } + if self._pending_preprint_skip and set(skipped) >= set(self._pending_preprint_skip): + self._pending_preprint_skip = [] + self._pending_preprint_skip_deadline = 0.0 if payload.get("state") == "done" or payload.get("code") == 200: log.info(f"Skip-Antwort: state={payload.get('state')} code={payload.get('code')} skipped={skipped}") @@ -2434,6 +2447,15 @@ class KobraXBridge: excluded_objects = body.get("excluded_objects") or [] if not isinstance(excluded_objects, list): excluded_objects = [] + # Pre-Print-Auswahl sofort im UI-Status spiegeln (wird später durch + # skip/report vom Drucker überschrieben/aktualisiert). + self._skip_state = {"skipped": list(excluded_objects), "ts": int(time.time())} + if excluded_objects: + self._pending_preprint_skip = [str(n) for n in excluded_objects if isinstance(n, str) and n] + self._pending_preprint_skip_deadline = time.time() + 12.0 + else: + self._pending_preprint_skip = [] + self._pending_preprint_skip_deadline = 0.0 if assignments: ams_box_mapping, unused_count, invalid_count = self._build_assigned_ams_box_mapping(assignments) @@ -2476,6 +2498,9 @@ class KobraXBridge: "ai_settings": {"status": 0, "count": 0, "type": 1}, "timelapse": {"status": 0, "count": 0, "type": 64}, "drying_settings": {"status": 0, "target_temp": 0, "duration": 0, "remain_time": 0}, + # Firmware-Variante A (funktioniert bei mehreren Builds) + "objects_skip_parts": excluded_objects, + # Firmware-Variante B (beibehalten für Kompatibilität) "model_objects_skip_parts": excluded_objects, }, } @@ -2488,6 +2513,9 @@ class KobraXBridge: if result is None: return self._json_cors({"error": "Keine Antwort vom Drucker"}, status=504) + if excluded_objects: + loop.run_in_executor(None, lambda: self._apply_preprint_skip_after_start(excluded_objects)) + # Job in History starten self._current_job_id = self._store.start_job( gcode_file_id=gcode_file["id"], @@ -2860,6 +2888,10 @@ class KobraXBridge: def _start_print(self, filename: str, url: str = "", md5: str = "", filesize: int = 0, gcode_filaments: list | None = None): self._state["file_ready"] = "" + # Neuer Druck ohne Pre-Print-Excludes: Skip-State zurücksetzen. + self._skip_state = {"skipped": [], "ts": int(time.time())} + self._pending_preprint_skip = [] + self._pending_preprint_skip_deadline = 0.0 loaded = self._select_loaded_slots_for_print(warn_on_empty_default=True) # Nur die im GCode TATSÄCHLICH genutzten Paints auf Slots mappen. OrcaSlicer @@ -2915,6 +2947,37 @@ class KobraXBridge: else: log.warning("Druckstart: keine Antwort vom Drucker") + def _apply_preprint_skip_after_start(self, names: list[str], retries: int = 20, delay_s: float = 0.75): + wanted = [str(n) for n in (names or []) if isinstance(n, str) and n] + if not wanted: + return False + + self._pending_preprint_skip = list(wanted) + self._pending_preprint_skip_deadline = time.time() + max(3.0, float(retries) * float(delay_s) + 2.0) + + # UI should already reflect intended skips immediately. + self._skip_state = {"skipped": list(wanted), "ts": int(time.time())} + + for i in range(max(1, int(retries))): + try: + if self._state.get("kobra_state") != "printing": + time.sleep(max(0.1, float(delay_s))) + continue + resp = self.client.skip_objects(wanted) + if resp is not None: + log.info(f"Pre-Print skip applied ({len(wanted)} objects) on attempt {i+1}/{retries}") + self._pending_preprint_skip = [] + self._pending_preprint_skip_deadline = 0.0 + return True + except Exception as e: + log.debug(f"Pre-Print skip attempt {i+1}/{retries} failed: {e}") + time.sleep(max(0.1, float(delay_s))) + + log.warning(f"Pre-Print skip could not be confirmed after {retries} attempts") + self._pending_preprint_skip = [] + self._pending_preprint_skip_deadline = 0.0 + return False + def _theme_index_path(self) -> str: return os.path.join(_WEB_BASE, "web", "themes", self._ui_theme, "index.html") @@ -2958,6 +3021,15 @@ class KobraXBridge: excluded_objects = body.get("excluded_objects") or [] if not isinstance(excluded_objects, list): excluded_objects = [] + # Pre-Print-Auswahl sofort im UI-Status spiegeln (wird später durch + # skip/report vom Drucker überschrieben/aktualisiert). + self._skip_state = {"skipped": list(excluded_objects), "ts": int(time.time())} + if excluded_objects: + self._pending_preprint_skip = [str(n) for n in excluded_objects if isinstance(n, str) and n] + self._pending_preprint_skip_deadline = time.time() + 12.0 + else: + self._pending_preprint_skip = [] + self._pending_preprint_skip_deadline = 0.0 if filament_assignments is not None: ams_box_mapping, unused_count, invalid_count = self._build_assigned_ams_box_mapping(filament_assignments) if unused_count: @@ -2997,6 +3069,9 @@ class KobraXBridge: "ai_settings": {"status": 0, "count": 0, "type": 0}, "timelapse": {"status": 0, "count": 0, "type": 0}, "drying_settings": {"status": 0, "target_temp": 0, "duration": 0, "remain_time": 0}, + # Firmware-Variante A (funktioniert bei mehreren Builds) + "objects_skip_parts": excluded_objects, + # Firmware-Variante B (beibehalten für Kompatibilität) "model_objects_skip_parts": excluded_objects, }, } @@ -3013,6 +3088,9 @@ class KobraXBridge: if result is None: return web.json_response({"error": "Keine Antwort vom Drucker"}, status=504) + if excluded_objects: + loop.run_in_executor(None, lambda: self._apply_preprint_skip_after_start(excluded_objects)) + return web.json_response({"result": "ok"}) async def handle_print_pause(self, request):