diff --git a/docs/Configuration.md b/docs/Configuration.md index 154ff944..e60a0175 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -78,14 +78,6 @@ moonraker_port: 7125 # calibrate_x_position: 100 # calibrate_y_position: 100 - -# Bed Screws -# define the screw positons required for odd number of screws in a comma separated list (CSV) -# possible values are: bl, br, bm, fl, fr, fm, lm, rm, center -# they correspond to back-left, back-right, back-middle, front-left, front-right, front-middle, left-middle, right-middle -# example: -# screw_positions: bl, br, fm - # Rotation is useful if the screen is not directly in front of the machine. # It will affect the bed mesh visualization. # Valid values are 0 90 180 270 diff --git a/docs/Panels/Screws.md b/docs/Panels/Screws.md index 0dc0c299..75f32651 100644 --- a/docs/Panels/Screws.md +++ b/docs/Panels/Screws.md @@ -20,24 +20,33 @@ and reports the amount to be adjusted into the labels of the corner buttons. ### Why `[bed_screws]` are ignored/not used? -Because if the probe offset is changed or the difference between `[bed_screws]` and `[screws_tilt_adjust]` wasn't calculated correctly, -the "Screws adjust" button stops working. - +`[screws_tilt_adjust]` positions with offset will be used over `[bed_screws]`, because if the probe offset is changed or +the difference between `[bed_screws]` and `[screws_tilt_adjust]` wasn't calculated correctly, +the "Screws adjust" button stops working correctly. ### Why the probe offset is applied to `[screws_tilt_adjust]`? -Because the corner buttons in KlipperScreen should place the nozzle above the screw to do the ["paper test"](https://www.klipper3d.org/Bed_Level.html?h=paper#the-paper-test). It doesn't affect the function of `SCREWS_TILT_CALCULATE`, which will go to the defined positions. +Because the corner buttons in KlipperScreen should place the nozzle above the screw to do the ["paper test"](https://www.klipper3d.org/Bed_Level.html?h=paper#the-paper-test). +It doesn't affect the function of `SCREWS_TILT_CALCULATE`, which will go to the defined positions. -## Not supported for auto-detection +## Is `BED_SCREWS_ADJUST` supported? -This message will appear if you are using 3 or 5 screws and didn't define them in KlipperScreen.conf. -See "screw_positions" in the [printer options](https://klipperscreen.readthedocs.io/en/latest/Configuration/#printer-options) +No, but you can achieve the same thing by using the corner buttons and going in a circle pattern adjusting until desired. +`BED_SCREWS_ADJUST` is just a helper that moves to the screws in sequence ## Limitations -The panel doesn't support more than 9 screws. if there is a center screw define the positions to avoid issues. -See "screw_positions" in the [printer options](https://klipperscreen.readthedocs.io/en/latest/Configuration/#printer-options) +The panel doesn't support more than 9 screws or grids bigger than 3x3. +## Rotation + +The layout can be rotated if the screen is not directly in front of the machine. +in the printer configuration you would add screw_rotation: 90 +Valid values are 0 90 180 270 + +## Inversion + +The layout will be inverted if an axis is inverted in the move panel ## Is there an alternative? diff --git a/panels/bed_level.py b/panels/bed_level.py index 8b37011a..9cb16e27 100644 --- a/panels/bed_level.py +++ b/panels/bed_level.py @@ -13,7 +13,7 @@ from ks_includes.screen_panel import ScreenPanel # but return None if the distance is above max_distance. # If remove is set to true, the screw is also removed # from the list of passed in screws. -def find_closest(screws, point, max_distance, remove=False): +def find_closest(screws, point, max_distance): if len(screws) == 0: return None closest = screws[0] @@ -26,9 +26,7 @@ def find_closest(screws, point, max_distance, remove=False): if min_distance > max_distance: return None - - if remove: - screws.remove(closest) + screws.remove(closest) return closest @@ -36,19 +34,18 @@ class Panel(ScreenPanel): def __init__(self, screen, title): super().__init__(screen, title) - self.screw_dict = {} + self.screw_positions = {} self.screws = [] - self.y_cnt = 0 - self.x_cnt = 0 self.x_offset = 0 self.y_offset = 0 self.buttons = {'dm': self._gtk.Button("motor-off", _("Disable Motors"), "color3")} self.buttons['dm'].connect("clicked", self.disable_motors) - rotation = None + rotation = 0 self.probe_z_height = 0 self.lift_speed = 5 self.horizontal_move_z = 5 self.horizontal_speed = 50 + invert_x = invert_y = False grid = Gtk.Grid(row_homogeneous=True, column_homogeneous=True) grid.attach(self.buttons['dm'], 0, 0, 1, 1) @@ -82,39 +79,22 @@ class Panel(ScreenPanel): self.screws = self._get_screws("bed_screws") logging.info(f"bed_screws: {self.screws}") - nscrews = len(self.screws) # KS config - valid_positions = True - valid_screws = ["bl", "fl", "fr", "br", "bm", "fm", "lm", "rm", "center"] if self.ks_printer_cfg is not None: - screw_positions = self.ks_printer_cfg.get("screw_positions", "") - if screw_positions: - screw_positions = [str(i.strip()) for i in screw_positions.split(',')] - logging.info(f"Positions: {screw_positions}") - for screw in screw_positions: - if screw not in valid_screws: - logging.error(f"Unknown screw: {screw}") - self._screen.show_popup_message(_("Unknown screw position") + f": {screw}") - valid_positions = False - if not (3 <= len(screw_positions) <= 9): - valid_positions = False - else: - if nscrews in (3, 5, 7): - valid_positions = False - screw_positions = valid_screws rotation = self.ks_printer_cfg.getint("screw_rotation", 0) + if rotation not in (0, 90, 180, 270): + self._screen.show_popup_message(_("Rotation invalid") + f" {rotation} \n") + logging.info(f"Rotation invalid: {rotation}") + rotation = 0 logging.info(f"Rotation: {rotation}") - else: - if nscrews in (3, 5, 7): - valid_positions = False - screw_positions = valid_screws + invert_x = self._config.get_config()['main'].getboolean("invert_x", False) + invert_y = self._config.get_config()['main'].getboolean("invert_y", False) + logging.info(f"Inversion X: {invert_x} Y: {invert_y}") # get dimensions x_positions = {x[0] for x in self.screws} y_positions = {y[1] for y in self.screws} logging.info(f"X: {x_positions}\nY: {y_positions}") - self.x_cnt = len(x_positions) - self.y_cnt = len(y_positions) min_x = min(x_positions) max_x = max(x_positions) @@ -124,32 +104,49 @@ class Panel(ScreenPanel): max_y = max(y_positions) mid_y = round((min_y + max_y) / 2) - max_distance = math.ceil( - math.hypot(max_x - min_x, max_y - min_y) - / min(self.x_cnt, self.y_cnt, 3) - ) + max_distance = math.floor(min(max_x - min_x, max_y - min_y) / 3) logging.debug(f"Using max_distance: {max_distance} to fit: {len(self.screws)} screws.") remaining_screws = self.screws[:] - fl = find_closest(remaining_screws, (min_x, min_y), max_distance, remove="fl" in screw_positions) - bl = find_closest(remaining_screws, (min_x, max_y), max_distance, remove="bl" in screw_positions) - br = find_closest(remaining_screws, (max_x, max_y), max_distance, remove="br" in screw_positions) - fr = find_closest(remaining_screws, (max_x, min_y), max_distance, remove="fr" in screw_positions) + # The order here it's important because the rotation function will + # shift the values according to the angle of rotation + self.screw_positions = { + 'bl': find_closest(remaining_screws, (min_x, max_y), max_distance), + 'fm': find_closest(remaining_screws, (mid_x, min_y), max_distance), + 'br': find_closest(remaining_screws, (max_x, max_y), max_distance), + 'lm': find_closest(remaining_screws, (min_x, mid_y), max_distance), + 'fr': find_closest(remaining_screws, (max_x, min_y), max_distance), + 'bm': find_closest(remaining_screws, (mid_x, max_y), max_distance), + 'fl': find_closest(remaining_screws, (min_x, min_y), max_distance), + 'rm': find_closest(remaining_screws, (max_x, mid_y), max_distance), + } - fm = find_closest(remaining_screws, (mid_x, min_y), max_distance, remove="fm" in screw_positions) - bm = find_closest(remaining_screws, (mid_x, max_y), max_distance, remove="bm" in screw_positions) + if invert_x and invert_y: + rotation = (rotation + 180) % 360 + invert_x = invert_y = False + if rotation != 0: + self.screw_positions = self.map_rotation(self.screw_positions, rotation) + logging.info(f"Rotated: {rotation}") + if invert_x or invert_y: + self.screw_positions = self.map_invert(self.screw_positions, invert_x, invert_y) - lm = find_closest(remaining_screws, (min_x, mid_y), max_distance, remove="lm" in screw_positions) - rm = find_closest(remaining_screws, (max_x, mid_y), max_distance, remove="rm" in screw_positions) - - center = find_closest(remaining_screws, (mid_x, mid_y), max_distance, remove="center" in screw_positions) + self.screw_positions['center'] = find_closest(remaining_screws, (mid_x, mid_y), max_distance) if len(remaining_screws) != 0: + found = [] + for pos in self.screw_positions: + if self.screw_positions[pos]: + found.append(pos) + logging.debug(f"Found: {found}") logging.debug(f"Screws not used: {remaining_screws}") - self._screen.show_popup_message(f"Screws not used: {remaining_screws} \n" - f"It's possible that the configuration is not correct", 2) + if len(self.screws) > 9: + error_msg = _("This panel supports up-to 9 screws in a 3x3 Grid") + else: + error_msg = _("It's possible that the configuration is not correct") + self._screen.show_popup_message(_("Screws not used:") + f" {remaining_screws} \n" + + error_msg, 2) logging.debug(f"Using {len(self.screws) - len(remaining_screws)}/{len(self.screws)}-screw locations") @@ -165,138 +162,77 @@ class Panel(ScreenPanel): self.buttons['bm'] = self._gtk.Button("bed-level-t-m", scale=button_scale) self.buttons['center'] = self._gtk.Button("bed-level-center", scale=button_scale) + screw_layout_map = { + 'fr': [3, 2, 1, 1], + 'fm': [2, 2, 1, 1], + 'fl': [1, 2, 1, 1], + 'rm': [3, 1, 1, 1], + 'br': [3, 0, 1, 1], + 'bm': [2, 0, 1, 1], + 'bl': [1, 0, 1, 1], + 'lm': [1, 1, 1, 1], + 'center': [2, 1, 1, 1], + } + bedgrid = Gtk.Grid() + for pos in screw_layout_map: + bedgrid.attach(self.buttons[pos], *screw_layout_map[pos]) + self.buttons[pos].set_no_show_all(True) + if pos in self.screw_positions and self.screw_positions[pos]: + self.buttons[pos].show() - if valid_positions: - if "bl" in screw_positions and bl: - bedgrid.attach(self.buttons['bl'], 1, 0, 1, 1) - if "fl" in screw_positions and fl: - bedgrid.attach(self.buttons['fl'], 1, 2, 1, 1) - if "fr" in screw_positions and fr: - bedgrid.attach(self.buttons['fr'], 3, 2, 1, 1) - if "br" in screw_positions and br: - bedgrid.attach(self.buttons['br'], 3, 0, 1, 1) - if "bm" in screw_positions and bm: - bedgrid.attach(self.buttons['bm'], 2, 0, 1, 1) - if "fm" in screw_positions and fm: - bedgrid.attach(self.buttons['fm'], 2, 2, 1, 1) - if "lm" in screw_positions and lm: - bedgrid.attach(self.buttons['lm'], 1, 1, 1, 1) - if "rm" in screw_positions and rm: - bedgrid.attach(self.buttons['rm'], 3, 1, 1, 1) - if "center" in screw_positions and center: - bedgrid.attach(self.buttons['center'], 2, 1, 1, 1) - self.buttons['center'].connect("clicked", self.go_to_position, center) - else: - label = Gtk.Label(wrap=True, wrap_mode=Pango.WrapMode.WORD_CHAR) - label.set_text( - _("Bed screw configuration:") + f" {nscrews}\n\n" - + _("Not supported for auto-detection, it needs to be configured in klipperscreen.conf") - ) - grid.attach(label, 1, 0, 3, 2) - self.content.add(grid) - return + for layout_pos in self.screw_positions: + self.buttons[layout_pos].connect("clicked", self.go_to_position, self.screw_positions[layout_pos]) - if rotation == 90: - # fl lm bl - # fm bm - # fr rm br - - self.buttons['bl'].connect("clicked", self.go_to_position, fl) - self.buttons['bm'].connect("clicked", self.go_to_position, lm) - self.buttons['br'].connect("clicked", self.go_to_position, bl) - self.buttons['rm'].connect("clicked", self.go_to_position, bm) - self.buttons['fr'].connect("clicked", self.go_to_position, br) - self.buttons['fm'].connect("clicked", self.go_to_position, rm) - self.buttons['fl'].connect("clicked", self.go_to_position, fr) - self.buttons['lm'].connect("clicked", self.go_to_position, fm) - self.screw_dict = { - 'bl': fl, - 'bm': lm, - 'br': bl, - 'rm': bm, - 'fr': br, - 'fm': rm, - 'fl': fr, - 'lm': fm - } - elif rotation == 180: - # fr fm fl - # rm lm - # br bm bl - self.buttons['bl'].connect("clicked", self.go_to_position, fr) - self.buttons['bm'].connect("clicked", self.go_to_position, fm) - self.buttons['br'].connect("clicked", self.go_to_position, fl) - self.buttons['rm'].connect("clicked", self.go_to_position, lm) - self.buttons['fr'].connect("clicked", self.go_to_position, bl) - self.buttons['fm'].connect("clicked", self.go_to_position, bm) - self.buttons['fl'].connect("clicked", self.go_to_position, br) - self.buttons['lm'].connect("clicked", self.go_to_position, rm) - self.screw_dict = { - 'bl': fr, - 'bm': fm, - 'br': fl, - 'rm': lm, - 'fr': bl, - 'fm': bm, - 'fl': br, - 'lm': rm - } - elif rotation == 270: - # br rm fr - # bm fm - # bl lm fl - self.buttons['bl'].connect("clicked", self.go_to_position, br) - self.buttons['bm'].connect("clicked", self.go_to_position, rm) - self.buttons['br'].connect("clicked", self.go_to_position, fr) - self.buttons['rm'].connect("clicked", self.go_to_position, fm) - self.buttons['fr'].connect("clicked", self.go_to_position, fl) - self.buttons['fm'].connect("clicked", self.go_to_position, lm) - self.buttons['fl'].connect("clicked", self.go_to_position, bl) - self.buttons['lm'].connect("clicked", self.go_to_position, bm) - self.screw_dict = { - 'bl': br, - 'bm': rm, - 'br': fr, - 'rm': fm, - 'fr': fl, - 'fm': lm, - 'fl': bl, - 'lm': bm - } - else: - # bl bm br - # lm rm - # fl fm fr - self.buttons['bl'].connect("clicked", self.go_to_position, bl) - self.buttons['bm'].connect("clicked", self.go_to_position, bm) - self.buttons['br'].connect("clicked", self.go_to_position, br) - self.buttons['rm'].connect("clicked", self.go_to_position, rm) - self.buttons['fr'].connect("clicked", self.go_to_position, fr) - self.buttons['fm'].connect("clicked", self.go_to_position, fm) - self.buttons['fl'].connect("clicked", self.go_to_position, fl) - self.buttons['lm'].connect("clicked", self.go_to_position, lm) - self.screw_dict = { - 'bl': bl, - 'bm': bm, - 'br': br, - 'rm': rm, - 'fr': fr, - 'fm': fm, - 'fl': fl, - 'lm': lm - } - self.screw_dict['center'] = center remove_list = [] - for screw in self.screw_dict: - if screw not in screw_positions: + for screw in self.screw_positions: + if self.screw_positions[screw] is None: remove_list.append(screw) for screw in remove_list: - self.screw_dict.pop(screw) + self.screw_positions.pop(screw) + + logging.info(f"screw_positions: {self.screw_positions}") grid.attach(bedgrid, 1, 0, 3, 2) self.content.add(grid) + @staticmethod + def map_invert(positions, invert_x, invert_y): + if invert_x: + return { + 'fr': positions['fl'], + 'fm': positions['fm'], + 'fl': positions['fr'], + 'rm': positions['lm'], + 'bl': positions['br'], + 'bm': positions['bm'], + 'br': positions['bl'], + 'lm': positions['rm'] + } + if invert_y: + return { + 'fr': positions['br'], + 'fm': positions['bm'], + 'fl': positions['bl'], + 'rm': positions['rm'], + 'bl': positions['fl'], + 'bm': positions['fm'], + 'br': positions['fr'], + 'lm': positions['lm'] + } + return positions + + @staticmethod + def map_rotation(positions, angle): + angle %= 360 + shift = (angle // 90) * 2 + rotated_positions = {} + keys = list(positions.keys()) + for i, key in enumerate(keys): + new_key = keys[(i + shift) % len(keys)] + rotated_positions[new_key] = positions[key] + return rotated_positions + def home(self): # Test if all axes have been homed. Home if necessary. if self._printer.get_stat("toolhead", "homed_axes") != "xyz": @@ -344,7 +280,7 @@ class Panel(ScreenPanel): x, y = section[screw].split(',') x = float(x) + self.x_offset y = float(y) + self.y_offset - for key, value in self.screw_dict.items(): + for key, value in self.screw_positions.items(): if value and x == value[0] and y == value[1]: logging.debug(f"X: {x} Y: {y} Adjust: {result['adjust']} Pos: {key}") if result['is_base']: diff --git a/panels/move.py b/panels/move.py index 0263c9ae..5faa15ad 100644 --- a/panels/move.py +++ b/panels/move.py @@ -121,8 +121,10 @@ class Panel(ScreenPanel): max_z_velocity = max_velocity configurable_options = [ - {"invert_x": {"section": "main", "name": _("Invert X"), "type": "binary", "value": "False"}}, - {"invert_y": {"section": "main", "name": _("Invert Y"), "type": "binary", "value": "False"}}, + {"invert_x": {"section": "main", "name": _("Invert X"), "type": "binary", "value": "False", + "callback": self.reinit_screw_panel}}, + {"invert_y": {"section": "main", "name": _("Invert Y"), "type": "binary", "value": "False", + "callback": self.reinit_screw_panel}}, {"invert_z": {"section": "main", "name": _("Invert Z"), "type": "binary", "value": "False"}}, {"move_speed_xy": { "section": "main", "name": _("XY Speed (mm/s)"), "type": "scale", "value": "50", @@ -140,6 +142,10 @@ class Panel(ScreenPanel): name = list(option)[0] self.options.update(self.add_option('options', self.settings, name, option[name])) + def reinit_screw_panel(self, value): + logging.info(self._screen.panels) + self._screen.panels_reinit.append("bed_level") + def process_update(self, action, data): if action != "notify_status_update": return