probe: Convert pull_probed_results() to return ProbeResult

Change the low-level probe code to return ProbeResult tuples from
probe_session.pull_probed_results().  Also update callers to use the
calculated bed_xyz values found in the tuple instead of calculating
them from the probe's xyz offsets.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
Kevin O'Connor 2025-12-22 22:17:39 -05:00
parent f33c76da22
commit 2a1027ce41
6 changed files with 63 additions and 46 deletions

View file

@ -8,6 +8,13 @@ All dates in this document are approximate.
## Changes ## Changes
20260109: The g-code console text output from the `PROBE`,
`PROBE_ACCURACY`, and similar commands has changed. Now Z heights are
reported relative to the nominal bed Z position instead of relative to
the probe's configured `z_offset`. Similarly, intermediate probe x and
y console reports will also have the probe's configured `x_offset` and
`y_offset` applied.
20260109: The `[screws_tilt_adjust]` module now reports the status 20260109: The `[screws_tilt_adjust]` module now reports the status
variable `{printer.screws_tilt_adjust.result.screw1.z}` with the variable `{printer.screws_tilt_adjust.result.screw1.z}` with the
probe's `z_offset` applied. That is, one would previously need to probe's `z_offset` applied. That is, one would previously need to

View file

@ -51,21 +51,27 @@ class AxisTwistCompensation:
self.printer.register_event_handler("probe:update_results", self.printer.register_event_handler("probe:update_results",
self._update_z_compensation_value) self._update_z_compensation_value)
def _update_z_compensation_value(self, pos): def _update_z_compensation_value(self, poslist):
pos = poslist[0]
zo = 0.
if self.z_compensations: if self.z_compensations:
pos[2] += self._get_interpolated_z_compensation( zo += self._get_interpolated_z_compensation(
pos[0], self.z_compensations, pos.test_x, self.z_compensations,
self.compensation_start_x, self.compensation_start_x,
self.compensation_end_x self.compensation_end_x
) )
if self.zy_compensations: if self.zy_compensations:
pos[2] += self._get_interpolated_z_compensation( zo += self._get_interpolated_z_compensation(
pos[1], self.zy_compensations, pos.test_y, self.zy_compensations,
self.compensation_start_y, self.compensation_start_y,
self.compensation_end_y self.compensation_end_y
) )
pos = manual_probe.ProbeResult(pos.bed_x, pos.bed_y, pos.bed_z + zo,
pos.test_x, pos.test_y, pos.test_z)
poslist[0] = pos
def _get_interpolated_z_compensation( def _get_interpolated_z_compensation(
self, coord, z_compensations, self, coord, z_compensations,
comp_start, comp_start,
@ -267,7 +273,7 @@ class Calibrater:
# probe the point # probe the point
pos = probe.run_single_probe(self.probe, self.gcmd) pos = probe.run_single_probe(self.probe, self.gcmd)
self.current_measured_z = pos[2] self.current_measured_z = pos.bed_z
# horizontal_move_z (to prevent probe trigger or hitting bed) # horizontal_move_z (to prevent probe trigger or hitting bed)
self._move_helper((None, None, self.horizontal_move_z)) self._move_helper((None, None, self.horizontal_move_z))

View file

@ -1220,10 +1220,6 @@ class RapidScanHelper:
if is_probe_pt: if is_probe_pt:
probe_session.run_probe(gcmd) probe_session.run_probe(gcmd)
results = probe_session.pull_probed_results() results = probe_session.pull_probed_results()
import manual_probe # XXX
results = [manual_probe.ProbeResult(
r[0]+offsets[0], r[1]+offsets[1], r[2]-offsets[2], r[0], r[1], r[2])
for r in results]
toolhead.get_last_move_time() toolhead.get_last_move_time()
self.finalize_callback(results) self.finalize_callback(results)
probe_session.end_probe_session() probe_session.end_probe_session()

View file

@ -562,7 +562,8 @@ class TapSession:
# probe until a single good sample is returned or retries are exhausted # probe until a single good sample is returned or retries are exhausted
def run_probe(self, gcmd): def run_probe(self, gcmd):
epos, is_good = self._tapping_move.run_tap(gcmd) epos, is_good = self._tapping_move.run_tap(gcmd)
self._results.append(epos) res = self._probe_offsets.create_probe_result(epos)
self._results.append(res)
def pull_probed_results(self): def pull_probed_results(self):
res = self._results res = self._results

View file

@ -17,11 +17,12 @@ can travel further (the Z minimum position can be negative).
def calc_probe_z_average(positions, method='average'): def calc_probe_z_average(positions, method='average'):
if method != 'median': if method != 'median':
# Use mean average # Use mean average
count = float(len(positions)) inv_count = 1. / float(len(positions))
return [sum([pos[i] for pos in positions]) / count return manual_probe.ProbeResult(
for i in range(3)] *[sum([pos[i] for pos in positions]) * inv_count
for i in range(len(positions[0]))])
# Use median # Use median
z_sorted = sorted(positions, key=(lambda p: p[2])) z_sorted = sorted(positions, key=(lambda p: p.bed_z))
middle = len(positions) // 2 middle = len(positions) // 2
if (len(positions) & 1) == 1: if (len(positions) & 1) == 1:
# odd number of samples # odd number of samples
@ -52,7 +53,7 @@ class ProbeCommandHelper:
gcode.register_command('PROBE', self.cmd_PROBE, gcode.register_command('PROBE', self.cmd_PROBE,
desc=self.cmd_PROBE_help) desc=self.cmd_PROBE_help)
# PROBE_CALIBRATE command # PROBE_CALIBRATE command
self.probe_calibrate_z = 0. self.probe_calibrate_pos = None
gcode.register_command('PROBE_CALIBRATE', self.cmd_PROBE_CALIBRATE, gcode.register_command('PROBE_CALIBRATE', self.cmd_PROBE_CALIBRATE,
desc=self.cmd_PROBE_CALIBRATE_help) desc=self.cmd_PROBE_CALIBRATE_help)
# Other commands # Other commands
@ -81,12 +82,15 @@ class ProbeCommandHelper:
cmd_PROBE_help = "Probe Z-height at current XY position" cmd_PROBE_help = "Probe Z-height at current XY position"
def cmd_PROBE(self, gcmd): def cmd_PROBE(self, gcmd):
pos = run_single_probe(self.probe, gcmd) pos = run_single_probe(self.probe, gcmd)
gcmd.respond_info("Result is z=%.6f" % (pos[2],)) gcmd.respond_info("Result: at %.3f,%.3f estimate contact at z=%.6f"
self.last_z_result = pos[2] % (pos.bed_x, pos.bed_y, pos.bed_z))
x_offset, y_offset, z_offset = self.probe.get_offsets()
self.last_z_result = pos.bed_z + z_offset # XXX
def probe_calibrate_finalize(self, mpresult): def probe_calibrate_finalize(self, mpresult):
if mpresult is None: if mpresult is None:
return return
z_offset = self.probe_calibrate_z - mpresult.bed_z x_offset, y_offset, z_offset = self.probe.get_offsets()
z_offset += mpresult.bed_z - self.probe_calibrate_pos.bed_z
gcode = self.printer.lookup_object('gcode') gcode = self.printer.lookup_object('gcode')
gcode.respond_info( gcode.respond_info(
"%s: z_offset: %.3f\n" "%s: z_offset: %.3f\n"
@ -99,17 +103,17 @@ class ProbeCommandHelper:
manual_probe.verify_no_manual_probe(self.printer) manual_probe.verify_no_manual_probe(self.printer)
params = self.probe.get_probe_params(gcmd) params = self.probe.get_probe_params(gcmd)
# Perform initial probe # Perform initial probe
curpos = run_single_probe(self.probe, gcmd) pos = run_single_probe(self.probe, gcmd)
# Move away from the bed # Move away from the bed
self.probe_calibrate_z = curpos[2] curpos = self.printer.lookup_object('toolhead').get_position()
curpos[2] += 5. curpos[2] += 5.
self._move(curpos, params['lift_speed']) self._move(curpos, params['lift_speed'])
# Move the nozzle over the probe point # Move the nozzle over the probe point
x_offset, y_offset, z_offset = self.probe.get_offsets() curpos[0] = pos.bed_x
curpos[0] += x_offset curpos[1] = pos.bed_y
curpos[1] += y_offset
self._move(curpos, params['probe_speed']) self._move(curpos, params['probe_speed'])
# Start manual probe # Start manual probe
self.probe_calibrate_pos = pos
manual_probe.ManualProbeHelper(self.printer, gcmd, manual_probe.ManualProbeHelper(self.printer, gcmd,
self.probe_calibrate_finalize) self.probe_calibrate_finalize)
cmd_PROBE_ACCURACY_help = "Probe Z-height accuracy at current XY position" cmd_PROBE_ACCURACY_help = "Probe Z-height accuracy at current XY position"
@ -143,15 +147,15 @@ class ProbeCommandHelper:
positions = probe_session.pull_probed_results() positions = probe_session.pull_probed_results()
probe_session.end_probe_session() probe_session.end_probe_session()
# Calculate maximum, minimum and average values # Calculate maximum, minimum and average values
max_value = max([p[2] for p in positions]) max_value = max([p.bed_z for p in positions])
min_value = min([p[2] for p in positions]) min_value = min([p.bed_z for p in positions])
range_value = max_value - min_value range_value = max_value - min_value
avg_value = calc_probe_z_average(positions, 'average')[2] avg_value = calc_probe_z_average(positions, 'average').bed_z
median = calc_probe_z_average(positions, 'median')[2] median = calc_probe_z_average(positions, 'median').bed_z
# calculate the standard deviation # calculate the standard deviation
deviation_sum = 0 deviation_sum = 0
for i in range(len(positions)): for i in range(len(positions)):
deviation_sum += pow(positions[i][2] - avg_value, 2.) deviation_sum += pow(positions[i].bed_z - avg_value, 2.)
sigma = (deviation_sum / len(positions)) ** 0.5 sigma = (deviation_sum / len(positions)) ** 0.5
# Show information # Show information
gcmd.respond_info( gcmd.respond_info(
@ -260,7 +264,9 @@ class HomingViaProbeHelper:
pos[2] = self.z_min_position pos[2] = self.z_min_position
speed = self.param_helper.get_probe_params(gcmd)['probe_speed'] speed = self.param_helper.get_probe_params(gcmd)['probe_speed']
phoming = self.printer.lookup_object('homing') phoming = self.printer.lookup_object('homing')
self.results.append(phoming.probing_move(self.mcu_probe, pos, speed)) ppos = phoming.probing_move(self.mcu_probe, pos, speed)
res = self.probe_offsets.create_probe_result(ppos)
self.results.append(res)
def pull_probed_results(self): def pull_probed_results(self):
res = self.results res = self.results
self.results = [] self.results = []
@ -368,12 +374,12 @@ class ProbeSessionHelper:
reason += HINT_TIMEOUT reason += HINT_TIMEOUT
raise self.printer.command_error(reason) raise self.printer.command_error(reason)
# Allow axis_twist_compensation to update results # Allow axis_twist_compensation to update results
self.printer.send_event("probe:update_results", epos) self.printer.send_event("probe:update_results", [epos])
# Report results # Report results
gcode = self.printer.lookup_object('gcode') gcode = self.printer.lookup_object('gcode')
gcode.respond_info("probe at %.3f,%.3f is z=%.6f" gcode.respond_info("probe: at %.3f,%.3f bed will contact at z=%.6f"
% (epos[0], epos[1], epos[2])) % (epos.bed_x, epos.bed_y, epos.bed_z))
return epos[:3] return epos
def run_probe(self, gcmd): def run_probe(self, gcmd):
if self.hw_probe_session is None: if self.hw_probe_session is None:
self._probe_state_error() self._probe_state_error()
@ -388,7 +394,7 @@ class ProbeSessionHelper:
pos = self._probe(gcmd) pos = self._probe(gcmd)
positions.append(pos) positions.append(pos)
# Check samples tolerance # Check samples tolerance
z_positions = [p[2] for p in positions] z_positions = [p.bed_z for p in positions]
if max(z_positions)-min(z_positions) > params['samples_tolerance']: if max(z_positions)-min(z_positions) > params['samples_tolerance']:
if retries >= params['samples_tolerance_retries']: if retries >= params['samples_tolerance_retries']:
raise gcmd.error("Probe samples exceed samples_tolerance") raise gcmd.error("Probe samples exceed samples_tolerance")
@ -397,8 +403,9 @@ class ProbeSessionHelper:
positions = [] positions = []
# Retract # Retract
if len(positions) < sample_count: if len(positions) < sample_count:
cur_z = toolhead.get_position()[2]
toolhead.manual_move( toolhead.manual_move(
probexy + [pos[2] + params['sample_retract_dist']], probexy + [cur_z + params['sample_retract_dist']],
params['lift_speed']) params['lift_speed'])
# Calculate result # Calculate result
epos = calc_probe_z_average(positions, params['samples_result']) epos = calc_probe_z_average(positions, params['samples_result'])
@ -416,6 +423,10 @@ class ProbeOffsetsHelper:
self.z_offset = config.getfloat('z_offset') self.z_offset = config.getfloat('z_offset')
def get_offsets(self): def get_offsets(self):
return self.x_offset, self.y_offset, self.z_offset return self.x_offset, self.y_offset, self.z_offset
def create_probe_result(self, test_pos):
return manual_probe.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])
###################################################################### ######################################################################
@ -503,10 +514,6 @@ class ProbePointsHelper:
self._raise_tool(not probe_num) self._raise_tool(not probe_num)
if probe_num >= len(self.probe_points): if probe_num >= len(self.probe_points):
results = probe_session.pull_probed_results() results = probe_session.pull_probed_results()
results = [manual_probe.ProbeResult(
r[0] + self.probe_offsets[0], r[1] + self.probe_offsets[1],
r[2] - self.probe_offsets[2], r[0], r[1], r[2])
for r in results]
done = self._invoke_callback(results) done = self._invoke_callback(results)
if done: if done:
break break

View file

@ -374,11 +374,11 @@ class EddyGatherSamples:
if sensor_z <= -OUT_OF_RANGE or sensor_z >= OUT_OF_RANGE: if sensor_z <= -OUT_OF_RANGE or sensor_z >= OUT_OF_RANGE:
raise self._printer.command_error( raise self._printer.command_error(
"probe_eddy_current sensor not in valid range") "probe_eddy_current sensor not in valid range")
# Callers expect position relative to z_offset, so recalculate res = manual_probe.ProbeResult(
z_offset = self._offsets[2] toolhead_pos[0]+self._offsets[0],
bed_deviation = toolhead_pos[2] - sensor_z toolhead_pos[1]+self._offsets[1], toolhead_pos[2]-sensor_z,
toolhead_pos[2] = z_offset + bed_deviation toolhead_pos[0], toolhead_pos[1], toolhead_pos[2])
results.append(toolhead_pos) results.append(res)
del self._probe_results[:] del self._probe_results[:]
return results return results
def note_probe(self, start_time, end_time, toolhead_pos): def note_probe(self, start_time, end_time, toolhead_pos):
@ -530,7 +530,7 @@ class EddyScanningProbe:
results = self._gather.pull_probed() results = self._gather.pull_probed()
# Allow axis_twist_compensation to update results # Allow axis_twist_compensation to update results
for epos in results: for epos in results:
self._printer.send_event("probe:update_results", epos) self._printer.send_event("probe:update_results", [epos])
return results return results
def end_probe_session(self): def end_probe_session(self):
self._gather.finish() self._gather.finish()