From e9f4dc0aa15bc0182129270b34595b443ea04107 Mon Sep 17 00:00:00 2001 From: yannicklp Date: Fri, 22 May 2026 15:50:31 +0200 Subject: [PATCH 01/15] fix: add support for klipper v0.13 (see issue https://github.com/markniu/Bed_Distance_sensor/issues/248) --- klipper/BDsensor.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/klipper/BDsensor.py b/klipper/BDsensor.py index 0dedff5..31c31e9 100644 --- a/klipper/BDsensor.py +++ b/klipper/BDsensor.py @@ -10,6 +10,20 @@ from threading import Timer from mcu import MCU, MCU_trsync from . import manual_probe +try: + ProbeResult = manual_probe.ProbeResult +except AttributeError: + class ProbeResult(list): + def __init__(self, bed_x, bed_y, bed_z, probe_x=None, probe_y=None, probe_z=None): + # Initialize as list [x, y, z] to satisfy Klipper unpacking + super().__init__([bed_x, bed_y, bed_z]) + # Keep attributes for BDsensor internal usage + self.bed_x = bed_x + self.bed_y = bed_y + self.bed_z = bed_z + self.probe_x = probe_x + self.probe_y = probe_y + self.probe_z = probe_z from . import probe BD_TIMER = 0.600 TRSYNC_TIMEOUT = 0.025 From 1c8c855e71d491995a5279c4a11f97605f53f1b9 Mon Sep 17 00:00:00 2001 From: yannicklp Date: Fri, 22 May 2026 15:53:07 +0200 Subject: [PATCH 02/15] fix: add support for klipper v0.13 (see issue https://github.com/markniu/Bed_Distance_sensor/issues/248) --- klipper/BDsensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/klipper/BDsensor.py b/klipper/BDsensor.py index 31c31e9..db4483f 100644 --- a/klipper/BDsensor.py +++ b/klipper/BDsensor.py @@ -71,7 +71,7 @@ def calc_probe_z_average(positions, method='average'): if method != 'median': # Use mean average inv_count = 1. / float(len(positions)) - return manual_probe.ProbeResult( + return ProbeResult( *[sum([pos[i] for pos in positions]) * inv_count for i in range(len(positions[0]))]) # Use median @@ -100,7 +100,7 @@ def __init__(self, config): def get_offsets(self, gcmd=None): return self.x_offset, self.y_offset, self.z_offset def create_probe_result(self, test_pos): - return manual_probe.ProbeResult( + return ProbeResult( test_pos[0]+self.x_offset, test_pos[1]+self.y_offset, test_pos[2]-self.z_offset, test_pos[0], test_pos[1], test_pos[2]) From 5d146520735b7aa9c62f9e7cebab1d0748c33406 Mon Sep 17 00:00:00 2001 From: yannicklp Date: Fri, 22 May 2026 15:54:36 +0200 Subject: [PATCH 03/15] feat: add homing probe offsets support in BDPrinterProbe + fixed PROBE_CALIBRATE suite. --- klipper/BDsensor.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/klipper/BDsensor.py b/klipper/BDsensor.py index db4483f..37c8f6d 100644 --- a/klipper/BDsensor.py +++ b/klipper/BDsensor.py @@ -103,11 +103,24 @@ def create_probe_result(self, test_pos): return ProbeResult( test_pos[0]+self.x_offset, test_pos[1]+self.y_offset, test_pos[2]-self.z_offset, test_pos[0], test_pos[1], test_pos[2]) +# Helper to read the xyz homing probe offsets from the config +class HomingProbeOffsetsHelper: + def __init__(self, config): + self.x_offset = config.getfloat('homing_probe_x_offset', 0.) + self.y_offset = config.getfloat('homing_probe_y_offset', 0.) + self.z_offset = config.getfloat('homing_probe_z_offset', 0.) + def get_offsets(self, gcmd=None): + return self.x_offset, self.y_offset, self.z_offset + def create_probe_result(self, test_pos): + return ProbeResult( + test_pos[0]+self.x_offset, test_pos[1]+self.y_offset, + test_pos[2]-self.z_offset, test_pos[0], test_pos[1], test_pos[2]) class BDPrinterProbe: def __init__(self, config, mcu_probe): self.printer = config.get_printer() self.probe_offsets = ProbeOffsetsHelper(config) + self.homing_probe_offsets = HomingProbeOffsetsHelper(config) self.name = config.get_name() self.config = config self.mcu_probe = mcu_probe @@ -324,7 +337,10 @@ def _probe_external_endstop(self, speed): reason += HINT_TIMEOUT raise self.printer.command_error(reason) # Allow axis_twist_compensation to update results - epos = self.probe_offsets.create_probe_result(ppos) + # Use homing_probe_offsets here because the external endstop (e.g. Tap) + # is physically at the nozzle — it has its own x/y/z offsets, not the + # BD sensor ones stored in probe_offsets. + epos = self.homing_probe_offsets.create_probe_result(ppos) self.printer.send_event("probe:update_results", [epos]) # add z compensation to probe position gcode = self.printer.lookup_object('gcode') @@ -644,6 +660,20 @@ def cmd_Z_OFFSET_APPLY_PROBE(self, gcmd): configfile = self.printer.lookup_object('configfile') if offset == 0: self.gcode.respond_info("Nothing to do: Z Offset is 0") + elif self.mcu_probe.endstop_pin_num != self.mcu_probe.sda_pin_num: + # External endstop (Tap): fine-tune goes into homing_probe_z_offset. + # Increasing homing_probe_z_offset raises get_position_endstop(), + # which makes G28 set a higher Z at trigger → Z=0 is lower → nozzle + # goes further down. So: new_H = old_H - offset (same sign as BD). + hp_z_offset = self.homing_probe_offsets.get_offsets(gcmd)[2] + new_hp = hp_z_offset - offset + self.gcode.respond_info( + "%s: homing_probe_z_offset: %.3f\n" + "The SAVE_CONFIG command will update the printer config file\n" + "with the above and restart the printer." + % (self.name, new_hp)) + configfile.set(self.name, 'homing_probe_z_offset', + "%.3f" % new_hp) else: z_offset = self.probe_offsets.get_offsets(gcmd)[2] new_calibrate = z_offset - offset From 0cda30cd1dc26d0072327ac7f9e388e3346c1071 Mon Sep 17 00:00:00 2001 From: yannicklp Date: Fri, 22 May 2026 15:55:36 +0200 Subject: [PATCH 04/15] feat: add homing probe offsets support + minor cleanup --- klipper/BDsensor.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/klipper/BDsensor.py b/klipper/BDsensor.py index 37c8f6d..64c327a 100644 --- a/klipper/BDsensor.py +++ b/klipper/BDsensor.py @@ -1611,9 +1611,11 @@ def probe_finish(self, hmove): self.raise_probe() def get_position_endstop(self): - # print("BD get_position_endstop") if self.endstop_pin_num != self.sda_pin_num: - return 0 + # External endstop (Tap): Klipper sets Z to this value when the + # endstop fires. position_endstop is the coarse trigger height; + # homing_probe_z_offset is the PROBE_CALIBRATE / babystep fine-tune. + return self.position_endstop + self.homing_probe_z_offset return self.position_endstop From 70beb77b202f64c786ae77e517a27ac25ed06511 Mon Sep 17 00:00:00 2001 From: yannicklp Date: Fri, 22 May 2026 15:59:45 +0200 Subject: [PATCH 05/15] feat: enhance probe calibration for external endstop support and refine z_offset handling --- klipper/BDsensor.py | 69 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/klipper/BDsensor.py b/klipper/BDsensor.py index 64c327a..4db9080 100644 --- a/klipper/BDsensor.py +++ b/klipper/BDsensor.py @@ -624,14 +624,38 @@ def probe_calibrate_finalize(self, mpresult): if mpresult is None: return ppos, offsets = self.probe_calibrate_info - z_offset = offsets[2] - mpresult.bed_z + ppos.bed_z - self.gcode.respond_info( - "%s: z_offset: %.3f\n" - "The SAVE_CONFIG command will update the printer config file\n" - "with the above and restart the printer." % (self.name, z_offset) - ) configfile = self.printer.lookup_object('configfile') - configfile.set(self.name, 'z_offset', "%.3f" % z_offset) + if self.mcu_probe.endstop_pin_num != self.mcu_probe.sda_pin_num: + # External endstop (e.g. Tap): calibrate homing_probe_z_offset. + # We want: after G28, Z=0 = paper/bed surface. + # At trigger the raw Z is ppos.probe_z; at the paper test it is + # mpresult.bed_z. The new offset is therefore: + # new = trigger_z - paper_z → trigger_z - new = paper_z = 0 ✓ + # get_position_endstop() = position_endstop + homing_probe_z_offset + # After trigger: ppos.probe_z = position_endstop + homing_probe_z_offset + # We want new get_position_endstop() = ppos.probe_z - mpresult[2] + # → new_H = ppos.probe_z - mpresult[2] - position_endstop + # mpresult is a plain kin_pos list [x, y, z, e] from manual_probe + new_offset = (ppos.probe_z - mpresult[2] + - self.mcu_probe.position_endstop) + self.gcode.respond_info( + "%s: homing_probe_z_offset: %.3f\n" + "The SAVE_CONFIG command will update the printer config file\n" + "with the above and restart the printer." + % (self.name, new_offset) + ) + configfile.set(self.name, 'homing_probe_z_offset', + "%.3f" % new_offset) + else: + # BD sensor mode: calibrate BD z_offset (standard formula). + # mpresult is a plain kin_pos list [x, y, z, e] from manual_probe + z_offset = offsets[2] - mpresult[2] + ppos.bed_z + self.gcode.respond_info( + "%s: z_offset: %.3f\n" + "The SAVE_CONFIG command will update the printer config file\n" + "with the above and restart the printer." % (self.name, z_offset) + ) + configfile.set(self.name, 'z_offset', "%.3f" % z_offset) cmd_PROBE_CALIBRATE_help = "Calibrate the probe's z_offset" @@ -698,16 +722,27 @@ def __init__(self, config): self.g28_cmd = config.get('homing_cmd', 'G28') self.z_adjust = config.getfloat('z_adjust', 0., minval=-0.3, below=0.3) self.z_offset = config.getfloat('z_offset', 0., minval=-0.6, maxval=0.6) - self.position_endstop = config.getfloat('position_endstop', 0.7, - minval=0.5, below=2.5) - if self.z_adjust > self.position_endstop: - raise self.printer.command_error("The 'z_adjust' cannot be greater" - " than 'position_endstop' in " - "section [BDsensor]") - if self.z_offset > self.position_endstop: - raise self.printer.command_error("The 'z_offset' cannot be greater" - " than 'position_endstop' in " - "section [BDsensor]") + # When an external endstop (e.g. Tap) is configured the position_endstop + # is used as a coarse trigger-height setting and may span the full Z + # travel range. For BD-sensor-only mode the original tight limits apply. + _has_external_endstop = config.get('endstop_pin', None) is not None + if _has_external_endstop: + self.position_endstop = config.getfloat('position_endstop', 0.) + else: + self.position_endstop = config.getfloat('position_endstop', 0.7, + minval=0.5, below=2.5) + if self.z_adjust > self.position_endstop: + raise self.printer.command_error( + "The 'z_adjust' cannot be greater than 'position_endstop'" + " in section [BDsensor]") + if self.z_offset > self.position_endstop: + raise self.printer.command_error( + "The 'z_offset' cannot be greater than 'position_endstop'" + " in section [BDsensor]") + # Fine-tune offset for external endstop (Tap); calibrated via + # PROBE_CALIBRATE or Z_OFFSET_APPLY_PROBE. Kept separate from + # position_endstop so the coarse value never needs editing. + self.homing_probe_z_offset = config.getfloat('homing_probe_z_offset', 0.) self.stow_on_each_sample = config.getboolean( 'deactivate_on_each_sample', True) self.no_stop_probe = config.get('no_stop_probe', None) From 3574b140a836bdc3783e54215b967f914ce800e1 Mon Sep 17 00:00:00 2001 From: yannicklp Date: Fri, 22 May 2026 16:03:12 +0200 Subject: [PATCH 06/15] feat: implement rapid scan functionality for probe sessions and enhance probe point handling --- klipper/BDsensor.py | 143 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 127 insertions(+), 16 deletions(-) diff --git a/klipper/BDsensor.py b/klipper/BDsensor.py index 4db9080..8c71319 100644 --- a/klipper/BDsensor.py +++ b/klipper/BDsensor.py @@ -132,6 +132,9 @@ def __init__(self, config, mcu_probe): self.probe_calibrate_z = 0. self.multi_probe_pending = False self.rapid_scan = False + self._rapid_scan_idx = 0 # per-session counter for rapid_scan + self._rapid_scan_pts = None # probe_points cache for rapid_scan + self._rapid_scan_offsets = None # (x_off, y_off) cache for rapid_scan self.last_state = False self.last_z_result = 0. self.homing_speed_tmp = 0 @@ -198,16 +201,11 @@ def _probe_state_error(self): raise self.printer.command_error( "Internal probe error - start/end probe session mismatch") - def start_probe_session(self, gcmd): - self._probe_times=[] - if "BED_MESH_CALIBRATE" in gcmd.get_command(): - try: - if self.mcu_probe.no_stop_probe is not None: - self.rapid_scan = True - self.reactor.update_timer(self.bd_sample_timer, self.reactor.NOW) - except AttributeError as e: - gcmd.respond_info("%s" % str(e)) - raise gcmd.error("%s" % str(e)) + def start_probe_session(self, gcmd): + # Called only by run_single_probe() (PROBE, PROBE_CALIBRATE). + # For BED_MESH_CALIBRATE / QGL / Z_TILT the new probe.py calls + # multi_probe_begin() directly, so rapid_scan is started there. + self._probe_times = [] if self.multi_probe_pending: self._probe_state_error() self.mcu_probe.multi_probe_begin() @@ -298,10 +296,25 @@ def _handle_command_error(self): def multi_probe_begin(self): self.mcu_probe.multi_probe_begin() self.multi_probe_pending = True + self._probe_times = [] + self.mcu_probe.results = [] + self._rapid_scan_idx = 0 + # Enable rapid-scan (no_stop_probe) mode when the feature is configured. + # The bd_sample_timer reads the sensor at each lookahead-registered + # printtime so the toolhead doesn't need to fully stop at every point. + if getattr(self.mcu_probe, 'no_stop_probe', None) is not None: + self.rapid_scan = True + self.reactor.update_timer(self.bd_sample_timer, self.reactor.NOW) def multi_probe_end(self): if self.multi_probe_pending: self.multi_probe_pending = False + if self.rapid_scan: + self.reactor.update_timer(self.bd_sample_timer, + self.reactor.NEVER) + self.rapid_scan = False + self._probe_times = [] + self.mcu_probe.results = [] self.mcu_probe.multi_probe_end() def setup_pin(self, pin_type, pin_params): @@ -442,13 +455,111 @@ def scan_sample_event(self, eventtime): def _scan_lookahead_cb(self, printtime): self._probe_times.append(printtime) - def run_probe(self, gcmd): + def _get_next_probe_xy(self, gcmd, current_idx): + """Return [x, y, None] toolhead position for the next probe point, + or None if unknown / already at the last point. + Used by rapid_scan to pre-queue the next move so the toolhead passes + through the current point without stopping. + """ + try: + if "BED_MESH_CALIBRATE" in gcmd.get_command(): + bedmesh = self.printer.lookup_object('bed_mesh', None) + if bedmesh is None: + return None + ph = bedmesh.bmc.probe_helper + pts = ph.probe_points + next_idx = current_idx + 1 + if next_idx >= len(pts): + return None + x_off, y_off, _ = self.probe_offsets.get_offsets() + return [pts[next_idx][0] - x_off, + pts[next_idx][1] - y_off, + None] + except AttributeError: + pass + return None + + _RAPID_SCAN_CMDS = ("BED_MESH_CALIBRATE", "QUAD_GANTRY_LEVEL", + "Z_TILT_ADJUST") + + def run_probe(self, gcmd, retry_session=None): toolhead = self.printer.lookup_object("toolhead") - if self.rapid_scan == True: - if len(self._probe_times) == 0: - toolhead.wait_moves() - toolhead.register_lookahead_callback(self._scan_lookahead_cb) - return + # --- rapid-scan (no_stop_probe) path --- + # BD sensor reads distance while the toolhead is in motion, so we + # use lookahead timing callbacks to record the printtime at each + # probe point and read the sensor in the bd_sample_timer event. + # + # Key insight: pre-queuing only ONE move ahead still causes a stop + # because each pre-queued move is the *last* move when flushed, + # causing the lookahead to plan deceleration to 0. + # + # Fix: on the very first call (idx==0) register callbacks AND + # pre-queue ALL remaining probe points in one shot. The entire + # mesh sweep is in the lookahead before the first flush, so every + # intermediate point is a smooth pass-through. Subsequent + # run_probe calls (idx>0) just spin-wait for their pre-recorded + # result and return immediately. + if self.rapid_scan and any( + cmd in gcmd.get_command() for cmd in self._RAPID_SCAN_CMDS): + idx = self._rapid_scan_idx + self._rapid_scan_idx += 1 + if idx == 0: + # Register callback for P_0 (probe.py already queued its + # move — it is the last move in the lookahead right now). + toolhead.register_lookahead_callback(self._scan_lookahead_cb) + # Pre-queue P_1 … P_{N-1} and attach a callback to each, + # so the whole sweep is planned as one continuous lookahead + # path — every intermediate point is a smooth pass-through. + self._rapid_scan_pts = None + self._rapid_scan_offsets = None + try: + if "BED_MESH_CALIBRATE" in gcmd.get_command(): + bedmesh = self.printer.lookup_object('bed_mesh', None) + if bedmesh is not None: + ph = bedmesh.bmc.probe_helper + pts = ph.probe_points + x_off, y_off, _ = self.probe_offsets.get_offsets() + travel_speed = ph.speed + self._rapid_scan_pts = pts + self._rapid_scan_offsets = (x_off, y_off) + for i in range(1, len(pts)): + toolhead.manual_move( + [pts[i][0] - x_off, + pts[i][1] - y_off, + None], + travel_speed) + toolhead.register_lookahead_callback( + self._scan_lookahead_cb) + # After pre-queuing, commanded_pos is at P_{N-1}. + # Reset it to P_1 so probe.py's next _move_next() + # queues a zero-distance (skipped) move instead of + # driving the toolhead backward for a 2nd pass. + if len(pts) > 1: + toolhead.commanded_pos[0] = ( + pts[1][0] - x_off) + toolhead.commanded_pos[1] = ( + pts[1][1] - y_off) + except AttributeError: + pass + else: + # For each subsequent point: advance commanded_pos to P_{idx+1} + # so probe.py's _move_next() for that point is zero-distance + # and gets silently skipped (no 2nd-pass backward moves). + pts = self._rapid_scan_pts + if pts is not None and idx + 1 < len(pts): + x_off, y_off = self._rapid_scan_offsets + toolhead.commanded_pos[0] = pts[idx + 1][0] - x_off + toolhead.commanded_pos[1] = pts[idx + 1][1] - y_off + # Reactor-safe spin until scan_sample_event delivers result[idx]. + while len(self.mcu_probe.results) <= idx: + toolhead.dwell(0.005) + epos = self.mcu_probe.results[idx] + self.printer.send_event("probe:update_results", [epos]) + if self.console_verbosity >= 1: + self.gcode.respond_info( + "rapid probe: at %.3f,%.3f bed will contact at z=%.6f" + % (epos.bed_x, epos.bed_y, epos.bed_z)) + return epos speed = gcmd.get_float("PROBE_SPEED", self.speed, above=0.) lift_speed = self.get_lift_speed(gcmd) From afe140e4e43d5237d9a0a65992f03dfc47519532 Mon Sep 17 00:00:00 2001 From: yannicklp Date: Fri, 22 May 2026 16:04:04 +0200 Subject: [PATCH 07/15] feat: add console verbosity configuration for BDPrinterProbe --- klipper/BDsensor.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/klipper/BDsensor.py b/klipper/BDsensor.py index 8c71319..489bc37 100644 --- a/klipper/BDsensor.py +++ b/klipper/BDsensor.py @@ -129,6 +129,7 @@ def __init__(self, config, mcu_probe): self.x_offset = config.getfloat('x_offset', 0.) self.y_offset = config.getfloat('y_offset', 0.) self.z_offset = config.getfloat('z_offset') + self.console_verbosity = config.getint('console_verbosity', 0, minval=0, maxval=2) self.probe_calibrate_z = 0. self.multi_probe_pending = False self.rapid_scan = False @@ -357,7 +358,8 @@ def _probe_external_endstop(self, speed): self.printer.send_event("probe:update_results", [epos]) # add z compensation to probe position gcode = self.printer.lookup_object('gcode') - gcode.respond_info("probe: at %.3f,%.3f bed will contact at z=%.6f" + if self.console_verbosity >= 2: + gcode.respond_info("probe: at %.3f,%.3f bed will contact at z=%.6f" % (epos.bed_x, epos.bed_y, epos.bed_z)) return epos @@ -395,7 +397,8 @@ def _probe(self, speed): self.printer.send_event("probe:update_results", [epos]) # Report results gcode = self.printer.lookup_object('gcode') - gcode.respond_info("0probe: at %.3f,%.3f bed will contact at z=%.6f" + if self.console_verbosity >= 1: + gcode.respond_info("0probe: at %.3f,%.3f bed will contact at z=%.6f" % (epos.bed_x, epos.bed_y, epos.bed_z)) #self.mcu_probe.homeing = 0 return epos @@ -443,6 +446,7 @@ def scan_sample_event(self, eventtime): #self.printer.send_event("probe:update_results", [epos]) # limit the message output to the console else it may take a lot of time if len(self.mcu_probe.results) < 500: + if self.console_verbosity >= 1: self.gcode.respond_info("probe: at %.3f,%.3f bed will contact at z=%.6f" % (epos.bed_x, epos.bed_y, epos.bed_z)) break @@ -600,8 +604,9 @@ def run_probe(self, gcmd, retry_session=None): self.printer.send_event("probe:update_results", [epos]) # Report results gcode = self.printer.lookup_object('gcode') - gcode.respond_info("run probe: at %.3f,%.3f bed will contact at z=%.6f" - % (epos.bed_x, epos.bed_y, epos.bed_z)) + if self.console_verbosity >= 1: + gcode.respond_info("run probe: at %.3f,%.3f bed will contact at z=%.6f" + % (epos.bed_x, epos.bed_y, epos.bed_z)) # return pos[:3] # positions.append(pos[:3]) positions.append(epos) From f3b8ece7e7e3a36ea170eb0cd3b4cde24e79b61b Mon Sep 17 00:00:00 2001 From: yannicklp Date: Fri, 22 May 2026 16:04:23 +0200 Subject: [PATCH 08/15] feat: add console verbosity configuration for BDPrinterProbe --- klipper/BDsensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/klipper/BDsensor.py b/klipper/BDsensor.py index 489bc37..ac1336b 100644 --- a/klipper/BDsensor.py +++ b/klipper/BDsensor.py @@ -447,7 +447,7 @@ def scan_sample_event(self, eventtime): # limit the message output to the console else it may take a lot of time if len(self.mcu_probe.results) < 500: if self.console_verbosity >= 1: - self.gcode.respond_info("probe: at %.3f,%.3f bed will contact at z=%.6f" + self.gcode.respond_info("probe: at %.3f,%.3f bed will contact at z=%.6f" % (epos.bed_x, epos.bed_y, epos.bed_z)) break toolhead.reactor.pause(systime + 0.002) From 2c67eefd803a6914a0ac0a383e77e446c6d56774 Mon Sep 17 00:00:00 2001 From: yannicklp Date: Fri, 22 May 2026 16:04:59 +0200 Subject: [PATCH 09/15] fix: add support for klipper v0.13 (see issue https://github.com/markniu/Bed_Distance_sensor/issues/248) --- klipper/BDsensor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/klipper/BDsensor.py b/klipper/BDsensor.py index ac1336b..78ad543 100644 --- a/klipper/BDsensor.py +++ b/klipper/BDsensor.py @@ -652,6 +652,8 @@ def run_probe(self, gcmd, retry_session=None): epos = calc_probe_z_average(positions, samples_result) #self.results.append(epos) self.mcu_probe.results.append(epos) + # New probe.py collects results via run_probe's return value + return epos cmd_PROBE_help = "Probe Z-height at current XY position" From b3f62b1a1f6ba6efeed7d349ab8ebae5beffd831 Mon Sep 17 00:00:00 2001 From: yannicklp Date: Fri, 22 May 2026 16:05:57 +0200 Subject: [PATCH 10/15] misc: trailing whitespaces and empty lines reformatted --- klipper/BDsensor.py | 94 ++++++++++++++++++++++----------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/klipper/BDsensor.py b/klipper/BDsensor.py index 78ad543..0963b07 100644 --- a/klipper/BDsensor.py +++ b/klipper/BDsensor.py @@ -115,7 +115,7 @@ def create_probe_result(self, test_pos): return ProbeResult( test_pos[0]+self.x_offset, test_pos[1]+self.y_offset, test_pos[2]-self.z_offset, test_pos[0], test_pos[1], test_pos[2]) - + class BDPrinterProbe: def __init__(self, config, mcu_probe): self.printer = config.get_printer() @@ -197,7 +197,7 @@ def __init__(self, config, mcu_probe): self.reactor = self.printer.get_reactor() self.bd_sample_timer = self.reactor.register_timer( self.scan_sample_event) - + def _probe_state_error(self): raise self.printer.command_error( "Internal probe error - start/end probe session mismatch") @@ -213,11 +213,11 @@ def start_probe_session(self, gcmd): self.multi_probe_pending = True self.mcu_probe.results = [] return self - + def pull_probed_results(self): toolhead = self.printer.lookup_object("toolhead") toolhead.get_last_move_time() - if self.rapid_scan == True: + if self.rapid_scan == True: self.bedmesh = self.printer.lookup_object('bed_mesh', None) helperc = self.bedmesh.bmc.probe_mgr.probe_helper while len(self.mcu_probe.results) < len(helperc.probe_points): @@ -231,10 +231,10 @@ def pull_probed_results(self): def end_probe_session(self): if not self.multi_probe_pending: self._probe_state_error() - self.mcu_probe.results = [] + self.mcu_probe.results = [] self.multi_probe_pending = False self.mcu_probe.multi_probe_end() - + def get_probe_params(self, gcmd=None): if gcmd is None: gcmd = self.dummy_gcode_cmd @@ -272,7 +272,7 @@ def _handle_home_rails_begin(self, homing_state, rails): self.mcu_probe.multi_probe_begin() self.multi_probe_pending = True for i, rail in enumerate(rails): - if (self.mcu_probe.collision_homing == 1 + if (self.mcu_probe.collision_homing == 1 and rail.homing_retract_dist == 0): self.homing_speed_tmp = rail.homing_speed rail.homing_speed = rail.second_homing_speed @@ -286,7 +286,7 @@ def _handle_home_rails_end(self, homing_state, rails): and rail.homing_retract_dist == 0): rail.homing_speed = self.homing_speed_tmp self.multi_probe_end() - + def _handle_command_error(self): try: @@ -373,7 +373,7 @@ def _probe(self, speed): raise self.printer.command_error("Must home before probe") phoming = self.printer.lookup_object('homing') pos = toolhead.get_position() - self.mcu_probe.reactor.update_timer(self.mcu_probe.bd_update_timer, + self.mcu_probe.reactor.update_timer(self.mcu_probe.bd_update_timer, self.mcu_probe.reactor.NEVER) pos[2] = self.z_position try: @@ -420,7 +420,7 @@ def _calc_median(self, positions): # even number of samples return self._calc_mean(z_sorted[middle - 1:middle + 1]) - + def _lookup_toolhead_pos(self, pos_time): toolhead = self.printer.lookup_object('toolhead') kin = toolhead.get_kinematics() @@ -428,7 +428,7 @@ def _lookup_toolhead_pos(self, pos_time): s.get_past_mcu_position(pos_time)) for s in kin.get_steppers()} return kin.calc_position(kin_spos) - + def scan_sample_event(self, eventtime): toolhead = self.printer.lookup_object("toolhead") while self._probe_times: @@ -438,7 +438,7 @@ def scan_sample_event(self, eventtime): est_print_time = toolhead.mcu.estimated_print_time(systime) if est_print_time>=pos_time: pos = self._lookup_toolhead_pos(pos_time) - intd = self.mcu_probe.BD_Sensor_Read(0) + intd = self.mcu_probe.BD_Sensor_Read(0) pos[2] = pos[2] - intd + self.mcu_probe.endstop_bdsensor_offset epos = self.probe_offsets.create_probe_result(pos) self.mcu_probe.results.append(epos) @@ -452,11 +452,11 @@ def scan_sample_event(self, eventtime): break toolhead.reactor.pause(systime + 0.002) self._probe_times.pop(0) - + return eventtime + 0.005 - - def _scan_lookahead_cb(self, printtime): + + def _scan_lookahead_cb(self, printtime): self._probe_times.append(printtime) def _get_next_probe_xy(self, gcmd, current_idx): @@ -700,7 +700,7 @@ def cmd_PROBE_ACCURACY(self, gcmd): sample_count, sample_retract_dist, speed, lift_speed)) # Probe bed sample_count times - + # toolhead.manual_move([None, None, pos[2]], speed) # toolhead.wait_moves() fo_params = dict(gcmd.get_command_parameters()) @@ -777,7 +777,7 @@ def probe_calibrate_finalize(self, mpresult): cmd_PROBE_CALIBRATE_help = "Calibrate the probe's z_offset" - def cmd_PROBE_CALIBRATE(self, gcmd): + def cmd_PROBE_CALIBRATE(self, gcmd): manual_probe.verify_no_manual_probe(self.printer) params = self.get_probe_params(gcmd) # Perform initial probe @@ -794,7 +794,7 @@ def cmd_PROBE_CALIBRATE(self, gcmd): self.probe_calibrate_info = (ppos, self.get_offsets(gcmd)) manual_probe.ManualProbeHelper(self.printer, gcmd, self.probe_calibrate_finalize) - + cmd_Z_OFFSET_APPLY_PROBE_help = "Adjust the probe's z_offset" def cmd_Z_OFFSET_APPLY_PROBE(self, gcmd): @@ -871,7 +871,7 @@ def __init__(self, config): self.QGL_Tilt_Probe = config.getint('QGL_Tilt_Probe', 1) self.switch_mode_sample_time = config.getfloat('SWITCH_MODE_SAMPLE_TIME', 0.006) self.speed = config.getfloat('speed', 3.0, above=0.) - + gcode_macro = self.printer.load_object(config, 'gcode_macro') self.activate_gcode = \ @@ -882,7 +882,7 @@ def __init__(self, config): self.collision_calibrating = 0 self.switch_mode = 0 self.printer.register_event_handler("stepper_enable:motor_off", - self.event_motor_off) + self.event_motor_off) ppins = self.printer.lookup_object('pins') # self.mcu_pwm = ppins.setup_pin('pwm', config.get('scl_pin')) self.bdversion = '' @@ -973,7 +973,7 @@ def __init__(self, config): self.reactor = self.printer.get_reactor() self.bd_update_timer = self.reactor.register_timer( self.bd_update_event) - + self.status_dis = None # try: # self.status_dis=self.printer.lookup_object('display_status') @@ -1088,7 +1088,7 @@ def bd_set_cur_z(self, hgt, s_log): #self.gcode.respond_info("Since current z <0 ") return; hgt = hgt-(self.z_offset-self.z_offset_adj) - hgt=int(hgt*1000) + hgt=int(hgt*1000) self.I2C_BD_send(1026, hgt) def event_motor_off(self,print_time=0): if self.adjust_range != 0: @@ -1099,10 +1099,10 @@ def bd_update_z(self,z): kin = self.toolhead.get_kinematics() for stepper in kin.get_steppers(): if stepper.is_active_axis('z') or stepper.is_active_axis('a') or stepper.is_active_axis('b') or stepper.is_active_axis('c'): - self.bd_set_cur_z(z,1) + self.bd_set_cur_z(z,1) break self.I2C_BD_send(CMD_DISTANCE_MODE) - + def bd_update_event(self, eventtime): z=self.gcode_move.last_position[2] - self.gcode_move.base_position[2] if self.homing == 1: @@ -1222,7 +1222,7 @@ def BD_calibrate(self, gcmd): raise self.printer.command_error("Unable to communicate with bdsensor,%d"%raw_d) raise self.printer.command_error("BDsensor is too far from the bed:%d"%raw_d) self.I2C_BD_send(CMD_DISTANCE_MODE) - + self.I2C_BD_send(CMD_START_CALIBRATE) self.I2C_BD_send(CMD_START_CALIBRATE) self.gcode.run_script_from_command("SET_KINEMATIC_POSITION Z=0") @@ -1330,7 +1330,7 @@ def bd_set(self, gcmd): cmd_bd = gcmd.get_float('REAL_TIME_HEIGHT', None) if cmd_bd is not None: - self.BD_real_time(cmd_bd) + self.BD_real_time(cmd_bd) return cmd_bd = gcmd.get_int('NO_STOP_PROBE', None) @@ -1366,7 +1366,7 @@ def BD_real_time(self, bd_height): elif bd_height < 0.0: bd_height = 0 self.gcode.respond_info("Real time leveling height:%.2f ( %.2f - z_offset:%.2f ) "%(bd_height-self.z_offset,bd_height,self.z_offset)) - + if bd_height == 0: self.adjust_range = 0 else: @@ -1401,7 +1401,7 @@ def BD_real_time(self, bd_height): self.reactor.update_timer(self.bd_update_timer, self.reactor.NEVER) else: self.reactor.update_timer(self.bd_update_timer, self.reactor.NOW) - + def process_M102(self, gcmd): self.process_m102 = 1 cmd_bd = 0 @@ -1420,12 +1420,12 @@ def process_M102(self, gcmd): elif cmd_bd == -2: # gcode M102 S-2 read distance data self.bd_distance(gcmd) elif cmd_bd == -7: - self.I2C_BD_send(CMD_DISTANCE_RAWDATA_TYPE) + self.I2C_BD_send(CMD_DISTANCE_RAWDATA_TYPE) strd = "Raw data:" + str(self.I2C_BD_send(CMD_READ_DATA, 1)) self.I2C_BD_send(CMD_DISTANCE_MODE) self.bd_value = self.BD_Sensor_Read(1) strd = strd + ", Then we can calculate the distance by comparing to the calibration data:"+str(self.bd_value) + "mm" - gcmd.respond_raw(strd) + gcmd.respond_raw(strd) return elif cmd_bd == -8: self.I2C_BD_send(CMD_REBOOT_SENSOR) @@ -1634,7 +1634,7 @@ def adjust_probe_up(self, up_steps, second_steps, logd): break intr = raw_d break - intr = raw_d + intr = raw_d return 0,0 def adjust_probe_down(self, down_steps): @@ -1656,46 +1656,46 @@ def adjust_probe_down(self, down_steps): intr = raw_d - + def adjust_probe(self): self.toolhead = self.printer.lookup_object('toolhead') homepos = self.toolhead.get_position() self.I2C_BD_send(CMD_DISTANCE_RAWDATA_TYPE) self.I2C_BD_send(CMD_DISTANCE_RAWDATA_TYPE) - adj_z,adj_raw = self.adjust_probe_up(0.1,0,1) + adj_z,adj_raw = self.adjust_probe_up(0.1,0,1) #if adj_z <= 0.15: # and adj_raw >= 6: - + self.adjust_probe_down(0.1) - adj_z,adj_raw = self.adjust_probe_up(0.05,0.01,0) - + adj_z,adj_raw = self.adjust_probe_up(0.05,0.01,0) + self.adjust_probe_down(0.05) - adj_z,adj_raw = self.adjust_probe_up(0.05,0.005,1) - + adj_z,adj_raw = self.adjust_probe_up(0.05,0.005,1) + #if adj_z <= 0.1: # and adj_raw >= 6: # self.gcode.respond_info("re-adjusting") # self.adjust_probe_down(0.1) - # adj_z,adj_raw = self.adjust_probe_up(0.05,0.01,1) + # adj_z,adj_raw = self.adjust_probe_up(0.05,0.01,1) self.bd_value = self.BD_Sensor_Read(2) self.I2C_BD_send(CMD_DISTANCE_MODE) def multi_probe_end(self): self.toolhead = self.printer.lookup_object('toolhead') - homepos = self.toolhead.get_position() + homepos = self.toolhead.get_position() if self.endstop_pin_num == self.sda_pin_num: - + if self.switch_mode == 1 \ and self.homing == 1 \ and (self.collision_homing == 1 or self.collision_calibrating == 1): - + self.adjust_probe() #homepos[2] = 0 if self.collision_calibrating != 1: # homepos[2] = 0 + self.z_offset + 0.5 - self.I2C_BD_send(CMD_DISTANCE_MODE) + self.I2C_BD_send(CMD_DISTANCE_MODE) homepos = self.toolhead.get_position() - homepos[2] +=0.5 + homepos[2] +=0.5 self.toolhead.manual_move([None, None, homepos[2]], 2) self.toolhead.wait_moves() self.bd_value = self.BD_Sensor_Read(2) @@ -1710,11 +1710,11 @@ def multi_probe_end(self): # self.toolhead.manual_move([None, None, homepos[2]+10], 2) self.gcode.run_script_from_command("SET_KINEMATIC_POSITION Z=0") self.gcode.run_script_from_command("M102 S-6") - + self.homing = 0 return - - + + else: homepos[2] = 0 self.toolhead.set_position(homepos) From b32f6fd18981fc98229d7f6424289870a31d6837 Mon Sep 17 00:00:00 2001 From: yannicklp Date: Thu, 28 May 2026 17:19:10 +0200 Subject: [PATCH 11/15] feat: resolve endstop pin for different MCU configurations in BDsensorEndstopWrapper --- klipper/BDsensor.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/klipper/BDsensor.py b/klipper/BDsensor.py index 0963b07..9660635 100644 --- a/klipper/BDsensor.py +++ b/klipper/BDsensor.py @@ -917,11 +917,15 @@ def __init__(self, config): self._invert_endstop = self._invert self.oid_endstop = self.oid self.endstop_pin_num = self.sda_pin_num + self.endstop_pin_num_resolved = self.sda_pin_num self.endstop_bdsensor_offset = 0 try: pin = config.get('endstop_pin') pin_params = ppins.lookup_pin(pin, can_invert=True, can_pullup=True) self.endstop_pin_num = pin_params['pin'] + pin_resolver = ppins.get_pin_resolver(pin_params['chip_name']) + self.endstop_pin_num_resolved = pin_resolver.aliases.get( + pin_params['pin'], pin_params['pin']) self.mcu_endstop = pin_params['chip'] self._invert_endstop = pin_params['invert'] if self.mcu_endstop is not self.mcu: @@ -1536,6 +1540,11 @@ def home_start(self, print_time, sample_time, sample_count, rest_time, ffi_lib.trdispatch_start(self._trdispatch, self.etrsync.REASON_HOST_REQUEST) + # When endstop is on a different MCU, scl_gpio/sda_pin are not + # initialized on that MCU (only config_I2C_BD on the main MCU sets + # them up). Sending sw=1 would cause BD_setLow(scl_gpio) with an + # uninitialized gpio_out -> "Not an output pin" firmware shutdown. + ext_sw_mode = 0 if self.mcu_endstop is not self.mcu else self.switch_mode self._home_cmd.send( [ self.oid_endstop, @@ -1546,8 +1555,8 @@ def home_start(self, print_time, sample_time, sample_count, rest_time, triggered ^ self._invert_endstop, self.etrsync.get_oid(), self.etrsync.REASON_ENDSTOP_HIT, - self.endstop_pin_num, - self.switch_mode,collision_value + self.endstop_pin_num_resolved, + ext_sw_mode, collision_value ], reqclock=clock ) @@ -1566,7 +1575,7 @@ def home_wait(self, home_end_time): self.trigger_completion.complete(True) self.trigger_completion.wait() self._home_cmd.send([self.oid_endstop, 0, 0, 0, 0, 0, 0, 0, - self.endstop_pin_num,0,0]) + self.endstop_pin_num_resolved,0,0]) ffi_main, ffi_lib = chelper.get_ffi() ffi_lib.trdispatch_stop(self._trdispatch) res = [trsync.stop() for trsync in self._trsyncs] From 07ed7fe8001bb5d1e810edbd3c62de16e72ef9ba Mon Sep 17 00:00:00 2001 From: yannicklp Date: Fri, 29 May 2026 14:56:41 +0200 Subject: [PATCH 12/15] feat: update ProbeResult class to use test coordinates and improve external endstop handling --- klipper/BDsensor.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/klipper/BDsensor.py b/klipper/BDsensor.py index 9660635..98fdd0a 100644 --- a/klipper/BDsensor.py +++ b/klipper/BDsensor.py @@ -14,16 +14,16 @@ ProbeResult = manual_probe.ProbeResult except AttributeError: class ProbeResult(list): - def __init__(self, bed_x, bed_y, bed_z, probe_x=None, probe_y=None, probe_z=None): + def __init__(self, bed_x, bed_y, bed_z, test_x=None, test_y=None, test_z=None): # Initialize as list [x, y, z] to satisfy Klipper unpacking super().__init__([bed_x, bed_y, bed_z]) # Keep attributes for BDsensor internal usage self.bed_x = bed_x self.bed_y = bed_y self.bed_z = bed_z - self.probe_x = probe_x - self.probe_y = probe_y - self.probe_z = probe_z + self.test_x = test_x + self.test_y = test_y + self.test_z = test_z from . import probe BD_TIMER = 0.600 TRSYNC_TIMEOUT = 0.025 @@ -365,7 +365,7 @@ def _probe_external_endstop(self, speed): def _probe(self, speed): self.mcu_probe.homing = 0 - if self.mcu_probe.endstop_pin_num != self.mcu_probe.sda_pin_num: + if self.mcu_probe.has_external_endstop: return self._probe_external_endstop(speed) toolhead = self.printer.lookup_object('toolhead') curtime = self.printer.get_reactor().monotonic() @@ -743,18 +743,18 @@ def probe_calibrate_finalize(self, mpresult): return ppos, offsets = self.probe_calibrate_info configfile = self.printer.lookup_object('configfile') - if self.mcu_probe.endstop_pin_num != self.mcu_probe.sda_pin_num: + if self.mcu_probe.has_external_endstop: # External endstop (e.g. Tap): calibrate homing_probe_z_offset. # We want: after G28, Z=0 = paper/bed surface. - # At trigger the raw Z is ppos.probe_z; at the paper test it is + # At trigger the raw Z is ppos.test_z; at the paper test it is # mpresult.bed_z. The new offset is therefore: # new = trigger_z - paper_z → trigger_z - new = paper_z = 0 ✓ # get_position_endstop() = position_endstop + homing_probe_z_offset - # After trigger: ppos.probe_z = position_endstop + homing_probe_z_offset - # We want new get_position_endstop() = ppos.probe_z - mpresult[2] - # → new_H = ppos.probe_z - mpresult[2] - position_endstop + # After trigger: ppos.test_z = raw toolhead Z at trigger + # We want new get_position_endstop() = ppos.test_z - mpresult[2] + # → new_H = ppos.test_z - mpresult[2] - position_endstop # mpresult is a plain kin_pos list [x, y, z, e] from manual_probe - new_offset = (ppos.probe_z - mpresult[2] + new_offset = (ppos.test_z - mpresult[2] - self.mcu_probe.position_endstop) self.gcode.respond_info( "%s: homing_probe_z_offset: %.3f\n" @@ -764,6 +764,8 @@ def probe_calibrate_finalize(self, mpresult): ) configfile.set(self.name, 'homing_probe_z_offset', "%.3f" % new_offset) + # Reset any stale z_offset left over from a previous BD-only run. + configfile.set(self.name, 'z_offset', '0.000') else: # BD sensor mode: calibrate BD z_offset (standard formula). # mpresult is a plain kin_pos list [x, y, z, e] from manual_probe @@ -791,7 +793,10 @@ def cmd_PROBE_CALIBRATE(self, gcmd): curpos[1] = ppos.bed_y self._move(curpos, params['probe_speed']) # Start manual probe - self.probe_calibrate_info = (ppos, self.get_offsets(gcmd)) + if self.mcu_probe.has_external_endstop: + self.probe_calibrate_info = (ppos, self.homing_probe_offsets.get_offsets(gcmd)) + else: + self.probe_calibrate_info = (ppos, self.get_offsets(gcmd)) manual_probe.ManualProbeHelper(self.printer, gcmd, self.probe_calibrate_finalize) From b1e650dcdfe0ba7381b47899a75ab269af1b4769 Mon Sep 17 00:00:00 2001 From: yannicklp Date: Fri, 29 May 2026 14:57:10 +0200 Subject: [PATCH 13/15] feat: enhance external endstop handling in BDPrinterProbe and BDsensorEndstopWrapper --- klipper/BDsensor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/klipper/BDsensor.py b/klipper/BDsensor.py index 98fdd0a..c20ead5 100644 --- a/klipper/BDsensor.py +++ b/klipper/BDsensor.py @@ -807,7 +807,7 @@ def cmd_Z_OFFSET_APPLY_PROBE(self, gcmd): configfile = self.printer.lookup_object('configfile') if offset == 0: self.gcode.respond_info("Nothing to do: Z Offset is 0") - elif self.mcu_probe.endstop_pin_num != self.mcu_probe.sda_pin_num: + elif self.mcu_probe.has_external_endstop: # External endstop (Tap): fine-tune goes into homing_probe_z_offset. # Increasing homing_probe_z_offset raises get_position_endstop(), # which makes G28 set a higher Z at trigger → Z=0 is lower → nozzle @@ -848,8 +848,8 @@ def __init__(self, config): # When an external endstop (e.g. Tap) is configured the position_endstop # is used as a coarse trigger-height setting and may span the full Z # travel range. For BD-sensor-only mode the original tight limits apply. - _has_external_endstop = config.get('endstop_pin', None) is not None - if _has_external_endstop: + self.has_external_endstop = config.get('endstop_pin', None) is not None + if self.has_external_endstop: self.position_endstop = config.getfloat('position_endstop', 0.) else: self.position_endstop = config.getfloat('position_endstop', 0.7, From a85645090f2785e2c39cb05c2b4a42174a308087 Mon Sep 17 00:00:00 2001 From: yannicklp Date: Sat, 30 May 2026 16:15:30 +0200 Subject: [PATCH 14/15] fix: update probe_helper reference to use probe_mgr in BDPrinterProbe class --- klipper/BDsensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/klipper/BDsensor.py b/klipper/BDsensor.py index c20ead5..18fe3eb 100644 --- a/klipper/BDsensor.py +++ b/klipper/BDsensor.py @@ -470,7 +470,7 @@ def _get_next_probe_xy(self, gcmd, current_idx): bedmesh = self.printer.lookup_object('bed_mesh', None) if bedmesh is None: return None - ph = bedmesh.bmc.probe_helper + ph = bedmesh.bmc.probe_mgr.probe_helper pts = ph.probe_points next_idx = current_idx + 1 if next_idx >= len(pts): @@ -520,7 +520,7 @@ def run_probe(self, gcmd, retry_session=None): if "BED_MESH_CALIBRATE" in gcmd.get_command(): bedmesh = self.printer.lookup_object('bed_mesh', None) if bedmesh is not None: - ph = bedmesh.bmc.probe_helper + ph = bedmesh.bmc.probe_mgr.probe_helper pts = ph.probe_points x_off, y_off, _ = self.probe_offsets.get_offsets() travel_speed = ph.speed From 6a1012fd9842ab28ad3389412d34318c02de5147 Mon Sep 17 00:00:00 2001 From: yannicklp Date: Sat, 30 May 2026 16:16:33 +0200 Subject: [PATCH 15/15] fix: rapid scan handling in BDPrinterProbe for external and internal probe use according to overriden tilt and sgl setup --- klipper/BDsensor.py | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/klipper/BDsensor.py b/klipper/BDsensor.py index 18fe3eb..fe536a3 100644 --- a/klipper/BDsensor.py +++ b/klipper/BDsensor.py @@ -203,15 +203,29 @@ def _probe_state_error(self): "Internal probe error - start/end probe session mismatch") def start_probe_session(self, gcmd): - # Called only by run_single_probe() (PROBE, PROBE_CALIBRATE). - # For BED_MESH_CALIBRATE / QGL / Z_TILT the new probe.py calls - # multi_probe_begin() directly, so rapid_scan is started there. self._probe_times = [] if self.multi_probe_pending: self._probe_state_error() self.mcu_probe.multi_probe_begin() self.multi_probe_pending = True self.mcu_probe.results = [] + self._rapid_scan_idx = 0 + # Modern Klipper calls start_probe_session instead of multi_probe_begin, + # so we must activate rapid_scan here when no_stop_probe is configured. + # Only activate for commands that benefit from rapid_scan BD-sensor reads. + # QGL/Z_TILT default to the external endstop (physical trigger) when one + # is configured — rapid_scan is BD-sensor-only and must not override that. + # Set QGL_Tilt_Probe=0 to explicitly opt QGL/Z_TILT into BD-sensor reads. + _cmd = gcmd.get_command() + _is_qgl_tilt = ("QUAD_GANTRY_LEVEL" in _cmd or "Z_TILT_ADJUST" in _cmd) + _ext_handles_qgl = (self.mcu_probe.has_external_endstop + and self.mcu_probe.QGL_Tilt_Probe != 0 + and _is_qgl_tilt) + if (getattr(self.mcu_probe, 'no_stop_probe', None) is not None + and any(cmd in _cmd for cmd in self._RAPID_SCAN_CMDS) + and not _ext_handles_qgl): + self.rapid_scan = True + self.reactor.update_timer(self.bd_sample_timer, self.reactor.NOW) return self def pull_probed_results(self): @@ -233,6 +247,10 @@ def end_probe_session(self): self._probe_state_error() self.mcu_probe.results = [] self.multi_probe_pending = False + if self.rapid_scan: + self.reactor.update_timer(self.bd_sample_timer, self.reactor.NEVER) + self.rapid_scan = False + self._probe_times = [] self.mcu_probe.multi_probe_end() def get_probe_params(self, gcmd=None): @@ -503,6 +521,19 @@ def run_probe(self, gcmd, retry_session=None): # intermediate point is a smooth pass-through. Subsequent # run_probe calls (idx>0) just spin-wait for their pre-recorded # result and return immediately. + # If rapid_scan was enabled by the legacy multi_probe_begin path but + # this command should use the external endstop instead of BD-sensor + # distance reads (QGL/Z_TILT with external endstop and QGL_Tilt_Probe!=0), + # disable rapid_scan now so we fall through to the normal probe path. + if self.rapid_scan: + _cmd = gcmd.get_command() + if (self.mcu_probe.has_external_endstop + and self.mcu_probe.QGL_Tilt_Probe != 0 + and ("QUAD_GANTRY_LEVEL" in _cmd + or "Z_TILT_ADJUST" in _cmd)): + self.rapid_scan = False + self.reactor.update_timer(self.bd_sample_timer, + self.reactor.NEVER) if self.rapid_scan and any( cmd in gcmd.get_command() for cmd in self._RAPID_SCAN_CMDS): idx = self._rapid_scan_idx