The pages written on top of the old README inherited its premise: that after flashing you face a gauntlet -- go/no-go checks, a safety net, a mandatory calibration -- before the machine can be trusted with a model. That is not what happens. The factory numbers import themselves on the first boot, a bed mesh loads at the start of every print, and the mod is built to run the files the stock firmware ran. A printer that printed before the flash prints after it. "Your first print" now says so in its first line, and everything under it is optional, ordered by what you would actually reach for: check the offsets are applied, and calibrate only if that disagrees with the machine. Three audiences now, because owner and contributor was not the real split. "For experienced users" sits between them: How a print runs, and How calibration works. Someone who knows Klipper and wants to change what happens around their file has a place to go that is neither a walkthrough nor the build system. What was cut is most of what changed. Pages that were cryptic to the reader they were aimed at -- Printing, Upgrading -- are out of the nav and kept on disk to be rewritten rather than deleted. Within the pages that stayed: Expected output, Probe sampling, What is being measured, the symptom table, the dark-screen diagnosis, TOOL_Z_ADJUST, PLATE_CHECK, the ff-*.cfg inventory, [ff_legacy] and SAVE_CONFIG in a paragraph about factory calibration. Filenames and internals earn their place on an owner page only when the reader has to type them or look at them; the rest moved to the experienced pages or to Internals, where the audience wants them. Calibration became a section rather than a page, because tool offsets are not the only calibration: bed mesh has its own page now, and its own gotcha written down -- BED_MESH_CALIBRATE writes `default`, a print loads `MESH_DATA`, so a mesh probed by hand is a mesh no print will use. Input shaping is described but has no procedure page yet. Several claims came out because they were not true. "Is this for you" listed calibration and input shaping as things the mod gives you; both are the touchscreen's own sequences, recovered and reimplemented, and the notes say so plainly. What the mod actually gives you is that anything speaking Moonraker can drive the machine -- on stock, FlashForge's Klipper does not toolchange at all, it raises a flag and waits for an application that is not watching a Mainsail print, and the print hangs at the first T2. The page now opens by telling most owners not to flash it. Contributors are still deferred. Their pages are regrouped, not rewritten. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S3XZh4SqPdXAowVndz6tMr
4.9 KiB
The print pipeline
What drives a print on stock firmware, what breaks when the application is not there to drive it, and what this mod sends in its place. The owner-facing version of this — why a stock-sliced file still prints, and the two knobs for changing that — is How a print runs.
Addresses and the recovery work behind the stock side are in Print lifecycle and firmwareExe vs Klipper; this page is the comparison rather than the source.
Stock, step by step
The chain is BuildPage::doPreparetion → prepareForEddy →
BuildPage::startPrint → CommMgr::serialPrint, the last being a 37 KB
engine thread inside the application. Preparation, in order:
G90,M82,BED_MESH_CLEAR,M400G28, aborting on failureSET_GCODE_OFFSET X=0 Y=0 MOVE=1 MOVE_SPEED=600- two probe touch checks, internal to the app
- heat bed and chamber; dock whatever tool is held; presence-check the file's first tool — the app's E0165
SET_IDLE_TIMEOUT TIMEOUT=1800000; eddy probe at (265, 4.8); nozzle clean of every tool the file uses — purge at the chute, wipe at (266.5, 13.8), cool by 100 °C, release- bed and chamber soak, 5 minutes from the bed reaching target, then a
G28 Zre-home - leveling toggle: on →
BED_MESH_CALIBRATEatACCEL=2000thenBED_MESH_PROFILE LOAD=default; off →LOAD=MESH_DATA. A mesh is always active for a print - heat and grab the first tool,
G1 Z10 F1200for clearance
Then the absolute print Z offset — the ~3.2 mm eddy-to-nozzle gap, computed
once — and the preamble: G1 X250 F2400 to clear the docks,
SDCARD_SET_CHANNEL CHANNEL=<tool>, M21 → M23 /<name> → M26 S0 →
M24, SET_IDLE_TIMEOUT TIMEOUT=864000. No heating is sent for a fresh
print: the temperatures came from preparation and from the file.
Pause is inline in that thread — the UI buttons send no G-code of their own.
It sends M400 and M25, waits for the SD state to read paused, saves every
tool's target, SAVE_GCODE_STATE NAME=PAUSE_state, turns all hotends off
(bed and chamber stay), parks upward, and writes a power-loss record. Resume
reheats in two stages, e1+e2 then e3+e4, re-grabs the head if needed, and
restores. Cancel and normal completion share one exit block.
Where a Mainsail print breaks on stock
This is the specific thing that makes the mod necessary rather than merely
nicer. FlashForge's Klipper fork intercepts toolchange lines in
virtual_sdcard.py before they reach the G-code engine:
if line.startswith("T") and line in VALID_GCODE_T:
self.print_channel = int(line[1:])
if self.print_channel != self.load_channel:
self.gcode.run_script("M400")
self.change_filament = True # exposed as 'refuelling'
self.doingChangeEx = True
while self.change_filament: # busy-waits for the UI app
self.reactor.pause(...)
The fork performs no motion at all. It raises a flag and waits for
firmwareExe to do the physical change and send SDCARD_CLEAR_REFUELLING. For
a print nobody is servicing, that loop never ends.
(line comes from data.split('\n') and is never stripped, so T2 ; anything
did not match and fell through to the G-code engine. Earlier releases of this
mod exploited exactly that with a ; ff-toolchange marker in the slicer's
change-filament field. We ship upstream virtual_sdcard now, so bare Tn
reaches ff_toolchange directly and the marker is gone — but files still
carrying it print correctly, because the parser discards the comment.)
Here, step by step
[ff_print] hooks SDCARD_PRINT_FILE and M23, and calls two macros around
the job: FF_BEFORE_PRINT_START before the file's first line and
FF_AFTER_PRINT_END once it leaves the printing state. CANCEL_PRINT is
overridden as well.
FF_BEFORE_PRINT_START always runs _FF_PREFLIGHT — the calibration and
tool-presence gate raises before anything heats, homes or grabs, and there is
no origin for which skipping it is right. It then runs START_PRINT if its
prepare variable is 1, which is the shipped default.
START_PRINT follows the app's order, with the app's own addresses noted in
the config beside each step: _FF_PREFLIGHT for every tool in TOOLS=, then
G90, M82, BED_MESH_CLEAR, G28, SET_GCODE_OFFSET X=0 Y=0 MOVE=1,
SET_IDLE_TIMEOUT TIMEOUT=864000, M140 so the bed heats during the
clean, _FF_NOZZLE_CLEAN for the used tools, M190 and the optional soak,
G28 Z, the mesh (calibrate or load), M104 and T<n> to grab the first
tool, and TOOLCHANGE_SET_PRINT_OFFSET for the thermal, bed and thin-layer
terms.
The Z frame differs from the app's by design. The app applied one absolute
offset per print; here every T<n> grab applies that tool's own
nozzle_z − station_z + z_adjust, so Z=0 is the bed whenever a tool is
mounted, and TOOLCHANGE_SET_PRINT_OFFSET adds only the print-time terms.