diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 528465e64..1d95f6333 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -2295,6 +2295,31 @@ sensor_type: ldc1612 # See the "probe" section for information on these parameters. ``` +### [probe_as_z_home] + +This tool allows the homing procedure (```G28```) to use probing (i.e. the same as the ```PROBE``` command) so that in some cases the Z axis can be more accurately probed. + +You should use this option if you are occasionally experiencing innacurate homing. + +Configuring this tool will also cause the ```G28``` G-Code to emit a console message stating the correction that was performed. You can use this to measure the level of benefit that the tool is providing. + +To enable this tool, add the following configuration section anywhere before either ```[safe_z_homing]``` section or the ```[homing_override]``` section. + +There is no specific G-Code for this command since it simply alters the behavior of the +existing standard 'G28' G-Code command. +See [command reference](G-Codes.md#G-Code_commands) for further information. + +``` +[probe_as_z_home] +#g28_probe_cmd_args: +# Optional command arguments passed to the PROBE command while homing. +# See [g-code PROBE command](G-Codes.md#probe). +# Example: +# g28_probe_cmd_args: SAMPLES=3 SAMPLES_TOLERANCE_RETRIES=5 +``` + +_Note: The standing homing procedure is still perofrmed before probing since this is required to ensure safety and compatibility with the probing method since probing requires homing to first be performed_ + ### [axis_twist_compensation] A tool to compensate for inaccurate probe readings due to twist in X or Y diff --git a/klippy/extras/probe_as_z_home.py b/klippy/extras/probe_as_z_home.py new file mode 100644 index 000000000..73eafc5e6 --- /dev/null +++ b/klippy/extras/probe_as_z_home.py @@ -0,0 +1,101 @@ +# Change Z Homing behavior to use probing instead of the typical G28 homing. +# +# Copyright (C) 2025 Nick Weedon +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import logging +import copy + +class ProbeAsZHome: + def __init__(self, config): + logging.debug("Probe As Home: Initializing") + self.printer = config.get_printer() + self.gcode = self.printer.lookup_object('gcode') + if self.gcode.register_command("G28", None) is not None: + raise config.error("probe_as_z_home must be defined before \ + homing_override or safe_z_homing") + self.printer.load_object(config, 'homing') + self.prev_G28 = self.gcode.register_command("G28", None) + self.gcode.register_command("G28", self.cmd_G28) + self.g28_probe_cmd_args = config.get("g28_probe_cmd_args", None) + + def cmd_G28(self, gcmd): + logging.debug("Probe As Home: Performing G28") + + # We need to pass on the parameters to the original G28 command + # we are 'decorating' but omit the Z axis as this tool will handle it + new_params = copy.deepcopy(gcmd.get_command_parameters()) + # If neither X,Y or Z are specified, then this implies + # that all axes need to be homed. + if all(new_params.get(axis, None) is None for axis in "XYZ"): + new_params['X'] = '0' + new_params['Y'] = '0' + + original_z_param = new_params.pop('Z', None) + + # Perform XY homing if necessary using the original G28 command + if any(new_params.get(axis, None) is not None for axis in "XY"): + g28_gcmd = self.gcode.create_gcode_command("G28", "G28", new_params) + self.prev_G28(g28_gcmd) + + # Perform Z homing using the probe + if original_z_param is not None: + logging.debug("Beggining probe based Z homing") + new_params['Z'] = original_z_param + new_params.pop('X', None) + new_params.pop('Y', None) + g28_gcmd = self.gcode.create_gcode_command("G28", "G28", new_params) + self.prev_G28(g28_gcmd) + + # Probe Z + probe = self.printer.lookup_object('probe', None) + + # Retract before probing since the homing procedure can position + # the toolhead too close to the bed such that BL-Touch based + # probes cannot deploy. + self.toolhead = self.printer.lookup_object('toolhead') + current_pos = self.toolhead.get_position() + + lift_speed = probe.get_probe_params()['lift_speed'] + retract_dist = probe.get_probe_params()['sample_retract_dist'] + + current_pos[2] += retract_dist + self.toolhead.move(current_pos, lift_speed) + + # Parse and pass any arguments defined in the g28_probe_cmd_args + # configuration to the PROBE command. + probe_args_dict = {} + + if self.g28_probe_cmd_args is not None: + probe_arg_tokens = self.g28_probe_cmd_args.split(" ") + probe_arg_tokens = filter(lambda token: token.strip() != "", + probe_arg_tokens) + probe_args_dict = { operand[0]: operand[1] for operand in \ + [arg.split("=") for arg in probe_arg_tokens]} + + probe_gcmd = self.gcode.create_gcode_command( + "PROBE", "PROBE", probe_args_dict) + probe_session = probe.start_probe_session(probe_gcmd) + probe_session.run_probe(probe_gcmd) + probed_pos = probe_session.pull_probed_results()[0] + current_pos = self.toolhead.get_position() + z_error_delta = probed_pos[2] - current_pos[2] + logging.debug("Result of probe: " + repr(probed_pos)) + logging.debug("Toolhead position: " + repr(current_pos)) + gcmd.respond_info("Correcting homed Z by: " + str(z_error_delta)) + + # Set the new position based on the probe result + new_position = current_pos.copy() + new_position[2] = probed_pos[2] + self.toolhead.set_position(new_position) + + # End the probe session only after setting the new position + # in case 'end_probe_session()' moves the toolhead. + probe_session.end_probe_session() + + def get_status(self, eventtime): + return {'probe_as_home': True} + + +def load_config(config): + return ProbeAsZHome(config)