Compare commits

...
22 Commits
Author SHA1 Message Date
mangavalkandClaude Opus 5.5 87a06f9c30 First layer: AFC fault detection less sensitive
A large, slow first layer tripped AFC's buffer fault detection ("AFC NOT
FEEDING"). _FIRST_LAYER_FAULT_DETECTION ON=1 at the end of PRINT_START
sets first_layer_sensitivity (4: pause after 70 mm instead of 30 mm);
ON=0 restores the [AFC_buffer] config value. The slicer calls ON=0 at
the start of layer 2:
  {if layer_num == 1}_FIRST_LAYER_FAULT_DETECTION ON=0{endif}
_PRINT_CLEANUP calls it too, for prints that end in the first layer.
ON=0 only restores what ON=1 changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 16:47:11 +02:00
mangavalkandClaude Opus 5.5 a63b66a7d8 bdp_guard: new zero at every ARM
With TPU in a nozzle heating to 150C the pressure kept changing for
minutes (+43, then -56 as it oozed), faster than the sensor's own
baseline update, and the guard stopped the printer at the start of
z_check and home_xy with the nozzle in the air. Every ARM (also a phase
switch) now writes reset_probe (register 52) and waits 0.2 s without
reading, so only force during the guarded moves counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 16:34:26 +02:00
mangavalkandClaude Opus 5.5 29604b2f1e PA calibration: wider PA range for TPU/TPE
PA_CALIBRATE takes PA_STEP (default 0.002, 0-0.098 over 50 lines).
_PA_CFG.material_pa_step uses 0.004 (0-0.196) for tpu/tpe/flex; soft TPU
measured 0.094, close to the old end. The "no result" rejection uses the
end of the range actually tested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 16:29:02 +02:00
mangavalkandClaude Opus 5.5 8a5f6b6dd9 PA calibration: lower test flow for TPU/TPE
_PA_CFG.material_max_volumetric lowers MAX_VOLUMETRIC for materials that
cannot extrude 25 mm3/s (tpu, tpe, flex: 8). The calibration line shows
the flow used.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 16:15:03 +02:00
mangavalkandClaude Opus 5.5 93ae8795b8 PA calibration reads Spoolman first; guard resets a sensor left in PA mode
- LANE_CALIBRATE_PA: when a calibration will run, assign the lane's spool
  again (SET_SPOOL_ID) so AFC reads the Spoolman temperature now; AFC
  only reads it at spool assignment and Klipper start (TPU calibrated at
  210C after Spoolman was set to 230C). The calibration moved to
  _LANE_CALIBRATE_PA_RUN, rendered after the refresh.
- bdp_guard: an emergency stop during PA_CALIBRATE leaves the sensor in
  PA mode (FIRMWARE_RESTART does not reset it). When arming, read the
  mode register and switch the sensor back to probe mode with a fresh
  baseline before watching it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 16:04:40 +02:00
mangavalkandClaude Opus 5.5 d49e65c0bb Flexible lanes load slower; purge at the previous filament's temperature
- flex_lanes.cfg: a 5 s poller sets SET_LONG_MOVE_SPEED to 50 mm/s (AFC's
  minimum) for lanes whose material is TPU/TPE/flex and back to the
  configured long_moves_speed otherwise; reapplied after a restart.
- BLOBIFIER_CHECKED: remembers the temperature of the filament in the
  nozzle (saved nozzle_last_temp). When it is more than flush_margin
  (10C) above the new filament's, the purge runs at that temperature and
  the nozzle cools back afterwards, so the leftover melts out (seen: ASA
  leftover with TPU loaded at 210C).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 15:59:27 +02:00
mangavalkandClaude Opus 5.5 464a7c1046 CLAUDE.md: purge check peaks up to 246 with ASA at 250-260C
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 15:20:02 +02:00
mangavalkandClaude Opus 5.5 0b1b2a6947 Purge check: accept load-check firmware v2; chamber load levels first
- bdpressure_check.py accepted only extension version 1. The sensor now
  runs firmware version 2 (bytes 54-65 unchanged, diagnostics added), so
  every purge check reported bd_pressure as not answering. Accept 1 and 2.
- _LOAD_FOR_CHAMBER: the Blobifier only purges with the QGL applied, so
  the load before the chamber soak skipped its purge (0.0 mm extruded).
  Level the gantry and home Z first when the QGL is not applied.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 12:12:25 +02:00
mangavalkandClaude Opus 5.5 b9d2f3a062 Nozzle guard in stop mode (brush log-only), wait for the nozzle before HOME
Test print start: guard peaks 1-8 in every phase except the brush scrub
(peak 47, 4 reads above 30 in a row), 0 I2C errors; a light finger push
gives 41. Stop mode for all phases, the brush stays log-only.

The contact homing in HOME restarted 6 times (sample spread too large)
while the nozzle was still heating to 150C; PRINT_WARMUP now waits for a
steady contact temperature first, as PRINT_START does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 23:25:28 +02:00
mangavalkandClaude Opus 5.5 a16b57a0a9 Move bd_pressure.cfg and TEST_SPEED into the repo
All macros now come from this repo. bd_pressure.cfg ([bdpressure bd_pa]
and the vendor PA_CALIBRATE macros) and Macros/TEST_SPEED.cfg were loose
files in ~/printer_data/config.

PA_CALIBRATE no longer sets Z back to z_current at the end: it was read
before the test ran, so a Z hop in the G28 XY (Beacon home_z_hop) would
leave Klipper believing the nozzle is lower than it is. Z is never switched
off or moved by the test, so it keeps its position without that line.
The ACC_WALL bug (read from ACCEL_TO_DECEL, always 4000) is documented,
not fixed: the cached PA values were measured at 4000.

printer.cfg must drop [include bd_pressure.cfg] and Macros/macros.cfg its
TEST_SPEED include, else the old copies (included later) still win.

Reference values from the load-check-afc branch and from the guard and
Z check tests added to CLAUDE.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 23:11:02 +02:00
mangavalkandClaude Opus 5.5 8eef0ee298 bdp_guard: one lock for all I2C transfers of the bd_pressure
The guard's timer read the sensor while G28 ran; bdpressure.py writes the
probe mode at every homing move, and Klipper allows only one I2C request
at a time per device: KeyError ('i2c_response', 1), Klipper shut down
during HOME. The guard now wraps the sensor's i2c in a reactor mutex at
connect; bdpressure and bdpressure_check (now looks up sensor.i2c at
every use) go through it too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 23:06:49 +02:00
mangavalkandClaude Opus 5.5 61fa8acb05 Print start: contact homing, nozzle guard, Z check, purge before chamber heat
- HOME always homes Z with Beacon contact and recalibrates, also when the
  printer is already homed (home_method_when_homed = proximity used the
  model of an earlier print without touching the bed). PRINT_START homes
  Z again after QGL, before the brush takes the nozzle below Z0.
- _Z_CHECK: after the soak, compare Klipper's Z with a BEACON_QUERY
  reading at Z3 over the bed centre and stop the print above 1 mm.
  Warns and continues when BEACON_QUERY leaves no new
  printer.beacon.last_sample.
- Nozzle guard (extras/bdp_guard.py, print/nozzle_guard.cfg): reads the
  bd_pressure force over I2C every 20 ms while armed and, in stop mode,
  shuts Klipper down on a touch. Armed for HOME X/Y, QGL, Z homing, brush,
  mesh and the Z check; starts in log mode to collect peaks per phase.
- Chamber prints: load and purge the first tool before the chamber heats
  (_LOAD_FOR_CHAMBER replaces _UNLOAD_FOR_CHAMBER). A cut unload leaves a
  piece of PLA in the extruder that softened in the hot chamber and jammed
  the ASA load.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 22:39:26 +02:00
mangavalkandClaude Opus 5.5 2100857846 Chamber prints: unload a lane that is not the first tool before the soak
With the chamber heated, a filament left in the toolhead path softens in
the PTFE tube (PLA loaded, ASA print). After the first HOME, PRINT_WARMUP
unloads the loaded lane when it is not mapped to the print's first tool
(PRINT_START would swap it anyway). A failed unload stops the print
before the chamber heats.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:56:30 +02:00
mangavalkandClaude Opus 5.5 6cc43f951d Print start: soak before QGL and Z, load after Z, PA per lane temperature
PRINT_WARMUP: bed heats during the warmup, the nozzle only waits while it
is above 170C (Beacon contact limit 180C), one HOME to get over the bed,
then M190 and HEAT_SOAK. No more filament load and 220->150C cycle there.

PRINT_START (on the soaked bed): one QGL, one contact Z calibration, mesh,
heat over the bucket, load the initial tool, PA for all lanes, load the
initial tool again if a calibration changed lanes, G28 X Y reference.
Loading after the contact keeps ooze off the nozzle for the calibration.

PA: calibrate each lane at its own filament temperature (was the
temperature from before CHANGE_TOOL), and apply the loaded lane's PA
after PA_CALIBRATE_ALL_LANES (the last cached lane's PA won before).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:46:05 +02:00
mangavalkandClaude Opus 5.5 e5be65834e LEDs: nozzle light follows the motion sensor, new logo status macro
The motion timeout switched the nozzle LEDs off during prints, motion
switched them off when idle or paused, the timeout counted from the first
movement, and every _status_* macro stopped the nozzle light.

- _LIGHTS sets case light and nozzle LEDs from presence, print state and
  CASELIGHT MODE=AUTO|ON|OFF; the timeout (60 s) starts at the sensor
  release
- _STATUS STATE=... replaces the _status_* macros; logo effects only
  cover the logo LEDs and use REPLACE=1
- heater gauge while heating, print progress, paused, error flash from
  _ERROR, part ready once the bed is cool, dim night mode when idle and
  nobody near, cleaning during BLOBIFIER_CLEAN
- removed unused effects and macros, 12 fps for the toolhead effects

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:32:42 +02:00
mangavalkandClaude Opus 5.5 fbdb2d5e1d Show errors and warnings on the display (_ERROR, _WARN)
action_raise_error aborts while the macro renders, so an M117 in the same
macro never ran and errors such as the RESUME lane check only reached the
console. messages.cfg adds _ERROR (M117, then raise in a separate macro)
and _WARN (RESPOND TYPE=error + M117); all errors and warnings use them.
PAUSE shows AFC's error as the pause reason; RESUME, CANCEL_PRINT and
PRINT_WARMUP clear the display.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 17:41:41 +02:00
mangavalkandClaude Opus 5.5 a67e99015f RESUME: check the AFC lane, add AFC_CLEAR_ALL_MESSAGES
PAUSE stores the lane the print needs (AFC's next_lane during a failed
tool change, otherwise the loaded lane). RESUME stops with an error when
another lane or none is loaded; RESUME LANE_CHECK=0 skips the check.

AFC_CLEAR_ALL_MESSAGES clears AFC's whole message queue
(AFC_CLEAR_MESSAGE only removes the first message).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 22:12:53 +02:00
mangavalkandClaude Opus 5.5 ec1f67e455 PA_CALIBRATE_ALL_LANES: skip lanes without filament
Lanes whose AFC prep and load sensors do not both see filament are
skipped with a message, as are names that are not AFC lanes. The
"complete" message is now a RESPOND, so it appears after the
calibrations instead of before them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 15:17:20 +02:00
mangavalkandClaude Opus 5.5 536db5836d Purge check: fail when bd_pressure does not answer, add override
A bd_pressure that does not answer used to be reported as "pressure
skipped" and the purge passed unchecked. It now fails the check, so in
enforce mode the printer pauses (and a resume purge stays paused).
PURGE_CHECK_IGNORE_SENSOR lets purges pass without the sensor until the
print ends; _PRINT_CLEANUP clears it so the next print checks again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 21:18:21 +02:00
mangavalkandClaude Opus 5.5 25891640b8 Reject PA calibrations with no result or bad sensor data
bdpressure.py leaves the extruder at the last PA_CALIBRATE test value
(0.098) when it finds no result, and _PA_AFTER_CALIBRATE saved that as
the calibrated PA (lane1 after an AFC pause, lane5 after the sensor went
silent). A run is now rejected, with nothing cached or written to
Spoolman, when the PA is still at the end of the scan, the printer
paused during the test, or bd_pressure gave too few or too few different
readings. The previous valid PA (or fallback_pa) is applied instead.

bdpressure_check reports pa_points and pa_distinct from the bdpressure
module's last PA_CALIBRATE data for this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 21:10:06 +02:00
mangavalkandClaude Opus 5.5 82f9486160 README: remove the section on switching from config folder copies
The printer now runs from the repo, so the one-time migration notes are
no longer needed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 19:46:36 +02:00
mangavalkandClaude Opus 5.5 93f00c87a7 Stop using bdwidth while printing, work without it
bdwidth's motion reading stays at 0, its per-reading width compensation
made the flow jump between 77 and 119%, and a USB dropout shut down
Klipper. PRINT_START no longer enables it. _PRINT_CLEANUP,
BLOBIFIER_CHECKED and PURGE_UNTIL_EMPTY only send bdwidth commands when
[bdwidth fila_width_0] exists, and bdwidth_status reports present: False
with neutral values when it is not configured, so the section can be
removed from printer.cfg.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:18:40 +02:00
23 changed files with 1606 additions and 498 deletions

No files matched your search

+24 -6
View File
@@ -11,10 +11,10 @@ Voron 2.4 350, Klipper + Moonraker + Mainsail on a Raspberry Pi 4
5 lanes, Blobifier purge bucket, Beacon (Z homing and probing), Nitehawk-SB
toolboard (USB), Leviathan mainboard as USB-CAN bridge, bd_pressure
(load-check firmware, branch `load-check-extension` in
`D:\Claude\bd_pressure-load-check\bd_pressure`), bdwidth (measures width and
motion optically with a CCD; its motion reading stays at 0, so it runs width
only (`ENABLE_WIDTH` in PRINT_START) and the purge check runs with
`motion_check: False`; clogs and breaks are caught by AFC's buffer fault
`D:\Claude\bd_pressure-load-check\bd_pressure`), bdwidth (CCD width/motion sensor on USB; switched off: its motion reading
stays at 0, its width compensation made the flow jump 77-119% and a USB
dropout once shut down Klipper. The macros work with or without
`[bdwidth fila_width_0]`; clogs and breaks are caught by AFC's buffer fault
detection, `filament_error_sensitivity`), iHeater chamber heater.
## Working with the printer
@@ -64,7 +64,17 @@ detection, `filament_error_sensitivity`), iHeater chamber heater.
(remembered in the saved variable `nozzle_retract`); `PRINT_START` undoes
it over the bucket with `_NOZZLE_UNRETRACT`.
- Inside `gcode:` use `;` for comments on G-code lines; `#` lines are
stripped by the config parser.
stripped by the config parser. Never put a comment on an `M117` line:
M117 shows the raw rest of the line, comment included.
- Errors and warnings go through `_ERROR` / `_WARN` (`macros/messages.cfg`),
not `action_raise_error` / `RESPOND TYPE=error`, so they also show on the
display. `action_raise_error` aborts while rendering, so an `M117` in the
same macro would never run. No double quotes in `MSG`/`DISPLAY`.
- Nozzle guard (`print/nozzle_guard.cfg`, `extras/bdp_guard.py`): wrap only
moves where nothing may touch the nozzle in `_GUARD_ON PHASE=x` /
`_GUARD_OFF`. Never leave it on across contact homing, extrusion, purges,
the Blobifier park or AFC cuts and loads: in `mode: stop` that is an
instant shutdown.
- After a refactor run `python tools/effective_config.py HEAD` and check that
only the intended sections changed.
- Render-test macros with Jinja2 using Klipper's delimiters
@@ -84,7 +94,15 @@ detection, `filament_error_sensitivity`), iHeater chamber heater.
## Reference values
- Purge check (150 mm Blobifier purge): good load = longest pressure
13-18 s, peak 64-181; empty nozzle about 30 ms.
13-18 s, peak 64-246 (ASA at 250-260C peaks highest); empty nozzle
about 30 ms. A real failed load in a
print start (AFC reported the lane loaded, nothing reached the nozzle):
longest 1915 ms, peak 45, caught by the 5000 ms limit.
- Nozzle guard (bdp_guard): peaks 1-8 in home_xy, QGL, Z homing, mesh and
the Z check; the brush scrub 47 (4 reads above 30 in a row, so it runs
in log mode); a light finger push on the cold nozzle gives 41
(threshold 30).
- Z check after a contact homing: Beacon vs Klipper Z about 0.06 mm.
- XY drift check: endstop noise about 0.01-0.02 mm; limit 0.1 mm (one motor
skipping one cycle = 0.4 mm shows as 0.2 mm in X and Y).
+16 -30
View File
@@ -16,14 +16,17 @@ everything below, grouped by topic. Comment out what you do not use there.
| File | Macros | Needs |
|---|---|---|
| `save_variables.cfg` | the one `[save_variables]` for all macros (file `bd_pa_variables.cfg` in the config folder) | — |
| `messages.cfg` | `_ERROR`, `_WARN`: errors and warnings shown in the console and on the display (M117, the status line in Mainsail and KlipperScreen); PAUSE shows AFC's error, RESUME, CANCEL_PRINT and PRINT_WARMUP clear the display | — |
| **blobifier/** | | |
| `blobifier.cfg` | `BLOBIFIER`, `BLOBIFIER_CLEAN`, `BLOBIFIER_PARK`, `BLOBIFIER_SHAKE_BUCKET`, ...: purge bucket and nozzle brush for AFC (Turtleblobifier, heavily modified) | Blobifier hardware, AFC |
| `blobifier_hw.cfg` | servo and bucket switch pins | — |
| **print/** | | |
| `print_start_end.cfg` | `PRINT_WARMUP`, `PRINT_START`, `PRINT_END` | Beacon, AFC, Blobifier, `HEAT_SOAK`, iHeater, bdwidth, bd_pressure PA macros |
| `print_start_end.cfg` | `PRINT_WARMUP`, `PRINT_START`, `PRINT_END`; `_FIRST_LAYER_FAULT_DETECTION`: AFC buffer fault detection less sensitive during the first layer (needs one line in the slicer's layer change G-code, see the macro) | Beacon, AFC, Blobifier, `HEAT_SOAK`, iHeater, bd_pressure PA macros (bdwidth is no longer enabled while printing) |
| `pause_resume.cfg` | `PAUSE`, `RESUME`, `CANCEL_PRINT`, `M600` (RESUME also handles a running `HEAT_SOAK`, and after a PAUSE purges 40 mm with `BLOBIFIER_CHECKED` first: a failed check keeps the print paused, `RESUME PURGE=0` skips the purge; CANCEL_PRINT runs the same cleanup as `PRINT_END`) | `heating/heat_soak.cfg`, `leds/`, `blobifier/`, `afc/purge_check.cfg` |
| `filament.cfg` | `LOAD_FILAMENT`, `UNLOAD_FILAMENT` | — |
| `homing.cfg` | `HOME` | — |
| `homing.cfg` | `HOME`: X/Y, then Z always with Beacon contact (also when already homed); `_Z_CHECK`: stops the print when Klipper's Z and the Beacon reading differ (run in `PRINT_START` after the soak) | Beacon |
| `nozzle_guard.cfg` | `[bdp_guard]`, `_GUARD_ON PHASE=<name>`, `_GUARD_OFF`: bd_pressure watches the nozzle force during print-start moves where nothing should touch it (QGL, Z homing, mesh); `mode: stop` shuts Klipper down on a touch (the brush runs log-only), `mode: log` only reports the peak per phase | `extras/bdp_guard.py` + bd_pressure load-check firmware |
| `test_speed.cfg` | `TEST_SPEED`: skipped-steps test for speed and acceleration | — |
| `idle_timeout.cfg` | `[idle_timeout]`: heaters, chamber heater and motors off | iHeater macros |
| **heating/** | | |
| `heat_soak.cfg` | `HEAT_SOAK`, `STOP_HEAT_SOAK`, `CANCEL_HEAT_SOAK`, `HEAT_SOAK_RESUME`, `RESUME` | — |
@@ -34,24 +37,27 @@ everything below, grouped by topic. Comment out what you do not use there.
| **afc/** | | |
| `xy_safety_check.cfg` | `AFC_XY_SAFETY_CHECK`, `_AFC_STORE_HOME_REFERENCE`, `AFC_XY_RESUME`: lost-step check around tool changes | `extras/xy_drift.py` (CoreXY) |
| `lane_tools.cfg` | `LANE_FEED_UNTIL_HUB_OPEN`, `LANE_FEED_ABORT` | AFC |
| `purge_check.cfg` | `BLOBIFIER_CHECKED`: checks every AFC purge with bd_pressure (and optionally bdwidth motion) | Blobifier, AFC, `extras/bdpressure_check.py` + bd_pressure load-check firmware, `extras/bdwidth_status.py` |
| `purge_check.cfg` | `BLOBIFIER_CHECKED`: checks every AFC purge with bd_pressure (and optionally bdwidth motion); a bd_pressure that does not answer fails the check, `PURGE_CHECK_IGNORE_SENSOR` lets the current print finish without it; when the previous filament (saved `nozzle_last_temp`) needs more than 10°C above the new one, the purge runs at the previous filament's temperature and the nozzle cools back afterwards, so its leftover melts out | Blobifier, AFC, `extras/bdpressure_check.py` + bd_pressure load-check firmware, `extras/bdwidth_status.py` |
| `flex_lanes.cfg` | Lanes holding TPU/TPE (material from Spoolman) get slower long moves (50 mm/s, AFC's minimum) through the tube; back to the configured speed when the lane holds something else. Polls every 5 s, reapplies after a restart. The last stretch into the extruder (`tool_load_speed`) stays as configured | AFC |
| `purge_until_empty.cfg` | `PURGE_UNTIL_EMPTY`: purges a snapped filament out until there is no more nozzle pressure | same as `purge_check.cfg` |
| **pressure_advance/** | | |
| `bd_pa_spoolman.cfg` + `bd_pa_*.sh` | `LANE_CALIBRATE_PA`, `PA_CALIBRATE_ALL_LANES`, `LANE_RECAL_PA`, `SET_SPOOL_ID`, `PA_STATUS`: pressure advance per lane/spool, cached locally and in Spoolman | bd_pressure, AFC, Spoolman, `gcode_shell_command` |
| `bd_pressure.cfg` | `[bdpressure bd_pa]` (I2C on the Nitehawk) and the vendor macros `PA_CALIBRATE`, `PA_E`, `PA_RESET`, `PA_STATE`; `PA_CALIBRATE` no longer sets Z back to a value read before the test (wrong after a Z hop in `G28 XY`) | `bdpressure.py` from the bd_pressure repo |
| `bd_pa_spoolman.cfg` + `bd_pa_*.sh` | `LANE_CALIBRATE_PA`, `PA_CALIBRATE_ALL_LANES`, `LANE_RECAL_PA`, `SET_SPOOL_ID`, `PA_STATUS`: pressure advance per lane/spool, cached locally and in Spoolman; before a calibration the lane's spool is assigned again so a temperature just changed in Spoolman is used; flexible materials (TPU/TPE) calibrate at a lower flow (8 mm³/s) and over a wider PA range (0–0.196 in 0.004 steps instead of 0–0.098); a calibration with no result, a pause during the test or too few/stuck sensor readings is rejected and not saved | bd_pressure, AFC, Spoolman, `gcode_shell_command` |
| **spoolman/** | | |
| `active_spool.cfg` | `SET_ACTIVE_SPOOL`, `CLEAR_ACTIVE_SPOOL` | Spoolman in Moonraker |
| **leds/** | | |
| `stealthburner_leds.cfg` | Stealthburner LED effects and `_status_*` macros | `led_effect` |
| `stealthburner_leds.cfg` | Stealthburner logo status (`_STATUS STATE=...`: heating and print progress as a filling logo, paused, error flash from `_ERROR`, part ready once the bed is cool, dim when idle and nobody is near) and the nozzle light | `led_effect`, `neopixel:bt_logo` and `rainbow_turtle` from AFC |
| **sensors/** | | |
| `motion_sensor.cfg` | PIR motion sensor switching the case light | sensor on a pin, `[output_pin caselight]` |
| `motion_sensor.cfg` | PIR motion sensor: case light and nozzle LEDs on while someone is near (timeout from the last motion), `CASELIGHT MODE=AUTO\|ON\|OFF` | sensor on a pin, `[output_pin caselight]`, `leds/stealthburner_leds.cfg` |
### Klipper modules (`extras/`)
| File | For |
|---|---|
| `xy_drift.py` | `AFC_XY_SAFETY_CHECK`: measures lost XY steps from the stepper step counts at the endstop triggers (CoreXY). The toolhead position after homing cannot show lost steps. |
| `bdwidth_status.py` | exposes bdwidth's motion counter and width to macros, and keeps a USB dropout of bdwidth (port: usb) from shutting down Klipper: the read is retried every 10 s and the port reopened |
| `bdpressure_check.py` | bd_pressure load check (`BDP_CHECK_START/STOP/QUERY`). Needs the load-check firmware from the bd_pressure `load-check-extension` branch; on stock firmware it reports "not available" and does nothing. |
| `bdwidth_status.py` | exposes bdwidth's motion counter and width to macros (`present: False` with neutral values when no bdwidth is configured, so the macros also work without it), and keeps a USB dropout of bdwidth (port: usb) from shutting down Klipper: the read is retried every 10 s and the port reopened |
| `bdpressure_check.py` | bd_pressure load check (`BDP_CHECK_START/STOP/QUERY`). Needs the load-check firmware from the bd_pressure `load-check-extension` branch; on stock firmware it reports "not available" and does nothing. Also reports how many readings the last `PA_CALIBRATE` got (`pa_points`, `pa_distinct`), used to reject runs with a silent or stuck sensor. |
| `bdp_guard.py` | nozzle collision guard: while armed (`BDP_GUARD_ARM/DISARM/QUERY`) reads the bd_pressure force every 20 ms over I2C and, in stop mode, shuts Klipper down when it stays above the threshold. Reports reads, peak and errors per phase in the console and klippy.log. When it arms and finds the sensor still in PA mode (an emergency stop during `PA_CALIBRATE`; `FIRMWARE_RESTART` does not reset the sensor), it switches it to probe mode and takes a new zero first. Puts a lock on the sensor's I2C connection that `bdpressure.py` and `bdpressure_check.py` also go through (Klipper allows one I2C request at a time per device). Needs the load-check firmware. |
### Scripts (`scripts/`)
@@ -114,28 +120,6 @@ Then:
Updates then come through Moonraker; it re-runs `install.sh` and restarts
Klipper.
### Switching from copies in the config folder
If these macros already exist as files in your config folder (as on the
original printer: `Macros/*.cfg` and `bd_pa_afc_spoolman.cfg`), remove those
includes when adding `klipper-macros.cfg`, otherwise the macros are defined
twice and the old copy may win:
- remove `[include bd_pa_afc_spoolman.cfg]` and
`[include ./blobifier/blobifier.cfg]` from `printer.cfg`,
- remove `[include ./Macros/macros.cfg]` and add
`[include klipper-macros/klipper-macros.cfg]` after `[include AFC/*.cfg]`,
- keep including what is not in this repo yourself, for example
`[include Macros/timelapse.cfg]` and `[include Macros/TEST_SPEED.cfg]`.
`bd_pa_variables.cfg` (saved PA values and the Blobifier blob count) stays
where it is, in the config folder; it is data, not part of the repo. Before,
both `blobifier.cfg` and `bd_pa_afc_spoolman.cfg` had their own
`[save_variables]`; Klipper merged them and the last one won, so the blob
count was already stored in `bd_pa_variables.cfg` and
`blobifier/blob_vars.cfg` was no longer used. Now it is defined once, in
`save_variables.cfg`.
## Adapting to another printer
These are the settings of one printer. Before using a file, check:
@@ -179,6 +163,8 @@ original licenses apply to those parts:
| `heating/heat_soak.cfg` | community `HEAT_SOAK` macro |
| `heating/chamber_iheater.cfg` | iHeater vendor configuration |
| `heating/filament_drying.cfg` | community filament drying macro |
| `pressure_advance/bd_pressure.cfg` | [bd_pressure](https://github.com/markniu/bd_pressure) `klipper/bd_pressure.cfg` |
| `print/test_speed.cfg` | [Ellis' TEST_SPEED](https://ellis3dp.com/Print-Tuning-Guide/articles/determining_max_speeds_accels.html) |
| `print/pause_resume.cfg`, `print/filament.cfg` | [KlipperScreen recommended macros](https://klipperscreen.readthedocs.io/en/latest/macros/#recommended) |
| `scripts/` | [Voron docs: automating MCU updates](https://docs.vorondesign.com/community/howto/drachenkatze/automating_klipper_mcu_updates.html), [Esoterical's CANBus guide](https://canbus.esoterical.online/) |
+283
View File
@@ -0,0 +1,283 @@
# Nozzle collision guard with bd_pressure (load-check firmware extension).
#
# The bd_pressure strain gauge sees the force on the nozzle. The load-check
# firmware keeps the current force minus its baseline in register 64-65
# (chk_delta), also outside a load check; it is the same value the endstop
# output switches on (|delta| >= thrhold). While the guard is armed this
# module reads it every INTERVAL seconds. In stop mode, SAMPLES reads in a
# row at or above THRESHOLD shut Klipper down at once (like M112, without
# waiting for the running macro to finish).
#
# Every ARM (also a phase switch) first sets a new zero (register 52), so
# slow pressure changes before the guarded moves do not count (TPU in a
# nozzle that heats up keeps changing pressure for minutes).
#
# Arm it only around moves where the nozzle must not touch anything: not
# around contact homing, extrusion, purges or AFC cuts and loads, which
# press on the nozzle on purpose. ARM and DISARM act immediately, not in
# the move queue: put an M400 before them (the _GUARD_ON/_GUARD_OFF macros
# do this).
#
# Install: copy to ~/klipper/klippy/extras/bdp_guard.py and add
#
# [bdp_guard]
# sensor: bd_pa # name after "bdpressure" in [bdpressure bd_pa]
# mode: log # log: only measure; stop: shut down on a touch
# threshold: 30 # |delta| that counts as a touch
# samples: 3 # reads in a row at or above threshold
# interval: 0.02 # seconds between reads while armed
# timeout: 600 # disarm by itself when a phase runs this long
# # (a macro that failed before its DISARM)
#
# Commands:
# BDP_GUARD_ARM PHASE=<name> [MODE=log|stop] start (or switch phase)
# BDP_GUARD_DISARM stop and report the phase
# BDP_GUARD_QUERY report without stopping
#
# Each phase reports (console and klippy.log, "bdp_guard:") the reads, the
# peak |delta|, the longest run of reads at or above threshold and the
# read errors, so a log-mode print start shows what each phase normally
# measures before stop mode is switched on.
#
# Status (printer.bdp_guard): armed, mode, phase, threshold, samples, peak,
# longest_run, reads, errors, available
import logging
REG_MODE = 48 # status_clk: 7 = PA mode, 2 = probe (endstop) mode
REG_RESET_PROBE = 52 # set_normal: write 1 = take the current reading as zero
MODE_PA = 7
REG_EXT = 54 # extension marker 0xBD + version
REG_DELTA = 64 # chk_delta, int16 little-endian
EXT_MAGIC = 0xBD
MODES = ('log', 'stop')
# Klipper allows only one I2C request at a time per device: a second one
# while the first waits for its answer raises KeyError ('i2c_response', oid)
# and shuts Klipper down. The guard reads from a timer while G-code runs,
# and bdpressure.py writes the probe mode at every homing move (seen: G28
# during an armed guard), so all transfers of the sensor take this lock.
# The guard puts it on the sensor object at connect; bdpressure and
# bdpressure_check use sensor.i2c and so get it too.
class LockedI2C:
def __init__(self, i2c, mutex):
self._i2c = i2c
self._mutex = mutex
def i2c_read(self, *args, **kwargs):
with self._mutex:
return self._i2c.i2c_read(*args, **kwargs)
def i2c_write(self, *args, **kwargs):
with self._mutex:
return self._i2c.i2c_write(*args, **kwargs)
def i2c_write_wait_ack(self, *args, **kwargs):
with self._mutex:
return self._i2c.i2c_write_wait_ack(*args, **kwargs)
def i2c_transfer(self, *args, **kwargs):
with self._mutex:
return self._i2c.i2c_transfer(*args, **kwargs)
def __getattr__(self, name):
return getattr(self._i2c, name)
class BDPGuard:
def __init__(self, config):
self.printer = config.get_printer()
self.reactor = self.printer.get_reactor()
self.sensor_name = 'bdpressure ' + config.get('sensor', 'bd_pa')
self.mode = config.getchoice('mode', {m: m for m in MODES}, 'log')
self.threshold = config.getint('threshold', 30, minval=1)
self.samples = config.getint('samples', 3, minval=1)
self.interval = config.getfloat('interval', 0.02, minval=0.005)
self.timeout = config.getfloat('timeout', 600., minval=10.)
self.armed_until = 0.
self.sensor = self.i2c = None
self.available = False
self.timer = None
self.zeroing = False
self.phase_mode = self.mode
self._reset_phase('')
self.printer.register_event_handler('klippy:connect',
self._handle_connect)
self.printer.register_event_handler('klippy:shutdown',
self._handle_shutdown)
gcode = self.printer.lookup_object('gcode')
gcode.register_command('BDP_GUARD_ARM', self.cmd_ARM,
desc="Arm the bd_pressure nozzle collision guard")
gcode.register_command('BDP_GUARD_DISARM', self.cmd_DISARM,
desc="Disarm the bd_pressure nozzle collision guard")
gcode.register_command('BDP_GUARD_QUERY', self.cmd_QUERY,
desc="Report the bd_pressure guard values")
def _handle_connect(self):
self.sensor = self.printer.lookup_object(self.sensor_name, None)
i2c = getattr(self.sensor, 'i2c', None)
if i2c is not None and not isinstance(i2c, LockedI2C):
# Every user of the sensor (bdpressure, bdpressure_check, this
# guard) now goes through one lock, see LockedI2C
i2c = LockedI2C(i2c, self.reactor.mutex())
self.sensor.i2c = i2c
self.i2c = i2c
def _handle_shutdown(self):
self._stop_timer()
def _reset_phase(self, phase):
self.phase = phase
self.peak = self.longest_run = self.run = 0
self.reads = self.errors = 0
def _check_available(self):
if self.i2c is None:
return False
try:
data = bytearray(self.i2c.i2c_read([REG_EXT], 2)['response'])
except Exception:
logging.exception("bdp_guard: reading the extension marker failed")
return False
return len(data) == 2 and data[0] == EXT_MAGIC and data[1] >= 1
def _ensure_probe_mode(self, gcmd):
# An emergency stop during PA_CALIBRATE leaves the sensor in PA mode
# (FIRMWARE_RESTART does not reset it); bdpressure.py only switches
# it back at the next homing move, after the guard is armed. In PA
# mode the delta means nothing and could trip the guard. Register 48
# is the mode: 7 = PA, 2 = probe.
if getattr(self.sensor, 'last_state', 0):
return # a calibration runs, leave the sensor alone
try:
mode = bytearray(self.i2c.i2c_read([REG_MODE], 1)['response'])
if len(mode) != 1 or mode[0] != MODE_PA:
return
self.sensor.set_probe_mode()
# new samples at the probe sample rate, then a fresh baseline
self.reactor.pause(self.reactor.monotonic() + 0.5)
self.i2c.i2c_write([REG_RESET_PROBE, 1])
self.reactor.pause(self.reactor.monotonic() + 0.3)
except Exception:
logging.exception("bdp_guard: switching the sensor to probe mode failed")
return
msg = ("BDP_GUARD: bd_pressure was still in PA mode (stopped "
"calibration?), switched to probe mode")
logging.info(msg)
gcmd.respond_info(msg)
def _rezero(self):
# A fresh zero at every ARM: only force that appears during the
# guarded moves counts, not slow changes before them. Seen with TPU
# in a nozzle heating to 150C: the pressure kept rising (+43) or
# falling as it oozed (-56) for minutes, faster than the sensor's
# own baseline update (only when steady, about every 7 s), and
# stopped the printer at the start of a phase with the nozzle in
# the air. ARM comes after M400, with the nozzle clear of
# everything, so this does not hide a real touch.
self.zeroing = True
try:
self.i2c.i2c_write([REG_RESET_PROBE, 1])
self.reactor.pause(self.reactor.monotonic() + 0.2)
except Exception:
logging.exception("bdp_guard: setting a new zero failed")
self.zeroing = False
def _stop_timer(self):
if self.timer is not None:
self.reactor.unregister_timer(self.timer)
self.timer = None
def _poll(self, eventtime):
# Never raise here: an exception in a timer shuts Klipper down.
try:
if self.printer.is_shutdown():
return self.reactor.NEVER
if eventtime > self.armed_until:
logging.info("bdp_guard: %s armed longer than %.0f s, "
"disarmed (%d reads, peak %d)", self.phase,
self.timeout, self.reads, self.peak)
self._stop_timer()
self.phase = ''
return self.reactor.NEVER
# PA mode (SET_BDPRESSURE START): the delta means nothing then;
# while ARM sets a new zero the readings are in between
if self.zeroing or getattr(self.sensor, 'last_state', 0):
return eventtime + self.interval
data = bytearray(self.i2c.i2c_read([REG_DELTA], 2)['response'])
if len(data) != 2:
self.errors += 1
return eventtime + self.interval
delta = data[0] | (data[1] << 8)
if delta & 0x8000:
delta -= 0x10000
mag = abs(delta)
self.reads += 1
self.peak = max(self.peak, mag)
self.run = self.run + 1 if mag >= self.threshold else 0
self.longest_run = max(self.longest_run, self.run)
if self.run >= self.samples and self.phase_mode == 'stop':
# klippy:shutdown unregisters this timer
self.printer.invoke_shutdown(
"bd_pressure guard: nozzle touched something during %s "
"(delta %d, threshold %d)"
% (self.phase or 'an armed move', delta, self.threshold))
return self.reactor.NEVER
except Exception:
self.errors += 1
if self.errors == 1: # 50 reads/s: only the first per phase
logging.exception("bdp_guard: read failed")
return eventtime + self.interval
def _report(self, gcmd, prefix):
msg = ("%s %s (%s): %d reads, peak %d, longest run >= %d: %d reads, "
"%d errors" % (prefix, self.phase or '-', self.phase_mode,
self.reads, self.peak, self.threshold,
self.longest_run, self.errors))
logging.info("bdp_guard: %s", msg)
gcmd.respond_info(msg)
def cmd_ARM(self, gcmd):
phase = gcmd.get('PHASE', 'armed')
mode = gcmd.get('MODE', self.mode).lower()
if mode not in MODES:
raise gcmd.error("BDP_GUARD_ARM: MODE must be log or stop")
if self.timer is not None:
self._report(gcmd, "BDP_GUARD")
else:
self.available = self._check_available()
if not self.available:
gcmd.respond_info("BDP_GUARD_ARM: bd_pressure load-check "
"extension not found, guard not armed")
return
self._ensure_probe_mode(gcmd)
self._rezero()
self.phase_mode = mode
self._reset_phase(phase)
self.armed_until = self.reactor.monotonic() + self.timeout
if self.timer is None:
self.timer = self.reactor.register_timer(
self._poll, self.reactor.monotonic() + self.interval)
def cmd_DISARM(self, gcmd):
if self.timer is None:
return
self._stop_timer()
self._report(gcmd, "BDP_GUARD")
self.phase = ''
def cmd_QUERY(self, gcmd):
self._report(gcmd, "BDP_GUARD_QUERY" + (" armed" if self.timer
else " not armed"))
def get_status(self, eventtime):
return {'armed': self.timer is not None, 'mode': self.phase_mode,
'phase': self.phase, 'threshold': self.threshold,
'samples': self.samples, 'peak': self.peak,
'longest_run': self.longest_run, 'reads': self.reads,
'errors': self.errors, 'available': self.available}
def load_config(config):
return BDPGuard(config)
+31 -8
View File
@@ -2,7 +2,7 @@
#
# Uses the I2C connection the existing [bdpressure <name>] section already
# set up. Before writing anything it reads the extension marker (bytes
# 54-55 = 0xBD 0x01). On stock firmware the marker is missing and every
# 54-55 = 0xBD + version 1 or 2). On stock firmware the marker is missing and every
# command only reports "not available": nothing is written, because
# writing past the stock register table would land in the sensor's RAM.
#
@@ -19,18 +19,27 @@
#
# Status (printer.bdpressure_check), updated by the commands above:
# available, running, peak, above_ms, longest_ms, delta
# pa_points, pa_distinct - readings the [bdpressure] module collected in
# its last PA_CALIBRATE, and how many of them differ (read
# from that module, not from the sensor). A sensor that
# stops answering gives few points; one that hangs repeats
# the same reading. LANE_CALIBRATE_PA rejects such runs.
REG_EXT = 54 # first byte of the extension
REG_CTRL = 56
EXT_LEN = 12 # bytes 54-65
EXT_MAGIC = (0xBD, 0x01)
EXT_LEN = 12 # bytes 54-65, the same in version 1 and 2
EXT_MAGIC = 0xBD
# Version 2 (load-check firmware 20261015 and later) only adds diagnostics
# at 66-71. Accepting only version 1 made the purge check report the sensor
# as not answering after the firmware update.
EXT_VERSIONS = (1, 2)
class BDPressureCheck:
def __init__(self, config):
self.printer = config.get_printer()
self.sensor_name = 'bdpressure ' + config.get('sensor', 'bd_pa')
self.i2c = None
self.sensor = None
self.status = {'available': False, 'running': False, 'peak': 0,
'above_ms': 0, 'longest_ms': 0, 'delta': 0}
self.printer.register_event_handler('klippy:connect',
@@ -44,15 +53,21 @@ class BDPressureCheck:
desc="Read the bd_pressure load check values")
def _handle_connect(self):
sensor = self.printer.lookup_object(self.sensor_name)
self.i2c = getattr(sensor, 'i2c', None)
self.sensor = self.printer.lookup_object(self.sensor_name)
@property
def i2c(self):
# Looked up at every use: bdp_guard replaces the sensor's i2c with a
# locked wrapper at connect, possibly after this module's connect.
return getattr(self.sensor, 'i2c', None)
def _read(self):
if self.i2c is None:
self.status['available'] = False
return False
data = bytearray(self.i2c.i2c_read([REG_EXT], EXT_LEN)['response'])
if len(data) < EXT_LEN or tuple(data[0:2]) != EXT_MAGIC:
if (len(data) < EXT_LEN or data[0] != EXT_MAGIC
or data[1] not in EXT_VERSIONS):
self.status['available'] = False
return False
u16 = lambda i: data[i] | (data[i + 1] << 8)
@@ -100,7 +115,15 @@ class BDPressureCheck:
self._respond(gcmd, "BDP_CHECK_QUERY")
def get_status(self, eventtime):
return dict(self.status)
status = dict(self.status)
# [val, res, lk, rk, Hk, Ha] per reading, see bdpressure.py
try:
data = list(getattr(self.sensor, 'PA_data', None) or [])
status['pa_points'] = len(data)
status['pa_distinct'] = len(set(tuple(d[1:]) for d in data))
except Exception:
status['pa_points'] = status['pa_distinct'] = -1
return status
def load_config(config):
+13 -1
View File
@@ -26,6 +26,9 @@
# only starts reading on the first SET_BDWIDTH ... ENABLE
# linear_motion - counts per mm (motion_linear_coefficient)
# sample_time - seconds between sensor reads
# present - False when no [bdwidth <sensor>] is configured; the
# other fields then hold neutral defaults, so macros keep
# working with bdwidth removed from the config
# usb_ok - False while the USB connection is down (port: usb)
# usb_errors - USB read errors since Klipper start
#
@@ -54,7 +57,10 @@ class BDWidthStatus:
self._handle_connect)
def _handle_connect(self):
self.sensor = self.printer.lookup_object(self.sensor_name)
self.sensor = self.printer.lookup_object(self.sensor_name, None)
if self.sensor is None:
logging.info("bdwidth_status: no [%s] configured", self.sensor_name)
return
if (getattr(self.sensor, 'port', None) == 'usb'
and callable(getattr(self.sensor, 'Read_bdwidth', None))
and getattr(self.sensor, 'usb', None) is not None):
@@ -96,6 +102,11 @@ class BDWidthStatus:
def get_status(self, eventtime):
s = self.sensor
if s is None:
return {'name': self.name, 'present': False, 'reading': False,
'enabled': False, 'total_move': 0, 'last_motion': 0,
'diameter': 0., 'active': 'disable', 'linear_motion': 42.8,
'sample_time': 1., 'usb_ok': True, 'usb_errors': 0}
# bdwidth's own get_status() is the runout helper's, which carries
# the SET_FILAMENT_SENSOR enable flag
try:
@@ -110,6 +121,7 @@ class BDWidthStatus:
and 'disable' not in str(getattr(s, 'is_active', 'disable')))
return {
'name': self.name,
'present': True,
'reading': reading,
'enabled': enabled,
'total_move': getattr(s, 'actual_total_move', 0),
+54
View File
@@ -0,0 +1,54 @@
# Flexible filament (TPU, TPE): slower long moves for the lanes holding it.
#
# Soft filament can buckle in the long tube at the normal 100-200 mm/s.
# A poller checks the material of every lane (from Spoolman via AFC) every
# 5 s and sets the lane's long-move speed: flex_speed for a flexible
# material, the configured long_moves_speed again when the lane holds
# something else. Only changes are sent. SET_LONG_MOVE_SPEED is runtime
# only; after a Klipper restart the poller applies it again.
#
# Not covered: the last stretch into the extruder (tool_load_speed in
# AFC/AFC_Hardware.cfg, 25 mm/s) has no runtime command and is the same
# for every lane. The purge after the load is already slow (2.5 mm/s).
[gcode_macro _FLEX_LANES_CFG]
variable_materials: ["tpu", "tpe", "flex"] # material containing one of these (any case) is flexible
variable_flex_speed: 50 # mm/s; AFC's minimum for SET_LONG_MOVE_SPEED is 50
variable_slowed: "" # runtime: comma list of lanes now at flex_speed
gcode:
[delayed_gcode _FLEX_LANES_POLL]
initial_duration: 10
gcode:
{% set cfg = printer["gcode_macro _FLEX_LANES_CFG"] %}
{% if 'AFC' in printer %}
{% set settings = printer.configfile.settings %}
{% set default_speed = settings.afc.long_moves_speed|default(150) if 'afc' in settings else 150 %}
{% set was = cfg.slowed.split(',')|reject('equalto', '')|list %}
{% set now = [] %}
{% for lane in printer.AFC.lanes %}
{% set key = 'AFC_stepper ' ~ lane %}
{% set mat = (printer[key].material if key in printer else '')|default('', true)|string %}
{% set ns = namespace(flex=false) %}
{% for m in cfg.materials %}
{% if m in mat|lower %}
{% set ns.flex = true %}
{% endif %}
{% endfor %}
{% if ns.flex %}
{% set _ = now.append(lane) %}
{% if lane not in was %}
SET_LONG_MOVE_SPEED LANE={lane} FWD_SPEED={cfg.flex_speed}
RESPOND MSG="{lane} holds {mat}: long moves at {cfg.flex_speed} mm/s (flexible)"
{% endif %}
{% elif lane in was %}
{% set speed = settings[key|lower].long_moves_speed|default(default_speed) if (key|lower) in settings else default_speed %}
SET_LONG_MOVE_SPEED LANE={lane} FWD_SPEED={speed}
RESPOND MSG="{lane} holds {mat or 'nothing'}: long moves back at {speed} mm/s"
{% endif %}
{% endfor %}
{% if now|join(',') != was|join(',') %}
SET_GCODE_VARIABLE MACRO=_FLEX_LANES_CFG VARIABLE=slowed VALUE='"{now|join(',')}"'
{% endif %}
{% endif %}
UPDATE_DELAYED_GCODE ID=_FLEX_LANES_POLL DURATION=5
+14 -1
View File
@@ -1,4 +1,4 @@
# AFC lane tools: LANE_FEED_UNTIL_HUB_OPEN, LANE_FEED_ABORT
# AFC lane tools: LANE_FEED_UNTIL_HUB_OPEN, LANE_FEED_ABORT, AFC_CLEAR_ALL_MESSAGES
#####################################################################
## unloading help
@@ -98,3 +98,16 @@ gcode:
# T1 ; Unload
# G4 P3000 ; Wait 3s
# {% endfor %}
#####################################################################
## AFC messages
#####################################################################
# AFC queues every error and warning; the UI shows only the first one and
# AFC_CLEAR_MESSAGE removes only that one. AFC does not expose the queue
# length, so clear MAX times (clearing an empty queue does nothing).
[gcode_macro AFC_CLEAR_ALL_MESSAGES]
description: Clear all AFC error and warning messages (MAX=50)
gcode:
{% for i in range(params.MAX|default(50)|int) %}
AFC_CLEAR_MESSAGE
{% endfor %}
+69 -5
View File
@@ -32,7 +32,10 @@
[bdwidth_status]
sensor: fila_width_0
# Remove this section if bdpressure_check.py is not installed
# Remove this section if bdpressure_check.py is not installed. With it
# installed, a bd_pressure that does not answer (stock firmware, hung or
# unplugged sensor) fails the check, so in enforce mode every purge pauses;
# PURGE_CHECK_IGNORE_SENSOR lets the current print finish without it.
[bdpressure_check]
sensor: bd_pa
@@ -50,6 +53,15 @@ variable_bdwidth_warmup_ms: 15000
variable_start_counts: 0
variable_start_e: 0.0
variable_last_failed: False # last check failed in enforce mode (read by _RESUME_PURGE_VERIFY)
variable_ignore_missing_sensor: False # set by PURGE_CHECK_IGNORE_SENSOR, cleared at print end
# Hotter purge: the leftover of the previous filament (the piece below the
# cutter) has to melt to be pushed out. When the filament that last went
# through the nozzle (saved variable nozzle_last_temp) needs more than the
# new one plus flush_margin, the purge runs at that temperature and the
# nozzle cools back to the new filament's afterwards (seen: ASA leftover,
# TPU loaded at 210C, purge only worked after heating by hand).
variable_flush_margin: 10 # C; smaller differences purge at the new temperature
variable_flush_restore: 0 # runtime: temperature to cool back to after a hot purge
gcode:
[gcode_macro BLOBIFIER_CHECKED]
@@ -65,13 +77,15 @@ gcode:
# pause the printer (the check reports it instead). Afterwards the
# runout response is restored; bdwidth itself is only switched off
# again outside a print, so a print never loses it.
{% set bdw_on = cfg.motion_check and not printer.bdwidth_status.reading|default(false) %}
{% set has_bdw = printer.bdwidth_status.present|default(true) %}
{% set bdw_on = has_bdw and cfg.motion_check and not printer.bdwidth_status.reading|default(false) %}
{% set bdw_off = bdw_on and printer.print_stats.state not in ["printing", "paused"] %}
{% set runout_was = 1 if printer.bdwidth_status.enabled else 0 %}
# Without the motion check, bdwidth's own runout response is off during
# the purge too, so a bdwidth that does not see the movement cannot
# pause the printer here; the pressure check judges the purge.
{% set runout_off = bdw_on or not cfg.motion_check %}
{% set runout_off = has_bdw and (bdw_on or not cfg.motion_check) %}
_PURGE_FLUSH_HEAT
{% if cfg.mode == "off" %}
BLOBIFIER {rawparams}
{% else %}
@@ -102,9 +116,41 @@ gcode:
SET_FILAMENT_SENSOR SENSOR={bdw} ENABLE={runout_was}
{% endif %}
{% endif %}
_PURGE_FLUSH_DONE
# Separate macros so the status is read when they run, not when
# BLOBIFIER_CHECKED is rendered
[gcode_macro _PURGE_FLUSH_HEAT]
description: Helper for BLOBIFIER_CHECKED: heat to the previous filament's temperature when it is hotter than the new one
gcode:
{% set cfg = printer["gcode_macro _PURGE_CHECK_CFG"] %}
{% set old = printer.save_variables.variables.nozzle_last_temp|default(0)|int %}
{% set new = printer.extruder.target|int %}
{% set top = printer.configfile.settings.extruder.max_temp|int - 15 %}
{% set hot = [old, top]|min %}
{% if new > 0 and hot > new + cfg.flush_margin %}
RESPOND MSG="Purge at {hot}C: the previous filament needs {old}C, back to {new}C after the purge"
SET_GCODE_VARIABLE MACRO=_PURGE_CHECK_CFG VARIABLE=flush_restore VALUE={new}
M104 S{hot}
TEMPERATURE_WAIT SENSOR=extruder MINIMUM={hot - 2}
{% else %}
SET_GCODE_VARIABLE MACRO=_PURGE_CHECK_CFG VARIABLE=flush_restore VALUE=0
{% endif %}
[gcode_macro _PURGE_FLUSH_DONE]
description: Helper for BLOBIFIER_CHECKED: cool back after a hot purge and remember the new filament's temperature
gcode:
{% set cfg = printer["gcode_macro _PURGE_CHECK_CFG"] %}
{% set new = cfg.flush_restore|int if cfg.flush_restore|int > 0 else printer.extruder.target|int %}
{% if cfg.flush_restore|int > 0 %}
M104 S{new}
TEMPERATURE_WAIT SENSOR=extruder MAXIMUM={new + 3}
SET_GCODE_VARIABLE MACRO=_PURGE_CHECK_CFG VARIABLE=flush_restore VALUE=0
{% endif %}
{% if new > 0 %}
SAVE_VARIABLE VARIABLE=nozzle_last_temp VALUE={new}
{% endif %}
[gcode_macro _PURGE_CHECK_START]
gcode:
SET_GCODE_VARIABLE MACRO=_PURGE_CHECK_CFG VARIABLE=start_counts VALUE={printer.bdwidth_status.total_move}
@@ -143,15 +189,33 @@ gcode:
{% if p.longest_ms < cfg.pressure_min_ms %}
{% set _ = failed.append("no sustained nozzle pressure") %}
{% endif %}
{% elif 'bdpressure_check' in printer %}
{% if cfg.ignore_missing_sensor %}
{% set press = "pressure skipped: bd_pressure not answering (ignored, PURGE_CHECK_IGNORE_SENSOR)" %}
{% else %}
{% set press = "bd_pressure not answering" %}
{% set _ = failed.append("bd_pressure not answering (power-cycle the sensor, or PURGE_CHECK_IGNORE_SENSOR to finish this print without the check)") %}
{% endif %}
{% else %}
{% set press = "pressure skipped (no check firmware)" %}
{% set press = "pressure skipped (no bdpressure_check)" %}
{% endif %}
{% set checked = motion_on or 'bdpressure_check' in printer and printer.bdpressure_check.available %}
{% set verdict = 'FAIL: ' ~ failed|join(', ') if failed else ('OK' if checked else 'NOTHING CHECKED') %}
RESPOND MSG="PURGE_CHECK {lane}: {motion}; {press} -> {verdict}"
{% if failed and cfg.mode == "enforce" %}
RESPOND TYPE=error MSG="PURGE_CHECK: {lane} {failed|join(', ')} - check extruder and lane"
_WARN MSG="PURGE_CHECK: {lane} {failed|join(', ')} - check extruder and lane" DISPLAY="Purge check failed: {lane}, paused"
SET_GCODE_VARIABLE MACRO=_PURGE_CHECK_CFG VARIABLE=last_failed VALUE=True
PAUSE ; no effect while already paused (resume purge): see _RESUME_PURGE_VERIFY
{% endif %}
{% endif %}
[gcode_macro PURGE_CHECK_IGNORE_SENSOR]
description: Let purges pass without bd_pressure until the print ends (ENABLE=0 to undo)
gcode:
{% if params.ENABLE|default(1)|int %}
SET_GCODE_VARIABLE MACRO=_PURGE_CHECK_CFG VARIABLE=ignore_missing_sensor VALUE=True
RESPOND MSG="PURGE_CHECK: an unavailable bd_pressure is ignored until the print ends (purges are not checked for pressure then)"
{% else %}
SET_GCODE_VARIABLE MACRO=_PURGE_CHECK_CFG VARIABLE=ignore_missing_sensor VALUE=False
RESPOND MSG="PURGE_CHECK: an unavailable bd_pressure fails the check again"
{% endif %}
+8 -8
View File
@@ -49,13 +49,13 @@ gcode:
{% set min_temp = printer.configfile.settings.extruder.min_extrude_temp|float %}
{% set temp = params.TEMP|default([printer.extruder.target, 0]|max)|float %}
{% if 'bdpressure_check' not in printer %}
{action_raise_error("PURGE_UNTIL_EMPTY: needs bdpressure_check.py and [bdpressure_check]")}
_ERROR MSG="PURGE_UNTIL_EMPTY: needs bdpressure_check.py and [bdpressure_check]"
{% elif printer.print_stats.state == "printing" %}
{action_raise_error("PURGE_UNTIL_EMPTY: printer is printing, pause the print first")}
_ERROR MSG="PURGE_UNTIL_EMPTY: printer is printing, pause the print first"
{% elif "xyz" not in printer.toolhead.homed_axes %}
{action_raise_error("PURGE_UNTIL_EMPTY: home the printer first")}
_ERROR MSG="PURGE_UNTIL_EMPTY: home the printer first"
{% elif temp < min_temp %}
{action_raise_error("PURGE_UNTIL_EMPTY: give the filament temperature, e.g. PURGE_UNTIL_EMPTY TEMP=240")}
_ERROR MSG="PURGE_UNTIL_EMPTY: give the filament temperature, e.g. PURGE_UNTIL_EMPTY TEMP=240" DISPLAY="PURGE_UNTIL_EMPTY: give TEMP=..."
{% else %}
BDP_CHECK_QUERY
_PURGE_UNTIL_EMPTY_RUN MAX={max_purges} LENGTH={length} UNSYNC={unsync} TEMP={temp}
@@ -65,7 +65,7 @@ gcode:
[gcode_macro _PURGE_UNTIL_EMPTY_RUN]
gcode:
{% if not printer.bdpressure_check.available %}
{action_raise_error("PURGE_UNTIL_EMPTY: bd_pressure load-check firmware not found")}
_ERROR MSG="PURGE_UNTIL_EMPTY: bd_pressure load-check firmware not found" DISPLAY="PURGE_UNTIL_EMPTY: no bd_pressure"
{% endif %}
SET_GCODE_VARIABLE MACRO=PURGE_UNTIL_EMPTY VARIABLE=done VALUE=0
SET_GCODE_VARIABLE MACRO=PURGE_UNTIL_EMPTY VARIABLE=count VALUE=0
@@ -73,7 +73,7 @@ gcode:
RESPOND MSG="PURGE_UNTIL_EMPTY: unsyncing {printer.AFC.current_load} from the extruder (UNSET_LANE_LOADED)"
UNSET_LANE_LOADED
{% endif %}
{% set bdw = printer.bdwidth_status.name if 'bdwidth_status' in printer else '' %}
{% set bdw = printer.bdwidth_status.name if 'bdwidth_status' in printer and printer.bdwidth_status.present|default(true) else '' %}
{% set runout_was = 1 if bdw and printer.bdwidth_status.enabled else 0 %}
{% if runout_was %}
RESPOND MSG="PURGE_UNTIL_EMPTY: bdwidth runout response off during the purges"
@@ -105,7 +105,7 @@ gcode:
{% set p = printer.bdpressure_check %}
{% set n = cfg.count + 1 %}
{% if not p.available %}
{action_raise_error("PURGE_UNTIL_EMPTY: lost the bd_pressure load-check reading, stopped. bdwidth runout response may still be off: SET_FILAMENT_SENSOR SENSOR=<bdwidth name> ENABLE=1")}
_ERROR MSG="PURGE_UNTIL_EMPTY: lost the bd_pressure load-check reading, stopped. bdwidth runout response may still be off: SET_FILAMENT_SENSOR SENSOR=<bdwidth name> ENABLE=1" DISPLAY="PURGE_UNTIL_EMPTY: lost bd_pressure, stopped"
{% endif %}
{% set empty = p.longest_ms < cfg.empty_ms %}
SET_GCODE_VARIABLE MACRO=PURGE_UNTIL_EMPTY VARIABLE=count VALUE={n}
@@ -123,5 +123,5 @@ gcode:
{% if cfg.done %}
RESPOND MSG="PURGE_UNTIL_EMPTY: no more filament after {cfg.count} purge{'s' if cfg.count != 1 else ''}. The last bit between the extruder gears and the nozzle is pushed out by the next load."
{% else %}
RESPOND TYPE=error MSG="PURGE_UNTIL_EMPTY: still pressure after {params.MAX} purges. The lane may still be feeding, or the nozzle is blocked - check before running it again."
_WARN MSG="PURGE_UNTIL_EMPTY: still pressure after {params.MAX} purges. The lane may still be feeding, or the nozzle is blocked - check before running it again." DISPLAY="PURGE_UNTIL_EMPTY: still pressure, check lane and nozzle"
{% endif %}
+2 -3
View File
@@ -53,11 +53,10 @@ gcode:
{% endif %}
G1 X{th.axis_maximum.x - 10} Y{th.axis_maximum.y - 10} F5000
{% elif cfg.soft_fail|int == 1 %}
RESPOND TYPE=error MSG="AFC: XY drift detected! X={'%.3f' % d.x} Y={'%.3f' % d.y} mm. Axes are re-homed, print PAUSED. Run AFC_XY_RESUME to continue, or CANCEL_PRINT."
M117 AFC: XY drift! See console. Paused.
_WARN MSG="AFC: XY drift detected! X={'%.3f' % d.x} Y={'%.3f' % d.y} mm. Axes are re-homed, print PAUSED. Run AFC_XY_RESUME to continue, or CANCEL_PRINT." DISPLAY="AFC: XY drift! See console. Paused."
PAUSE
{% else %}
RESPOND TYPE=error MSG="AFC: XY drift X={'%.3f' % d.x} Y={'%.3f' % d.y} mm -> M112"
_WARN MSG="AFC: XY drift X={'%.3f' % d.x} Y={'%.3f' % d.y} mm -> M112"
M112
{% endif %}
+5 -3
View File
@@ -291,9 +291,9 @@ gcode:
# ======================================================================================
{% if "xyz" not in printer.toolhead.homed_axes %}
RESPOND MSG="BLOBIFIER: Not homed! Home xyz before blobbing"
_WARN MSG="BLOBIFIER: Not homed! Home xyz before blobbing"
{% elif printer.quad_gantry_level and printer.quad_gantry_level.applied == False %}
RESPOND MSG="BLOBIFIER: QGL not applied! run quad_gantry_level before blobbing"
_WARN MSG="BLOBIFIER: QGL not applied! run quad_gantry_level before blobbing"
{% else %}
# Part cooling fan
@@ -544,6 +544,7 @@ gcode:
{% set pos = printer.gcode_move.gcode_position %}
SAVE_GCODE_STATE NAME=BLOBIFIER_CLEAN_state
_STATUS_SHOW STATE=cleaning
G90
@@ -576,6 +577,7 @@ gcode:
{% endif %}
RESTORE_GCODE_STATE NAME=BLOBIFIER_CLEAN_state
_STATUS
@@ -780,7 +782,7 @@ gcode:
{% set position_x = bl.skew_correction %}
{% if "xyz" not in printer.toolhead.homed_axes %}
{action_raise_error("BLOBIFIER: Not homed. Home xyz first")}
_ERROR MSG="BLOBIFIER: Not homed. Home xyz first"
{% endif %}
SET_GCODE_VARIABLE MACRO=_BLOBIFIER_COUNT VARIABLE=last_shake VALUE={count.current_blobs}
+5
View File
@@ -12,6 +12,7 @@
#####################################################################
[include save_variables.cfg] # the one [save_variables] for all macros
[include messages.cfg] # _ERROR, _WARN: errors and warnings also on the display
# Blobifier purge bucket (Turtleblobifier for AFC, modified)
[include blobifier/blobifier.cfg]
@@ -19,9 +20,11 @@
# Printing
[include print/pause_resume.cfg]
[include print/filament.cfg]
[include print/nozzle_guard.cfg] # bd_pressure nozzle collision guard (needs extras/bdp_guard.py)
[include print/homing.cfg]
[include print/idle_timeout.cfg]
[include print/print_start_end.cfg]
[include print/test_speed.cfg] # TEST_SPEED: skipped-steps test
# Heating
[include heating/bed_fans.cfg]
@@ -35,8 +38,10 @@
[include afc/lane_tools.cfg]
[include afc/purge_check.cfg]
[include afc/purge_until_empty.cfg]
[include afc/flex_lanes.cfg] # TPU/TPE lanes: slower long moves
# Pressure advance per lane/spool (bd_pressure + Spoolman)
[include pressure_advance/bd_pressure.cfg] # [bdpressure bd_pa] and the vendor PA_CALIBRATE macros
[include pressure_advance/bd_pa_spoolman.cfg]
# Spoolman
+166 -248
View File
@@ -1,216 +1,191 @@
# Stealthburner LEDs: logo status effects (_STATUS) and the nozzle light
#
# Based on the Rainbow Barf logo LED effects by tanaes:
# https://github.com/tanaes/whopping_Voron_mods/blob/main/LEDs/Rainbow_Barf_Logo_LED/Code/stealthburner_led_effects_barf.cfg
# Macros for setting the status leds on the Voron StealthBurner toolhead (or for any neopixel-type leds).
# which builds on the Voron Stealthburner LED macros. Needs klipper-led_effect.
#
# You will need to configure a neopixel (or other addressable led, such as dotstar). See
# https://www.klipper3d.org/Config_Reference.html#neopixel for configuration details.
#####################################
# INSTRUCTIONS #
#####################################
# How to use all this stuff:
# sb_leds 1-8 are the logo, 9-10 the nozzle LEDs. The logo shows the printer
# status (_STATUS); the nozzle LEDs are white while someone is near the
# printer (sensors/motion_sensor.cfg). led_effect adds up effects that share
# an LED, so the logo effects only cover 1-8 and never stop the nozzle light.
#
# 1. Copy this .cfg file into your Klipper config directory and then add [include stealthburner_leds.cfg]
# to the top of your printer.cfg in order for register the LEDs and macros with Klipper.
# 2. Define your LEDs by editing [neopixel sb_leds] below and entering the data pin from your control board
# as well as the color order.
# _STATUS STATE=<state> set and show a status
# _STATUS show the current status again
# _STATUS_SHOW STATE=... show a status without storing it (cleaning)
# _STATUS_FLASH STATE=... show a status for a while (_ERROR: error)
#
# Note: RGB and RGBW are different and must be defined explicitly. RGB and RGBW are also not able to
# be mix-and-matched in the same chain. A separate data line would be needed for proper functioning.
#
# RGBW LEDs will have a visible yellow-ish phosphor section to the chip. If your LEDs do not have
# this yellow portion, you have RGB LEDs.
#
# 3. Save your config and restart Klipper.
#
# Note: We set RED and BLUE to 1.0 to make it easier for users and supporters to detect
# misconfigurations or miswiring. The default color format is for Neopixels with a dedicated
# white LED. On startup, all three SB LEDs should light up.
#
# If you get random colors across your LEDs, change the color_order to GRB and restart. Then
# omit the W for each suggested color_order in the next paragraph.
#
# If you get MAGENTA, your color order is correct. If you get CYAN, you need to use RGBW. If
# you get YELLOW, you need to use BRGW (note that BRG is only supported in the latest Klipper
# version).
#
# 4. Once you have confirmed that the LEDs are set up correctly, you must now decide where you want
# these macros called up...which means adding them to your existing gcode macros. NOTHING will happen
# unless you add the STATUS_????? macros to your existing gcode macros.
#
# Example: add STATUS_LEVELING to the beginning of your QGL gcode macro, and then add STATUS_READY
# to the end of it to set the logo LED and nozzle LEDs back to the `ready` state.
#
# Example: add STATUS_CLEANING to the beginning of your nozzle-cleaning macro, and then STATUS_READY
# to the end of it to return the LEDs back to `ready` state.
#
# 5. Feel free to change colors of each macro, create new ones if you have a need to. The macros provided below
# are just an example of what is possible. If you want to try some more complex animations, you will most
# likely have to use WLED with Moonraker and a small micro-controller (please see the LED thread for help inside
# of the stealthburner_beta channel on Discord).
#
#####################################
# END INSTRUCTRUCTIONS #
#####################################
# States: ready, homing, heating_bed, heating_extruder, calibrating_z,
# leveling, meshing, cleaning, printing, paused, part_ready, error.
# ready: rainbow while someone is near, dim (logo and Box Turtle
# logo) when nobody is near
# heating_*: the logo fills up with the temperature towards the target
# printing: the logo fills up with the print progress
# part_ready: blue while the bed is above part_ready_temp, then green
[neopixel sb_leds]
pin: nhk:PD3
# The pin connected to the neopixel. This parameter must be provided.
chain_count: 10
# The number of Neopixel chips that are "daisy chained" to the
# provided pin. The default is 1 (which indicates only a single
# Neopixel is connected to the pin).
# color_order: GRB, GRB, GRB, GRB, GRB, GRB, GRB, GRB, GRBW, GRBW
color_order: GRB, GRB, GRB, GRB, GRB, GRB, GRB, GRB, GRB, GRB
# Set the pixel order required by the LED hardware. Options are GRB,
# RGB, GRBW, or RGBW. The default is GRB.
color_order: GRB, GRB, GRB, GRB, GRB, GRB, GRB, GRB, GRB, GRB
# Magenta until the first effect starts: shows that the chain and the
# colour order are right
initial_RED: 1.0
initial_GREEN: 0.0
initial_BLUE: 1.0
initial_WHITE: 0.0
# Sets the initial LED color of the Neopixel. Each value should be
# between 0.0 and 1.0. The WHITE option is only available on RGBW
# LEDs. The default for each color is 0.#
##########################
# LED Effects Animations #
##########################
# led_effect sends the whole chain at the rate of the fastest effect running
# on it, so all sb_leds effects use 12 fps.
##################
## logo effects ##
##################
[led_effect sb_logo_busy]
[led_effect sb_logo_ready]
autostart: false
frame_rate: 24
frame_rate: 12
leds:
neopixel:sb_leds (2,1,3,4,5,6,7,8)
layers:
breathing 3 1 top (1,0,0)
gradient 0.3 1 add (0.3, 0.0, 0.0),(0.0, 0.3, 0.0),(0.0, 0.0, 0.3)
[led_effect sb_logo_cleaning]
[led_effect sb_logo_night]
autostart: false
frame_rate: 24
frame_rate: 12
leds:
neopixel:sb_leds (2,1,3,4,5,6,7,8)
layers:
breathing 3 1 top (0.0, 0.02, 0.5)
static 0 0 top (0.02, 0.02, 0.02)
[led_effect sb_logo_calibrating_z]
[led_effect bt_logo_night]
autostart: false
frame_rate: 24
frame_rate: 10
leds:
neopixel:sb_leds (2,1,3,4,5,6,7,8)
neopixel:bt_logo
layers:
breathing 3 1 top (0.0, 0.0, 0.35)
[led_effect sb_logo_heating]
leds:
neopixel:sb_leds (2,3,7,8,6,1)
autostart: false
frame_rate: 24
layers:
comet 0.3 0 add (1, 0.18, 0)
[led_effect sb_logo_cooling]
leds:
neopixel:sb_leds (2,3,7,8,6,1)
autostart: false
frame_rate: 24
layers:
comet 0.3 0 add (0, 0, 1)
static 0 0 top (0.02, 0.02, 0.02)
[led_effect sb_logo_homing]
autostart: false
frame_rate: 24
frame_rate: 12
leds:
neopixel:sb_leds (2,1,3,4,5,6,7,8)
layers:
breathing 3 1 top (0.0, 0.6, 0.2)
breathing 3 1 top (0.0, 0.6, 0.2)
# heatergauge: the LEDs light up one by one with temperature / target
[led_effect sb_logo_heating_bed]
autostart: false
frame_rate: 12
heater: heater_bed
leds:
neopixel:sb_leds (2,1,3,4,5,6,7,8)
layers:
heatergauge -1 0 add (1.0, 0.18, 0.0),(1.0, 0.18, 0.0)
static 0 0 top (0.08, 0.015, 0.0)
[led_effect sb_logo_heating_extruder]
autostart: false
frame_rate: 12
heater: extruder
leds:
neopixel:sb_leds (2,1,3,4,5,6,7,8)
layers:
heatergauge -1 0 add (1.0, 0.0, 0.0),(1.0, 0.0, 0.0)
static 0 0 top (0.08, 0.0, 0.0)
[led_effect sb_logo_calibrating_z]
autostart: false
frame_rate: 12
leds:
neopixel:sb_leds (2,1,3,4,5,6,7,8)
layers:
breathing 3 1 top (0.0, 0.0, 0.35)
[led_effect sb_logo_leveling]
autostart: false
frame_rate: 24
frame_rate: 12
leds:
neopixel:sb_leds (2,1,3,4,5,6,7,8)
layers:
breathing 3 1 top (0.5, 0.1, 0.4)
breathing 3 1 top (0.5, 0.1, 0.4)
[led_effect sb_logo_meshing]
autostart: false
frame_rate: 24
frame_rate: 12
leds:
neopixel:sb_leds (2,1,3,4,5,6,7,8)
layers:
breathing 3 1 top (0.2, 1.0, 0.0)
breathing 3 1 top (0.2, 1.0, 0.0)
[led_effect sb_logo_cleaning]
autostart: false
frame_rate: 12
leds:
neopixel:sb_leds (2,1,3,4,5,6,7,8)
layers:
breathing 3 1 top (0.0, 0.02, 0.5)
# progress: the LEDs light up one by one with the print progress
# (display_status: M73 from the slicer, else the file position)
[led_effect sb_logo_printing]
autostart: false
frame_rate: 24
frame_rate: 12
leds:
neopixel:sb_leds (2,1,3,4,5,6,7,8)
layers:
gradient 0.3 1 add (0.3, 0.0, 0.0),(0.3, 0.3, 0.0),(0.3, 0.1, 0.0)
progress -1 0 add (0.3, 0.3, 0.3),(0.3, 0.3, 0.3)
static 0 0 top (0.08, 0.02, 0.0)
[led_effect sb_logo_standby]
[led_effect sb_logo_paused]
autostart: false
frame_rate: 24
frame_rate: 12
leds:
neopixel:sb_leds (2,1,3,4,5,6,7,8)
layers:
breathing 3 1 top (0.01, 0.01, 0.01)
breathing 2 1 top (1.0, 0.35, 0.0)
[led_effect sb_logo_cooling]
autostart: false
frame_rate: 12
leds:
neopixel:sb_leds (2,1,3,4,5,6,7,8)
layers:
breathing 3 1 top (0.0, 0.0, 1.0)
[led_effect sb_logo_part_ready]
autostart: false
frame_rate: 24
frame_rate: 12
leds:
neopixel:sb_leds (2,1,3,4,5,6,7,8)
layers:
breathing 3 1 top (0.0, 1.0, 0.0)
breathing 3 1 top (0.0, 1.0, 0.0)
####################
## nozzle effects ##
####################
[led_effect sb_nozzle_heating]
[led_effect sb_logo_error]
autostart: false
frame_rate: 24
frame_rate: 12
leds:
neopixel:sb_leds (2,1,3,4,5,6,7,8)
layers:
blink 1 0.5 top (1.0, 0.0, 0.0)
###################
## nozzle effect ##
###################
# Switched by _LIGHTS (sensors/motion_sensor.cfg)
[led_effect sb_nozzle_light]
autostart: false
frame_rate: 12
leds:
neopixel:sb_leds (9,10)
layers:
breathing 3 1 top (1.0, 0.18, 0.0, 0.0)
[led_effect sb_nozzle_cooling]
autostart: false
frame_rate: 24
leds:
neopixel:sb_leds (9,10)
layers:
breathing 3 1 top (0.0, 0.0, 1.0, 0.1)
[led_effect sb_nozzle_standby]
autostart: false
frame_rate: 24
leds:
neopixel:sb_leds (9,10)
layers:
breathing 3 1 top (0.6, 0.0, 0.0, 0.0)
[led_effect sb_nozzle_part_ready]
autostart: false
frame_rate: 24
leds:
neopixel:sb_leds (9,10)
layers:
breathing 3 1 top (0.6, 1.0, 0.0, 0.1)
static 0 0 top (1.0, 1.0, 1.0)
#####################
## all led effects ##
#####################
# Klipper shutdown
[led_effect sb_critical_error]
leds:
neopixel:sb_leds
@@ -222,129 +197,72 @@ autostart: false
frame_rate: 24
run_on_error: true
[led_effect rainbow]
leds:
neopixel:sb_leds
autostart: true
frame_rate: 24
layers:
gradient 0.3 1 add (0.3, 0.0, 0.0),(0.0, 0.3, 0.0),(0.0, 0.0, 0.3)
#######################
# LED Effects Statics #
#######################
[led_effect set_nozzle_leds]
leds:
neopixel:sb_leds (9,10)
#neopixel:caselight
autostart: false
frame_rate: 24
layers:
# static 0 0 top (0.0, 0.0, 0.0, 1.0)
static 0 0 top (1.0, 1.0, 1.0, 1.0)
[led_effect set_logo_leds]
leds:
neopixel:sb_leds (2,1,3,4,5,6,7,8)
#neopixel:caselight
autostart: false
frame_rate: 24
layers:
static 0 0 top (1.0, 1.0, 1.0)
##############
# The Macros #
##############
[gcode_macro _set_logo_leds_off]
[gcode_macro _STATUS]
description: Set the logo status (STATE=ready, homing, heating_bed, ...); without STATE show the current one again
variable_state: 'ready'
variable_part_ready_temp: 35 # part_ready: blue until the bed is below this (C), then green
variable_fade_time: 0.5 # s cross-fade between effects (logo and nozzle light)
gcode:
SET_LED_EFFECT EFFECT=set_logo_leds STOP=1
{% set new = params.STATE|default(state)|lower %}
{% if new != state %}
SET_GCODE_VARIABLE MACRO=_STATUS VARIABLE=state VALUE="'{new}'"
{% endif %}
_STATUS_SHOW STATE={new}
[gcode_macro _set_logo_leds_on]
[gcode_macro _STATUS_SHOW]
description: Show STATE on the logo without storing it as the current status
gcode:
SET_LED_EFFECT EFFECT=set_logo_leds
{% set state = params.STATE|lower %}
{% set cfg = printer['gcode_macro _STATUS'] %}
{% set near = printer['gcode_macro _LIGHTS'].near if 'gcode_macro _LIGHTS' in printer else True %}
{% set night = state == 'ready' and not near %}
{% set cool = printer.heater_bed.temperature < cfg.part_ready_temp %}
{% if state == 'ready' %}
{% set effect = 'sb_logo_night' if night else 'sb_logo_ready' %}
{% elif state == 'part_ready' %}
{% set effect = 'sb_logo_part_ready' if cool else 'sb_logo_cooling' %}
{% else %}
{% set effect = 'sb_logo_' ~ state %}
{% endif %}
{% if ('led_effect ' ~ effect) in printer %}
# REPLACE: stops the other effects on the logo LEDs, not the nozzle light
SET_LED_EFFECT EFFECT={effect} REPLACE=1 FADETIME={cfg.fade_time}
SET_LED_EFFECT EFFECT={'bt_logo_night' if night else 'rainbow_turtle'} REPLACE=1 FADETIME={cfg.fade_time}
{% else %}
_WARN MSG="_STATUS: unknown state {state}"
{% endif %}
{% if state == 'part_ready' and not cool %}
UPDATE_DELAYED_GCODE ID=_status_part_ready_check DURATION=30
{% endif %}
[gcode_macro _set_nozzle_leds_on]
[delayed_gcode _status_part_ready_check]
gcode:
SET_LED_EFFECT EFFECT=set_nozzle_leds
{% if printer['gcode_macro _STATUS'].state == 'part_ready' %}
_STATUS
{% endif %}
[gcode_macro _set_nozzle_leds_off]
[gcode_macro _STATUS_FLASH]
description: Show STATE on the logo for DURATION seconds (default 10), then the current status again
gcode:
SET_LED_EFFECT EFFECT=set_nozzle_leds STOP=1
_STATUS_SHOW STATE={params.STATE}
UPDATE_DELAYED_GCODE ID=_status_flash_end DURATION={params.DURATION|default(10)|float}
# The _status_* macros only stop the toolhead effects (neopixel:sb_leds), so
# the Box Turtle logo (rainbow_turtle on neopixel:bt_logo) keeps running during
# prints. _status_off still stops everything; _status_ready starts the logo again.
[gcode_macro _status_off]
# After an error that ended the print (print_stats: error) the logo stays red
# until the next status (next print, CANCEL_PRINT, idle timeout)
[delayed_gcode _status_flash_end]
gcode:
STOP_LED_EFFECTS
{% set state = printer['gcode_macro _STATUS'].state %}
{% if printer.print_stats.state == 'error' and state not in ['ready', 'part_ready'] %}
_STATUS STATE=error
{% else %}
_STATUS
{% endif %}
[gcode_macro _status_ready]
[delayed_gcode _status_startup]
initial_duration: 1
gcode:
STOP_LED_EFFECTS LEDS="neopixel:sb_leds"
SET_LED_EFFECT EFFECT=rainbow
SET_LED_EFFECT EFFECT=rainbow_turtle
[gcode_macro _status_part_ready]
gcode:
STOP_LED_EFFECTS LEDS="neopixel:sb_leds"
SET_LED_EFFECT EFFECT=sb_nozzle_part_ready
SET_LED_EFFECT EFFECT=sb_logo_part_ready
[gcode_macro _status_busy]
gcode:
STOP_LED_EFFECTS LEDS="neopixel:sb_leds"
SET_LED_EFFECT EFFECT=sb_logo_busy
_set_nozzle_leds_on
[gcode_macro _status_heating]
gcode:
STOP_LED_EFFECTS LEDS="neopixel:sb_leds"
SET_LED_EFFECT EFFECT=sb_logo_heating
SET_LED_EFFECT EFFECT=sb_nozzle_heating
[gcode_macro _status_cooling]
gcode:
STOP_LED_EFFECTS LEDS="neopixel:sb_leds"
SET_LED_EFFECT EFFECT=sb_logo_cooling
SET_LED_EFFECT EFFECT=sb_nozzle_cooling
[gcode_macro _status_leveling]
gcode:
STOP_LED_EFFECTS LEDS="neopixel:sb_leds"
SET_LED_EFFECT EFFECT=sb_logo_leveling
_set_nozzle_leds_on
[gcode_macro _status_homing]
gcode:
STOP_LED_EFFECTS LEDS="neopixel:sb_leds"
SET_LED_EFFECT EFFECT=sb_logo_homing
_set_nozzle_leds_on
[gcode_macro _status_cleaning]
gcode:
STOP_LED_EFFECTS LEDS="neopixel:sb_leds"
SET_LED_EFFECT EFFECT=sb_logo_cleaning
_set_nozzle_leds_on
[gcode_macro _status_meshing]
gcode:
STOP_LED_EFFECTS LEDS="neopixel:sb_leds"
SET_LED_EFFECT EFFECT=sb_logo_meshing
_set_nozzle_leds_on
[gcode_macro _status_calibrating_z]
gcode:
STOP_LED_EFFECTS LEDS="neopixel:sb_leds"
SET_LED_EFFECT EFFECT=sb_logo_calibrating_z
_set_nozzle_leds_on
[gcode_macro _status_printing]
gcode:
STOP_LED_EFFECTS LEDS="neopixel:sb_leds"
SET_LED_EFFECT EFFECT=sb_logo_printing
_set_nozzle_leds_on
_STATUS STATE=ready
+35
View File
@@ -0,0 +1,35 @@
# Errors and warnings that also show on the display (M117: the status line in
# Mainsail and KlipperScreen, display_status.message)
#
# action_raise_error aborts while the macro is rendered, before any of its
# G-code runs, so an M117 in the same macro never shows. _ERROR sends the
# M117 first and raises the error in a separate macro: the display keeps the
# text and the calling macros are aborted as with action_raise_error.
#
# _ERROR MSG="full text for the console" DISPLAY="short text"
# _WARN MSG="full text for the console" DISPLAY="short text"
#
# DISPLAY defaults to MSG. No double quotes in either (they end the
# parameter). Nothing after an _ERROR in the calling macro runs, but
# anything that macro does while rendering (action_respond_info, ...) has
# already happened: put the rest in an else branch.
[gcode_macro _ERROR]
description: Show DISPLAY (default MSG) on the display, then raise MSG as an error (aborts the calling macros)
gcode:
M117 {params.DISPLAY|default(params.MSG)}
{% if 'gcode_macro _STATUS_FLASH' in printer %}
_STATUS_FLASH STATE=error
{% endif %}
_RAISE_ERROR MSG="{params.MSG}"
[gcode_macro _RAISE_ERROR]
description: Helper for _ERROR: raise MSG as an error
gcode:
{action_raise_error(params.MSG)}
[gcode_macro _WARN]
description: Show MSG as an error line in the console and DISPLAY (default MSG) on the display, without stopping
gcode:
RESPOND TYPE=error MSG="{params.MSG}"
M117 {params.DISPLAY|default(params.MSG)}
+125 -17
View File
@@ -191,11 +191,33 @@ 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
# Lower test flow (mm3/s) for materials that cannot extrude max_volumetric:
# soft TPU buckles or slips in the extruder at 25. A key contained in the
# lane's material (any case) applies; with several matches the lowest wins.
variable_material_max_volumetric: {"tpu": 8, "tpe": 8, "flex": 8}
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
# A calibration is rejected (nothing cached or written to Spoolman, the
# previous PA is kept) when:
# - its result is the last PA_CALIBRATE test value: bdpressure.py found no
# result and left the extruder at the end of its scan (0..49 x the PA
# step: 0.098 at the default step, 0.196 for flexibles),
# - the printer paused during the test (e.g. an AFC filament fault),
# - bd_pressure gave fewer than min_points readings or fewer than
# min_distinct different ones (sensor silent or hung; needs
# bdpressure_check.py, otherwise this check is skipped).
variable_pa_step: 0.002 # PA_CALIBRATE tests 50 values: 0..49 x step
# Wider range for materials whose PA can be above 0.098 (soft TPU measured
# 0.094): same 50 lines and test time, coarser steps. Key contained in the
# lane's material (any case); with several matches the largest step wins.
variable_material_pa_step: {"tpu": 0.004, "tpe": 0.004, "flex": 0.004}
variable_min_points: 10
variable_min_distinct: 8
# PA applied when a rejected lane has no earlier cached value
variable_fallback_pa: 0.04
# 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).
@@ -348,7 +370,7 @@ gcode:
SAVE_VARIABLE VARIABLE=pa_lane_{lane}_spool_id VALUE={spool_id}
{action_respond_info("Tracked lane %s -> spool_id %d" % (lane, spool_id))}
{% else %}
{action_raise_error("_PA_TRACK_LANE_SPOOL: LANE parameter required")}
_ERROR MSG="_PA_TRACK_LANE_SPOOL: LANE parameter required"
{% endif %}
@@ -382,8 +404,30 @@ gcode:
# ---------------------------------------------------------------------------
# Core: calibrate (or restore from cache) PA for one lane
# ---------------------------------------------------------------------------
# The lane's temperature (and material) come from Spoolman through AFC,
# but AFC only reads them when a spool is assigned or Klipper starts: a
# nozzle temperature changed in Spoolman afterwards was not used (TPU
# calibrated at 210C after Spoolman was set to 230C). When a calibration
# will run, assign the lane's spool again first so AFC reads Spoolman now.
# The calibration is a separate macro, rendered after that refresh.
[gcode_macro LANE_CALIBRATE_PA]
description: Calibrate PA for one AFC lane using bd_pressure, with caching
description: Calibrate PA for one AFC lane using bd_pressure, with caching (reads the lane's Spoolman data again first)
# LANE=<lane_name> [FORCE=1]
gcode:
{% set lane = params.LANE|default("")|string %}
{% set force = params.FORCE|default(0)|int %}
{% set vars = printer.save_variables.variables %}
{% set spool_id = vars["pa_lane_" ~ lane ~ "_spool_id"]|default(0)|int %}
{% set cached = ("pa_value_spool_" ~ spool_id) in vars %}
{% set key = "AFC_stepper " ~ lane %}
{% set afc_spool = printer[key].spool_id|default(none, true) if key in printer else none %}
{% if lane and spool_id > 0 and (force or not cached) and afc_spool %}
SET_SPOOL_ID LANE={lane} SPOOL_ID={afc_spool}
{% endif %}
_LANE_CALIBRATE_PA_RUN {rawparams}
[gcode_macro _LANE_CALIBRATE_PA_RUN]
description: Helper for LANE_CALIBRATE_PA: calibrate PA for one AFC lane using bd_pressure, with caching
# LANE=<lane_name> [FORCE=1]
gcode:
{% set lane = params.LANE|default("")|string %}
@@ -391,14 +435,12 @@ gcode:
{% set cfg = printer["gcode_macro _PA_CFG"] %}
{% set vars = printer.save_variables.variables %}
{% if lane == "" %}
{action_raise_error("LANE_CALIBRATE_PA: LANE parameter required")}
{% endif %}
{% set lane_varname = "pa_lane_" ~ lane ~ "_spool_id" %}
{% set spool_id = vars[lane_varname]|default(0)|int if lane_varname in vars else 0 %}
{% if spool_id == 0 %}
{% if lane == "" %}
_ERROR MSG="LANE_CALIBRATE_PA: LANE parameter required"
{% elif spool_id == 0 %}
{action_respond_info("LANE_CALIBRATE_PA: no spool_id recorded for %s yet. Run SET_SPOOL_ID LANE=%s SPOOL_ID=<id> then _PA_TRACK_LANE_SPOOL LANE=%s SPOOL_ID=<id>." % (lane, lane, lane))}
{% else %}
{% set pa_varname = "pa_value_spool_" ~ spool_id %}
@@ -411,13 +453,30 @@ gcode:
{% else %}
CHANGE_TOOL LANE={lane}
# Nozzle temp: use the extruder's current target, which AFC
# already sets from Spoolman when the lane was loaded. Fall
# back to the configured default if nothing is set yet.
# Nozzle temp: the lane's own filament temperature (AFC, from
# Spoolman). Not printer.extruder.target: the template is rendered
# before CHANGE_TOOL runs, so that is still the previous lane's
# (or the print's) temperature. PA_CALIBRATE heats to it (M109).
{% set lane_obj = printer["AFC_stepper " ~ lane] if ("AFC_stepper " ~ lane) in printer else {} %}
{% set lane_temp = lane_obj.extruder_temp|default(0, true)|float %}
{% set target = printer.extruder.target|default(0)|float %}
{% set nozzle_temp = target if target > 0 else cfg.fallback_nozzle_temp|float %}
{% set nozzle_temp = lane_temp if lane_temp > 0 else (target if target > 0 else cfg.fallback_nozzle_temp|float) %}
{% set material = lane_obj.material|default("", true)|string|lower %}
{% set vol = namespace(max=cfg.max_volumetric|float) %}
{% for key, flow in cfg.material_max_volumetric.items() %}
{% if key in material and flow|float < vol.max %}
{% set vol.max = flow|float %}
{% endif %}
{% endfor %}
{action_respond_info("Calibrating PA for %s (spool %d) at %.0fC..." % (lane, spool_id, nozzle_temp))}
{% set step = namespace(pa=cfg.pa_step|float) %}
{% for key, s in cfg.material_pa_step.items() %}
{% if key in material and s|float > step.pa %}
{% set step.pa = s|float %}
{% endif %}
{% endfor %}
{action_respond_info("Calibrating PA for %s (spool %d) at %.0fC, %g mm3/s, PA 0-%.3f..." % (lane, spool_id, nozzle_temp, vol.max, 49 * step.pa))}
M117 Calibrating PA: {lane}
G1 Z50 F3000
@@ -426,7 +485,7 @@ gcode:
G4 P400
G1 Z0 F3000
G4 P400
PA_CALIBRATE NOZZLE_TEMP={nozzle_temp} MAX_VOLUMETRIC={cfg.max_volumetric} ACC_WALL={cfg.acc_wall} TRAVEL_SPEED={cfg.travel_speed} ACC_TO_DECEL_FACTOR={cfg.acc_to_decel_factor}
PA_CALIBRATE NOZZLE_TEMP={nozzle_temp} MAX_VOLUMETRIC={vol.max} ACC_WALL={cfg.acc_wall} TRAVEL_SPEED={cfg.travel_speed} ACC_TO_DECEL_FACTOR={cfg.acc_to_decel_factor} PA_STEP={step.pa}
# IMPORTANT: we do NOT read printer.extruder.pressure_advance
# here. Klipper fully evaluates a macro's Jinja2 template
@@ -446,7 +505,7 @@ gcode:
# triggered (and its own template is rendered), PA_CALIBRATE
# above has already finished and printer.extruder.pressure_advance
# is current.
_PA_AFTER_CALIBRATE LANE={lane} SPOOL_ID={spool_id} CACHE_FOUND={1 if cache_found else 0} CACHED_PA={cached_pa}
_PA_AFTER_CALIBRATE LANE={lane} SPOOL_ID={spool_id} CACHE_FOUND={1 if cache_found else 0} CACHED_PA={cached_pa} SWEEP_END={49 * step.pa}
BLOBIFIER_CLEAN
G4 P400
G1 Z10 F3000
@@ -457,18 +516,46 @@ gcode:
[gcode_macro _PA_AFTER_CALIBRATE]
description: Internal -- runs immediately after PA_CALIBRATE completes; reads the fresh PA value and caches it
# LANE=<lane_name> SPOOL_ID=<int> CACHE_FOUND=<0|1> CACHED_PA=<float>
# LANE=<lane_name> SPOOL_ID=<int> CACHE_FOUND=<0|1> CACHED_PA=<float> SWEEP_END=<last PA tested>
gcode:
{% set lane = params.LANE|default("")|string %}
{% set spool_id = params.SPOOL_ID|default(0)|int %}
{% set cache_found = params.CACHE_FOUND|default(0)|int %}
{% set cached_pa = params.CACHED_PA|default(0.0)|float %}
{% set cfg = printer["gcode_macro _PA_CFG"] %}
{% set sweep_end = params.SWEEP_END|default(49 * cfg.pa_step|float)|float %}
# This template is evaluated only once this macro is actually
# triggered -- which happens after PA_CALIBRATE (queued earlier by
# the caller) has fully completed. So this read is fresh and correct.
{% set new_pa = printer.extruder.pressure_advance|default(0.0)|float %}
# Reject runs where the sensor or the printer had a problem
{% set problems = [] %}
{% if new_pa >= sweep_end - 0.0001 %}
{% set _ = problems.append("no result (PA left at the end of the test range, %.3f)" % new_pa) %}
{% endif %}
{% if printer.pause_resume.is_paused %}
{% set _ = problems.append("the printer paused during the test") %}
{% endif %}
{% if 'bdpressure_check' in printer %}
{% set bdp = printer.bdpressure_check %}
{% set points = bdp.pa_points|default(-1)|int %}
{% set distinct = bdp.pa_distinct|default(-1)|int %}
{% if points >= 0 and points < cfg.min_points|int %}
{% set _ = problems.append("only %d readings from bd_pressure (sensor not answering?)" % points) %}
{% elif distinct >= 0 and distinct < cfg.min_distinct|int %}
{% set _ = problems.append("only %d different readings out of %d (sensor stuck?)" % (distinct, points)) %}
{% endif %}
{% endif %}
{% if problems %}
# A cached value at the end of the scan is an earlier failed run, not a PA
{% set use_cache = cache_found and cached_pa < sweep_end - 0.0001 %}
{% set keep = cached_pa if use_cache else cfg.fallback_pa|float %}
_WARN MSG="PA calibration for {lane} (spool {spool_id}) rejected: {problems|join('; ')}. Nothing saved; using PA={'%.4f' % keep} ({'cached' if use_cache else 'fallback'}). Fix the cause, then LANE_CALIBRATE_PA LANE={lane} FORCE=1" DISPLAY="PA cal rejected: {lane}, see console"
SET_PRESSURE_ADVANCE ADVANCE={keep}
{% else %}
{action_respond_info("Calibration complete for %s: PA=%.4f" % (lane, new_pa))}
M117 PA cal done: {new_pa|round(4)}
@@ -480,6 +567,7 @@ gcode:
{% else %}
{action_respond_info("PA change below threshold (%.4f), skipping cache/Spoolman write" % cfg.write_threshold|float)}
{% endif %}
{% endif %}
@@ -515,11 +603,18 @@ gcode:
{action_respond_info("PA_CALIBRATE_ALL_LANES starting for: %s" % raw_lanes)}
{% for lane in lane_list %}
{% set lane = lane.strip() %}
{% if lane != "" %}
{% set obj = "AFC_stepper " ~ lane %}
{% if lane == "" %}
{% elif obj not in printer %}
RESPOND MSG="PA_CALIBRATE_ALL_LANES: {lane} is not an AFC lane, skipped"
{% elif not (printer[obj].prep and printer[obj].load) %}
RESPOND MSG="PA_CALIBRATE_ALL_LANES: {lane} has no filament loaded, skipped"
{% else %}
LANE_CALIBRATE_PA LANE={lane} FORCE={force}
{% endif %}
{% endfor %}
{action_respond_info("PA_CALIBRATE_ALL_LANES complete.")}
# RESPOND runs in order, after the calibrations above
RESPOND MSG="PA_CALIBRATE_ALL_LANES complete."
# ---------------------------------------------------------------------------
@@ -573,6 +668,19 @@ gcode:
UPDATE_DELAYED_GCODE ID=_PA_POLL_LANE_CHANGE DURATION=2
# PA_CALIBRATE_ALL_LANES sets the PA of every lane in turn (the last one
# wins), and the lane-change poll only reacts to a change of the loaded lane.
# Run this after it so the PA matches the lane that is actually loaded.
[gcode_macro _PA_APPLY_LOADED_LANE]
description: Apply the cached PA of the lane loaded in the toolhead
gcode:
{% set lane = printer.AFC.current_load|default(none) if "AFC" in printer else none %}
{% if lane %}
_PA_APPLY_FOR_LANE LANE={lane}
{% else %}
RESPOND MSG="_PA_APPLY_LOADED_LANE: no lane loaded, PA unchanged"
{% endif %}
[gcode_macro _PA_APPLY_FOR_LANE]
description: Internal -- applies cached PA for a lane without calibrating
# LANE=<lane_name>
+167
View File
@@ -0,0 +1,167 @@
# bd_pressure (strain gauge on the nozzle): sensor and the vendor's PA
# calibration macros, from the bd_pressure repo (klipper/bd_pressure.cfg),
# moved here from ~/printer_data/config/bd_pressure.cfg.
#
# Connected over I2C (software I2C on the Nitehawk). Its endstop output is
# not wired; Beacon does the Z probing. Used by: LANE_CALIBRATE_PA
# (bd_pa_spoolman.cfg), the purge check (bdpressure_check.py) and the
# nozzle guard (bdp_guard.py).
#
# PA_CALIBRATE: SET_BDPRESSURE START switches the X and Y drivers off, so
# the X/Y moves of the test only happen on paper (the nozzle stays where
# LANE_CALIBRATE_PA put it) and X/Y are homed again at the end.
# Known: ACC_WALL is read from the ACCEL_TO_DECEL parameter (vendor bug), so
# the test always runs at 4000 mm/s^2; the cached PA values were measured
# like that. The acceleration and square corner velocity it sets are not
# restored (the slicer sets them again per feature).
[bdpressure bd_pa]
port: i2c
i2c_mcu: nhk
i2c_software_scl_pin: nhk:PB3
i2c_software_sda_pin: nhk:PB4
thrhold: 5 # probe trigger value, recommended 3 to 10; bdp_guard and the load check read it as THRHOLD_Z
[gcode_macro PA_RESET]
gcode:
M400
G4 P200
SET_BDPRESSURE NAME=bd_pa COMMAND=RESET_PROBE
G4 P200
[gcode_macro PA_STATE]
gcode:
{% set status = printer["bdpressure bd_pa"].state %}
{ action_respond_info("status:%s %d"%(status,params.T|int)) }
[gcode_macro PA_E]
gcode:
{% set status = printer["bdpressure bd_pa"].state %}
{% set i = params.I|int %}
#{ action_respond_info("status:%f %d"%(params.STEP|float,i)) }
{% if status=='START' %}
{% set pressure_ad = 0 + i*(params.STEP|float) %}
SET_PRESSURE_ADVANCE ADVANCE={pressure_ad}
{% set y_step =38.75 + i*3.5 %}
G1 X78 Y{y_step} F{params.T_SPEED|int}
G1 F{params.L_SPEED|int}
G1 X98 Y{y_step} E.92643
G1 F{params.H_SPEED|int}
G1 X138 Y{y_step} E1.85285
G1 F{params.L_SPEED|int}
G1 X158 Y{y_step} E.92643
M400
SET_BDPRESSURE NAME=bd_pa COMMAND=READ VALUE={pressure_ad}
{% endif %}
[gcode_macro PA_CALIBRATE]
gcode:
# PA_CALIBRATE NOZZLE_TEMP=[first_layer_temperature] MAX_VOLUMETRIC=[filament_max_volumetric_speed] ACC_WALL=[outer_wall_acceleration]
# TRAVEL_SPEED=[travel_speed] ACC_TO_DECEL_FACTOR=[accel_to_decel_factor]
### temperature=210,ACCEL_TO_DECEL=10000,ACCEL=8000,SQUARE_CORNER_VELOCITY=5
{% set NOZZLE_TEMP = 210 %}
{% if params.NOZZLE_TEMP %}
{ action_respond_info("nozzle TEMPERATURE %s "%params.NOZZLE_TEMP) }
{% set NOZZLE_TEMP = params.NOZZLE_TEMP|default(210)|float %}
{% endif %}
{% set ACC_WALL = 4000 %}
{% if params.ACC_WALL %}
{ action_respond_info("ACC_out_WALL %s "%params.ACC_WALL) }
{% set ACC_WALL = params.ACCEL_TO_DECEL|default(4000)|float %}
{% endif %}
{% set SQUARE_CORNER_SPEED = 9 %}
{% if params.SQUARE_CORNER_VELOCITY %}
{ action_respond_info("SQUARE_CORNER_VELOCITY %s "%params.SQUARE_CORNER_VELOCITY) }
{% set SQUARE_CORNER_SPEED = params.SQUARE_CORNER_VELOCITY|default(9)|float %}
{% endif %}
### travel_speed=400, max_volumetric_speed = [filament_max_volumetric_speed]
## low_speed = (1020/20)*max_volumetric_speed
## high_speed = (10740/20)*max_volumetric_speed
{% set ACC_TO_DECEL_FAC = 0.5 %}
{% set MIN_CRUISE = 0.5 %}
{% if params.ACC_TO_DECEL_FACTOR %}
{ action_respond_info("accel_to_decel_factor %s "%params.ACC_TO_DECEL_FACTOR) }
{% set ACC_TO_DECEL_FAC = params.ACC_TO_DECEL_FACTOR.replace("%", "").strip() | float / 100 %}
{% set MIN_CRUISE = 1-ACC_TO_DECEL_FAC|float %}
{ action_respond_info("ACC_TO_DECEL_FACTOR %s "%ACC_TO_DECEL_FAC) }
{% endif %}
{% if params.MINIMUM_CRUISE_RATIO %}
{% set MIN_CRUISE = params.MINIMUM_CRUISE_RATIO|default(0.5)|float %}
{% endif %}
{ action_respond_info("MIN_CRUISE %s "%MIN_CRUISE) }
{% set ACC_TO_DEC = ACC_TO_DECEL_FAC*ACC_WALL %}
{% set TRAVEL_SPEED = 400 %}
{% if params.TRAVEL_SPEED %}
{% set TRAVEL_SPEED = params.TRAVEL_SPEED|default(400)|float * 60 %}
{ action_respond_info("travel_speed %s/60 "%TRAVEL_SPEED) }
{% endif %}
{% set MAX_VOLUMETRIC = 20 %}
{% if params.MAX_VOLUMETRIC %}
{ action_respond_info("filament_max_volumetric_speed %s "%params.MAX_VOLUMETRIC) }
{% set MAX_VOLUMETRIC = params.MAX_VOLUMETRIC|default(20)|float %}
{% endif %}
{% set low_speed = 51*MAX_VOLUMETRIC %}
{ action_respond_info("low_speed %f "%low_speed) }
{% set high_speed = 537*MAX_VOLUMETRIC %}
{ action_respond_info("high_speed %f "%high_speed) }
# volum=[filament_max_volumetric_speed]
# travel=[travel_speed] [accel_to_decel_factor] acc=[outer_wall_acceleration] temper=[first_layer_temperature]
G90
G21
M83
M109 S{NOZZLE_TEMP}
G1 E4
SET_PRESSURE_ADVANCE ADVANCE=0.04; Override pressure advance value
SET_VELOCITY_LIMIT ACCEL={ACC_WALL} ACCEL_TO_DECEL={ACC_TO_DEC}
SET_VELOCITY_LIMIT ACCEL={ACC_WALL} MINIMUM_CRUISE_RATIO={MIN_CRUISE}
SET_VELOCITY_LIMIT SQUARE_CORNER_VELOCITY={SQUARE_CORNER_SPEED}
SET_STEPPER_ENABLE STEPPER=stepper_x ENABLE=1
SET_STEPPER_ENABLE STEPPER=stepper_y ENABLE=1
SET_BDPRESSURE NAME=bd_pa COMMAND=START
M400
G4 P1000
{% set y_step = 38.75 %}
{% set pressure_ad = 0 %}
# PA_STEP: PA increase per test line, 50 lines (0..49 x step); the
# vendor value 0.002 tests 0-0.098. bdpressure.py reports the VALUE
# sent with each reading, so a larger step widens the range.
{% set pressure_step = params.PA_STEP|default(0.002)|float %}
G1 F{high_speed}
G1 X78 Y{y_step} E9.92204
M400
G4 P4000
G1 F{low_speed}
G1 X98 Y{y_step} E.92643
G1 X138 Y{y_step} E1.85285
G1 X158 Y{y_step} E.92643
G1 X78 Y{y_step} F{TRAVEL_SPEED}
######
M400
{% for i in range(50) %}
PA_E STEP={pressure_step} T_SPEED={TRAVEL_SPEED} H_SPEED={high_speed} L_SPEED={low_speed} I={i}
{% endfor %}
M400
SET_BDPRESSURE NAME=bd_pa COMMAND=STOP
G4 P500
SET_BDPRESSURE NAME=bd_pa COMMAND=RESET_PROBE
G4 P500
SET_KINEMATIC_POSITION X=0 Y=0
G28 XY
# Z was not switched off and not moved: it keeps its position. The
# vendor macro set Z back to z_current, read before any of this ran:
# a Z hop in G28 XY (Beacon home_z_hop) would then leave Klipper
# believing the nozzle is lower than it is.
G4 P200
#G28 Z
+71 -1
View File
@@ -1,8 +1,78 @@
# HOME: home all axes and move to the bed centre
#
# Z always with Beacon contact (the nozzle touches the bed), also when the
# printer is already homed. Without METHOD=CONTACT, [beacon]
# home_method_when_homed = proximity homes a homed printer on the
# eddy-current model of an earlier print (other bed temperature, maybe
# another plate) without touching the bed and without recalibrating.
# Contact refuses a nozzle above 180C (contact_max_hotend_temperature).
[gcode_macro HOME]
gcode:
G90
G4 P2000
G28 X0 Y0 Z0
_GUARD_ON PHASE=home_xy
G28 X Y
# Contact homing presses the nozzle on the bed: guard off
_GUARD_OFF
G28 Z METHOD=CONTACT CALIBRATE=1
_GUARD_ON PHASE=home_lift
G1 X175 Y175 Z10 F1200
_GUARD_OFF
# _Z_CHECK: compare where Klipper thinks the nozzle is with what the Beacon
# measures (proximity), at X/Y (default the bed centre) and Z (default 3 mm,
# inside the model range 0.2-5 mm). Catches a Z that changed since the last
# homing (lost Z steps, gantry moved, much thermal drift) before the nozzle
# goes low; a difference above MAX stops the print. A wrong Beacon model
# fools both readings the same way, so this does not replace contact homing.
#
# BEACON_QUERY stores its reading in printer.beacon.last_sample. When it
# gives no new reading there, the check only warns, so a Beacon version
# without that field does not stop every print.
[gcode_macro _Z_CHECK]
description: Compare Klipper's Z with the Beacon reading at X Y Z (default 175 175 3), stop the print when they differ by more than MAX (default 0.5 mm)
gcode:
{% set x = params.X|default(175)|float %}
{% set y = params.Y|default(175)|float %}
{% set z = params.Z|default(3)|float %}
{% set max = params.MAX|default(0.5)|float %}
{% if 'xyz' not in printer.toolhead.homed_axes %}
_ERROR MSG="_Z_CHECK: printer not homed" DISPLAY="Z check: not homed"
{% elif 'beacon' not in printer %}
_WARN MSG="_Z_CHECK: no [beacon], check skipped"
{% else %}
{% set last = printer.beacon.last_sample %}
{% set prev = last.time if last and last.time is defined else -1 %}
SAVE_GCODE_STATE NAME=_z_check
G90
_GUARD_ON PHASE=z_check
{% if printer.toolhead.position.z < z %}
G1 Z{z} F600
{% endif %}
G1 X{x} Y{y} F12000
G1 Z{z} F600
_GUARD_OFF
G4 P300
BEACON_QUERY
_Z_CHECK_EVAL MAX={max} PREV={prev}
RESTORE_GCODE_STATE NAME=_z_check
{% endif %}
# Separate macro: rendered after BEACON_QUERY has run. toolhead.position is
# the real nozzle height (gcode offset and mesh included).
[gcode_macro _Z_CHECK_EVAL]
description: Helper for _Z_CHECK: compare the BEACON_QUERY reading with the toolhead Z
gcode:
{% set s = printer.beacon.last_sample %}
{% set z = printer.toolhead.position.z %}
{% set max = params.MAX|default(0.5)|float %}
{% if not s or s.time is not defined or s.dist is not defined or s.time == params.PREV|default(-1)|float %}
_WARN MSG="_Z_CHECK: BEACON_QUERY left no new reading in printer.beacon.last_sample, Z check skipped" DISPLAY="Z check skipped"
{% elif s.dist is none %}
_ERROR MSG="_Z_CHECK: Klipper Z={'%.2f' % z} but the Beacon reads nothing (more than 5 mm away or no model), print stopped" DISPLAY="Z check failed, print stopped"
{% elif (s.dist - z)|abs > max %}
_ERROR MSG="_Z_CHECK: Klipper Z={'%.3f' % z}, Beacon measures {'%.3f' % s.dist} mm (difference {'%.3f' % (s.dist - z)}, limit {max}), print stopped" DISPLAY="Z check failed, print stopped"
{% else %}
RESPOND MSG="Z check: Klipper Z={'%.3f' % z}, Beacon {'%.3f' % s.dist} mm, difference {'%.3f' % (s.dist - z)} (limit {max})"
{% endif %}
+1 -1
View File
@@ -16,7 +16,7 @@ gcode:
{% if printer.pause_resume.is_paused|lower == 'false' and park|lower == 'true'%}
_TOOLHEAD_PARK_PAUSE_CANCEL
{% endif %}
_status_ready
_STATUS STATE=ready
# A list of G-Code commands to execute on an idle timeout. See
# docs/Command_Templates.md for G-Code format. The default is to run
# "TURN_OFF_HEATERS" and "M84".
+35
View File
@@ -0,0 +1,35 @@
# Nozzle collision guard: bd_pressure watches the nozzle force during moves
# that must not touch anything (needs extras/bdp_guard.py and the bd_pressure
# load-check firmware). See extras/bdp_guard.py.
#
# mode: stop shuts Klipper down on a touch; log only measures and reports
# each phase (console and klippy.log, "bdp_guard:"). A phase can override it
# with _GUARD_ON MODE=log (the brush does).
# Measured 2026-10-18: peaks 1-8 in all phases except the brush (47); a
# light finger push on the cold nozzle gives 41.
[bdp_guard]
sensor: bd_pa
mode: stop
threshold: 30
samples: 3
interval: 0.02
# M400 first: BDP_GUARD_ARM/DISARM act at once, not in the move queue, so
# without it the guard would watch moves that are still queued (contact
# homing, extrusion) or stop watching before the armed moves ran.
[gcode_macro _GUARD_ON]
description: Arm the nozzle guard for PHASE=<name> [MODE=log|stop] after the queued moves (nothing without [bdp_guard])
gcode:
{% if 'bdp_guard' in printer %}
M400
BDP_GUARD_ARM PHASE={params.PHASE|default('armed')} {('MODE=' ~ params.MODE) if 'MODE' in params else ''}
{% endif %}
[gcode_macro _GUARD_OFF]
description: Disarm the nozzle guard after the queued moves (nothing without [bdp_guard])
gcode:
{% if 'bdp_guard' in printer %}
M400
BDP_GUARD_DISARM
{% endif %}
+46 -3
View File
@@ -10,6 +10,10 @@
# (BLOBIFIER_CHECKED, afc/purge_check.cfg) before going back to the print, so
# the nozzle is primed and clean. If the check fails the resume stops with an
# error and the printer stays paused. RESUME PURGE=0 resumes without the purge.
# Before that it checks the AFC lane: PAUSE stores the lane the print needs
# (the lane AFC was loading when a tool change failed, otherwise the loaded
# lane), and RESUME stops with an error when another lane (or none) is loaded.
# RESUME LANE_CHECK=0 resumes anyway (e.g. after remapping with SET_MAP).
# https://klipperscreen.readthedocs.io/en/latest/macros/#recommended
[gcode_macro PAUSE]
@@ -17,13 +21,27 @@ description: Pause the actual running print
rename_existing: PAUSE_BASE
variable_saved_fan_speed: 0.0
variable_parked: False # set by PAUSE, used by _RESUME_PRINT
variable_expected_lane: '' # AFC lane the print needs after RESUME ('' = no check)
gcode:
{% if not printer.pause_resume.is_paused %}
# AFC sets next_lane during a tool change and still has it when a failed
# change pauses the print; it clears it once CHANGE_TOOL returns
{% if 'AFC' in printer %}
{% set lane = printer.AFC.next_lane or printer.AFC.current_load or '' %}
SET_GCODE_VARIABLE MACRO=PAUSE VARIABLE=expected_lane VALUE="'{lane}'"
# AFC pauses after an error: show its first line. Pauses from these
# macros set their own display text before calling PAUSE.
{% set afc_msg = printer.AFC.message|default({}) %}
{% if printer.AFC.error_state and afc_msg.type|default('') == 'error' and afc_msg.message|default('') %}
M117 Paused: {afc_msg.message.split('\n')[0]|trim}
{% endif %}
{% endif %}
SET_GCODE_VARIABLE MACRO=PAUSE VARIABLE=saved_fan_speed VALUE={printer.fan.speed}
# Pause first, so the saved state is the print position, then retract and park
PAUSE_BASE
_TOOLHEAD_PARK_PAUSE_CANCEL
SET_GCODE_VARIABLE MACRO=PAUSE VARIABLE=parked VALUE=True
_STATUS STATE=paused
# Set fan to S28 (28/255 ≈ 0.1098)
M106 S28
{% else %}
@@ -52,6 +70,7 @@ gcode:
##### end of definitions #####
# Only undo what PAUSE did; HEAT_SOAK pauses with PAUSE_BASE (no retract, no park)
{% if pause.parked %}
_RESUME_LANE_CHECK {'LANE_CHECK=%s' % params.LANE_CHECK if 'LANE_CHECK' in params else ''}
{% if printer.extruder.can_extrude|lower == 'true' %}
{% if purge > 0 and 'gcode_macro BLOBIFIER' in printer %}
_RESUME_PURGE LENGTH={purge} ; also replaces the unretract; stops here if the check fails
@@ -72,10 +91,31 @@ gcode:
M107
{% endif %}
SET_GCODE_VARIABLE MACRO=PAUSE VARIABLE=parked VALUE=False
SET_GCODE_VARIABLE MACRO=PAUSE VARIABLE=expected_lane VALUE="''"
# Clear the pause reason from the display (no comment on an M117 line: it would show)
M117
{% endif %}
RESUME_BASE {get_params}
_status_printing
_STATUS STATE=printing
# Raising an error (_ERROR, also shown on the display) aborts the whole
# RESUME like a failed purge check: the printer stays paused and the next
# RESUME checks again.
[gcode_macro _RESUME_LANE_CHECK]
description: Stop the resume when AFC has another lane loaded than the one the print needs
gcode:
{% set expected = printer["gcode_macro PAUSE"].expected_lane %}
{% if expected and 'AFC' in printer and params.LANE_CHECK|default(1)|int %}
{% set loaded = printer.AFC.current_load %}
{% if loaded != expected %}
{% set obj = 'AFC_stepper ' ~ expected %}
{% set tool = printer[obj].map if obj in printer else '?' %}
_ERROR MSG="Lane check: the print needs {expected} ({tool}) but {loaded or 'no lane'} is loaded. Load {expected} with {tool}, then RESUME again (RESUME LANE_CHECK=0 resumes anyway)." DISPLAY="Lane check: load {expected} with {tool}, then RESUME"
{% else %}
RESPOND MSG="Lane check: {expected} loaded, OK"
{% endif %}
{% endif %}
[gcode_macro _RESUME_PURGE]
description: Purge LENGTH mm into the Blobifier (with the purge check if installed) before resuming
@@ -95,7 +135,7 @@ gcode:
[gcode_macro _RESUME_PURGE_VERIFY]
gcode:
{% if printer["gcode_macro _PURGE_CHECK_CFG"].last_failed %}
{action_raise_error("Purge check failed: the print stays paused. Check the extruder and lane, then RESUME again (RESUME PURGE=0 resumes without purging).")}
_ERROR MSG="Purge check failed: the print stays paused. Check the extruder and lane, then RESUME again (RESUME PURGE=0 resumes without purging)." DISPLAY="Purge check failed: still paused, see console"
{% endif %}
[gcode_macro _TOOLHEAD_PARK_PAUSE_CANCEL]
@@ -162,7 +202,10 @@ gcode:
# Same cleanup as PRINT_END: heaters incl. chamber heater, fan, flow, bdwidth
_PRINT_CLEANUP
SET_GCODE_VARIABLE MACRO=PAUSE VARIABLE=parked VALUE=False
_status_ready
# Clear a pause reason from the display
M117
_STATUS STATE=ready
_LIGHTS_ON
[gcode_macro M600]
#
+217 -137
View File
@@ -1,57 +1,43 @@
# Print start and end: PRINT_WARMUP, PRINT_START, PRINT_END
# Idle time (in seconds) to wait before running the above G-Code
# commands. The default is 600 seconds.
#####################################################################
# Macros
#####################################################################
# [gcode_macro PRINT_START]
# gcode:
# PRINT_WARMUP BED={params.BED} EXTRUDER={params.EXTRUDER} CHAMBER={params.CHAMBER|default("0")} TOOL={params.TOOL|default("0")}
# PRINT_START_2 EXTRUDER={params.EXTRUDER}
# OrcaSlicer calls PRINT_WARMUP and then PRINT_START. HEAT_SOAK at the end of
# PRINT_WARMUP pauses the print file and resumes it when the soak is done, so
# PRINT_START runs on a soaked bed:
# PRINT_WARMUP bed heats while the nozzle cools and the printer homes,
# wait for the bed, HEAT_SOAK
# PRINT_START QGL, Z, contact Z calibration, mesh, load the initial tool,
# PA for all lanes, XY reference
# The filament is loaded after the Z calibration, so the contact always
# touches a clean nozzle that has not been oozing at 150C for minutes.
# Chamber prints are the exception: PRINT_WARMUP loads and purges the first
# tool before the chamber heats (_LOAD_FOR_CHAMBER), retracted for the soak;
# BLOBIFIER_CLEAN cleans the nozzle before the contact.
[gcode_macro PRINT_WARMUP]
description: Perform initial homing and heating tasks
description: Print start before HEAT_SOAK: home while the bed heats, wait for the bed, soak
gcode:
{% set target_bed = params.BED|int %}
{% set target_extruder = params.EXTRUDER|int %}
{% set target_chamber = params.CHAMBER|default("0")|int %}
# {% set x_wait = printer.toolhead.axis_maximum.x|float / 2 %}
# {% set y_wait = printer.toolhead.axis_maximum.y|float / 2 %}
{% set initial_tool = params.TOOL|default("0")|int %}
{% set chamber_temp = printer["temperature_sensor chamber"].temperature|float %}
RESET_FAILURE
CLEAR_PAUSE
CLEAR_PAUSE
# Clear what an earlier print or error left on the display
M117
_motion_detected
_LIGHTS_ON
#SET_PIN PIN=caselight VALUE=1.00
UPDATE_DELAYED_GCODE ID=_delayed_motors_off DURATION=0
BED_MESH_CLEAR
SET_GCODE_OFFSET Z=0
CLEAR_PAUSE
SKEW_PROFILE LOAD=Califlower
{% set current_temp = printer.extruder.temperature %}
{% if current_temp > 150 %}
RESPOND MSG="nozzle to hot, lowering to 150"
M104 S150 ; set nozzle temp to 150
M109 S150 ; wait until nozzle reaches 150 ; wait until nozzle reaches 0
{% endif %}
_status_homing
RESPOND MSG="homing"
HOME
RESPOND MSG="homing finished"
# G0 Z2 ; position beacon at 2mm for heat soak
M106 S0
# Bed first, so it heats during the rest of the warmup
{% if target_bed < 60 %}
RESPOND MSG="Using PID for <60°C bed"
SET_HEATER_TEMPERATURE HEATER=heater_bed TARGET={target_bed} PID_KP=33.606 PID_KI=0.694 PID_KD=407.058
@@ -59,49 +45,114 @@ gcode:
RESPOND MSG="Using PID for ≥60°C bed"
SET_HEATER_TEMPERATURE HEATER=heater_bed TARGET={target_bed} pid_Kp=46.843 pid_Ki=2.002 pid_Kd=274.034
{% endif %}
G1 X175 Y175 Z2 F1200
_status_heating
RESPOND MSG="heating"
# Only to get the toolhead over the bed for the soak; the Z that counts
# is the contact calibration in PRINT_START on the soaked bed
_STATUS STATE=homing
# HOME homes Z with Beacon contact: wait for a steady nozzle temperature,
# a nozzle still heating spreads the contact samples (6 restarts seen)
_NOZZLE_CONTACT_TEMP WAIT=1
RESPOND MSG="homing"
HOME
{% if target_chamber > 30 %}
_LOAD_FOR_CHAMBER TOOL={initial_tool}
{% endif %}
_GUARD_ON PHASE=warmup_park
G1 X175 Y175 Z5 F1200
_GUARD_OFF
_STATUS STATE=heating_bed
{% if target_bed > 0 %}
M140 S{target_bed} ; start bed heater
M104 S150 ; set nozzle temp to 150
M109 S150 ; wait until nozzle reaches 150 ; wait until nozzle reaches 0
M190 S{target_bed} ; wait on bed temperature
RESPOND MSG="heating"
M190 S{target_bed}
{% endif %}
HOME
QUAD_GANTRY_LEVEL
BLOBIFIER_CLEAN ; call another macro to wipe nozzle if available
G1 X175 Y175 Z5 F1200
_HEAT_FOR_LOADED_LANE TOOL={initial_tool} ; unload needs the loaded filament's temperature
T{initial_tool} #PURGE_LENGTH=100 #Load Initial Tool
M104 S{target_extruder} ; set extruder to print temp
M109 S{target_extruder} ; wait for extruder temp
_NOZZLE_RETRACT LENGTH=2 ; no oozing while waiting; PRINT_START pushes it back
M104 S150 ; set nozzle temp to 150
M109 S150 ; wait until nozzle reaches 150 ; wait until nozzle reaches 0
BLOBIFIER_CLEAN ; call another macro to wipe nozzle if available
G1 X175 Y175 Z5 F1200
{% if target_chamber > 30 %}
RESPOND MSG="waiting for chamber temperature"
M141 S{target_chamber}
# M191 S{target_chamber} ; Waits until the chamber temperature reaches 60°C
# TEMPERATURE_WAIT SENSOR="temperature_sensor chamber" MINIMUM={target_chamber}
RESPOND MSG="waiting for chamber temperature"
M141 S{target_chamber}
M106 S255
# HEAT_SOAK HEATER='heater_bed' TARGET={BED_TEMP} SOAKER='temperature_sensor iHeater_S' SOAK_TEMP={target_chamber} TIMEOUT=60 CANCEL='_NOTHING'
# Unreachable SOAK_TEMP + CANCEL='_NOTHING': soaks until the TIMEOUT, then continues the print
HEAT_SOAK HEATER='heater_bed' TARGET={target_bed} SOAKER='temperature_sensor chamber' SOAK_TEMP={target_chamber} TIMEOUT=60 CANCEL='_NOTHING'
# (CANCEL='_NOTHING') and having unreachable SOAK_TEMP makes it run till the TIMEOUT and continue without canceling the print.
{% else %}
RESPOND MSG="waiting for bed core temperature"
RESPOND MSG="waiting for bed core temperature"
HEAT_SOAK HEATER='heater_bed' TARGET={target_bed} SOAKER='temperature_sensor bed_core' SOAK_TEMP={target_bed - 5} TIMEOUT=30 CANCEL='_NOTHING'
# (CANCEL='_NOTHING') and having unreachable SOAK_TEMP makes it run till the TIMEOUT and continue without canceling the print.
{% endif %}
# With the chamber heated, filament in the toolhead path softens: a PLA lane
# still loaded softens in the PTFE tube, and the piece of PLA that a cut
# unload leaves below the cutter softens in the extruder and jams the next
# load (seen: PLA unloaded, ASA jammed after the soak). The piece stays until
# the next load, so an empty toolhead can have it too. For a chamber print,
# load the print's first tool and purge it now, before the chamber heats:
# the purge at the new filament's temperature pushes the leftover out.
# AFC unloads a loaded lane back into the Box Turtle, outside the chamber.
[gcode_macro _LOAD_FOR_CHAMBER]
description: Before a chamber soak: load and purge TOOL (the print's first tool) unless its lane is already loaded
gcode:
{% set tool = 'T' ~ params.TOOL|default(0)|int %}
{% if 'AFC' in printer %}
{% set cur = printer.AFC.current_load %}
{% set ns = namespace(lane=none) %}
{% for l in printer.AFC.lanes if ('AFC_stepper ' ~ l) in printer and printer['AFC_stepper ' ~ l].map == tool %}
{% set ns.lane = l %}
{% endfor %}
{% if ns.lane is none %}
_ERROR MSG="Chamber heating: no AFC lane is mapped to {tool}, print stopped before the chamber heats" DISPLAY="No lane for {tool}, print stopped"
{% elif cur != ns.lane %}
{% set new = printer['AFC_stepper ' ~ ns.lane] %}
# Hot enough to unload the old filament and to purge the new one
{% set temps = [new.extruder_temp|default(0, true)|int, printer.configfile.settings.extruder.min_extrude_temp|int + 20] %}
{% if cur and ('AFC_stepper ' ~ cur) in printer %}
{% set temps = temps + [printer['AFC_stepper ' ~ cur].extruder_temp|default(0, true)|int] %}
RESPOND MSG="Chamber heating: unloading {cur} ({printer['AFC_stepper ' ~ cur].material}), loading and purging {ns.lane} ({new.material}, {tool}) before the chamber heats"
{% else %}
RESPOND MSG="Chamber heating: loading and purging {ns.lane} ({new.material}, {tool}) before the chamber heats"
{% endif %}
# The Blobifier only purges with the QGL applied: without it AFC
# loaded the lane and skipped the purge. Level now (PRINT_START
# levels again on the soaked bed) and home Z after it.
{% if printer.quad_gantry_level and not printer.quad_gantry_level.applied %}
_GUARD_ON PHASE=warmup_qgl
QUAD_GANTRY_LEVEL
_GUARD_ON PHASE=warmup_z_home
G28 Z
_GUARD_OFF
{% endif %}
M109 S{temps|max}
{tool}
_LOAD_FOR_CHAMBER_VERIFY LANE={ns.lane}
{% endif %}
{% endif %}
# Separate macro: rendered after the tool change has run. AFC pauses on a
# failed load instead of raising an error, so check the result here: a
# failed load stops the print before the chamber heats.
[gcode_macro _LOAD_FOR_CHAMBER_VERIFY]
description: Helper for _LOAD_FOR_CHAMBER: stop the print when LANE is not loaded, else retract and cool the nozzle for the soak
gcode:
{% if printer.AFC.current_load != params.LANE %}
_ERROR MSG="Chamber heating: {params.LANE} could not be loaded ({printer.AFC.current_load or 'nothing'} in the toolhead), print stopped before the chamber heats" DISPLAY="Load failed, print stopped"
{% else %}
# New filament, just purged: a retract remembered for the old one is
# gone. Retract so it does not ooze during the soak; PRINT_START
# undoes it over the bucket.
SAVE_VARIABLE VARIABLE=nozzle_retract VALUE=0
_NOZZLE_RETRACT
M104 S150
{% endif %}
# Beacon contact refuses a nozzle above contact_max_hotend_temperature (180C)
[gcode_macro _NOZZLE_CONTACT_TEMP]
description: Nozzle to 150C for Beacon contact; wait only while it is above MAXIMUM (default 170C), WAIT=1 also waits until it is at least 145C
gcode:
{% set max = params.MAXIMUM|default(170)|float %}
{% set heat = params.WAIT|default(0)|int == 1 %}
M104 S150
{% if heat %}
TEMPERATURE_WAIT SENSOR=extruder MINIMUM=145 MAXIMUM={max}
{% elif printer.extruder.temperature > max %}
RESPOND MSG="Nozzle at {printer.extruder.temperature|round|int}C, waiting until it is below {max|int}C"
TEMPERATURE_WAIT SENSOR=extruder MAXIMUM={max}
{% endif %}
# AFC heats for a tool change from the print file's per-tool temperatures.
@@ -128,87 +179,82 @@ gcode:
RESUME_BASE
[gcode_macro PRINT_START]
description: Print start after HEAT_SOAK: QGL, Z calibration, mesh, load the initial tool, PA
gcode:
RESPOND MSG="print start macro continued"
_motion_detected
{% set target_extruder = params.EXTRUDER|int %}
{% set initial_tool = params.TOOL|default("0")|int %}
RESPOND MSG="print start macro continued"
_LIGHTS_ON
M106 S0
M104 S150 ; set nozzle temp to 150
M109 S150 ; wait until nozzle reaches 150 ; wait until nozzle reaches 0
# The last homing was before the soak: check that Z still matches the
# Beacon before the gantry goes low. MAX=1.0: the soak itself drifts Z
# a few tenths; the console line shows how much.
_Z_CHECK MAX=1.0
HOME
BLOBIFIER_CLEAN ; call another macro to wipe nozzle if available
_status_calibrating_z
G28 Z METHOD=CONTACT CALIBRATE=1 ; calibrate z offset and beacon model hot
_status_leveling
RESPOND MSG="quad ganrty level"
# The bed is soaked now: level and calibrate Z on the final geometry
_STATUS STATE=leveling
RESPOND MSG="quad gantry level"
# Nozzle guard (bd_pressure) on while nothing should touch the nozzle
_GUARD_ON PHASE=qgl
QUAD_GANTRY_LEVEL
_status_meshing
RESPOND MSG="meshing"
BED_MESH_CALIBRATE RUNS=2 ; bed mesh in scan mode
# QGL moved the Z steppers and the frame drifted during the soak: home Z
# again (proximity, on the model of the contact homing in PRINT_WARMUP)
# before the brush takes the nozzle below Z0
_GUARD_ON PHASE=z_home
G28 Z
BLOBIFIER_CLEAN ; call another macro to wipe nozzle if available
M104 S150 ; set nozzle temp to 150
M109 S150 ; wait until nozzle reaches 150 ; wait until nozzle reaches 0
_status_calibrating_z
G28 Z METHOD=CONTACT CALIBRATE=1 ; calibrate z offset and beacon model hot
_status_heating
M104 S{target_extruder} ; set extruder to print temp
M109 S{target_extruder} ; wait for extruder temp
BLOBIFIER_PARK ; over the bucket, then undo the retracts
_NOZZLE_UNRETRACT ; of PRINT_WARMUP and the previous PRINT_END
# ; needs fine tuning, long meltzones require more
# SET_GCODE_OFFSET Z_ADJUST={OFFSET} ; apply optional material squish via slicer
#SET_GCODE_OFFSET Z=-0.06 ; squish
# SET_GCODE_OFFSET Z=0.02 ; squish # worked before but a bit to high now
# SET_GCODE_OFFSET Z=-0.05 ; squish
SET_GCODE_OFFSET Z=-0.00 ; squish
RESPOND MSG="starting"
;PRIME_NOZZLE ; call another macro to purge or prime nozzle
# WIPE_NOZZLE ; call another macro to wipe nozzle if available
# Line_Purge
# AFC_POOP PURGE_LENGTH=100
# AFC_KICK
# Own phase, log only: the bristles press on the nozzle (peak 47,
# 4 reads above 30 in a row), stop mode would shut down every start
_GUARD_ON PHASE=brush MODE=log
BLOBIFIER_CLEAN
_GUARD_OFF
_STATUS STATE=calibrating_z
_NOZZLE_CONTACT_TEMP WAIT=1
G28 Z METHOD=CONTACT CALIBRATE=1 ; calibrate z offset and beacon model hot
_STATUS STATE=meshing
RESPOND MSG="meshing"
_GUARD_ON PHASE=mesh
BED_MESH_CALIBRATE RUNS=2 ; bed mesh in scan mode
# From here on the nozzle extrudes and AFC cuts and loads: guard off
_GUARD_OFF
# Heat over the bucket, then undo the retracts of the previous PRINT_END
_STATUS STATE=heating_extruder
BLOBIFIER_PARK
M109 S{target_extruder}
_NOZZLE_UNRETRACT
# Load the initial tool (unloading another lane needs that lane's temperature)
_HEAT_FOR_LOADED_LANE TOOL={initial_tool}
T{initial_tool}
PA_CALIBRATE_ALL_LANES LANES="lane1,lane2,lane3,lane4,lane5"
# A calibration can leave another lane loaded, and every cached lane
# sets its PA: load the initial tool again and apply its PA
T{initial_tool}
_PA_APPLY_LOADED_LANE
M104 S{target_extruder} ; set extruder to print temp
M109 S{target_extruder} ; wait for extruder temp
M109 S{target_extruder}
SET_GCODE_OFFSET Z=-0.00 ; squish
BLOBIFIER_CLEAN
; start printing
G90 ; absolute positioning
G1 Z20 F30000 ; move nozzle away from bed
G1 X175 Y175 F30000 ; move nozzle away from bed
G28 XYZ
#G11 ; unretract
# XY reference for AFC_XY_SAFETY_CHECK (homes X and Y; Z stays from the
# contact calibration)
_AFC_STORE_HOME_REFERENCE
# Width only (flow compensation and runout when no filament is measured).
# The CCD motion reading stays at 0 (actual_total_move:0), so motion mode
# (ENABLE_MOTION / ENABLE_ALL) only gives false "Rounout" messages.
SET_BDWIDTH NAME=fila_width_0 COMMAND=ENABLE_WIDTH
_status_printing
# bdwidth is not used while printing: its motion reading stays at 0 and its
# per-reading width compensation made the flow jump between 77 and 119%.
# To use it again: SET_BDWIDTH NAME=fila_width_0 COMMAND=ENABLE_WIDTH
# A large, slow first layer can trip AFC's buffer fault detection
# ("AFC NOT FEEDING"); the slicer switches it back at layer 2
_FIRST_LAYER_FAULT_DETECTION ON=1
RESPOND MSG="starting"
_STATUS STATE=printing
[gcode_macro PRINT_END]
@@ -222,7 +268,7 @@ gcode:
SAVE_GCODE_STATE NAME=STATE_PRINT_END
_motion_detected
_LIGHTS_ON
M400 ; wait for buffer to clear
G92 E0 ; zero the extruder
@@ -237,7 +283,7 @@ gcode:
M107 ; turn off fan
#SET_PIN PIN=caselight VALUE=0.03
_status_part_ready
_STATUS STATE=part_ready
UPDATE_DELAYED_GCODE ID=_delayed_motors_off DURATION=3600
@@ -247,15 +293,49 @@ gcode:
# End-of-print cleanup shared by PRINT_END and CANCEL_PRINT
[gcode_macro _PRINT_CLEANUP]
description: Heaters (incl. chamber heater) and part fan off, flow back to 100%, bdwidth off, mesh and skew cleared
description: Heaters (incl. chamber heater) and part fan off, flow back to 100%, bdwidth off, mesh and skew cleared, nozzle guard off
gcode:
_GUARD_OFF ; a print start that failed while it was armed
TURN_OFF_HEATERS
iHEATER_OFF ; stops the chamber heater control loop too
M107
{% if 'bdwidth fila_width_0' in printer %}
SET_BDWIDTH NAME=fila_width_0 COMMAND=DISABLE ; also sets M221 S100
{% endif %}
M221 S100
BED_MESH_CLEAR
SET_SKEW CLEAR=1
{% if 'gcode_macro _PURGE_CHECK_CFG' in printer %}
SET_GCODE_VARIABLE MACRO=_PURGE_CHECK_CFG VARIABLE=ignore_missing_sensor VALUE=False
{% endif %}
_FIRST_LAYER_FAULT_DETECTION ON=0 ; a print that ended during the first layer
# AFC buffer fault detection (filament_error_sensitivity, 0-10, higher =
# pauses sooner) with its own value during the first layer.
# ON=1 at the end of PRINT_START, ON=0 from the slicer's layer change G-code
# at the start of layer 2 (OrcaSlicer, layer_num counts from 0):
# {if layer_num == 1}_FIRST_LAYER_FAULT_DETECTION ON=0{endif}
# _PRINT_CLEANUP also calls ON=0. The normal value is read from the
# [AFC_buffer] config; ON=0 only restores it if ON=1 changed it.
[gcode_macro _FIRST_LAYER_FAULT_DETECTION]
description: AFC filament fault detection with first_layer_sensitivity during the first layer (ON=1), back to the config value (ON=0)
variable_buffer: "Turtle_1"
variable_first_layer_sensitivity: 4 # 0-10: 0 = off, 4 = pause after 70 mm, 8 = after 30 mm
variable_lowered: False
gcode:
{% set key = "afc_buffer " ~ buffer|lower %}
{% set normal = printer.configfile.settings[key].filament_error_sensitivity|default(0)|float if key in printer.configfile.settings else 0 %}
{% if params.ON|default(1)|int %}
{% if normal > 0 and first_layer_sensitivity|float != normal %}
AFC_SET_ERROR_SENSITIVITY BUFFER={buffer} SENSITIVITY={first_layer_sensitivity}
SET_GCODE_VARIABLE MACRO=_FIRST_LAYER_FAULT_DETECTION VARIABLE=lowered VALUE=True
RESPOND MSG="AFC fault detection {first_layer_sensitivity} for the first layer (normally {normal})"
{% endif %}
{% elif lowered %}
AFC_SET_ERROR_SENSITIVITY BUFFER={buffer} SENSITIVITY={normal}
SET_GCODE_VARIABLE MACRO=_FIRST_LAYER_FAULT_DETECTION VARIABLE=lowered VALUE=False
RESPOND MSG="AFC fault detection back to {normal}"
{% endif %}
# Retract/unretract that is remembered across prints and restarts.
# Klipper's G10 only retracts once until a G11, and nothing sends G11 here,
@@ -295,7 +375,7 @@ gcode:
RESTORE_GCODE_STATE NAME=_nozzle_unretract
SAVE_VARIABLE VARIABLE=nozzle_retract VALUE=0
{% else %}
RESPOND TYPE=error MSG="_NOZZLE_UNRETRACT: extruder too cold, {done} mm still retracted"
_WARN MSG="_NOZZLE_UNRETRACT: extruder too cold, {done} mm still retracted"
{% endif %}
{% endif %}
+127
View File
@@ -0,0 +1,127 @@
# TEST_SPEED: skipped-steps test for speed and acceleration (common Voron
# macro, moved here from ~/printer_data/config/Macros/TEST_SPEED.cfg)
[gcode_macro TEST_SPEED]
# Home, get position, throw around toolhead, home again.
# If MCU stepper positions (first line in GET_POSITION) are greater than a full step different (your number of microsteps), then skipping occured.
# We only measure to a full step to accomodate for endstop variance.
# Example: TEST_SPEED SPEED=300 ACCEL=5000 ITERATIONS=10, ZHEIGHT=20
description: Test for max speed and acceleration parameters for the printer. Procedure: Home -> ReadPositionFromMCU -> MovesToolhead@Vel&Accel -> Home -> ReadPositionfromMCU
gcode:
# Speed
{% set speed = params.SPEED|default(printer.configfile.settings.printer.max_velocity)|int %}
# Iterations
{% set iterations = params.ITERATIONS|default(5)|int %}
# Acceleration
{% set accel = params.ACCEL|default(printer.configfile.settings.printer.max_accel)|int %}
# Minimum Cruise Ratio
{% set min_cruise_ratio = params.MIN_CRUISE_RATIO|default(0.5)|float %}
# Bounding inset for large pattern (helps prevent slamming the toolhead into the sides after small skips, and helps to account for machines with imperfectly set dimensions)
{% set bound = params.BOUND|default(20)|int %}
{% set zHeight = params.ZHEIGHT|default(20)|int %}
# Size for small pattern box
{% set smallpatternsize = SMALLPATTERNSIZE|default(20)|int %}
# Large pattern
# Max positions, inset by BOUND
{% set x_min = printer.toolhead.axis_minimum.x + bound %}
{% set x_max = printer.toolhead.axis_maximum.x - bound %}
{% set y_min = printer.toolhead.axis_minimum.y + bound %}
{% set y_max = printer.toolhead.axis_maximum.y - bound %}
# Small pattern at center
# Find X/Y center point
{% set x_center = (printer.toolhead.axis_minimum.x|float + printer.toolhead.axis_maximum.x|float ) / 2 %}
{% set y_center = (printer.toolhead.axis_minimum.y|float + printer.toolhead.axis_maximum.y|float ) / 2 %}
# Set small pattern box around center point
{% set x_center_min = x_center - (smallpatternsize/2) %}
{% set x_center_max = x_center + (smallpatternsize/2) %}
{% set y_center_min = y_center - (smallpatternsize/2) %}
{% set y_center_max = y_center + (smallpatternsize/2) %}
# Save current gcode state (absolute/relative, etc)
SAVE_GCODE_STATE NAME=TEST_SPEED
# Output parameters to g-code terminal
{ action_respond_info("TEST_SPEED: starting %d iterations at speed %d, accel %d" % (iterations, speed, accel)) }
# Home and get position for comparison later:
M400 # Finish moves - https://github.com/AndrewEllis93/Print-Tuning-Guide/issues/66
G28
# QGL if not already QGLd (only if QGL section exists in config)
{% if printer.configfile.settings.quad_gantry_level %}
{% if printer.quad_gantry_level.applied == False %}
QUAD_GANTRY_LEVEL
G28 Z
{% endif %}
{% endif %}
# Move 50mm away from max position and home again (to help with hall effect endstop accuracy - https://github.com/AndrewEllis93/Print-Tuning-Guide/issues/24)
G90
G1 X{printer.toolhead.axis_maximum.x-50} Y{printer.toolhead.axis_maximum.y-50} F{30*60}
M400 # Finish moves - https://github.com/AndrewEllis93/Print-Tuning-Guide/issues/66
G28 X Y
G0 X{printer.toolhead.axis_maximum.x-1} Y{printer.toolhead.axis_maximum.y-1} F{30*60}
G4 P1000
GET_POSITION
# Go to starting position
G0 X{x_min} Y{y_min} Z{zHeight + 10} F{speed*60}
# Set new limits
{% if printer.configfile.settings.printer.minimum_cruise_ratio is defined %}
SET_VELOCITY_LIMIT VELOCITY={speed} ACCEL={accel} MINIMUM_CRUISE_RATIO={min_cruise_ratio}
{% else %}
SET_VELOCITY_LIMIT VELOCITY={speed} ACCEL={accel} ACCEL_TO_DECEL={accel / 2}
{% endif %}
{% for i in range(iterations) %}
# Large pattern diagonals
G0 X{x_min} Y{y_min} F{speed*60}
G0 X{x_max} Y{y_max} F{speed*60}
G0 X{x_min} Y{y_min} F{speed*60}
G0 X{x_max} Y{y_min} F{speed*60}
G0 X{x_min} Y{y_max} F{speed*60}
G0 X{x_max} Y{y_min} F{speed*60}
# Large pattern box
G0 X{x_min} Y{y_min} F{speed*60}
G0 X{x_min} Y{y_max} F{speed*60}
G0 X{x_max} Y{y_max} F{speed*60}
G0 X{x_max} Y{y_min} F{speed*60}
# Small pattern diagonals
G0 X{x_center_min} Y{y_center_min} F{speed*60}
G0 X{x_center_max} Y{y_center_max} F{speed*60}
G0 X{x_center_min} Y{y_center_min} F{speed*60}
G0 X{x_center_max} Y{y_center_min} F{speed*60}
G0 X{x_center_min} Y{y_center_max} F{speed*60}
G0 X{x_center_max} Y{y_center_min} F{speed*60}
# Small pattern box
G0 X{x_center_min} Y{y_center_min} F{speed*60}
G0 X{x_center_min} Y{y_center_max} F{speed*60}
G0 X{x_center_max} Y{y_center_max} F{speed*60}
G0 X{x_center_max} Y{y_center_min} F{speed*60}
{% endfor %}
# Restore max speed/accel/accel_to_decel to their configured values
{% if printer.configfile.settings.printer.minimum_cruise_ratio is defined %}
SET_VELOCITY_LIMIT VELOCITY={printer.configfile.settings.printer.max_velocity} ACCEL={printer.configfile.settings.printer.max_accel} MINIMUM_CRUISE_RATIO={printer.configfile.settings.printer.minimum_cruise_ratio}
{% else %}
SET_VELOCITY_LIMIT VELOCITY={printer.configfile.settings.printer.max_velocity} ACCEL={printer.configfile.settings.printer.max_accel} ACCEL_TO_DECEL={printer.configfile.settings.printer.max_accel_to_decel}
{% endif %}
# Re-home and get position again for comparison:
M400 # Finish moves - https://github.com/AndrewEllis93/Print-Tuning-Guide/issues/66
G28 # This is a full G28 to fix an issue with CoreXZ - https://github.com/AndrewEllis93/Print-Tuning-Guide/issues/12
# Go to XY home positions (in case your homing override leaves it elsewhere)
G90
G0 X{printer.toolhead.axis_maximum.x-1} Y{printer.toolhead.axis_maximum.y-1} F{30*60}
G4 P1000
GET_POSITION
# Restore previous gcode state (absolute/relative, etc)
RESTORE_GCODE_STATE NAME=TEST_SPEED
+92 -26
View File
@@ -1,42 +1,108 @@
# PIR motion sensor: case light on when someone is near the printer
# PIR motion sensor (AM312): case light and nozzle LEDs while someone is near
#
# The AM312 keeps its output on as long as it sees movement: press = motion,
# release = no more motion. The timeout starts at the release, so the lights
# stay on while someone works at the printer.
#
# someone near: case light near_value, nozzle LEDs white
# nobody near, print/pause: case light print_value, nozzle LEDs off
# nobody near, idle: case light idle_value, nozzle LEDs off,
# logo and Box Turtle logo dim (_STATUS ready)
#
# CASELIGHT MODE=ON [VALUE=0.5] | OFF | AUTO overrides the sensor until the
# next Klipper restart. Setting the case light in Mainsail only lasts until
# the next motion.
[gcode_button am312]
pin: !PC0
press_gcode:
_motion_detected
[gcode_macro _motion_detected]
description: Motion sensor triggered
_MOTION_START
release_gcode:
_MOTION_END
[gcode_macro _LIGHTS]
description: Set the case light and nozzle LEDs from motion, print state and CASELIGHT mode
variable_timeout: 60 # s without motion before the lights dim or turn off
variable_near_value: 1.0 # case light while someone is near
variable_print_value: 0.1 # case light while printing or paused, nobody near
variable_idle_value: 0.0 # case light while idle, nobody near
variable_on_value: 1.0 # case light for CASELIGHT MODE=ON
variable_mode: 'auto' # auto (motion sensor), on, off: set by CASELIGHT
variable_present: False # someone near: set by the motion sensor
variable_near: False # lights on (present or MODE=ON): read by _STATUS
variable_debug: False # True: report every change in the console
gcode:
{% set printing = printer.print_stats.state == "printing" %}
# RESPOND MSG="AM312: Motion detected"
{% if printing %}
# RESPOND MSG="AM312: Printer is printing -> light ON (100%)"
SET_PIN PIN=caselight VALUE=1.0
_set_nozzle_leds_on
{% set busy = printer.print_stats.state in ['printing', 'paused'] %}
{% if mode == 'on' %}
{% set lit = True %}
{% set value = on_value %}
{% elif mode == 'off' %}
{% set lit = False %}
{% set value = 0.0 %}
{% else %}
# RESPOND MSG="AM312: Printer idle -> light ON (10%)"
SET_PIN PIN=caselight VALUE=0.1
_set_nozzle_leds_off
{% set lit = present %}
{% set value = near_value if present else (print_value if busy else idle_value) %}
{% endif %}
{% if debug %}
RESPOND MSG="Lights: mode={mode} present={present} print={printer.print_stats.state}: case light {value}, nozzle {'on' if lit else 'off'}"
{% endif %}
{% if printer['output_pin caselight'].value != value %}
SET_PIN PIN=caselight VALUE={value}
{% endif %}
{% if printer['led_effect sb_nozzle_light'].enabled != lit %}
SET_LED_EFFECT EFFECT=sb_nozzle_light {'' if lit else 'STOP=1'} FADETIME={printer['gcode_macro _STATUS'].fade_time}
{% endif %}
{% if near != lit %}
SET_GCODE_VARIABLE MACRO=_LIGHTS VARIABLE=near VALUE={lit}
# Night mode of the logo when idle
_STATUS
{% endif %}
# RESPOND MSG="AM312: Resetting timeout to 30 seconds"
UPDATE_DELAYED_GCODE ID=_motion_timeout DURATION=30
[gcode_macro _MOTION_START]
description: Motion sensor: someone is near
gcode:
UPDATE_DELAYED_GCODE ID=_motion_timeout DURATION=0
{% if not printer['gcode_macro _LIGHTS'].present %}
SET_GCODE_VARIABLE MACRO=_LIGHTS VARIABLE=present VALUE=True
_LIGHTS
{% endif %}
[gcode_macro _MOTION_END]
description: Motion sensor: no more motion, lights off after the timeout
gcode:
UPDATE_DELAYED_GCODE ID=_motion_timeout DURATION={printer['gcode_macro _LIGHTS'].timeout}
[delayed_gcode _motion_timeout]
gcode:
{% set printing = printer.print_stats.state == "printing" %}
SET_GCODE_VARIABLE MACRO=_LIGHTS VARIABLE=present VALUE=False
_LIGHTS
# RESPOND MSG="AM312: Timeout reached, no motion"
[gcode_macro _LIGHTS_ON]
description: Lights on as if someone walked by (print start and end, startup)
gcode:
_MOTION_START
{% if printer['gcode_button am312'].state == 'RELEASED' %}
_MOTION_END
{% endif %}
{% if printing %}
# RESPOND MSG="AM312: Printing -> dim light to 10%"
SET_PIN PIN=caselight VALUE=0.1
[delayed_gcode _lights_startup]
initial_duration: 2
gcode:
_LIGHTS_ON
[gcode_macro CASELIGHT]
description: Case light and nozzle LEDs: MODE=AUTO (motion sensor), ON [VALUE=0-1] or OFF; no MODE: show the mode
gcode:
{% set l = printer['gcode_macro _LIGHTS'] %}
{% set mode = params.MODE|default('')|lower %}
{% if mode == '' %}
RESPOND MSG="Case light: mode {l.mode}, {'someone near' if l.present else 'nobody near'}, value {printer['output_pin caselight'].value}"
{% elif mode not in ['auto', 'on', 'off'] %}
_ERROR MSG="CASELIGHT: MODE must be AUTO, ON or OFF"
{% else %}
# RESPOND MSG="AM312: Idle -> light OFF"
SET_PIN PIN=caselight VALUE=0.0
{% if 'VALUE' in params %}
SET_GCODE_VARIABLE MACRO=_LIGHTS VARIABLE=on_value VALUE={[[params.VALUE|float, 0.0]|max, 1.0]|min}
{% endif %}
SET_GCODE_VARIABLE MACRO=_LIGHTS VARIABLE=mode VALUE="'{mode}'"
_LIGHTS
{% endif %}
_set_nozzle_leds_off