zcalibrate: rewrite the functions that get the location to probe due to reported issues

thanks Aubey
This commit is contained in:
alfrix 2024-03-05 00:07:39 -03:00
parent 01193c203c
commit f58cdcb31a
2 changed files with 131 additions and 112 deletions

View File

@ -8,22 +8,37 @@ It's strongly suggested to read Klipper documentation about [Bed level](https://
## Buttons ## Buttons
* "Start" will initiate the only method available, or ask the user if multiple methods are available. * "Start" will initiate the only method available, or ask the user if multiple methods are available.
!!! note !!! note
KlipperScreen will home if needed and move to the middle of the bed, KlipperScreen will automatically Home(`G28`) if needed
but the location can be configured in [KlipperScreen.conf](https://klipperscreen.readthedocs.io/en/latest/Configuration/#printer-options)
* The raise(+) and lower(-) buttons send `TESTZ Z=distance` where distance is selected in the bottom row. * The raise(+) and lower(-) buttons send `TESTZ Z=distance` where distance is selected in the bottom row.
* Accept will send `ACCEPT` * Accept will send `ACCEPT`
* Abort will send `ABORT` * Abort will send `ABORT`
## Calibration methods
### Endstop (`Z_ENDSTOP_CALIBRATE`)
Available when a physical endstop is defined for `[stepper_z]`
See Klipper documentation: [Calibrating a Z endstop](https://www.klipper3d.org/Manual_Level.html#calibrating-a-z-endstop) ## Calibration methods
### Probe (`PROBE_CALIBRATE`) ### Probe (`PROBE_CALIBRATE`)
Available when a probe is defined. (BL-Touch is a probe) Available when a probe is defined. (BL-Touch is a probe)
See Klipper documentation: [Calibrating probe Z offset](https://www.klipper3d.org/Probe_Calibrate.html#calibrating-probe-z-offset) KlipperScreen will try to position the probe in the correct place before sendind a `PROBE_CALIBRATE`
??? info "Search order to select location"
1. `calibrate_x_position` and `calibrate_y_position` in [KlipperScreen.conf](https://klipperscreen.readthedocs.io/en/latest/Configuration/#printer-options)
Both need to be configured, probe offsets are not applied. This is considered an override
2. Probe at the [zero reference position of the mesh](https://www.klipper3d.org/Bed_Mesh.html#configuring-the-zero-reference-position)
3. If `[safe_z_home]` is defined, those values are used. Unless `Z_ENDSTOP_CALIBRATE` is available.
In other words, only use `[safe_z_home]` if `z_virtual_endstop` is used
4. If the kinematics are `delta` probe is placed at 0, 0
5. Probe at the center of the `bed_mesh`
6. Probe at the center of the axes (`position_max` / 2)
Klipper documentation: [Calibrating probe Z offset](https://www.klipper3d.org/Probe_Calibrate.html#calibrating-probe-z-offset)
### Endstop (`Z_ENDSTOP_CALIBRATE`)
Available when a physical endstop is defined for `[stepper_z]`
Klipper documentation: [Calibrating a Z endstop](https://www.klipper3d.org/Manual_Level.html#calibrating-a-z-endstop)
### Bed mesh (`BED_MESH_CALIBRATE`) ### Bed mesh (`BED_MESH_CALIBRATE`)
Available when a probe is not defined and `[bed_mesh]` is defined Available when a probe is not defined and `[bed_mesh]` is defined
@ -38,7 +53,4 @@ this mode lets you create a mesh leveling bed using the paper test in various po
### Delta Automatic/Manual (`DELTA_CALIBRATE`) ### Delta Automatic/Manual (`DELTA_CALIBRATE`)
Available when the kinematics are defined as delta. Available when the kinematics are defined as delta.
See Klipper documentation: [Delta calibration](https://www.klipper3d.org/Delta_Calibrate.html) Klipper documentation: [Delta calibration](https://www.klipper3d.org/Delta_Calibrate.html)
!!! note
KlipperScreen will automatically Home(`G28`) if needed

View File

@ -13,21 +13,35 @@ class Panel(ScreenPanel):
def __init__(self, screen, title): def __init__(self, screen, title):
super().__init__(screen, title) super().__init__(screen, title)
self.z_offset = None self.mesh_min = []
self.mesh_max = []
self.zero_ref = []
self.z_hop_speed = 15.0
self.z_hop = 5.0
self.probe = self._printer.get_probe() self.probe = self._printer.get_probe()
if self.probe: if self.probe:
self.x_offset = float(self.probe['x_offset']) if "x_offset" in self.probe else 0.0
self.y_offset = float(self.probe['y_offset']) if "y_offset" in self.probe else 0.0
self.z_offset = float(self.probe['z_offset']) self.z_offset = float(self.probe['z_offset'])
logging.info(f"Z offset: {self.z_offset}") if "sample_retract_dist" in self.probe:
self.z_hop = float(self.probe['sample_retract_dist'])
if "speed" in self.probe:
self.z_hop_speed = float(self.probe['speed'])
else:
self.x_offset = 0.0
self.y_offset = 0.0
self.z_offset = 0.0
logging.info(f"Offset X:{self.x_offset} Y:{self.y_offset} Z:{self.z_offset}")
self.widgets['zposition'] = Gtk.Label(label="Z: ?") self.widgets['zposition'] = Gtk.Label(label="Z: ?")
self.widgets['zoffset'] = Gtk.Label(label="?")
pos = Gtk.Grid(row_homogeneous=True, column_homogeneous=True) pos = Gtk.Grid(row_homogeneous=True, column_homogeneous=True)
pos.attach(self.widgets['zposition'], 0, 1, 2, 1) pos.attach(self.widgets['zposition'], 0, 1, 2, 1)
if self.z_offset is not None: if self.probe:
self.widgets['zoffset'] = Gtk.Label(label="?") pos.attach(Gtk.Label(label=_("Probe Offset") + ": "), 0, 2, 2, 1)
pos.attach(Gtk.Label(_("Probe Offset") + ": "), 0, 2, 2, 1) pos.attach(Gtk.Label(label=_("Saved")), 0, 3, 1, 1)
pos.attach(Gtk.Label(_("Saved")), 0, 3, 1, 1) pos.attach(Gtk.Label(label=_("New")), 1, 3, 1, 1)
pos.attach(Gtk.Label(_("New")), 1, 3, 1, 1) pos.attach(Gtk.Label(label=f"{self.z_offset:.3f}"), 0, 4, 1, 1)
pos.attach(Gtk.Label(f"{self.z_offset:.3f}"), 0, 4, 1, 1)
pos.attach(self.widgets['zoffset'], 1, 4, 1, 1) pos.attach(self.widgets['zoffset'], 1, 4, 1, 1)
self.buttons = { self.buttons = {
'zpos': self._gtk.Button('z-farther', _("Raise Nozzle"), 'color4'), 'zpos': self._gtk.Button('z-farther', _("Raise Nozzle"), 'color4'),
@ -41,36 +55,9 @@ class Panel(ScreenPanel):
self.buttons['complete'].connect("clicked", self.accept) self.buttons['complete'].connect("clicked", self.accept)
self.buttons['cancel'].connect("clicked", self.abort) self.buttons['cancel'].connect("clicked", self.abort)
functions = []
pobox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
if "Z_ENDSTOP_CALIBRATE" in self._printer.available_commands:
self._add_button("Endstop", "endstop", pobox)
functions.append("endstop")
if "PROBE_CALIBRATE" in self._printer.available_commands:
self._add_button("Probe", "probe", pobox)
functions.append("probe")
if "BED_MESH_CALIBRATE" in self._printer.available_commands and "probe" not in functions:
# This is used to do a manual bed mesh if there is no probe
self._add_button("Bed mesh", "mesh", pobox)
functions.append("mesh")
if "DELTA_CALIBRATE" in self._printer.available_commands:
if "probe" in functions:
self._add_button("Delta Automatic", "delta", pobox)
functions.append("delta")
# Since probes may not be accturate enough for deltas, always show the manual method
self._add_button("Delta Manual", "delta_manual", pobox)
functions.append("delta_manual")
logging.info(f"Available functions for calibration: {functions}")
self.labels['popover'] = Gtk.Popover(position=Gtk.PositionType.BOTTOM) self.labels['popover'] = Gtk.Popover(position=Gtk.PositionType.BOTTOM)
self.labels['popover'].add(pobox)
if len(functions) > 1: self.set_functions()
self.buttons['start'].connect("clicked", self.on_popover_clicked)
else:
self.buttons['start'].connect("clicked", self.start_calibration, functions[0])
distgrid = Gtk.Grid() distgrid = Gtk.Grid()
for j, i in enumerate(self.distances): for j, i in enumerate(self.distances):
@ -107,6 +94,45 @@ class Panel(ScreenPanel):
grid.attach(distances, 0, 2, 3, 1) grid.attach(distances, 0, 2, 3, 1)
self.content.add(grid) self.content.add(grid)
def set_functions(self):
functions = []
pobox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
if "Z_ENDSTOP_CALIBRATE" in self._printer.available_commands:
self._add_button("Endstop", "endstop", pobox)
functions.append("endstop")
if "PROBE_CALIBRATE" in self._printer.available_commands:
self._add_button("Probe", "probe", pobox)
functions.append("probe")
if "BED_MESH_CALIBRATE" in self._printer.available_commands:
self.mesh_min = self._csv_to_array(self._printer.get_config_section("bed_mesh")['mesh_min'])
self.mesh_max = self._csv_to_array(self._printer.get_config_section("bed_mesh")['mesh_max'])
if 'zero_reference_position' in self._printer.get_config_section("bed_mesh"):
self.zero_ref = self._csv_to_array(
self._printer.get_config_section("bed_mesh")['zero_reference_position'])
if "probe" not in functions:
# This is used to do a manual bed mesh if there is no probe
self._add_button("Bed mesh", "mesh", pobox)
functions.append("mesh")
if "DELTA_CALIBRATE" in self._printer.available_commands:
if "probe" in functions:
self._add_button("Delta Automatic", "delta", pobox)
functions.append("delta")
# Since probes may not be accturate enough for deltas, always show the manual method
self._add_button("Delta Manual", "delta_manual", pobox)
functions.append("delta_manual")
self.labels['popover'].add(pobox)
if len(functions) > 1:
self.buttons['start'].connect("clicked", self.on_popover_clicked)
else:
self.buttons['start'].connect("clicked", self.start_calibration, functions[0])
logging.info(f"Available functions for calibration: {functions}")
@staticmethod
def _csv_to_array(string):
return [float(i.strip()) for i in string.split(',')]
def _add_button(self, label, method, pobox): def _add_button(self, label, method, pobox):
popover_button = self._gtk.Button(label=label) popover_button = self._gtk.Button(label=label)
popover_button.connect("clicked", self.start_calibration, method) popover_button.connect("clicked", self.start_calibration, method)
@ -127,7 +153,7 @@ class Panel(ScreenPanel):
else: else:
self._screen._ws.klippy.gcode_script("BED_MESH_CLEAR") self._screen._ws.klippy.gcode_script("BED_MESH_CLEAR")
if method == "probe": if method == "probe":
self._move_to_position() self._move_to_position(*self._get_probe_location())
self._screen._ws.klippy.gcode_script("PROBE_CALIBRATE") self._screen._ws.klippy.gcode_script("PROBE_CALIBRATE")
elif method == "delta": elif method == "delta":
self._screen._ws.klippy.gcode_script("DELTA_CALIBRATE") self._screen._ws.klippy.gcode_script("DELTA_CALIBRATE")
@ -136,79 +162,61 @@ class Panel(ScreenPanel):
elif method == "endstop": elif method == "endstop":
self._screen._ws.klippy.gcode_script("Z_ENDSTOP_CALIBRATE") self._screen._ws.klippy.gcode_script("Z_ENDSTOP_CALIBRATE")
def _move_to_position(self): def _move_to_position(self, x, y):
x_position = y_position = None if not x or not y:
z_hop = speed = None self._screen.show_popup_message(_("Error: Couldn't get a position to probe"))
# Get position from config return
logging.info(f"Lifting Z: {self.z_hop}mm {self.z_hop_speed}mm/s")
self._screen._ws.klippy.gcode_script(f"G91\nG0 Z{self.z_hop} F{self.z_hop_speed * 60}")
logging.info(f"Moving to X:{x} Y:{y}")
self._screen._ws.klippy.gcode_script(f'G90\nG0 X{x} Y{y} F3000')
def _get_probe_location(self):
if self.ks_printer_cfg is not None: if self.ks_printer_cfg is not None:
x_position = self.ks_printer_cfg.getfloat("calibrate_x_position", None) x = self.ks_printer_cfg.getfloat("calibrate_x_position", None)
y_position = self.ks_printer_cfg.getfloat("calibrate_y_position", None) y = self.ks_printer_cfg.getfloat("calibrate_y_position", None)
if x and y:
logging.debug(f"Using KS configured position: {x}, {y}")
return x, y
if self.probe: if self.zero_ref:
if "sample_retract_dist" in self.probe: logging.debug(f"Using zero reference position: {self.zero_ref}")
z_hop = self.probe['sample_retract_dist'] return self.zero_ref[0] - self.x_offset, self.zero_ref[1] - self.y_offset
if "speed" in self.probe:
speed = self.probe['speed']
# Use safe_z_home position
if ("safe_z_home" in self._printer.get_config_section_list() and if ("safe_z_home" in self._printer.get_config_section_list() and
"Z_ENDSTOP_CALIBRATE" not in self._printer.available_commands): "Z_ENDSTOP_CALIBRATE" not in self._printer.available_commands):
safe_z = self._printer.get_config_section("safe_z_home") return self._get_safe_z()
safe_z_xy = safe_z['home_xy_position'] if "delta" in self._printer.get_config_section("printer")['kinematics']:
safe_z_xy = [str(i.strip()) for i in safe_z_xy.split(',')]
if x_position is None:
x_position = float(safe_z_xy[0])
logging.debug(f"Using safe_z x:{x_position}")
if y_position is None:
y_position = float(safe_z_xy[1])
logging.debug(f"Using safe_z y:{y_position}")
if 'z_hop' in safe_z:
z_hop = safe_z['z_hop']
if 'z_hop_speed' in safe_z:
speed = safe_z['z_hop_speed']
speed = 15 if speed is None else speed
z_hop = 5 if z_hop is None else z_hop
self._screen._ws.klippy.gcode_script(f"G91\nG0 Z{z_hop} F{float(speed) * 60}")
if self._printer.get_stat("gcode_move", "absolute_coordinates"):
self._screen._ws.klippy.gcode_script("G90")
if x_position is not None and y_position is not None:
logging.debug(f"Configured probing position X: {x_position} Y: {y_position}")
self._screen._ws.klippy.gcode_script(f'G0 X{x_position} Y{y_position} F3000')
elif "delta" in self._printer.get_config_section("printer")['kinematics']:
logging.info("Detected delta kinematics calibrating at 0,0") logging.info("Detected delta kinematics calibrating at 0,0")
self._screen._ws.klippy.gcode_script('G0 X0 Y0 F3000') return 0 - self.x_offset, 0 - self.y_offset
else:
self._calculate_position() x, y = self._calculate_position()
return x, y
def _get_safe_z(self):
safe_z = self._printer.get_config_section("safe_z_home")
safe_z_xy = self._csv_to_array(safe_z['home_xy_position'])
logging.debug(f"Using safe_z {safe_z_xy[0]}, {safe_z_xy[1]}")
if 'z_hop' in safe_z:
self.z_hop = float(safe_z['z_hop'])
if 'z_hop_speed' in safe_z:
self.z_hop_speed = float(safe_z['z_hop_speed'])
return safe_z_xy[0], safe_z_xy[1]
def _calculate_position(self): def _calculate_position(self):
logging.debug("Position not configured, probing the middle of the bed") if self.mesh_max and self.mesh_min:
mesh_mid_x = (self.mesh_min[0] + self.mesh_max[0]) / 2
mesh_mid_y = (self.mesh_min[1] + self.mesh_max[1]) / 2
logging.debug(f"Probe in the mesh center X:{mesh_mid_x} Y:{mesh_mid_y}")
return mesh_mid_x - self.x_offset, mesh_mid_y - self.y_offset
try: try:
xmax = float(self._printer.get_config_section("stepper_x")['position_max']) mid_x = float(self._printer.get_config_section("stepper_x")['position_max']) / 2
ymax = float(self._printer.get_config_section("stepper_y")['position_max']) mid_y = float(self._printer.get_config_section("stepper_y")['position_max']) / 2
except KeyError: except KeyError:
logging.error("Couldn't get max position from stepper_x and stepper_y") logging.error("Couldn't get max position from stepper_x and stepper_y")
return return None, None
x_position = xmax / 2 logging.debug(f"Probe in the center X:{mid_x} Y:{mid_y}")
y_position = ymax / 2 return mid_x - self.x_offset, mid_y - self.y_offset
logging.info(f"Center position X:{x_position} Y:{y_position}")
# Find probe offset
x_offset = y_offset = None
if self.probe:
if "x_offset" in self.probe:
x_offset = float(self.probe['x_offset'])
if "y_offset" in self.probe:
y_offset = float(self.probe['y_offset'])
logging.info(f"Offset X:{x_offset} Y:{y_offset}")
if x_offset is not None:
x_position = x_position - x_offset
if y_offset is not None:
y_position = y_position - y_offset
logging.info(f"Moving to X:{x_position} Y:{y_position}")
self._screen._ws.klippy.gcode_script(f'G0 X{x_position} Y{y_position} F3000')
def activate(self): def activate(self):
if self._printer.get_stat("manual_probe", "is_active"): if self._printer.get_stat("manual_probe", "is_active"):
@ -238,8 +246,7 @@ class Panel(ScreenPanel):
def update_position(self, position): def update_position(self, position):
self.widgets['zposition'].set_text(f"Z: {position[2]:.3f}") self.widgets['zposition'].set_text(f"Z: {position[2]:.3f}")
if self.z_offset is not None: self.widgets['zoffset'].set_text(f"{abs(position[2] - self.z_offset):.3f}")
self.widgets['zoffset'].set_text(f"{abs(position[2] - self.z_offset):.3f}")
def change_distance(self, widget, distance): def change_distance(self, widget, distance):
logging.info(f"### Distance {distance}") logging.info(f"### Distance {distance}")