Print start: contact homing, nozzle guard, Z check, purge before chamber heat
- HOME always homes Z with Beacon contact and recalibrates, also when the printer is already homed (home_method_when_homed = proximity used the model of an earlier print without touching the bed). PRINT_START homes Z again after QGL, before the brush takes the nozzle below Z0. - _Z_CHECK: after the soak, compare Klipper's Z with a BEACON_QUERY reading at Z3 over the bed centre and stop the print above 1 mm. Warns and continues when BEACON_QUERY leaves no new printer.beacon.last_sample. - Nozzle guard (extras/bdp_guard.py, print/nozzle_guard.cfg): reads the bd_pressure force over I2C every 20 ms while armed and, in stop mode, shuts Klipper down on a touch. Armed for HOME X/Y, QGL, Z homing, brush, mesh and the Z check; starts in log mode to collect peaks per phase. - Chamber prints: load and purge the first tool before the chamber heats (_LOAD_FOR_CHAMBER replaces _UNLOAD_FOR_CHAMBER). A cut unload leaves a piece of PLA in the extruder that softened in the hot chamber and jammed the ASA load. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2100857846
commit
61fa8acb05
7 files changed
+373
-25
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
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ everything below, grouped by topic. Comment out what you do not use there.
|
|||||||
| `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` | 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: log` only reports the peak per phase, `mode: stop` shuts Klipper down on a touch | `extras/bdp_guard.py` + bd_pressure load-check firmware |
|
||||||
| `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` | — |
|
||||||
@@ -53,6 +54,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. Needs the load-check firmware. |
|
||||||
|
|
||||||
### Scripts (`scripts/`)
|
### Scripts (`scripts/`)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,192 @@
|
|||||||
|
# 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).
|
||||||
|
#
|
||||||
|
# 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_EXT = 54 # extension marker 0xBD + version
|
||||||
|
REG_DELTA = 64 # chk_delta, int16 little-endian
|
||||||
|
EXT_MAGIC = 0xBD
|
||||||
|
MODES = ('log', 'stop')
|
||||||
|
|
||||||
|
|
||||||
|
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.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)
|
||||||
|
self.i2c = getattr(self.sensor, 'i2c', None)
|
||||||
|
|
||||||
|
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 _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
|
||||||
|
if 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.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)
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
# 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]
|
||||||
|
|||||||
+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,33 @@
|
|||||||
|
# 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: log only measures and reports each phase (console and klippy.log,
|
||||||
|
# "bdp_guard:"). Run a few print starts in log mode, set threshold well
|
||||||
|
# above the peaks the phases show, then switch to mode: stop.
|
||||||
|
|
||||||
|
[bdp_guard]
|
||||||
|
sensor: bd_pa
|
||||||
|
mode: log
|
||||||
|
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,10 +9,13 @@
|
|||||||
# 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
|
||||||
@@ -50,9 +53,11 @@ gcode:
|
|||||||
RESPOND MSG="homing"
|
RESPOND MSG="homing"
|
||||||
HOME
|
HOME
|
||||||
{% if target_chamber > 30 %}
|
{% if target_chamber > 30 %}
|
||||||
_UNLOAD_FOR_CHAMBER TOOL={initial_tool}
|
_LOAD_FOR_CHAMBER TOOL={initial_tool}
|
||||||
{% endif %}
|
{% 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 %}
|
||||||
@@ -71,35 +76,56 @@ 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, a filament that stays in the toolhead path softens
|
# With the chamber heated, filament in the toolhead path softens: a PLA lane
|
||||||
# in the PTFE tube (PLA loaded, ASA print). PRINT_START would swap a lane that
|
# still loaded softens in the PTFE tube, and the piece of PLA that a cut
|
||||||
# is not the first tool's anyway, so unload it now, before the chamber heats.
|
# unload leaves below the cutter softens in the extruder and jams the next
|
||||||
# AFC moves the filament back into the Box Turtle, outside the chamber.
|
# load (seen: PLA unloaded, ASA jammed after the soak). The piece stays until
|
||||||
[gcode_macro _UNLOAD_FOR_CHAMBER]
|
# the next load, so an empty toolhead can have it too. For a chamber print,
|
||||||
description: Before a chamber soak: unload the toolhead when its lane is not mapped to TOOL (the print's first tool)
|
# 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:
|
gcode:
|
||||||
{% set tool = 'T' ~ params.TOOL|default(0)|int %}
|
{% set tool = 'T' ~ params.TOOL|default(0)|int %}
|
||||||
{% set cur = printer.AFC.current_load if 'AFC' in printer else none %}
|
{% 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 %}
|
{% if cur and ('AFC_stepper ' ~ cur) in printer %}
|
||||||
{% set lane = printer['AFC_stepper ' ~ cur] %}
|
{% set temps = temps + [printer['AFC_stepper ' ~ cur].extruder_temp|default(0, true)|int] %}
|
||||||
{% if lane.map != tool %}
|
RESPOND MSG="Chamber heating: unloading {cur} ({printer['AFC_stepper ' ~ cur].material}), loading and purging {ns.lane} ({new.material}, {tool}) before the chamber heats"
|
||||||
RESPOND MSG="Chamber heating: unloading {cur} ({lane.material}, {lane.map}), the print starts with {tool}"
|
{% else %}
|
||||||
_HEAT_FOR_LOADED_LANE TOOL={params.TOOL|default(0)|int}
|
RESPOND MSG="Chamber heating: loading and purging {ns.lane} ({new.material}, {tool}) before the chamber heats"
|
||||||
TOOL_UNLOAD LANE={cur}
|
{% endif %}
|
||||||
_UNLOAD_FOR_CHAMBER_VERIFY LANE={cur}
|
M109 S{temps|max}
|
||||||
|
{tool}
|
||||||
|
_LOAD_FOR_CHAMBER_VERIFY LANE={ns.lane}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
# Separate macro: rendered after TOOL_UNLOAD has run. A failed unload stops
|
# Separate macro: rendered after the tool change has run. AFC pauses on a
|
||||||
# the print before the chamber heats.
|
# failed load instead of raising an error, so check the result here: a
|
||||||
[gcode_macro _UNLOAD_FOR_CHAMBER_VERIFY]
|
# failed load stops the print before the chamber heats.
|
||||||
description: Helper for _UNLOAD_FOR_CHAMBER: stop the print when the lane is still loaded
|
[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:
|
gcode:
|
||||||
{% if printer.AFC.current_load %}
|
{% if printer.AFC.current_load != params.LANE %}
|
||||||
_ERROR MSG="Chamber heating: {params.LANE} could not be unloaded ({printer.AFC.current_load} still in the toolhead), print stopped before the chamber heats" DISPLAY="Unload failed, print stopped"
|
_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 %}
|
{% else %}
|
||||||
# The filament the retract was remembered for is gone
|
# 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
|
SAVE_VARIABLE VARIABLE=nozzle_retract VALUE=0
|
||||||
|
_NOZZLE_RETRACT
|
||||||
M104 S150
|
M104 S150
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
@@ -150,19 +176,37 @@ 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: the bristles press on the nozzle too
|
||||||
|
_GUARD_ON PHASE=brush
|
||||||
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
|
||||||
@@ -232,8 +276,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
|
||||||
|
|||||||
Reference in new issue
Block a user