Compare commits
13
Commits
6cc43f951d
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87a06f9c30 | ||
|
|
a63b66a7d8 | ||
|
|
29604b2f1e | ||
|
|
8a5f6b6dd9 | ||
|
|
93ae8795b8 | ||
|
|
d49e65c0bb | ||
|
|
464a7c1046 | ||
|
|
0b1b2a6947 | ||
|
|
b9d2f3a062 | ||
|
|
a16b57a0a9 | ||
|
|
8eef0ee298 | ||
|
|
61fa8acb05 | ||
|
|
2100857846 |
No files matched your search
@@ -70,6 +70,11 @@ detection, `filament_error_sensitivity`), iHeater chamber heater.
|
|||||||
not `action_raise_error` / `RESPOND TYPE=error`, so they also show on the
|
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
|
display. `action_raise_error` aborts while rendering, so an `M117` in the
|
||||||
same macro would never run. No double quotes in `MSG`/`DISPLAY`.
|
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
|
- After a refactor run `python tools/effective_config.py HEAD` and check that
|
||||||
only the intended sections changed.
|
only the intended sections changed.
|
||||||
- Render-test macros with Jinja2 using Klipper's delimiters
|
- Render-test macros with Jinja2 using Klipper's delimiters
|
||||||
@@ -89,7 +94,15 @@ detection, `filament_error_sensitivity`), iHeater chamber heater.
|
|||||||
## Reference values
|
## Reference values
|
||||||
|
|
||||||
- Purge check (150 mm Blobifier purge): good load = longest pressure
|
- 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
|
- 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).
|
skipping one cycle = 0.4 mm shows as 0.2 mm in X and Y).
|
||||||
|
|
||||||
|
|||||||
@@ -21,10 +21,12 @@ everything below, grouped by topic. Comment out what you do not use there.
|
|||||||
| `blobifier.cfg` | `BLOBIFIER`, `BLOBIFIER_CLEAN`, `BLOBIFIER_PARK`, `BLOBIFIER_SHAKE_BUCKET`, ...: purge bucket and nozzle brush for AFC (Turtleblobifier, heavily modified) | Blobifier hardware, AFC |
|
| `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 | — |
|
| `blobifier_hw.cfg` | servo and bucket switch pins | — |
|
||||||
| **print/** | | |
|
| **print/** | | |
|
||||||
| `print_start_end.cfg` | `PRINT_WARMUP`, `PRINT_START`, `PRINT_END` | Beacon, AFC, Blobifier, `HEAT_SOAK`, iHeater, bd_pressure PA macros (bdwidth is no longer enabled while printing) |
|
| `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` |
|
| `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` | — |
|
| `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 |
|
| `idle_timeout.cfg` | `[idle_timeout]`: heaters, chamber heater and motors off | iHeater macros |
|
||||||
| **heating/** | | |
|
| **heating/** | | |
|
||||||
| `heat_soak.cfg` | `HEAT_SOAK`, `STOP_HEAT_SOAK`, `CANCEL_HEAT_SOAK`, `HEAT_SOAK_RESUME`, `RESUME` | — |
|
| `heat_soak.cfg` | `HEAT_SOAK`, `STOP_HEAT_SOAK`, `CANCEL_HEAT_SOAK`, `HEAT_SOAK_RESUME`, `RESUME` | — |
|
||||||
@@ -35,10 +37,12 @@ everything below, grouped by topic. Comment out what you do not use there.
|
|||||||
| **afc/** | | |
|
| **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) |
|
| `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 |
|
| `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); a bd_pressure that does not answer fails the check, `PURGE_CHECK_IGNORE_SENSOR` lets the current print finish without it | 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` |
|
| `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/** | | |
|
| **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; 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` |
|
| `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/** | | |
|
| **spoolman/** | | |
|
||||||
| `active_spool.cfg` | `SET_ACTIVE_SPOOL`, `CLEAR_ACTIVE_SPOOL` | Spoolman in Moonraker |
|
| `active_spool.cfg` | `SET_ACTIVE_SPOOL`, `CLEAR_ACTIVE_SPOOL` | Spoolman in Moonraker |
|
||||||
| **leds/** | | |
|
| **leds/** | | |
|
||||||
@@ -53,6 +57,7 @@ everything below, grouped by topic. Comment out what you do not use there.
|
|||||||
| `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. |
|
| `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 (`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 |
|
| `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. |
|
| `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/`)
|
### Scripts (`scripts/`)
|
||||||
|
|
||||||
@@ -158,6 +163,8 @@ original licenses apply to those parts:
|
|||||||
| `heating/heat_soak.cfg` | community `HEAT_SOAK` macro |
|
| `heating/heat_soak.cfg` | community `HEAT_SOAK` macro |
|
||||||
| `heating/chamber_iheater.cfg` | iHeater vendor configuration |
|
| `heating/chamber_iheater.cfg` | iHeater vendor configuration |
|
||||||
| `heating/filament_drying.cfg` | community filament drying macro |
|
| `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) |
|
| `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/) |
|
| `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
|
# Uses the I2C connection the existing [bdpressure <name>] section already
|
||||||
# set up. Before writing anything it reads the extension marker (bytes
|
# 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
|
# command only reports "not available": nothing is written, because
|
||||||
# writing past the stock register table would land in the sensor's RAM.
|
# writing past the stock register table would land in the sensor's RAM.
|
||||||
#
|
#
|
||||||
@@ -27,15 +27,18 @@
|
|||||||
|
|
||||||
REG_EXT = 54 # first byte of the extension
|
REG_EXT = 54 # first byte of the extension
|
||||||
REG_CTRL = 56
|
REG_CTRL = 56
|
||||||
EXT_LEN = 12 # bytes 54-65
|
EXT_LEN = 12 # bytes 54-65, the same in version 1 and 2
|
||||||
EXT_MAGIC = (0xBD, 0x01)
|
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:
|
class BDPressureCheck:
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self.printer = config.get_printer()
|
self.printer = config.get_printer()
|
||||||
self.sensor_name = 'bdpressure ' + config.get('sensor', 'bd_pa')
|
self.sensor_name = 'bdpressure ' + config.get('sensor', 'bd_pa')
|
||||||
self.i2c = None
|
|
||||||
self.sensor = None
|
self.sensor = None
|
||||||
self.status = {'available': False, 'running': False, 'peak': 0,
|
self.status = {'available': False, 'running': False, 'peak': 0,
|
||||||
'above_ms': 0, 'longest_ms': 0, 'delta': 0}
|
'above_ms': 0, 'longest_ms': 0, 'delta': 0}
|
||||||
@@ -50,16 +53,21 @@ class BDPressureCheck:
|
|||||||
desc="Read the bd_pressure load check values")
|
desc="Read the bd_pressure load check values")
|
||||||
|
|
||||||
def _handle_connect(self):
|
def _handle_connect(self):
|
||||||
sensor = self.printer.lookup_object(self.sensor_name)
|
self.sensor = self.printer.lookup_object(self.sensor_name)
|
||||||
self.sensor = sensor
|
|
||||||
self.i2c = getattr(sensor, 'i2c', None)
|
@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):
|
def _read(self):
|
||||||
if self.i2c is None:
|
if self.i2c is None:
|
||||||
self.status['available'] = False
|
self.status['available'] = False
|
||||||
return False
|
return False
|
||||||
data = bytearray(self.i2c.i2c_read([REG_EXT], EXT_LEN)['response'])
|
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
|
self.status['available'] = False
|
||||||
return False
|
return False
|
||||||
u16 = lambda i: data[i] | (data[i + 1] << 8)
|
u16 = lambda i: data[i] | (data[i + 1] << 8)
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -54,6 +54,14 @@ variable_start_counts: 0
|
|||||||
variable_start_e: 0.0
|
variable_start_e: 0.0
|
||||||
variable_last_failed: False # last check failed in enforce mode (read by _RESUME_PURGE_VERIFY)
|
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
|
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:
|
||||||
|
|
||||||
[gcode_macro BLOBIFIER_CHECKED]
|
[gcode_macro BLOBIFIER_CHECKED]
|
||||||
@@ -77,6 +85,7 @@ gcode:
|
|||||||
# the purge too, so a bdwidth that does not see the movement cannot
|
# the purge too, so a bdwidth that does not see the movement cannot
|
||||||
# pause the printer here; the pressure check judges the purge.
|
# pause the printer here; the pressure check judges the purge.
|
||||||
{% set runout_off = has_bdw and (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" %}
|
{% if cfg.mode == "off" %}
|
||||||
BLOBIFIER {rawparams}
|
BLOBIFIER {rawparams}
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -107,9 +116,41 @@ gcode:
|
|||||||
SET_FILAMENT_SENSOR SENSOR={bdw} ENABLE={runout_was}
|
SET_FILAMENT_SENSOR SENSOR={bdw} ENABLE={runout_was}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
_PURGE_FLUSH_DONE
|
||||||
|
|
||||||
# Separate macros so the status is read when they run, not when
|
# Separate macros so the status is read when they run, not when
|
||||||
# BLOBIFIER_CHECKED is rendered
|
# 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_macro _PURGE_CHECK_START]
|
||||||
gcode:
|
gcode:
|
||||||
SET_GCODE_VARIABLE MACRO=_PURGE_CHECK_CFG VARIABLE=start_counts VALUE={printer.bdwidth_status.total_move}
|
SET_GCODE_VARIABLE MACRO=_PURGE_CHECK_CFG VARIABLE=start_counts VALUE={printer.bdwidth_status.total_move}
|
||||||
|
|||||||
@@ -20,9 +20,11 @@
|
|||||||
# Printing
|
# Printing
|
||||||
[include print/pause_resume.cfg]
|
[include print/pause_resume.cfg]
|
||||||
[include print/filament.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/homing.cfg]
|
||||||
[include print/idle_timeout.cfg]
|
[include print/idle_timeout.cfg]
|
||||||
[include print/print_start_end.cfg]
|
[include print/print_start_end.cfg]
|
||||||
|
[include print/test_speed.cfg] # TEST_SPEED: skipped-steps test
|
||||||
|
|
||||||
# Heating
|
# Heating
|
||||||
[include heating/bed_fans.cfg]
|
[include heating/bed_fans.cfg]
|
||||||
@@ -36,8 +38,10 @@
|
|||||||
[include afc/lane_tools.cfg]
|
[include afc/lane_tools.cfg]
|
||||||
[include afc/purge_check.cfg]
|
[include afc/purge_check.cfg]
|
||||||
[include afc/purge_until_empty.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)
|
# 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]
|
[include pressure_advance/bd_pa_spoolman.cfg]
|
||||||
|
|
||||||
# Spoolman
|
# Spoolman
|
||||||
|
|||||||
@@ -191,6 +191,10 @@ variable_fallback_nozzle_temp: 260
|
|||||||
# bd_pressure PA_CALIBRATE parameters -- tune to your printer/filament.
|
# bd_pressure PA_CALIBRATE parameters -- tune to your printer/filament.
|
||||||
# See https://pandapi3d.cn/bdpressure/home for guidance on these values.
|
# See https://pandapi3d.cn/bdpressure/home for guidance on these values.
|
||||||
variable_max_volumetric: 25
|
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_acc_wall: 5000
|
||||||
variable_travel_speed: 300
|
variable_travel_speed: 300
|
||||||
variable_acc_to_decel_factor: 50
|
variable_acc_to_decel_factor: 50
|
||||||
@@ -199,12 +203,17 @@ variable_write_threshold: 0.002
|
|||||||
# A calibration is rejected (nothing cached or written to Spoolman, the
|
# A calibration is rejected (nothing cached or written to Spoolman, the
|
||||||
# previous PA is kept) when:
|
# previous PA is kept) when:
|
||||||
# - its result is the last PA_CALIBRATE test value: bdpressure.py found no
|
# - 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 0.002),
|
# 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),
|
# - the printer paused during the test (e.g. an AFC filament fault),
|
||||||
# - bd_pressure gave fewer than min_points readings or fewer than
|
# - bd_pressure gave fewer than min_points readings or fewer than
|
||||||
# min_distinct different ones (sensor silent or hung; needs
|
# min_distinct different ones (sensor silent or hung; needs
|
||||||
# bdpressure_check.py, otherwise this check is skipped).
|
# bdpressure_check.py, otherwise this check is skipped).
|
||||||
variable_sweep_end_pa: 0.098
|
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_points: 10
|
||||||
variable_min_distinct: 8
|
variable_min_distinct: 8
|
||||||
# PA applied when a rejected lane has no earlier cached value
|
# PA applied when a rejected lane has no earlier cached value
|
||||||
@@ -395,8 +404,30 @@ gcode:
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Core: calibrate (or restore from cache) PA for one lane
|
# 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]
|
[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]
|
# LANE=<lane_name> [FORCE=1]
|
||||||
gcode:
|
gcode:
|
||||||
{% set lane = params.LANE|default("")|string %}
|
{% set lane = params.LANE|default("")|string %}
|
||||||
@@ -430,8 +461,22 @@ gcode:
|
|||||||
{% set lane_temp = lane_obj.extruder_temp|default(0, true)|float %}
|
{% set lane_temp = lane_obj.extruder_temp|default(0, true)|float %}
|
||||||
{% set target = printer.extruder.target|default(0)|float %}
|
{% set target = printer.extruder.target|default(0)|float %}
|
||||||
{% set nozzle_temp = lane_temp if lane_temp > 0 else (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}
|
M117 Calibrating PA: {lane}
|
||||||
|
|
||||||
G1 Z50 F3000
|
G1 Z50 F3000
|
||||||
@@ -440,7 +485,7 @@ gcode:
|
|||||||
G4 P400
|
G4 P400
|
||||||
G1 Z0 F3000
|
G1 Z0 F3000
|
||||||
G4 P400
|
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
|
# IMPORTANT: we do NOT read printer.extruder.pressure_advance
|
||||||
# here. Klipper fully evaluates a macro's Jinja2 template
|
# here. Klipper fully evaluates a macro's Jinja2 template
|
||||||
@@ -460,7 +505,7 @@ gcode:
|
|||||||
# triggered (and its own template is rendered), PA_CALIBRATE
|
# triggered (and its own template is rendered), PA_CALIBRATE
|
||||||
# above has already finished and printer.extruder.pressure_advance
|
# above has already finished and printer.extruder.pressure_advance
|
||||||
# is current.
|
# 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
|
BLOBIFIER_CLEAN
|
||||||
G4 P400
|
G4 P400
|
||||||
G1 Z10 F3000
|
G1 Z10 F3000
|
||||||
@@ -471,13 +516,14 @@ gcode:
|
|||||||
|
|
||||||
[gcode_macro _PA_AFTER_CALIBRATE]
|
[gcode_macro _PA_AFTER_CALIBRATE]
|
||||||
description: Internal -- runs immediately after PA_CALIBRATE completes; reads the fresh PA value and caches it
|
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:
|
gcode:
|
||||||
{% set lane = params.LANE|default("")|string %}
|
{% set lane = params.LANE|default("")|string %}
|
||||||
{% set spool_id = params.SPOOL_ID|default(0)|int %}
|
{% set spool_id = params.SPOOL_ID|default(0)|int %}
|
||||||
{% set cache_found = params.CACHE_FOUND|default(0)|int %}
|
{% set cache_found = params.CACHE_FOUND|default(0)|int %}
|
||||||
{% set cached_pa = params.CACHED_PA|default(0.0)|float %}
|
{% set cached_pa = params.CACHED_PA|default(0.0)|float %}
|
||||||
{% set cfg = printer["gcode_macro _PA_CFG"] %}
|
{% 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
|
# This template is evaluated only once this macro is actually
|
||||||
# triggered -- which happens after PA_CALIBRATE (queued earlier by
|
# triggered -- which happens after PA_CALIBRATE (queued earlier by
|
||||||
@@ -486,7 +532,7 @@ gcode:
|
|||||||
|
|
||||||
# Reject runs where the sensor or the printer had a problem
|
# Reject runs where the sensor or the printer had a problem
|
||||||
{% set problems = [] %}
|
{% set problems = [] %}
|
||||||
{% if new_pa >= cfg.sweep_end_pa|float - 0.0001 %}
|
{% if new_pa >= sweep_end - 0.0001 %}
|
||||||
{% set _ = problems.append("no result (PA left at the end of the test range, %.3f)" % new_pa) %}
|
{% set _ = problems.append("no result (PA left at the end of the test range, %.3f)" % new_pa) %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if printer.pause_resume.is_paused %}
|
{% if printer.pause_resume.is_paused %}
|
||||||
@@ -505,7 +551,7 @@ gcode:
|
|||||||
|
|
||||||
{% if problems %}
|
{% if problems %}
|
||||||
# A cached value at the end of the scan is an earlier failed run, not a PA
|
# 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 < cfg.sweep_end_pa|float - 0.0001 %}
|
{% set use_cache = cache_found and cached_pa < sweep_end - 0.0001 %}
|
||||||
{% set keep = cached_pa if use_cache else cfg.fallback_pa|float %}
|
{% 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"
|
_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}
|
SET_PRESSURE_ADVANCE ADVANCE={keep}
|
||||||
|
|||||||
@@ -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
|
# 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_macro HOME]
|
||||||
gcode:
|
gcode:
|
||||||
G90
|
G90
|
||||||
G4 P2000
|
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
|
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 %}
|
||||||
@@ -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 %}
|
||||||
@@ -9,16 +9,20 @@
|
|||||||
# PRINT_START runs on a soaked bed:
|
# PRINT_START runs on a soaked bed:
|
||||||
# PRINT_WARMUP bed heats while the nozzle cools and the printer homes,
|
# PRINT_WARMUP bed heats while the nozzle cools and the printer homes,
|
||||||
# wait for the bed, HEAT_SOAK
|
# wait for the bed, HEAT_SOAK
|
||||||
# PRINT_START QGL, contact Z calibration, mesh, load the initial tool,
|
# PRINT_START QGL, Z, contact Z calibration, mesh, load the initial tool,
|
||||||
# PA for all lanes, XY reference
|
# PA for all lanes, XY reference
|
||||||
# The filament is loaded after the Z calibration, so the contact always
|
# 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.
|
# 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]
|
[gcode_macro PRINT_WARMUP]
|
||||||
description: Print start before HEAT_SOAK: home while the bed heats, wait for the bed, soak
|
description: Print start before HEAT_SOAK: home while the bed heats, wait for the bed, soak
|
||||||
gcode:
|
gcode:
|
||||||
{% set target_bed = params.BED|int %}
|
{% set target_bed = params.BED|int %}
|
||||||
{% set target_chamber = params.CHAMBER|default("0")|int %}
|
{% set target_chamber = params.CHAMBER|default("0")|int %}
|
||||||
|
{% set initial_tool = params.TOOL|default("0")|int %}
|
||||||
|
|
||||||
RESET_FAILURE
|
RESET_FAILURE
|
||||||
CLEAR_PAUSE
|
CLEAR_PAUSE
|
||||||
@@ -45,10 +49,17 @@ gcode:
|
|||||||
# Only to get the toolhead over the bed for the soak; the Z that counts
|
# 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
|
# is the contact calibration in PRINT_START on the soaked bed
|
||||||
_STATUS STATE=homing
|
_STATUS STATE=homing
|
||||||
_NOZZLE_CONTACT_TEMP
|
# 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"
|
RESPOND MSG="homing"
|
||||||
HOME
|
HOME
|
||||||
|
{% if target_chamber > 30 %}
|
||||||
|
_LOAD_FOR_CHAMBER TOOL={initial_tool}
|
||||||
|
{% endif %}
|
||||||
|
_GUARD_ON PHASE=warmup_park
|
||||||
G1 X175 Y175 Z5 F1200
|
G1 X175 Y175 Z5 F1200
|
||||||
|
_GUARD_OFF
|
||||||
|
|
||||||
_STATUS STATE=heating_bed
|
_STATUS STATE=heating_bed
|
||||||
{% if target_bed > 0 %}
|
{% if target_bed > 0 %}
|
||||||
@@ -67,6 +78,69 @@ gcode:
|
|||||||
HEAT_SOAK HEATER='heater_bed' TARGET={target_bed} SOAKER='temperature_sensor bed_core' SOAK_TEMP={target_bed - 5} TIMEOUT=30 CANCEL='_NOTHING'
|
HEAT_SOAK HEATER='heater_bed' TARGET={target_bed} SOAKER='temperature_sensor bed_core' SOAK_TEMP={target_bed - 5} TIMEOUT=30 CANCEL='_NOTHING'
|
||||||
{% endif %}
|
{% 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)
|
# Beacon contact refuses a nozzle above contact_max_hotend_temperature (180C)
|
||||||
[gcode_macro _NOZZLE_CONTACT_TEMP]
|
[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
|
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
|
||||||
@@ -114,19 +188,38 @@ gcode:
|
|||||||
_LIGHTS_ON
|
_LIGHTS_ON
|
||||||
M106 S0
|
M106 S0
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
# The bed is soaked now: level and calibrate Z on the final geometry
|
# The bed is soaked now: level and calibrate Z on the final geometry
|
||||||
_STATUS STATE=leveling
|
_STATUS STATE=leveling
|
||||||
RESPOND MSG="quad gantry level"
|
RESPOND MSG="quad gantry level"
|
||||||
|
# Nozzle guard (bd_pressure) on while nothing should touch the nozzle
|
||||||
|
_GUARD_ON PHASE=qgl
|
||||||
QUAD_GANTRY_LEVEL
|
QUAD_GANTRY_LEVEL
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# 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
|
BLOBIFIER_CLEAN
|
||||||
|
_GUARD_OFF
|
||||||
_STATUS STATE=calibrating_z
|
_STATUS STATE=calibrating_z
|
||||||
_NOZZLE_CONTACT_TEMP WAIT=1
|
_NOZZLE_CONTACT_TEMP WAIT=1
|
||||||
G28 Z METHOD=CONTACT CALIBRATE=1 ; calibrate z offset and beacon model hot
|
G28 Z METHOD=CONTACT CALIBRATE=1 ; calibrate z offset and beacon model hot
|
||||||
|
|
||||||
_STATUS STATE=meshing
|
_STATUS STATE=meshing
|
||||||
RESPOND MSG="meshing"
|
RESPOND MSG="meshing"
|
||||||
|
_GUARD_ON PHASE=mesh
|
||||||
BED_MESH_CALIBRATE RUNS=2 ; bed mesh in scan mode
|
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
|
# Heat over the bucket, then undo the retracts of the previous PRINT_END
|
||||||
_STATUS STATE=heating_extruder
|
_STATUS STATE=heating_extruder
|
||||||
@@ -156,6 +249,10 @@ gcode:
|
|||||||
# per-reading width compensation made the flow jump between 77 and 119%.
|
# 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
|
# 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"
|
RESPOND MSG="starting"
|
||||||
_STATUS STATE=printing
|
_STATUS STATE=printing
|
||||||
|
|
||||||
@@ -196,8 +293,9 @@ gcode:
|
|||||||
|
|
||||||
# End-of-print cleanup shared by PRINT_END and CANCEL_PRINT
|
# End-of-print cleanup shared by PRINT_END and CANCEL_PRINT
|
||||||
[gcode_macro _PRINT_CLEANUP]
|
[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:
|
gcode:
|
||||||
|
_GUARD_OFF ; a print start that failed while it was armed
|
||||||
TURN_OFF_HEATERS
|
TURN_OFF_HEATERS
|
||||||
iHEATER_OFF ; stops the chamber heater control loop too
|
iHEATER_OFF ; stops the chamber heater control loop too
|
||||||
M107
|
M107
|
||||||
@@ -210,6 +308,34 @@ gcode:
|
|||||||
{% if 'gcode_macro _PURGE_CHECK_CFG' in printer %}
|
{% if 'gcode_macro _PURGE_CHECK_CFG' in printer %}
|
||||||
SET_GCODE_VARIABLE MACRO=_PURGE_CHECK_CFG VARIABLE=ignore_missing_sensor VALUE=False
|
SET_GCODE_VARIABLE MACRO=_PURGE_CHECK_CFG VARIABLE=ignore_missing_sensor VALUE=False
|
||||||
{% endif %}
|
{% 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.
|
# Retract/unretract that is remembered across prints and restarts.
|
||||||
# Klipper's G10 only retracts once until a G11, and nothing sends G11 here,
|
# Klipper's G10 only retracts once until a G11, and nothing sends G11 here,
|
||||||
|
|||||||
@@ -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
|
||||||
Reference in new issue
Block a user