diff --git a/ks_includes/KlipperScreen.conf b/ks_includes/KlipperScreen.conf index cc0f800d..767ed945 100644 --- a/ks_includes/KlipperScreen.conf +++ b/ks_includes/KlipperScreen.conf @@ -98,10 +98,17 @@ name: Bed Level icon: bed-level panel: bed_level +[menu __main config bedmesh] +name: Bed Mesh +icon: bed-level +panel: bed_mesh +enable: {{ printer.bed_mesh is defined }} + [menu __main config zoffset] name: Z Calibrate icon: z-offset-increase panel: zcalibrate +enable: {{ ((printer.bltouch is defined) or (printer.probe is defined)) }} [menu __main config network] name: Network diff --git a/ks_includes/KlippyGcodes.py b/ks_includes/KlippyGcodes.py index 3fdb47bb..1a084f06 100644 --- a/ks_includes/KlippyGcodes.py +++ b/ks_includes/KlippyGcodes.py @@ -34,16 +34,16 @@ class KlippyGcodes: @staticmethod def set_bed_temp(temp): - return KlippyGcodes.SET_BED_TEMP + " S" + str(temp) + return "%s S%s" % (KlippyGcodes.SET_BED_TEMP, str(temp)) @staticmethod def set_ext_temp(temp, tool=0): - return KlippyGcodes.SET_EXT_TEMP + " T" + str(tool) + " S" + str(temp) + return "%s T%s S%s" % (KlippyGcodes.SET_EXT_TEMP, str(tool), str(temp)) @staticmethod def set_fan_speed(speed): speed = str( int(float(int(speed) % 101)/100*255) ) - return KlippyGcodes.SET_FAN_SPEED + " S"+ speed + return "%s S%s" % (KlippyGcodes.SET_FAN_SPEED, speed) @staticmethod def set_extrusion_rate(rate): @@ -59,4 +59,12 @@ class KlippyGcodes: @staticmethod def extrude(dist, speed=500): - return KlippyGcodes.MOVE + " E" + dist + " F" + speed + return "%s E%s F%s" % (KlippyGcodes.MOVE, dist, speed) + + @staticmethod + def bed_mesh_load(profile): + return "BED_MESH_PROFILE LOAD=%s" % profile + + @staticmethod + def bed_mesh_save(profile): + return "BED_MESH_PROFILE SAVE=%s" % profile diff --git a/ks_includes/printer.py b/ks_includes/printer.py index a1d049ab..2bc61792 100644 --- a/ks_includes/printer.py +++ b/ks_includes/printer.py @@ -40,6 +40,15 @@ class Printer: "temperature": 0, "target": 0 } + if x.startswith('bed_mesh '): + r = self.config[x] + r['x_count'] = int(r['x_count']) + r['y_count'] = int(r['y_count']) + r['max_x'] = float(r['max_x']) + r['min_x'] = float(r['min_x']) + r['max_y'] = float(r['max_y']) + r['min_y'] = float(r['min_y']) + r['points'] = [[float(j.strip()) for j in i.split(",")] for i in r['points'].strip().split("\n")] self.process_update(data) logger.info("Klipper version: %s", self.klipper['version']) @@ -57,8 +66,16 @@ class Printer: logger.debug("Power devices: %s" % self.power_devices) def process_update(self, data): - keys = ['virtual_sdcard','pause_resume','idle_timeout','print_stats'] - keys = ['fan','gcode_move','idle_timeout','pause_resume','print_stats','toolhead','virtual_sdcard'] + keys = [ + 'bed_mesh', + 'fan', + 'gcode_move', + 'idle_timeout', + 'pause_resume', + 'print_stats', + 'toolhead', + 'virtual_sdcard' + ] for x in keys: if x in data: if x not in self.data: diff --git a/panels/bed_mesh.py b/panels/bed_mesh.py new file mode 100644 index 00000000..a88a5c41 --- /dev/null +++ b/panels/bed_mesh.py @@ -0,0 +1,183 @@ +import gi +import logging + +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk, Gdk, GLib, Pango + +from ks_includes.KlippyGtk import KlippyGtk +from ks_includes.KlippyGcodes import KlippyGcodes +from ks_includes.screen_panel import ScreenPanel + +logger = logging.getLogger("KlipperScreen.BedMeshPanel") + +def create_panel(*args): + return BedMeshPanel(*args) + +class BedMeshPanel(ScreenPanel): + active_mesh = None + graphs = {} + + def initialize(self, panel_name): + _ = self.lang.gettext + + scroll = Gtk.ScrolledWindow() + scroll.set_property("overlay-scrolling", False) + scroll.set_vexpand(True) + + # Create a grid for all profiles + self.labels['profiles'] = Gtk.Grid() + scroll.add(self.labels['profiles']) + + # Create a box to contain all of the above + box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) + box.set_vexpand(True) + box.pack_start(scroll, True, True, 0) + + self.load_meshes() + + self.content.add(box) + self._screen.add_subscription(panel_name) + + def activate(self): + am = self._screen.printer.get_stat("bed_mesh","profile_name") + self.activate_mesh(am) + + def activate_mesh(self, profile): + if profile == "": + profile = None + + logger.debug("Activating profile: %s %s" % (self.active_mesh, profile)) + if profile != self.active_mesh: + if self.active_mesh != None: + a = self.profiles[self.active_mesh] + a['buttons'].remove(a['refresh']) + a['buttons'].pack_start(a['load'], False, False, 0) + self.active_mesh = profile + if self.active_mesh != None: + a = self.profiles[profile] + a['buttons'].remove(a['load']) + a['buttons'].pack_start(a['refresh'], False, False, 0) + self._screen.show_all() + + def add_profile(self, profile): + frame = Gtk.Frame() + frame.set_property("shadow-type",Gtk.ShadowType.NONE) + + name = Gtk.Label() + name.set_markup("%s" % (profile)) + name.set_hexpand(True) + name.set_vexpand(True) + name.set_halign(Gtk.Align.START) + name.set_line_wrap(True) + name.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR) + + load = KlippyGtk.ButtonImage("load",None,"color2") + load.connect("clicked", self.send_load_mesh, profile) + load.set_size_request(60,0) + load.set_hexpand(False) + load.set_halign(Gtk.Align.END) + + refresh = KlippyGtk.ButtonImage("refresh",None,"color4") + refresh.connect("clicked", self.calibrate_mesh) + refresh.set_size_request(60,0) + refresh.set_hexpand(False) + refresh.set_halign(Gtk.Align.END) + + info = KlippyGtk.ButtonImage("info",None,"color3") + info.connect("clicked", self.show_mesh, profile) + info.set_size_request(60,0) + info.set_hexpand(False) + info.set_halign(Gtk.Align.END) + + save = KlippyGtk.ButtonImage("sd",None,"color3") + save.connect("clicked", self.send_save_mesh, profile) + save.set_size_request(60,0) + save.set_hexpand(False) + save.set_halign(Gtk.Align.END) + + labels = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + labels.add(name) + + dev = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) + dev.set_margin_top(10) + dev.set_margin_end(15) + dev.set_margin_start(15) + dev.set_margin_bottom(10) + dev.set_hexpand(True) + dev.set_vexpand(False) + dev.add(labels) + + buttons = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) + logger.debug("Profile compare: '%s' '%s'" % (self.active_mesh, profile)) + if self.active_mesh == profile: + buttons.pack_start(refresh, False, False, 0) + else: + buttons.pack_start(load, False, False, 0) + #buttons.pack_end(info, False, False, 0) + + if profile != "default": + buttons.pack_end(save, False, False, 0) + + dev.add(buttons) + frame.add(dev) + + self.profiles[profile] = { + "box": dev, + "buttons": buttons, + "row": frame, + "load": load, + "refresh": refresh, + "save": save + } + + profiles = sorted(self.profiles) + pos = profiles.index(profile) + + self.labels['profiles'].insert_row(pos) + self.labels['profiles'].attach(self.profiles[profile]['row'], 0, pos, 1, 1) + self.labels['profiles'].show_all() + + #Gdk.threads_add_idle(GLib.PRIORITY_LOW, self.create_graph, profile) + + def calibrate_mesh(self, widget): + self._screen._ws.klippy.gcode_script( + "BED_MESH_CALIBRATE" + ) + + def load_meshes(self): + bm_profiles = self._screen.printer.get_config_section_list("bed_mesh ") + self.profiles = {} + for prof in bm_profiles: + self.add_profile(prof[9:]) + + def process_update(self, action, data): + if action == "notify_gcode_response": + if "must home axis first" in data.lower(): + self._screen.show_popup_message("Must home axis first.") + + if action == "notify_status_update": + if "bed_mesh" in data and "profile_name" in data['bed_mesh']: + logger.debug("bed_mesh: %s" % data) + if data['bed_mesh']['profile_name'] != self.active_mesh: + self.activate_mesh(data['bed_mesh']['profile_name']) + + def send_load_mesh(self, widget, profile): + self._screen._ws.klippy.gcode_script( + KlippyGcodes.bed_mesh_load(profile) + ) + + def send_save_mesh(self, widget, profile): + self._screen._ws.klippy.gcode_script( + KlippyGcodes.bed_mesh_save(profile) + ) + + def show_mesh(self, widget, profile): + _ = self.lang.gettext + + buttons = [ + {"name": _("Close"), "response": Gtk.ResponseType.CANCEL} + ] + dialog = KlippyGtk.Dialog(self._screen, buttons, self.graphs[profile], self._close_dialog) + + def _close_dialog(self, widget, response): + widget.destroy() diff --git a/screen.py b/screen.py index 04b195fb..30697dc5 100644 --- a/screen.py +++ b/screen.py @@ -114,6 +114,7 @@ class KlipperScreen(Gtk.Window): def ws_subscribe(self): requested_updates = { "objects": { + "bed_mesh": ["profile_name","mesh_max","mesh_min","probed_matrix"], "configfile": ["config"], "extruder": ["target","temperature","pressure_advance","smooth_time"], "fan": ["speed"], @@ -397,6 +398,7 @@ class KlipperScreen(Gtk.Window): self.shutdown = False status_objects = [ + 'bed_mesh', 'idle_timeout', 'configfile', 'gcode_move',