######################################################################## # BD Pressure Advance x AFC BoxTurtle x Spoolman Integration # --------------------------------------------------------------- # Built and verified using ONLY documented/source-confirmed APIs: # # bd_pressure (https://github.com/markniu/bd_pressure): # PA_CALIBRATE NOZZLE_TEMP= MAX_VOLUMETRIC= ACC_WALL= # TRAVEL_SPEED= ACC_TO_DECEL_FACTOR= # - filament must already be loaded into the nozzle before calling # - the command itself homes, heats, calibrates, and calls # SET_PRESSURE_ADVANCE -- no separate heating step is needed # Source: https://pandapi3d.cn/bdpressure/home # # AFC-Klipper-Add-On (https://github.com/ArmoredTurtle/AFC-Klipper-Add-On): # printer.AFC.current_load -- a live status field (confirmed in # klippy/extras/AFC.py get_status(): str['current_load'] = self.current) # that always reflects whichever lane is currently loaded in the # toolhead, regardless of how it got there (T0-T4, CHANGE_TOOL, # BT_CHANGE_TOOL, or AFC's own infinite-spool runout remapping). # Confirmed working live on this printer via a test macro. # SET_SPOOL_ID LANE=.. SPOOL_ID=.. -- assigns a spool, AFC then pulls # material/color/weight/temps from Spoolman. # Source: https://www.armoredturtle.xyz/docs/afc-klipper-add-on/ # and confirmed directly against installed AFC source. # # Klipper core (https://www.klipper3d.org/Command_Templates.html): # [save_variables] + SAVE_VARIABLE / printer.save_variables.variables # -- persists the PA cache and lane->spool_id tracking across # restarts. No shell_command/curl/Moonraker REST calls needed. # [delayed_gcode] + UPDATE_DELAYED_GCODE -- used to poll # printer.AFC.current_load every 2 seconds and detect tool changes # (see "WHY POLLING" below for why this is the mechanism, instead # of intercepting a tool-change command directly). # # WHY POLLING INSTEAD OF INTERCEPTING A TOOL-CHANGE COMMAND # --------------------------------------------------------------------- # This was not the first design tried. Three other approaches were # attempted and ruled out, each one verified directly against this # printer's installed AFC Python source (not just AFC's docs, which # turned out to describe behavior that didn't match the installed # version in two separate cases): # # 1. AFC_POST_PREP (an AFC-documented "fires after every lane load" # macro hook): verified against source (AFC_lane.py) that this is # tied to a lane's prep+load SENSOR transition (filament freshly # inserted into the physical lane) and to an unset-by-default # `post_prep_macro` config option -- not to a tool change moving # filament into the toolhead. An empirical test on a real tool # change confirmed it never fires for that case. # # 2. Wrapping CHANGE_TOOL with rename_existing: CHANGE_TOOL the gcode # command is real (registered via register_command in AFC.py) and # the wrapper DOES work when CHANGE_TOOL is called by that exact # name -- confirmed working live. But T0-T4 (what slicers and # normal use actually send) do NOT route through it: verified in # AFC_functions.py that T(n) commands are registered with # `self.afc.gcode.register_command(cur_lane.map, self.afc.cmd_CHANGE_TOOL, ...)` # -- a direct reference to the original Python method captured at # registration time. Renaming the "CHANGE_TOOL" dispatcher key has # zero effect on these separately-registered "T0".."T4" keys. # Confirmed empirically: CHANGE_TOOL LANE=lane1 typed directly # applied cached PA correctly; T1 typed directly did not. # # 3. Wrapping T0-T4 directly with rename_existing: ruled out for two # reasons. First, T(n) commands are registered dynamically at PREP # time (a runtime event), not at config-load time, so # rename_existing (config-load-time only) can't reliably target # them. Second, even if that were solved, AFC's SET_MAP / # RESET_AFC_MAPPING / infinite-spool-runout features rely on which # lane a given T(n) points to being mutable at runtime -- so # statically pinning T0-T4 via `map:` in lane config would fight # that real, separate AFC feature. # # Given all of that, polling printer.AFC.current_load (a plain status # read with no dependency on which command triggered the change) is the # most robust mechanism available: it works identically regardless of # whether a tool change was triggered by T0-T4, CHANGE_TOOL, # BT_CHANGE_TOOL, or AFC's internal runout remapping, and needs no # load-order considerations at all. The cost is a small polling delay # (up to ~2 seconds) between a tool change finishing and PA being # applied, instead of it happening in the same instant. # # OTHER PAST MISTAKES, FOR REFERENCE # --------------------------------------------------------------------- # - "BD_SENSOR_CALIBRATE_PA" is not a real command. Real: PA_CALIBRATE, # with required parameters (see above). # - "printer.afc.lanes[lane].spool_id" is not a real, readable Klipper # status path. AFC's per-lane spool data partly lives in Moonraker's # database (namespace "lane_data"), which a Klipper gcode_macro # cannot reach directly (no HTTP from Jinja2 templates). This file # tracks lane->spool_id itself instead (see _PA_TRACK_LANE_SPOOL). # - SET_SPOOL_ID is NOT wrapped with rename_existing, because it is # registered via register_command (confirmed against installed AFC # source) and wrapping it failed with "Existing command 'SET_SPOOL_ID' # not found in gcode_macro rename" when this file's [include] line # in printer.cfg appeared BEFORE AFC's own includes. The # companion-macro approach (_PA_TRACK_LANE_SPOOL, called manually # right after SET_SPOOL_ID) has no such load-order dependency, at # the small cost of one extra line each time you assign a spool. # - Calibration nozzle temp comes from printer.extruder.target (a # documented Klipper status field) at calibration time. AFC already # sets this from Spoolman's nozzle_temp field when a lane is loaded # (AFC's own documented "Setting extruder temp" feature), so this # answers "can the calibration temp come from Spoolman" with a real # yes, via a path that needs no new plumbing. # # KNOWN LIMITATIONS / THINGS TO VERIFY ON YOUR PRINTER # --------------------------------------------------------------------- # 1) You must remember to call _PA_TRACK_LANE_SPOOL LANE=.. SPOOL_ID=.. # every time you assign a spool to a lane (right after SET_SPOOL_ID, # or after assigning via the Mainsail/Fluidd spool UI). If you skip # this, LANE_CALIBRATE_PA and the auto-apply poller will report # "no spool_id recorded yet" for that lane. PA_STATUS shows you which # lanes are missing this. # # 2) PA is applied up to ~2 seconds after a tool change completes, not # instantly, because of the polling interval. If you need it tighter, # lower the DURATION values in _PA_POLL_LANE_CHANGE below (the # initial_duration and both UPDATE_DELAYED_GCODE DURATION values), # at the cost of slightly more frequent background checks. # # 3) If printer.AFC ever doesn't exist (AFC failed to load, or a future # AFC version renames its status object), _PA_POLL_LANE_CHANGE will # print one warning message and then go silent -- it will NOT spam # the console, and it will NOT apply PA automatically until this is # resolved. PA_STATUS and manual LANE_CALIBRATE_PA calls still work # fine in that case; only the automatic apply-on-tool-change part is # affected. # REQUIRED SETUP # --------------------------------------------------------------------- # 1) [save_variables] is declared below in this same file (see the # [save_variables] section right after this header) -- you do NOT # need to add it to printer.cfg separately. # IMPORTANT: if you already have a [save_variables] section # somewhere else in your config (printer.cfg or another include), # DELETE the one below -- Klipper will error on a duplicate section # if it appears twice anywhere in your merged config. Only one # [save_variables] section is allowed printer-wide, but it stores # ALL variables from ALL macros in one shared file, so having it # declared once, here, works fine for everything in your setup. # # 2) Make sure moonraker.conf has [spoolman] configured so AFC can pull # nozzle_temp/material from Spoolman when a lane is loaded: # [spoolman] # server: http://:7912 # sync_rate: 5 # # 3) Add to printer.cfg: # [include bd_pa_afc_spoolman.cfg] # (Include order relative to AFC no longer matters for this version # -- the polling approach has no load-order dependency, unlike the # CHANGE_TOOL/SET_SPOOL_ID wrapper attempts described above.) # # 4) Whenever you assign a spool to a lane -- via SET_SPOOL_ID, or via # the Mainsail/Fluidd spool-assignment UI -- also call # _PA_TRACK_LANE_SPOOL with the same lane and spool_id, e.g.: # SET_SPOOL_ID LANE=lane1 SPOOL_ID=12345 # _PA_TRACK_LANE_SPOOL LANE=lane1 SPOOL_ID=12345 # This is what lets the PA cache know which spool_id is in which # lane (see KNOWN LIMITATIONS #1 above for why this is manual). # # USAGE # --------------------------------------------------------------------- # After assigning spools to lanes (step 4 above), in PRINT_START, # after your PREP call, run: # PA_CALIBRATE_ALL_LANES LANES="lane1,lane2,lane3,lane4,lane5" # This calibrates (or restores from cache) PA for every lane listed # that currently has a spool_id recorded. # # Per-lane PA is then applied automatically within ~2 seconds of every # tool change, via the _PA_POLL_LANE_CHANGE background poller -- no # action needed on your part once it's running. # # To force a re-calibration of one lane: # LANE_RECAL_PA LANE=lane2 # # To see what's cached, and which lanes are missing a tracked # spool_id: # PA_STATUS ######################################################################## # --------------------------------------------------------------------------- # Persistent variable storage -- required by SAVE_VARIABLE/printer.save_variables # used throughout this file for the PA cache and lane->spool_id tracking. # DELETE THIS SECTION if you already have a [save_variables] section # defined anywhere else in your config (only one is allowed printer-wide). # --------------------------------------------------------------------------- [save_variables] filename: ~/printer_data/config/bd_pa_variables.cfg # The file above does not need to exist yet -- Klipper creates it on # first SAVE_VARIABLE call and reloads it on every restart. # --------------------------------------------------------------------------- # USER CONFIGURATION -- edit these to match your printer # --------------------------------------------------------------------------- [gcode_macro _PA_CFG] description: Central config -- edit values here variable_lane_names: "lane1,lane2,lane3,lane4,lane5" # Fallback nozzle temp (C) used ONLY if the extruder's target temp reads 0 # at calibration time (e.g. you called this macro before AFC set a temp) variable_fallback_nozzle_temp: 260 # bd_pressure PA_CALIBRATE parameters -- tune to your printer/filament. # See https://pandapi3d.cn/bdpressure/home for guidance on these values. variable_max_volumetric: 25 variable_acc_wall: 5000 variable_travel_speed: 300 variable_acc_to_decel_factor: 50 # Minimum PA delta before we bother re-saving the cached value variable_write_threshold: 0.002 # Spoolman URL and the custom "extra" field name used to push calibrated # PA values back to the spool record (so the value travels with the # spool, not just with this printer's local save_variables cache). # Create the field once with: _PA_SETUP_SPOOLMAN_FIELD (see below). variable_spoolman_url: "http://localhost:7912" variable_spoolman_field: "pa_value" gcode: # intentionally empty -- variables only # --------------------------------------------------------------------------- # Spoolman write-back: push a calibrated PA value to a spool's "extra" # field, so the value travels with the spool (e.g. if you move it to a # different printer, or reinstall save_variables from scratch). # # Requires the custom field to exist once -- run _PA_SETUP_SPOOLMAN_FIELD # (below) a single time, or create it manually: # curl -X POST http://:7912/api/v1/field/spool/pa_value -H "Content-Type: application/json" -d '{"name": "Pressure Advance", "field_type": "float", "default_value": "0"}' # # UNVERIFIED -- needs live testing the same way everything else in this # file has needed it. In particular: confirm curl and python3 are both # available in the shell shell_command runs in (they're standard on # MainsailOS/FluiddOS, but worth confirming), and confirm the merge-write # below doesn't clobber any other custom Spoolman fields you may have. # --------------------------------------------------------------------------- [gcode_shell_command _pa_write_spoolman] # HISTORICAL NOTE ON ARGUMENT OFFSET (no longer applicable here, kept # for context): an earlier version of this command ran the script # inline via `bash -c '