mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-09-30 11:12:35 +02:00
The photo fired the moment layer_num reached total_layer_num. That edge is where the printer *starts* its final layer, not where it finishes it: the reporter's H2C capture shows it arriving at 92% with mc_remaining_time=2, three minutes and seventeen seconds and one filament change before the print actually ended, so the frame caught the toolhead mid-print over the model. The trigger also latched _finish_photo_captured, which locked out both the stage-22 and FINISH triggers for the rest of the print — so on firmware that never reports an end-of-print filament unload (H2C and A1 Mini confirmed) nothing could replace the bad frame. Remove the last-layer trigger. The photo is now taken at the FINISH-state trigger, which every model sends and which lands after the toolhead parks. Since Bambu's end G-code drops the plate ~100mm just before that, restore the framing before capturing: absolute G90/G1 Z to max_z_height + 10mm clearance, settle, capture, then drop it back so the print is as reachable as the printer left it. Absolute is the safety argument — that Z is a height the toolhead occupied seconds earlier, so it is inside the travel limits by construction and leaves the nozzle above the part, and it is unambiguous across model families because Z is the nozzle-to-bed gap whether the bed moves or the toolhead does. M211 is never touched (#2579). This is what #1145, #1397 and #1565 asked for. The height is only trusted when two independent sources agree: the archive is matched by the finished print's subtask_name by equality (not LIKE, so "Cube" cannot resolve to "Cube v2"), and its layer count from the 3MF must match the layer count the printer reported over MQTT. Matching on "most recent archive for this printer" was not safe — on_print_complete pops the _active_prints binding concurrently, and a print Bambuddy failed to archive would have resolved to its predecessor. A wrong height is the one failure that could drive the nozzle into the model. The move is additionally skipped when the print height is unknown, when a queue item is pending for the printer, when the printer has left FINISH, and when the new finish_photo_restore_plate setting is off. for every FINISH-state capture — which is what shipped the mid-print photo — the bank is used only when the dispatcher recorded that it injected End G-code into this print, since a SwapMod snippet may have ejected the plate. The flag is handed over in two steps (mark_pending at dispatch, adopt at print start) so it can never outlive its print: a job started from the slicer or SD card adopts False rather than inheriting its predecessor's answer. Those prints also skip the plate move outright, bank or no bank. The bank now refreshes on mc_percent advances as well as layer changes, via a new on_print_progress callback. Layer changes stop the instant the final layer begins, which left the #1867 fallback frame stale by the whole length of that layer; progress keeps ticking there and freezes before the End G-code runs, so a swapped plate still cannot reach the bank. The last-layer throttle exemption is dropped, since it would now fire a grab on every percent tick. On the timelapse path the moment producer returns early, so the consumer does the restore itself before its live-grab fallback — the documented usual outcome on P1-series, where the video has not transferred by the time the notification goes out and the shipped photo was of an already-dropped plate. The two waits are now derived from the settle window and the video poll timeout rather than hardcoded; at the old flat 75s that fallback was guaranteed to be cut off mid-settle. extract_max_z_height_from_3mf reads only a bounded prefix of the plate G-code, since a sliced plate is routinely tens of megabytes and the header is ~40 lines. It returns None for missing, unparseable, zero and negative values so callers must treat "don't know" as such rather than defaulting.
69 lines
2.6 KiB
Python
69 lines
2.6 KiB
Python
"""Whether Bambuddy injected End G-code into the print now running (#2547).
|
|
|
|
The finish-photo path has to know one thing at print completion that no MQTT
|
|
field reports: did this print end with user End G-code? If it did, a SwapMod
|
|
snippet may already have ejected the plate, so the scene in front of the camera
|
|
at ``gcode_state=FINISH`` is not the finished print and the photo must come from
|
|
the in-print frame bank instead (#1867).
|
|
|
|
Only the dispatcher ever sees this, so it is recorded here in two steps:
|
|
|
|
1. ``mark_pending`` when the scheduler injects an End G-code snippet.
|
|
2. ``adopt`` when the printer reports a print starting, which moves the pending
|
|
flag onto the running print and consumes it.
|
|
|
|
The two steps exist so the flag can never outlive its print. A print Bambuddy
|
|
did not dispatch — started from the slicer, the SD card, or the printer's own
|
|
screen — finds no pending flag and correctly adopts ``False``, instead of
|
|
inheriting the answer from whatever ran before it.
|
|
|
|
In-memory and best-effort: a restart mid-print loses the flag, and ``False`` is
|
|
the safe way to be wrong (a live grab that might show a swapped plate, rather
|
|
than silently substituting a mid-print frame).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Printers the scheduler has injected End G-code for, awaiting a print start.
|
|
_pending: set[int] = set()
|
|
# Printers whose *currently running* print has injected End G-code.
|
|
_active: set[int] = set()
|
|
|
|
|
|
def mark_pending(printer_id: int) -> None:
|
|
"""Record that the job now being sent to ``printer_id`` has End G-code."""
|
|
_pending.add(printer_id)
|
|
logger.debug("[DISPATCH-CTX] printer %s: End G-code injected, awaiting print start", printer_id)
|
|
|
|
|
|
def adopt(printer_id: int) -> bool:
|
|
"""Bind any pending flag to the print that just started, and return it.
|
|
|
|
Called once per print start. Always writes ``_active`` — including the
|
|
``False`` case — so a print Bambuddy didn't dispatch clears its
|
|
predecessor's flag rather than inheriting it.
|
|
"""
|
|
injected = printer_id in _pending
|
|
_pending.discard(printer_id)
|
|
if injected:
|
|
_active.add(printer_id)
|
|
logger.debug("[DISPATCH-CTX] printer %s: running print has injected End G-code", printer_id)
|
|
else:
|
|
_active.discard(printer_id)
|
|
return injected
|
|
|
|
|
|
def end_gcode_injected(printer_id: int) -> bool:
|
|
"""True if the print currently running on ``printer_id`` has End G-code."""
|
|
return printer_id in _active
|
|
|
|
|
|
def clear(printer_id: int) -> None:
|
|
"""Forget everything about this printer (disconnect, removal, tests)."""
|
|
_pending.discard(printer_id)
|
|
_active.discard(printer_id)
|