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
* "Start" will initiate the only method available, or ask the user if multiple methods are available.
!!! note
KlipperScreen will home if needed and move to the middle of the bed,
but the location can be configured in [KlipperScreen.conf](https://klipperscreen.readthedocs.io/en/latest/Configuration/#printer-options)
KlipperScreen will automatically Home(`G28`) if needed
* The raise(+) and lower(-) buttons send `TESTZ Z=distance` where distance is selected in the bottom row.
* Accept will send `ACCEPT`
* 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`)
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`)
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`)
Available when the kinematics are defined as delta.
See Klipper documentation: [Delta calibration](https://www.klipper3d.org/Delta_Calibrate.html)
!!! note
KlipperScreen will automatically Home(`G28`) if needed
Klipper documentation: [Delta calibration](https://www.klipper3d.org/Delta_Calibrate.html)

View File

@ -13,21 +13,35 @@ class Panel(ScreenPanel):
def __init__(self, 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()
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'])
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['zoffset'] = Gtk.Label(label="?")
pos = Gtk.Grid(row_homogeneous=True, column_homogeneous=True)
pos.attach(self.widgets['zposition'], 0, 1, 2, 1)
if self.z_offset is not None:
self.widgets['zoffset'] = Gtk.Label(label="?")
pos.attach(Gtk.Label(_("Probe Offset") + ": "), 0, 2, 2, 1)
pos.attach(Gtk.Label(_("Saved")), 0, 3, 1, 1)
pos.attach(Gtk.Label(_("New")), 1, 3, 1, 1)
pos.attach(Gtk.Label(f"{self.z_offset:.3f}"), 0, 4, 1, 1)
if self.probe:
pos.attach(Gtk.Label(label=_("Probe Offset") + ": "), 0, 2, 2, 1)
pos.attach(Gtk.Label(label=_("Saved")), 0, 3, 1, 1)
pos.attach(Gtk.Label(label=_("New")), 1, 3, 1, 1)
pos.attach(Gtk.Label(label=f"{self.z_offset:.3f}"), 0, 4, 1, 1)
pos.attach(self.widgets['zoffset'], 1, 4, 1, 1)
self.buttons = {
'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['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'].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])
self.set_functions()
distgrid = Gtk.Grid()
for j, i in enumerate(self.distances):
@ -107,6 +94,45 @@ class Panel(ScreenPanel):
grid.attach(distances, 0, 2, 3, 1)
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):
popover_button = self._gtk.Button(label=label)
popover_button.connect("clicked", self.start_calibration, method)
@ -127,7 +153,7 @@ class Panel(ScreenPanel):
else:
self._screen._ws.klippy.gcode_script("BED_MESH_CLEAR")
if method == "probe":
self._move_to_position()
self._move_to_position(*self._get_probe_location())
self._screen._ws.klippy.gcode_script("PROBE_CALIBRATE")
elif method == "delta":
self._screen._ws.klippy.gcode_script("DELTA_CALIBRATE")
@ -136,79 +162,61 @@ class Panel(ScreenPanel):
elif method == "endstop":
self._screen._ws.klippy.gcode_script("Z_ENDSTOP_CALIBRATE")
def _move_to_position(self):
x_position = y_position = None
z_hop = speed = None
# Get position from config
def _move_to_position(self, x, y):
if not x or not y:
self._screen.show_popup_message(_("Error: Couldn't get a position to probe"))
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:
x_position = self.ks_printer_cfg.getfloat("calibrate_x_position", None)
y_position = self.ks_printer_cfg.getfloat("calibrate_y_position", None)
x = self.ks_printer_cfg.getfloat("calibrate_x_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 "sample_retract_dist" in self.probe:
z_hop = self.probe['sample_retract_dist']
if "speed" in self.probe:
speed = self.probe['speed']
if self.zero_ref:
logging.debug(f"Using zero reference position: {self.zero_ref}")
return self.zero_ref[0] - self.x_offset, self.zero_ref[1] - self.y_offset
# Use safe_z_home position
if ("safe_z_home" in self._printer.get_config_section_list() and
"Z_ENDSTOP_CALIBRATE" not in self._printer.available_commands):
safe_z = self._printer.get_config_section("safe_z_home")
safe_z_xy = safe_z['home_xy_position']
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']:
return self._get_safe_z()
if "delta" in self._printer.get_config_section("printer")['kinematics']:
logging.info("Detected delta kinematics calibrating at 0,0")
self._screen._ws.klippy.gcode_script('G0 X0 Y0 F3000')
else:
self._calculate_position()
return 0 - self.x_offset, 0 - self.y_offset
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):
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:
xmax = float(self._printer.get_config_section("stepper_x")['position_max'])
ymax = float(self._printer.get_config_section("stepper_y")['position_max'])
mid_x = float(self._printer.get_config_section("stepper_x")['position_max']) / 2
mid_y = float(self._printer.get_config_section("stepper_y")['position_max']) / 2
except KeyError:
logging.error("Couldn't get max position from stepper_x and stepper_y")
return
x_position = xmax / 2
y_position = ymax / 2
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')
return None, None
logging.debug(f"Probe in the center X:{mid_x} Y:{mid_y}")
return mid_x - self.x_offset, mid_y - self.y_offset
def activate(self):
if self._printer.get_stat("manual_probe", "is_active"):
@ -238,8 +246,7 @@ class Panel(ScreenPanel):
def update_position(self, position):
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):
logging.info(f"### Distance {distance}")