Files
firmware/payload
Oleksandr ShyshatskyiandClaude Opus 5 168020ab0d Close four holes in the print and calibration paths
_FF_PREFLIGHT now runs for every job origin, not only SDCARD_PRINT_FILE. It
was gated on that origin, and prepare_on_m23 defaults to 0, so an M23-started
print got neither the prepare nor the gate -- skipping the check that exists
to stop the nozzle being driven ~3 mm into the plate. The gate only raises; it
moves and heats nothing, so no origin is a reason to skip it. The
prepare_on_m23 comment is corrected too: it was written for FlashForge's
touchscreen preparing before M23, and that binary is replaced.

_FF_JOB.ended is now cleared when a job starts rather than only when
FF_AFTER_PRINT_END runs. It exists so a slicer footer's END_PRINT cannot make
the exit block run twice, but only ff_print clears it, and only for a job it
announced (self.active is armed after before_macro succeeds). So an END_PRINT
or CANCEL_PRINT typed at the console while idle latched it -- and the NEXT
print silently skipped its whole exit block: hotends and bed left at target,
tool never docked, Z offset never reset, idle timeout left at 864000 s.
CANCEL_PRINT calls END_PRINT, and both shipped gcode files call it directly,
so this was reachable without touching a console at all.

END_PRINT's SET_GCODE_OFFSET Z=0 MOVE=1 moved outside the homed_axes guard
written two lines above it. MOVE=1 makes gcode_move issue a real Z move and
cartesian.check_move refuses one on an unhomed Z, so a failed G28 Z earlier in
the job made END_PRINT raise there -- skipping M18, the idle-timeout reset and
the ended flag, which then let FF_AFTER_PRINT_END run the block a second time.
Unhomed now clears the offset with MOVE=0.

Calibration left the Z frame broken. _two_pass zeroes the G-code offsets to
probe in raw coordinates and never restores them, and RELEASE defaults to 0,
so TOOL_OFFSET_CALIBRATE ended with a tool mounted and Z=0 at the eddy plane,
~3.2 mm below the bed. Worse, ff_toolchange._z_tool_term still held the
pre-calibration term, so the next grab computed its babystep against something
no longer applied and propagated the zeroing instead of curing it. New
ff_toolchange.reapply_offsets_after_external_zero() resets the term and
re-applies the mounted tool's offsets; both calibration commands call it.

GRAB=0 was silently defeated. It means the operator mounted the tool by hand,
but the plate check parks whatever is mounted to measure an empty carriage --
so it undid the hand-mount and stored the bare carriage as that tool's nozzle
position, out by the full gap in the crash direction, with the gap guard
unable to catch it before a station_z exists. It now refuses with the two ways
out rather than doing the wrong thing quietly.

54 passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 19:48:45 +03:00
..