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
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
variable `{printer.screws_tilt_adjust.result.screw1.z}` with the
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._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:
pos[2] += self._get_interpolated_z_compensation(
pos[0], self.z_compensations,
zo += self._get_interpolated_z_compensation(
pos.test_x, self.z_compensations,
self.compensation_start_x,
self.compensation_end_x
)
if self.zy_compensations:
pos[2] += self._get_interpolated_z_compensation(
pos[1], self.zy_compensations,
zo += self._get_interpolated_z_compensation(
pos.test_y, self.zy_compensations,
self.compensation_start_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(
self, coord, z_compensations,
comp_start,
@@ -267,7 +273,7 @@ class Calibrater:
# probe the point
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)
self._move_helper((None, None, self.horizontal_move_z))

View File

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

View File

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