Compare commits
22
Commits
76172fbc4f
...
main
No files matched your search
@@ -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,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/) |
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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):
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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)}
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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 %}
|
||||
@@ -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".
|
||||
|
||||
@@ -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 %}
|
||||
@@ -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
@@ -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 %}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in new issue
Block a user