alfrix b93fd08b6f base_panel: resource warning changes
make the notification contain the name of the printer

use avg cpu instead of individual core to be less intrusive, also because moonraker seems to report higher core usage than htop, which is strange
2024-04-01 11:46:32 -03:00

367 lines
17 KiB
Python

# -*- coding: utf-8 -*-
import logging
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import GLib, Gtk, Pango
from jinja2 import Environment
from datetime import datetime
from math import log
from ks_includes.screen_panel import ScreenPanel
class BasePanel(ScreenPanel):
def __init__(self, screen, title):
super().__init__(screen, title)
self.current_panel = None
self.time_min = -1
self.time_format = self._config.get_main_config().getboolean("24htime", True)
self.time_update = None
self.titlebar_items = []
self.titlebar_name_type = None
self.current_extruder = None
self.last_usage_report = datetime.now()
self.usage_report = 0
# Action bar buttons
abscale = self.bts * 1.1
self.control['back'] = self._gtk.Button('back', scale=abscale)
self.control['back'].connect("clicked", self.back)
self.control['home'] = self._gtk.Button('main', scale=abscale)
self.control['home'].connect("clicked", self._screen._menu_go_back, True)
for control in self.control:
self.set_control_sensitive(False, control)
self.control['estop'] = self._gtk.Button('emergency', scale=abscale)
self.control['estop'].connect("clicked", self.emergency_stop)
self.control['estop'].set_no_show_all(True)
self.shutdown = {
"name": None,
"panel": "shutdown",
"icon": "shutdown",
}
self.control['shutdown'] = self._gtk.Button('shutdown', scale=abscale)
self.control['shutdown'].connect("clicked", self.menu_item_clicked, self.shutdown)
self.control['shutdown'].set_no_show_all(True)
self.control['printer_select'] = self._gtk.Button('shuffle', scale=abscale)
self.control['printer_select'].connect("clicked", self._screen.show_printer_select)
self.control['printer_select'].set_no_show_all(True)
self.shorcut = {
"name": "Macros",
"panel": "gcode_macros",
"icon": "custom-script",
}
self.control['shortcut'] = self._gtk.Button(self.shorcut['icon'], scale=abscale)
self.control['shortcut'].connect("clicked", self.menu_item_clicked, self.shorcut)
self.control['shortcut'].set_no_show_all(True)
# Any action bar button should close the keyboard
for item in self.control:
self.control[item].connect("clicked", self._screen.remove_keyboard)
# Action bar
self.action_bar = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
if self._screen.vertical_mode:
self.action_bar.set_hexpand(True)
self.action_bar.set_vexpand(False)
else:
self.action_bar.set_hexpand(False)
self.action_bar.set_vexpand(True)
self.action_bar.get_style_context().add_class('action_bar')
self.action_bar.set_size_request(self._gtk.action_bar_width, self._gtk.action_bar_height)
self.action_bar.add(self.control['back'])
self.action_bar.add(self.control['home'])
self.action_bar.add(self.control['printer_select'])
self.action_bar.add(self.control['shortcut'])
self.action_bar.add(self.control['estop'])
self.action_bar.add(self.control['shutdown'])
self.show_printer_select(len(self._config.get_printers()) > 1)
# Titlebar
# This box will be populated by show_heaters
self.control['temp_box'] = Gtk.Box(spacing=10)
self.titlelbl = Gtk.Label(hexpand=True, halign=Gtk.Align.CENTER, ellipsize=Pango.EllipsizeMode.END)
self.control['time'] = Gtk.Label(label="00:00 AM")
self.control['time_box'] = Gtk.Box(halign=Gtk.Align.END)
self.control['time_box'].pack_end(self.control['time'], True, True, 10)
self.titlebar = Gtk.Box(spacing=5, valign=Gtk.Align.CENTER)
self.titlebar.get_style_context().add_class("title_bar")
self.titlebar.add(self.control['temp_box'])
self.titlebar.add(self.titlelbl)
self.titlebar.add(self.control['time_box'])
self.set_title(title)
# Main layout
self.main_grid = Gtk.Grid()
if self._screen.vertical_mode:
self.main_grid.attach(self.titlebar, 0, 0, 1, 1)
self.main_grid.attach(self.content, 0, 1, 1, 1)
self.main_grid.attach(self.action_bar, 0, 2, 1, 1)
self.action_bar.set_orientation(orientation=Gtk.Orientation.HORIZONTAL)
else:
self.main_grid.attach(self.action_bar, 0, 0, 1, 2)
self.action_bar.set_orientation(orientation=Gtk.Orientation.VERTICAL)
self.main_grid.attach(self.titlebar, 1, 0, 1, 1)
self.main_grid.attach(self.content, 1, 1, 1, 1)
self.update_time()
def show_heaters(self, show=True):
try:
for child in self.control['temp_box'].get_children():
self.control['temp_box'].remove(child)
devices = self._printer.get_temp_devices()
if not show or not devices:
return
img_size = self._gtk.img_scale * self.bts
for device in devices:
self.labels[device] = Gtk.Label(ellipsize=Pango.EllipsizeMode.START)
self.labels[f'{device}_box'] = Gtk.Box()
icon = self.get_icon(device, img_size)
if icon is not None:
self.labels[f'{device}_box'].pack_start(icon, False, False, 3)
self.labels[f'{device}_box'].pack_start(self.labels[device], False, False, 0)
# Limit the number of items according to resolution
nlimit = int(round(log(self._screen.width, 10) * 5 - 10.5))
n = 0
if len(self._printer.get_tools()) > (nlimit - 1):
self.current_extruder = self._printer.get_stat("toolhead", "extruder")
if self.current_extruder and f"{self.current_extruder}_box" in self.labels:
self.control['temp_box'].add(self.labels[f"{self.current_extruder}_box"])
else:
self.current_extruder = False
for device in devices:
if n >= nlimit:
break
if device.startswith("extruder") and self.current_extruder is False:
self.control['temp_box'].add(self.labels[f"{device}_box"])
n += 1
elif device.startswith("heater"):
self.control['temp_box'].add(self.labels[f"{device}_box"])
n += 1
for device in devices:
# Users can fill the bar if they want
if n >= nlimit + 1:
break
name = device.split()[1] if len(device.split()) > 1 else device
for item in self.titlebar_items:
if name == item:
self.control['temp_box'].add(self.labels[f"{device}_box"])
n += 1
break
self.control['temp_box'].show_all()
except Exception as e:
logging.debug(f"Couldn't create heaters box: {e}")
def get_icon(self, device, img_size):
if device.startswith("extruder"):
if self._printer.extrudercount > 1:
if device == "extruder":
device = "extruder0"
return self._gtk.Image(f"extruder-{device[8:]}", img_size, img_size)
return self._gtk.Image("extruder", img_size, img_size)
elif device.startswith("heater_bed"):
return self._gtk.Image("bed", img_size, img_size)
# Extra items
elif self.titlebar_name_type is not None:
# The item has a name, do not use an icon
return None
elif device.startswith("temperature_fan"):
return self._gtk.Image("fan", img_size, img_size)
elif device.startswith("heater_generic"):
return self._gtk.Image("heater", img_size, img_size)
else:
return self._gtk.Image("heat-up", img_size, img_size)
def activate(self):
if self.time_update is None:
self.time_update = GLib.timeout_add_seconds(1, self.update_time)
def add_content(self, panel):
printing = self._printer and self._printer.state in {"printing", "paused"}
connected = self._printer and self._printer.state not in {'disconnected', 'startup', 'shutdown', 'error'}
self.control['estop'].set_visible(printing)
self.control['shutdown'].set_visible(not printing)
self.show_shortcut(connected)
self.show_heaters(connected)
for control in ('back', 'home'):
self.set_control_sensitive(len(self._screen._cur_panels) > 1, control=control)
self.current_panel = panel
self.set_title(panel.title)
self.content.add(panel.content)
def back(self, widget=None):
if self.current_panel is None:
return
self._screen.remove_keyboard()
if hasattr(self.current_panel, "back") \
and not self.current_panel.back() \
or not hasattr(self.current_panel, "back"):
self._screen._menu_go_back()
def process_update(self, action, data):
if action == "notify_proc_stat_update":
cpu = data["system_cpu_usage"]["cpu"]
memory = (data["system_memory"]["used"] / data["system_memory"]["total"]) * 100
error = "message_popup_error"
ctx = self.titlebar.get_style_context()
msg = f"CPU: {cpu:2.0f}% RAM: {memory:2.0f}%"
if cpu > 80 or memory > 85:
if self.usage_report < 3:
self.usage_report += 1
return
self.last_usage_report = datetime.now()
if not ctx.has_class(error):
ctx.add_class(error)
self._screen.log_notification(f"{self._screen.connecting_to_printer}: {msg}", 2)
self.titlelbl.set_label(msg)
elif ctx.has_class(error):
if (datetime.now() - self.last_usage_report).seconds < 5:
self.titlelbl.set_label(msg)
return
self.usage_report = 0
ctx.remove_class(error)
self.titlelbl.set_label(f"{self._screen.connecting_to_printer}")
return
if action == "notify_update_response":
if self.update_dialog is None:
self.show_update_dialog()
if 'message' in data:
self.labels['update_progress'].set_text(
f"{self.labels['update_progress'].get_text().strip()}\n"
f"{data['message']}\n")
if 'complete' in data and data['complete']:
logging.info("Update complete")
if self.update_dialog is not None:
try:
self.update_dialog.set_response_sensitive(Gtk.ResponseType.OK, True)
self.update_dialog.get_widget_for_response(Gtk.ResponseType.OK).show()
except AttributeError:
logging.error("error trying to show the updater button the dialog might be closed")
self._screen.updating = False
for dialog in self._screen.dialogs:
self._gtk.remove_dialog(dialog)
return
if action != "notify_status_update" or self._screen.printer is None:
return
for device in self._printer.get_temp_devices():
temp = self._printer.get_dev_stat(device, "temperature")
if temp is not None and device in self.labels:
name = ""
if not (device.startswith("extruder") or device.startswith("heater_bed")):
if self.titlebar_name_type == "full":
name = device.split()[1] if len(device.split()) > 1 else device
name = f'{self.prettify(name)}: '
elif self.titlebar_name_type == "short":
name = device.split()[1] if len(device.split()) > 1 else device
name = f"{name[:1].upper()}: "
self.labels[device].set_label(f"{name}{int(temp)}°")
if (self.current_extruder and 'toolhead' in data and 'extruder' in data['toolhead']
and data["toolhead"]["extruder"] != self.current_extruder):
self.control['temp_box'].remove(self.labels[f"{self.current_extruder}_box"])
self.current_extruder = data["toolhead"]["extruder"]
self.control['temp_box'].pack_start(self.labels[f"{self.current_extruder}_box"], True, True, 3)
self.control['temp_box'].reorder_child(self.labels[f"{self.current_extruder}_box"], 0)
self.control['temp_box'].show_all()
return False
def remove(self, widget):
self.content.remove(widget)
def set_control_sensitive(self, value=True, control='shortcut'):
self.control[control].set_sensitive(value)
def show_shortcut(self, show=True):
show = (
show
and self._config.get_main_config().getboolean('side_macro_shortcut', True)
and self._printer.get_printer_status_data()["printer"]["gcode_macros"]["count"] > 0
)
self.control['shortcut'].set_visible(show)
self.set_control_sensitive(self._screen._cur_panels[-1] != self.shorcut['panel'])
self.set_control_sensitive(self._screen._cur_panels[-1] != self.shutdown['panel'], control='shutdown')
def show_printer_select(self, show=True):
self.control['printer_select'].set_visible(show)
def set_title(self, title):
self.titlebar.get_style_context().remove_class("message_popup_error")
if not title:
self.titlelbl.set_label(f"{self._screen.connecting_to_printer}")
return
try:
env = Environment(extensions=["jinja2.ext.i18n"], autoescape=True)
env.install_gettext_translations(self._config.get_lang())
j2_temp = env.from_string(title)
title = j2_temp.render()
except Exception as e:
logging.debug(f"Error parsing jinja for title: {title}\n{e}")
self.titlelbl.set_label(f"{self._screen.connecting_to_printer} | {title}")
def update_time(self):
now = datetime.now()
confopt = self._config.get_main_config().getboolean("24htime", True)
if now.minute != self.time_min or self.time_format != confopt:
if confopt:
self.control['time'].set_text(f'{now:%H:%M }')
else:
self.control['time'].set_text(f'{now:%I:%M %p}')
self.time_min = now.minute
self.time_format = confopt
return True
def set_ks_printer_cfg(self, printer):
ScreenPanel.ks_printer_cfg = self._config.get_printer_config(printer)
if self.ks_printer_cfg is not None:
self.titlebar_name_type = self.ks_printer_cfg.get("titlebar_name_type", None)
titlebar_items = self.ks_printer_cfg.get("titlebar_items", None)
if titlebar_items is not None:
self.titlebar_items = [str(i.strip()) for i in titlebar_items.split(',')]
logging.info(f"Titlebar name type: {self.titlebar_name_type} items: {self.titlebar_items}")
else:
self.titlebar_items = []
def show_update_dialog(self):
if self.update_dialog is not None:
return
button = [{"name": _("Finish"), "response": Gtk.ResponseType.OK}]
self.labels['update_progress'] = Gtk.Label(hexpand=True, vexpand=True, ellipsize=Pango.EllipsizeMode.END)
self.labels['update_scroll'] = self._gtk.ScrolledWindow(steppers=False)
self.labels['update_scroll'].set_property("overlay-scrolling", True)
self.labels['update_scroll'].add(self.labels['update_progress'])
self.labels['update_scroll'].connect("size-allocate", self._autoscroll)
dialog = self._gtk.Dialog(_("Updating"), button, self.labels['update_scroll'], self.finish_updating)
dialog.connect("delete-event", self.close_update_dialog)
dialog.set_response_sensitive(Gtk.ResponseType.OK, False)
dialog.get_widget_for_response(Gtk.ResponseType.OK).hide()
self.update_dialog = dialog
self._screen.updating = True
def finish_updating(self, dialog, response_id):
if response_id != Gtk.ResponseType.OK:
return
logging.info("Finishing update")
self._screen.updating = False
self._gtk.remove_dialog(dialog)
self._screen._menu_go_back(home=True)
def close_update_dialog(self, *args):
logging.info("Closing update dialog")
if self.update_dialog in self._screen.dialogs:
self._screen.dialogs.remove(self.update_dialog)
self.update_dialog = None
self._screen._menu_go_back(home=True)