diff --git a/BlocksScreen/BlocksScreen.py b/BlocksScreen/BlocksScreen.py
index ab198a7e..dab6cb59 100644
--- a/BlocksScreen/BlocksScreen.py
+++ b/BlocksScreen/BlocksScreen.py
@@ -1,6 +1,5 @@
import logging
import sys
-import typing
from logger import CrashHandler, LogManager, install_crash_handler, setup_logging
@@ -39,7 +38,7 @@ def notify(self, a0: QtCore.QObject, a1: QtCore.QEvent) -> bool: # type: ignore
RESET = "\033[0m"
-def show_splash(window: typing.Optional[QtWidgets.QWidget] = None):
+def show_splash(window: QtWidgets.QWidget | None = None):
"""Show splash screen on app initialization"""
logo = QtGui.QPixmap("BlocksScreen/BlocksScreen/lib/ui/resources/logoblocks.png")
splash = QtWidgets.QSplashScreen(pixmap=logo)
diff --git a/BlocksScreen/lib/filament.py b/BlocksScreen/lib/filament.py
index cb4c0232..0c3d0b44 100644
--- a/BlocksScreen/lib/filament.py
+++ b/BlocksScreen/lib/filament.py
@@ -1,6 +1,7 @@
# Class that represents a filament spool
-from typing import Optional
+from __future__ import annotations
+
import enum
@@ -29,17 +30,18 @@ def __init__(
self,
name: str,
temperature: int,
- brand: Optional[str] = None,
- spool_type: Optional[SpoolMaterial] = None,
- spool_weight: Optional[float] = None,
+ brand: str | None = None,
+ spool_type: SpoolMaterial | None = None,
+ spool_weight: float | None = None,
):
if not isinstance(name, str) or not isinstance(temperature, int):
raise TypeError("__init__() invalid argument type")
self._name: str = name
self._temperature: int = temperature
- self._weight: Optional[float] = None
- self._brand: Optional[str] = brand
+ self._weight: float | None = None
+ self._brand: str | None = brand
+ self._spool_type: Filament.SpoolMaterial | None = None
if spool_type is not None and spool_type in self.SpoolMaterial:
self._spool_type = spool_type
@@ -55,7 +57,7 @@ def temperature(self) -> int:
return self._temperature
@property
- def weight(self) -> Optional[float]:
+ def weight(self) -> float | None:
if self._weight is None:
return
return self._weight
@@ -65,22 +67,19 @@ def weight(self, new_value: float):
self._weight = new_value
@property
- def brand(self) -> Optional[str]:
+ def brand(self) -> str | None:
return self._brand
@brand.setter
- def brand(self, new_value: str) -> Optional[str]:
+ def brand(self, new_value: str) -> None:
self._brand = new_value
@property
- def spool_type(self) -> Optional[SpoolMaterial]:
+ def spool_type(self) -> SpoolMaterial | None:
return self._spool_type
@spool_type.setter
- def spool_type(self, new):
- if new not in self.SpoolMaterial:
- if isinstance(new, self.SpoolMaterial):
- raise ValueError(
- "Spool Material type is invalid"
- ) # Correct type but invalid option
+ def spool_type(self, new) -> None:
+ if new is not None and not isinstance(new, self.SpoolMaterial):
+ raise ValueError(f"Spool Material type is invalid: {new!r}")
self._spool_type = new
diff --git a/BlocksScreen/lib/files.py b/BlocksScreen/lib/files.py
index 412f0648..87396151 100644
--- a/BlocksScreen/lib/files.py
+++ b/BlocksScreen/lib/files.py
@@ -8,7 +8,6 @@
from pathlib import Path
import events
-from events import ReceivedFileData
from lib.moonrakerComm import MoonWebSocket
from PyQt6 import QtCore, QtGui, QtWidgets
@@ -54,7 +53,7 @@ class FileMetadata:
filename: str = ""
thumbnail_images: list[QtGui.QImage] = field(default_factory=list)
- filament_total: typing.Union[dict, str, float] = field(default_factory=dict)
+ filament_total: dict | str | float = field(default_factory=dict)
estimated_time: int = 0
layer_count: int = -1
total_layer: int = -1
@@ -74,8 +73,8 @@ class FileMetadata:
slicer_version: str = "Unknown"
gcode_start_byte: int = 0
gcode_end_byte: int = 0
- print_start_time: typing.Optional[float] = None
- job_id: typing.Optional[str] = None
+ print_start_time: float | None = None
+ job_id: str | None = None
def to_dict(self) -> dict:
"""Convert to dictionary for signal emission."""
@@ -255,7 +254,7 @@ def is_loaded(self) -> bool:
"""Check if initial load is complete."""
return self._initial_load_complete
- def get_file_metadata(self, filename: str) -> typing.Optional[FileMetadata]:
+ def get_file_metadata(self, filename: str) -> FileMetadata | None:
"""Get cached metadata for a file."""
return self._files_metadata.get(filename.removeprefix("/"))
@@ -279,7 +278,7 @@ def initial_load(self) -> None:
self._initial_load_complete = False
self.request_dir_info[str, bool].emit("", True)
- def handle_filelist_changed(self, data: typing.Union[dict, list]) -> None:
+ def handle_filelist_changed(self, data: dict | list) -> None:
"""Handle notify_filelist_changed from Moonraker."""
if isinstance(data, dict) and "params" in data:
data = data.get("params", [])
@@ -492,7 +491,7 @@ def _process_metadata(self, data: dict) -> None:
self.fileinfo.emit(metadata.to_dict())
logger.debug(f"Metadata loaded for: {filename}")
- def handle_metadata_error(self, error_data: typing.Union[str, dict]) -> None:
+ def handle_metadata_error(self, error_data: str | dict) -> None:
"""
Handle metadata request error from Moonraker.
@@ -538,7 +537,7 @@ def _preload_usb_contents(self, usb_path: str) -> None:
self._usb_preload_queue.append(usb_path)
self.ws.api.get_dir_information(usb_path, True)
- def get_cached_usb_files(self, usb_path: str) -> typing.Optional[list[dict]]:
+ def get_cached_usb_files(self, usb_path: str) -> list[dict] | None:
"""
Get cached files for a USB path if available.
@@ -646,7 +645,7 @@ def on_request_fileinfo(self, filename: str) -> None:
@QtCore.pyqtSlot(str, bool, name="get_dir_info")
def get_dir_information(
self, directory: str = "", extended: bool = True
- ) -> typing.Optional[list]:
+ ) -> list | None:
"""Get directory information."""
self._current_directory = directory
@@ -669,8 +668,8 @@ def eventFilter(self, obj: QtCore.QObject, event: QtCore.QEvent) -> bool:
def event(self, event: QtCore.QEvent) -> bool:
"""Handle object-level events."""
- if event.type() == ReceivedFileData.type():
- if isinstance(event, ReceivedFileData):
+ if event.type() == events.ReceivedFileData.type():
+ if isinstance(event, events.ReceivedFileData):
self.handle_message_received(event.method, event.data, event.params)
return True
return super().event(event)
diff --git a/BlocksScreen/lib/moonrakerComm.py b/BlocksScreen/lib/moonrakerComm.py
index 5f889d9f..74286e4e 100644
--- a/BlocksScreen/lib/moonrakerComm.py
+++ b/BlocksScreen/lib/moonrakerComm.py
@@ -21,7 +21,7 @@ class OneShotTokenError(Exception):
"""Raised when unable to get oneshot token to connect to a websocket"""
def __init__(self, message="Unable to get oneshot token", errors=None) -> None:
- super(OneShotTokenError).__init__(message, errors)
+ super().__init__(message, errors)
self.errors = errors
self.message = message
@@ -30,10 +30,6 @@ class MoonWebSocket(QtCore.QObject, threading.Thread):
"""MoonWebSocket class object for creating a websocket connection to Moonraker."""
QUERY_KLIPPY_TIMEOUT: int = 2
- connected = False
- connecting = False
- callback_table = {}
- _reconnect_count = 0
max_retries = 3
timeout = 3
@@ -45,9 +41,19 @@ class MoonWebSocket(QtCore.QObject, threading.Thread):
query_server_info_signal = QtCore.pyqtSignal(name="query_server_information")
def __init__(self, parent: QtCore.QObject) -> None:
+ """Initialize the websocket thread, timers, and Moonraker API helper."""
super().__init__(parent)
self.daemon = True
+ self.connected = False
+ self.connecting = False
+ self.disconnected = False
+ self._reconnect_count = 0
+ self.callback_table: dict = {}
+
+ self._state_lock = threading.RLock()
+ self._request_lock = threading.Lock()
+
self._host = parent.config.get("host", parser=str, default="localhost")
self._port = parent.config.get("port", parser=int, default=7125)
@@ -55,10 +61,11 @@ def __init__(self, parent: QtCore.QObject) -> None:
self._callback = None
self._wst = None
self._request_id = 0
- self.request_table = {}
+ self.request_table: dict = {}
+ self._klippy_retry_count = 0
self._moonRest = MoonRest(host=self._host, port=self._port)
self.api: MoonAPI = MoonAPI(self)
- self._retry_timer: RepeatedTimer
+ self._retry_timer: RepeatedTimer | None = None
websocket.setdefaulttimeout(self.timeout)
self.query_server_info_signal.connect(self.api.api_query_server_info)
@@ -69,41 +76,56 @@ def __init__(self, parent: QtCore.QObject) -> None:
self.klippy_state_signal.connect(self.api.request_printer_info)
logger.info("Websocket object initialized")
+ @property
+ def moonRest(self) -> MoonRest:
+ """Returns the current moonrestAPI object"""
+ return self._moonRest
+
@QtCore.pyqtSlot(name="retry_wb_conn")
def retry_wb_conn(self):
"""Retry websocket connection"""
- if self.connecting is True and self.connected is False:
- return False
- self._reconnect_count = 0
+ with self._state_lock:
+ if self.connecting is True and self.connected is False:
+ return False
+ self._reconnect_count = 0
self.try_connection()
def try_connection(self):
"""Try connecting to websocket"""
- self.connecting = True
+ with self._state_lock:
+ self.connecting = True
+ if self._retry_timer is not None:
+ self._retry_timer.stopTimer()
self._retry_timer = RepeatedTimer(self.timeout, self.reconnect)
return self.connect()
def reconnect(self):
"""Reconnect to websocket"""
- if self.connected:
- return True
-
- if self._reconnect_count >= self.max_retries:
- self._retry_timer.stopTimer()
+ with self._state_lock:
+ if self.connected:
+ return True
+ over_limit = self._reconnect_count >= self.max_retries
+
+ if over_limit:
+ if self._retry_timer is not None:
+ self._retry_timer.stopTimer()
unable_to_connect_event = WebSocketError(
data="Unable to establish connection to Websocket"
)
self.connecting_signal[int].emit(0)
- self.connecting = False
+ with self._state_lock:
+ self.connecting = False
try:
instance = QtWidgets.QApplication.instance()
if instance is not None:
- instance.sendEvent(self.parent(), unable_to_connect_event)
+ instance.postEvent(self.parent(), unable_to_connect_event)
else:
raise TypeError("QApplication.instance expected ad non-None value")
except Exception as e:
logger.error(
- f"Error on sending Event {unable_to_connect_event.__class__.__name__} | Error message: {e}"
+ "Error on sending Event %s | Error message: %s",
+ unable_to_connect_event.__class__.__name__,
+ e,
)
logger.info(
"Maximum number of connection retries reached, Unable to establish connection with Moonraker"
@@ -113,17 +135,19 @@ def reconnect(self):
def connect(self) -> bool:
"""Connect to websocket"""
- if self.connected:
- logger.info("Connection established")
- return True
- self._reconnect_count += 1
- self.connecting_signal[int].emit(int(self._reconnect_count))
+ with self._state_lock:
+ if self.connected:
+ logger.info("Connection established")
+ return True
+ self._reconnect_count += 1
+ _count_snapshot = self._reconnect_count
+ self.connecting_signal[int].emit(int(_count_snapshot))
logger.debug(
- f"Establishing connection to Moonraker...\n Try number {self._reconnect_count}"
+ "Establishing connection to Moonraker...\n Try number %d",
+ _count_snapshot,
)
- # TODO Handle if i cannot connect to moonraker, request server.info and see if i get a result
try:
- _oneshot_token = self._moonRest.get_oneshot_token()
+ _oneshot_token = self.moonRest.get_oneshot_token()
if _oneshot_token is None:
raise OneShotTokenError("Unable to retrieve oneshot token")
except Exception as e:
@@ -152,7 +176,7 @@ def connect(self) -> bool:
logger.debug(self.ws.url)
self._wst.start()
except Exception as e:
- logger.info(f"Unexpected while starting websocket {self._wst.name}: {e}")
+ logger.info("Unexpected while starting websocket %s: %s", self._wst.name, e)
return False
return True
@@ -161,17 +185,17 @@ def wb_disconnect(self) -> None:
if self._wst is not None and self.ws is not None:
self.ws.close()
if self._wst.is_alive():
- self._wst.join()
+ self._wst.join(timeout=self.timeout + 1)
logger.info("Websocket closed")
def on_error(self, *args) -> None:
"""Websocket error callback"""
# First argument is ws second is error message
- # TODO: Handle error messages
_error = args[1] if len(args) == 2 else args[0]
- logger.info(f"Websocket error, disconnected: {_error}")
- self.connected = False
- self.disconnected = True
+ logger.error("Websocket error, disconnected: %s", _error)
+ with self._state_lock:
+ self.connected = False
+ self.disconnected = True
def on_close(self, *args) -> None:
"""Websocket on close callback
@@ -184,7 +208,8 @@ def on_close(self, *args) -> None:
return
_close_status_code = args[1] if len(args) == 3 else None
_close_message = args[2] if len(args) == 3 else None
- self.connected = False
+ with self._state_lock:
+ self.connected = False
self.ws.keep_running = False
self.connection_lost[str].emit(
f"code: {_close_status_code} | message {_close_message}"
@@ -220,8 +245,10 @@ def on_open(self, *args) -> None:
TypeError: When QApplication.instance `is` None
"""
_ws = args[0] if len(args) == 1 else None
- self.connecting = False
- self.connected = True
+ with self._state_lock:
+ self.connecting = False
+ self.connected = True
+ self._klippy_retry_count = 0
self.evaluate_klippy_status()
open_event = WebSocketOpen(data="Connected")
try:
@@ -231,11 +258,12 @@ def on_open(self, *args) -> None:
else:
raise TypeError("QApplication.instance expected non None value")
except Exception as e:
- logger.info(f"Unexpected error opening websocket: {e}")
+ logger.info("Unexpected error opening websocket: %s", e)
self.connected_signal.emit()
- self._retry_timer.stopTimer()
- logger.info(f"Connection to websocket achieved on {_ws}")
+ if self._retry_timer is not None:
+ self._retry_timer.stopTimer()
+ logger.info("Connection to websocket achieved on %s", _ws)
def on_message(self, *args) -> None:
"""Websocket on message callback
@@ -247,24 +275,44 @@ def on_message(self, *args) -> None:
args[1] if len(args) == 2 else args[0]
) # First argument is ws second is message
- response: dict = json.loads(_message)
- if "id" in response and response["id"] in self.request_table:
- _entry = self.request_table.pop(response["id"])
+ try:
+ response: dict = json.loads(_message)
+ except json.JSONDecodeError as e:
+ logger.error("Failed to decode websocket message: %s", e)
+ return
+ if "id" in response:
+ with self._request_lock:
+ _entry = self.request_table.pop(response["id"], None)
+ else:
+ _entry = None
+
+ if _entry is not None:
if "server.info" in _entry[0]:
- if response["result"]["klippy_state"] == "ready":
+ if "error" in response:
+ return
+ _result = response.get("result", {})
+ _klippy_state = _result.get("klippy_state")
+ if not _klippy_state:
+ return
+ if _klippy_state == "ready":
self.query_klippy_status_timer.stopTimer()
+ self._klippy_retry_count = 0
self.api.update_status() # Request update status immediately after klippy ready DEVDEBT
- elif response["result"]["klippy_state"] == "startup":
- # request server.info in 2 seconds
- if not self.query_klippy_status_timer.running:
- self.query_klippy_status_timer.startTimer()
- elif response["result"]["klippy_state"] == "disconnected":
- if not self.query_klippy_status_timer.running:
+ elif _klippy_state in ("startup", "disconnected"):
+ self._klippy_retry_count += 1
+ if self._klippy_retry_count >= 30:
+ self.query_klippy_status_timer.stopTimer()
+ logger.error(
+ "Klippy startup sequence timed out after %d retries (state=%s)",
+ self._klippy_retry_count,
+ _klippy_state,
+ )
+ elif not self.query_klippy_status_timer.running:
self.query_klippy_status_timer.startTimer()
self.klippy_connected_signal.emit(
- response["result"]["klippy_connected"]
+ _result.get("klippy_connected", False)
)
- self.klippy_state_signal.emit(response["result"]["klippy_state"])
+ self.klippy_state_signal.emit(_klippy_state)
return
else:
if "error" in response:
@@ -276,13 +324,15 @@ def on_message(self, *args) -> None:
else:
message_event = WebSocketMessageReceived(
method=str(_entry[0]),
- data=response["result"],
+ data=response.get("result", {}),
metadata=_entry,
)
elif "method" in response:
if (
str(response["method"]).lower() == "notify_klippy_disconnected"
): # Checkout for notify_klippy_disconnect
+ self.klippy_state_signal.emit("disconnected")
+ self._klippy_retry_count = 0
self.evaluate_klippy_status()
message_event = (
@@ -292,6 +342,8 @@ def on_message(self, *args) -> None:
metadata=None,
)
)
+ else:
+ return
try:
instance = QtWidgets.QApplication.instance()
@@ -300,36 +352,46 @@ def on_message(self, *args) -> None:
else:
raise TypeError("QApplication.instance expected non None value")
except Exception as e:
- logger.info(f"Unexpected error while creating websocket message event: {e}")
+ logger.info(
+ "Unexpected error while creating websocket message event: %s", e
+ )
- def send_request(self, method: str, params: dict = {}) -> bool:
+ def send_request(self, method: str, params: dict | None = None) -> bool:
"""Send a request over the websocket
Args:
method (str): Websocket method name
- params (dict, optional): parameters for the websocket method. Defaults to {}.
+ params (dict, optional): parameters for the websocket method. Defaults to None.
Returns:
bool: Whether the method finished and a request was sent
"""
- if not self.connected or self.ws is None:
+ if params is None:
+ params = {}
+ with self._state_lock:
+ _connected = self.connected
+ _ws = self.ws
+ if not _connected or _ws is None:
return False
- self._request_id += 1
- self.request_table[self._request_id] = [method, params]
+ with self._request_lock:
+ self._request_id += 1
+ _rid = self._request_id
+ self.request_table[_rid] = [method, params]
+
packet = {
"jsonrpc": "2.0",
"method": method,
"params": params,
- "id": self._request_id,
+ "id": _rid,
}
- self.ws.send(json.dumps(packet))
+ _ws.send(json.dumps(packet))
return True
class MoonAPI(QtCore.QObject):
def __init__(self, ws: MoonWebSocket):
- super(MoonAPI, self).__init__(ws)
+ super().__init__(ws)
self._ws: MoonWebSocket = ws
@QtCore.pyqtSlot(name="api_query_server_info")
@@ -446,11 +508,11 @@ def restart_service(self, service):
@QtCore.pyqtSlot(name="firmware_restart")
def firmware_restart(self):
- """Request Klipper firmware restart
+ """`POST MoonrakerAPI` /printer/firmware_restart
+ Firmware restart to Klipper
- HTTP_REQUEST: POST /printer/firmware_restart
-
- JSON_RPC_REQUEST: printer.firmware_restart
+ Returns:
+ str: Returns an 'ok' from Moonraker
"""
return self._ws.send_request(method="printer.firmware_restart")
@@ -605,7 +667,7 @@ def move_file(self, source_dir: str, dest_dir: str):
isinstance(source_dir, str) is False
or isinstance(dest_dir, str) is False
or source_dir is None
- or dest_dir is False
+ or dest_dir is None
):
return False
return self._ws.send_request(
@@ -619,7 +681,7 @@ def copy_file(self, source_dir: str, dest_dir: str):
isinstance(source_dir, str) is False
or isinstance(dest_dir, str) is False
or source_dir is None
- or dest_dir is False
+ or dest_dir is None
):
return False
return self._ws.send_request(
@@ -732,7 +794,7 @@ def update_status(self, refresh: bool = False) -> bool:
@QtCore.pyqtSlot(name="update-refresh")
@QtCore.pyqtSlot(str, name="update-refresh")
- def refresh_update_status(self, name: str = None) -> bool:
+ def refresh_update_status(self, name: str | None = None) -> bool:
"""Refresh packages state"""
if isinstance(name, str):
return self._ws.send_request(
@@ -763,7 +825,9 @@ def update_client(self, client_name: str = "") -> bool:
"""Issue client update"""
if not isinstance(client_name, str) or not client_name:
return False
- return self._ws.send_request(method="machine.update.client")
+ return self._ws.send_request(
+ method="machine.update.client", params={"name": client_name}
+ )
@QtCore.pyqtSlot(name="update-system")
def update_system(self):
@@ -787,7 +851,7 @@ def rollback_update(self, name: str):
if not isinstance(name, str) or not name:
return False
return self._ws.send_request(
- method="machine,update.rollback", params={"name": name}
+ method="machine.update.rollback", params={"name": name}
)
def history_list(self, limit, start, since, before, order):
diff --git a/BlocksScreen/lib/moonrest.py b/BlocksScreen/lib/moonrest.py
index 2c663531..079ca577 100644
--- a/BlocksScreen/lib/moonrest.py
+++ b/BlocksScreen/lib/moonrest.py
@@ -29,7 +29,6 @@
import logging
import requests
-from requests import Request, Response
logger = logging.getLogger(__name__)
@@ -38,7 +37,7 @@ class UncallableError(Exception):
"""Raised when a method is not callable"""
def __init__(self, message="Unable to call method", errors=None):
- super(UncallableError, self).__init__(message, errors)
+ super().__init__(message, errors)
self.errors = errors
self.message = message
@@ -128,7 +127,7 @@ def _request(
_headers = {"x-api-key": self._api_key} if self._api_key else {}
try:
if hasattr(requests, request_type):
- _request_method: Request = getattr(requests, request_type)
+ _request_method = getattr(requests, request_type)
if not callable(_request_method):
raise UncallableError(
"Invalid request method",
@@ -142,9 +141,9 @@ def _request(
headers=_headers,
timeout=timeout,
)
- if isinstance(response, Response):
+ if isinstance(response, requests.Response):
response.raise_for_status()
return response.json() if json_response else response.content
except Exception as e:
- logger.info(f"Unexpected error while sending HTTP request: {e}")
+ logger.info("Unexpected error while sending HTTP request: %s", e)
diff --git a/BlocksScreen/lib/panels/controlTab.py b/BlocksScreen/lib/panels/controlTab.py
index 8df4ab4d..70484c9e 100644
--- a/BlocksScreen/lib/panels/controlTab.py
+++ b/BlocksScreen/lib/panels/controlTab.py
@@ -1,717 +1,726 @@
-from __future__ import annotations
-import re
-import typing
-from functools import partial
-import logging
-
-from helper_methods import normalize
-from lib.moonrakerComm import MoonWebSocket
-from lib.panels.widgets.numpadPage import CustomNumpad
-from lib.panels.widgets.optionCardWidget import OptionCard
-from lib.panels.widgets.popupDialogWidget import Popup
-from lib.panels.widgets.printcorePage import SwapPrintcorePage
-from lib.panels.widgets.probeHelperPage import ProbeHelper
-from lib.panels.widgets.slider_selector_page import SliderPage
-from lib.printer import Printer
-from lib.ui.controlStackedWidget_ui import Ui_controlStackedWidget
-from lib.utils.display_button import DisplayButton
-from PyQt6 import QtCore, QtGui, QtWidgets
-
-
-_logger = logging.getLogger(__name__)
-
-
-class ControlTab(QtWidgets.QStackedWidget):
- """Printer Control Stacked Widget"""
-
- request_back_button = QtCore.pyqtSignal(name="request-back-button")
- request_change_page = QtCore.pyqtSignal(int, int, name="request-change-page")
- request_numpad_signal = QtCore.pyqtSignal(
- int,
- str,
- str,
- "PyQt_PyObject",
- QtWidgets.QStackedWidget,
- name="request-numpad",
- )
- run_gcode_signal: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
- str, name="run-gcode"
- )
- disable_popups: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
- bool, name="disable-popups"
- )
- request_numpad: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
- [str, int, "PyQt_PyObject"],
- [str, int, "PyQt_PyObject", int, int],
- name="request-numpad",
- )
- request_file_info: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
- str, name="request-file-info"
- )
- call_load_panel = QtCore.pyqtSignal(bool, str, name="call-load-panel")
- toggle_conn_page = QtCore.pyqtSignal(bool, name="call-load-panel")
- tune_display_buttons: dict = {}
- card_options: dict = {}
-
- def __init__(
- self,
- parent: QtWidgets.QWidget,
- ws: MoonWebSocket,
- printer: Printer,
- /,
- ) -> None:
- super().__init__(parent)
- self.panel = Ui_controlStackedWidget()
- self.panel.setupUi(self)
-
- self.popup = Popup(self)
-
- self.ws: MoonWebSocket = ws
- self.printer: Printer = printer
- self.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight)
- self.timers = []
- self.ztilt_state = False
- self.extruder_info: dict = {}
- self.bed_info: dict = {}
- self.toolhead_info: dict = {}
- self.extrude_length: int = 10
- self.extrude_feedrate: int = 2
- self.extrude_page_message: str = ""
- self.move_length: float = 1.0
- self.move_speed: float = 25.0
- self.probe_helper_page = ProbeHelper(self)
- self.probe_helper_page.toggle_conn_page.connect(self.toggle_conn_page)
- self.probe_helper_page.disable_popups.connect(self.disable_popups)
- self.addWidget(self.probe_helper_page)
- self.probe_helper_page.call_load_panel.connect(self.call_load_panel)
- self.printcores_page = SwapPrintcorePage(self)
- self.addWidget(self.printcores_page)
-
- self.sliderPage = SliderPage(self)
- self.addWidget(self.sliderPage)
- self.sliderPage.request_back.connect(self.back_button)
-
- self.probe_helper_page.request_page_view.connect(
- partial(self.change_page, self.indexOf(self.probe_helper_page))
- )
- self.probe_helper_page.query_printer_object.connect(self.ws.api.object_query)
- self.probe_helper_page.run_gcode_signal.connect(self.ws.api.run_gcode)
- self.probe_helper_page.request_back.connect(self.back_button)
- self.printer.print_stats_update[str, str].connect(
- self.probe_helper_page.on_print_stats_update
- )
- self.printer.print_stats_update[str, dict].connect(
- self.probe_helper_page.on_print_stats_update
- )
- self.printer.print_stats_update[str, float].connect(
- self.probe_helper_page.on_print_stats_update
- )
- self.printer.available_gcode_cmds.connect(
- self.probe_helper_page.on_available_gcode_cmds
- )
- self.probe_helper_page.subscribe_config[str, "PyQt_PyObject"].connect(
- self.printer.on_subscribe_config
- )
- self.probe_helper_page.subscribe_config[list, "PyQt_PyObject"].connect(
- self.printer.on_subscribe_config
- )
- self.printer.extruder_update.connect(self.probe_helper_page.on_extruder_update)
- self.printer.gcode_move_update.connect(
- self.probe_helper_page.on_gcode_move_update
- )
- self.printer.manual_probe_update.connect(
- self.probe_helper_page.on_manual_probe_update
- )
- self.printer.printer_config.connect(self.probe_helper_page.on_printer_config)
- self.printer.gcode_response.connect(
- self.probe_helper_page.handle_gcode_response
- )
- self.printer.toolhead_update[str, list].connect(self.on_toolhead_update)
- self.printer.extruder_update.connect(self.on_extruder_update)
- self.printer.heater_bed_update.connect(self.on_heater_bed_update)
- self.panel.cp_motion_btn.clicked.connect(
- partial(self.change_page, self.indexOf(self.panel.motion_page))
- )
- self.panel.cp_temperature_btn.clicked.connect(
- partial(self.change_page, self.indexOf(self.panel.temperature_page))
- )
- self.panel.cp_fans_btn.clicked.connect(
- partial(self.change_page, self.indexOf(self.panel.fans_page))
- )
- self.panel.fans_back_btn.clicked.connect(self.back_button)
- self.panel.cp_switch_print_core_btn.clicked.connect(self.show_swapcore)
- self.panel.cp_nozzles_calibration_btn.clicked.connect(
- partial(self.change_page, self.indexOf(self.probe_helper_page))
- )
- self.panel.motion_extrude_btn.clicked.connect(
- partial(self.change_page, self.indexOf(self.panel.extrude_page))
- )
- self.panel.motion_move_axis_btn.clicked.connect(
- partial(self.change_page, self.indexOf(self.panel.move_axis_page))
- )
- self.panel.mp_back_btn.clicked.connect(self.back_button)
- self.panel.motion_auto_home_btn.clicked.connect(
- partial(self.run_gcode_signal.emit, "G28\nM400")
- )
- self.panel.motion_disable_steppers_btn.clicked.connect(
- partial(self.run_gcode_signal.emit, "M84\nM400")
- )
- self.panel.exp_back_btn.clicked.connect(self.back_button)
- self.panel.extrude_select_length_10_btn.toggled.connect(
- partial(
- self.handle_toggle_extrude_length,
- caller=self.panel.extrude_select_length_10_btn,
- value=10,
- )
- )
- self.panel.extrude_select_length_50_btn.toggled.connect(
- partial(
- self.handle_toggle_extrude_length,
- caller=self.panel.extrude_select_length_50_btn,
- value=50,
- )
- )
- self.panel.extrude_select_length_100_btn.toggled.connect(
- partial(
- self.handle_toggle_extrude_length,
- caller=self.panel.extrude_select_length_100_btn,
- value=100,
- )
- )
- self.panel.extrude_select_feedrate_2_btn.toggled.connect(
- partial(
- self.handle_toggle_extrude_feedrate,
- caller=self.panel.extrude_select_feedrate_2_btn,
- value=2,
- )
- )
- self.panel.extrude_select_feedrate_5_btn.toggled.connect(
- partial(
- self.handle_toggle_extrude_feedrate,
- caller=self.panel.extrude_select_feedrate_5_btn,
- value=5,
- )
- )
- self.panel.extrude_select_feedrate_10_btn.toggled.connect(
- partial(
- self.handle_toggle_extrude_feedrate,
- caller=self.panel.extrude_select_feedrate_10_btn,
- value=10,
- )
- )
- self.panel.mva_select_length_1_btn.toggled.connect(
- partial(self.handle_select_move_length, value=1.0)
- )
- self.panel.mva_select_length_10_btn.toggled.connect(
- partial(self.handle_select_move_length, value=10.0)
- )
- self.panel.mva_select_length_100_btn.toggled.connect(
- partial(self.handle_select_move_length, value=100.0)
- )
- self.panel.mva_select_speed_25_btn.toggled.connect(
- partial(self.handle_select_move_speed, value=25.0)
- )
- self.panel.mva_select_speed_50_btn.toggled.connect(
- partial(self.handle_select_move_speed, value=50.0)
- )
- self.panel.mva_select_speed_100_btn.toggled.connect(
- partial(self.handle_select_move_speed, value=100.0)
- )
- self.panel.exp_extrude_btn.clicked.connect(
- partial(self.handle_extrusion, True)
- ) # True for extrusion
- self.panel.exp_unextrude_btn.clicked.connect(
- partial(self.handle_extrusion, False)
- ) # False for retraction
- # Move Axis
- self.panel.mva_back_btn.clicked.connect(self.back_button)
- self.panel.mva_home_x_btn.clicked.connect(
- partial(self.run_gcode_signal.emit, "G28 X\nM400")
- )
- self.panel.mva_home_y_btn.clicked.connect(
- partial(self.run_gcode_signal.emit, "G28 Y\nM400")
- )
- self.panel.mva_home_z_btn.clicked.connect(
- partial(self.run_gcode_signal.emit, "G28 Z\nM400")
- )
- self.panel.mva_home_all_btn.clicked.connect(
- partial(self.run_gcode_signal.emit, "G28\nM400")
- )
- self.panel.mva_up_btn.clicked.connect(partial(self.handle_move_axis, "Y"))
- self.panel.mva_down_btn.clicked.connect(partial(self.handle_move_axis, "Y-"))
- self.panel.mva_right_btn.clicked.connect(partial(self.handle_move_axis, "X"))
- self.panel.mva_left_btn.clicked.connect(partial(self.handle_move_axis, "X-"))
- self.panel.mva_z_up.clicked.connect(
- partial(self.handle_move_axis, "Z-") # Move nozzle closer to bed
- )
- self.panel.mva_z_down.clicked.connect(
- partial(self.handle_move_axis, "Z") # Move nozzle away from bed
- )
- self.panel.temp_back_button.clicked.connect(self.back_button)
- self.panel.printer_settings_back_btn.clicked.connect(self.back_button)
- self.run_gcode_signal.connect(self.ws.api.run_gcode)
- # @ object temperature change clicked
- self.numpadPage = CustomNumpad(self)
- self.numpadPage.request_back.connect(self.request_back_button)
- self.addWidget(self.numpadPage)
-
- self.request_numpad[str, int, "PyQt_PyObject", int, int].connect(
- self.on_numpad_request
- )
-
- self.panel.cooldown_btn.clicked.connect(
- lambda: self.run_gcode_signal.emit(
- "SET_HEATER_TEMPERATURE HEATER=heater_bed TARGET=0\n\
- SET_HEATER_TEMPERATURE HEATER=extruder TARGET=0"
- )
- )
-
- self.path = {
- "fan_cage": QtGui.QPixmap(":/fan_related/media/btn_icons/fan_cage.svg"),
- "blower": QtGui.QPixmap(":/fan_related/media/btn_icons/blower.svg"),
- "fan": QtGui.QPixmap(":/fan_related/media/btn_icons/fan.svg"),
- }
-
- self.panel.cp_z_tilt_btn.clicked.connect(lambda: self.handle_ztilt())
-
- self.printcores_page.pc_accept.clicked.connect(self.handle_swapcore)
-
- self.ws.klippy_state_signal.connect(self.on_klippy_status)
- self.ws.klippy_state_signal.connect(self.probe_helper_page.on_klippy_status)
- self.printer.on_printcore_update.connect(self.handle_printcoreupdate)
- self.printer.gcode_response.connect(self._handle_gcode_response)
- self.printer.z_tilt_update.connect(self._handle_z_tilt_object_update)
- # self.panel.cp_printer_settings_btn.hide()
- self.panel.temperature_cooldown_btn.hide()
- self.panel.cooldown_btn.hide()
- self.panel.cp_switch_print_core_btn.hide()
-
- self.printer.fan_update[str, str, float].connect(self.on_fan_object_update)
- self.printer.fan_update[str, str, int].connect(self.on_fan_object_update)
-
- self.printer.printer_config.connect(self.on_printer_config)
-
- def _handle_z_tilt_object_update(self, value, state):
- if state:
- self.call_load_panel.emit(False, "")
-
- @QtCore.pyqtSlot(str, str, float, name="on_fan_update")
- @QtCore.pyqtSlot(str, str, int, name="on_fan_update")
- def on_fan_object_update(
- self, name: str, field: str, new_value: int | float
- ) -> None:
- """Slot that receives updates from fan objects.
-
- Args:
- name (str): Fan object name
- field (str): Field name
- new_value (int | float): New value for the field
- """
- if "speed" not in field:
- return
-
- fields = name.split()
- first_field = fields[0]
- second_field = fields[1] if len(fields) > 1 else None
- name = second_field.replace("_", " ") if second_field else name
-
- fan_card = self.tune_display_buttons.get(name)
- if fan_card is None and first_field in (
- "fan",
- "fan_generic",
- ):
- icon = self.path.get("fan")
- if second_field:
- second_field = second_field.lower()
- pattern_blower = r"(?:^|_)(?:blower|auxiliary)(?:_|$)"
- pattern_exhaust = r"(?:^|_)exhaust(?:_|$)"
- if re.search(pattern_blower, second_field):
- icon = self.path.get("blower")
- elif re.search(pattern_exhaust, second_field):
- icon = self.path.get("fan_cage")
-
- card = OptionCard(self, name, str(name), icon) # type: ignore
- card.setObjectName(str(name))
-
- # Add card to layout and record reference
- self.card_options[name] = card
- self.panel.fans_content_layout.addWidget(card)
-
- # If the card doesn't have expected UI properties, discard it
- if not hasattr(card, "continue_clicked"):
- del card
- self.card_options.pop(name, None)
- return
-
- card.setMode(True)
- card.secondtext.setText(f"{new_value}%")
- card.continue_clicked.connect(
- lambda: self.on_slidePage_request(
- str(name),
- card.secondtext.text().replace("%", ""),
- self.on_slider_change,
- 0,
- 100,
- )
- )
-
- self.tune_display_buttons[name] = card
- self.update()
- fan_card = card
-
- if fan_card:
- value_percent = new_value * 100 if new_value <= 1 else new_value
- fan_card.secondtext.setText(f"{value_percent:.0f}%")
-
- @QtCore.pyqtSlot(str, int, "PyQt_PyObject", name="on_slidePage_request")
- @QtCore.pyqtSlot(str, int, "PyQt_PyObject", int, int, name="on_slidePage_request")
- def on_slidePage_request(
- self,
- name: str,
- current_value: int,
- callback,
- min_value: int = 0,
- max_value: int = 100,
- ) -> None:
- self.sliderPage.value_selected.connect(callback)
- self.sliderPage.set_name(name)
- self.sliderPage.set_slider_position(int(current_value))
- self.sliderPage.set_slider_minimum(min_value)
- self.sliderPage.set_slider_maximum(max_value)
- self.change_page(self.indexOf(self.sliderPage))
-
- @QtCore.pyqtSlot(str, int, name="on_slider_change")
- def on_slider_change(self, name: str, new_value: int) -> None:
- if "speed" in name.lower():
- self.speed_factor_override = new_value / 100
- self.run_gcode_signal.emit(f"M220 S{new_value}")
- if name.lower() == "fan":
- self.run_gcode_signal.emit(
- f"M106 S{int(round((normalize(float(new_value / 100), 0.0, 1.0, 0, 255))))}"
- ) # [0, 255] Range
- else:
- name = name.replace(" ", "_")
- self.run_gcode_signal.emit(
- f'SET_FAN_SPEED FAN="{name}" SPEED={float(new_value / 100.00)}'
- ) # [0.0, 1.0] Range
-
- def create_display_button(self, name: str) -> DisplayButton:
- """Create and return a DisplayButton
-
- Args:
- name (str): Name for the display button
-
- Returns:
- DisplayButton: The created DisplayButton object
- """
- display_button = DisplayButton()
- display_button.setObjectName(str(name + "_display"))
- display_button.setMinimumSize(QtCore.QSize(150, 50))
- display_button.setMaximumSize(QtCore.QSize(150, 80))
- font = QtGui.QFont()
- font.setPointSize(16)
- display_button.setFont(font)
- return display_button
-
- def handle_printcoreupdate(self, value: dict):
- if value["swapping"] == "idle":
- return
-
- if value["swapping"] == "in_pos":
- self.call_load_panel.emit(False, "")
- self.printcores_page.show()
- self.disable_popups.emit(True)
- self.printcores_page.setText(
- "Please Insert Print Core \n \n Afterwards click continue"
- )
- if value["swapping"] == "unloading":
- self.call_load_panel.emit(True, "Unloading print core")
- if value["swapping"] == "cleaning":
- self.call_load_panel.emit(True, "Cleaning print core")
-
- def _handle_gcode_response(self, messages: list):
- """Handle gcode response for Z-tilt adjustment"""
- pattern = r"Retries:\s*(\d+)/(\d+).*?range:\s*([\d.]+)\s*tolerance:\s*([\d.]+)"
-
- for msg_list in messages:
- if not msg_list:
- continue
-
- if (
- "Retries:" in msg_list
- and "range:" in msg_list
- and "tolerance:" in msg_list
- ):
- match = re.search(pattern, msg_list)
-
- if match:
- retries_done = int(match.group(1))
- retries_total = int(match.group(2))
- probed_range = float(match.group(3))
- tolerance = float(match.group(4))
- if retries_done == retries_total:
- self.call_load_panel.emit(False, "")
- return
- self.call_load_panel.emit(
- True,
- f"Retries: {retries_done}/{retries_total} | Range: {probed_range:.6f} | Tolerance: {tolerance:.6f}",
- )
-
- @QtCore.pyqtSlot(dict, name="printer_config")
- def on_printer_config(self, config: dict) -> None:
- """Slot that receives the full printer configuration,
-
- Additionally, this method configures the signal connections
- between controllable heaters and numpad calls
- """
- try:
- self.panel.extruder_temp_display.clicked.disconnect()
- self.panel.bed_temp_display.clicked.disconnect()
- except Exception:
- _logger.debug("Signals were not connected")
- extruder = config.get("extruder", None) or {}
- bed = config.get("heater_bed", None) or {}
- e_min_temp = extruder.get("min_temp", 0)
- e_max_temp = extruder.get("max_temp", 300)
- b_max_temp = bed.get("max_temp", 100)
- b_min_temp = bed.get("min_temp", 0)
- # Configure numpads
- self.panel.extruder_temp_display.clicked.connect(
- lambda: self.request_numpad[str, int, "PyQt_PyObject", int, int].emit(
- "Extruder Temperature",
- int(round(float(self.panel.extruder_temp_display.secondary_text))),
- self.on_numpad_change,
- int(e_min_temp),
- int(e_max_temp),
- )
- )
- self.panel.bed_temp_display.clicked.connect(
- lambda: self.request_numpad[str, int, "PyQt_PyObject", int, int].emit(
- "Bed Temperature",
- int(round(float(self.panel.bed_temp_display.secondary_text))),
- self.on_numpad_change,
- int(b_min_temp),
- int(b_max_temp),
- )
- )
-
- def handle_ztilt(self):
- """Handle Z-Tilt Adjustment"""
- self.call_load_panel.emit(True, "Please wait, performing Z-axis calibration.")
- self.run_gcode_signal.emit("G28\nM400")
- self.run_gcode_signal.emit("Z_TILT_ADJUST")
-
- @QtCore.pyqtSlot(str, name="on-klippy-status")
- def on_klippy_status(self, state: str):
- """Handles incoming klippy status changes"""
- if state.lower() == "ready":
- self.printcores_page.hide()
- self.disable_popups.emit(False)
- return
- if state.lower() == "startup":
- self.printcores_page.setText("Almost done \n be patient")
- return
-
- def show_swapcore(self):
- """Show swap printcore"""
- self.run_gcode_signal.emit("CHANGE_PRINTCORES")
- self.call_load_panel.emit(True, "Preparing to swap print core")
-
- def handle_swapcore(self):
- """Handle swap printcore routine finish"""
- self.printcores_page.setText("Executing \n Firmware Restart")
- self.run_gcode_signal.emit("FIRMWARE_RESTART")
-
- @QtCore.pyqtSlot(str, int, "PyQt_PyObject", name="on-numpad-request")
- @QtCore.pyqtSlot(str, int, "PyQt_PyObject", int, int, name="on-numpad-request")
- def on_numpad_request(
- self,
- name: str,
- current_value: int,
- callback,
- min_value: int = 0,
- max_value: int = 100,
- ) -> None:
- """Handles numpad widget request"""
- self.numpadPage.value_selected.connect(callback)
- self.numpadPage.set_name(name)
- self.numpadPage.set_value(current_value)
- self.numpadPage.set_min_value(min_value)
- self.numpadPage.set_max_value(max_value)
- self.change_page(self.indexOf(self.numpadPage))
-
- @QtCore.pyqtSlot(str, int, name="on-numpad-change")
- def on_numpad_change(self, name: str, new_value: int) -> None:
- """Handles inputted numpad values"""
- if "bed" in name.lower():
- name = "heater_bed"
- elif "extruder" in name.lower():
- name = "extruder"
- self.run_gcode_signal.emit(
- f"SET_HEATER_TEMPERATURE HEATER={name} TARGET={new_value}"
- )
-
- def change_page(self, index):
- """Handles changing page"""
- self.request_change_page.emit(2, index)
-
- def back_button(self):
- """Handle back button click"""
- self.request_back_button.emit()
-
- def register_timed_callback(self, time: int, callback: callable) -> None:
- """Registers timed callback and starts the timeout"""
- _timer = QtCore.QTimer()
- _timer.setSingleShot(True)
- _timer.timeout.connect(callback)
- _timer.start(int(time))
- self.timers.append(_timer)
-
- @QtCore.pyqtSlot(bool, "PyQt_PyObject", int, name="select-extrude-feedrate")
- def handle_toggle_extrude_feedrate(self, checked: bool, caller, value: int) -> None:
- """Slot to change the extruder feedrate, mainly used for toggle buttons
-
- Args:
- checked (bool): Button checked state
- caller (PyQtObject): The button that called this slot
- value (int): New value for the extruder feedrate
- """
- if value == self.extrude_feedrate:
- return
- self.extrude_feedrate = value
-
- @QtCore.pyqtSlot(bool, "PyQt_PyObject", int, name="select-extrude-length")
- def handle_toggle_extrude_length(self, checked: bool, caller, value: int) -> None:
- """Slot that changes the extrude length, mainly used for toggle buttons
-
- Args:
- checked (bool): Button checked state
- caller (PyQtObject): The button that called this slot
- value (int): New value for the extrude length
- """
- if self.extrude_length == value:
- return
- self.extrude_length = value
-
- @QtCore.pyqtSlot(bool, float, name="handle-select-move-speed")
- def handle_select_move_speed(self, checked: bool, value: float) -> None:
- """Slot that changes the move speed of manual move commands, mainly used
- for toggle buttons
-
- Args:
- checked (bool): Button checked state
- value (float): New move speed value
- """
- if self.move_speed == value:
- return
- self.move_speed = value
-
- @QtCore.pyqtSlot(bool, float, name="handle-select-move-length")
- def handle_select_move_length(self, checked: bool, value: float) -> None:
- """Slot that changes the move length of manual move commands,
- mainly used for toggle buttons
-
-
- Args:
- checked (bool): Button checked state
- value (float): New length value
- """
- if self.move_length == value:
- return
- self.move_length = value
-
- @QtCore.pyqtSlot(str, name="handle-extrusion")
- def handle_extrusion(self, extrude: bool) -> None:
- """Slot that requests an extrusion/unextrusion move
-
- Args:
- extrude (bool): If True extrudes otherwise unextrudes.
- """
- can_extrude = bool(self.printer.heaters_object["extruder"]["can_extrude"])
- if not can_extrude:
- self.extrude_page_message = "Temperature too cold to extrude"
- self.panel.exp_info_label.setText(self.extrude_page_message)
- return
- if extrude:
- self.run_gcode_signal.emit(
- f"M83\nG1 E{self.extrude_length} F{self.extrude_feedrate * 60}\nM82\nM400"
- )
- self.extrude_page_message = "Extruding"
- self.panel.exp_info_label.setText(self.extrude_page_message)
- else:
- self.run_gcode_signal.emit(
- f"M83\nG1 E-{self.extrude_length} F{self.extrude_feedrate * 60}\nM82\nM400"
- )
- self.extrude_page_message = "Retracting"
- self.panel.exp_info_label.setText(self.extrude_page_message)
- # This block of code schedules a method to be called in x amount of milliseconds
- _sch_time_s = float(
- self.extrude_length / self.extrude_feedrate
- ) # calculate the amount of time it'll take for the operation
- self.extrude_page_message = "Ready"
- self.register_timed_callback(
- int(_sch_time_s + 2.0) * 1000, # In milliseconds
- lambda: self.panel.exp_info_label.setText(self.extrude_page_message),
- )
-
- @QtCore.pyqtSlot(str, name="handle-move-axis")
- def handle_move_axis(self, axis: str) -> None:
- """Slot that requests manual move command
-
- Args:
- axis (str): String that contains one of the following axis `
- ['X',
- 'X-'
- ,'Y'
- ,'Y-'
- ,'Z'
- ,'Z-']`. [^1]
-
- ---
-
- [^1]: The **-** symbol indicates the negative direction for that axis
-
- """
- if axis not in ["X", "X-", "Y", "Y-", "Z", "Z-"]:
- return
- self.run_gcode_signal.emit(
- f"G91\nG0 {axis}{float(self.move_length)} F{float(self.move_speed * 60)}\nG90\nM400"
- )
-
- @QtCore.pyqtSlot(str, list, name="on-toolhead-update")
- def on_toolhead_update(self, field: str, values: list) -> None:
- """Handles updated from toolhead printer object"""
- if field == "position":
- self.panel.mva_x_value_label.setText(f"{values[0]:.2f}")
- self.panel.mva_y_value_label.setText(f"{values[1]:.2f}")
- self.panel.mva_z_value_label.setText(f"{values[2]:.3f}")
-
- if values[0] == "252,50" and values[1] == "250" and values[2] == "50":
- self.call_load_panel.emit(False, "")
- self.toolhead_info.update({f"{field}": values})
-
- @QtCore.pyqtSlot(str, str, float, name="on-extruder-update")
- def on_extruder_update(
- self, extruder_name: str, field: str, new_value: float
- ) -> None:
- """Handles updates from extruder printer object"""
- if extruder_name == "extruder" and field == "temperature":
- self.panel.extruder_temp_display.setText(f"{new_value:.1f}")
- if extruder_name == "extruder" and field == "target":
- self.panel.extruder_temp_display.secondary_text = f"{new_value:.1f}"
- self.extruder_info.update({f"{extruder_name}": {f"{field}": new_value}})
-
- @QtCore.pyqtSlot(str, str, float, name="on-heater-bed-update")
- def on_heater_bed_update(self, name: str, field: str, new_value: float) -> None:
- """Handles updated from heater_bed printer object"""
- if field == "temperature":
- self.panel.bed_temp_display.setText(f"{new_value:.1f}")
- if field == "target":
- self.panel.bed_temp_display.secondary_text = f"{new_value:.1f}"
- self.bed_info.update({f"{name}": {f"{field}": new_value}})
-
- def paintEvent(self, a0: QtGui.QPaintEvent) -> None:
- """Handles ControlTab Widget painting"""
- if self.panel.extrude_page.isVisible():
- self.panel.exp_info_label.setText(self.extrude_page_message)
- return super().paintEvent(a0)
+from __future__ import annotations
+import re
+import typing
+from functools import partial
+import logging
+
+from lib.moonrakerComm import MoonWebSocket
+from lib.panels.widgets.numpadPage import CustomNumpad
+from lib.panels.widgets.optionCardWidget import OptionCard
+from lib.panels.widgets.popupDialogWidget import Popup
+from lib.panels.widgets.printcorePage import SwapPrintcorePage
+from lib.panels.widgets.probeHelperPage import ProbeHelper
+from lib.panels.widgets.slider_selector_page import SliderPage
+from lib.printer import Printer
+from lib.ui.controlStackedWidget_ui import Ui_controlStackedWidget
+from lib.utils.display_button import DisplayButton
+from PyQt6 import QtCore, QtGui, QtWidgets
+
+logger = logging.getLogger(__name__)
+
+
+_logger = logging.getLogger(__name__)
+
+
+class ControlTab(QtWidgets.QStackedWidget):
+ """Printer Control Stacked Widget"""
+
+ request_back_button = QtCore.pyqtSignal(name="request-back-button")
+ request_change_page = QtCore.pyqtSignal(int, int, name="request-change-page")
+ request_numpad_signal = QtCore.pyqtSignal(
+ int,
+ str,
+ str,
+ "PyQt_PyObject",
+ QtWidgets.QStackedWidget,
+ name="request-numpad",
+ )
+ run_gcode_signal: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
+ str, name="run-gcode"
+ )
+ disable_popups: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
+ bool, name="disable-popups"
+ )
+ request_numpad: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
+ [str, int, "PyQt_PyObject"],
+ [str, int, "PyQt_PyObject", int, int],
+ name="request-numpad",
+ )
+ request_file_info: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
+ str, name="request-file-info"
+ )
+ call_load_panel = QtCore.pyqtSignal(bool, str, name="call-load-panel")
+ toggle_conn_page = QtCore.pyqtSignal(bool, name="call-load-panel")
+ tune_display_buttons: dict = {}
+ card_options: dict = {}
+
+ def __init__(
+ self,
+ parent: QtWidgets.QWidget,
+ ws: MoonWebSocket,
+ printer: Printer,
+ /,
+ ) -> None:
+ super().__init__(parent)
+ self.panel = Ui_controlStackedWidget()
+ self.panel.setupUi(self)
+
+ self.popup = Popup(self)
+
+ self.ws: MoonWebSocket = ws
+ self.printer: Printer = printer
+ self.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight)
+ self.timers = []
+ self.ztilt_state = False
+ self.extruder_info: dict = {}
+ self.bed_info: dict = {}
+ self.toolhead_info: dict = {}
+ self.extrude_length: int = 10
+ self.extrude_feedrate: int = 2
+ self.extrude_page_message: str = ""
+ self.move_length: float = 1.0
+ self.move_speed: float = 25.0
+ self.probe_helper_page = ProbeHelper(self)
+ self.probe_helper_page.toggle_conn_page.connect(self.toggle_conn_page)
+ self.probe_helper_page.disable_popups.connect(self.disable_popups)
+ self.addWidget(self.probe_helper_page)
+ self.probe_helper_page.call_load_panel.connect(self.call_load_panel)
+ self.printcores_page = SwapPrintcorePage(self)
+ self.addWidget(self.printcores_page)
+
+ self.sliderPage = SliderPage(self)
+ self.addWidget(self.sliderPage)
+ self.sliderPage.request_back.connect(self.back_button)
+
+ self.probe_helper_page.request_page_view.connect(
+ partial(self.change_page, self.indexOf(self.probe_helper_page))
+ )
+ self.probe_helper_page.query_printer_object.connect(self.ws.api.object_query)
+ self.probe_helper_page.run_gcode_signal.connect(self.ws.api.run_gcode)
+ self.probe_helper_page.request_back.connect(self.back_button)
+ self.printer.print_stats_update[str, str].connect(
+ self.probe_helper_page.on_print_stats_update
+ )
+ self.printer.print_stats_update[str, dict].connect(
+ self.probe_helper_page.on_print_stats_update
+ )
+ self.printer.print_stats_update[str, float].connect(
+ self.probe_helper_page.on_print_stats_update
+ )
+ self.printer.available_gcode_cmds.connect(
+ self.probe_helper_page.on_available_gcode_cmds
+ )
+ self.probe_helper_page.subscribe_config[str, "PyQt_PyObject"].connect(
+ self.printer.on_subscribe_config
+ )
+ self.probe_helper_page.subscribe_config[list, "PyQt_PyObject"].connect(
+ self.printer.on_subscribe_config
+ )
+ self.printer.extruder_update.connect(self.probe_helper_page.on_extruder_update)
+ self.printer.gcode_move_update.connect(
+ self.probe_helper_page.on_gcode_move_update
+ )
+ self.printer.manual_probe_update.connect(
+ self.probe_helper_page.on_manual_probe_update
+ )
+ self.printer.printer_config.connect(self.probe_helper_page.on_printer_config)
+ self.printer.gcode_response.connect(
+ self.probe_helper_page.handle_gcode_response
+ )
+ self.printer.toolhead_update[str, list].connect(self.on_toolhead_update)
+ self.printer.extruder_update.connect(self.on_extruder_update)
+ self.printer.heater_bed_update.connect(self.on_heater_bed_update)
+ self.panel.cp_motion_btn.clicked.connect(
+ partial(self.change_page, self.indexOf(self.panel.motion_page))
+ )
+ self.panel.cp_temperature_btn.clicked.connect(
+ partial(self.change_page, self.indexOf(self.panel.temperature_page))
+ )
+ self.panel.cp_fans_btn.clicked.connect(
+ partial(self.change_page, self.indexOf(self.panel.fans_page))
+ )
+ self.panel.fans_back_btn.clicked.connect(self.back_button)
+ self.panel.cp_switch_print_core_btn.clicked.connect(self.show_swapcore)
+ self.panel.cp_nozzles_calibration_btn.clicked.connect(
+ partial(self.change_page, self.indexOf(self.probe_helper_page))
+ )
+ self.panel.motion_extrude_btn.clicked.connect(
+ partial(self.change_page, self.indexOf(self.panel.extrude_page))
+ )
+ self.panel.motion_move_axis_btn.clicked.connect(
+ partial(self.change_page, self.indexOf(self.panel.move_axis_page))
+ )
+ self.panel.mp_back_btn.clicked.connect(self.back_button)
+ self.panel.motion_auto_home_btn.clicked.connect(
+ partial(self.run_gcode_signal.emit, "G28\nM400")
+ )
+ self.panel.motion_disable_steppers_btn.clicked.connect(
+ partial(self.run_gcode_signal.emit, "M84\nM400")
+ )
+ self.panel.exp_back_btn.clicked.connect(self.back_button)
+ self.panel.extrude_select_length_10_btn.toggled.connect(
+ partial(
+ self.handle_toggle_extrude_length,
+ caller=self.panel.extrude_select_length_10_btn,
+ value=10,
+ )
+ )
+ self.panel.extrude_select_length_50_btn.toggled.connect(
+ partial(
+ self.handle_toggle_extrude_length,
+ caller=self.panel.extrude_select_length_50_btn,
+ value=50,
+ )
+ )
+ self.panel.extrude_select_length_100_btn.toggled.connect(
+ partial(
+ self.handle_toggle_extrude_length,
+ caller=self.panel.extrude_select_length_100_btn,
+ value=100,
+ )
+ )
+ self.panel.extrude_select_feedrate_2_btn.toggled.connect(
+ partial(
+ self.handle_toggle_extrude_feedrate,
+ caller=self.panel.extrude_select_feedrate_2_btn,
+ value=2,
+ )
+ )
+ self.panel.extrude_select_feedrate_5_btn.toggled.connect(
+ partial(
+ self.handle_toggle_extrude_feedrate,
+ caller=self.panel.extrude_select_feedrate_5_btn,
+ value=5,
+ )
+ )
+ self.panel.extrude_select_feedrate_10_btn.toggled.connect(
+ partial(
+ self.handle_toggle_extrude_feedrate,
+ caller=self.panel.extrude_select_feedrate_10_btn,
+ value=10,
+ )
+ )
+ self.panel.mva_select_length_1_btn.toggled.connect(
+ partial(self.handle_select_move_length, value=1.0)
+ )
+ self.panel.mva_select_length_10_btn.toggled.connect(
+ partial(self.handle_select_move_length, value=10.0)
+ )
+ self.panel.mva_select_length_100_btn.toggled.connect(
+ partial(self.handle_select_move_length, value=100.0)
+ )
+ self.panel.mva_select_speed_25_btn.toggled.connect(
+ partial(self.handle_select_move_speed, value=25.0)
+ )
+ self.panel.mva_select_speed_50_btn.toggled.connect(
+ partial(self.handle_select_move_speed, value=50.0)
+ )
+ self.panel.mva_select_speed_100_btn.toggled.connect(
+ partial(self.handle_select_move_speed, value=100.0)
+ )
+ self.panel.exp_extrude_btn.clicked.connect(
+ partial(self.handle_extrusion, True)
+ ) # True for extrusion
+ self.panel.exp_unextrude_btn.clicked.connect(
+ partial(self.handle_extrusion, False)
+ ) # False for retraction
+ # Move Axis
+ self.panel.mva_back_btn.clicked.connect(self.back_button)
+ self.panel.mva_home_x_btn.clicked.connect(
+ partial(self.run_gcode_signal.emit, "G28 X\nM400")
+ )
+ self.panel.mva_home_y_btn.clicked.connect(
+ partial(self.run_gcode_signal.emit, "G28 Y\nM400")
+ )
+ self.panel.mva_home_z_btn.clicked.connect(
+ partial(self.run_gcode_signal.emit, "G28 Z\nM400")
+ )
+ self.panel.mva_home_all_btn.clicked.connect(
+ partial(self.run_gcode_signal.emit, "G28\nM400")
+ )
+ self.panel.mva_up_btn.clicked.connect(partial(self.handle_move_axis, "Y"))
+ self.panel.mva_down_btn.clicked.connect(partial(self.handle_move_axis, "Y-"))
+ self.panel.mva_right_btn.clicked.connect(partial(self.handle_move_axis, "X"))
+ self.panel.mva_left_btn.clicked.connect(partial(self.handle_move_axis, "X-"))
+ self.panel.mva_z_up.clicked.connect(
+ partial(self.handle_move_axis, "Z-") # Move nozzle closer to bed
+ )
+ self.panel.mva_z_down.clicked.connect(
+ partial(self.handle_move_axis, "Z") # Move nozzle away from bed
+ )
+ self.panel.temp_back_button.clicked.connect(self.back_button)
+ self.panel.printer_settings_back_btn.clicked.connect(self.back_button)
+ self.run_gcode_signal.connect(self.ws.api.run_gcode)
+ # @ object temperature change clicked
+ self.numpadPage = CustomNumpad(self)
+ self.numpadPage.request_back.connect(self.request_back_button)
+ self.addWidget(self.numpadPage)
+
+ self.request_numpad[str, int, "PyQt_PyObject", int, int].connect(
+ self.on_numpad_request
+ )
+
+ self.panel.cooldown_btn.clicked.connect(
+ lambda: self.run_gcode_signal.emit(
+ "SET_HEATER_TEMPERATURE HEATER=heater_bed TARGET=0\n\
+ SET_HEATER_TEMPERATURE HEATER=extruder TARGET=0"
+ )
+ )
+
+ self.path = {
+ "fan_cage": QtGui.QPixmap(":/fan_related/media/btn_icons/fan_cage.svg"),
+ "blower": QtGui.QPixmap(":/fan_related/media/btn_icons/blower.svg"),
+ "fan": QtGui.QPixmap(":/fan_related/media/btn_icons/fan.svg"),
+ }
+
+ self.panel.cp_z_tilt_btn.clicked.connect(lambda: self.handle_ztilt())
+
+ self.printcores_page.pc_accept.clicked.connect(self.handle_swapcore)
+
+ self.ws.klippy_state_signal.connect(self.on_klippy_status)
+ self.ws.klippy_state_signal.connect(self.probe_helper_page.on_klippy_status)
+ self.printer.on_printcore_update.connect(self.handle_printcoreupdate)
+ self.printer.gcode_response.connect(self._handle_gcode_response)
+ self.printer.z_tilt_update.connect(self._handle_z_tilt_object_update)
+ # self.panel.cp_printer_settings_btn.hide()
+ self.panel.temperature_cooldown_btn.hide()
+ self.panel.cooldown_btn.hide()
+ self.panel.cp_switch_print_core_btn.hide()
+
+ self.printer.fan_update[str, str, float].connect(self.on_fan_object_update)
+ self.printer.fan_update[str, str, int].connect(self.on_fan_object_update)
+
+ self.printer.printer_config.connect(self.on_printer_config)
+
+ def _handle_z_tilt_object_update(self, value, state):
+ if state:
+ self.call_load_panel.emit(False, "")
+
+ @QtCore.pyqtSlot(str, str, float, name="on_fan_update")
+ @QtCore.pyqtSlot(str, str, int, name="on_fan_update")
+ def on_fan_object_update(
+ self, name: str, field: str, new_value: int | float
+ ) -> None:
+ """Slot that receives updates from fan objects.
+
+ Args:
+ name (str): Fan object name
+ field (str): Field name
+ new_value (int | float): New value for the field
+ """
+ if "speed" not in field:
+ return
+
+ fields = name.split()
+ first_field = fields[0]
+ second_field = fields[1] if len(fields) > 1 else None
+ name = second_field.replace("_", " ") if second_field else name
+
+ fan_card = self.tune_display_buttons.get(name)
+ if fan_card is None and first_field in (
+ "fan",
+ "fan_generic",
+ ):
+ icon = self.path.get("fan")
+ if second_field:
+ second_field = second_field.lower()
+ pattern_blower = r"(?:^|_)(?:blower|auxiliary)(?:_|$)"
+ pattern_exhaust = r"(?:^|_)exhaust(?:_|$)"
+ if re.search(pattern_blower, second_field):
+ icon = self.path.get("blower")
+ elif re.search(pattern_exhaust, second_field):
+ icon = self.path.get("fan_cage")
+
+ card = OptionCard(self, name, str(name), icon) # type: ignore
+ card.setObjectName(str(name))
+
+ # Add card to layout and record reference
+ self.card_options[name] = card
+ self.panel.fans_content_layout.addWidget(card)
+
+ # If the card doesn't have expected UI properties, discard it
+ if not hasattr(card, "continue_clicked"):
+ del card
+ self.card_options.pop(name, None)
+ return
+
+ card.setMode(True)
+ card.secondtext.setText(f"{new_value}%")
+ card.continue_clicked.connect(
+ lambda: self.on_slidePage_request(
+ str(name),
+ card.secondtext.text().replace("%", ""),
+ self.on_slider_change,
+ 0,
+ 100,
+ )
+ )
+
+ self.tune_display_buttons[name] = card
+ self.update()
+ fan_card = card
+
+ if fan_card:
+ value_percent = new_value * 100 if new_value <= 1 else new_value
+ fan_card.secondtext.setText(f"{value_percent:.0f}%")
+
+ @QtCore.pyqtSlot(str, int, "PyQt_PyObject", name="on_slidePage_request")
+ @QtCore.pyqtSlot(str, int, "PyQt_PyObject", int, int, name="on_slidePage_request")
+ def on_slidePage_request(
+ self,
+ name: str,
+ current_value: int,
+ callback,
+ min_value: int = 0,
+ max_value: int = 100,
+ ) -> None:
+ """Configure and navigate to the slider page with the given name, value, and callback."""
+ try:
+ self.sliderPage.value_selected.disconnect()
+ except (RuntimeError, TypeError):
+ pass # no connections yet
+ self.sliderPage.value_selected.connect(callback)
+ self.sliderPage.set_name(name)
+ self.sliderPage.set_slider_position(int(current_value))
+ self.sliderPage.set_slider_minimum(min_value)
+ self.sliderPage.set_slider_maximum(max_value)
+ self.change_page(self.indexOf(self.sliderPage))
+
+ @QtCore.pyqtSlot(str, int, name="on_slider_change")
+ def on_slider_change(self, name: str, new_value: int) -> None:
+ """Handle slider value change for fan controls.
+
+ In controlTab, only fan_generic cards invoke this slot — the "speed"
+ and "fan" branches are kept for parity with tunePage but should never
+ match here.
+ """
+ gcode_name = name.replace(" ", "_")
+ self.run_gcode_signal.emit(
+ f"SET_FAN_SPEED FAN={gcode_name} SPEED={float(new_value / 100.00)}"
+ ) # [0.0, 1.0] Range
+
+ def create_display_button(self, name: str) -> DisplayButton:
+ """Create and return a DisplayButton
+
+ Args:
+ name (str): Name for the display button
+
+ Returns:
+ DisplayButton: The created DisplayButton object
+ """
+ display_button = DisplayButton()
+ display_button.setObjectName(str(name + "_display"))
+ display_button.setMinimumSize(QtCore.QSize(150, 50))
+ display_button.setMaximumSize(QtCore.QSize(150, 80))
+ font = QtGui.QFont()
+ font.setPointSize(16)
+ display_button.setFont(font)
+ return display_button
+
+ def handle_printcoreupdate(self, value: dict):
+ _swapping = value.get("swapping")
+ if _swapping is None or _swapping == "idle":
+ return
+
+ if _swapping == "in_pos":
+ self.call_load_panel.emit(False, "")
+ self.printcores_page.show()
+ self.disable_popups.emit(True)
+ self.printcores_page.setText(
+ "Please Insert Print Core \n \n Afterwards click continue"
+ )
+ if _swapping == "unloading":
+ self.call_load_panel.emit(True, "Unloading print core")
+ if _swapping == "cleaning":
+ self.call_load_panel.emit(True, "Cleaning print core")
+
+ def _handle_gcode_response(self, messages: list):
+ """Handle gcode response for Z-tilt adjustment"""
+ pattern = r"Retries:\s*(\d+)/(\d+).*?range:\s*([\d.]+)\s*tolerance:\s*([\d.]+)"
+
+ for msg_list in messages:
+ if not msg_list:
+ continue
+
+ if (
+ "Retries:" in msg_list
+ and "range:" in msg_list
+ and "tolerance:" in msg_list
+ ):
+ match = re.search(pattern, msg_list)
+
+ if match:
+ retries_done = int(match.group(1))
+ retries_total = int(match.group(2))
+ probed_range = float(match.group(3))
+ tolerance = float(match.group(4))
+ if retries_done == retries_total:
+ self.call_load_panel.emit(False, "")
+ return
+ self.call_load_panel.emit(
+ True,
+ f"Retries: {retries_done}/{retries_total} | Range: {probed_range:.6f} | Tolerance: {tolerance:.6f}",
+ )
+
+ @QtCore.pyqtSlot(dict, name="printer_config")
+ def on_printer_config(self, config: dict) -> None:
+ """Slot that receives the full printer configuration,
+
+ Additionally, this method configures the signal connections
+ between controllable heaters and numpad calls
+ """
+ try:
+ self.panel.extruder_temp_display.clicked.disconnect()
+ self.panel.bed_temp_display.clicked.disconnect()
+ except Exception:
+ _logger.debug("Signals were not connected")
+ extruder = config.get("extruder", None) or {}
+ bed = config.get("heater_bed", None) or {}
+ e_min_temp = extruder.get("min_temp", 0)
+ e_max_temp = extruder.get("max_temp", 300)
+ b_max_temp = bed.get("max_temp", 100)
+ b_min_temp = bed.get("min_temp", 0)
+ # Configure numpads
+ self.panel.extruder_temp_display.clicked.connect(
+ lambda: self.request_numpad[str, int, "PyQt_PyObject", int, int].emit(
+ "Extruder Temperature",
+ int(round(float(self.panel.extruder_temp_display.secondary_text))),
+ self.on_numpad_change,
+ int(e_min_temp),
+ int(e_max_temp),
+ )
+ )
+ self.panel.bed_temp_display.clicked.connect(
+ lambda: self.request_numpad[str, int, "PyQt_PyObject", int, int].emit(
+ "Bed Temperature",
+ int(round(float(self.panel.bed_temp_display.secondary_text))),
+ self.on_numpad_change,
+ int(b_min_temp),
+ int(b_max_temp),
+ )
+ )
+
+ def handle_ztilt(self):
+ """Handle Z-Tilt Adjustment"""
+ self.call_load_panel.emit(True, "Please wait, performing Z-axis calibration.")
+ self.run_gcode_signal.emit("G28\nM400")
+ self.run_gcode_signal.emit("Z_TILT_ADJUST")
+
+ @QtCore.pyqtSlot(str, name="on-klippy-status")
+ def on_klippy_status(self, state: str):
+ """Handles incoming klippy status changes"""
+ if state.lower() == "ready":
+ self.printcores_page.hide()
+ self.disable_popups.emit(False)
+ return
+ if state.lower() == "startup":
+ self.printcores_page.setText("Almost done \n be patient")
+ return
+
+ def show_swapcore(self):
+ """Show swap printcore"""
+ self.run_gcode_signal.emit("CHANGE_PRINTCORES")
+ self.call_load_panel.emit(True, "Preparing to swap print core")
+
+ def handle_swapcore(self):
+ """Handle swap printcore routine finish"""
+ self.printcores_page.setText("Executing \n Firmware Restart")
+ self.run_gcode_signal.emit("FIRMWARE_RESTART")
+
+ @QtCore.pyqtSlot(str, int, "PyQt_PyObject", name="on-numpad-request")
+ @QtCore.pyqtSlot(str, int, "PyQt_PyObject", int, int, name="on-numpad-request")
+ def on_numpad_request(
+ self,
+ name: str,
+ current_value: int,
+ callback,
+ min_value: int = 0,
+ max_value: int = 100,
+ ) -> None:
+ """Handles numpad widget request"""
+ try:
+ self.numpadPage.value_selected.disconnect()
+ except (RuntimeError, TypeError):
+ pass # no connections yet
+ self.numpadPage.value_selected.connect(callback)
+ self.numpadPage.set_name(name)
+ self.numpadPage.set_value(current_value)
+ self.numpadPage.set_min_value(min_value)
+ self.numpadPage.set_max_value(max_value)
+ self.change_page(self.indexOf(self.numpadPage))
+
+ @QtCore.pyqtSlot(str, int, name="on-numpad-change")
+ def on_numpad_change(self, name: str, new_value: int) -> None:
+ """Handles inputted numpad values"""
+ if "bed" in name.lower():
+ name = "heater_bed"
+ elif "extruder" in name.lower():
+ name = "extruder"
+ self.run_gcode_signal.emit(
+ f"SET_HEATER_TEMPERATURE HEATER={name} TARGET={new_value}"
+ )
+
+ def change_page(self, index):
+ """Handles changing page"""
+ self.request_change_page.emit(2, index)
+
+ def back_button(self):
+ """Handle back button click"""
+ self.request_back_button.emit()
+
+ def register_timed_callback(self, time: int, callback: callable) -> None:
+ """Registers timed callback and starts the timeout"""
+ _timer = QtCore.QTimer()
+ _timer.setSingleShot(True)
+ _timer.timeout.connect(callback)
+ _timer.start(int(time))
+ self.timers.append(_timer)
+
+ @QtCore.pyqtSlot(bool, "PyQt_PyObject", int, name="select-extrude-feedrate")
+ def handle_toggle_extrude_feedrate(self, checked: bool, caller, value: int) -> None:
+ """Slot to change the extruder feedrate, mainly used for toggle buttons
+
+ Args:
+ checked (bool): Button checked state
+ caller (PyQtObject): The button that called this slot
+ value (int): New value for the extruder feedrate
+ """
+ if value == self.extrude_feedrate:
+ return
+ self.extrude_feedrate = value
+
+ @QtCore.pyqtSlot(bool, "PyQt_PyObject", int, name="select-extrude-length")
+ def handle_toggle_extrude_length(self, checked: bool, caller, value: int) -> None:
+ """Slot that changes the extrude length, mainly used for toggle buttons
+
+ Args:
+ checked (bool): Button checked state
+ caller (PyQtObject): The button that called this slot
+ value (int): New value for the extrude length
+ """
+ if self.extrude_length == value:
+ return
+ self.extrude_length = value
+
+ @QtCore.pyqtSlot(bool, float, name="handle-select-move-speed")
+ def handle_select_move_speed(self, checked: bool, value: float) -> None:
+ """Slot that changes the move speed of manual move commands, mainly used
+ for toggle buttons
+
+ Args:
+ checked (bool): Button checked state
+ value (float): New move speed value
+ """
+ if self.move_speed == value:
+ return
+ self.move_speed = value
+
+ @QtCore.pyqtSlot(bool, float, name="handle-select-move-length")
+ def handle_select_move_length(self, checked: bool, value: float) -> None:
+ """Slot that changes the move length of manual move commands,
+ mainly used for toggle buttons
+
+
+ Args:
+ checked (bool): Button checked state
+ value (float): New length value
+ """
+ if self.move_length == value:
+ return
+ self.move_length = value
+
+ @QtCore.pyqtSlot(str, name="handle-extrusion")
+ def handle_extrusion(self, extrude: bool) -> None:
+ """Slot that requests an extrusion/unextrusion move
+
+ Args:
+ extrude (bool): If True extrudes otherwise unextrudes.
+ """
+ can_extrude = bool(self.printer.heaters_object["extruder"]["can_extrude"])
+ if not can_extrude:
+ self.extrude_page_message = "Temperature too cold to extrude"
+ self.panel.exp_info_label.setText(self.extrude_page_message)
+ return
+ if extrude:
+ self.run_gcode_signal.emit(
+ f"M83\nG1 E{self.extrude_length} F{self.extrude_feedrate * 60}\nM82\nM400"
+ )
+ self.extrude_page_message = "Extruding"
+ self.panel.exp_info_label.setText(self.extrude_page_message)
+ else:
+ self.run_gcode_signal.emit(
+ f"M83\nG1 E-{self.extrude_length} F{self.extrude_feedrate * 60}\nM82\nM400"
+ )
+ self.extrude_page_message = "Retracting"
+ self.panel.exp_info_label.setText(self.extrude_page_message)
+ # This block of code schedules a method to be called in x amount of milliseconds
+ _sch_time_s = float(
+ self.extrude_length / self.extrude_feedrate
+ ) # calculate the amount of time it'll take for the operation
+ self.extrude_page_message = "Ready"
+ self.register_timed_callback(
+ int(_sch_time_s + 2.0) * 1000, # In milliseconds
+ lambda: self.panel.exp_info_label.setText(self.extrude_page_message),
+ )
+
+ @QtCore.pyqtSlot(str, name="handle-move-axis")
+ def handle_move_axis(self, axis: str) -> None:
+ """Slot that requests manual move command
+
+ Args:
+ axis (str): String that contains one of the following axis `
+ ['X',
+ 'X-'
+ ,'Y'
+ ,'Y-'
+ ,'Z'
+ ,'Z-']`. [^1]
+
+ ---
+
+ [^1]: The **-** symbol indicates the negative direction for that axis
+
+ """
+ if axis not in ["X", "X-", "Y", "Y-", "Z", "Z-"]:
+ return
+ self.run_gcode_signal.emit(
+ f"G91\nG0 {axis}{float(self.move_length)} F{float(self.move_speed * 60)}\nG90\nM400"
+ )
+
+ @QtCore.pyqtSlot(str, list, name="on-toolhead-update")
+ def on_toolhead_update(self, field: str, values: list) -> None:
+ """Handles updated from toolhead printer object"""
+ if field == "position":
+ self.panel.mva_x_value_label.setText(f"{values[0]:.2f}")
+ self.panel.mva_y_value_label.setText(f"{values[1]:.2f}")
+ self.panel.mva_z_value_label.setText(f"{values[2]:.3f}")
+
+ if values[0] == "252,50" and values[1] == "250" and values[2] == "50":
+ self.call_load_panel.emit(False, "")
+ self.toolhead_info.update({f"{field}": values})
+
+ @QtCore.pyqtSlot(str, str, float, name="on-extruder-update")
+ def on_extruder_update(
+ self, extruder_name: str, field: str, new_value: float
+ ) -> None:
+ """Handles updates from extruder printer object"""
+ if extruder_name == "extruder" and field == "temperature":
+ self.panel.extruder_temp_display.setText(f"{new_value:.1f}")
+ if extruder_name == "extruder" and field == "target":
+ self.panel.extruder_temp_display.secondary_text = f"{new_value:.1f}"
+ self.extruder_info.update({f"{extruder_name}": {f"{field}": new_value}})
+
+ @QtCore.pyqtSlot(str, str, float, name="on-heater-bed-update")
+ def on_heater_bed_update(self, name: str, field: str, new_value: float) -> None:
+ """Handles updated from heater_bed printer object"""
+ if field == "temperature":
+ self.panel.bed_temp_display.setText(f"{new_value:.1f}")
+ if field == "target":
+ self.panel.bed_temp_display.secondary_text = f"{new_value:.1f}"
+ self.bed_info.update({f"{name}": {f"{field}": new_value}})
+
+ def paintEvent(self, a0: QtGui.QPaintEvent) -> None:
+ """Handles ControlTab Widget painting"""
+ if self.panel.extrude_page.isVisible():
+ self.panel.exp_info_label.setText(self.extrude_page_message)
+ return super().paintEvent(a0)
diff --git a/BlocksScreen/lib/panels/filamentTab.py b/BlocksScreen/lib/panels/filamentTab.py
index 04fc5ef4..f5849f7b 100644
--- a/BlocksScreen/lib/panels/filamentTab.py
+++ b/BlocksScreen/lib/panels/filamentTab.py
@@ -1,12 +1,11 @@
import enum
+import typing
from functools import partial
-
-from lib.printer import Printer
from lib.filament import Filament
-from lib.ui.filamentStackedWidget_ui import Ui_filamentStackedWidget
-
from lib.panels.widgets.popupDialogWidget import Popup
+from lib.printer import Printer
+from lib.ui.filamentStackedWidget_ui import Ui_filamentStackedWidget
from PyQt6 import QtCore, QtGui, QtWidgets
@@ -18,6 +17,9 @@ class FilamentTab(QtWidgets.QStackedWidget):
request_toolhead_count = QtCore.pyqtSignal(int, name="toolhead_number_received")
run_gcode = QtCore.pyqtSignal(str, name="run_gcode")
call_load_panel = QtCore.pyqtSignal(bool, str, name="call-load-panel")
+ filament_type_changed: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
+ str, name="filament-type-changed"
+ )
class FilamentTypes(enum.Enum):
PLA = Filament(name="PLA", temperature=220)
@@ -44,6 +46,7 @@ def __init__(self, parent: QtWidgets.QWidget, printer: Printer, ws, /) -> None:
self.has_load_unload_objects = None
self._filament_state = self.FilamentStates.UNKNOWN
self._sensor_states = {}
+ self._loaded_filament_name: str = ""
self.filament_type: Filament | None = None
self.panel.filament_page_load_btn.clicked.connect(
partial(self.change_page, self.indexOf(self.panel.load_page))
@@ -52,22 +55,22 @@ def __init__(self, parent: QtWidgets.QWidget, printer: Printer, ws, /) -> None:
self.panel.load_custom_btn.hide()
self.panel.load_header_back_button.clicked.connect(self.back_button)
self.panel.load_pla_btn.clicked.connect(
- partial(self.load_filament, toolhead=0, temp=220)
+ partial(self.load_filament, toolhead=0, temp=220, name="PLA")
)
self.panel.load_petg_btn.clicked.connect(
- partial(self.load_filament, toolhead=0, temp=240)
+ partial(self.load_filament, toolhead=0, temp=240, name="PETG")
)
self.panel.load_abs_btn.clicked.connect(
- partial(self.load_filament, toolhead=0, temp=250)
+ partial(self.load_filament, toolhead=0, temp=250, name="ABS")
)
self.panel.load_hips_btn.clicked.connect(
- partial(self.load_filament, toolhead=0, temp=250)
+ partial(self.load_filament, toolhead=0, temp=250, name="HIPS")
)
self.panel.load_nylon_btn.clicked.connect(
- partial(self.load_filament, toolhead=0, temp=270)
+ partial(self.load_filament, toolhead=0, temp=270, name="Nylon")
)
self.panel.load_tpu_btn.clicked.connect(
- partial(self.load_filament, toolhead=0, temp=230)
+ partial(self.load_filament, toolhead=0, temp=230, name="TPU")
)
self.panel.filament_page_unload_btn.clicked.connect(
lambda: self.unload_filament(toolhead=0, temp=250)
@@ -122,32 +125,32 @@ def on_extruder_update(
self, extruder_name: str, field: str, new_value: float
) -> None:
"""Handle extruder update"""
- if not self.isVisible:
+ if not self.isVisible():
return
if not self.loadignore or not self.unloadignore:
if self.target_temp != 0:
if self.current_temp == self.target_temp:
- if self.isVisible:
+ if self.isVisible():
self.call_load_panel.emit(
True, "Extruder heated up \n Please wait"
)
return
if field == "temperature":
self.current_temp = round(new_value, 0)
- if self.isVisible:
+ if self.isVisible():
self.call_load_panel.emit(
True,
f"Heating up ({new_value}/{self.target_temp}) \n Please wait",
)
if field == "target":
self.target_temp = round(new_value, 0)
- if self.isVisible:
+ if self.isVisible():
self.call_load_panel.emit(True, "Heating up \n Please wait")
@QtCore.pyqtSlot(bool, name="on_load_filament")
def on_load_filament(self, status: bool):
"""Handle load filament object updated"""
- if not self.isVisible:
+ if not self.isVisible():
return
if self.loadignore:
return
@@ -158,12 +161,14 @@ def on_load_filament(self, status: bool):
self.target_temp = 0
self.call_load_panel.emit(False, "")
self._filament_state = self.FilamentStates.LOADED
+ if self._loaded_filament_name:
+ self.filament_type_changed.emit(self._loaded_filament_name)
self.handle_filament_state()
@QtCore.pyqtSlot(bool, name="on_unload_filament")
def on_unload_filament(self, status: bool):
"""Handle unload filament object updated"""
- if not self.isVisible:
+ if not self.isVisible():
return
if self.unloadignore:
return
@@ -174,12 +179,19 @@ def on_unload_filament(self, status: bool):
self.call_load_panel.emit(False, "")
self.target_temp = 0
self._filament_state = self.FilamentStates.UNLOADED
+ self._loaded_filament_name = ""
self.handle_filament_state()
- @QtCore.pyqtSlot(int, int, name="load_filament")
- def load_filament(self, toolhead: int = 0, temp: int = 220) -> None:
- """Handle load filament buttons clicked"""
- if not self.isVisible:
+ @QtCore.pyqtSlot(int, int, str, name="load_filament")
+ def load_filament(self, toolhead: int = 0, temp: int = 220, name: str = "") -> None:
+ """Handle load filament buttons clicked.
+
+ Args:
+ toolhead: Toolhead index.
+ temp: Target temperature for loading.
+ name: Filament type name (e.g. "PLA", "PETG").
+ """
+ if not self.isVisible():
return
if self._filament_state == self.FilamentStates.UNKNOWN:
@@ -194,6 +206,7 @@ def load_filament(self, toolhead: int = 0, temp: int = 220) -> None:
message="Filament is already loaded.",
)
return
+ self._loaded_filament_name = name
self.loadignore = False
self.call_load_panel.emit(True, "Loading Filament")
self.run_gcode.emit(f"LOAD_FILAMENT TOOLHEAD=load_toolhead TEMPERATURE={temp}")
@@ -201,7 +214,7 @@ def load_filament(self, toolhead: int = 0, temp: int = 220) -> None:
@QtCore.pyqtSlot(str, int, name="unload_filament")
def unload_filament(self, toolhead: int = 0, temp: int = 220) -> None:
"""Handle unload filament button clicked"""
- if not self.isVisible:
+ if not self.isVisible():
return
if self._filament_state == self.FilamentStates.UNKNOWN:
@@ -260,15 +273,15 @@ def find_routine_objects(self):
_available_objects = self.printer.available_objects.copy()
- if "load_filament" in _available_objects.keys():
+ if "load_filament" in _available_objects:
self.has_load_unload_objects = True
return True
- if "unload_filament" in _available_objects.keys():
+ if "unload_filament" in _available_objects:
self.has_load_unload_objects = True
return True
- if "gcode_macro LOAD_FILAMENT" in _available_objects.keys():
+ if "gcode_macro LOAD_FILAMENT" in _available_objects:
return True
- if "gcode_macro UNLOAD_FILAMENT" in _available_objects.keys():
+ if "gcode_macro UNLOAD_FILAMENT" in _available_objects:
return True
return False
diff --git a/BlocksScreen/lib/panels/mainWindow.py b/BlocksScreen/lib/panels/mainWindow.py
index 12fbc628..baca9b52 100644
--- a/BlocksScreen/lib/panels/mainWindow.py
+++ b/BlocksScreen/lib/panels/mainWindow.py
@@ -106,7 +106,7 @@ class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
"""Set up UI, instantiate subsystems, and wire all inter-component signals."""
- super(MainWindow, self).__init__()
+ super().__init__()
self.config: BlocksScreenConfig = get_configparser()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
@@ -125,6 +125,7 @@ def __init__(self):
self.mc = MachineControl(self)
self.file_data = Files(self, self.ws)
self.index_stack = deque(maxlen=4)
+ self._printing_active = False
self.printer = Printer(self, self.ws)
self.conn_window = ConnectionPage(self, self.ws)
self.update_page = UpdatePage(self)
@@ -154,6 +155,7 @@ def __init__(self):
self.printPanel.request_change_page.connect(slot=self.global_change_page)
self.filamentPanel.request_back.connect(slot=self.global_back)
self.filamentPanel.request_change_page.connect(slot=self.global_change_page)
+ self.filamentPanel.filament_type_changed.connect(self.set_header_filament_type)
self.controlPanel.request_back_button.connect(slot=self.global_back)
self.controlPanel.request_change_page.connect(slot=self.global_change_page)
self.utilitiesPanel.request_back.connect(slot=self.global_back)
@@ -253,8 +255,9 @@ def __init__(self):
)
self.loadscreen.add_widget(self.loadwidget)
self.controlPanel.toggle_conn_page.connect(self.conn_window.set_toggle)
- self.cancelpage = CancelPage(self, ws=self.ws)
+ self.cancelpage = CancelPage(self)
self.cancelpage.request_file_info.connect(self.file_data.on_request_fileinfo)
+ self.cancelpage.reprint_start.connect(self.ws.api.start_print)
self.cancelpage.run_gcode.connect(self.ws.api.run_gcode)
self.printer.print_stats_update[str, str].connect(
self.cancelpage.on_print_stats_update
@@ -333,9 +336,13 @@ def show_update_page(self, fullscreen: bool):
@QtCore.pyqtSlot(name="on-cancel-print")
def on_cancel_print(self):
"""Slot for cancel print signal"""
+ self._printing_active = False
self.enable_tab_bar()
- self.ui.extruder_temp_display.clicked.disconnect()
- self.ui.bed_temp_display.clicked.disconnect()
+ try:
+ self.ui.extruder_temp_display.clicked.disconnect()
+ self.ui.bed_temp_display.clicked.disconnect()
+ except TypeError:
+ pass
self.ui.filament_type_icon.setDisabled(False)
self.ui.nozzle_size_icon.setDisabled(False)
self.ui.extruder_temp_display.clicked.connect(
@@ -378,16 +385,16 @@ def enable_tab_bar(self) -> bool:
self.ui.header_main_layout.setEnabled(True)
return all(
[
- not self.ui.main_content_widget.isTabEnabled(
+ self.ui.main_content_widget.isTabEnabled(
self.ui.main_content_widget.indexOf(self.ui.filamentTab)
),
- not self.ui.main_content_widget.isTabEnabled(
+ self.ui.main_content_widget.isTabEnabled(
self.ui.main_content_widget.indexOf(self.ui.controlTab)
),
- not self.ui.main_content_widget.isTabEnabled(
+ self.ui.main_content_widget.isTabEnabled(
self.ui.main_content_widget.indexOf(self.ui.utilitiesTab)
),
- not self.ui.header_main_layout.isEnabled(),
+ self.ui.header_main_layout.isEnabled(),
]
)
@@ -513,8 +520,18 @@ def global_change_page(self, tab_index: int, panel_index: int) -> None:
_logger.debug("User is already on the requested page")
return
self.index_stack.append(current_page)
+ # Temporarily enable the target tab so setCurrentIndex works,
+ # then re-disable the tab bar if a print is active.
+ was_enabled = self.ui.main_content_widget.isTabEnabled(tab_index)
+ if not was_enabled:
+ self.ui.main_content_widget.setTabEnabled(tab_index, True)
self.ui.main_content_widget.setCurrentIndex(tab_index)
self.set_current_panel_index(panel_index)
+ if self._printing_active:
+ self.disable_tab_bar()
+ # Keep the target tab enabled — Qt auto-switches away from
+ # a disabled current tab, which undoes the navigation.
+ self.ui.main_content_widget.setTabEnabled(tab_index, True)
_logger.debug(
f"Requested page change -> Tab index : {requested_page[0]} | panel index : {requested_page[1]}",
)
@@ -525,9 +542,16 @@ def global_back(self) -> None:
if not bool(self.index_stack):
_logger.debug("Index stack is empty, cannot go back any further")
return
- self.ui.main_content_widget.setCurrentIndex(self.index_stack[-1][0])
- self.set_current_panel_index(self.index_stack[-1][1])
- self.index_stack.pop() # Remove the last position.
+ tab_index, panel_index = self.index_stack[-1]
+ was_enabled = self.ui.main_content_widget.isTabEnabled(tab_index)
+ if not was_enabled:
+ self.ui.main_content_widget.setTabEnabled(tab_index, True)
+ self.ui.main_content_widget.setCurrentIndex(tab_index)
+ self.set_current_panel_index(panel_index)
+ if self._printing_active:
+ self.disable_tab_bar()
+ self.ui.main_content_widget.setTabEnabled(tab_index, True)
+ self.index_stack.pop()
_logger.debug("Successfully went back a page.")
@QtCore.pyqtSlot(name="bo-start-websocket-connection")
@@ -560,7 +584,7 @@ def messageReceivedEvent(self, event: events.WebSocketMessageReceived) -> None:
return
api_reference = _method.split(".")
if "klippy" in _method:
- api_reference = "notify_klippy"
+ api_reference = ["notify_klippy"]
method_handle = f"_handle_{api_reference[0]}_message"
if hasattr(self, method_handle):
obj = getattr(self, method_handle)
@@ -576,10 +600,8 @@ def _handle_server_message(self, method, data, metadata) -> None:
QtWidgets.QApplication.postEvent(self.file_data, file_data_event)
except Exception as e:
_logger.error(
- (
- "Error posting event for file related information",
- "received from websocket | error message received: %s",
- ),
+ "Error posting event for file related information "
+ "received from websocket | error message received: %s",
str(e),
)
@@ -589,20 +611,18 @@ def _handle_machine_message(self, method, data, metadata) -> None:
if "ok" in data:
return
if "update" in method:
- if ("status" or "refresh") in method:
+ if "status" in method or "refresh" in method:
self.on_update_message.emit(dict(data))
@api_handler
def _handle_notify_update_response_message(self, method, data, metadata) -> None:
"""Handle update response messages"""
- self.on_update_message.emit(
- dict(dict(data.get("params", {})[0]))
- ) # Also necessary, notify klippy can also signal update complete
+ self.on_update_message.emit(dict(dict(data.get("params", [{}])[0])))
@api_handler
def _handle_notify_update_refreshed_message(self, method, data, metadata) -> None:
"""Handle update refreshed messages"""
- self.on_update_message.emit(dict(data.get("params", {})[0]))
+ self.on_update_message.emit(dict(data.get("params", [{}])[0]))
@api_handler
def _handle_printer_message(self, method, data, metadata) -> None:
@@ -621,15 +641,19 @@ def _handle_printer_message(self, method, data, metadata) -> None:
self.printer_state_signal.emit("canceled")
if "objects" in method:
if "list" in method:
- _object_list: list = data["objects"]
+ _object_list: list = data.get("objects", [])
self.query_object_list[list].emit(_object_list)
if "subscribe" in method:
- _objects_response_list = [data["status"], data["eventtime"]]
+ _objects_response_list = [
+ data.get("status", {}),
+ data.get("eventtime", 0),
+ ]
self.printer_object_report_signal[list].emit(_objects_response_list)
if "query" in method:
- if isinstance(data["status"], dict):
- _object_report = [data["status"]]
- _object_report_keys = data["status"].items()
+ _query_status = data.get("status")
+ if isinstance(_query_status, dict):
+ _object_report = [_query_status]
+ _object_report_keys = _query_status.items()
_object_report_list_dict: list = []
for _, key in enumerate(_object_report_keys):
_helper_dict: dict = {key[0]: key[1]}
@@ -690,7 +714,9 @@ def _handle_notify_service_state_changed_message(
if self._popup_toggle:
return
service_entry: dict = entry[0]
- service_name, service_info = service_entry.popitem()
+ if not service_entry:
+ return
+ service_name, service_info = next(iter(service_entry.items()))
self.show_notifications.emit(
"mainwindow",
str(
@@ -703,12 +729,15 @@ def _handle_notify_service_state_changed_message(
@api_handler
def _handle_notify_gcode_response_message(self, method, data, metadata) -> None:
"""Handle websocket gcode responses messages"""
- _gcode_response = data.get("params")
+ _gcode_response = data.get("params", [])
self.gcode_response[list].emit(_gcode_response)
if _gcode_response:
if self._popup_toggle:
return
- _gcode_msg_type, _message = str(_gcode_response[0]).split(" ", maxsplit=1)
+ _parts = str(_gcode_response[0]).split(" ", maxsplit=1)
+ if len(_parts) < 2:
+ return
+ _gcode_msg_type, _message = _parts
popupWhitelist = ["filament runout", "no filament"]
if _message.lower() not in popupWhitelist or _gcode_msg_type != "!!":
return
@@ -753,22 +782,27 @@ def _handle_notify_cpu_throttled_message(self, method, data, metadata) -> None:
"Currently Throttled": 1 << 2,
"Temperature Limit Active": 1 << 3,
}
- _bits = data.get("bits", None)
- if not _bits:
+ _params = data.get("params", [{}])
+ _bits = _params[0].get("bits") if _params else None
+ if _bits is None:
self.show_notifications.emit(
"mainWindow", "Cpu throttled unknown reason", 2, False
)
return
+ if _bits == 0:
+ return
_active_flags = [name for name, mask in flags.items() if _bits & mask]
self.show_notifications.emit("mainwindow", str(_active_flags), 2, False)
except Exception:
- logging.debug("Error emitting notification for cpu throttled notification.")
+ _logger.debug("Error emitting notification for cpu throttled notification.")
return
@api_handler
def _handle_notify_status_update_message(self, method, data, metadata) -> None:
"""Handle websocket printer objects status update messages"""
- _object_report = data["params"]
+ _object_report = data.get("params")
+ if not _object_report:
+ return
self.printer_object_report_signal[list].emit(_object_report)
@QtCore.pyqtSlot(str, str, float, name="on-extruder-update")
@@ -825,9 +859,13 @@ def event(self, event: QtCore.QEvent) -> bool:
return True
return False
if event.type() == events.PrintStart.type():
+ self._printing_active = True
self.disable_tab_bar()
- self.ui.extruder_temp_display.clicked.disconnect()
- self.ui.bed_temp_display.clicked.disconnect()
+ try:
+ self.ui.extruder_temp_display.clicked.disconnect()
+ self.ui.bed_temp_display.clicked.disconnect()
+ except TypeError:
+ pass
self.ui.filament_type_icon.setDisabled(True)
self.ui.nozzle_size_icon.setDisabled(True)
self.ui.extruder_temp_display.clicked.connect(
@@ -851,9 +889,13 @@ def event(self, event: QtCore.QEvent) -> bool:
):
if event.type() == events.PrintCancelled.type():
self.handle_cancel_print()
+ self._printing_active = False
self.enable_tab_bar()
- self.ui.extruder_temp_display.clicked.disconnect()
- self.ui.bed_temp_display.clicked.disconnect()
+ try:
+ self.ui.extruder_temp_display.clicked.disconnect()
+ self.ui.bed_temp_display.clicked.disconnect()
+ except TypeError:
+ pass
self.ui.filament_type_icon.setDisabled(False)
self.ui.nozzle_size_icon.setDisabled(False)
self.ui.extruder_temp_display.clicked.connect(
@@ -874,4 +916,4 @@ def event(self, event: QtCore.QEvent) -> bool:
def sizeHint(self) -> QtCore.QSize:
"""Sets default size for the widget"""
self.adjustSize()
- return super().sizeHint(QtCore.QSize(800, 480))
+ return QtCore.QSize(800, 480)
diff --git a/BlocksScreen/lib/panels/networkWindow.py b/BlocksScreen/lib/panels/networkWindow.py
index f9b17a52..b39a1bd5 100644
--- a/BlocksScreen/lib/panels/networkWindow.py
+++ b/BlocksScreen/lib/panels/networkWindow.py
@@ -35,7 +35,6 @@
from lib.utils.icon_button import IconButton
from lib.utils.list_model import EntryDelegate, EntryListModel, ListItem
from PyQt6 import QtCore, QtGui, QtWidgets
-from PyQt6.QtCore import QTimer, pyqtSlot
logger = logging.getLogger(__name__)
@@ -255,7 +254,7 @@ def _prefill_ip_from_os(self) -> None:
except OSError:
continue
- @pyqtSlot()
+ @QtCore.pyqtSlot()
def _on_reconnect_complete(self) -> None:
"""Navigate back to the main panel after a static-IP or DHCP-reset operation."""
logger.debug("reconnect_complete received — navigating to main_network_page")
@@ -263,7 +262,7 @@ def _on_reconnect_complete(self) -> None:
def _init_timers(self) -> None:
"""Initialize timers."""
- self._load_timer = QTimer(self)
+ self._load_timer = QtCore.QTimer(self)
self._load_timer.setSingleShot(True)
self._load_timer.timeout.connect(self._handle_load_timeout)
@@ -277,7 +276,7 @@ def _init_model_view(self) -> None:
self._entry_delegate.item_selected.connect(self._on_ssid_item_clicked)
self._configure_list_view_palette()
- @pyqtSlot(NetworkState)
+ @QtCore.pyqtSlot(NetworkState)
def _on_network_state_changed(self, state: NetworkState) -> None:
"""React to a NetworkState update: sync toggles, populate header and connection info."""
logger.debug(
@@ -438,7 +437,7 @@ def _on_network_state_changed(self, state: NetworkState) -> None:
self._emit_status_icon(state)
self._sync_active_network_list_icon(state)
- @pyqtSlot(list)
+ @QtCore.pyqtSlot(list)
def _on_scan_complete(self, networks: list[NetworkInfo]) -> None:
"""Receive scan results, filter/sort them, and rebuild the SSID list view.
@@ -457,9 +456,11 @@ def _on_scan_complete(self, networks: list[NetworkInfo]) -> None:
# Stamp the connected AP as ACTIVE so the list is correct on first
# render even when the scan ran before the connection fully settled.
filtered = [
- replace(net, network_status=NetworkStatus.ACTIVE)
- if net.ssid == current_ssid
- else net
+ (
+ replace(net, network_status=NetworkStatus.ACTIVE)
+ if net.ssid == current_ssid
+ else net
+ )
for net in filtered
]
active = next((n for n in filtered if n.ssid == current_ssid), None)
@@ -478,12 +479,12 @@ def _on_scan_complete(self, networks: list[NetworkInfo]) -> None:
state = self._nm.current_state
self._emit_status_icon(state)
- @pyqtSlot(list)
+ @QtCore.pyqtSlot(list)
def _on_saved_networks_loaded(self, networks: list[SavedNetwork]) -> None:
"""Receive saved-network data and update the priority spinbox for the active SSID."""
logger.debug("Loaded %d saved networks", len(networks))
- @pyqtSlot(ConnectionResult)
+ @QtCore.pyqtSlot(ConnectionResult)
def _on_operation_complete(self, result: ConnectionResult) -> None:
"""Handle network operation completion."""
logger.debug("Operation: success=%s, msg=%s", result.success, result.message)
@@ -570,7 +571,7 @@ def _on_operation_complete(self, result: ConnectionResult) -> None:
result.message,
)
ssid = self._target_ssid
- QTimer.singleShot(
+ QtCore.QTimer.singleShot(
2000, lambda _ssid=ssid: self._nm.connect_network(_ssid)
)
return # Keep loading visible; state machine handles completion
@@ -578,7 +579,7 @@ def _on_operation_complete(self, result: ConnectionResult) -> None:
self._clear_loading()
self._show_error_popup(result.message)
- @pyqtSlot(str, str)
+ @QtCore.pyqtSlot(str, str)
def _on_network_error(self, operation: str, message: str) -> None:
"""Log network errors and surface critical failures in the info box."""
logger.error("Network error [%s]: %s", operation, message)
@@ -658,13 +659,15 @@ def _sync_active_network_list_icon(self, state: NetworkState) -> None:
# Update the cached entry with the authoritative signal and status
updated = [
- replace(
- net,
- signal_strength=self._active_signal,
- network_status=NetworkStatus.ACTIVE,
+ (
+ replace(
+ net,
+ signal_strength=self._active_signal,
+ network_status=NetworkStatus.ACTIVE,
+ )
+ if net.ssid == state.current_ssid
+ else net
)
- if net.ssid == state.current_ssid
- else net
for net in self._cached_scan_networks
]
@@ -1063,7 +1066,9 @@ def _handle_wifi_toggle(self, is_on: bool) -> None:
# Non-blocking: disable hotspot then connect
self._nm.toggle_hotspot(False)
_ssid_to_connect = self._target_ssid
- QTimer.singleShot(500, lambda: self._nm.connect_network(_ssid_to_connect))
+ QtCore.QTimer.singleShot(
+ 500, lambda: self._nm.connect_network(_ssid_to_connect)
+ )
def _handle_hotspot_toggle(self, is_on: bool) -> None:
"""Enable or disable the hotspot, enforcing the ethernet/Wi-Fi mutual-exclusion rule."""
diff --git a/BlocksScreen/lib/panels/printTab.py b/BlocksScreen/lib/panels/printTab.py
index 3f53c91a..bc1ce03b 100644
--- a/BlocksScreen/lib/panels/printTab.py
+++ b/BlocksScreen/lib/panels/printTab.py
@@ -146,6 +146,7 @@ def __init__(
self.file_data.usb_files_loaded.connect(
self.filesPage_widget.on_usb_files_loaded
)
+ self.file_data.fileinfo.connect(self.confirmPage_widget.on_fileinfo)
self.jobStatusPage_widget = JobStatusWidget(self)
self.addWidget(self.jobStatusPage_widget)
self.confirmPage_widget.on_accept.connect(
@@ -292,6 +293,10 @@ def on_numpad_request(
max_value: int = 100,
) -> None:
"""Handle numpad request"""
+ try:
+ self.numpadPage.value_selected.disconnect()
+ except (RuntimeError, TypeError):
+ pass
self.numpadPage.value_selected.connect(callback)
self.numpadPage.set_name(name)
self.numpadPage.set_value(current_value)
@@ -311,6 +316,10 @@ def on_slidePage_request(
max_value: int = 100,
) -> None:
"""Handle slider page request"""
+ try:
+ self.sliderPage.value_selected.disconnect()
+ except (RuntimeError, TypeError):
+ pass
self.sliderPage.value_selected.connect(callback)
self.sliderPage.set_name(name)
self.sliderPage.set_slider_position(int(current_value))
diff --git a/BlocksScreen/lib/panels/utilitiesTab.py b/BlocksScreen/lib/panels/utilitiesTab.py
index 6cff5f27..d2dc1679 100644
--- a/BlocksScreen/lib/panels/utilitiesTab.py
+++ b/BlocksScreen/lib/panels/utilitiesTab.py
@@ -1,9 +1,14 @@
+import logging
+import re
import typing
from dataclasses import dataclass
from enum import Enum, auto
from functools import partial
from lib.moonrakerComm import MoonWebSocket
+from lib.panels.widgets.basePopup import BasePopup
+from lib.panels.widgets.inputshaperPage import InputShaperPage
+from lib.panels.widgets.optionCardWidget import OptionCard
from lib.panels.widgets.troubleshootPage import TroubleshootPage
from lib.printer import Printer
from lib.ui.utilitiesStackedWidget_ui import Ui_utilitiesStackedWidget
@@ -11,11 +16,7 @@
from lib.utils.toggleAnimatedButton import ToggleAnimatedButton
from PyQt6 import QtCore, QtGui, QtWidgets
-from lib.panels.widgets.optionCardWidget import OptionCard
-from lib.panels.widgets.inputshaperPage import InputShaperPage
-from lib.panels.widgets.basePopup import BasePopup
-
-import re
+logger = logging.getLogger(__name__)
@dataclass
@@ -113,8 +114,8 @@ def __init__(
self.x_inputshaper: dict = {}
self.stepper_limits: dict = {}
- self.current_object: typing.Optional[str] = None
- self.current_process: typing.Optional[Process] = None
+ self.current_object: str | None = None
+ self.current_process: Process | None = None
self.axis_in: str = "x"
self.amount: int = 1
self.tb: bool = False
@@ -250,8 +251,9 @@ def handle_gcode_response(self, data: list[str]) -> None:
"""
if not isinstance(data, list) or len(data) != 1 or not isinstance(data[0], str):
- print(
- f"WARNING: Invalid input format. Expected a list with one string. Received: {data}"
+ logger.warning(
+ "handle_gcode_response: invalid input format. Expected list[str], received: %r",
+ data,
)
return
@@ -317,7 +319,7 @@ def handle_gcode_response(self, data: list[str]) -> None:
self.is_page.set_type_dictionary(self.is_types)
first_key = next(iter(reordered.keys()), None)
- for key in reordered.keys():
+ for key in reordered:
if key == first_key:
self.is_page.add_type_entry(key, "Recommended type")
else:
@@ -365,7 +367,7 @@ def on_object_list(self, object_list: list) -> None:
@QtCore.pyqtSlot(dict, name="on_object_config")
@QtCore.pyqtSlot(list, name="on_object_config")
- def on_object_config(self, config: typing.Union[dict, list]) -> None:
+ def on_object_config(self, config: dict | list) -> None:
"""Handle receiving printer object configurations"""
if not config:
return
@@ -381,12 +383,12 @@ def on_object_config(self, config: typing.Union[dict, list]) -> None:
pos_max = value.get("position_max")
if pos_min is not None or pos_max is not None:
self.stepper_limits[key] = {
- "min": float(pos_min)
- if pos_min is not None
- else -float("inf"),
- "max": float(pos_max)
- if pos_max is not None
- else float("inf"),
+ "min": (
+ float(pos_min) if pos_min is not None else -float("inf")
+ ),
+ "max": (
+ float(pos_max) if pos_max is not None else float("inf")
+ ),
}
def on_printer_config_received(self, config: dict) -> None:
diff --git a/BlocksScreen/lib/panels/widgets/bannerPopup.py b/BlocksScreen/lib/panels/widgets/bannerPopup.py
index 7db54547..99077539 100644
--- a/BlocksScreen/lib/panels/widgets/bannerPopup.py
+++ b/BlocksScreen/lib/panels/widgets/bannerPopup.py
@@ -80,6 +80,8 @@ def _calculate_target_geometry(self) -> QtCore.QRect:
if isinstance(widget, QtWidgets.QMainWindow):
main_window = widget
break
+ if main_window is None:
+ return QtCore.QRect()
parent_rect = main_window.geometry()
width = int(parent_rect.width() * 0.35)
height = 80
diff --git a/BlocksScreen/lib/panels/widgets/basePopup.py b/BlocksScreen/lib/panels/widgets/basePopup.py
index a9a4d188..16c42c85 100644
--- a/BlocksScreen/lib/panels/widgets/basePopup.py
+++ b/BlocksScreen/lib/panels/widgets/basePopup.py
@@ -1,5 +1,3 @@
-import typing
-
from PyQt6 import QtCore, QtGui, QtWidgets
@@ -47,13 +45,11 @@ def __init__(
self.setAttribute(QtCore.Qt.WidgetAttribute.WA_TranslucentBackground, True)
self.setWindowModality(QtCore.Qt.WindowModality.ApplicationModal)
else:
- self.setStyleSheet(
- """
+ self.setStyleSheet("""
#MyParent {
background-image: url(:/background/media/1st_background.png);
}
- """
- )
+ """)
def _update_button_style(self) -> None:
"""Applies the current color variables and adds the central border to the stylesheets."""
@@ -61,26 +57,21 @@ def _update_button_style(self) -> None:
return
if not self.floating:
- self.confirm_button.setStyleSheet(
- f"""
+ self.confirm_button.setStyleSheet(f"""
background-color: {self.confirm_bk_color};
color: {self.confirm_ft_color};
border: none;
padding: 10px;
- """
- )
+ """)
- self.cancel_button.setStyleSheet(
- f"""
+ self.cancel_button.setStyleSheet(f"""
background-color: {self.cancel_bk_color};
color: {self.cancel_ft_color};
border: none;
padding: 10px;
- """
- )
+ """)
else:
- self.confirm_button.setStyleSheet(
- f"""
+ self.confirm_button.setStyleSheet(f"""
background-color: {self.confirm_bk_color};
color: {self.confirm_ft_color};
border-top: none;
@@ -89,11 +80,9 @@ def _update_button_style(self) -> None:
border-right: 1px solid #80807e;
border-bottom-left-radius: 16px;
padding: 10px;
- """
- )
+ """)
- self.cancel_button.setStyleSheet(
- f"""
+ self.cancel_button.setStyleSheet(f"""
background-color: {self.cancel_bk_color};
color: {self.cancel_ft_color};
border-left: 1px solid #80807e;;
@@ -101,8 +90,7 @@ def _update_button_style(self) -> None:
border-right: 2px solid #80807e;
border-bottom-right-radius: 16px;
padding: 10px;
- """
- )
+ """)
def set_message(self, message: str) -> None:
self.label.setText(message)
@@ -151,7 +139,7 @@ def add_widget(self, widget: QtWidgets.QWidget) -> None:
layout.insertWidget(index, widget)
widget.show()
- def _get_mainWindow_widget(self) -> typing.Optional[QtWidgets.QMainWindow]:
+ def _get_mainWindow_widget(self) -> QtWidgets.QMainWindow | None:
"""Get the main application window"""
app_instance = QtWidgets.QApplication.instance()
if not app_instance:
diff --git a/BlocksScreen/lib/panels/widgets/cancelPage.py b/BlocksScreen/lib/panels/widgets/cancelPage.py
index e16fb2e6..832c2f29 100644
--- a/BlocksScreen/lib/panels/widgets/cancelPage.py
+++ b/BlocksScreen/lib/panels/widgets/cancelPage.py
@@ -1,17 +1,16 @@
+import logging
+import typing
+
from lib.utils.blocks_button import BlocksCustomButton
from lib.utils.blocks_frame import BlocksCustomFrame
from lib.utils.blocks_label import BlocksLabel
from PyQt6 import QtCore, QtGui, QtWidgets
-import typing
-from lib.moonrakerComm import MoonWebSocket
+logger = logging.getLogger(__name__)
class CancelPage(QtWidgets.QWidget):
- """Update GUI Page,
- retrieves from moonraker available clients and adds functionality
- for updating or recovering them
- """
+ """Displayed when a print is cancelled; offers reprint or ignore."""
request_file_info: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
str, name="request_file_info"
@@ -23,16 +22,13 @@ class CancelPage(QtWidgets.QWidget):
str, name="run_gcode"
)
- def __init__(self, parent: QtWidgets.QWidget, ws: MoonWebSocket) -> None:
+ def __init__(self, parent: QtWidgets.QWidget) -> None:
super().__init__(parent)
- self.ws: MoonWebSocket = ws
self._setupUI()
self.filename = ""
-
- self.reprint_start.connect(self.ws.api.start_print)
+ self._thumbnail_scan_done: bool = False
self.confirm_button.clicked.connect(lambda: self._handle_accept())
-
self.refuse_button.clicked.connect(lambda: self._handle_refuse())
self.setAttribute(QtCore.Qt.WidgetAttribute.WA_StyledBackground, True)
@@ -52,8 +48,10 @@ def _handle_refuse(self):
def on_print_stats_update(self, field: str, value: dict | float | str) -> None:
if isinstance(value, str):
if "filename" in field:
+ if value != self.filename:
+ self._thumbnail_scan_done = False
self.filename = value
- if self.isVisible:
+ if self.isVisible():
self.set_file_name(value)
def show(self):
@@ -91,22 +89,25 @@ def set_pixmap(self, pixmap: QtGui.QPixmap) -> None:
def set_file_name(self, file_name: str) -> None:
self.cf_file_name.setText(file_name)
- def _show_screen_thumbnail(self, dict):
- try:
- thumbnails = dict["thumbnail_images"]
+ def _show_screen_thumbnail(self, metadata: dict | None) -> None:
+ """Display the largest thumbnail from file metadata.
- last_thumb = QtGui.QPixmap.fromImage(thumbnails[-1])
-
- if last_thumb.isNull():
- last_thumb = QtGui.QPixmap(
- "BlocksScreen/lib/ui/resources/media/logoblocks400x300.png"
- )
- except Exception as e:
- print(e)
- last_thumb = QtGui.QPixmap(
- "BlocksScreen/lib/ui/resources/media/logoblocks400x300.png"
- )
- self.set_pixmap(last_thumb)
+ ``thumbnail_images`` values are pre-loaded ``QImage``
+ objects produced by ``Files._process_metadata``.
+ """
+ fallback = QtGui.QPixmap(
+ "BlocksScreen/lib/ui/resources/media/logoblocks400x300.png"
+ )
+ thumbnails = metadata.get("thumbnail_images", []) if metadata else []
+ if not thumbnails:
+ self.set_pixmap(fallback)
+ return
+
+ last_thumb = thumbnails[-1]
+ if isinstance(last_thumb, QtGui.QImage) and not last_thumb.isNull():
+ self.set_pixmap(QtGui.QPixmap.fromImage(last_thumb))
+ else:
+ self.set_pixmap(fallback)
def _setupUI(self) -> None:
"""Setup widget ui"""
@@ -119,11 +120,9 @@ def _setupUI(self) -> None:
sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
self.setSizePolicy(sizePolicy)
self.setObjectName("cancelPage")
- self.setStyleSheet(
- """#cancelPage {
+ self.setStyleSheet("""#cancelPage {
background-image: url(:/background/media/1st_background.png);
- }"""
- )
+ }""")
self.setMinimumSize(QtCore.QSize(800, 480))
self.setMaximumSize(QtCore.QSize(800, 480))
self.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight)
diff --git a/BlocksScreen/lib/panels/widgets/confirmPage.py b/BlocksScreen/lib/panels/widgets/confirmPage.py
index 0f35ba39..d759f0ea 100644
--- a/BlocksScreen/lib/panels/widgets/confirmPage.py
+++ b/BlocksScreen/lib/panels/widgets/confirmPage.py
@@ -1,3 +1,4 @@
+import logging
import os
import typing
@@ -8,8 +9,12 @@
from lib.utils.icon_button import IconButton
from PyQt6 import QtCore, QtGui, QtWidgets
+logger = logging.getLogger(__name__)
+
class ConfirmWidget(QtWidgets.QWidget):
+ """Widget displayed when a user selects a file to print."""
+
on_accept: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
str, name="on_accept"
)
@@ -26,7 +31,6 @@ def __init__(self, parent) -> None:
self.setMouseTracking(True)
self.setAttribute(QtCore.Qt.WidgetAttribute.WA_AcceptTouchEvents, True)
self.thumbnail: QtGui.QImage = self._blocksthumbnail
- self._thumbnails: typing.List = []
self.directory = "gcodes"
self.filename = ""
self.confirm_button.clicked.connect(
@@ -39,32 +43,34 @@ def __init__(self, parent) -> None:
lambda: self.on_delete.emit(self.filename, self.directory)
)
- @QtCore.pyqtSlot(str, dict, name="on_show_widget")
- def on_show_widget(self, text: str, filedata: dict | None = None) -> None:
- """Handle widget show"""
- if not filedata:
- return
+ @QtCore.pyqtSlot(str, object, name="on_show_widget")
+ def on_show_widget(self, text: str, metadata: dict | None = None) -> None:
+ """Handle widget show."""
directory = os.path.dirname(text)
filename = os.path.basename(text)
self.directory = directory
self.filename = filename
self.cf_file_name.setText(self.filename)
- self._thumbnails = filedata.get("thumbnail_images", [])
- if self._thumbnails:
- _biggest_thumbnail = self._thumbnails[-1] # Show last which is biggest
- self.thumbnail = QtGui.QImage(_biggest_thumbnail)
- else:
+ if metadata is None:
self.thumbnail = self._blocksthumbnail
- _total_filament = filedata.get("filament_weight_total")
- _estimated_time = filedata.get("estimated_time")
- if isinstance(_estimated_time, str):
- seconds = 0
- else:
- seconds = _estimated_time
+ self.cf_info_tf.setText("Total Filament: loading...")
+ self.cf_info_tr.setText("Slicer time: loading...")
+ self.update()
+ return
+ self._update_metadata_labels(metadata)
+ self.update()
+
+ def _update_metadata_labels(self, metadata: dict) -> None:
+ """Update thumbnail and text labels from metadata."""
+ self._apply_thumbnail(metadata)
+ raw_weight = metadata.get("filament_weight_total", 0)
+ _total_filament: float | str = raw_weight if raw_weight > 0 else 0
+ seconds = metadata.get("estimated_time", 0)
+ seconds = seconds if seconds > 0 else 0
days, hours, minutes, _ = helper_methods.estimate_print_time(seconds)
if seconds <= 0:
- time_str = "??"
+ time_str = "Unknown"
elif seconds < 60:
time_str = "less than 1 minute"
else:
@@ -83,9 +89,39 @@ def on_show_widget(self, text: str, filedata: dict | None = None) -> None:
_total_filament = str("%.2f" % _total_filament) + "g"
filament_label = f"Total Filament: {_total_filament}"
time_label = f"Slicer time: {time_str}"
- self.cf_info_tf.setText(f"{filament_label}")
- self.cf_info_tr.setText(f"{time_label}")
- self.repaint()
+ self.cf_info_tf.setText(filament_label)
+ self.cf_info_tr.setText(time_label)
+
+ def _apply_thumbnail(self, metadata: dict) -> None:
+ """Set self.thumbnail from metadata, falling back to the logo."""
+ thumbnails = metadata.get("thumbnail_images", [])
+ if thumbnails:
+ last = thumbnails[-1]
+ if isinstance(last, QtGui.QImage) and not last.isNull():
+ self.thumbnail = last
+ return
+ self.thumbnail = self._blocksthumbnail
+
+ @QtCore.pyqtSlot(dict, name="on_fileinfo")
+ def on_fileinfo(self, metadata: dict) -> None:
+ """Update thumbnail and metadata labels when new data arrives."""
+ if not metadata or not self.filename:
+ return
+ incoming = metadata.get("filename", "")
+ current = (
+ f"{self.directory}/{self.filename}" if self.directory else self.filename
+ )
+ # Also accept bare-filename match for USB files: Moonraker may strip the
+ # USB directory prefix from the returned filename.
+ is_usb_bare_match = (
+ incoming == self.filename
+ and self.directory.startswith("USB-")
+ and incoming == os.path.basename(incoming)
+ )
+ if incoming != current and not is_usb_bare_match:
+ return
+ self._update_metadata_labels(metadata)
+ self.update()
def estimate_print_time(self, seconds: int) -> list:
"""Convert time in seconds format to days, hours, minutes, seconds.
@@ -142,8 +178,8 @@ def paintEvent(self, event: QtGui.QPaintEvent) -> None:
def showEvent(self, a0: QtGui.QShowEvent) -> None:
"""Re-implemented method, Handle widget show event"""
- if not self.thumbnail:
- self.cf_thumbnail.close()
+ if self.thumbnail.isNull():
+ self.cf_thumbnail.hide()
return super().showEvent(a0)
def _setupUI(self) -> None:
@@ -252,7 +288,6 @@ def _setupUI(self) -> None:
"icon_pixmap", QtGui.QPixmap(":/dialog/media/btn_icons/yes.svg")
)
self.confirm_button.setText("Print")
- # 2. Align buttons to the right
self.cf_confirm_layout.addWidget(
self.confirm_button, 0, QtCore.Qt.AlignmentFlag.AlignCenter
)
@@ -266,7 +301,6 @@ def _setupUI(self) -> None:
"icon_pixmap", QtGui.QPixmap(":/ui/media/btn_icons/garbage-icon.svg")
)
self.delete_file_button.setText("Delete")
- # 2. Align buttons to the right
self.cf_confirm_layout.addWidget(
self.delete_file_button, 0, QtCore.Qt.AlignmentFlag.AlignCenter
)
diff --git a/BlocksScreen/lib/panels/widgets/connectionPage.py b/BlocksScreen/lib/panels/widgets/connectionPage.py
index 3cd1fc21..745b8195 100644
--- a/BlocksScreen/lib/panels/widgets/connectionPage.py
+++ b/BlocksScreen/lib/panels/widgets/connectionPage.py
@@ -57,7 +57,7 @@ def __init__(self, parent: QtWidgets.QWidget, ws: MoonWebSocket, /):
self.restart_klipper_clicked.emit
)
self.ws.connection_lost.connect(slot=self.show)
- self.ws.klippy_connected_signal.connect(self.on_klippy_connected)
+ self.ws.klippy_connected_signal.connect(self.on_klippy_connection)
self.ws.klippy_state_signal.connect(self.on_klippy_state)
@QtCore.pyqtSlot(bool, name="toggle_connection_page")
@@ -82,7 +82,7 @@ def showEvent(self, a0: QtCore.QEvent | None):
self.call_cancel_panel.emit(False)
return super().showEvent(a0)
- @QtCore.pyqtSlot(bool, name="on_klippy_connected")
+ @QtCore.pyqtSlot(bool, name="on_klippy_connection")
def on_klippy_connection(self, connected: bool):
"""Handle klippy connection state"""
self.dot_timer.stop()
@@ -143,15 +143,13 @@ def text_update(self, text: int | str | None = None):
if self.state == "shutdown" and self.message is not None:
return False
self.dot_timer.stop()
- logger.debug(f"[ConnectionWindowPanel] text_update: {text}")
+ logger.debug("[ConnectionWindowPanel] text_update: %r", text)
if text == "wb lost":
self.panel.connectionTextBox.setText("Moonraker connection lost")
if text is None:
- self.panel.connectionTextBox.setText(
- """
+ self.panel.connectionTextBox.setText("""
Not connected to Moonraker Websocket
- """
- )
+ """)
return True
if isinstance(text, str):
self.panel.connectionTextBox.setText(
@@ -208,7 +206,7 @@ def eventFilter(self, object: QtCore.QObject, event: QtCore.QEvent) -> bool:
elif event.type() == KlippyShutdown.type():
self.dot_timer.stop()
if not self.isVisible():
- self.panel.connectionTextBox.setText(f"{self.message}")
+ self.panel.connectionTextBox.setText(self.message or "")
self.show()
return True
diff --git a/BlocksScreen/lib/panels/widgets/fansPage.py b/BlocksScreen/lib/panels/widgets/fansPage.py
index 925c0230..c31a8600 100644
--- a/BlocksScreen/lib/panels/widgets/fansPage.py
+++ b/BlocksScreen/lib/panels/widgets/fansPage.py
@@ -1,15 +1,14 @@
from PyQt6 import QtCore, QtWidgets
-import typing
class FansPage(QtWidgets.QWidget):
def __init__(
self,
- parent: typing.Optional["QtWidgets.QWidget"],
- flags: typing.Optional["QtCore.Qt.WindowType"],
+ parent: QtWidgets.QWidget | None,
+ flags: QtCore.Qt.WindowType | None,
) -> None:
if parent is not None and flags is not None:
- super(FansPage, self).__init__(parent, flags)
+ super().__init__(parent, flags)
else:
- super(FansPage, self).__init__()
+ super().__init__()
diff --git a/BlocksScreen/lib/panels/widgets/filesPage.py b/BlocksScreen/lib/panels/widgets/filesPage.py
index 969399ac..d731eb9c 100644
--- a/BlocksScreen/lib/panels/widgets/filesPage.py
+++ b/BlocksScreen/lib/panels/widgets/filesPage.py
@@ -1,6 +1,5 @@
import json
import logging
-import typing
import helper_methods
from lib.utils.blocks_Scrollbar import CustomScrollBar
@@ -34,13 +33,13 @@ class FilesPage(QtWidgets.QWidget):
ICON_PATHS = {
"back_folder": ":/ui/media/btn_icons/back_folder.svg",
"folder": ":/ui/media/btn_icons/folderIcon.svg",
- "right_arrow": ":/arrow_icons/media/btn_icons/right_arrow.svg",
+ "right_arrow": ":/arrow_icons/media/btn_icons/arrow_right.svg",
"usb": ":/ui/media/btn_icons/usb_icon.svg",
"back": ":/ui/media/btn_icons/back.svg",
"refresh": ":/ui/media/btn_icons/refresh.svg",
}
- def __init__(self, parent: typing.Optional[QtWidgets.QWidget] = None) -> None:
+ def __init__(self, parent: QtWidgets.QWidget | None = None) -> None:
super().__init__(parent)
self._file_list: list[dict] = []
@@ -263,7 +262,7 @@ def _find_file_insert_position(self, modified_time: float) -> int:
return insert_pos
- def _find_file_key_by_display_name(self, display_name: str) -> typing.Optional[str]:
+ def _find_file_key_by_display_name(self, display_name: str) -> str | None:
"""Find the file key in _files_data by its display name."""
for key in self._files_data:
if self._get_display_name(key) == display_name:
@@ -691,7 +690,7 @@ def _add_file_to_list(self, file_item: dict) -> None:
if item:
self._model.add_item(item)
- def _create_file_list_item(self, filedata: dict) -> typing.Optional[ListItem]:
+ def _create_file_list_item(self, filedata: dict) -> ListItem | None:
"""Create a ListItem from file metadata."""
filename = filedata.get("filename", "")
if not filename:
diff --git a/BlocksScreen/lib/panels/widgets/inputshaperPage.py b/BlocksScreen/lib/panels/widgets/inputshaperPage.py
index ead82cfb..dd890fb5 100644
--- a/BlocksScreen/lib/panels/widgets/inputshaperPage.py
+++ b/BlocksScreen/lib/panels/widgets/inputshaperPage.py
@@ -1,11 +1,11 @@
+import typing
+
from lib.utils.blocks_button import BlocksCustomButton
from lib.utils.blocks_frame import BlocksCustomFrame
from lib.utils.icon_button import IconButton
from lib.utils.list_model import EntryDelegate, EntryListModel, ListItem
from PyQt6 import QtCore, QtGui, QtWidgets
-import typing
-
class InputShaperPage(QtWidgets.QWidget):
"""Update GUI Page,
@@ -24,6 +24,7 @@ def __init__(self, parent=None) -> None:
else:
super().__init__()
self._setupUI()
+ self.currentItem: ListItem | None = None
self.selected_item: ListItem | None = None
self.ongoing_update: bool = False
self.type_dict: dict = {}
@@ -96,21 +97,26 @@ def on_item_clicked(self, item: ListItem) -> None:
if not current_info:
return
- self.vib_label.setText(str("%.0f" % current_info.get("vibration", "N/A")) + "%")
+ _vib = current_info.get("vibration")
+ self.vib_label.setText(f"{float(_vib):.0f}%" if _vib is not None else "N/A%")
+ _accel = current_info.get("max_accel")
self.sug_accel_label.setText(
- str("%.0f" % current_info.get("max_accel", "N/A")) + "mm/s²"
+ f"{float(_accel):.0f}mm/s²" if _accel is not None else "N/Amm/s²"
)
self.action_btn.show()
def handle_ism_confirm(self) -> None:
+ """Apply the selected input shaper type to the printer and save the config."""
+ if self.currentItem is None:
+ return
current_info = self.type_dict.get(self.currentItem.text, {})
frequency = current_info.get("frequency", "N/A")
- if self.type_dict["Axis"] == "x":
+ if self.type_dict.get("Axis") == "x":
self.run_gcode_signal.emit(
f"SET_INPUT_SHAPER SHAPER_TYPE_X={self.currentItem.text} SHAPER_FREQ_X={frequency}"
)
- elif self.type_dict["Axis"] == "y":
+ elif self.type_dict.get("Axis") == "y":
self.run_gcode_signal.emit(
f"SET_INPUT_SHAPER SHAPER_TYPE_Y={self.currentItem.text} SHAPER_FREQ_Y={frequency}"
)
@@ -138,7 +144,8 @@ def _setupUI(self) -> None:
font_id = QtGui.QFontDatabase.addApplicationFont(
":/font/media/fonts for text/Momcake-Bold.ttf"
)
- font_family = QtGui.QFontDatabase.applicationFontFamilies(font_id)[0]
+ _families = QtGui.QFontDatabase.applicationFontFamilies(font_id)
+ font_family = _families[0] if _families else ""
sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Policy.MinimumExpanding,
QtWidgets.QSizePolicy.Policy.MinimumExpanding,
diff --git a/BlocksScreen/lib/panels/widgets/notificationPage.py b/BlocksScreen/lib/panels/widgets/notificationPage.py
index a14b6cbc..6bf1fd00 100644
--- a/BlocksScreen/lib/panels/widgets/notificationPage.py
+++ b/BlocksScreen/lib/panels/widgets/notificationPage.py
@@ -1,15 +1,12 @@
-from lib.utils.blocks_frame import BlocksCustomFrame
-from lib.utils.blocks_button import BlocksCustomButton
-from lib.utils.icon_button import IconButton
-from lib.utils.list_model import EntryDelegate, EntryListModel, ListItem
-from PyQt6 import QtCore, QtGui, QtWidgets
import typing
-
from collections import deque
-from typing import Deque
-
from lib.panels.widgets.popupDialogWidget import Popup
+from lib.utils.blocks_button import BlocksCustomButton
+from lib.utils.blocks_frame import BlocksCustomFrame
+from lib.utils.icon_button import IconButton
+from lib.utils.list_model import EntryDelegate, EntryListModel, ListItem
+from PyQt6 import QtCore, QtGui, QtWidgets
class NotificationPage(QtWidgets.QWidget):
@@ -28,7 +25,7 @@ def __init__(self, parent=None) -> None:
else:
super().__init__()
self._setupUI()
- self.cli_tracking: Deque = deque()
+ self.cli_tracking: deque = deque()
self.selected_item: ListItem | None = None
self.ongoing_update: bool = False
self.popup = Popup(self)
@@ -78,6 +75,8 @@ def reset_view_model(self) -> None:
def build_model_list(self) -> None:
"""Builds the model list (`self.model`) containing updatable clients"""
+ if not self.cli_tracking:
+ return
self.update_buttons_list_widget.blockSignals(True)
message, origin, priority = self.cli_tracking.popleft()
match priority:
@@ -194,11 +193,9 @@ def _setupUI(self) -> None:
font.setPointSize(20)
self.setSizePolicy(sizePolicy)
self.setObjectName("updatePage")
- self.setStyleSheet(
- """#updatePage {
+ self.setStyleSheet("""#updatePage {
background-image: url(:/background/media/1st_background.png);
- }"""
- )
+ }""")
self.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight)
self.update_page_content_layout = QtWidgets.QVBoxLayout()
self.setMinimumSize(800, 480)
@@ -347,10 +344,10 @@ def _setupUI(self) -> None:
self.time_title.setFont(font)
self.time_title.setStyleSheet("color:#FFFFFF")
- self.time_title.setFont(font)
+ self.type_label.setFont(font)
self.type_label.setStyleSheet("color:#FFFFFF")
- self.time_title.setFont(font)
+ self.time_label.setFont(font)
self.time_label.setStyleSheet("color:#FFFFFF")
self.info_frame.setLayout(self.info_box_layout)
diff --git a/BlocksScreen/lib/panels/widgets/numpadPage.py b/BlocksScreen/lib/panels/widgets/numpadPage.py
index b904645c..40e222cc 100644
--- a/BlocksScreen/lib/panels/widgets/numpadPage.py
+++ b/BlocksScreen/lib/panels/widgets/numpadPage.py
@@ -1,7 +1,6 @@
-from lib.utils.icon_button import IconButton
from lib.utils.blocks_label import BlocksLabel
+from lib.utils.icon_button import IconButton
from lib.utils.numpad_button import NumpadButton
-
from PyQt6 import QtCore, QtGui, QtWidgets
diff --git a/BlocksScreen/lib/panels/widgets/optionCardWidget.py b/BlocksScreen/lib/panels/widgets/optionCardWidget.py
index 6fbfe20d..e331049d 100644
--- a/BlocksScreen/lib/panels/widgets/optionCardWidget.py
+++ b/BlocksScreen/lib/panels/widgets/optionCardWidget.py
@@ -1,7 +1,7 @@
import typing
-from PyQt6 import QtCore, QtGui, QtWidgets
from lib.utils.icon_button import IconButton
+from PyQt6 import QtCore, QtGui, QtWidgets
class OptionCard(QtWidgets.QAbstractButton):
diff --git a/BlocksScreen/lib/panels/widgets/popupDialogWidget.py b/BlocksScreen/lib/panels/widgets/popupDialogWidget.py
index d565bd3a..b702e840 100644
--- a/BlocksScreen/lib/panels/widgets/popupDialogWidget.py
+++ b/BlocksScreen/lib/panels/widgets/popupDialogWidget.py
@@ -1,6 +1,5 @@
import enum
from collections import deque
-from typing import Deque
from lib.utils.icon_button import IconButton
from PyQt6 import QtCore, QtGui, QtWidgets
@@ -26,9 +25,9 @@ def __init__(self, parent) -> None:
super().__init__(parent)
self.timeout_timer = QtCore.QTimer(self)
self.timeout_timer.setSingleShot(True)
- self.messages: Deque = deque()
+ self.messages: deque = deque()
self.isShown = False
- self.persistent_notifications: Deque = deque()
+ self.persistent_notifications: deque = deque()
self.message_type: Popup.MessageType = Popup.MessageType.INFO
self.default_background_color = QtGui.QColor(164, 164, 164)
self.info_icon = QtGui.QPixmap(":ui/media/btn_icons/info.svg")
@@ -81,7 +80,8 @@ def _calculate_target_geometry(self) -> QtCore.QRect:
if isinstance(widget, QtWidgets.QMainWindow):
main_window = widget
break
-
+ if main_window is None:
+ return QtCore.QRect()
parent_rect = main_window.geometry()
width = int(parent_rect.width() * 0.85)
diff --git a/BlocksScreen/lib/panels/widgets/printcorePage.py b/BlocksScreen/lib/panels/widgets/printcorePage.py
index c2683cd1..b5942e54 100644
--- a/BlocksScreen/lib/panels/widgets/printcorePage.py
+++ b/BlocksScreen/lib/panels/widgets/printcorePage.py
@@ -34,6 +34,8 @@ def _geometry_calc(self) -> None:
for widget in app_instance.allWidgets():
if isinstance(widget, QtWidgets.QMainWindow):
main_window = widget
+ if main_window is None:
+ return
x = main_window.geometry().x()
y = main_window.geometry().y()
width = main_window.width()
diff --git a/BlocksScreen/lib/panels/widgets/probeHelperPage.py b/BlocksScreen/lib/panels/widgets/probeHelperPage.py
index 219adb6a..902f9a4c 100644
--- a/BlocksScreen/lib/panels/widgets/probeHelperPage.py
+++ b/BlocksScreen/lib/panels/widgets/probeHelperPage.py
@@ -42,7 +42,6 @@ class ProbeHelper(QtWidgets.QWidget):
)
distances = ["0.01", ".025", "0.1", "0.5", "1"]
- _calibration_commands: list = []
helper_start: bool = False
helper_initialize: bool = False
_zhop_height: float = float(distances[0])
@@ -52,6 +51,7 @@ class ProbeHelper(QtWidgets.QWidget):
z_offset_calibration_speed: int = 100
def __init__(self, parent: QtWidgets.QWidget) -> None:
+ """Initialize the probe helper widget and connect internal signals."""
super().__init__(parent)
self.setObjectName("probe_offset_page")
@@ -92,6 +92,7 @@ def __init__(self, parent: QtWidgets.QWidget) -> None:
self.target_temp = 0
self.current_temp = 0
self._eddy_calibration_state = False
+ self._calibration_commands: list = []
@QtCore.pyqtSlot(str, dict, name="on_print_stats_update")
@QtCore.pyqtSlot(str, float, name="on_print_stats_update")
@@ -281,8 +282,6 @@ def on_object_config(self, config: dict | list) -> None:
if not _config:
return
if _config.get("home_xy_position"):
- if not _config.get("home_xy_position"):
- return
self.z_offset_safe_xy = tuple(
map(
lambda value: float(value),
@@ -333,6 +332,7 @@ def on_printer_config(self, config: dict) -> None:
@QtCore.pyqtSlot(dict, name="on_available_gcode_cmds")
def on_available_gcode_cmds(self, gcode_cmds: dict) -> None:
"""Setup available probe calibration commands"""
+ self._calibration_commands.clear()
_available_commands = gcode_cmds.keys()
if "PROBE_CALIBRATE" in _available_commands:
self._calibration_commands.append("PROBE_CALIBRATE")
@@ -367,16 +367,11 @@ def _build_calibration_command(self, tool: str) -> str:
return "Z_ENDSTOP_CALIBRATE"
elif "eddy" in tool:
if self._verify_gcode("PROBE_EDDY_CURRENT_CALIBRATE"):
- _name = tool.split(" ")[1]
- # if not _name:
- # return ""
- # return (
- # f"PROBE_EDDY_CURRENT_CALIBRATE CHIP={tool.split(' ')[1]}"
- # )
- return (
- f"PROBE_EDDY_CURRENT_CALIBRATE CHIP={tool.split(' ')[1]}"
- * bool(_name)
- ) + ("" * ~bool(_name))
+ _parts = tool.split(" ", 1)
+ if len(_parts) < 2:
+ return ""
+ _name = _parts[1]
+ return f"PROBE_EDDY_CURRENT_CALIBRATE CHIP={_name}" if _name else ""
elif "probe" in tool or "bltouch" in tool:
if self._verify_gcode("PROBE_CALIBRATE"):
@@ -403,7 +398,7 @@ def handle_zhopHeight_change(self, new_value: float) -> None:
self._zhop_height = new_value
@QtCore.pyqtSlot("PyQt_PyObject", name="handle_start_tool")
- def handle_start_tool(self, sender: typing.Type[OptionCard]) -> None:
+ def handle_start_tool(self, sender: OptionCard) -> None:
"""Handle probe tool helper start by sending
the correct gcode command according to the
clicked option card. This is achieved by
@@ -414,7 +409,7 @@ def handle_start_tool(self, sender: typing.Type[OptionCard]) -> None:
sender.
Args:
- sender (typing.Type[OptionCard]): The clicked OptionCard object
+ sender (OptionCard): The clicked OptionCard instance
"""
if not sender:
return
@@ -429,19 +424,21 @@ def handle_start_tool(self, sender: typing.Type[OptionCard]) -> None:
lambda: self.query_printer_object.emit({"manual_probe": None})
)
_timer.start(int(300))
- # self.query_printer_object.emit({"manual_probe": None})
- _cmd = self._build_calibration_command(sender.name) # type:ignore
+ _cmd = self._build_calibration_command(sender.name) # type: ignore
if not _cmd:
return
self.disable_popups.emit(True)
self.run_gcode_signal.emit("G28\nM400")
- if "eddy" in sender.name: # type:ignore
+ if "eddy" in sender.name: # type: ignore
self.call_load_panel.emit(True, "Preparing Eddy Current Calibration...")
self.toggle_conn_page.emit(False)
self._move_to_pos(self.z_offset_safe_xy[0], self.z_offset_safe_xy[1], 100)
+ _name_parts = sender.name.split(" ", 1) # type: ignore
+ if len(_name_parts) < 2:
+ return
self.run_gcode_signal.emit(
- f"LDC_CALIBRATE_DRIVE_CURRENT CHIP={sender.name.split(' ')[1]}" # type:ignore
+ f"LDC_CALIBRATE_DRIVE_CURRENT CHIP={_name_parts[1]}"
)
self.run_gcode_signal.emit("M400\nSAVE_CONFIG")
@@ -467,19 +464,19 @@ def on_extruder_update(
return
if self.target_temp != 0:
if self.current_temp == self.target_temp:
- if self.isVisible:
+ if self.isVisible():
self.call_load_panel.emit(True, "Extruder heated up \n Please wait")
return
if field == "temperature":
self.current_temp = round(new_value, 0)
- if self.isVisible:
+ if self.isVisible():
self.call_load_panel.emit(
True,
f"Heating up ({new_value}/{self.target_temp}) \n Please wait",
)
if field == "target":
self.target_temp = round(new_value, 0)
- if self.isVisible:
+ if self.isVisible():
self.call_load_panel.emit(True, "Cleaning the nozzle \n Please wait")
@QtCore.pyqtSlot(name="handle_accept")
@@ -523,32 +520,32 @@ def on_gcode_move_update(self, name: str, value: list) -> None:
@QtCore.pyqtSlot(dict, name="on_manual_probe_update")
def on_manual_probe_update(self, update: dict) -> None:
- """Handle manual probe update"""
+ """Handle manual probe update.
+
+ Only process ``is_active`` state transitions when the key is
+ actually present in the update dict. Klipper sends partial
+ updates (e.g. only position data after TESTZ) and defaulting
+ ``is_active`` to False on those would reset the entire UI.
+ """
if not update:
return
- # if update.get("z_position_lower"):
- # f"{update.get('z_position_lower'):.4f} mm"
-
- is_active = update.get("is_active", None)
- if update.get("z_position_upper"):
- self.old_offset_info.setText(f"{update.get('z_position_upper'):.4f} mm")
- if update.get("z_position"):
- self.current_offset_info.setText(f"{update.get('z_position'):.4f} mm")
+ if "is_active" in update:
+ is_active = update["is_active"]
+ if is_active and not self.isVisible():
+ self.request_page_view.emit()
+ self.helper_initialize = False
+ self.helper_start = is_active
+ self._toggle_tool_buttons(is_active)
+ if is_active:
+ self._hide_option_cards()
+ else:
+ self._show_option_cards()
- if not is_active:
- return
- if not self.isVisible():
- self.request_page_view.emit()
- # Shared state updates
- self.helper_initialize = False
- self.helper_start = is_active
- # UI updates
- self._toggle_tool_buttons(is_active)
- if is_active:
- self._hide_option_cards()
- else:
- self._show_option_cards()
+ if update.get("z_position_upper") is not None:
+ self.old_offset_info.setText(f"{update['z_position_upper']:.4f} mm")
+ if update.get("z_position") is not None:
+ self.current_offset_info.setText(f"{update['z_position']:.4f} mm")
@QtCore.pyqtSlot(list, name="handle_gcode_response")
def handle_gcode_response(self, data: list) -> None:
@@ -558,6 +555,8 @@ def handle_gcode_response(self, data: list) -> None:
data (list): A list containing the gcode that originated
the response and the response
"""
+ if not data:
+ return
if self.isVisible():
if data[0].startswith("!!"): # An error occurred
if "already in a manual z probe" in data[0].strip("!! ").lower():
diff --git a/BlocksScreen/lib/panels/widgets/sensorWidget.py b/BlocksScreen/lib/panels/widgets/sensorWidget.py
index e0ed9955..23fc46dc 100644
--- a/BlocksScreen/lib/panels/widgets/sensorWidget.py
+++ b/BlocksScreen/lib/panels/widgets/sensorWidget.py
@@ -37,10 +37,11 @@ class SensorState(enum.IntEnum):
def __init__(self, parent, sensor_name: str):
super().__init__(parent)
- self.name = str(sensor_name).split(" ")[1]
+ _parts = str(sensor_name).split(" ", 1)
+ self.name = _parts[1] if len(_parts) > 1 else _parts[0]
self.sensor_type: SensorWidget.SensorType = (
self.SensorType.SWITCH
- if "switch" in str(sensor_name).split(" ")[0].lower()
+ if "switch" in _parts[0].lower()
else self.SensorType.MOTION
)
@@ -96,13 +97,13 @@ def text(self, new_text) -> None:
self._text_label.setText(f"{new_text}")
self._text = new_text
- @QtCore.pyqtSlot(FilamentState, name="change_fil_sensor_state")
- def change_fil_sensor_state(self, state: FilamentState):
- """Invert the filament state in response to a Klipper update"""
+ def set_filament_state(self, state: FilamentState) -> None:
+ """Set the filament state directly from a Klipper update."""
if not isinstance(state, SensorWidget.FilamentState):
return
- self.filament_state = SensorWidget.FilamentState(not state.value)
- self.update()
+ if self.filament_state != state:
+ self.filament_state = state
+ self.update()
def toggle_button_state(self, state: ToggleAnimatedButton.State) -> None:
"""Called when the Klipper firmware reports an update to the filament sensor state"""
diff --git a/BlocksScreen/lib/panels/widgets/sensorsPanel.py b/BlocksScreen/lib/panels/widgets/sensorsPanel.py
index df63cfb5..77fe5865 100644
--- a/BlocksScreen/lib/panels/widgets/sensorsPanel.py
+++ b/BlocksScreen/lib/panels/widgets/sensorsPanel.py
@@ -19,7 +19,7 @@ class SensorsWindow(QtWidgets.QWidget):
)
def __init__(self, parent):
- super(SensorsWindow, self).__init__(parent)
+ super().__init__(parent)
self.model = EntryListModel()
self.entry_delegate = EntryDelegate()
self.sensor_tracking_widget = {}
@@ -32,11 +32,15 @@ def __init__(self, parent):
self.fs_back_button.clicked.connect(self.request_back)
def reset_view_model(self) -> None:
- """Clears items from ListView
- (Resets `QAbstractListModel` by clearing entries)
- """
+ """Clears items from ListView and removes existing sensor widgets."""
self.model.clear()
self.entry_delegate.clear()
+ for widget in self.sensor_tracking_widget.values():
+ self.info_box_layout.removeWidget(widget)
+ widget.deleteLater()
+ self.sensor_tracking_widget.clear()
+ self.sensor_list.clear()
+ self.current_widget = None
@QtCore.pyqtSlot(dict, name="handle_available_fil_sensors")
def handle_available_fil_sensors(self, sensors: dict) -> None:
@@ -46,7 +50,7 @@ def handle_available_fil_sensors(self, sensors: dict) -> None:
self.reset_view_model()
filtered_sensors = [
sensor
- for sensor in sensors.keys()
+ for sensor in sensors
if sensor.startswith(
("filament_switch_sensor", "filament_motion_sensor", "cutter_sensor")
)
@@ -63,14 +67,20 @@ def handle_available_fil_sensors(self, sensors: dict) -> None:
def handle_fil_state_change(
self, sensor_name: str, parameter: str, value: bool
) -> None:
- """Handle Klipper signals for filament sensor changes"""
+ """Handle Klipper signals for filament sensor changes."""
_item = self.sensor_tracking_widget.get(sensor_name)
- if _item:
- if parameter == "filament_detected":
- state = SensorWidget.FilamentState(not value)
- _item.change_fil_sensor_state(state)
- elif parameter == "enabled":
- _item.toggle_button_state(SensorWidget.SensorState(value))
+ if not _item:
+ return
+ if parameter == "filament_detected":
+ # filament_detected=True means filament IS present
+ state = (
+ SensorWidget.FilamentState.PRESENT
+ if value
+ else SensorWidget.FilamentState.MISSING
+ )
+ _item.set_filament_state(state)
+ elif parameter == "enabled":
+ _item.toggle_button_state(SensorWidget.SensorState(value))
def showEvent(self, event: QtGui.QShowEvent | None) -> None:
"""Re-add clients to update list"""
@@ -108,7 +118,8 @@ def create_sensor_widget(self, name: str) -> SensorWidget:
else:
_item_widget.show()
self.current_widget = _item_widget
- name_id = str(name).split(" ")[1]
+ _parts = str(name).split(" ", 1)
+ name_id = _parts[1] if len(_parts) > 1 else _parts[0]
item = ListItem(
text=name_id,
right_text="",
@@ -133,7 +144,8 @@ def _setupUi(self) -> None:
font_id = QtGui.QFontDatabase.addApplicationFont(
":/font/media/fonts for text/Momcake-Bold.ttf"
)
- font_family = QtGui.QFontDatabase.applicationFontFamilies(font_id)[0]
+ _families = QtGui.QFontDatabase.applicationFontFamilies(font_id)
+ font_family = _families[0] if _families else ""
sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Policy.MinimumExpanding,
QtWidgets.QSizePolicy.Policy.MinimumExpanding,
diff --git a/BlocksScreen/lib/panels/widgets/troubleshootPage.py b/BlocksScreen/lib/panels/widgets/troubleshootPage.py
index 0c327ac7..8673f045 100644
--- a/BlocksScreen/lib/panels/widgets/troubleshootPage.py
+++ b/BlocksScreen/lib/panels/widgets/troubleshootPage.py
@@ -1,6 +1,5 @@
-from PyQt6 import QtCore, QtGui, QtWidgets
-
from lib.utils.icon_button import IconButton
+from PyQt6 import QtCore, QtGui, QtWidgets
class TroubleshootPage(QtWidgets.QDialog):
@@ -9,14 +8,12 @@ def __init__(
parent: QtWidgets.QWidget,
) -> None:
super().__init__(parent)
- self.setStyleSheet(
- """
+ self.setStyleSheet("""
#troubleshoot_page {
background-image: url(:/background/media/1st_background.png);
border: none;
}
- """
- )
+ """)
self.setWindowFlags(
QtCore.Qt.WindowType.Popup | QtCore.Qt.WindowType.FramelessWindowHint
)
diff --git a/BlocksScreen/lib/panels/widgets/tunePage.py b/BlocksScreen/lib/panels/widgets/tunePage.py
index 1b68c2f7..2394184c 100644
--- a/BlocksScreen/lib/panels/widgets/tunePage.py
+++ b/BlocksScreen/lib/panels/widgets/tunePage.py
@@ -2,7 +2,6 @@
import typing
import logging
-from helper_methods import normalize
from lib.utils.blocks_button import BlocksCustomButton
from lib.utils.display_button import DisplayButton
from lib.utils.icon_button import IconButton
@@ -53,7 +52,7 @@ def __init__(self, parent) -> None:
lambda: self.request_sliderPage[str, int, "PyQt_PyObject", int, int].emit(
"Speed",
int(self.speed_factor_override * 100),
- self.on_slider_change,
+ self._on_speed_slider_change,
10,
300,
)
@@ -99,30 +98,26 @@ def on_printer_config(self, config: dict) -> None:
@QtCore.pyqtSlot(str, int, name="on_numpad_change")
def on_numpad_change(self, name: str, new_value: int) -> None:
- """Handle numpad value inserted"""
+ """Handle numpad value inserted."""
if "bed" in name.lower():
name = "heater_bed"
elif "extruder" in name.lower():
name = "extruder"
self.run_gcode.emit(f"SET_HEATER_TEMPERATURE HEATER={name} TARGET={new_value}")
+ @QtCore.pyqtSlot(str, int)
+ def _on_speed_slider_change(self, _name: str, new_value: int) -> None:
+ """Handle print speed slider change."""
+ self.speed_factor_override = new_value / 100
+ self.run_gcode.emit(f"M220 S{new_value}")
+
@QtCore.pyqtSlot(str, int, name="on_slider_change")
def on_slider_change(self, name: str, new_value: int) -> None:
- """Handle slider page value inserted"""
- if "speed" in name.lower():
- self.speed_factor_override = new_value / 100
- self.run_gcode.emit(f"M220 S{new_value}")
-
- if "fan" in name.lower():
- if name.lower() == "fan":
- self.run_gcode.emit(
- f"M106 S{int(round((normalize(float(new_value / 100), 0.0, 1.0, 0, 255))))}"
- ) # [0, 255] Range
- else:
- name = name.replace(" ", "_")
- self.run_gcode.emit(
- f"SET_FAN_SPEED FAN={name} SPEED={float(new_value / 100.00)}"
- ) # [0.0, 1.0] Range
+ """Handle fan_generic slider value change."""
+ gcode_name = name.replace(" ", "_")
+ self.run_gcode.emit(
+ f"SET_FAN_SPEED FAN={gcode_name} SPEED={float(new_value / 100.00)}"
+ ) # [0.0, 1.0] Range
@QtCore.pyqtSlot(str, str, float, name="on_fan_update")
@QtCore.pyqtSlot(str, str, int, name="on_fan_update")
@@ -137,6 +132,8 @@ def on_fan_object_update(
new_value (int | float): New value for field name
"""
fields = name.split()
+ if not fields:
+ return
first_field = fields[0]
second_field = fields[1] if len(fields) > 1 else None
name = second_field.replace("_", " ") if second_field else name
diff --git a/BlocksScreen/lib/panels/widgets/updatePage.py b/BlocksScreen/lib/panels/widgets/updatePage.py
index b91e41d3..15e72b00 100644
--- a/BlocksScreen/lib/panels/widgets/updatePage.py
+++ b/BlocksScreen/lib/panels/widgets/updatePage.py
@@ -101,7 +101,7 @@ def on_request_reload(self, service: str | None = None) -> None:
"""Handles reload button click, requests update status refresh"""
self.show_loading(True)
if service:
- self.request_refresh_update.emit([service])
+ self.request_refresh_update[str].emit(service)
else:
self.request_refresh_update.emit()
@@ -197,14 +197,16 @@ def on_item_clicked(self, item: ListItem) -> None:
if not _remote_version:
self.remote_version_title.hide()
self.remote_version_tracking.hide()
- self.remote_version_title.show()
- self.remote_version_tracking.show()
- self.remote_version_title.setText("Remote Version: ")
- self.remote_version_tracking.setText(_remote_version)
+ else:
+ self.remote_version_title.show()
+ self.remote_version_tracking.show()
+ self.remote_version_title.setText("Remote Version: ")
+ self.remote_version_tracking.setText(_remote_version)
_curr_version = cli_data.get("version", None)
if not _curr_version:
# There is no version information something is seriously wrong here
self.action_btn.setText("Recover")
+ return
self.version_title.show()
self.version_tracking_info.show()
self.version_tracking_info.setText(_curr_version)
@@ -295,7 +297,8 @@ def _setupUI(self) -> None:
font_id = QtGui.QFontDatabase.addApplicationFont(
":/font/media/fonts for text/Momcake-Bold.ttf"
)
- font_family = QtGui.QFontDatabase.applicationFontFamilies(font_id)[0]
+ _families = QtGui.QFontDatabase.applicationFontFamilies(font_id)
+ font_family = _families[0] if _families else ""
sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Policy.MinimumExpanding,
QtWidgets.QSizePolicy.Policy.MinimumExpanding,
@@ -304,11 +307,9 @@ def _setupUI(self) -> None:
sizePolicy.setVerticalStretch(1)
self.setSizePolicy(sizePolicy)
self.setObjectName("updatePage")
- self.setStyleSheet(
- """#updatePage {
+ self.setStyleSheet("""#updatePage {
background-image: url(:/background/media/1st_background.png);
- }"""
- )
+ }""")
self.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight)
self.update_page_content_layout = QtWidgets.QVBoxLayout()
self.update_page_content_layout.setContentsMargins(15, 15, 15, 15)
diff --git a/BlocksScreen/lib/printer.py b/BlocksScreen/lib/printer.py
index c6c76fbc..fc820730 100644
--- a/BlocksScreen/lib/printer.py
+++ b/BlocksScreen/lib/printer.py
@@ -105,7 +105,7 @@ class Printer(QtCore.QObject):
current_loaded_file_metadata: str = ""
def __init__(self, parent: QtCore.QObject, ws: MoonWebSocket, /) -> None:
- super(Printer, self).__init__(parent)
+ super().__init__(parent)
self.ws = ws
self.active_extruder_name: str = ""
@@ -138,6 +138,18 @@ def clear_printer_objs(self) -> None:
self.printer_busy = False
self.current_loaded_file = ""
self.current_loaded_file_metadata = ""
+ _heater_attributes: dict = {
+ "current_temperature": 0.0,
+ "target_temperature": 0.0,
+ "can_extrude": False,
+ }
+ self.heaters_object = {
+ "extruder": _heater_attributes.copy(),
+ "bed": _heater_attributes.copy(),
+ }
+ self.active_extruder_name = ""
+ self.available_filament_sensors = {}
+ self.has_chamber = False
@QtCore.pyqtSlot(str, name="on_klippy_status")
def on_klippy_status(self, state: str):
@@ -224,7 +236,7 @@ def get_config(self, section_name: str) -> dict:
return _config[0].get(section_name, {})
def search_config_list(
- self, search_list: list[str], _objects: typing.Optional[list] = None
+ self, search_list: list[str], _objects: list | None = None
) -> list:
"""
Search a list of printer objects recursively
@@ -295,7 +307,7 @@ def _gcode_response(self, report: list) -> None:
self.gcode_response.emit(report)
def _webhook_printcore_updated(self, value: dict):
- self.on_printcore_update[dict].emit(value)
+ self.on_printcore_update.emit(value)
def _webhooks_object_updated(self, value: dict, name: str = "webhooks") -> None:
"""Sends an event type according to the received state
@@ -305,7 +317,7 @@ def _webhooks_object_updated(self, value: dict, name: str = "webhooks") -> None:
value (dict): _description_
name (str, optional): _description_. Defaults to "".
"""
- if "state" in value.keys() and "state_message" in value.keys():
+ if "state" in value and "state_message" in value:
self.webhooks_update.emit(value["state"], value["state_message"])
logger.debug("Webhooks message received")
_state: str = value["state"]
@@ -318,71 +330,71 @@ def _webhooks_object_updated(self, value: dict, name: str = "webhooks") -> None:
event = _event_callback(value["state"], value["state_message"])
instance = QtWidgets.QApplication.instance()
if instance is not None and isinstance(event, QtCore.QEvent):
- instance.sendEvent(self.parent(), event)
+ instance.postEvent(self.parent(), event)
else:
raise TypeError("QApplication.instance is None type.")
except Exception as e:
logger.debug(
- "Unable to send internal Klippy %s notification : %e",
+ "Unable to send internal Klippy %s notification : %s",
_state_call,
e,
)
def _gcode_move_object_updated(self, value: dict, name: str = "gcode_move") -> None:
- if "speed_factor" in value.keys():
+ if "speed_factor" in value:
self.gcode_move_update[str, float].emit(
"speed_factor", value["speed_factor"]
)
- if "speed" in value.keys():
+ if "speed" in value:
self.gcode_move_update[str, float].emit("speed", value["speed"])
- if "extrude_factor" in value.keys():
+ if "extrude_factor" in value:
self.gcode_move_update[str, float].emit(
"extruder_factor", value["extrude_factor"]
)
- if "absolute_coordinates" in value.keys():
+ if "absolute_coordinates" in value:
self.gcode_move_update[str, bool].emit(
"absolute_coordinates", value["absolute_coordinates"]
)
- if "absolute_extrude" in value.keys():
+ if "absolute_extrude" in value:
self.gcode_move_update[str, bool].emit(
"absolute_extrude", value["absolute_extrude"]
)
- if "homing_origin" in value.keys():
+ if "homing_origin" in value:
self.gcode_move_update[str, list].emit(
"homing_origin", value["homing_origin"]
)
- if "position" in value.keys():
+ if "position" in value:
self.gcode_move_update[str, list].emit("position", value["position"])
- if "gcode_position" in value.keys():
+ if "gcode_position" in value:
self.gcode_move_update[str, list].emit(
"gcode_position", value["gcode_position"]
)
def _toolhead_object_updated(self, values: dict, name: str = "toolhead") -> None:
- if "homed_axes" in values.keys():
+ if "homed_axes" in values:
self.toolhead_update[str, str].emit("homed_axes", values["homed_axes"])
- if "print_time" in values.keys():
+ if "print_time" in values:
self.toolhead_update[str, float].emit("print_time", values["print_time"])
- if "estimated_print_time" in values.keys():
+ if "estimated_print_time" in values:
self.toolhead_update[str, float].emit(
"estimated_print_time", values["estimated_print_time"]
)
- if "extruder" in values.keys():
+ if "extruder" in values:
self.toolhead_update[str, str].emit("extruder", values["extruder"])
self.active_extruder_name = values["extruder"]
- if "position" in values.keys():
+ if "position" in values:
self.toolhead_update[str, list].emit("position", values["position"])
- if "max_velocity" in values.keys():
+ if "max_velocity" in values:
self.toolhead_update[str, float].emit(
"max_velocity", values["max_velocity"]
)
- if "max_accel" in values.keys():
+ if "max_accel" in values:
self.toolhead_update[str, float].emit("max_accel", values["max_accel"])
- if "max_accel_to_decel" in values.keys():
+ if "max_accel_to_decel" in values:
self.toolhead_update[str, float].emit(
"max_accel_to_decel", values["max_accel_to_decel"]
)
- if "square_corner_velocity" in values.keys():
+ if "square_corner_velocity" in values:
self.toolhead_update[str, float].emit(
"square_corner_velocity", values["square_corner_velocity"]
)
@@ -390,76 +402,79 @@ def _toolhead_object_updated(self, values: dict, name: str = "toolhead") -> None
def _extruder_object_updated(
self, value: dict, extruder_name: str = "extruder"
) -> None:
- if "temperature" in value.keys():
+ """Handle extruder object updates and emit corresponding signals."""
+ if extruder_name not in self.heaters_object:
+ self.heaters_object[extruder_name] = {}
+ if "temperature" in value:
self.extruder_update.emit(
extruder_name, "temperature", value["temperature"]
)
self.heaters_object[f"{extruder_name}"]["actual_temperature"] = value[
"temperature"
]
- if "target" in value.keys():
+ if "target" in value:
self.extruder_update.emit(extruder_name, "target", value["target"])
self.heaters_object[f"{extruder_name}"]["target_temperature"] = value[
"target"
]
- if "can_extrude" in value.keys():
+ if "can_extrude" in value:
self.heaters_object[f"{extruder_name}"]["can_extrude"] = value[
"can_extrude"
]
- if "power" in value.keys():
+ if "power" in value:
self.extruder_update.emit(extruder_name, "power", value["power"])
- if "pressure_advance" in value.keys():
+ if "pressure_advance" in value:
self.extruder_update.emit(
extruder_name, "pressure_advance", value["pressure_advance"]
)
- if "smooth_time" in value.keys():
+ if "smooth_time" in value:
self.extruder_update.emit(
extruder_name, "smooth_time", value["smooth_time"]
)
- if "can_extrude" in value.keys():
+ if "can_extrude" in value:
pass
def _heater_bed_object_updated(
self, value: dict, heater_name: str = "heater_bed"
) -> None:
- if "temperature" in value.keys():
+ if "temperature" in value:
self.heater_bed_update.emit(
heater_name, "temperature", value["temperature"]
)
self.heaters_object["bed"]["actual_temperature"] = value["temperature"]
- if "target" in value.keys():
+ if "target" in value:
self.heater_bed_update.emit(heater_name, "target", value["target"])
self.heaters_object["bed"]["target_temperature"] = value["target"]
- if "power" in value.keys():
+ if "power" in value:
self.heater_bed_update.emit(heater_name, "power", value["power"])
def _chamber_object_updated(self, value: dict, heater_name: str = "chamber"):
self.has_chamber = True
def _fan_object_updated(self, value: dict, fan_name: str = "fan") -> None:
- if "speed" in value.keys():
+ if "speed" in value:
self.fan_update[str, str, float].emit("fan", "speed", value["speed"])
- if "rpm" in value.keys():
+ if "rpm" in value:
self.fan_update[str, str, int].emit("fan", "rpm", value["rpm"])
def _fan_generic_object_updated(self, value: dict, fan_name: str = "") -> None:
_names = ["fan_generic", fan_name]
object_name = " ".join(_names)
- if "speed" in value.keys():
+ if "speed" in value:
self.fan_update[str, str, float].emit(
object_name, "speed", value.get("speed")
)
- if "rpm" in value.keys():
+ if "rpm" in value:
self.fan_update[str, str, int].emit(object_name, "rpm", value.get("rpm"))
def _controller_fan_object_updated(self, value: dict, fan_name: str = "") -> None:
_names = ["controller_fan", fan_name]
object_name = " ".join(_names)
- if "speed" in value.keys():
+ if "speed" in value:
self.fan_update[str, str, float].emit(
object_name, "speed", value.get("speed")
)
- elif "rpm" in value.keys():
+ elif "rpm" in value:
self.fan_update[str, str, int].emit(object_name, "rpm", value.get("rpm"))
def _heater_fan_object_updated(self, value: dict, fan_name: str = "") -> None:
@@ -469,20 +484,20 @@ def _heater_fan_object_updated(self, value: dict, fan_name: str = "") -> None:
# object_name = " ".join(_names)
def _z_tilt_object_updated(self, value: dict, name: str = "") -> None:
- if value["applied"]:
+ if value.get("applied"):
self.z_tilt_update[str, bool].emit("applied", value["applied"])
def _idle_timeout_object_updated(
self, value: dict, name: str = "idle_timeout"
) -> None:
- if "state" in value.keys():
+ if "state" in value:
self.idle_timeout_update[str, str].emit("state", value["state"])
if "printing" in value["state"]:
self.printer_busy = True
elif self.printing_state != "printing" and value["state"] != "printing":
# It's also busy if the printer is printing or paused
self.printer_busy = False
- if "printing_time" in value.keys():
+ if "printing_time" in value:
self.idle_timeout_update[str, float].emit(
"printing_time", value["printing_time"]
)
@@ -490,11 +505,11 @@ def _idle_timeout_object_updated(
def _virtual_sdcard_object_updated(
self, values: dict, name: str = "virtual_sdcard"
) -> None:
- if "progress" in values.keys():
+ if "progress" in values:
self.virtual_sdcard_update[str, float].emit("progress", values["progress"])
- if "is_active" in values.keys():
+ if "is_active" in values:
self.virtual_sdcard_update[str, bool].emit("is_active", values["is_active"])
- if "file_position" in values.keys():
+ if "file_position" in values:
self.virtual_sdcard_update[str, float].emit(
"file_position", float(values["file_position"])
)
@@ -508,6 +523,8 @@ def send_print_event(self, event: str):
Raises:
TypeError: Thrown when QApplication is None
"""
+ if not event:
+ return
_print_state_upper = event[0].upper()
_print_state_call = f"{_print_state_upper}{event[1:]}"
if hasattr(events, f"Print{_print_state_call}"):
@@ -516,15 +533,16 @@ def send_print_event(self, event: str):
_print_state_call,
f"Print{_print_state_call}",
)
- _event_callback: QtCore.QEvent = getattr(
- events, f"Print{_print_state_call}"
- )
+ _event_callback = getattr(events, f"Print{_print_state_call}")
if callable(_event_callback):
try:
instance = QtWidgets.QApplication.instance()
- if instance:
- instance.postEvent(self.window(), _event_callback)
- else:
+ # Printer is a QObject, not QWidget — use parent()
+ # to reach the MainWindow (which has the event handler).
+ target = self.parent()
+ if instance and target:
+ instance.postEvent(target, _event_callback())
+ elif not instance:
raise TypeError("QApplication.instance expected non None value")
except Exception as e:
logger.info(
@@ -534,24 +552,24 @@ def send_print_event(self, event: str):
def _print_stats_object_updated(
self, values: dict, name: str = "print_stats"
) -> None:
- if "filename" in values.keys():
+ if "filename" in values:
self.print_stats_update[str, str].emit("filename", values["filename"])
self.print_file_loaded = True
- if "total_duration" in values.keys():
+ if "total_duration" in values:
self.print_stats_update[str, float].emit(
"total_duration", values["total_duration"]
)
- if "print_duration" in values.keys():
+ if "print_duration" in values:
self.print_stats_update[str, float].emit(
"print_duration", values["print_duration"]
)
- if "filament_used" in values.keys():
+ if "filament_used" in values:
self.print_stats_update[str, float].emit(
"filament_used", values["filament_used"]
)
- if "state" in values.keys():
+ if "state" in values:
self.print_stats_update[str, str].emit("state", values["state"])
- self.printing_state = values.get("state", None)
+ self.printing_state = values.get("state") or ""
if not self.printing_state:
return
self.send_print_event(self.printing_state)
@@ -562,33 +580,33 @@ def _print_stats_object_updated(
self.print_file_loaded = True
if values["state"] == "printing" or values["state"] == "pause":
self.printing = True
- if "message" in values.keys():
+ if "message" in values:
self.print_stats_update[str, str].emit("message", values["message"])
- if "info" in values.keys():
+ if "info" in values:
self.print_stats_update[str, dict].emit("info", values["info"])
def _display_status_object_updated(
self, values: dict, name: str = "display_status"
) -> None:
- if "message" in values.keys():
+ if "message" in values:
self.display_update[str, str].emit("message", values["message"])
- if "progress" in values.keys():
+ if "progress" in values:
self.display_update[str, float].emit("progress", values["progress"])
def _temperature_sensor_object_updated(
self, values: dict, temperature_sensor_name: str
) -> None:
- if "temperature" in values.keys():
+ if "temperature" in values:
self.temperature_sensor_update.emit(
temperature_sensor_name, "temperature", values["temperature"]
)
- if "measured_min_temp" in values.keys():
+ if "measured_min_temp" in values:
self.temperature_sensor_update.emit(
temperature_sensor_name,
"measured_min_temp",
values["measured_min_temp"],
)
- if "measured_max_temp" in values.keys():
+ if "measured_max_temp" in values:
self.temperature_sensor_update.emit(
temperature_sensor_name,
"measured_max_temp",
@@ -600,19 +618,19 @@ def _temperature_fan_object_updated(
) -> None:
_names = ["temperature_fan", temperature_fan_name]
object_name = " ".join(_names)
- if "speed" in values.keys():
+ if "speed" in values:
self.temperature_fan_update.emit(
object_name,
"speed",
values["speed"],
)
- if "temperature" in values.keys():
+ if "temperature" in values:
self.temperature_fan_update.emit(
object_name,
"temperature",
values["temperature"],
)
- if "target" in values.keys():
+ if "target" in values:
self.temperature_fan_update.emit(
object_name,
"target",
@@ -622,14 +640,14 @@ def _temperature_fan_object_updated(
def _filament_switch_sensor_object_updated(
self, values: dict, filament_switch_name: str
) -> None:
- if "filament_detected" in values.keys():
+ if "filament_detected" in values:
self.filament_switch_sensor_update.emit(
filament_switch_name,
"filament_detected",
values["filament_detected"],
)
self.available_filament_sensors.update({f"{filament_switch_name}": values})
- if "enabled" in values.keys():
+ if "enabled" in values:
self.filament_switch_sensor_update.emit(
filament_switch_name, "enabled", values["enabled"]
)
@@ -638,7 +656,7 @@ def _filament_switch_sensor_object_updated(
def _filament_motion_sensor_object_updated(
self, values: dict, filament_motion_name: str
) -> None:
- if "filament_detected" in values.keys():
+ if "filament_detected" in values:
self.filament_motion_sensor_update.emit(
filament_motion_name,
"filament_detected",
@@ -648,18 +666,18 @@ def _filament_motion_sensor_object_updated(
{f"{filament_motion_name}": values["filament_detected"]}
)
- if "enabled" in values.keys():
+ if "enabled" in values:
self.filament_motion_sensor_update.emit(
filament_motion_name, "enabled", values["enabled"]
)
self.available_filament_sensors.update({f"{filament_motion_name}": values})
def _cutter_sensor_object_updated(self, values: dict, cutter_name: str) -> None:
- if "filament_detected" in values.keys():
+ if "filament_detected" in values:
self.filament_switch_sensor_update.emit(
cutter_name, "filament_detected", values["filament_detected"]
)
- if "enabled" in values.keys():
+ if "enabled" in values:
self.filament_switch_sensor_update.emit(
cutter_name, "enabled", values["enabled"]
)
@@ -667,7 +685,7 @@ def _cutter_sensor_object_updated(self, values: dict, cutter_name: str) -> None:
self.available_filament_sensors.update({f"{cutter_name}": values})
def _output_pin_object_updated(self, values: dict, output_pin_name: str) -> None:
- if "value" in values.keys():
+ if "value" in values:
self.output_pin_update.emit(output_pin_name, "value", values["value"])
def _bed_mesh_object_updated(self, values: dict, name: str = "bed_mesh") -> None:
@@ -684,17 +702,17 @@ def _configfile_object_updated(
self, values: dict, name: str = "configfile"
) -> None:
self.configfile.update(values)
- if "config" in values.keys():
+ if "config" in values:
self.printer_config.emit(values["config"])
- if "settings" in values.keys():
+ if "settings" in values:
# TODO
...
- if "save_config_pending" in values.keys():
+ if "save_config_pending" in values:
self.save_config_pending.emit()
- if "save_config_pending_items" in values.keys():
+ if "save_config_pending_items" in values:
# TODO
...
- if "warnings" in values.keys():
+ if "warnings" in values:
# TODO
...
@@ -734,9 +752,9 @@ def _temperature_probe_object_updated(self, values: dict, name: str) -> None:
# TODO: testing needed here idk if does work
def _unload_filament_object_updated(self, values: dict, name: str) -> None:
- if "state" in values.keys():
+ if "state" in values:
self.unload_filament_update[bool].emit(values["state"])
def _load_filament_object_updated(self, values: dict, name: str) -> None:
- if "state" in values.keys():
+ if "state" in values:
self.load_filament_update[bool].emit(values["state"])
diff --git a/BlocksScreen/lib/qrcode_gen.py b/BlocksScreen/lib/qrcode_gen.py
index 1840f0ae..1caab81a 100644
--- a/BlocksScreen/lib/qrcode_gen.py
+++ b/BlocksScreen/lib/qrcode_gen.py
@@ -1,7 +1,6 @@
import qrcode
-
-from PyQt6.QtGui import QImage, QColor, QPainter
from PyQt6.QtCore import Qt
+from PyQt6.QtGui import QColor, QImage, QPainter
BLOCKS_URL = "https://blockstec.com"
RF50_MANUAL_PAGE = "https://blockstec.com/RF50"
diff --git a/BlocksScreen/lib/ui/resources/icon_resources_rc.py b/BlocksScreen/lib/ui/resources/icon_resources_rc.py
index 45c93d8f..c22059f4 100644
--- a/BlocksScreen/lib/ui/resources/icon_resources_rc.py
+++ b/BlocksScreen/lib/ui/resources/icon_resources_rc.py
@@ -2,7 +2,7 @@
# Resource object code
#
-# Created by: The Resource Compiler for PyQt5 (Qt v5.15.14)
+# Created by: The Resource Compiler for PyQt5 (Qt v5.15.15)
#
# WARNING! All changes made in this file will be lost!
@@ -11519,151 +11519,165 @@
\x37\x30\x2e\x30\x31\x20\x33\x34\x30\x2e\x35\x37\x20\x32\x32\x34\
\x2e\x38\x31\x20\x33\x38\x35\x2e\x37\x37\x22\x2f\x3e\x3c\x2f\x73\
\x76\x67\x3e\
-\x00\x00\x08\xde\
+\x00\x00\x09\xb9\
\x3c\
-\x73\x76\x67\x20\x69\x64\x3d\x22\x4c\x61\x79\x65\x72\x5f\x31\x22\
-\x20\x64\x61\x74\x61\x2d\x6e\x61\x6d\x65\x3d\x22\x4c\x61\x79\x65\
-\x72\x20\x31\x22\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\
-\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\
-\x30\x30\x2f\x73\x76\x67\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\
-\x22\x30\x20\x30\x20\x36\x30\x30\x20\x36\x30\x30\x22\x3e\x3c\x64\
-\x65\x66\x73\x3e\x3c\x73\x74\x79\x6c\x65\x3e\x2e\x63\x6c\x73\x2d\
-\x31\x7b\x66\x69\x6c\x6c\x3a\x23\x65\x30\x65\x30\x64\x66\x3b\x7d\
-\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x3c\x2f\x64\x65\x66\x73\x3e\x3c\
-\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x6c\x73\x2d\
-\x31\x22\x20\x64\x3d\x22\x4d\x33\x30\x30\x2c\x35\x33\x34\x2e\x38\
-\x38\x71\x2d\x38\x39\x2e\x39\x31\x2c\x30\x2d\x31\x37\x39\x2e\x38\
-\x34\x2c\x30\x61\x35\x35\x2c\x35\x35\x2c\x30\x2c\x30\x2c\x31\x2d\
-\x35\x35\x2e\x31\x2d\x35\x35\x2e\x30\x36\x71\x2e\x31\x34\x2d\x31\
-\x37\x39\x2e\x34\x39\x2c\x30\x2d\x33\x35\x39\x61\x35\x35\x2e\x37\
-\x34\x2c\x35\x35\x2e\x37\x34\x2c\x30\x2c\x30\x2c\x31\x2c\x35\x35\
-\x2e\x37\x39\x2d\x35\x35\x2e\x37\x38\x71\x31\x37\x38\x2c\x2e\x31\
-\x34\x2c\x33\x35\x35\x2e\x39\x33\x2c\x30\x61\x35\x38\x2e\x31\x32\
-\x2c\x35\x38\x2e\x31\x32\x2c\x30\x2c\x30\x2c\x31\x2c\x35\x38\x2e\
-\x31\x36\x2c\x35\x38\x2e\x31\x37\x71\x2d\x2e\x31\x33\x2c\x31\x37\
-\x36\x2e\x39\x34\x2c\x30\x2c\x33\x35\x33\x2e\x38\x39\x61\x35\x37\
-\x2e\x37\x33\x2c\x35\x37\x2e\x37\x33\x2c\x30\x2c\x30\x2c\x31\x2d\
-\x35\x37\x2e\x38\x33\x2c\x35\x37\x2e\x37\x37\x51\x33\x38\x38\x2e\
-\x35\x35\x2c\x35\x33\x34\x2e\x38\x32\x2c\x33\x30\x30\x2c\x35\x33\
-\x34\x2e\x38\x38\x5a\x4d\x34\x39\x37\x2e\x33\x35\x2c\x33\x30\x30\
-\x2e\x33\x37\x63\x2e\x30\x36\x2d\x31\x30\x39\x2e\x38\x38\x2d\x38\
-\x38\x2e\x38\x31\x2d\x31\x39\x39\x2e\x36\x34\x2d\x31\x39\x37\x2e\
-\x37\x37\x2d\x31\x39\x39\x2e\x37\x36\x43\x31\x38\x35\x2e\x37\x35\
-\x2c\x31\x30\x30\x2e\x34\x38\x2c\x31\x30\x31\x2e\x37\x34\x2c\x31\
-\x39\x31\x2e\x37\x35\x2c\x39\x39\x2c\x32\x39\x34\x2e\x34\x31\x63\
-\x2d\x32\x2e\x38\x39\x2c\x31\x30\x39\x2e\x37\x34\x2c\x38\x34\x2e\
-\x30\x37\x2c\x32\x30\x33\x2c\x31\x39\x35\x2e\x38\x39\x2c\x32\x30\
-\x34\x2e\x37\x31\x43\x34\x30\x39\x2c\x35\x30\x30\x2e\x38\x33\x2c\
-\x34\x39\x37\x2e\x35\x34\x2c\x34\x30\x38\x2e\x36\x35\x2c\x34\x39\
-\x37\x2e\x33\x35\x2c\x33\x30\x30\x2e\x33\x37\x5a\x6d\x30\x2c\x31\
-\x37\x37\x2e\x38\x38\x61\x32\x30\x2e\x34\x37\x2c\x32\x30\x2e\x34\
-\x37\x2c\x30\x2c\x30\x2c\x30\x2d\x32\x30\x2e\x37\x32\x2d\x32\x30\
-\x2e\x37\x37\x2c\x32\x30\x2e\x38\x37\x2c\x32\x30\x2e\x38\x37\x2c\
-\x30\x2c\x30\x2c\x30\x2d\x32\x31\x2e\x31\x38\x2c\x32\x30\x2e\x34\
-\x36\x63\x2d\x2e\x32\x33\x2c\x31\x31\x2e\x34\x34\x2c\x39\x2e\x36\
-\x36\x2c\x32\x31\x2e\x33\x34\x2c\x32\x31\x2e\x32\x34\x2c\x32\x31\
-\x2e\x32\x36\x41\x32\x30\x2e\x38\x36\x2c\x32\x30\x2e\x38\x36\x2c\
-\x30\x2c\x30\x2c\x30\x2c\x34\x39\x37\x2e\x33\x34\x2c\x34\x37\x38\
-\x2e\x32\x35\x5a\x4d\x39\x39\x2e\x32\x33\x2c\x31\x32\x31\x2e\x33\
-\x33\x63\x2d\x2e\x30\x36\x2c\x31\x32\x2c\x38\x2e\x35\x31\x2c\x32\
-\x30\x2e\x38\x39\x2c\x32\x30\x2e\x32\x35\x2c\x32\x31\x2e\x31\x61\
-\x32\x30\x2e\x36\x37\x2c\x32\x30\x2e\x36\x37\x2c\x30\x2c\x30\x2c\
-\x30\x2c\x32\x31\x2e\x33\x33\x2d\x32\x30\x2e\x38\x38\x41\x32\x31\
-\x2e\x31\x37\x2c\x32\x31\x2e\x31\x37\x2c\x30\x2c\x30\x2c\x30\x2c\
-\x31\x32\x30\x2c\x31\x30\x30\x2e\x36\x31\x2c\x32\x30\x2e\x36\x34\
-\x2c\x32\x30\x2e\x36\x34\x2c\x30\x2c\x30\x2c\x30\x2c\x39\x39\x2e\
-\x32\x33\x2c\x31\x32\x31\x2e\x33\x33\x5a\x6d\x33\x39\x38\x2e\x31\
-\x31\x2e\x32\x33\x61\x32\x30\x2e\x39\x32\x2c\x32\x30\x2e\x39\x32\
-\x2c\x30\x2c\x30\x2c\x30\x2d\x32\x30\x2e\x37\x34\x2d\x32\x31\x2c\
-\x32\x31\x2e\x33\x38\x2c\x32\x31\x2e\x33\x38\x2c\x30\x2c\x30\x2c\
-\x30\x2d\x32\x31\x2e\x31\x38\x2c\x32\x30\x2e\x37\x33\x63\x2d\x2e\
-\x32\x31\x2c\x31\x31\x2e\x35\x32\x2c\x39\x2e\x35\x31\x2c\x32\x31\
-\x2e\x31\x38\x2c\x32\x31\x2e\x32\x36\x2c\x32\x31\x2e\x31\x33\x41\
-\x32\x30\x2e\x36\x38\x2c\x32\x30\x2e\x36\x38\x2c\x30\x2c\x30\x2c\
-\x30\x2c\x34\x39\x37\x2e\x33\x34\x2c\x31\x32\x31\x2e\x35\x36\x5a\
-\x4d\x31\x32\x30\x2c\x34\x35\x37\x2e\x34\x38\x63\x2d\x31\x31\x2e\
-\x38\x38\x2d\x2e\x30\x36\x2d\x32\x30\x2e\x37\x32\x2c\x38\x2e\x36\
-\x36\x2d\x32\x30\x2e\x37\x38\x2c\x32\x30\x2e\x35\x31\x2d\x2e\x30\
-\x36\x2c\x31\x32\x2e\x31\x33\x2c\x39\x2c\x32\x31\x2e\x33\x33\x2c\
-\x32\x30\x2e\x38\x39\x2c\x32\x31\x2e\x32\x31\x61\x32\x31\x2c\x32\
-\x31\x2c\x30\x2c\x30\x2c\x30\x2c\x32\x30\x2e\x36\x39\x2d\x32\x31\
-\x41\x32\x30\x2e\x36\x31\x2c\x32\x30\x2e\x36\x31\x2c\x30\x2c\x30\
-\x2c\x30\x2c\x31\x32\x30\x2c\x34\x35\x37\x2e\x34\x38\x5a\x22\x2f\
-\x3e\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x6c\
-\x73\x2d\x31\x22\x20\x64\x3d\x22\x4d\x34\x30\x36\x2e\x37\x33\x2c\
-\x32\x31\x33\x2e\x36\x32\x63\x2d\x32\x31\x2e\x35\x38\x2d\x32\x2e\
-\x31\x38\x2d\x34\x31\x2e\x34\x35\x2e\x38\x33\x2d\x36\x30\x2e\x33\
-\x32\x2c\x39\x2e\x36\x37\x61\x38\x34\x2e\x31\x37\x2c\x38\x34\x2e\
-\x31\x37\x2c\x30\x2c\x30\x2c\x30\x2d\x33\x37\x2e\x39\x2c\x33\x34\
-\x2e\x32\x32\x63\x2d\x32\x2e\x39\x31\x2c\x35\x2d\x32\x2e\x39\x35\
-\x2c\x38\x2e\x33\x36\x2c\x31\x2e\x36\x32\x2c\x31\x32\x2e\x36\x2c\
-\x39\x2e\x37\x33\x2c\x39\x2e\x30\x35\x2c\x39\x2e\x33\x38\x2c\x39\
-\x2e\x30\x39\x2c\x32\x31\x2e\x33\x31\x2c\x34\x2e\x34\x31\x2c\x33\
-\x36\x2e\x35\x34\x2d\x31\x34\x2e\x33\x36\x2c\x36\x38\x2e\x32\x38\
-\x2d\x34\x2e\x31\x38\x2c\x39\x38\x2e\x33\x32\x2c\x31\x38\x2e\x33\
-\x32\x2c\x31\x30\x2e\x38\x38\x2c\x38\x2e\x31\x36\x2c\x31\x30\x2e\
-\x32\x32\x2c\x31\x37\x2e\x35\x31\x2c\x38\x2e\x36\x31\x2c\x32\x38\
-\x2e\x35\x31\x51\x34\x33\x30\x2e\x35\x34\x2c\x33\x37\x35\x2c\x33\
-\x38\x39\x2e\x32\x33\x2c\x34\x30\x39\x2e\x39\x61\x38\x2e\x30\x38\
-\x2c\x38\x2e\x30\x38\x2c\x30\x2c\x30\x2c\x31\x2d\x32\x2c\x31\x2e\
-\x30\x37\x63\x2d\x2e\x31\x38\x2e\x30\x38\x2d\x2e\x34\x39\x2d\x2e\
-\x31\x33\x2d\x31\x2e\x31\x32\x2d\x2e\x33\x32\x2c\x32\x2e\x31\x37\
-\x2d\x32\x30\x2e\x36\x39\x2e\x31\x32\x2d\x34\x31\x2d\x38\x2e\x35\
-\x36\x2d\x36\x30\x2e\x31\x39\x2d\x37\x2e\x36\x36\x2d\x31\x36\x2e\
-\x39\x34\x2d\x31\x39\x2e\x33\x33\x2d\x33\x30\x2e\x33\x36\x2d\x33\
-\x35\x2e\x36\x31\x2d\x33\x39\x2e\x36\x36\x2d\x33\x2e\x36\x36\x2d\
-\x32\x2e\x30\x39\x2d\x35\x2e\x37\x39\x2d\x31\x2e\x36\x32\x2d\x39\
-\x2e\x33\x2c\x31\x2e\x32\x32\x2d\x39\x2e\x31\x36\x2c\x37\x2e\x34\
-\x31\x2d\x31\x30\x2e\x31\x35\x2c\x31\x33\x2e\x38\x31\x2d\x35\x2e\
-\x34\x2c\x32\x35\x2e\x36\x32\x2c\x31\x33\x2e\x34\x37\x2c\x33\x33\
-\x2e\x34\x37\x2c\x32\x2e\x32\x33\x2c\x36\x33\x2e\x34\x34\x2d\x31\
-\x38\x2c\x39\x31\x2e\x31\x34\x2d\x37\x2e\x37\x2c\x31\x30\x2e\x35\
-\x35\x2d\x31\x36\x2e\x32\x36\x2c\x31\x34\x2e\x38\x32\x2d\x33\x30\
-\x2e\x31\x31\x2c\x31\x32\x2e\x33\x34\x2d\x33\x33\x2e\x39\x31\x2d\
-\x36\x2e\x30\x39\x2d\x36\x32\x2e\x31\x36\x2d\x32\x31\x2e\x33\x32\
-\x2d\x38\x35\x2d\x34\x36\x2e\x39\x61\x31\x37\x2e\x37\x38\x2c\x31\
-\x37\x2e\x37\x38\x2c\x30\x2c\x30\x2c\x31\x2d\x31\x2e\x35\x31\x2d\
-\x32\x2e\x38\x34\x2c\x31\x31\x37\x2e\x36\x39\x2c\x31\x31\x37\x2e\
-\x36\x39\x2c\x30\x2c\x30\x2c\x30\x2c\x34\x33\x2e\x36\x36\x2d\x33\
-\x2e\x36\x35\x63\x32\x33\x2e\x33\x2d\x36\x2e\x34\x36\x2c\x34\x32\
-\x2d\x31\x39\x2e\x32\x2c\x35\x34\x2e\x36\x36\x2d\x34\x30\x2e\x32\
-\x39\x2c\x33\x2e\x31\x35\x2d\x35\x2e\x32\x32\x2c\x32\x2e\x36\x37\
-\x2d\x38\x2e\x35\x37\x2d\x31\x2e\x35\x36\x2d\x31\x32\x2e\x37\x31\
-\x2d\x39\x2e\x35\x39\x2d\x39\x2e\x34\x32\x2d\x39\x2e\x33\x37\x2d\
-\x39\x2e\x34\x33\x2d\x32\x32\x2d\x34\x2e\x33\x37\x2d\x33\x34\x2e\
-\x37\x33\x2c\x31\x33\x2e\x38\x39\x2d\x36\x35\x2e\x32\x2c\x34\x2e\
-\x32\x31\x2d\x39\x34\x2e\x35\x35\x2d\x31\x35\x2e\x39\x35\x2d\x31\
-\x33\x2e\x35\x2d\x39\x2e\x32\x36\x2d\x31\x33\x2e\x36\x2d\x32\x30\
-\x2e\x34\x38\x2d\x31\x31\x2e\x32\x31\x2d\x33\x34\x2e\x32\x35\x2c\
-\x35\x2e\x39\x34\x2d\x33\x34\x2e\x32\x2c\x32\x31\x2e\x39\x31\x2d\
-\x36\x32\x2e\x34\x35\x2c\x34\x38\x2e\x32\x33\x2d\x38\x35\x2c\x2e\
-\x37\x2d\x2e\x35\x39\x2c\x31\x2e\x35\x2d\x31\x2e\x30\x35\x2c\x33\
-\x2d\x32\x2e\x31\x31\x2e\x38\x31\x2c\x31\x33\x2d\x2e\x37\x34\x2c\
-\x32\x35\x2e\x31\x31\x2c\x31\x2e\x36\x35\x2c\x33\x37\x2e\x30\x37\
-\x2c\x35\x2e\x33\x34\x2c\x32\x36\x2e\x38\x32\x2c\x31\x38\x2c\x34\
-\x38\x2e\x35\x36\x2c\x34\x31\x2e\x38\x33\x2c\x36\x33\x2e\x31\x36\
-\x2c\x34\x2e\x34\x2c\x32\x2e\x37\x2c\x37\x2e\x32\x35\x2c\x33\x2c\
-\x31\x31\x2e\x31\x37\x2d\x31\x2e\x31\x35\x2c\x39\x2e\x37\x39\x2d\
-\x31\x30\x2e\x32\x36\x2c\x39\x2e\x37\x2d\x39\x2e\x39\x2c\x34\x2e\
-\x38\x2d\x32\x33\x2e\x33\x34\x2d\x31\x32\x2e\x33\x32\x2d\x33\x33\
-\x2e\x37\x39\x2d\x33\x2e\x31\x37\x2d\x36\x33\x2e\x36\x36\x2c\x31\
-\x37\x2d\x39\x31\x2e\x36\x31\x2c\x38\x2e\x30\x38\x2d\x31\x31\x2e\
-\x32\x31\x2c\x31\x36\x2e\x38\x32\x2d\x31\x36\x2e\x33\x39\x2c\x33\
-\x31\x2e\x38\x36\x2d\x31\x33\x2e\x35\x31\x2c\x33\x33\x2e\x35\x33\
-\x2c\x36\x2e\x34\x31\x2c\x36\x31\x2e\x35\x2c\x32\x31\x2e\x35\x33\
-\x2c\x38\x34\x2e\x32\x36\x2c\x34\x36\x2e\x37\x43\x34\x30\x35\x2e\
-\x39\x32\x2c\x32\x31\x30\x2e\x39\x33\x2c\x34\x30\x36\x2c\x32\x31\
-\x31\x2e\x37\x37\x2c\x34\x30\x36\x2e\x37\x33\x2c\x32\x31\x33\x2e\
-\x36\x32\x5a\x4d\x32\x39\x39\x2e\x36\x33\x2c\x33\x31\x39\x2e\x37\
-\x39\x63\x33\x2e\x37\x39\x2e\x31\x31\x2c\x31\x37\x2e\x32\x35\x2d\
-\x31\x33\x2e\x31\x36\x2c\x31\x37\x2e\x34\x37\x2d\x31\x37\x2e\x32\
-\x31\x73\x2d\x31\x32\x2e\x36\x35\x2d\x31\x37\x2e\x32\x39\x2d\x31\
-\x37\x2d\x31\x37\x2e\x35\x32\x63\x2d\x33\x2e\x37\x31\x2d\x2e\x32\
-\x2d\x31\x37\x2e\x34\x33\x2c\x31\x33\x2e\x31\x37\x2d\x31\x37\x2e\
-\x35\x35\x2c\x31\x37\x2e\x31\x53\x32\x39\x35\x2e\x35\x32\x2c\x33\
-\x31\x39\x2e\x36\x37\x2c\x32\x39\x39\x2e\x36\x33\x2c\x33\x31\x39\
-\x2e\x37\x39\x5a\x22\x2f\x3e\x3c\x2f\x73\x76\x67\x3e\
-\x00\x00\x05\xdd\
+\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
+\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\
+\x2d\x38\x22\x3f\x3e\x0a\x3c\x73\x76\x67\x20\x69\x64\x3d\x22\x4c\
+\x61\x79\x65\x72\x5f\x31\x22\x20\x64\x61\x74\x61\x2d\x6e\x61\x6d\
+\x65\x3d\x22\x4c\x61\x79\x65\x72\x20\x31\x22\x20\x78\x6d\x6c\x6e\
+\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\
+\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x76\
+\x69\x65\x77\x42\x6f\x78\x3d\x22\x30\x20\x30\x20\x36\x30\x30\x20\
+\x36\x30\x30\x22\x3e\x0a\x20\x20\x3c\x64\x65\x66\x73\x3e\x0a\x20\
+\x20\x20\x20\x3c\x73\x74\x79\x6c\x65\x3e\x0a\x20\x20\x20\x20\x20\
+\x20\x2e\x63\x6c\x73\x2d\x31\x20\x7b\x0a\x20\x20\x20\x20\x20\x20\
+\x20\x20\x66\x69\x6c\x6c\x3a\x20\x23\x65\x30\x65\x30\x64\x66\x3b\
+\x0a\x20\x20\x20\x20\x20\x20\x7d\x0a\x20\x20\x20\x20\x3c\x2f\x73\
+\x74\x79\x6c\x65\x3e\x0a\x20\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0a\
+\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\
+\x6c\x73\x2d\x31\x22\x20\x64\x3d\x22\x4d\x33\x30\x30\x2c\x35\x30\
+\x35\x2e\x35\x34\x63\x2d\x35\x32\x2e\x34\x36\x2c\x30\x2d\x31\x30\
+\x34\x2e\x39\x31\x2d\x2e\x30\x32\x2d\x31\x35\x37\x2e\x33\x37\x2e\
+\x30\x34\x2d\x32\x36\x2e\x36\x34\x2e\x30\x33\x2d\x34\x38\x2e\x32\
+\x34\x2d\x32\x31\x2e\x35\x34\x2d\x34\x38\x2e\x32\x32\x2d\x34\x38\
+\x2e\x31\x38\x2e\x30\x38\x2d\x31\x30\x34\x2e\x37\x32\x2e\x30\x38\
+\x2d\x32\x30\x39\x2e\x34\x34\x2c\x30\x2d\x33\x31\x34\x2e\x31\x35\
+\x2d\x2e\x30\x32\x2d\x32\x36\x2e\x39\x37\x2c\x32\x31\x2e\x38\x34\
+\x2d\x34\x38\x2e\x38\x33\x2c\x34\x38\x2e\x38\x31\x2d\x34\x38\x2e\
+\x38\x31\x2c\x31\x30\x33\x2e\x38\x32\x2e\x30\x38\x2c\x32\x30\x37\
+\x2e\x36\x35\x2e\x30\x38\x2c\x33\x31\x31\x2e\x34\x37\x2c\x30\x2c\
+\x32\x38\x2e\x31\x32\x2d\x2e\x30\x32\x2c\x35\x30\x2e\x39\x32\x2c\
+\x32\x32\x2e\x37\x37\x2c\x35\x30\x2e\x39\x2c\x35\x30\x2e\x39\x2d\
+\x2e\x30\x37\x2c\x31\x30\x33\x2e\x32\x33\x2d\x2e\x30\x37\x2c\x32\
+\x30\x36\x2e\x34\x36\x2c\x30\x2c\x33\x30\x39\x2e\x36\x38\x2e\x30\
+\x32\x2c\x32\x37\x2e\x39\x36\x2d\x32\x32\x2e\x36\x35\x2c\x35\x30\
+\x2e\x35\x39\x2d\x35\x30\x2e\x36\x2c\x35\x30\x2e\x35\x36\x2d\x35\
+\x31\x2e\x36\x36\x2d\x2e\x30\x36\x2d\x31\x30\x33\x2e\x33\x32\x2d\
+\x2e\x30\x33\x2d\x31\x35\x34\x2e\x39\x39\x2d\x2e\x30\x33\x5a\x4d\
+\x34\x37\x32\x2e\x37\x2c\x33\x30\x30\x2e\x33\x33\x63\x2e\x30\x35\
+\x2d\x39\x36\x2e\x31\x36\x2d\x37\x37\x2e\x37\x31\x2d\x31\x37\x34\
+\x2e\x37\x31\x2d\x31\x37\x33\x2e\x30\x36\x2d\x31\x37\x34\x2e\x38\
+\x31\x2d\x39\x39\x2e\x36\x32\x2d\x2e\x31\x31\x2d\x31\x37\x33\x2e\
+\x31\x33\x2c\x37\x39\x2e\x37\x36\x2d\x31\x37\x35\x2e\x35\x2c\x31\
+\x36\x39\x2e\x35\x39\x2d\x32\x2e\x35\x33\x2c\x39\x36\x2e\x30\x33\
+\x2c\x37\x33\x2e\x35\x37\x2c\x31\x37\x37\x2e\x36\x37\x2c\x31\x37\
+\x31\x2e\x34\x32\x2c\x31\x37\x39\x2e\x31\x34\x2c\x39\x39\x2e\x38\
+\x36\x2c\x31\x2e\x35\x2c\x31\x37\x37\x2e\x33\x31\x2d\x37\x39\x2e\
+\x31\x36\x2c\x31\x37\x37\x2e\x31\x35\x2d\x31\x37\x33\x2e\x39\x32\
+\x5a\x4d\x34\x37\x32\x2e\x36\x39\x2c\x34\x35\x35\x2e\x39\x38\x63\
+\x30\x2d\x31\x30\x2e\x32\x33\x2d\x37\x2e\x38\x37\x2d\x31\x38\x2e\
+\x31\x31\x2d\x31\x38\x2e\x31\x34\x2d\x31\x38\x2e\x31\x37\x2d\x31\
+\x30\x2e\x31\x35\x2d\x2e\x30\x36\x2d\x31\x38\x2e\x33\x33\x2c\x37\
+\x2e\x38\x35\x2d\x31\x38\x2e\x35\x33\x2c\x31\x37\x2e\x39\x2d\x2e\
+\x32\x2c\x31\x30\x2c\x38\x2e\x34\x36\x2c\x31\x38\x2e\x36\x37\x2c\
+\x31\x38\x2e\x35\x39\x2c\x31\x38\x2e\x36\x2c\x31\x30\x2e\x30\x32\
+\x2d\x2e\x30\x37\x2c\x31\x38\x2e\x30\x38\x2d\x38\x2e\x32\x34\x2c\
+\x31\x38\x2e\x30\x38\x2d\x31\x38\x2e\x33\x33\x5a\x4d\x31\x32\x34\
+\x2e\x33\x31\x2c\x31\x34\x33\x2e\x36\x35\x63\x2d\x2e\x30\x35\x2c\
+\x31\x30\x2e\x34\x38\x2c\x37\x2e\x34\x34\x2c\x31\x38\x2e\x32\x38\
+\x2c\x31\x37\x2e\x37\x32\x2c\x31\x38\x2e\x34\x36\x2c\x31\x30\x2e\
+\x34\x33\x2e\x31\x38\x2c\x31\x38\x2e\x36\x36\x2d\x37\x2e\x38\x37\
+\x2c\x31\x38\x2e\x36\x36\x2d\x31\x38\x2e\x32\x37\x2c\x30\x2d\x39\
+\x2e\x39\x31\x2d\x38\x2e\x32\x38\x2d\x31\x38\x2e\x32\x35\x2d\x31\
+\x38\x2e\x32\x2d\x31\x38\x2e\x33\x32\x2d\x31\x30\x2e\x30\x34\x2d\
+\x2e\x30\x38\x2d\x31\x38\x2e\x31\x33\x2c\x38\x2d\x31\x38\x2e\x31\
+\x38\x2c\x31\x38\x2e\x31\x34\x5a\x4d\x34\x37\x32\x2e\x36\x39\x2c\
+\x31\x34\x33\x2e\x38\x35\x63\x2d\x2e\x30\x32\x2d\x31\x30\x2e\x30\
+\x39\x2d\x38\x2e\x31\x35\x2d\x31\x38\x2e\x33\x31\x2d\x31\x38\x2e\
+\x31\x35\x2d\x31\x38\x2e\x33\x34\x2d\x39\x2e\x39\x33\x2d\x2e\x30\
+\x33\x2d\x31\x38\x2e\x33\x35\x2c\x38\x2e\x32\x32\x2d\x31\x38\x2e\
+\x35\x33\x2c\x31\x38\x2e\x31\x34\x2d\x2e\x31\x38\x2c\x31\x30\x2e\
+\x30\x38\x2c\x38\x2e\x33\x32\x2c\x31\x38\x2e\x35\x34\x2c\x31\x38\
+\x2e\x36\x31\x2c\x31\x38\x2e\x34\x39\x2c\x31\x30\x2e\x30\x39\x2d\
+\x2e\x30\x34\x2c\x31\x38\x2e\x31\x2d\x38\x2e\x31\x34\x2c\x31\x38\
+\x2e\x30\x38\x2d\x31\x38\x2e\x33\x5a\x4d\x31\x34\x32\x2e\x35\x2c\
+\x34\x33\x37\x2e\x38\x31\x63\x2d\x31\x30\x2e\x34\x2d\x2e\x30\x36\
+\x2d\x31\x38\x2e\x31\x33\x2c\x37\x2e\x35\x37\x2d\x31\x38\x2e\x31\
+\x39\x2c\x31\x37\x2e\x39\x34\x2d\x2e\x30\x35\x2c\x31\x30\x2e\x36\
+\x32\x2c\x37\x2e\x38\x38\x2c\x31\x38\x2e\x36\x37\x2c\x31\x38\x2e\
+\x32\x38\x2c\x31\x38\x2e\x35\x36\x2c\x31\x30\x2d\x2e\x31\x2c\x31\
+\x38\x2e\x31\x32\x2d\x38\x2e\x33\x34\x2c\x31\x38\x2e\x31\x2d\x31\
+\x38\x2e\x33\x37\x2d\x2e\x30\x31\x2d\x31\x30\x2e\x31\x34\x2d\x37\
+\x2e\x39\x38\x2d\x31\x38\x2e\x30\x37\x2d\x31\x38\x2e\x32\x2d\x31\
+\x38\x2e\x31\x33\x5a\x22\x2f\x3e\x0a\x20\x20\x3c\x70\x61\x74\x68\
+\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x6c\x73\x2d\x31\x22\x20\x64\
+\x3d\x22\x4d\x33\x39\x33\x2e\x34\x2c\x32\x32\x34\x2e\x34\x31\x63\
+\x2d\x31\x38\x2e\x38\x38\x2d\x31\x2e\x39\x31\x2d\x33\x36\x2e\x32\
+\x37\x2e\x37\x33\x2d\x35\x32\x2e\x37\x38\x2c\x38\x2e\x34\x36\x2d\
+\x31\x34\x2e\x30\x39\x2c\x36\x2e\x36\x2d\x32\x35\x2e\x32\x33\x2c\
+\x31\x36\x2e\x34\x34\x2d\x33\x33\x2e\x31\x37\x2c\x32\x39\x2e\x39\
+\x35\x2d\x32\x2e\x35\x35\x2c\x34\x2e\x33\x34\x2d\x32\x2e\x35\x38\
+\x2c\x37\x2e\x33\x31\x2c\x31\x2e\x34\x31\x2c\x31\x31\x2e\x30\x32\
+\x2c\x38\x2e\x35\x32\x2c\x37\x2e\x39\x32\x2c\x38\x2e\x32\x31\x2c\
+\x37\x2e\x39\x36\x2c\x31\x38\x2e\x36\x35\x2c\x33\x2e\x38\x36\x2c\
+\x33\x31\x2e\x39\x38\x2d\x31\x32\x2e\x35\x37\x2c\x35\x39\x2e\x37\
+\x36\x2d\x33\x2e\x36\x36\x2c\x38\x36\x2e\x30\x34\x2c\x31\x36\x2e\
+\x30\x34\x2c\x39\x2e\x35\x33\x2c\x37\x2e\x31\x34\x2c\x38\x2e\x39\
+\x34\x2c\x31\x35\x2e\x33\x32\x2c\x37\x2e\x35\x34\x2c\x32\x34\x2e\
+\x39\x35\x2d\x34\x2e\x35\x37\x2c\x33\x31\x2e\x33\x2d\x31\x38\x2e\
+\x39\x2c\x35\x37\x2e\x30\x38\x2d\x34\x33\x2e\x30\x31\x2c\x37\x37\
+\x2e\x34\x39\x2d\x2e\x34\x39\x2e\x34\x31\x2d\x31\x2e\x31\x32\x2e\
+\x36\x38\x2d\x31\x2e\x37\x31\x2e\x39\x34\x2d\x2e\x31\x36\x2e\x30\
+\x37\x2d\x2e\x34\x32\x2d\x2e\x31\x31\x2d\x2e\x39\x38\x2d\x2e\x32\
+\x39\x2c\x31\x2e\x39\x2d\x31\x38\x2e\x31\x2e\x31\x2d\x33\x35\x2e\
+\x38\x35\x2d\x37\x2e\x35\x2d\x35\x32\x2e\x36\x37\x2d\x36\x2e\x37\
+\x2d\x31\x34\x2e\x38\x33\x2d\x31\x36\x2e\x39\x31\x2d\x32\x36\x2e\
+\x35\x37\x2d\x33\x31\x2e\x31\x36\x2d\x33\x34\x2e\x37\x31\x2d\x33\
+\x2e\x32\x2d\x31\x2e\x38\x33\x2d\x35\x2e\x30\x37\x2d\x31\x2e\x34\
+\x32\x2d\x38\x2e\x31\x33\x2c\x31\x2e\x30\x37\x2d\x38\x2e\x30\x32\
+\x2c\x36\x2e\x34\x39\x2d\x38\x2e\x38\x38\x2c\x31\x32\x2e\x30\x39\
+\x2d\x34\x2e\x37\x33\x2c\x32\x32\x2e\x34\x32\x2c\x31\x31\x2e\x37\
+\x38\x2c\x32\x39\x2e\x32\x39\x2c\x31\x2e\x39\x35\x2c\x35\x35\x2e\
+\x35\x32\x2d\x31\x35\x2e\x37\x35\x2c\x37\x39\x2e\x37\x36\x2d\x36\
+\x2e\x37\x34\x2c\x39\x2e\x32\x33\x2d\x31\x34\x2e\x32\x33\x2c\x31\
+\x32\x2e\x39\x37\x2d\x32\x36\x2e\x33\x35\x2c\x31\x30\x2e\x37\x39\
+\x2d\x32\x39\x2e\x36\x38\x2d\x35\x2e\x33\x33\x2d\x35\x34\x2e\x33\
+\x39\x2d\x31\x38\x2e\x36\x35\x2d\x37\x34\x2e\x33\x39\x2d\x34\x31\
+\x2e\x30\x34\x2d\x2e\x34\x32\x2d\x2e\x34\x37\x2d\x2e\x36\x31\x2d\
+\x31\x2e\x31\x34\x2d\x31\x2e\x33\x32\x2d\x32\x2e\x34\x39\x2c\x31\
+\x33\x2e\x32\x38\x2c\x31\x2e\x33\x32\x2c\x32\x35\x2e\x38\x39\x2e\
+\x32\x33\x2c\x33\x38\x2e\x32\x2d\x33\x2e\x31\x39\x2c\x32\x30\x2e\
+\x33\x39\x2d\x35\x2e\x36\x36\x2c\x33\x36\x2e\x37\x32\x2d\x31\x36\
+\x2e\x38\x31\x2c\x34\x37\x2e\x38\x33\x2d\x33\x35\x2e\x32\x36\x2c\
+\x32\x2e\x37\x35\x2d\x34\x2e\x35\x37\x2c\x32\x2e\x33\x33\x2d\x37\
+\x2e\x35\x2d\x31\x2e\x33\x36\x2d\x31\x31\x2e\x31\x32\x2d\x38\x2e\
+\x34\x2d\x38\x2e\x32\x34\x2d\x38\x2e\x32\x2d\x38\x2e\x32\x36\x2d\
+\x31\x39\x2e\x32\x38\x2d\x33\x2e\x38\x33\x2d\x33\x30\x2e\x34\x2c\
+\x31\x32\x2e\x31\x36\x2d\x35\x37\x2e\x30\x35\x2c\x33\x2e\x36\x39\
+\x2d\x38\x32\x2e\x37\x34\x2d\x31\x33\x2e\x39\x35\x2d\x31\x31\x2e\
+\x38\x31\x2d\x38\x2e\x31\x31\x2d\x31\x31\x2e\x39\x2d\x31\x37\x2e\
+\x39\x32\x2d\x39\x2e\x38\x31\x2d\x32\x39\x2e\x39\x37\x2c\x35\x2e\
+\x32\x2d\x32\x39\x2e\x39\x33\x2c\x31\x39\x2e\x31\x37\x2d\x35\x34\
+\x2e\x36\x35\x2c\x34\x32\x2e\x32\x31\x2d\x37\x34\x2e\x33\x34\x2e\
+\x36\x31\x2d\x2e\x35\x32\x2c\x31\x2e\x33\x31\x2d\x2e\x39\x32\x2c\
+\x32\x2e\x36\x35\x2d\x31\x2e\x38\x34\x2e\x37\x31\x2c\x31\x31\x2e\
+\x33\x39\x2d\x2e\x36\x35\x2c\x32\x31\x2e\x39\x37\x2c\x31\x2e\x34\
+\x34\x2c\x33\x32\x2e\x34\x33\x2c\x34\x2e\x36\x38\x2c\x32\x33\x2e\
+\x34\x37\x2c\x31\x35\x2e\x37\x38\x2c\x34\x32\x2e\x35\x2c\x33\x36\
+\x2e\x36\x2c\x35\x35\x2e\x32\x37\x2c\x33\x2e\x38\x36\x2c\x32\x2e\
+\x33\x36\x2c\x36\x2e\x33\x35\x2c\x32\x2e\x36\x2c\x39\x2e\x37\x38\
+\x2d\x31\x2e\x30\x31\x2c\x38\x2e\x35\x36\x2d\x38\x2e\x39\x37\x2c\
+\x38\x2e\x34\x38\x2d\x38\x2e\x36\x36\x2c\x34\x2e\x32\x2d\x32\x30\
+\x2e\x34\x33\x2d\x31\x30\x2e\x37\x38\x2d\x32\x39\x2e\x35\x37\x2d\
+\x32\x2e\x37\x37\x2d\x35\x35\x2e\x37\x2c\x31\x34\x2e\x38\x37\x2d\
+\x38\x30\x2e\x31\x37\x2c\x37\x2e\x30\x37\x2d\x39\x2e\x38\x2c\x31\
+\x34\x2e\x37\x32\x2d\x31\x34\x2e\x33\x34\x2c\x32\x37\x2e\x38\x38\
+\x2d\x31\x31\x2e\x38\x32\x2c\x32\x39\x2e\x33\x34\x2c\x35\x2e\x36\
+\x31\x2c\x35\x33\x2e\x38\x32\x2c\x31\x38\x2e\x38\x34\x2c\x37\x33\
+\x2e\x37\x34\x2c\x34\x30\x2e\x38\x37\x2e\x34\x2e\x34\x35\x2e\x34\
+\x39\x2c\x31\x2e\x31\x38\x2c\x31\x2e\x31\x31\x2c\x32\x2e\x38\x5a\
+\x4d\x32\x39\x39\x2e\x36\x38\x2c\x33\x31\x37\x2e\x33\x32\x63\x33\
+\x2e\x33\x32\x2e\x30\x39\x2c\x31\x35\x2e\x31\x2d\x31\x31\x2e\x35\
+\x31\x2c\x31\x35\x2e\x32\x38\x2d\x31\x35\x2e\x30\x36\x2e\x31\x39\
+\x2d\x33\x2e\x35\x35\x2d\x31\x31\x2e\x30\x37\x2d\x31\x35\x2e\x31\
+\x33\x2d\x31\x34\x2e\x38\x39\x2d\x31\x35\x2e\x33\x33\x2d\x33\x2e\
+\x32\x35\x2d\x2e\x31\x37\x2d\x31\x35\x2e\x32\x35\x2c\x31\x31\x2e\
+\x35\x33\x2d\x31\x35\x2e\x33\x35\x2c\x31\x34\x2e\x39\x37\x2d\x2e\
+\x31\x2c\x33\x2e\x35\x2c\x31\x31\x2e\x33\x37\x2c\x31\x35\x2e\x33\
+\x33\x2c\x31\x34\x2e\x39\x36\x2c\x31\x35\x2e\x34\x33\x5a\x22\x2f\
+\x3e\x0a\x3c\x2f\x73\x76\x67\x3e\
+\x00\x00\x05\xcf\
\x3c\
\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\
@@ -11680,86 +11694,85 @@
\x0a\x20\x20\x20\x20\x20\x20\x7d\x0a\x20\x20\x20\x20\x3c\x2f\x73\
\x74\x79\x6c\x65\x3e\x0a\x20\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0a\
\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\
-\x6c\x73\x2d\x31\x22\x20\x64\x3d\x22\x4d\x34\x38\x30\x2e\x30\x34\
-\x2c\x31\x35\x30\x2e\x34\x37\x63\x2d\x33\x36\x2e\x33\x35\x2d\x33\
-\x2e\x36\x37\x2d\x36\x39\x2e\x38\x34\x2c\x31\x2e\x34\x2d\x31\x30\
-\x31\x2e\x36\x32\x2c\x31\x36\x2e\x32\x39\x2d\x32\x37\x2e\x31\x33\
-\x2c\x31\x32\x2e\x37\x31\x2d\x34\x38\x2e\x35\x38\x2c\x33\x31\x2e\
-\x36\x34\x2d\x36\x33\x2e\x38\x36\x2c\x35\x37\x2e\x36\x36\x2d\x34\
-\x2e\x39\x31\x2c\x38\x2e\x33\x36\x2d\x34\x2e\x39\x36\x2c\x31\x34\
-\x2e\x30\x38\x2c\x32\x2e\x37\x32\x2c\x32\x31\x2e\x32\x32\x2c\x31\
-\x36\x2e\x34\x31\x2c\x31\x35\x2e\x32\x35\x2c\x31\x35\x2e\x38\x31\
-\x2c\x31\x35\x2e\x33\x32\x2c\x33\x35\x2e\x39\x2c\x37\x2e\x34\x33\
-\x2c\x36\x31\x2e\x35\x38\x2d\x32\x34\x2e\x31\x39\x2c\x31\x31\x35\
-\x2e\x30\x35\x2d\x37\x2e\x30\x34\x2c\x31\x36\x35\x2e\x36\x35\x2c\
-\x33\x30\x2e\x38\x38\x2c\x31\x38\x2e\x33\x34\x2c\x31\x33\x2e\x37\
-\x34\x2c\x31\x37\x2e\x32\x32\x2c\x32\x39\x2e\x34\x39\x2c\x31\x34\
-\x2e\x35\x31\x2c\x34\x38\x2e\x30\x33\x2d\x38\x2e\x38\x2c\x36\x30\
-\x2e\x32\x35\x2d\x33\x36\x2e\x34\x2c\x31\x30\x39\x2e\x39\x2d\x38\
-\x32\x2e\x38\x2c\x31\x34\x39\x2e\x31\x38\x2d\x2e\x39\x34\x2e\x38\
-\x2d\x32\x2e\x31\x36\x2c\x31\x2e\x33\x2d\x33\x2e\x33\x2c\x31\x2e\
-\x38\x31\x2d\x2e\x33\x2e\x31\x33\x2d\x2e\x38\x31\x2d\x2e\x32\x32\
-\x2d\x31\x2e\x38\x38\x2d\x2e\x35\x35\x2c\x33\x2e\x36\x35\x2d\x33\
-\x34\x2e\x38\x36\x2e\x31\x39\x2d\x36\x39\x2e\x30\x31\x2d\x31\x34\
-\x2e\x34\x33\x2d\x31\x30\x31\x2e\x34\x2d\x31\x32\x2e\x39\x2d\x32\
-\x38\x2e\x35\x35\x2d\x33\x32\x2e\x35\x36\x2d\x35\x31\x2e\x31\x36\
-\x2d\x35\x39\x2e\x39\x39\x2d\x36\x36\x2e\x38\x33\x2d\x36\x2e\x31\
-\x37\x2d\x33\x2e\x35\x32\x2d\x39\x2e\x37\x35\x2d\x32\x2e\x37\x32\
-\x2d\x31\x35\x2e\x36\x36\x2c\x32\x2e\x30\x35\x2d\x31\x35\x2e\x34\
-\x35\x2c\x31\x32\x2e\x35\x2d\x31\x37\x2e\x31\x2c\x32\x33\x2e\x32\
-\x37\x2d\x39\x2e\x31\x2c\x34\x33\x2e\x31\x37\x2c\x32\x32\x2e\x36\
-\x39\x2c\x35\x36\x2e\x33\x39\x2c\x33\x2e\x37\x35\x2c\x31\x30\x36\
-\x2e\x38\x39\x2d\x33\x30\x2e\x33\x32\x2c\x31\x35\x33\x2e\x35\x36\
-\x2d\x31\x32\x2e\x39\x38\x2c\x31\x37\x2e\x37\x38\x2d\x32\x37\x2e\
-\x34\x2c\x32\x34\x2e\x39\x37\x2d\x35\x30\x2e\x37\x32\x2c\x32\x30\
-\x2e\x37\x38\x2d\x35\x37\x2e\x31\x33\x2d\x31\x30\x2e\x32\x36\x2d\
-\x31\x30\x34\x2e\x37\x32\x2d\x33\x35\x2e\x39\x31\x2d\x31\x34\x33\
-\x2e\x32\x31\x2d\x37\x39\x2e\x30\x31\x2d\x2e\x38\x31\x2d\x2e\x39\
-\x2d\x31\x2e\x31\x38\x2d\x32\x2e\x31\x39\x2d\x32\x2e\x35\x33\x2d\
-\x34\x2e\x37\x39\x2c\x32\x35\x2e\x35\x37\x2c\x32\x2e\x35\x34\x2c\
-\x34\x39\x2e\x38\x35\x2e\x34\x33\x2c\x37\x33\x2e\x35\x35\x2d\x36\
-\x2e\x31\x34\x2c\x33\x39\x2e\x32\x35\x2d\x31\x30\x2e\x38\x39\x2c\
-\x37\x30\x2e\x37\x2d\x33\x32\x2e\x33\x36\x2c\x39\x32\x2e\x30\x39\
-\x2d\x36\x37\x2e\x38\x38\x2c\x35\x2e\x33\x2d\x38\x2e\x38\x2c\x34\
-\x2e\x34\x39\x2d\x31\x34\x2e\x34\x34\x2d\x32\x2e\x36\x32\x2d\x32\
-\x31\x2e\x34\x32\x2d\x31\x36\x2e\x31\x37\x2d\x31\x35\x2e\x38\x37\
-\x2d\x31\x35\x2e\x37\x39\x2d\x31\x35\x2e\x38\x39\x2d\x33\x37\x2e\
-\x31\x32\x2d\x37\x2e\x33\x37\x2d\x35\x38\x2e\x35\x32\x2c\x32\x33\
-\x2e\x34\x2d\x31\x30\x39\x2e\x38\x34\x2c\x37\x2e\x31\x2d\x31\x35\
-\x39\x2e\x33\x2d\x32\x36\x2e\x38\x36\x2d\x32\x32\x2e\x37\x33\x2d\
-\x31\x35\x2e\x36\x31\x2d\x32\x32\x2e\x39\x31\x2d\x33\x34\x2e\x35\
-\x2d\x31\x38\x2e\x38\x38\x2d\x35\x37\x2e\x37\x31\x2c\x31\x30\x2e\
-\x30\x31\x2d\x35\x37\x2e\x36\x32\x2c\x33\x36\x2e\x39\x31\x2d\x31\
-\x30\x35\x2e\x32\x32\x2c\x38\x31\x2e\x32\x37\x2d\x31\x34\x33\x2e\
-\x31\x32\x2c\x31\x2e\x31\x37\x2d\x31\x2c\x32\x2e\x35\x33\x2d\x31\
-\x2e\x37\x37\x2c\x35\x2e\x31\x2d\x33\x2e\x35\x35\x2c\x31\x2e\x33\
-\x36\x2c\x32\x31\x2e\x39\x33\x2d\x31\x2e\x32\x34\x2c\x34\x32\x2e\
-\x33\x2c\x32\x2e\x37\x37\x2c\x36\x32\x2e\x34\x35\x2c\x39\x2e\x30\
-\x31\x2c\x34\x35\x2e\x31\x39\x2c\x33\x30\x2e\x33\x37\x2c\x38\x31\
-\x2e\x38\x32\x2c\x37\x30\x2e\x34\x37\x2c\x31\x30\x36\x2e\x34\x31\
-\x2c\x37\x2e\x34\x32\x2c\x34\x2e\x35\x35\x2c\x31\x32\x2e\x32\x32\
-\x2c\x35\x2c\x31\x38\x2e\x38\x33\x2d\x31\x2e\x39\x34\x2c\x31\x36\
-\x2e\x34\x38\x2d\x31\x37\x2e\x32\x38\x2c\x31\x36\x2e\x33\x33\x2d\
-\x31\x36\x2e\x36\x38\x2c\x38\x2e\x30\x38\x2d\x33\x39\x2e\x33\x33\
-\x2d\x32\x30\x2e\x37\x35\x2d\x35\x36\x2e\x39\x33\x2d\x35\x2e\x33\
-\x33\x2d\x31\x30\x37\x2e\x32\x35\x2c\x32\x38\x2e\x36\x32\x2d\x31\
-\x35\x34\x2e\x33\x34\x2c\x31\x33\x2e\x36\x31\x2d\x31\x38\x2e\x38\
-\x38\x2c\x32\x38\x2e\x33\x34\x2d\x32\x37\x2e\x36\x31\x2c\x35\x33\
-\x2e\x36\x37\x2d\x32\x32\x2e\x37\x36\x2c\x35\x36\x2e\x34\x39\x2c\
-\x31\x30\x2e\x38\x31\x2c\x31\x30\x33\x2e\x36\x32\x2c\x33\x36\x2e\
-\x32\x38\x2c\x31\x34\x31\x2e\x39\x37\x2c\x37\x38\x2e\x36\x39\x2e\
-\x37\x38\x2e\x38\x36\x2e\x39\x33\x2c\x32\x2e\x32\x37\x2c\x32\x2e\
-\x31\x34\x2c\x35\x2e\x33\x39\x5a\x4d\x32\x39\x39\x2e\x36\x2c\x33\
-\x32\x39\x2e\x33\x35\x63\x36\x2e\x33\x39\x2e\x31\x38\x2c\x32\x39\
-\x2e\x30\x37\x2d\x32\x32\x2e\x31\x37\x2c\x32\x39\x2e\x34\x33\x2d\
-\x32\x39\x2c\x2e\x33\x36\x2d\x36\x2e\x38\x33\x2d\x32\x31\x2e\x33\
-\x2d\x32\x39\x2e\x31\x33\x2d\x32\x38\x2e\x36\x37\x2d\x32\x39\x2e\
-\x35\x32\x2d\x36\x2e\x32\x35\x2d\x2e\x33\x33\x2d\x32\x39\x2e\x33\
-\x36\x2c\x32\x32\x2e\x32\x2d\x32\x39\x2e\x35\x36\x2c\x32\x38\x2e\
-\x38\x32\x2d\x2e\x32\x2c\x36\x2e\x37\x33\x2c\x32\x31\x2e\x38\x38\
-\x2c\x32\x39\x2e\x35\x31\x2c\x32\x38\x2e\x38\x2c\x32\x39\x2e\x37\
-\x31\x5a\x22\x2f\x3e\x0a\x3c\x2f\x73\x76\x67\x3e\
-\x00\x00\x0b\x8f\
+\x6c\x73\x2d\x31\x22\x20\x64\x3d\x22\x4d\x34\x32\x39\x2e\x30\x34\
+\x2c\x31\x39\x32\x2e\x38\x33\x63\x2d\x32\x36\x2e\x30\x35\x2d\x32\
+\x2e\x36\x33\x2d\x35\x30\x2e\x30\x35\x2c\x31\x2e\x30\x31\x2d\x37\
+\x32\x2e\x38\x33\x2c\x31\x31\x2e\x36\x38\x2d\x31\x39\x2e\x34\x35\
+\x2c\x39\x2e\x31\x31\x2d\x33\x34\x2e\x38\x32\x2c\x32\x32\x2e\x36\
+\x38\x2d\x34\x35\x2e\x37\x37\x2c\x34\x31\x2e\x33\x32\x2d\x33\x2e\
+\x35\x32\x2c\x35\x2e\x39\x39\x2d\x33\x2e\x35\x36\x2c\x31\x30\x2e\
+\x30\x39\x2c\x31\x2e\x39\x35\x2c\x31\x35\x2e\x32\x31\x2c\x31\x31\
+\x2e\x37\x36\x2c\x31\x30\x2e\x39\x33\x2c\x31\x31\x2e\x33\x33\x2c\
+\x31\x30\x2e\x39\x38\x2c\x32\x35\x2e\x37\x33\x2c\x35\x2e\x33\x32\
+\x2c\x34\x34\x2e\x31\x33\x2d\x31\x37\x2e\x33\x34\x2c\x38\x32\x2e\
+\x34\x36\x2d\x35\x2e\x30\x35\x2c\x31\x31\x38\x2e\x37\x32\x2c\x32\
+\x32\x2e\x31\x33\x2c\x31\x33\x2e\x31\x34\x2c\x39\x2e\x38\x35\x2c\
+\x31\x32\x2e\x33\x34\x2c\x32\x31\x2e\x31\x33\x2c\x31\x30\x2e\x34\
+\x2c\x33\x34\x2e\x34\x32\x2d\x36\x2e\x33\x31\x2c\x34\x33\x2e\x31\
+\x38\x2d\x32\x36\x2e\x30\x39\x2c\x37\x38\x2e\x37\x37\x2d\x35\x39\
+\x2e\x33\x34\x2c\x31\x30\x36\x2e\x39\x32\x2d\x2e\x36\x37\x2e\x35\
+\x37\x2d\x31\x2e\x35\x34\x2e\x39\x33\x2d\x32\x2e\x33\x36\x2c\x31\
+\x2e\x33\x2d\x2e\x32\x31\x2e\x31\x2d\x2e\x35\x38\x2d\x2e\x31\x36\
+\x2d\x31\x2e\x33\x35\x2d\x2e\x34\x2c\x32\x2e\x36\x32\x2d\x32\x34\
+\x2e\x39\x38\x2e\x31\x34\x2d\x34\x39\x2e\x34\x36\x2d\x31\x30\x2e\
+\x33\x34\x2d\x37\x32\x2e\x36\x37\x2d\x39\x2e\x32\x34\x2d\x32\x30\
+\x2e\x34\x36\x2d\x32\x33\x2e\x33\x34\x2d\x33\x36\x2e\x36\x37\x2d\
+\x34\x33\x2d\x34\x37\x2e\x39\x2d\x34\x2e\x34\x32\x2d\x32\x2e\x35\
+\x33\x2d\x36\x2e\x39\x39\x2d\x31\x2e\x39\x35\x2d\x31\x31\x2e\x32\
+\x32\x2c\x31\x2e\x34\x37\x2d\x31\x31\x2e\x30\x37\x2c\x38\x2e\x39\
+\x36\x2d\x31\x32\x2e\x32\x36\x2c\x31\x36\x2e\x36\x38\x2d\x36\x2e\
+\x35\x32\x2c\x33\x30\x2e\x39\x34\x2c\x31\x36\x2e\x32\x36\x2c\x34\
+\x30\x2e\x34\x32\x2c\x32\x2e\x36\x39\x2c\x37\x36\x2e\x36\x31\x2d\
+\x32\x31\x2e\x37\x33\x2c\x31\x31\x30\x2e\x30\x36\x2d\x39\x2e\x33\
+\x2c\x31\x32\x2e\x37\x34\x2d\x31\x39\x2e\x36\x34\x2c\x31\x37\x2e\
+\x39\x2d\x33\x36\x2e\x33\x35\x2c\x31\x34\x2e\x38\x39\x2d\x34\x30\
+\x2e\x39\x35\x2d\x37\x2e\x33\x35\x2d\x37\x35\x2e\x30\x36\x2d\x32\
+\x35\x2e\x37\x34\x2d\x31\x30\x32\x2e\x36\x34\x2d\x35\x36\x2e\x36\
+\x33\x2d\x2e\x35\x38\x2d\x2e\x36\x35\x2d\x2e\x38\x35\x2d\x31\x2e\
+\x35\x37\x2d\x31\x2e\x38\x32\x2d\x33\x2e\x34\x33\x2c\x31\x38\x2e\
+\x33\x32\x2c\x31\x2e\x38\x32\x2c\x33\x35\x2e\x37\x33\x2e\x33\x31\
+\x2c\x35\x32\x2e\x37\x32\x2d\x34\x2e\x34\x2c\x32\x38\x2e\x31\x33\
+\x2d\x37\x2e\x38\x2c\x35\x30\x2e\x36\x37\x2d\x32\x33\x2e\x31\x39\
+\x2c\x36\x36\x2d\x34\x38\x2e\x36\x35\x2c\x33\x2e\x38\x2d\x36\x2e\
+\x33\x31\x2c\x33\x2e\x32\x32\x2d\x31\x30\x2e\x33\x35\x2d\x31\x2e\
+\x38\x38\x2d\x31\x35\x2e\x33\x35\x2d\x31\x31\x2e\x35\x39\x2d\x31\
+\x31\x2e\x33\x37\x2d\x31\x31\x2e\x33\x32\x2d\x31\x31\x2e\x33\x39\
+\x2d\x32\x36\x2e\x36\x31\x2d\x35\x2e\x32\x38\x2d\x34\x31\x2e\x39\
+\x34\x2c\x31\x36\x2e\x37\x37\x2d\x37\x38\x2e\x37\x33\x2c\x35\x2e\
+\x30\x39\x2d\x31\x31\x34\x2e\x31\x37\x2d\x31\x39\x2e\x32\x35\x2d\
+\x31\x36\x2e\x32\x39\x2d\x31\x31\x2e\x31\x39\x2d\x31\x36\x2e\x34\
+\x32\x2d\x32\x34\x2e\x37\x33\x2d\x31\x33\x2e\x35\x33\x2d\x34\x31\
+\x2e\x33\x36\x2c\x37\x2e\x31\x38\x2d\x34\x31\x2e\x32\x39\x2c\x32\
+\x36\x2e\x34\x35\x2d\x37\x35\x2e\x34\x32\x2c\x35\x38\x2e\x32\x34\
+\x2d\x31\x30\x32\x2e\x35\x38\x2e\x38\x34\x2d\x2e\x37\x31\x2c\x31\
+\x2e\x38\x31\x2d\x31\x2e\x32\x37\x2c\x33\x2e\x36\x35\x2d\x32\x2e\
+\x35\x34\x2e\x39\x38\x2c\x31\x35\x2e\x37\x32\x2d\x2e\x38\x39\x2c\
+\x33\x30\x2e\x33\x31\x2c\x31\x2e\x39\x39\x2c\x34\x34\x2e\x37\x36\
+\x2c\x36\x2e\x34\x36\x2c\x33\x32\x2e\x33\x39\x2c\x32\x31\x2e\x37\
+\x37\x2c\x35\x38\x2e\x36\x34\x2c\x35\x30\x2e\x35\x31\x2c\x37\x36\
+\x2e\x32\x36\x2c\x35\x2e\x33\x32\x2c\x33\x2e\x32\x36\x2c\x38\x2e\
+\x37\x36\x2c\x33\x2e\x35\x38\x2c\x31\x33\x2e\x35\x2d\x31\x2e\x33\
+\x39\x2c\x31\x31\x2e\x38\x31\x2d\x31\x32\x2e\x33\x38\x2c\x31\x31\
+\x2e\x37\x31\x2d\x31\x31\x2e\x39\x36\x2c\x35\x2e\x37\x39\x2d\x32\
+\x38\x2e\x31\x39\x2d\x31\x34\x2e\x38\x37\x2d\x34\x30\x2e\x38\x2d\
+\x33\x2e\x38\x32\x2d\x37\x36\x2e\x38\x36\x2c\x32\x30\x2e\x35\x31\
+\x2d\x31\x31\x30\x2e\x36\x32\x2c\x39\x2e\x37\x35\x2d\x31\x33\x2e\
+\x35\x33\x2c\x32\x30\x2e\x33\x31\x2d\x31\x39\x2e\x37\x39\x2c\x33\
+\x38\x2e\x34\x37\x2d\x31\x36\x2e\x33\x31\x2c\x34\x30\x2e\x34\x39\
+\x2c\x37\x2e\x37\x34\x2c\x37\x34\x2e\x32\x36\x2c\x32\x36\x2c\x31\
+\x30\x31\x2e\x37\x35\x2c\x35\x36\x2e\x34\x2e\x35\x36\x2e\x36\x31\
+\x2e\x36\x37\x2c\x31\x2e\x36\x33\x2c\x31\x2e\x35\x33\x2c\x33\x2e\
+\x38\x36\x5a\x4d\x32\x39\x39\x2e\x37\x32\x2c\x33\x32\x31\x2e\x30\
+\x33\x63\x34\x2e\x35\x38\x2e\x31\x33\x2c\x32\x30\x2e\x38\x33\x2d\
+\x31\x35\x2e\x38\x39\x2c\x32\x31\x2e\x30\x39\x2d\x32\x30\x2e\x37\
+\x39\x2e\x32\x36\x2d\x34\x2e\x38\x39\x2d\x31\x35\x2e\x32\x37\x2d\
+\x32\x30\x2e\x38\x38\x2d\x32\x30\x2e\x35\x35\x2d\x32\x31\x2e\x31\
+\x36\x2d\x34\x2e\x34\x38\x2d\x2e\x32\x34\x2d\x32\x31\x2e\x30\x34\
+\x2c\x31\x35\x2e\x39\x31\x2d\x32\x31\x2e\x31\x38\x2c\x32\x30\x2e\
+\x36\x35\x2d\x2e\x31\x34\x2c\x34\x2e\x38\x33\x2c\x31\x35\x2e\x36\
+\x38\x2c\x32\x31\x2e\x31\x35\x2c\x32\x30\x2e\x36\x34\x2c\x32\x31\
+\x2e\x32\x39\x5a\x22\x2f\x3e\x0a\x3c\x2f\x73\x76\x67\x3e\
+\x00\x00\x0b\x76\
\x3c\
\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\
@@ -11776,176 +11789,175 @@
\x0a\x20\x20\x20\x20\x20\x20\x7d\x0a\x20\x20\x20\x20\x3c\x2f\x73\
\x74\x79\x6c\x65\x3e\x0a\x20\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0a\
\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\
-\x6c\x73\x2d\x31\x22\x20\x64\x3d\x22\x4d\x34\x35\x37\x2e\x37\x39\
-\x2c\x33\x31\x38\x2e\x30\x33\x63\x2e\x34\x37\x2d\x31\x32\x2e\x30\
-\x31\x2c\x35\x2e\x32\x34\x2d\x31\x37\x2e\x36\x2c\x31\x35\x2e\x30\
-\x31\x2d\x31\x36\x2e\x39\x36\x2c\x31\x30\x2e\x38\x36\x2e\x37\x2c\
-\x31\x33\x2e\x33\x38\x2c\x37\x2e\x38\x35\x2c\x31\x33\x2e\x33\x37\
-\x2c\x31\x37\x2e\x33\x39\x2d\x2e\x31\x31\x2c\x36\x36\x2e\x36\x39\
-\x2d\x2e\x30\x32\x2c\x31\x33\x33\x2e\x33\x38\x2d\x2e\x31\x31\x2c\
-\x32\x30\x30\x2e\x30\x38\x2d\x2e\x30\x31\x2c\x31\x31\x2e\x31\x34\
-\x2d\x35\x2e\x30\x32\x2c\x31\x36\x2e\x37\x2d\x31\x34\x2e\x33\x33\
-\x2c\x31\x36\x2e\x33\x37\x2d\x31\x30\x2e\x30\x36\x2d\x2e\x33\x35\
-\x2d\x31\x34\x2e\x35\x39\x2d\x36\x2e\x35\x38\x2d\x31\x33\x2e\x37\
-\x33\x2d\x31\x35\x2e\x37\x37\x2c\x31\x2e\x30\x36\x2d\x31\x31\x2e\
-\x33\x33\x2d\x33\x2e\x34\x31\x2d\x31\x33\x2e\x37\x36\x2d\x31\x34\
-\x2e\x31\x35\x2d\x31\x33\x2e\x36\x39\x2d\x36\x35\x2e\x31\x36\x2e\
-\x34\x38\x2d\x31\x33\x30\x2e\x33\x33\x2e\x33\x39\x2d\x31\x39\x35\
-\x2e\x35\x2e\x31\x33\x2d\x34\x34\x2e\x30\x39\x2d\x2e\x31\x38\x2d\
-\x38\x32\x2e\x33\x35\x2d\x31\x35\x2e\x37\x36\x2d\x31\x31\x35\x2e\
-\x34\x32\x2d\x34\x34\x2e\x37\x38\x2d\x37\x2e\x31\x2d\x36\x2e\x32\
-\x33\x2d\x31\x34\x2e\x37\x35\x2d\x31\x31\x2e\x39\x35\x2d\x32\x32\
-\x2e\x36\x38\x2d\x31\x37\x2e\x30\x37\x43\x34\x30\x2e\x35\x32\x2c\
-\x33\x39\x38\x2e\x36\x34\x2c\x34\x2e\x33\x34\x2c\x33\x31\x34\x2e\
-\x39\x39\x2c\x31\x39\x2e\x34\x33\x2c\x32\x33\x33\x2e\x31\x37\x2c\
-\x34\x31\x2e\x34\x34\x2c\x31\x31\x33\x2e\x37\x37\x2c\x31\x36\x33\
-\x2e\x35\x38\x2c\x33\x39\x2e\x30\x31\x2c\x32\x38\x30\x2e\x32\x33\
-\x2c\x37\x33\x2e\x35\x34\x63\x38\x32\x2e\x36\x36\x2c\x32\x34\x2e\
-\x34\x37\x2c\x31\x34\x31\x2e\x33\x2c\x39\x36\x2e\x39\x2c\x31\x34\
-\x36\x2e\x31\x31\x2c\x31\x38\x33\x2e\x30\x34\x2c\x31\x2e\x33\x33\
-\x2c\x32\x33\x2e\x38\x39\x2d\x32\x2e\x32\x32\x2c\x34\x38\x2e\x30\
-\x36\x2d\x33\x2e\x35\x36\x2c\x37\x32\x2e\x36\x32\x2c\x39\x2e\x36\
-\x38\x2c\x30\x2c\x32\x31\x2e\x33\x36\x2c\x30\x2c\x33\x34\x2e\x34\
-\x33\x2c\x30\x2c\x2e\x32\x34\x2d\x34\x2e\x34\x37\x2e\x34\x35\x2d\
-\x37\x2e\x38\x31\x2e\x35\x38\x2d\x31\x31\x2e\x31\x36\x5a\x4d\x32\
-\x32\x32\x2e\x39\x39\x2c\x39\x33\x2e\x38\x38\x63\x2d\x39\x37\x2e\
-\x37\x36\x2d\x2e\x34\x35\x2d\x31\x37\x38\x2e\x39\x38\x2c\x38\x30\
-\x2e\x37\x39\x2d\x31\x37\x38\x2e\x30\x33\x2c\x31\x37\x38\x2e\x30\
-\x36\x2e\x39\x35\x2c\x39\x36\x2e\x38\x33\x2c\x38\x30\x2e\x32\x34\
-\x2c\x31\x37\x35\x2e\x37\x34\x2c\x31\x37\x36\x2e\x36\x2c\x31\x37\
-\x35\x2e\x37\x35\x2c\x39\x38\x2c\x30\x2c\x31\x37\x37\x2e\x32\x35\
-\x2d\x37\x39\x2e\x32\x33\x2c\x31\x37\x37\x2e\x32\x36\x2d\x31\x37\
-\x37\x2e\x32\x33\x2c\x30\x2d\x39\x36\x2e\x30\x33\x2d\x37\x39\x2e\
-\x37\x37\x2d\x31\x37\x36\x2e\x31\x34\x2d\x31\x37\x35\x2e\x38\x34\
-\x2d\x31\x37\x36\x2e\x35\x38\x5a\x22\x2f\x3e\x0a\x20\x20\x3c\x70\
-\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x6c\x73\x2d\x31\
-\x22\x20\x64\x3d\x22\x4d\x33\x32\x34\x2e\x33\x38\x2c\x31\x38\x35\
-\x2e\x36\x36\x63\x2d\x32\x30\x2e\x36\x39\x2d\x32\x2e\x30\x39\x2d\
-\x33\x39\x2e\x37\x36\x2e\x38\x2d\x35\x37\x2e\x38\x35\x2c\x39\x2e\
-\x32\x37\x2d\x31\x35\x2e\x34\x35\x2c\x37\x2e\x32\x34\x2d\x32\x37\
-\x2e\x36\x35\x2c\x31\x38\x2e\x30\x31\x2d\x33\x36\x2e\x33\x35\x2c\
-\x33\x32\x2e\x38\x32\x2d\x32\x2e\x38\x2c\x34\x2e\x37\x36\x2d\x32\
-\x2e\x38\x33\x2c\x38\x2e\x30\x31\x2c\x31\x2e\x35\x35\x2c\x31\x32\
-\x2e\x30\x38\x2c\x39\x2e\x33\x34\x2c\x38\x2e\x36\x38\x2c\x39\x2c\
-\x38\x2e\x37\x32\x2c\x32\x30\x2e\x34\x34\x2c\x34\x2e\x32\x33\x2c\
-\x33\x35\x2e\x30\x35\x2d\x31\x33\x2e\x37\x37\x2c\x36\x35\x2e\x34\
-\x39\x2d\x34\x2e\x30\x31\x2c\x39\x34\x2e\x33\x2c\x31\x37\x2e\x35\
-\x38\x2c\x31\x30\x2e\x34\x34\x2c\x37\x2e\x38\x32\x2c\x39\x2e\x38\
-\x2c\x31\x36\x2e\x37\x39\x2c\x38\x2e\x32\x36\x2c\x32\x37\x2e\x33\
-\x34\x2d\x35\x2e\x30\x31\x2c\x33\x34\x2e\x33\x2d\x32\x30\x2e\x37\
-\x32\x2c\x36\x32\x2e\x35\x36\x2d\x34\x37\x2e\x31\x34\x2c\x38\x34\
-\x2e\x39\x33\x2d\x2e\x35\x34\x2e\x34\x35\x2d\x31\x2e\x32\x33\x2e\
-\x37\x34\x2d\x31\x2e\x38\x38\x2c\x31\x2e\x30\x33\x2d\x2e\x31\x37\
-\x2e\x30\x38\x2d\x2e\x34\x36\x2d\x2e\x31\x33\x2d\x31\x2e\x30\x37\
-\x2d\x2e\x33\x31\x2c\x32\x2e\x30\x38\x2d\x31\x39\x2e\x38\x34\x2e\
-\x31\x31\x2d\x33\x39\x2e\x32\x39\x2d\x38\x2e\x32\x32\x2d\x35\x37\
-\x2e\x37\x32\x2d\x37\x2e\x33\x34\x2d\x31\x36\x2e\x32\x35\x2d\x31\
-\x38\x2e\x35\x33\x2d\x32\x39\x2e\x31\x32\x2d\x33\x34\x2e\x31\x35\
-\x2d\x33\x38\x2e\x30\x34\x2d\x33\x2e\x35\x31\x2d\x32\x2e\x30\x31\
-\x2d\x35\x2e\x35\x35\x2d\x31\x2e\x35\x35\x2d\x38\x2e\x39\x31\x2c\
-\x31\x2e\x31\x37\x2d\x38\x2e\x37\x39\x2c\x37\x2e\x31\x31\x2d\x39\
-\x2e\x37\x34\x2c\x31\x33\x2e\x32\x35\x2d\x35\x2e\x31\x38\x2c\x32\
-\x34\x2e\x35\x37\x2c\x31\x32\x2e\x39\x32\x2c\x33\x32\x2e\x31\x2c\
-\x32\x2e\x31\x33\x2c\x36\x30\x2e\x38\x35\x2d\x31\x37\x2e\x32\x36\
-\x2c\x38\x37\x2e\x34\x32\x2d\x37\x2e\x33\x39\x2c\x31\x30\x2e\x31\
-\x32\x2d\x31\x35\x2e\x36\x2c\x31\x34\x2e\x32\x31\x2d\x32\x38\x2e\
-\x38\x37\x2c\x31\x31\x2e\x38\x33\x2d\x33\x32\x2e\x35\x32\x2d\x35\
-\x2e\x38\x34\x2d\x35\x39\x2e\x36\x32\x2d\x32\x30\x2e\x34\x35\x2d\
-\x38\x31\x2e\x35\x33\x2d\x34\x34\x2e\x39\x38\x2d\x2e\x34\x36\x2d\
-\x2e\x35\x31\x2d\x2e\x36\x37\x2d\x31\x2e\x32\x35\x2d\x31\x2e\x34\
-\x34\x2d\x32\x2e\x37\x33\x2c\x31\x34\x2e\x35\x35\x2c\x31\x2e\x34\
-\x34\x2c\x32\x38\x2e\x33\x38\x2e\x32\x35\x2c\x34\x31\x2e\x38\x37\
-\x2d\x33\x2e\x35\x2c\x32\x32\x2e\x33\x35\x2d\x36\x2e\x32\x2c\x34\
-\x30\x2e\x32\x35\x2d\x31\x38\x2e\x34\x32\x2c\x35\x32\x2e\x34\x32\
-\x2d\x33\x38\x2e\x36\x34\x2c\x33\x2e\x30\x32\x2d\x35\x2e\x30\x31\
-\x2c\x32\x2e\x35\x36\x2d\x38\x2e\x32\x32\x2d\x31\x2e\x34\x39\x2d\
-\x31\x32\x2e\x31\x39\x2d\x39\x2e\x32\x31\x2d\x39\x2e\x30\x33\x2d\
-\x38\x2e\x39\x39\x2d\x39\x2e\x30\x35\x2d\x32\x31\x2e\x31\x33\x2d\
-\x34\x2e\x31\x39\x2d\x33\x33\x2e\x33\x31\x2c\x31\x33\x2e\x33\x32\
-\x2d\x36\x32\x2e\x35\x33\x2c\x34\x2e\x30\x34\x2d\x39\x30\x2e\x36\
-\x39\x2d\x31\x35\x2e\x32\x39\x2d\x31\x32\x2e\x39\x34\x2d\x38\x2e\
-\x38\x39\x2d\x31\x33\x2e\x30\x34\x2d\x31\x39\x2e\x36\x34\x2d\x31\
-\x30\x2e\x37\x35\x2d\x33\x32\x2e\x38\x35\x2c\x35\x2e\x37\x2d\x33\
-\x32\x2e\x38\x2c\x32\x31\x2e\x30\x31\x2d\x35\x39\x2e\x39\x2c\x34\
-\x36\x2e\x32\x36\x2d\x38\x31\x2e\x34\x38\x2e\x36\x36\x2d\x2e\x35\
-\x37\x2c\x31\x2e\x34\x34\x2d\x31\x2e\x30\x31\x2c\x32\x2e\x39\x2d\
-\x32\x2e\x30\x32\x2e\x37\x38\x2c\x31\x32\x2e\x34\x39\x2d\x2e\x37\
-\x31\x2c\x32\x34\x2e\x30\x38\x2c\x31\x2e\x35\x38\x2c\x33\x35\x2e\
-\x35\x35\x2c\x35\x2e\x31\x33\x2c\x32\x35\x2e\x37\x33\x2c\x31\x37\
-\x2e\x32\x39\x2c\x34\x36\x2e\x35\x38\x2c\x34\x30\x2e\x31\x32\x2c\
-\x36\x30\x2e\x35\x37\x2c\x34\x2e\x32\x33\x2c\x32\x2e\x35\x39\x2c\
-\x36\x2e\x39\x36\x2c\x32\x2e\x38\x34\x2c\x31\x30\x2e\x37\x32\x2d\
-\x31\x2e\x31\x2c\x39\x2e\x33\x38\x2d\x39\x2e\x38\x34\x2c\x39\x2e\
-\x33\x2d\x39\x2e\x35\x2c\x34\x2e\x36\x2d\x32\x32\x2e\x33\x39\x2d\
-\x31\x31\x2e\x38\x31\x2d\x33\x32\x2e\x34\x31\x2d\x33\x2e\x30\x34\
-\x2d\x36\x31\x2e\x30\x35\x2c\x31\x36\x2e\x32\x39\x2d\x38\x37\x2e\
-\x38\x36\x2c\x37\x2e\x37\x35\x2d\x31\x30\x2e\x37\x35\x2c\x31\x36\
-\x2e\x31\x33\x2d\x31\x35\x2e\x37\x31\x2c\x33\x30\x2e\x35\x35\x2d\
-\x31\x32\x2e\x39\x36\x2c\x33\x32\x2e\x31\x36\x2c\x36\x2e\x31\x35\
-\x2c\x35\x38\x2e\x39\x39\x2c\x32\x30\x2e\x36\x35\x2c\x38\x30\x2e\
-\x38\x32\x2c\x34\x34\x2e\x38\x2e\x34\x34\x2e\x34\x39\x2e\x35\x33\
-\x2c\x31\x2e\x32\x39\x2c\x31\x2e\x32\x32\x2c\x33\x2e\x30\x37\x5a\
-\x4d\x32\x32\x31\x2e\x36\x36\x2c\x32\x38\x37\x2e\x34\x39\x63\x33\
-\x2e\x36\x34\x2e\x31\x2c\x31\x36\x2e\x35\x35\x2d\x31\x32\x2e\x36\
-\x32\x2c\x31\x36\x2e\x37\x35\x2d\x31\x36\x2e\x35\x31\x2e\x32\x2d\
-\x33\x2e\x38\x39\x2d\x31\x32\x2e\x31\x33\x2d\x31\x36\x2e\x35\x38\
-\x2d\x31\x36\x2e\x33\x32\x2d\x31\x36\x2e\x38\x2d\x33\x2e\x35\x36\
-\x2d\x2e\x31\x39\x2d\x31\x36\x2e\x37\x31\x2c\x31\x32\x2e\x36\x34\
-\x2d\x31\x36\x2e\x38\x33\x2c\x31\x36\x2e\x34\x2d\x2e\x31\x31\x2c\
-\x33\x2e\x38\x33\x2c\x31\x32\x2e\x34\x36\x2c\x31\x36\x2e\x38\x2c\
-\x31\x36\x2e\x34\x2c\x31\x36\x2e\x39\x31\x5a\x22\x2f\x3e\x0a\x20\
-\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x6c\
-\x73\x2d\x31\x22\x20\x64\x3d\x22\x4d\x35\x38\x33\x2e\x39\x36\x2c\
-\x33\x33\x33\x2e\x31\x36\x63\x2d\x33\x2e\x36\x35\x2c\x32\x2e\x31\
-\x35\x2d\x37\x2e\x33\x31\x2c\x34\x2e\x32\x39\x2d\x31\x30\x2e\x39\
-\x35\x2c\x36\x2e\x34\x35\x2d\x31\x35\x2e\x33\x2c\x39\x2e\x31\x2d\
-\x33\x30\x2e\x35\x39\x2c\x31\x38\x2e\x32\x2d\x34\x35\x2e\x38\x38\
-\x2c\x32\x37\x2e\x33\x31\x2d\x2e\x31\x37\x2e\x31\x2d\x2e\x33\x34\
-\x2e\x31\x39\x2d\x2e\x36\x37\x2e\x33\x37\x2c\x30\x2d\x32\x2e\x33\
-\x34\x2d\x2e\x30\x35\x2d\x31\x39\x2e\x35\x35\x2d\x2e\x30\x35\x2d\
-\x31\x39\x2e\x35\x35\x68\x2d\x32\x33\x2e\x32\x36\x76\x2d\x32\x38\
-\x2e\x31\x35\x68\x32\x33\x2e\x32\x36\x73\x2d\x2e\x30\x32\x2d\x32\
-\x30\x2e\x35\x37\x2c\x30\x2d\x32\x30\x2e\x37\x32\x63\x2e\x30\x37\
-\x2c\x30\x2c\x31\x30\x2e\x32\x38\x2c\x35\x2e\x39\x37\x2c\x31\x34\
-\x2e\x39\x39\x2c\x38\x2e\x37\x37\x2c\x31\x34\x2e\x31\x39\x2c\x38\
-\x2e\x34\x34\x2c\x32\x38\x2e\x33\x38\x2c\x31\x36\x2e\x38\x38\x2c\
-\x34\x32\x2e\x35\x37\x2c\x32\x35\x2e\x33\x32\x2c\x30\x2c\x2e\x30\
-\x36\x2c\x30\x2c\x2e\x31\x32\x2c\x30\x2c\x2e\x31\x39\x5a\x22\x2f\
+\x6c\x73\x2d\x31\x22\x20\x64\x3d\x22\x4d\x34\x33\x30\x2e\x35\x35\
+\x2c\x33\x31\x34\x2e\x39\x32\x63\x2e\x33\x39\x2d\x39\x2e\x39\x34\
+\x2c\x34\x2e\x33\x33\x2d\x31\x34\x2e\x35\x36\x2c\x31\x32\x2e\x34\
+\x32\x2d\x31\x34\x2e\x30\x34\x2c\x38\x2e\x39\x39\x2e\x35\x38\x2c\
+\x31\x31\x2e\x30\x37\x2c\x36\x2e\x35\x2c\x31\x31\x2e\x30\x36\x2c\
+\x31\x34\x2e\x33\x38\x2d\x2e\x30\x39\x2c\x35\x35\x2e\x31\x38\x2d\
+\x2e\x30\x32\x2c\x31\x31\x30\x2e\x33\x36\x2d\x2e\x30\x39\x2c\x31\
+\x36\x35\x2e\x35\x33\x2d\x2e\x30\x31\x2c\x39\x2e\x32\x31\x2d\x34\
+\x2e\x31\x35\x2c\x31\x33\x2e\x38\x31\x2d\x31\x31\x2e\x38\x35\x2c\
+\x31\x33\x2e\x35\x35\x2d\x38\x2e\x33\x32\x2d\x2e\x32\x39\x2d\x31\
+\x32\x2e\x30\x37\x2d\x35\x2e\x34\x35\x2d\x31\x31\x2e\x33\x36\x2d\
+\x31\x33\x2e\x30\x34\x2e\x38\x38\x2d\x39\x2e\x33\x38\x2d\x32\x2e\
+\x38\x32\x2d\x31\x31\x2e\x33\x39\x2d\x31\x31\x2e\x37\x31\x2d\x31\
+\x31\x2e\x33\x32\x2d\x35\x33\x2e\x39\x31\x2e\x34\x2d\x31\x30\x37\
+\x2e\x38\x33\x2e\x33\x32\x2d\x31\x36\x31\x2e\x37\x35\x2e\x31\x31\
+\x2d\x33\x36\x2e\x34\x38\x2d\x2e\x31\x34\x2d\x36\x38\x2e\x31\x34\
+\x2d\x31\x33\x2e\x30\x34\x2d\x39\x35\x2e\x35\x2d\x33\x37\x2e\x30\
+\x35\x2d\x35\x2e\x38\x37\x2d\x35\x2e\x31\x35\x2d\x31\x32\x2e\x32\
+\x2d\x39\x2e\x38\x38\x2d\x31\x38\x2e\x37\x36\x2d\x31\x34\x2e\x31\
+\x33\x2d\x35\x37\x2e\x36\x39\x2d\x33\x37\x2e\x33\x2d\x38\x37\x2e\
+\x36\x32\x2d\x31\x30\x36\x2e\x35\x31\x2d\x37\x35\x2e\x31\x34\x2d\
+\x31\x37\x34\x2e\x32\x31\x2c\x31\x38\x2e\x32\x31\x2d\x39\x38\x2e\
+\x37\x38\x2c\x31\x31\x39\x2e\x32\x37\x2d\x31\x36\x30\x2e\x36\x34\
+\x2c\x32\x31\x35\x2e\x37\x38\x2d\x31\x33\x32\x2e\x30\x37\x2c\x36\
+\x38\x2e\x33\x39\x2c\x32\x30\x2e\x32\x34\x2c\x31\x31\x36\x2e\x39\
+\x2c\x38\x30\x2e\x31\x37\x2c\x31\x32\x30\x2e\x38\x38\x2c\x31\x35\
+\x31\x2e\x34\x34\x2c\x31\x2e\x31\x2c\x31\x39\x2e\x37\x37\x2d\x31\
+\x2e\x38\x34\x2c\x33\x39\x2e\x37\x36\x2d\x32\x2e\x39\x35\x2c\x36\
+\x30\x2e\x30\x39\x2c\x38\x2e\x30\x31\x2c\x30\x2c\x31\x37\x2e\x36\
+\x37\x2c\x30\x2c\x32\x38\x2e\x34\x39\x2c\x30\x2c\x2e\x32\x2d\x33\
+\x2e\x36\x39\x2e\x33\x37\x2d\x36\x2e\x34\x36\x2e\x34\x38\x2d\x39\
+\x2e\x32\x34\x5a\x4d\x32\x33\x36\x2e\x32\x38\x2c\x31\x32\x39\x2e\
+\x34\x36\x63\x2d\x38\x30\x2e\x38\x38\x2d\x2e\x33\x37\x2d\x31\x34\
+\x38\x2e\x30\x38\x2c\x36\x36\x2e\x38\x35\x2d\x31\x34\x37\x2e\x32\
+\x39\x2c\x31\x34\x37\x2e\x33\x32\x2e\x37\x39\x2c\x38\x30\x2e\x31\
+\x32\x2c\x36\x36\x2e\x33\x39\x2c\x31\x34\x35\x2e\x34\x2c\x31\x34\
+\x36\x2e\x31\x31\x2c\x31\x34\x35\x2e\x34\x31\x2c\x38\x31\x2e\x30\
+\x38\x2c\x30\x2c\x31\x34\x36\x2e\x36\x35\x2d\x36\x35\x2e\x35\x35\
+\x2c\x31\x34\x36\x2e\x36\x36\x2d\x31\x34\x36\x2e\x36\x33\x2c\x30\
+\x2d\x37\x39\x2e\x34\x35\x2d\x36\x36\x2d\x31\x34\x35\x2e\x37\x33\
+\x2d\x31\x34\x35\x2e\x34\x38\x2d\x31\x34\x36\x2e\x31\x5a\x22\x2f\
\x3e\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\
-\x22\x63\x6c\x73\x2d\x31\x22\x20\x64\x3d\x22\x4d\x35\x38\x33\x2e\
-\x39\x36\x2c\x34\x39\x36\x2e\x30\x35\x63\x2d\x33\x2e\x36\x35\x2c\
-\x32\x2e\x31\x35\x2d\x37\x2e\x33\x31\x2c\x34\x2e\x32\x39\x2d\x31\
-\x30\x2e\x39\x35\x2c\x36\x2e\x34\x35\x2d\x31\x35\x2e\x33\x2c\x39\
-\x2e\x31\x2d\x33\x30\x2e\x35\x39\x2c\x31\x38\x2e\x32\x2d\x34\x35\
-\x2e\x38\x38\x2c\x32\x37\x2e\x33\x31\x2d\x2e\x31\x37\x2e\x31\x2d\
-\x2e\x33\x34\x2e\x31\x39\x2d\x2e\x36\x37\x2e\x33\x37\x2c\x30\x2d\
-\x32\x2e\x33\x34\x2d\x2e\x30\x35\x2d\x31\x39\x2e\x35\x35\x2d\x2e\
-\x30\x35\x2d\x31\x39\x2e\x35\x35\x68\x2d\x32\x33\x2e\x32\x36\x76\
-\x2d\x32\x38\x2e\x31\x35\x68\x32\x33\x2e\x32\x36\x73\x2d\x2e\x30\
-\x32\x2d\x32\x30\x2e\x35\x37\x2c\x30\x2d\x32\x30\x2e\x37\x32\x63\
-\x2e\x30\x37\x2c\x30\x2c\x31\x30\x2e\x32\x38\x2c\x35\x2e\x39\x37\
-\x2c\x31\x34\x2e\x39\x39\x2c\x38\x2e\x37\x37\x2c\x31\x34\x2e\x31\
-\x39\x2c\x38\x2e\x34\x34\x2c\x32\x38\x2e\x33\x38\x2c\x31\x36\x2e\
-\x38\x38\x2c\x34\x32\x2e\x35\x37\x2c\x32\x35\x2e\x33\x32\x2c\x30\
-\x2c\x2e\x30\x36\x2c\x30\x2c\x2e\x31\x32\x2c\x30\x2c\x2e\x31\x39\
-\x5a\x22\x2f\x3e\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\
-\x73\x73\x3d\x22\x63\x6c\x73\x2d\x31\x22\x20\x64\x3d\x22\x4d\x35\
-\x38\x33\x2e\x39\x36\x2c\x34\x31\x34\x2e\x36\x31\x63\x2d\x33\x2e\
-\x36\x35\x2c\x32\x2e\x31\x35\x2d\x37\x2e\x33\x31\x2c\x34\x2e\x32\
-\x39\x2d\x31\x30\x2e\x39\x35\x2c\x36\x2e\x34\x35\x2d\x31\x35\x2e\
-\x33\x2c\x39\x2e\x31\x2d\x33\x30\x2e\x35\x39\x2c\x31\x38\x2e\x32\
-\x2d\x34\x35\x2e\x38\x38\x2c\x32\x37\x2e\x33\x31\x2d\x2e\x31\x37\
-\x2e\x31\x2d\x2e\x33\x34\x2e\x31\x39\x2d\x2e\x36\x37\x2e\x33\x37\
-\x2c\x30\x2d\x32\x2e\x33\x34\x2d\x2e\x30\x35\x2d\x31\x39\x2e\x35\
-\x35\x2d\x2e\x30\x35\x2d\x31\x39\x2e\x35\x35\x68\x2d\x32\x33\x2e\
-\x32\x36\x76\x2d\x32\x38\x2e\x31\x35\x68\x32\x33\x2e\x32\x36\x73\
-\x2d\x2e\x30\x32\x2d\x32\x30\x2e\x35\x37\x2c\x30\x2d\x32\x30\x2e\
-\x37\x32\x63\x2e\x30\x37\x2c\x30\x2c\x31\x30\x2e\x32\x38\x2c\x35\
-\x2e\x39\x37\x2c\x31\x34\x2e\x39\x39\x2c\x38\x2e\x37\x37\x2c\x31\
-\x34\x2e\x31\x39\x2c\x38\x2e\x34\x34\x2c\x32\x38\x2e\x33\x38\x2c\
-\x31\x36\x2e\x38\x38\x2c\x34\x32\x2e\x35\x37\x2c\x32\x35\x2e\x33\
-\x32\x2c\x30\x2c\x2e\x30\x36\x2c\x30\x2c\x2e\x31\x32\x2c\x30\x2c\
-\x2e\x31\x39\x5a\x22\x2f\x3e\x0a\x3c\x2f\x73\x76\x67\x3e\
+\x22\x63\x6c\x73\x2d\x31\x22\x20\x64\x3d\x22\x4d\x33\x32\x30\x2e\
+\x31\x37\x2c\x32\x30\x35\x2e\x34\x63\x2d\x31\x37\x2e\x31\x32\x2d\
+\x31\x2e\x37\x33\x2d\x33\x32\x2e\x38\x39\x2e\x36\x36\x2d\x34\x37\
+\x2e\x38\x36\x2c\x37\x2e\x36\x37\x2d\x31\x32\x2e\x37\x38\x2c\x35\
+\x2e\x39\x39\x2d\x32\x32\x2e\x38\x38\x2c\x31\x34\x2e\x39\x2d\x33\
+\x30\x2e\x30\x38\x2c\x32\x37\x2e\x31\x36\x2d\x32\x2e\x33\x31\x2c\
+\x33\x2e\x39\x34\x2d\x32\x2e\x33\x34\x2c\x36\x2e\x36\x33\x2c\x31\
+\x2e\x32\x38\x2c\x39\x2e\x39\x39\x2c\x37\x2e\x37\x33\x2c\x37\x2e\
+\x31\x38\x2c\x37\x2e\x34\x35\x2c\x37\x2e\x32\x32\x2c\x31\x36\x2e\
+\x39\x31\x2c\x33\x2e\x35\x2c\x32\x39\x2d\x31\x31\x2e\x33\x39\x2c\
+\x35\x34\x2e\x31\x39\x2d\x33\x2e\x33\x32\x2c\x37\x38\x2e\x30\x32\
+\x2c\x31\x34\x2e\x35\x34\x2c\x38\x2e\x36\x34\x2c\x36\x2e\x34\x37\
+\x2c\x38\x2e\x31\x31\x2c\x31\x33\x2e\x38\x39\x2c\x36\x2e\x38\x34\
+\x2c\x32\x32\x2e\x36\x32\x2d\x34\x2e\x31\x34\x2c\x32\x38\x2e\x33\
+\x38\x2d\x31\x37\x2e\x31\x34\x2c\x35\x31\x2e\x37\x36\x2d\x33\x39\
+\x2c\x37\x30\x2e\x32\x36\x2d\x2e\x34\x34\x2e\x33\x38\x2d\x31\x2e\
+\x30\x32\x2e\x36\x31\x2d\x31\x2e\x35\x35\x2e\x38\x35\x2d\x2e\x31\
+\x34\x2e\x30\x36\x2d\x2e\x33\x38\x2d\x2e\x31\x2d\x2e\x38\x39\x2d\
+\x2e\x32\x36\x2c\x31\x2e\x37\x32\x2d\x31\x36\x2e\x34\x32\x2e\x30\
+\x39\x2d\x33\x32\x2e\x35\x31\x2d\x36\x2e\x38\x2d\x34\x37\x2e\x37\
+\x36\x2d\x36\x2e\x30\x37\x2d\x31\x33\x2e\x34\x35\x2d\x31\x35\x2e\
+\x33\x33\x2d\x32\x34\x2e\x31\x2d\x32\x38\x2e\x32\x35\x2d\x33\x31\
+\x2e\x34\x37\x2d\x32\x2e\x39\x31\x2d\x31\x2e\x36\x36\x2d\x34\x2e\
+\x35\x39\x2d\x31\x2e\x32\x38\x2d\x37\x2e\x33\x38\x2e\x39\x37\x2d\
+\x37\x2e\x32\x38\x2c\x35\x2e\x38\x39\x2d\x38\x2e\x30\x36\x2c\x31\
+\x30\x2e\x39\x36\x2d\x34\x2e\x32\x39\x2c\x32\x30\x2e\x33\x33\x2c\
+\x31\x30\x2e\x36\x39\x2c\x32\x36\x2e\x35\x36\x2c\x31\x2e\x37\x37\
+\x2c\x35\x30\x2e\x33\x34\x2d\x31\x34\x2e\x32\x38\x2c\x37\x32\x2e\
+\x33\x32\x2d\x36\x2e\x31\x31\x2c\x38\x2e\x33\x37\x2d\x31\x32\x2e\
+\x39\x2c\x31\x31\x2e\x37\x36\x2d\x32\x33\x2e\x38\x39\x2c\x39\x2e\
+\x37\x39\x2d\x32\x36\x2e\x39\x31\x2d\x34\x2e\x38\x33\x2d\x34\x39\
+\x2e\x33\x32\x2d\x31\x36\x2e\x39\x32\x2d\x36\x37\x2e\x34\x35\x2d\
+\x33\x37\x2e\x32\x31\x2d\x2e\x33\x38\x2d\x2e\x34\x32\x2d\x2e\x35\
+\x36\x2d\x31\x2e\x30\x33\x2d\x31\x2e\x31\x39\x2d\x32\x2e\x32\x36\
+\x2c\x31\x32\x2e\x30\x34\x2c\x31\x2e\x31\x39\x2c\x32\x33\x2e\x34\
+\x38\x2e\x32\x2c\x33\x34\x2e\x36\x34\x2d\x32\x2e\x38\x39\x2c\x31\
+\x38\x2e\x34\x39\x2d\x35\x2e\x31\x33\x2c\x33\x33\x2e\x33\x2d\x31\
+\x35\x2e\x32\x34\x2c\x34\x33\x2e\x33\x37\x2d\x33\x31\x2e\x39\x37\
+\x2c\x32\x2e\x35\x2d\x34\x2e\x31\x35\x2c\x32\x2e\x31\x32\x2d\x36\
+\x2e\x38\x2d\x31\x2e\x32\x34\x2d\x31\x30\x2e\x30\x39\x2d\x37\x2e\
+\x36\x32\x2d\x37\x2e\x34\x37\x2d\x37\x2e\x34\x34\x2d\x37\x2e\x34\
+\x39\x2d\x31\x37\x2e\x34\x38\x2d\x33\x2e\x34\x37\x2d\x32\x37\x2e\
+\x35\x36\x2c\x31\x31\x2e\x30\x32\x2d\x35\x31\x2e\x37\x34\x2c\x33\
+\x2e\x33\x34\x2d\x37\x35\x2e\x30\x33\x2d\x31\x32\x2e\x36\x35\x2d\
+\x31\x30\x2e\x37\x31\x2d\x37\x2e\x33\x35\x2d\x31\x30\x2e\x37\x39\
+\x2d\x31\x36\x2e\x32\x35\x2d\x38\x2e\x38\x39\x2d\x32\x37\x2e\x31\
+\x38\x2c\x34\x2e\x37\x32\x2d\x32\x37\x2e\x31\x34\x2c\x31\x37\x2e\
+\x33\x38\x2d\x34\x39\x2e\x35\x36\x2c\x33\x38\x2e\x32\x38\x2d\x36\
+\x37\x2e\x34\x31\x2e\x35\x35\x2d\x2e\x34\x37\x2c\x31\x2e\x31\x39\
+\x2d\x2e\x38\x33\x2c\x32\x2e\x34\x2d\x31\x2e\x36\x37\x2e\x36\x34\
+\x2c\x31\x30\x2e\x33\x33\x2d\x2e\x35\x38\x2c\x31\x39\x2e\x39\x32\
+\x2c\x31\x2e\x33\x31\x2c\x32\x39\x2e\x34\x31\x2c\x34\x2e\x32\x34\
+\x2c\x32\x31\x2e\x32\x39\x2c\x31\x34\x2e\x33\x31\x2c\x33\x38\x2e\
+\x35\x34\x2c\x33\x33\x2e\x31\x39\x2c\x35\x30\x2e\x31\x32\x2c\x33\
+\x2e\x35\x2c\x32\x2e\x31\x34\x2c\x35\x2e\x37\x36\x2c\x32\x2e\x33\
+\x35\x2c\x38\x2e\x38\x37\x2d\x2e\x39\x31\x2c\x37\x2e\x37\x36\x2d\
+\x38\x2e\x31\x34\x2c\x37\x2e\x36\x39\x2d\x37\x2e\x38\x36\x2c\x33\
+\x2e\x38\x2d\x31\x38\x2e\x35\x32\x2d\x39\x2e\x37\x37\x2d\x32\x36\
+\x2e\x38\x31\x2d\x32\x2e\x35\x31\x2d\x35\x30\x2e\x35\x31\x2c\x31\
+\x33\x2e\x34\x38\x2d\x37\x32\x2e\x36\x39\x2c\x36\x2e\x34\x31\x2d\
+\x38\x2e\x38\x39\x2c\x31\x33\x2e\x33\x35\x2d\x31\x33\x2c\x32\x35\
+\x2e\x32\x38\x2d\x31\x30\x2e\x37\x32\x2c\x32\x36\x2e\x36\x31\x2c\
+\x35\x2e\x30\x39\x2c\x34\x38\x2e\x38\x2c\x31\x37\x2e\x30\x39\x2c\
+\x36\x36\x2e\x38\x37\x2c\x33\x37\x2e\x30\x36\x2e\x33\x37\x2e\x34\
+\x2e\x34\x34\x2c\x31\x2e\x30\x37\x2c\x31\x2e\x30\x31\x2c\x32\x2e\
+\x35\x34\x5a\x4d\x32\x33\x35\x2e\x31\x39\x2c\x32\x38\x39\x2e\x36\
+\x35\x63\x33\x2e\x30\x31\x2e\x30\x39\x2c\x31\x33\x2e\x36\x39\x2d\
+\x31\x30\x2e\x34\x34\x2c\x31\x33\x2e\x38\x36\x2d\x31\x33\x2e\x36\
+\x36\x2e\x31\x37\x2d\x33\x2e\x32\x31\x2d\x31\x30\x2e\x30\x33\x2d\
+\x31\x33\x2e\x37\x32\x2d\x31\x33\x2e\x35\x31\x2d\x31\x33\x2e\x39\
+\x2d\x32\x2e\x39\x34\x2d\x2e\x31\x36\x2d\x31\x33\x2e\x38\x33\x2c\
+\x31\x30\x2e\x34\x36\x2d\x31\x33\x2e\x39\x32\x2c\x31\x33\x2e\x35\
+\x37\x2d\x2e\x30\x39\x2c\x33\x2e\x31\x37\x2c\x31\x30\x2e\x33\x31\
+\x2c\x31\x33\x2e\x39\x2c\x31\x33\x2e\x35\x37\x2c\x31\x33\x2e\x39\
+\x39\x5a\x22\x2f\x3e\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\
+\x61\x73\x73\x3d\x22\x63\x6c\x73\x2d\x31\x22\x20\x64\x3d\x22\x4d\
+\x35\x33\x34\x2e\x39\x34\x2c\x33\x32\x37\x2e\x34\x34\x63\x2d\x33\
+\x2e\x30\x32\x2c\x31\x2e\x37\x38\x2d\x36\x2e\x30\x35\x2c\x33\x2e\
+\x35\x35\x2d\x39\x2e\x30\x36\x2c\x35\x2e\x33\x34\x2d\x31\x32\x2e\
+\x36\x36\x2c\x37\x2e\x35\x33\x2d\x32\x35\x2e\x33\x31\x2c\x31\x35\
+\x2e\x30\x36\x2d\x33\x37\x2e\x39\x36\x2c\x32\x32\x2e\x35\x39\x2d\
+\x2e\x31\x34\x2e\x30\x38\x2d\x2e\x32\x38\x2e\x31\x36\x2d\x2e\x35\
+\x36\x2e\x33\x31\x2c\x30\x2d\x31\x2e\x39\x33\x2d\x2e\x30\x34\x2d\
+\x31\x36\x2e\x31\x37\x2d\x2e\x30\x34\x2d\x31\x36\x2e\x31\x37\x68\
+\x2d\x31\x39\x2e\x32\x35\x76\x2d\x32\x33\x2e\x32\x39\x68\x31\x39\
+\x2e\x32\x35\x73\x2d\x2e\x30\x31\x2d\x31\x37\x2e\x30\x32\x2c\x30\
+\x2d\x31\x37\x2e\x31\x35\x63\x2e\x30\x36\x2c\x30\x2c\x38\x2e\x35\
+\x31\x2c\x34\x2e\x39\x34\x2c\x31\x32\x2e\x34\x2c\x37\x2e\x32\x36\
+\x2c\x31\x31\x2e\x37\x34\x2c\x36\x2e\x39\x38\x2c\x32\x33\x2e\x34\
+\x38\x2c\x31\x33\x2e\x39\x37\x2c\x33\x35\x2e\x32\x32\x2c\x32\x30\
+\x2e\x39\x35\x2c\x30\x2c\x2e\x30\x35\x2c\x30\x2c\x2e\x31\x2c\x30\
+\x2c\x2e\x31\x35\x5a\x22\x2f\x3e\x0a\x20\x20\x3c\x70\x61\x74\x68\
+\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x6c\x73\x2d\x31\x22\x20\x64\
+\x3d\x22\x4d\x35\x33\x34\x2e\x39\x34\x2c\x34\x36\x32\x2e\x32\x31\
+\x63\x2d\x33\x2e\x30\x32\x2c\x31\x2e\x37\x38\x2d\x36\x2e\x30\x35\
+\x2c\x33\x2e\x35\x35\x2d\x39\x2e\x30\x36\x2c\x35\x2e\x33\x34\x2d\
+\x31\x32\x2e\x36\x36\x2c\x37\x2e\x35\x33\x2d\x32\x35\x2e\x33\x31\
+\x2c\x31\x35\x2e\x30\x36\x2d\x33\x37\x2e\x39\x36\x2c\x32\x32\x2e\
+\x35\x39\x2d\x2e\x31\x34\x2e\x30\x38\x2d\x2e\x32\x38\x2e\x31\x36\
+\x2d\x2e\x35\x36\x2e\x33\x31\x2c\x30\x2d\x31\x2e\x39\x33\x2d\x2e\
+\x30\x34\x2d\x31\x36\x2e\x31\x37\x2d\x2e\x30\x34\x2d\x31\x36\x2e\
+\x31\x37\x68\x2d\x31\x39\x2e\x32\x35\x76\x2d\x32\x33\x2e\x32\x39\
+\x68\x31\x39\x2e\x32\x35\x73\x2d\x2e\x30\x31\x2d\x31\x37\x2e\x30\
+\x32\x2c\x30\x2d\x31\x37\x2e\x31\x35\x63\x2e\x30\x36\x2c\x30\x2c\
+\x38\x2e\x35\x31\x2c\x34\x2e\x39\x34\x2c\x31\x32\x2e\x34\x2c\x37\
+\x2e\x32\x36\x2c\x31\x31\x2e\x37\x34\x2c\x36\x2e\x39\x38\x2c\x32\
+\x33\x2e\x34\x38\x2c\x31\x33\x2e\x39\x37\x2c\x33\x35\x2e\x32\x32\
+\x2c\x32\x30\x2e\x39\x35\x2c\x30\x2c\x2e\x30\x35\x2c\x30\x2c\x2e\
+\x31\x2c\x30\x2c\x2e\x31\x35\x5a\x22\x2f\x3e\x0a\x20\x20\x3c\x70\
+\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x6c\x73\x2d\x31\
+\x22\x20\x64\x3d\x22\x4d\x35\x33\x34\x2e\x39\x34\x2c\x33\x39\x34\
+\x2e\x38\x32\x63\x2d\x33\x2e\x30\x32\x2c\x31\x2e\x37\x38\x2d\x36\
+\x2e\x30\x35\x2c\x33\x2e\x35\x35\x2d\x39\x2e\x30\x36\x2c\x35\x2e\
+\x33\x34\x2d\x31\x32\x2e\x36\x36\x2c\x37\x2e\x35\x33\x2d\x32\x35\
+\x2e\x33\x31\x2c\x31\x35\x2e\x30\x36\x2d\x33\x37\x2e\x39\x36\x2c\
+\x32\x32\x2e\x35\x39\x2d\x2e\x31\x34\x2e\x30\x38\x2d\x2e\x32\x38\
+\x2e\x31\x36\x2d\x2e\x35\x36\x2e\x33\x31\x2c\x30\x2d\x31\x2e\x39\
+\x33\x2d\x2e\x30\x34\x2d\x31\x36\x2e\x31\x37\x2d\x2e\x30\x34\x2d\
+\x31\x36\x2e\x31\x37\x68\x2d\x31\x39\x2e\x32\x35\x76\x2d\x32\x33\
+\x2e\x32\x39\x68\x31\x39\x2e\x32\x35\x73\x2d\x2e\x30\x31\x2d\x31\
+\x37\x2e\x30\x32\x2c\x30\x2d\x31\x37\x2e\x31\x35\x63\x2e\x30\x36\
+\x2c\x30\x2c\x38\x2e\x35\x31\x2c\x34\x2e\x39\x34\x2c\x31\x32\x2e\
+\x34\x2c\x37\x2e\x32\x36\x2c\x31\x31\x2e\x37\x34\x2c\x36\x2e\x39\
+\x38\x2c\x32\x33\x2e\x34\x38\x2c\x31\x33\x2e\x39\x37\x2c\x33\x35\
+\x2e\x32\x32\x2c\x32\x30\x2e\x39\x35\x2c\x30\x2c\x2e\x30\x35\x2c\
+\x30\x2c\x2e\x31\x2c\x30\x2c\x2e\x31\x35\x5a\x22\x2f\x3e\x0a\x3c\
+\x2f\x73\x76\x67\x3e\
\x00\x00\x04\xf7\
\x3c\
\x73\x76\x67\x20\x69\x64\x3d\x22\x4c\x61\x79\x65\x72\x5f\x31\x22\
@@ -22506,7 +22518,7 @@
\x31\x2e\x36\x20\x34\x34\x36\x2e\x36\x32\x20\x34\x33\x36\x2e\x32\
\x36\x20\x34\x34\x38\x2e\x37\x33\x20\x34\x33\x38\x2e\x34\x20\x35\
\x32\x32\x2e\x36\x35\x22\x2f\x3e\x3c\x2f\x73\x76\x67\x3e\
-\x00\x00\x05\xdd\
+\x00\x00\x05\xcf\
\x3c\
\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\
@@ -22523,86 +22535,85 @@
\x0a\x20\x20\x20\x20\x20\x20\x7d\x0a\x20\x20\x20\x20\x3c\x2f\x73\
\x74\x79\x6c\x65\x3e\x0a\x20\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0a\
\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\
-\x6c\x73\x2d\x31\x22\x20\x64\x3d\x22\x4d\x34\x38\x30\x2e\x30\x34\
-\x2c\x31\x35\x30\x2e\x34\x37\x63\x2d\x33\x36\x2e\x33\x35\x2d\x33\
-\x2e\x36\x37\x2d\x36\x39\x2e\x38\x34\x2c\x31\x2e\x34\x2d\x31\x30\
-\x31\x2e\x36\x32\x2c\x31\x36\x2e\x32\x39\x2d\x32\x37\x2e\x31\x33\
-\x2c\x31\x32\x2e\x37\x31\x2d\x34\x38\x2e\x35\x38\x2c\x33\x31\x2e\
-\x36\x34\x2d\x36\x33\x2e\x38\x36\x2c\x35\x37\x2e\x36\x36\x2d\x34\
-\x2e\x39\x31\x2c\x38\x2e\x33\x36\x2d\x34\x2e\x39\x36\x2c\x31\x34\
-\x2e\x30\x38\x2c\x32\x2e\x37\x32\x2c\x32\x31\x2e\x32\x32\x2c\x31\
-\x36\x2e\x34\x31\x2c\x31\x35\x2e\x32\x35\x2c\x31\x35\x2e\x38\x31\
-\x2c\x31\x35\x2e\x33\x32\x2c\x33\x35\x2e\x39\x2c\x37\x2e\x34\x33\
-\x2c\x36\x31\x2e\x35\x38\x2d\x32\x34\x2e\x31\x39\x2c\x31\x31\x35\
-\x2e\x30\x35\x2d\x37\x2e\x30\x34\x2c\x31\x36\x35\x2e\x36\x35\x2c\
-\x33\x30\x2e\x38\x38\x2c\x31\x38\x2e\x33\x34\x2c\x31\x33\x2e\x37\
-\x34\x2c\x31\x37\x2e\x32\x32\x2c\x32\x39\x2e\x34\x39\x2c\x31\x34\
-\x2e\x35\x31\x2c\x34\x38\x2e\x30\x33\x2d\x38\x2e\x38\x2c\x36\x30\
-\x2e\x32\x35\x2d\x33\x36\x2e\x34\x2c\x31\x30\x39\x2e\x39\x2d\x38\
-\x32\x2e\x38\x2c\x31\x34\x39\x2e\x31\x38\x2d\x2e\x39\x34\x2e\x38\
-\x2d\x32\x2e\x31\x36\x2c\x31\x2e\x33\x2d\x33\x2e\x33\x2c\x31\x2e\
-\x38\x31\x2d\x2e\x33\x2e\x31\x33\x2d\x2e\x38\x31\x2d\x2e\x32\x32\
-\x2d\x31\x2e\x38\x38\x2d\x2e\x35\x35\x2c\x33\x2e\x36\x35\x2d\x33\
-\x34\x2e\x38\x36\x2e\x31\x39\x2d\x36\x39\x2e\x30\x31\x2d\x31\x34\
-\x2e\x34\x33\x2d\x31\x30\x31\x2e\x34\x2d\x31\x32\x2e\x39\x2d\x32\
-\x38\x2e\x35\x35\x2d\x33\x32\x2e\x35\x36\x2d\x35\x31\x2e\x31\x36\
-\x2d\x35\x39\x2e\x39\x39\x2d\x36\x36\x2e\x38\x33\x2d\x36\x2e\x31\
-\x37\x2d\x33\x2e\x35\x32\x2d\x39\x2e\x37\x35\x2d\x32\x2e\x37\x32\
-\x2d\x31\x35\x2e\x36\x36\x2c\x32\x2e\x30\x35\x2d\x31\x35\x2e\x34\
-\x35\x2c\x31\x32\x2e\x35\x2d\x31\x37\x2e\x31\x2c\x32\x33\x2e\x32\
-\x37\x2d\x39\x2e\x31\x2c\x34\x33\x2e\x31\x37\x2c\x32\x32\x2e\x36\
-\x39\x2c\x35\x36\x2e\x33\x39\x2c\x33\x2e\x37\x35\x2c\x31\x30\x36\
-\x2e\x38\x39\x2d\x33\x30\x2e\x33\x32\x2c\x31\x35\x33\x2e\x35\x36\
-\x2d\x31\x32\x2e\x39\x38\x2c\x31\x37\x2e\x37\x38\x2d\x32\x37\x2e\
-\x34\x2c\x32\x34\x2e\x39\x37\x2d\x35\x30\x2e\x37\x32\x2c\x32\x30\
-\x2e\x37\x38\x2d\x35\x37\x2e\x31\x33\x2d\x31\x30\x2e\x32\x36\x2d\
-\x31\x30\x34\x2e\x37\x32\x2d\x33\x35\x2e\x39\x31\x2d\x31\x34\x33\
-\x2e\x32\x31\x2d\x37\x39\x2e\x30\x31\x2d\x2e\x38\x31\x2d\x2e\x39\
-\x2d\x31\x2e\x31\x38\x2d\x32\x2e\x31\x39\x2d\x32\x2e\x35\x33\x2d\
-\x34\x2e\x37\x39\x2c\x32\x35\x2e\x35\x37\x2c\x32\x2e\x35\x34\x2c\
-\x34\x39\x2e\x38\x35\x2e\x34\x33\x2c\x37\x33\x2e\x35\x35\x2d\x36\
-\x2e\x31\x34\x2c\x33\x39\x2e\x32\x35\x2d\x31\x30\x2e\x38\x39\x2c\
-\x37\x30\x2e\x37\x2d\x33\x32\x2e\x33\x36\x2c\x39\x32\x2e\x30\x39\
-\x2d\x36\x37\x2e\x38\x38\x2c\x35\x2e\x33\x2d\x38\x2e\x38\x2c\x34\
-\x2e\x34\x39\x2d\x31\x34\x2e\x34\x34\x2d\x32\x2e\x36\x32\x2d\x32\
-\x31\x2e\x34\x32\x2d\x31\x36\x2e\x31\x37\x2d\x31\x35\x2e\x38\x37\
-\x2d\x31\x35\x2e\x37\x39\x2d\x31\x35\x2e\x38\x39\x2d\x33\x37\x2e\
-\x31\x32\x2d\x37\x2e\x33\x37\x2d\x35\x38\x2e\x35\x32\x2c\x32\x33\
-\x2e\x34\x2d\x31\x30\x39\x2e\x38\x34\x2c\x37\x2e\x31\x2d\x31\x35\
-\x39\x2e\x33\x2d\x32\x36\x2e\x38\x36\x2d\x32\x32\x2e\x37\x33\x2d\
-\x31\x35\x2e\x36\x31\x2d\x32\x32\x2e\x39\x31\x2d\x33\x34\x2e\x35\
-\x2d\x31\x38\x2e\x38\x38\x2d\x35\x37\x2e\x37\x31\x2c\x31\x30\x2e\
-\x30\x31\x2d\x35\x37\x2e\x36\x32\x2c\x33\x36\x2e\x39\x31\x2d\x31\
-\x30\x35\x2e\x32\x32\x2c\x38\x31\x2e\x32\x37\x2d\x31\x34\x33\x2e\
-\x31\x32\x2c\x31\x2e\x31\x37\x2d\x31\x2c\x32\x2e\x35\x33\x2d\x31\
-\x2e\x37\x37\x2c\x35\x2e\x31\x2d\x33\x2e\x35\x35\x2c\x31\x2e\x33\
-\x36\x2c\x32\x31\x2e\x39\x33\x2d\x31\x2e\x32\x34\x2c\x34\x32\x2e\
-\x33\x2c\x32\x2e\x37\x37\x2c\x36\x32\x2e\x34\x35\x2c\x39\x2e\x30\
-\x31\x2c\x34\x35\x2e\x31\x39\x2c\x33\x30\x2e\x33\x37\x2c\x38\x31\
-\x2e\x38\x32\x2c\x37\x30\x2e\x34\x37\x2c\x31\x30\x36\x2e\x34\x31\
-\x2c\x37\x2e\x34\x32\x2c\x34\x2e\x35\x35\x2c\x31\x32\x2e\x32\x32\
-\x2c\x35\x2c\x31\x38\x2e\x38\x33\x2d\x31\x2e\x39\x34\x2c\x31\x36\
-\x2e\x34\x38\x2d\x31\x37\x2e\x32\x38\x2c\x31\x36\x2e\x33\x33\x2d\
-\x31\x36\x2e\x36\x38\x2c\x38\x2e\x30\x38\x2d\x33\x39\x2e\x33\x33\
-\x2d\x32\x30\x2e\x37\x35\x2d\x35\x36\x2e\x39\x33\x2d\x35\x2e\x33\
-\x33\x2d\x31\x30\x37\x2e\x32\x35\x2c\x32\x38\x2e\x36\x32\x2d\x31\
-\x35\x34\x2e\x33\x34\x2c\x31\x33\x2e\x36\x31\x2d\x31\x38\x2e\x38\
-\x38\x2c\x32\x38\x2e\x33\x34\x2d\x32\x37\x2e\x36\x31\x2c\x35\x33\
-\x2e\x36\x37\x2d\x32\x32\x2e\x37\x36\x2c\x35\x36\x2e\x34\x39\x2c\
-\x31\x30\x2e\x38\x31\x2c\x31\x30\x33\x2e\x36\x32\x2c\x33\x36\x2e\
-\x32\x38\x2c\x31\x34\x31\x2e\x39\x37\x2c\x37\x38\x2e\x36\x39\x2e\
-\x37\x38\x2e\x38\x36\x2e\x39\x33\x2c\x32\x2e\x32\x37\x2c\x32\x2e\
-\x31\x34\x2c\x35\x2e\x33\x39\x5a\x4d\x32\x39\x39\x2e\x36\x2c\x33\
-\x32\x39\x2e\x33\x35\x63\x36\x2e\x33\x39\x2e\x31\x38\x2c\x32\x39\
-\x2e\x30\x37\x2d\x32\x32\x2e\x31\x37\x2c\x32\x39\x2e\x34\x33\x2d\
-\x32\x39\x2c\x2e\x33\x36\x2d\x36\x2e\x38\x33\x2d\x32\x31\x2e\x33\
-\x2d\x32\x39\x2e\x31\x33\x2d\x32\x38\x2e\x36\x37\x2d\x32\x39\x2e\
-\x35\x32\x2d\x36\x2e\x32\x35\x2d\x2e\x33\x33\x2d\x32\x39\x2e\x33\
-\x36\x2c\x32\x32\x2e\x32\x2d\x32\x39\x2e\x35\x36\x2c\x32\x38\x2e\
-\x38\x32\x2d\x2e\x32\x2c\x36\x2e\x37\x33\x2c\x32\x31\x2e\x38\x38\
-\x2c\x32\x39\x2e\x35\x31\x2c\x32\x38\x2e\x38\x2c\x32\x39\x2e\x37\
-\x31\x5a\x22\x2f\x3e\x0a\x3c\x2f\x73\x76\x67\x3e\
-\x00\x00\x0b\x8f\
+\x6c\x73\x2d\x31\x22\x20\x64\x3d\x22\x4d\x34\x32\x39\x2e\x30\x34\
+\x2c\x31\x39\x32\x2e\x38\x33\x63\x2d\x32\x36\x2e\x30\x35\x2d\x32\
+\x2e\x36\x33\x2d\x35\x30\x2e\x30\x35\x2c\x31\x2e\x30\x31\x2d\x37\
+\x32\x2e\x38\x33\x2c\x31\x31\x2e\x36\x38\x2d\x31\x39\x2e\x34\x35\
+\x2c\x39\x2e\x31\x31\x2d\x33\x34\x2e\x38\x32\x2c\x32\x32\x2e\x36\
+\x38\x2d\x34\x35\x2e\x37\x37\x2c\x34\x31\x2e\x33\x32\x2d\x33\x2e\
+\x35\x32\x2c\x35\x2e\x39\x39\x2d\x33\x2e\x35\x36\x2c\x31\x30\x2e\
+\x30\x39\x2c\x31\x2e\x39\x35\x2c\x31\x35\x2e\x32\x31\x2c\x31\x31\
+\x2e\x37\x36\x2c\x31\x30\x2e\x39\x33\x2c\x31\x31\x2e\x33\x33\x2c\
+\x31\x30\x2e\x39\x38\x2c\x32\x35\x2e\x37\x33\x2c\x35\x2e\x33\x32\
+\x2c\x34\x34\x2e\x31\x33\x2d\x31\x37\x2e\x33\x34\x2c\x38\x32\x2e\
+\x34\x36\x2d\x35\x2e\x30\x35\x2c\x31\x31\x38\x2e\x37\x32\x2c\x32\
+\x32\x2e\x31\x33\x2c\x31\x33\x2e\x31\x34\x2c\x39\x2e\x38\x35\x2c\
+\x31\x32\x2e\x33\x34\x2c\x32\x31\x2e\x31\x33\x2c\x31\x30\x2e\x34\
+\x2c\x33\x34\x2e\x34\x32\x2d\x36\x2e\x33\x31\x2c\x34\x33\x2e\x31\
+\x38\x2d\x32\x36\x2e\x30\x39\x2c\x37\x38\x2e\x37\x37\x2d\x35\x39\
+\x2e\x33\x34\x2c\x31\x30\x36\x2e\x39\x32\x2d\x2e\x36\x37\x2e\x35\
+\x37\x2d\x31\x2e\x35\x34\x2e\x39\x33\x2d\x32\x2e\x33\x36\x2c\x31\
+\x2e\x33\x2d\x2e\x32\x31\x2e\x31\x2d\x2e\x35\x38\x2d\x2e\x31\x36\
+\x2d\x31\x2e\x33\x35\x2d\x2e\x34\x2c\x32\x2e\x36\x32\x2d\x32\x34\
+\x2e\x39\x38\x2e\x31\x34\x2d\x34\x39\x2e\x34\x36\x2d\x31\x30\x2e\
+\x33\x34\x2d\x37\x32\x2e\x36\x37\x2d\x39\x2e\x32\x34\x2d\x32\x30\
+\x2e\x34\x36\x2d\x32\x33\x2e\x33\x34\x2d\x33\x36\x2e\x36\x37\x2d\
+\x34\x33\x2d\x34\x37\x2e\x39\x2d\x34\x2e\x34\x32\x2d\x32\x2e\x35\
+\x33\x2d\x36\x2e\x39\x39\x2d\x31\x2e\x39\x35\x2d\x31\x31\x2e\x32\
+\x32\x2c\x31\x2e\x34\x37\x2d\x31\x31\x2e\x30\x37\x2c\x38\x2e\x39\
+\x36\x2d\x31\x32\x2e\x32\x36\x2c\x31\x36\x2e\x36\x38\x2d\x36\x2e\
+\x35\x32\x2c\x33\x30\x2e\x39\x34\x2c\x31\x36\x2e\x32\x36\x2c\x34\
+\x30\x2e\x34\x32\x2c\x32\x2e\x36\x39\x2c\x37\x36\x2e\x36\x31\x2d\
+\x32\x31\x2e\x37\x33\x2c\x31\x31\x30\x2e\x30\x36\x2d\x39\x2e\x33\
+\x2c\x31\x32\x2e\x37\x34\x2d\x31\x39\x2e\x36\x34\x2c\x31\x37\x2e\
+\x39\x2d\x33\x36\x2e\x33\x35\x2c\x31\x34\x2e\x38\x39\x2d\x34\x30\
+\x2e\x39\x35\x2d\x37\x2e\x33\x35\x2d\x37\x35\x2e\x30\x36\x2d\x32\
+\x35\x2e\x37\x34\x2d\x31\x30\x32\x2e\x36\x34\x2d\x35\x36\x2e\x36\
+\x33\x2d\x2e\x35\x38\x2d\x2e\x36\x35\x2d\x2e\x38\x35\x2d\x31\x2e\
+\x35\x37\x2d\x31\x2e\x38\x32\x2d\x33\x2e\x34\x33\x2c\x31\x38\x2e\
+\x33\x32\x2c\x31\x2e\x38\x32\x2c\x33\x35\x2e\x37\x33\x2e\x33\x31\
+\x2c\x35\x32\x2e\x37\x32\x2d\x34\x2e\x34\x2c\x32\x38\x2e\x31\x33\
+\x2d\x37\x2e\x38\x2c\x35\x30\x2e\x36\x37\x2d\x32\x33\x2e\x31\x39\
+\x2c\x36\x36\x2d\x34\x38\x2e\x36\x35\x2c\x33\x2e\x38\x2d\x36\x2e\
+\x33\x31\x2c\x33\x2e\x32\x32\x2d\x31\x30\x2e\x33\x35\x2d\x31\x2e\
+\x38\x38\x2d\x31\x35\x2e\x33\x35\x2d\x31\x31\x2e\x35\x39\x2d\x31\
+\x31\x2e\x33\x37\x2d\x31\x31\x2e\x33\x32\x2d\x31\x31\x2e\x33\x39\
+\x2d\x32\x36\x2e\x36\x31\x2d\x35\x2e\x32\x38\x2d\x34\x31\x2e\x39\
+\x34\x2c\x31\x36\x2e\x37\x37\x2d\x37\x38\x2e\x37\x33\x2c\x35\x2e\
+\x30\x39\x2d\x31\x31\x34\x2e\x31\x37\x2d\x31\x39\x2e\x32\x35\x2d\
+\x31\x36\x2e\x32\x39\x2d\x31\x31\x2e\x31\x39\x2d\x31\x36\x2e\x34\
+\x32\x2d\x32\x34\x2e\x37\x33\x2d\x31\x33\x2e\x35\x33\x2d\x34\x31\
+\x2e\x33\x36\x2c\x37\x2e\x31\x38\x2d\x34\x31\x2e\x32\x39\x2c\x32\
+\x36\x2e\x34\x35\x2d\x37\x35\x2e\x34\x32\x2c\x35\x38\x2e\x32\x34\
+\x2d\x31\x30\x32\x2e\x35\x38\x2e\x38\x34\x2d\x2e\x37\x31\x2c\x31\
+\x2e\x38\x31\x2d\x31\x2e\x32\x37\x2c\x33\x2e\x36\x35\x2d\x32\x2e\
+\x35\x34\x2e\x39\x38\x2c\x31\x35\x2e\x37\x32\x2d\x2e\x38\x39\x2c\
+\x33\x30\x2e\x33\x31\x2c\x31\x2e\x39\x39\x2c\x34\x34\x2e\x37\x36\
+\x2c\x36\x2e\x34\x36\x2c\x33\x32\x2e\x33\x39\x2c\x32\x31\x2e\x37\
+\x37\x2c\x35\x38\x2e\x36\x34\x2c\x35\x30\x2e\x35\x31\x2c\x37\x36\
+\x2e\x32\x36\x2c\x35\x2e\x33\x32\x2c\x33\x2e\x32\x36\x2c\x38\x2e\
+\x37\x36\x2c\x33\x2e\x35\x38\x2c\x31\x33\x2e\x35\x2d\x31\x2e\x33\
+\x39\x2c\x31\x31\x2e\x38\x31\x2d\x31\x32\x2e\x33\x38\x2c\x31\x31\
+\x2e\x37\x31\x2d\x31\x31\x2e\x39\x36\x2c\x35\x2e\x37\x39\x2d\x32\
+\x38\x2e\x31\x39\x2d\x31\x34\x2e\x38\x37\x2d\x34\x30\x2e\x38\x2d\
+\x33\x2e\x38\x32\x2d\x37\x36\x2e\x38\x36\x2c\x32\x30\x2e\x35\x31\
+\x2d\x31\x31\x30\x2e\x36\x32\x2c\x39\x2e\x37\x35\x2d\x31\x33\x2e\
+\x35\x33\x2c\x32\x30\x2e\x33\x31\x2d\x31\x39\x2e\x37\x39\x2c\x33\
+\x38\x2e\x34\x37\x2d\x31\x36\x2e\x33\x31\x2c\x34\x30\x2e\x34\x39\
+\x2c\x37\x2e\x37\x34\x2c\x37\x34\x2e\x32\x36\x2c\x32\x36\x2c\x31\
+\x30\x31\x2e\x37\x35\x2c\x35\x36\x2e\x34\x2e\x35\x36\x2e\x36\x31\
+\x2e\x36\x37\x2c\x31\x2e\x36\x33\x2c\x31\x2e\x35\x33\x2c\x33\x2e\
+\x38\x36\x5a\x4d\x32\x39\x39\x2e\x37\x32\x2c\x33\x32\x31\x2e\x30\
+\x33\x63\x34\x2e\x35\x38\x2e\x31\x33\x2c\x32\x30\x2e\x38\x33\x2d\
+\x31\x35\x2e\x38\x39\x2c\x32\x31\x2e\x30\x39\x2d\x32\x30\x2e\x37\
+\x39\x2e\x32\x36\x2d\x34\x2e\x38\x39\x2d\x31\x35\x2e\x32\x37\x2d\
+\x32\x30\x2e\x38\x38\x2d\x32\x30\x2e\x35\x35\x2d\x32\x31\x2e\x31\
+\x36\x2d\x34\x2e\x34\x38\x2d\x2e\x32\x34\x2d\x32\x31\x2e\x30\x34\
+\x2c\x31\x35\x2e\x39\x31\x2d\x32\x31\x2e\x31\x38\x2c\x32\x30\x2e\
+\x36\x35\x2d\x2e\x31\x34\x2c\x34\x2e\x38\x33\x2c\x31\x35\x2e\x36\
+\x38\x2c\x32\x31\x2e\x31\x35\x2c\x32\x30\x2e\x36\x34\x2c\x32\x31\
+\x2e\x32\x39\x5a\x22\x2f\x3e\x0a\x3c\x2f\x73\x76\x67\x3e\
+\x00\x00\x0b\x76\
\x3c\
\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\
@@ -22619,176 +22630,175 @@
\x0a\x20\x20\x20\x20\x20\x20\x7d\x0a\x20\x20\x20\x20\x3c\x2f\x73\
\x74\x79\x6c\x65\x3e\x0a\x20\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0a\
\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\
-\x6c\x73\x2d\x31\x22\x20\x64\x3d\x22\x4d\x34\x35\x37\x2e\x37\x39\
-\x2c\x33\x31\x38\x2e\x30\x33\x63\x2e\x34\x37\x2d\x31\x32\x2e\x30\
-\x31\x2c\x35\x2e\x32\x34\x2d\x31\x37\x2e\x36\x2c\x31\x35\x2e\x30\
-\x31\x2d\x31\x36\x2e\x39\x36\x2c\x31\x30\x2e\x38\x36\x2e\x37\x2c\
-\x31\x33\x2e\x33\x38\x2c\x37\x2e\x38\x35\x2c\x31\x33\x2e\x33\x37\
-\x2c\x31\x37\x2e\x33\x39\x2d\x2e\x31\x31\x2c\x36\x36\x2e\x36\x39\
-\x2d\x2e\x30\x32\x2c\x31\x33\x33\x2e\x33\x38\x2d\x2e\x31\x31\x2c\
-\x32\x30\x30\x2e\x30\x38\x2d\x2e\x30\x31\x2c\x31\x31\x2e\x31\x34\
-\x2d\x35\x2e\x30\x32\x2c\x31\x36\x2e\x37\x2d\x31\x34\x2e\x33\x33\
-\x2c\x31\x36\x2e\x33\x37\x2d\x31\x30\x2e\x30\x36\x2d\x2e\x33\x35\
-\x2d\x31\x34\x2e\x35\x39\x2d\x36\x2e\x35\x38\x2d\x31\x33\x2e\x37\
-\x33\x2d\x31\x35\x2e\x37\x37\x2c\x31\x2e\x30\x36\x2d\x31\x31\x2e\
-\x33\x33\x2d\x33\x2e\x34\x31\x2d\x31\x33\x2e\x37\x36\x2d\x31\x34\
-\x2e\x31\x35\x2d\x31\x33\x2e\x36\x39\x2d\x36\x35\x2e\x31\x36\x2e\
-\x34\x38\x2d\x31\x33\x30\x2e\x33\x33\x2e\x33\x39\x2d\x31\x39\x35\
-\x2e\x35\x2e\x31\x33\x2d\x34\x34\x2e\x30\x39\x2d\x2e\x31\x38\x2d\
-\x38\x32\x2e\x33\x35\x2d\x31\x35\x2e\x37\x36\x2d\x31\x31\x35\x2e\
-\x34\x32\x2d\x34\x34\x2e\x37\x38\x2d\x37\x2e\x31\x2d\x36\x2e\x32\
-\x33\x2d\x31\x34\x2e\x37\x35\x2d\x31\x31\x2e\x39\x35\x2d\x32\x32\
-\x2e\x36\x38\x2d\x31\x37\x2e\x30\x37\x43\x34\x30\x2e\x35\x32\x2c\
-\x33\x39\x38\x2e\x36\x34\x2c\x34\x2e\x33\x34\x2c\x33\x31\x34\x2e\
-\x39\x39\x2c\x31\x39\x2e\x34\x33\x2c\x32\x33\x33\x2e\x31\x37\x2c\
-\x34\x31\x2e\x34\x34\x2c\x31\x31\x33\x2e\x37\x37\x2c\x31\x36\x33\
-\x2e\x35\x38\x2c\x33\x39\x2e\x30\x31\x2c\x32\x38\x30\x2e\x32\x33\
-\x2c\x37\x33\x2e\x35\x34\x63\x38\x32\x2e\x36\x36\x2c\x32\x34\x2e\
-\x34\x37\x2c\x31\x34\x31\x2e\x33\x2c\x39\x36\x2e\x39\x2c\x31\x34\
-\x36\x2e\x31\x31\x2c\x31\x38\x33\x2e\x30\x34\x2c\x31\x2e\x33\x33\
-\x2c\x32\x33\x2e\x38\x39\x2d\x32\x2e\x32\x32\x2c\x34\x38\x2e\x30\
-\x36\x2d\x33\x2e\x35\x36\x2c\x37\x32\x2e\x36\x32\x2c\x39\x2e\x36\
-\x38\x2c\x30\x2c\x32\x31\x2e\x33\x36\x2c\x30\x2c\x33\x34\x2e\x34\
-\x33\x2c\x30\x2c\x2e\x32\x34\x2d\x34\x2e\x34\x37\x2e\x34\x35\x2d\
-\x37\x2e\x38\x31\x2e\x35\x38\x2d\x31\x31\x2e\x31\x36\x5a\x4d\x32\
-\x32\x32\x2e\x39\x39\x2c\x39\x33\x2e\x38\x38\x63\x2d\x39\x37\x2e\
-\x37\x36\x2d\x2e\x34\x35\x2d\x31\x37\x38\x2e\x39\x38\x2c\x38\x30\
-\x2e\x37\x39\x2d\x31\x37\x38\x2e\x30\x33\x2c\x31\x37\x38\x2e\x30\
-\x36\x2e\x39\x35\x2c\x39\x36\x2e\x38\x33\x2c\x38\x30\x2e\x32\x34\
-\x2c\x31\x37\x35\x2e\x37\x34\x2c\x31\x37\x36\x2e\x36\x2c\x31\x37\
-\x35\x2e\x37\x35\x2c\x39\x38\x2c\x30\x2c\x31\x37\x37\x2e\x32\x35\
-\x2d\x37\x39\x2e\x32\x33\x2c\x31\x37\x37\x2e\x32\x36\x2d\x31\x37\
-\x37\x2e\x32\x33\x2c\x30\x2d\x39\x36\x2e\x30\x33\x2d\x37\x39\x2e\
-\x37\x37\x2d\x31\x37\x36\x2e\x31\x34\x2d\x31\x37\x35\x2e\x38\x34\
-\x2d\x31\x37\x36\x2e\x35\x38\x5a\x22\x2f\x3e\x0a\x20\x20\x3c\x70\
-\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x6c\x73\x2d\x31\
-\x22\x20\x64\x3d\x22\x4d\x33\x32\x34\x2e\x33\x38\x2c\x31\x38\x35\
-\x2e\x36\x36\x63\x2d\x32\x30\x2e\x36\x39\x2d\x32\x2e\x30\x39\x2d\
-\x33\x39\x2e\x37\x36\x2e\x38\x2d\x35\x37\x2e\x38\x35\x2c\x39\x2e\
-\x32\x37\x2d\x31\x35\x2e\x34\x35\x2c\x37\x2e\x32\x34\x2d\x32\x37\
-\x2e\x36\x35\x2c\x31\x38\x2e\x30\x31\x2d\x33\x36\x2e\x33\x35\x2c\
-\x33\x32\x2e\x38\x32\x2d\x32\x2e\x38\x2c\x34\x2e\x37\x36\x2d\x32\
-\x2e\x38\x33\x2c\x38\x2e\x30\x31\x2c\x31\x2e\x35\x35\x2c\x31\x32\
-\x2e\x30\x38\x2c\x39\x2e\x33\x34\x2c\x38\x2e\x36\x38\x2c\x39\x2c\
-\x38\x2e\x37\x32\x2c\x32\x30\x2e\x34\x34\x2c\x34\x2e\x32\x33\x2c\
-\x33\x35\x2e\x30\x35\x2d\x31\x33\x2e\x37\x37\x2c\x36\x35\x2e\x34\
-\x39\x2d\x34\x2e\x30\x31\x2c\x39\x34\x2e\x33\x2c\x31\x37\x2e\x35\
-\x38\x2c\x31\x30\x2e\x34\x34\x2c\x37\x2e\x38\x32\x2c\x39\x2e\x38\
-\x2c\x31\x36\x2e\x37\x39\x2c\x38\x2e\x32\x36\x2c\x32\x37\x2e\x33\
-\x34\x2d\x35\x2e\x30\x31\x2c\x33\x34\x2e\x33\x2d\x32\x30\x2e\x37\
-\x32\x2c\x36\x32\x2e\x35\x36\x2d\x34\x37\x2e\x31\x34\x2c\x38\x34\
-\x2e\x39\x33\x2d\x2e\x35\x34\x2e\x34\x35\x2d\x31\x2e\x32\x33\x2e\
-\x37\x34\x2d\x31\x2e\x38\x38\x2c\x31\x2e\x30\x33\x2d\x2e\x31\x37\
-\x2e\x30\x38\x2d\x2e\x34\x36\x2d\x2e\x31\x33\x2d\x31\x2e\x30\x37\
-\x2d\x2e\x33\x31\x2c\x32\x2e\x30\x38\x2d\x31\x39\x2e\x38\x34\x2e\
-\x31\x31\x2d\x33\x39\x2e\x32\x39\x2d\x38\x2e\x32\x32\x2d\x35\x37\
-\x2e\x37\x32\x2d\x37\x2e\x33\x34\x2d\x31\x36\x2e\x32\x35\x2d\x31\
-\x38\x2e\x35\x33\x2d\x32\x39\x2e\x31\x32\x2d\x33\x34\x2e\x31\x35\
-\x2d\x33\x38\x2e\x30\x34\x2d\x33\x2e\x35\x31\x2d\x32\x2e\x30\x31\
-\x2d\x35\x2e\x35\x35\x2d\x31\x2e\x35\x35\x2d\x38\x2e\x39\x31\x2c\
-\x31\x2e\x31\x37\x2d\x38\x2e\x37\x39\x2c\x37\x2e\x31\x31\x2d\x39\
-\x2e\x37\x34\x2c\x31\x33\x2e\x32\x35\x2d\x35\x2e\x31\x38\x2c\x32\
-\x34\x2e\x35\x37\x2c\x31\x32\x2e\x39\x32\x2c\x33\x32\x2e\x31\x2c\
-\x32\x2e\x31\x33\x2c\x36\x30\x2e\x38\x35\x2d\x31\x37\x2e\x32\x36\
-\x2c\x38\x37\x2e\x34\x32\x2d\x37\x2e\x33\x39\x2c\x31\x30\x2e\x31\
-\x32\x2d\x31\x35\x2e\x36\x2c\x31\x34\x2e\x32\x31\x2d\x32\x38\x2e\
-\x38\x37\x2c\x31\x31\x2e\x38\x33\x2d\x33\x32\x2e\x35\x32\x2d\x35\
-\x2e\x38\x34\x2d\x35\x39\x2e\x36\x32\x2d\x32\x30\x2e\x34\x35\x2d\
-\x38\x31\x2e\x35\x33\x2d\x34\x34\x2e\x39\x38\x2d\x2e\x34\x36\x2d\
-\x2e\x35\x31\x2d\x2e\x36\x37\x2d\x31\x2e\x32\x35\x2d\x31\x2e\x34\
-\x34\x2d\x32\x2e\x37\x33\x2c\x31\x34\x2e\x35\x35\x2c\x31\x2e\x34\
-\x34\x2c\x32\x38\x2e\x33\x38\x2e\x32\x35\x2c\x34\x31\x2e\x38\x37\
-\x2d\x33\x2e\x35\x2c\x32\x32\x2e\x33\x35\x2d\x36\x2e\x32\x2c\x34\
-\x30\x2e\x32\x35\x2d\x31\x38\x2e\x34\x32\x2c\x35\x32\x2e\x34\x32\
-\x2d\x33\x38\x2e\x36\x34\x2c\x33\x2e\x30\x32\x2d\x35\x2e\x30\x31\
-\x2c\x32\x2e\x35\x36\x2d\x38\x2e\x32\x32\x2d\x31\x2e\x34\x39\x2d\
-\x31\x32\x2e\x31\x39\x2d\x39\x2e\x32\x31\x2d\x39\x2e\x30\x33\x2d\
-\x38\x2e\x39\x39\x2d\x39\x2e\x30\x35\x2d\x32\x31\x2e\x31\x33\x2d\
-\x34\x2e\x31\x39\x2d\x33\x33\x2e\x33\x31\x2c\x31\x33\x2e\x33\x32\
-\x2d\x36\x32\x2e\x35\x33\x2c\x34\x2e\x30\x34\x2d\x39\x30\x2e\x36\
-\x39\x2d\x31\x35\x2e\x32\x39\x2d\x31\x32\x2e\x39\x34\x2d\x38\x2e\
-\x38\x39\x2d\x31\x33\x2e\x30\x34\x2d\x31\x39\x2e\x36\x34\x2d\x31\
-\x30\x2e\x37\x35\x2d\x33\x32\x2e\x38\x35\x2c\x35\x2e\x37\x2d\x33\
-\x32\x2e\x38\x2c\x32\x31\x2e\x30\x31\x2d\x35\x39\x2e\x39\x2c\x34\
-\x36\x2e\x32\x36\x2d\x38\x31\x2e\x34\x38\x2e\x36\x36\x2d\x2e\x35\
-\x37\x2c\x31\x2e\x34\x34\x2d\x31\x2e\x30\x31\x2c\x32\x2e\x39\x2d\
-\x32\x2e\x30\x32\x2e\x37\x38\x2c\x31\x32\x2e\x34\x39\x2d\x2e\x37\
-\x31\x2c\x32\x34\x2e\x30\x38\x2c\x31\x2e\x35\x38\x2c\x33\x35\x2e\
-\x35\x35\x2c\x35\x2e\x31\x33\x2c\x32\x35\x2e\x37\x33\x2c\x31\x37\
-\x2e\x32\x39\x2c\x34\x36\x2e\x35\x38\x2c\x34\x30\x2e\x31\x32\x2c\
-\x36\x30\x2e\x35\x37\x2c\x34\x2e\x32\x33\x2c\x32\x2e\x35\x39\x2c\
-\x36\x2e\x39\x36\x2c\x32\x2e\x38\x34\x2c\x31\x30\x2e\x37\x32\x2d\
-\x31\x2e\x31\x2c\x39\x2e\x33\x38\x2d\x39\x2e\x38\x34\x2c\x39\x2e\
-\x33\x2d\x39\x2e\x35\x2c\x34\x2e\x36\x2d\x32\x32\x2e\x33\x39\x2d\
-\x31\x31\x2e\x38\x31\x2d\x33\x32\x2e\x34\x31\x2d\x33\x2e\x30\x34\
-\x2d\x36\x31\x2e\x30\x35\x2c\x31\x36\x2e\x32\x39\x2d\x38\x37\x2e\
-\x38\x36\x2c\x37\x2e\x37\x35\x2d\x31\x30\x2e\x37\x35\x2c\x31\x36\
-\x2e\x31\x33\x2d\x31\x35\x2e\x37\x31\x2c\x33\x30\x2e\x35\x35\x2d\
-\x31\x32\x2e\x39\x36\x2c\x33\x32\x2e\x31\x36\x2c\x36\x2e\x31\x35\
-\x2c\x35\x38\x2e\x39\x39\x2c\x32\x30\x2e\x36\x35\x2c\x38\x30\x2e\
-\x38\x32\x2c\x34\x34\x2e\x38\x2e\x34\x34\x2e\x34\x39\x2e\x35\x33\
-\x2c\x31\x2e\x32\x39\x2c\x31\x2e\x32\x32\x2c\x33\x2e\x30\x37\x5a\
-\x4d\x32\x32\x31\x2e\x36\x36\x2c\x32\x38\x37\x2e\x34\x39\x63\x33\
-\x2e\x36\x34\x2e\x31\x2c\x31\x36\x2e\x35\x35\x2d\x31\x32\x2e\x36\
-\x32\x2c\x31\x36\x2e\x37\x35\x2d\x31\x36\x2e\x35\x31\x2e\x32\x2d\
-\x33\x2e\x38\x39\x2d\x31\x32\x2e\x31\x33\x2d\x31\x36\x2e\x35\x38\
-\x2d\x31\x36\x2e\x33\x32\x2d\x31\x36\x2e\x38\x2d\x33\x2e\x35\x36\
-\x2d\x2e\x31\x39\x2d\x31\x36\x2e\x37\x31\x2c\x31\x32\x2e\x36\x34\
-\x2d\x31\x36\x2e\x38\x33\x2c\x31\x36\x2e\x34\x2d\x2e\x31\x31\x2c\
-\x33\x2e\x38\x33\x2c\x31\x32\x2e\x34\x36\x2c\x31\x36\x2e\x38\x2c\
-\x31\x36\x2e\x34\x2c\x31\x36\x2e\x39\x31\x5a\x22\x2f\x3e\x0a\x20\
-\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x6c\
-\x73\x2d\x31\x22\x20\x64\x3d\x22\x4d\x35\x38\x33\x2e\x39\x36\x2c\
-\x33\x33\x33\x2e\x31\x36\x63\x2d\x33\x2e\x36\x35\x2c\x32\x2e\x31\
-\x35\x2d\x37\x2e\x33\x31\x2c\x34\x2e\x32\x39\x2d\x31\x30\x2e\x39\
-\x35\x2c\x36\x2e\x34\x35\x2d\x31\x35\x2e\x33\x2c\x39\x2e\x31\x2d\
-\x33\x30\x2e\x35\x39\x2c\x31\x38\x2e\x32\x2d\x34\x35\x2e\x38\x38\
-\x2c\x32\x37\x2e\x33\x31\x2d\x2e\x31\x37\x2e\x31\x2d\x2e\x33\x34\
-\x2e\x31\x39\x2d\x2e\x36\x37\x2e\x33\x37\x2c\x30\x2d\x32\x2e\x33\
-\x34\x2d\x2e\x30\x35\x2d\x31\x39\x2e\x35\x35\x2d\x2e\x30\x35\x2d\
-\x31\x39\x2e\x35\x35\x68\x2d\x32\x33\x2e\x32\x36\x76\x2d\x32\x38\
-\x2e\x31\x35\x68\x32\x33\x2e\x32\x36\x73\x2d\x2e\x30\x32\x2d\x32\
-\x30\x2e\x35\x37\x2c\x30\x2d\x32\x30\x2e\x37\x32\x63\x2e\x30\x37\
-\x2c\x30\x2c\x31\x30\x2e\x32\x38\x2c\x35\x2e\x39\x37\x2c\x31\x34\
-\x2e\x39\x39\x2c\x38\x2e\x37\x37\x2c\x31\x34\x2e\x31\x39\x2c\x38\
-\x2e\x34\x34\x2c\x32\x38\x2e\x33\x38\x2c\x31\x36\x2e\x38\x38\x2c\
-\x34\x32\x2e\x35\x37\x2c\x32\x35\x2e\x33\x32\x2c\x30\x2c\x2e\x30\
-\x36\x2c\x30\x2c\x2e\x31\x32\x2c\x30\x2c\x2e\x31\x39\x5a\x22\x2f\
+\x6c\x73\x2d\x31\x22\x20\x64\x3d\x22\x4d\x34\x33\x30\x2e\x35\x35\
+\x2c\x33\x31\x34\x2e\x39\x32\x63\x2e\x33\x39\x2d\x39\x2e\x39\x34\
+\x2c\x34\x2e\x33\x33\x2d\x31\x34\x2e\x35\x36\x2c\x31\x32\x2e\x34\
+\x32\x2d\x31\x34\x2e\x30\x34\x2c\x38\x2e\x39\x39\x2e\x35\x38\x2c\
+\x31\x31\x2e\x30\x37\x2c\x36\x2e\x35\x2c\x31\x31\x2e\x30\x36\x2c\
+\x31\x34\x2e\x33\x38\x2d\x2e\x30\x39\x2c\x35\x35\x2e\x31\x38\x2d\
+\x2e\x30\x32\x2c\x31\x31\x30\x2e\x33\x36\x2d\x2e\x30\x39\x2c\x31\
+\x36\x35\x2e\x35\x33\x2d\x2e\x30\x31\x2c\x39\x2e\x32\x31\x2d\x34\
+\x2e\x31\x35\x2c\x31\x33\x2e\x38\x31\x2d\x31\x31\x2e\x38\x35\x2c\
+\x31\x33\x2e\x35\x35\x2d\x38\x2e\x33\x32\x2d\x2e\x32\x39\x2d\x31\
+\x32\x2e\x30\x37\x2d\x35\x2e\x34\x35\x2d\x31\x31\x2e\x33\x36\x2d\
+\x31\x33\x2e\x30\x34\x2e\x38\x38\x2d\x39\x2e\x33\x38\x2d\x32\x2e\
+\x38\x32\x2d\x31\x31\x2e\x33\x39\x2d\x31\x31\x2e\x37\x31\x2d\x31\
+\x31\x2e\x33\x32\x2d\x35\x33\x2e\x39\x31\x2e\x34\x2d\x31\x30\x37\
+\x2e\x38\x33\x2e\x33\x32\x2d\x31\x36\x31\x2e\x37\x35\x2e\x31\x31\
+\x2d\x33\x36\x2e\x34\x38\x2d\x2e\x31\x34\x2d\x36\x38\x2e\x31\x34\
+\x2d\x31\x33\x2e\x30\x34\x2d\x39\x35\x2e\x35\x2d\x33\x37\x2e\x30\
+\x35\x2d\x35\x2e\x38\x37\x2d\x35\x2e\x31\x35\x2d\x31\x32\x2e\x32\
+\x2d\x39\x2e\x38\x38\x2d\x31\x38\x2e\x37\x36\x2d\x31\x34\x2e\x31\
+\x33\x2d\x35\x37\x2e\x36\x39\x2d\x33\x37\x2e\x33\x2d\x38\x37\x2e\
+\x36\x32\x2d\x31\x30\x36\x2e\x35\x31\x2d\x37\x35\x2e\x31\x34\x2d\
+\x31\x37\x34\x2e\x32\x31\x2c\x31\x38\x2e\x32\x31\x2d\x39\x38\x2e\
+\x37\x38\x2c\x31\x31\x39\x2e\x32\x37\x2d\x31\x36\x30\x2e\x36\x34\
+\x2c\x32\x31\x35\x2e\x37\x38\x2d\x31\x33\x32\x2e\x30\x37\x2c\x36\
+\x38\x2e\x33\x39\x2c\x32\x30\x2e\x32\x34\x2c\x31\x31\x36\x2e\x39\
+\x2c\x38\x30\x2e\x31\x37\x2c\x31\x32\x30\x2e\x38\x38\x2c\x31\x35\
+\x31\x2e\x34\x34\x2c\x31\x2e\x31\x2c\x31\x39\x2e\x37\x37\x2d\x31\
+\x2e\x38\x34\x2c\x33\x39\x2e\x37\x36\x2d\x32\x2e\x39\x35\x2c\x36\
+\x30\x2e\x30\x39\x2c\x38\x2e\x30\x31\x2c\x30\x2c\x31\x37\x2e\x36\
+\x37\x2c\x30\x2c\x32\x38\x2e\x34\x39\x2c\x30\x2c\x2e\x32\x2d\x33\
+\x2e\x36\x39\x2e\x33\x37\x2d\x36\x2e\x34\x36\x2e\x34\x38\x2d\x39\
+\x2e\x32\x34\x5a\x4d\x32\x33\x36\x2e\x32\x38\x2c\x31\x32\x39\x2e\
+\x34\x36\x63\x2d\x38\x30\x2e\x38\x38\x2d\x2e\x33\x37\x2d\x31\x34\
+\x38\x2e\x30\x38\x2c\x36\x36\x2e\x38\x35\x2d\x31\x34\x37\x2e\x32\
+\x39\x2c\x31\x34\x37\x2e\x33\x32\x2e\x37\x39\x2c\x38\x30\x2e\x31\
+\x32\x2c\x36\x36\x2e\x33\x39\x2c\x31\x34\x35\x2e\x34\x2c\x31\x34\
+\x36\x2e\x31\x31\x2c\x31\x34\x35\x2e\x34\x31\x2c\x38\x31\x2e\x30\
+\x38\x2c\x30\x2c\x31\x34\x36\x2e\x36\x35\x2d\x36\x35\x2e\x35\x35\
+\x2c\x31\x34\x36\x2e\x36\x36\x2d\x31\x34\x36\x2e\x36\x33\x2c\x30\
+\x2d\x37\x39\x2e\x34\x35\x2d\x36\x36\x2d\x31\x34\x35\x2e\x37\x33\
+\x2d\x31\x34\x35\x2e\x34\x38\x2d\x31\x34\x36\x2e\x31\x5a\x22\x2f\
\x3e\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\
-\x22\x63\x6c\x73\x2d\x31\x22\x20\x64\x3d\x22\x4d\x35\x38\x33\x2e\
-\x39\x36\x2c\x34\x39\x36\x2e\x30\x35\x63\x2d\x33\x2e\x36\x35\x2c\
-\x32\x2e\x31\x35\x2d\x37\x2e\x33\x31\x2c\x34\x2e\x32\x39\x2d\x31\
-\x30\x2e\x39\x35\x2c\x36\x2e\x34\x35\x2d\x31\x35\x2e\x33\x2c\x39\
-\x2e\x31\x2d\x33\x30\x2e\x35\x39\x2c\x31\x38\x2e\x32\x2d\x34\x35\
-\x2e\x38\x38\x2c\x32\x37\x2e\x33\x31\x2d\x2e\x31\x37\x2e\x31\x2d\
-\x2e\x33\x34\x2e\x31\x39\x2d\x2e\x36\x37\x2e\x33\x37\x2c\x30\x2d\
-\x32\x2e\x33\x34\x2d\x2e\x30\x35\x2d\x31\x39\x2e\x35\x35\x2d\x2e\
-\x30\x35\x2d\x31\x39\x2e\x35\x35\x68\x2d\x32\x33\x2e\x32\x36\x76\
-\x2d\x32\x38\x2e\x31\x35\x68\x32\x33\x2e\x32\x36\x73\x2d\x2e\x30\
-\x32\x2d\x32\x30\x2e\x35\x37\x2c\x30\x2d\x32\x30\x2e\x37\x32\x63\
-\x2e\x30\x37\x2c\x30\x2c\x31\x30\x2e\x32\x38\x2c\x35\x2e\x39\x37\
-\x2c\x31\x34\x2e\x39\x39\x2c\x38\x2e\x37\x37\x2c\x31\x34\x2e\x31\
-\x39\x2c\x38\x2e\x34\x34\x2c\x32\x38\x2e\x33\x38\x2c\x31\x36\x2e\
-\x38\x38\x2c\x34\x32\x2e\x35\x37\x2c\x32\x35\x2e\x33\x32\x2c\x30\
-\x2c\x2e\x30\x36\x2c\x30\x2c\x2e\x31\x32\x2c\x30\x2c\x2e\x31\x39\
-\x5a\x22\x2f\x3e\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\
-\x73\x73\x3d\x22\x63\x6c\x73\x2d\x31\x22\x20\x64\x3d\x22\x4d\x35\
-\x38\x33\x2e\x39\x36\x2c\x34\x31\x34\x2e\x36\x31\x63\x2d\x33\x2e\
-\x36\x35\x2c\x32\x2e\x31\x35\x2d\x37\x2e\x33\x31\x2c\x34\x2e\x32\
-\x39\x2d\x31\x30\x2e\x39\x35\x2c\x36\x2e\x34\x35\x2d\x31\x35\x2e\
-\x33\x2c\x39\x2e\x31\x2d\x33\x30\x2e\x35\x39\x2c\x31\x38\x2e\x32\
-\x2d\x34\x35\x2e\x38\x38\x2c\x32\x37\x2e\x33\x31\x2d\x2e\x31\x37\
-\x2e\x31\x2d\x2e\x33\x34\x2e\x31\x39\x2d\x2e\x36\x37\x2e\x33\x37\
-\x2c\x30\x2d\x32\x2e\x33\x34\x2d\x2e\x30\x35\x2d\x31\x39\x2e\x35\
-\x35\x2d\x2e\x30\x35\x2d\x31\x39\x2e\x35\x35\x68\x2d\x32\x33\x2e\
-\x32\x36\x76\x2d\x32\x38\x2e\x31\x35\x68\x32\x33\x2e\x32\x36\x73\
-\x2d\x2e\x30\x32\x2d\x32\x30\x2e\x35\x37\x2c\x30\x2d\x32\x30\x2e\
-\x37\x32\x63\x2e\x30\x37\x2c\x30\x2c\x31\x30\x2e\x32\x38\x2c\x35\
-\x2e\x39\x37\x2c\x31\x34\x2e\x39\x39\x2c\x38\x2e\x37\x37\x2c\x31\
-\x34\x2e\x31\x39\x2c\x38\x2e\x34\x34\x2c\x32\x38\x2e\x33\x38\x2c\
-\x31\x36\x2e\x38\x38\x2c\x34\x32\x2e\x35\x37\x2c\x32\x35\x2e\x33\
-\x32\x2c\x30\x2c\x2e\x30\x36\x2c\x30\x2c\x2e\x31\x32\x2c\x30\x2c\
-\x2e\x31\x39\x5a\x22\x2f\x3e\x0a\x3c\x2f\x73\x76\x67\x3e\
+\x22\x63\x6c\x73\x2d\x31\x22\x20\x64\x3d\x22\x4d\x33\x32\x30\x2e\
+\x31\x37\x2c\x32\x30\x35\x2e\x34\x63\x2d\x31\x37\x2e\x31\x32\x2d\
+\x31\x2e\x37\x33\x2d\x33\x32\x2e\x38\x39\x2e\x36\x36\x2d\x34\x37\
+\x2e\x38\x36\x2c\x37\x2e\x36\x37\x2d\x31\x32\x2e\x37\x38\x2c\x35\
+\x2e\x39\x39\x2d\x32\x32\x2e\x38\x38\x2c\x31\x34\x2e\x39\x2d\x33\
+\x30\x2e\x30\x38\x2c\x32\x37\x2e\x31\x36\x2d\x32\x2e\x33\x31\x2c\
+\x33\x2e\x39\x34\x2d\x32\x2e\x33\x34\x2c\x36\x2e\x36\x33\x2c\x31\
+\x2e\x32\x38\x2c\x39\x2e\x39\x39\x2c\x37\x2e\x37\x33\x2c\x37\x2e\
+\x31\x38\x2c\x37\x2e\x34\x35\x2c\x37\x2e\x32\x32\x2c\x31\x36\x2e\
+\x39\x31\x2c\x33\x2e\x35\x2c\x32\x39\x2d\x31\x31\x2e\x33\x39\x2c\
+\x35\x34\x2e\x31\x39\x2d\x33\x2e\x33\x32\x2c\x37\x38\x2e\x30\x32\
+\x2c\x31\x34\x2e\x35\x34\x2c\x38\x2e\x36\x34\x2c\x36\x2e\x34\x37\
+\x2c\x38\x2e\x31\x31\x2c\x31\x33\x2e\x38\x39\x2c\x36\x2e\x38\x34\
+\x2c\x32\x32\x2e\x36\x32\x2d\x34\x2e\x31\x34\x2c\x32\x38\x2e\x33\
+\x38\x2d\x31\x37\x2e\x31\x34\x2c\x35\x31\x2e\x37\x36\x2d\x33\x39\
+\x2c\x37\x30\x2e\x32\x36\x2d\x2e\x34\x34\x2e\x33\x38\x2d\x31\x2e\
+\x30\x32\x2e\x36\x31\x2d\x31\x2e\x35\x35\x2e\x38\x35\x2d\x2e\x31\
+\x34\x2e\x30\x36\x2d\x2e\x33\x38\x2d\x2e\x31\x2d\x2e\x38\x39\x2d\
+\x2e\x32\x36\x2c\x31\x2e\x37\x32\x2d\x31\x36\x2e\x34\x32\x2e\x30\
+\x39\x2d\x33\x32\x2e\x35\x31\x2d\x36\x2e\x38\x2d\x34\x37\x2e\x37\
+\x36\x2d\x36\x2e\x30\x37\x2d\x31\x33\x2e\x34\x35\x2d\x31\x35\x2e\
+\x33\x33\x2d\x32\x34\x2e\x31\x2d\x32\x38\x2e\x32\x35\x2d\x33\x31\
+\x2e\x34\x37\x2d\x32\x2e\x39\x31\x2d\x31\x2e\x36\x36\x2d\x34\x2e\
+\x35\x39\x2d\x31\x2e\x32\x38\x2d\x37\x2e\x33\x38\x2e\x39\x37\x2d\
+\x37\x2e\x32\x38\x2c\x35\x2e\x38\x39\x2d\x38\x2e\x30\x36\x2c\x31\
+\x30\x2e\x39\x36\x2d\x34\x2e\x32\x39\x2c\x32\x30\x2e\x33\x33\x2c\
+\x31\x30\x2e\x36\x39\x2c\x32\x36\x2e\x35\x36\x2c\x31\x2e\x37\x37\
+\x2c\x35\x30\x2e\x33\x34\x2d\x31\x34\x2e\x32\x38\x2c\x37\x32\x2e\
+\x33\x32\x2d\x36\x2e\x31\x31\x2c\x38\x2e\x33\x37\x2d\x31\x32\x2e\
+\x39\x2c\x31\x31\x2e\x37\x36\x2d\x32\x33\x2e\x38\x39\x2c\x39\x2e\
+\x37\x39\x2d\x32\x36\x2e\x39\x31\x2d\x34\x2e\x38\x33\x2d\x34\x39\
+\x2e\x33\x32\x2d\x31\x36\x2e\x39\x32\x2d\x36\x37\x2e\x34\x35\x2d\
+\x33\x37\x2e\x32\x31\x2d\x2e\x33\x38\x2d\x2e\x34\x32\x2d\x2e\x35\
+\x36\x2d\x31\x2e\x30\x33\x2d\x31\x2e\x31\x39\x2d\x32\x2e\x32\x36\
+\x2c\x31\x32\x2e\x30\x34\x2c\x31\x2e\x31\x39\x2c\x32\x33\x2e\x34\
+\x38\x2e\x32\x2c\x33\x34\x2e\x36\x34\x2d\x32\x2e\x38\x39\x2c\x31\
+\x38\x2e\x34\x39\x2d\x35\x2e\x31\x33\x2c\x33\x33\x2e\x33\x2d\x31\
+\x35\x2e\x32\x34\x2c\x34\x33\x2e\x33\x37\x2d\x33\x31\x2e\x39\x37\
+\x2c\x32\x2e\x35\x2d\x34\x2e\x31\x35\x2c\x32\x2e\x31\x32\x2d\x36\
+\x2e\x38\x2d\x31\x2e\x32\x34\x2d\x31\x30\x2e\x30\x39\x2d\x37\x2e\
+\x36\x32\x2d\x37\x2e\x34\x37\x2d\x37\x2e\x34\x34\x2d\x37\x2e\x34\
+\x39\x2d\x31\x37\x2e\x34\x38\x2d\x33\x2e\x34\x37\x2d\x32\x37\x2e\
+\x35\x36\x2c\x31\x31\x2e\x30\x32\x2d\x35\x31\x2e\x37\x34\x2c\x33\
+\x2e\x33\x34\x2d\x37\x35\x2e\x30\x33\x2d\x31\x32\x2e\x36\x35\x2d\
+\x31\x30\x2e\x37\x31\x2d\x37\x2e\x33\x35\x2d\x31\x30\x2e\x37\x39\
+\x2d\x31\x36\x2e\x32\x35\x2d\x38\x2e\x38\x39\x2d\x32\x37\x2e\x31\
+\x38\x2c\x34\x2e\x37\x32\x2d\x32\x37\x2e\x31\x34\x2c\x31\x37\x2e\
+\x33\x38\x2d\x34\x39\x2e\x35\x36\x2c\x33\x38\x2e\x32\x38\x2d\x36\
+\x37\x2e\x34\x31\x2e\x35\x35\x2d\x2e\x34\x37\x2c\x31\x2e\x31\x39\
+\x2d\x2e\x38\x33\x2c\x32\x2e\x34\x2d\x31\x2e\x36\x37\x2e\x36\x34\
+\x2c\x31\x30\x2e\x33\x33\x2d\x2e\x35\x38\x2c\x31\x39\x2e\x39\x32\
+\x2c\x31\x2e\x33\x31\x2c\x32\x39\x2e\x34\x31\x2c\x34\x2e\x32\x34\
+\x2c\x32\x31\x2e\x32\x39\x2c\x31\x34\x2e\x33\x31\x2c\x33\x38\x2e\
+\x35\x34\x2c\x33\x33\x2e\x31\x39\x2c\x35\x30\x2e\x31\x32\x2c\x33\
+\x2e\x35\x2c\x32\x2e\x31\x34\x2c\x35\x2e\x37\x36\x2c\x32\x2e\x33\
+\x35\x2c\x38\x2e\x38\x37\x2d\x2e\x39\x31\x2c\x37\x2e\x37\x36\x2d\
+\x38\x2e\x31\x34\x2c\x37\x2e\x36\x39\x2d\x37\x2e\x38\x36\x2c\x33\
+\x2e\x38\x2d\x31\x38\x2e\x35\x32\x2d\x39\x2e\x37\x37\x2d\x32\x36\
+\x2e\x38\x31\x2d\x32\x2e\x35\x31\x2d\x35\x30\x2e\x35\x31\x2c\x31\
+\x33\x2e\x34\x38\x2d\x37\x32\x2e\x36\x39\x2c\x36\x2e\x34\x31\x2d\
+\x38\x2e\x38\x39\x2c\x31\x33\x2e\x33\x35\x2d\x31\x33\x2c\x32\x35\
+\x2e\x32\x38\x2d\x31\x30\x2e\x37\x32\x2c\x32\x36\x2e\x36\x31\x2c\
+\x35\x2e\x30\x39\x2c\x34\x38\x2e\x38\x2c\x31\x37\x2e\x30\x39\x2c\
+\x36\x36\x2e\x38\x37\x2c\x33\x37\x2e\x30\x36\x2e\x33\x37\x2e\x34\
+\x2e\x34\x34\x2c\x31\x2e\x30\x37\x2c\x31\x2e\x30\x31\x2c\x32\x2e\
+\x35\x34\x5a\x4d\x32\x33\x35\x2e\x31\x39\x2c\x32\x38\x39\x2e\x36\
+\x35\x63\x33\x2e\x30\x31\x2e\x30\x39\x2c\x31\x33\x2e\x36\x39\x2d\
+\x31\x30\x2e\x34\x34\x2c\x31\x33\x2e\x38\x36\x2d\x31\x33\x2e\x36\
+\x36\x2e\x31\x37\x2d\x33\x2e\x32\x31\x2d\x31\x30\x2e\x30\x33\x2d\
+\x31\x33\x2e\x37\x32\x2d\x31\x33\x2e\x35\x31\x2d\x31\x33\x2e\x39\
+\x2d\x32\x2e\x39\x34\x2d\x2e\x31\x36\x2d\x31\x33\x2e\x38\x33\x2c\
+\x31\x30\x2e\x34\x36\x2d\x31\x33\x2e\x39\x32\x2c\x31\x33\x2e\x35\
+\x37\x2d\x2e\x30\x39\x2c\x33\x2e\x31\x37\x2c\x31\x30\x2e\x33\x31\
+\x2c\x31\x33\x2e\x39\x2c\x31\x33\x2e\x35\x37\x2c\x31\x33\x2e\x39\
+\x39\x5a\x22\x2f\x3e\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\
+\x61\x73\x73\x3d\x22\x63\x6c\x73\x2d\x31\x22\x20\x64\x3d\x22\x4d\
+\x35\x33\x34\x2e\x39\x34\x2c\x33\x32\x37\x2e\x34\x34\x63\x2d\x33\
+\x2e\x30\x32\x2c\x31\x2e\x37\x38\x2d\x36\x2e\x30\x35\x2c\x33\x2e\
+\x35\x35\x2d\x39\x2e\x30\x36\x2c\x35\x2e\x33\x34\x2d\x31\x32\x2e\
+\x36\x36\x2c\x37\x2e\x35\x33\x2d\x32\x35\x2e\x33\x31\x2c\x31\x35\
+\x2e\x30\x36\x2d\x33\x37\x2e\x39\x36\x2c\x32\x32\x2e\x35\x39\x2d\
+\x2e\x31\x34\x2e\x30\x38\x2d\x2e\x32\x38\x2e\x31\x36\x2d\x2e\x35\
+\x36\x2e\x33\x31\x2c\x30\x2d\x31\x2e\x39\x33\x2d\x2e\x30\x34\x2d\
+\x31\x36\x2e\x31\x37\x2d\x2e\x30\x34\x2d\x31\x36\x2e\x31\x37\x68\
+\x2d\x31\x39\x2e\x32\x35\x76\x2d\x32\x33\x2e\x32\x39\x68\x31\x39\
+\x2e\x32\x35\x73\x2d\x2e\x30\x31\x2d\x31\x37\x2e\x30\x32\x2c\x30\
+\x2d\x31\x37\x2e\x31\x35\x63\x2e\x30\x36\x2c\x30\x2c\x38\x2e\x35\
+\x31\x2c\x34\x2e\x39\x34\x2c\x31\x32\x2e\x34\x2c\x37\x2e\x32\x36\
+\x2c\x31\x31\x2e\x37\x34\x2c\x36\x2e\x39\x38\x2c\x32\x33\x2e\x34\
+\x38\x2c\x31\x33\x2e\x39\x37\x2c\x33\x35\x2e\x32\x32\x2c\x32\x30\
+\x2e\x39\x35\x2c\x30\x2c\x2e\x30\x35\x2c\x30\x2c\x2e\x31\x2c\x30\
+\x2c\x2e\x31\x35\x5a\x22\x2f\x3e\x0a\x20\x20\x3c\x70\x61\x74\x68\
+\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x6c\x73\x2d\x31\x22\x20\x64\
+\x3d\x22\x4d\x35\x33\x34\x2e\x39\x34\x2c\x34\x36\x32\x2e\x32\x31\
+\x63\x2d\x33\x2e\x30\x32\x2c\x31\x2e\x37\x38\x2d\x36\x2e\x30\x35\
+\x2c\x33\x2e\x35\x35\x2d\x39\x2e\x30\x36\x2c\x35\x2e\x33\x34\x2d\
+\x31\x32\x2e\x36\x36\x2c\x37\x2e\x35\x33\x2d\x32\x35\x2e\x33\x31\
+\x2c\x31\x35\x2e\x30\x36\x2d\x33\x37\x2e\x39\x36\x2c\x32\x32\x2e\
+\x35\x39\x2d\x2e\x31\x34\x2e\x30\x38\x2d\x2e\x32\x38\x2e\x31\x36\
+\x2d\x2e\x35\x36\x2e\x33\x31\x2c\x30\x2d\x31\x2e\x39\x33\x2d\x2e\
+\x30\x34\x2d\x31\x36\x2e\x31\x37\x2d\x2e\x30\x34\x2d\x31\x36\x2e\
+\x31\x37\x68\x2d\x31\x39\x2e\x32\x35\x76\x2d\x32\x33\x2e\x32\x39\
+\x68\x31\x39\x2e\x32\x35\x73\x2d\x2e\x30\x31\x2d\x31\x37\x2e\x30\
+\x32\x2c\x30\x2d\x31\x37\x2e\x31\x35\x63\x2e\x30\x36\x2c\x30\x2c\
+\x38\x2e\x35\x31\x2c\x34\x2e\x39\x34\x2c\x31\x32\x2e\x34\x2c\x37\
+\x2e\x32\x36\x2c\x31\x31\x2e\x37\x34\x2c\x36\x2e\x39\x38\x2c\x32\
+\x33\x2e\x34\x38\x2c\x31\x33\x2e\x39\x37\x2c\x33\x35\x2e\x32\x32\
+\x2c\x32\x30\x2e\x39\x35\x2c\x30\x2c\x2e\x30\x35\x2c\x30\x2c\x2e\
+\x31\x2c\x30\x2c\x2e\x31\x35\x5a\x22\x2f\x3e\x0a\x20\x20\x3c\x70\
+\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x6c\x73\x2d\x31\
+\x22\x20\x64\x3d\x22\x4d\x35\x33\x34\x2e\x39\x34\x2c\x33\x39\x34\
+\x2e\x38\x32\x63\x2d\x33\x2e\x30\x32\x2c\x31\x2e\x37\x38\x2d\x36\
+\x2e\x30\x35\x2c\x33\x2e\x35\x35\x2d\x39\x2e\x30\x36\x2c\x35\x2e\
+\x33\x34\x2d\x31\x32\x2e\x36\x36\x2c\x37\x2e\x35\x33\x2d\x32\x35\
+\x2e\x33\x31\x2c\x31\x35\x2e\x30\x36\x2d\x33\x37\x2e\x39\x36\x2c\
+\x32\x32\x2e\x35\x39\x2d\x2e\x31\x34\x2e\x30\x38\x2d\x2e\x32\x38\
+\x2e\x31\x36\x2d\x2e\x35\x36\x2e\x33\x31\x2c\x30\x2d\x31\x2e\x39\
+\x33\x2d\x2e\x30\x34\x2d\x31\x36\x2e\x31\x37\x2d\x2e\x30\x34\x2d\
+\x31\x36\x2e\x31\x37\x68\x2d\x31\x39\x2e\x32\x35\x76\x2d\x32\x33\
+\x2e\x32\x39\x68\x31\x39\x2e\x32\x35\x73\x2d\x2e\x30\x31\x2d\x31\
+\x37\x2e\x30\x32\x2c\x30\x2d\x31\x37\x2e\x31\x35\x63\x2e\x30\x36\
+\x2c\x30\x2c\x38\x2e\x35\x31\x2c\x34\x2e\x39\x34\x2c\x31\x32\x2e\
+\x34\x2c\x37\x2e\x32\x36\x2c\x31\x31\x2e\x37\x34\x2c\x36\x2e\x39\
+\x38\x2c\x32\x33\x2e\x34\x38\x2c\x31\x33\x2e\x39\x37\x2c\x33\x35\
+\x2e\x32\x32\x2c\x32\x30\x2e\x39\x35\x2c\x30\x2c\x2e\x30\x35\x2c\
+\x30\x2c\x2e\x31\x2c\x30\x2c\x2e\x31\x35\x5a\x22\x2f\x3e\x0a\x3c\
+\x2f\x73\x76\x67\x3e\
\x00\x00\x07\xc6\
\x3c\
\x73\x76\x67\x20\x69\x64\x3d\x22\x4c\x61\x79\x65\x72\x5f\x31\x22\
@@ -27421,135 +27431,135 @@
\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x4d\
\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x03\x00\x00\x00\x4e\
\x00\x00\x09\x48\x00\x00\x00\x00\x00\x01\x00\x02\xc8\xc8\
-\x00\x00\x09\x66\x00\x00\x00\x00\x00\x01\x00\x02\xd1\xaa\
-\x00\x00\x09\x7a\x00\x00\x00\x00\x00\x01\x00\x02\xd7\x8b\
+\x00\x00\x09\x66\x00\x00\x00\x00\x00\x01\x00\x02\xd2\x85\
+\x00\x00\x09\x7a\x00\x00\x00\x00\x00\x01\x00\x02\xd8\x58\
\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x52\
\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x0d\x00\x00\x00\x53\
-\x00\x00\x09\x94\x00\x00\x00\x00\x00\x01\x00\x02\xe3\x1e\
-\x00\x00\x09\xbc\x00\x00\x00\x00\x00\x01\x00\x02\xe8\x19\
-\x00\x00\x09\xf4\x00\x00\x00\x00\x00\x01\x00\x02\xf0\xed\
-\x00\x00\x0a\x2c\x00\x00\x00\x00\x00\x01\x00\x02\xf9\x91\
-\x00\x00\x0a\x5c\x00\x00\x00\x00\x00\x01\x00\x03\x01\x30\
-\x00\x00\x0a\x80\x00\x00\x00\x00\x00\x01\x00\x03\x09\x0c\
-\x00\x00\x0a\xa4\x00\x00\x00\x00\x00\x01\x00\x03\x10\xc2\
-\x00\x00\x0a\xd8\x00\x00\x00\x00\x00\x01\x00\x03\x18\xd0\
-\x00\x00\x0b\x0c\x00\x00\x00\x00\x00\x01\x00\x03\x20\xb2\
-\x00\x00\x0b\x40\x00\x00\x00\x00\x00\x01\x00\x03\x28\x7c\
-\x00\x00\x0b\x7a\x00\x00\x00\x00\x00\x01\x00\x03\x2f\xe3\
-\x00\x00\x0b\x9e\x00\x00\x00\x00\x00\x01\x00\x03\x33\xa0\
-\x00\x00\x0b\xcc\x00\x00\x00\x00\x00\x01\x00\x03\x37\x79\
+\x00\x00\x09\x94\x00\x00\x00\x00\x00\x01\x00\x02\xe3\xd2\
+\x00\x00\x09\xbc\x00\x00\x00\x00\x00\x01\x00\x02\xe8\xcd\
+\x00\x00\x09\xf4\x00\x00\x00\x00\x00\x01\x00\x02\xf1\xa1\
+\x00\x00\x0a\x2c\x00\x00\x00\x00\x00\x01\x00\x02\xfa\x45\
+\x00\x00\x0a\x5c\x00\x00\x00\x00\x00\x01\x00\x03\x01\xe4\
+\x00\x00\x0a\x80\x00\x00\x00\x00\x00\x01\x00\x03\x09\xc0\
+\x00\x00\x0a\xa4\x00\x00\x00\x00\x00\x01\x00\x03\x11\x76\
+\x00\x00\x0a\xd8\x00\x00\x00\x00\x00\x01\x00\x03\x19\x84\
+\x00\x00\x0b\x0c\x00\x00\x00\x00\x00\x01\x00\x03\x21\x66\
+\x00\x00\x0b\x40\x00\x00\x00\x00\x00\x01\x00\x03\x29\x30\
+\x00\x00\x0b\x7a\x00\x00\x00\x00\x00\x01\x00\x03\x30\x97\
+\x00\x00\x0b\x9e\x00\x00\x00\x00\x00\x01\x00\x03\x34\x54\
+\x00\x00\x0b\xcc\x00\x00\x00\x00\x00\x01\x00\x03\x38\x2d\
\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x61\
\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x08\x00\x00\x00\x62\
-\x00\x00\x0b\xf2\x00\x00\x00\x00\x00\x01\x00\x03\x3d\x82\
-\x00\x00\x0c\x1e\x00\x00\x00\x00\x00\x01\x00\x03\x60\x06\
-\x00\x00\x0c\x4c\x00\x00\x00\x00\x00\x01\x00\x03\x65\xf3\
-\x00\x00\x0c\x76\x00\x00\x00\x00\x00\x01\x00\x03\x68\x13\
-\x00\x00\x0c\x9e\x00\x00\x00\x00\x00\x01\x00\x03\x70\xab\
-\x00\x00\x0c\xb8\x00\x00\x00\x00\x00\x01\x00\x03\x7f\xf4\
-\x00\x00\x0c\xe4\x00\x00\x00\x00\x00\x01\x00\x03\x86\x72\
-\x00\x00\x0d\x0c\x00\x00\x00\x00\x00\x01\x00\x03\x90\xec\
+\x00\x00\x0b\xf2\x00\x00\x00\x00\x00\x01\x00\x03\x3e\x36\
+\x00\x00\x0c\x1e\x00\x00\x00\x00\x00\x01\x00\x03\x60\xba\
+\x00\x00\x0c\x4c\x00\x00\x00\x00\x00\x01\x00\x03\x66\xa7\
+\x00\x00\x0c\x76\x00\x00\x00\x00\x00\x01\x00\x03\x68\xc7\
+\x00\x00\x0c\x9e\x00\x00\x00\x00\x00\x01\x00\x03\x71\x5f\
+\x00\x00\x0c\xb8\x00\x00\x00\x00\x00\x01\x00\x03\x80\xa8\
+\x00\x00\x0c\xe4\x00\x00\x00\x00\x00\x01\x00\x03\x87\x26\
+\x00\x00\x0d\x0c\x00\x00\x00\x00\x00\x01\x00\x03\x91\xa0\
\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x6b\
\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x0c\x00\x00\x00\x6c\
-\x00\x00\x0d\x42\x00\x00\x00\x00\x00\x01\x00\x03\x9a\x33\
-\x00\x00\x0d\x70\x00\x00\x00\x00\x00\x01\x00\x03\x9c\xda\
-\x00\x00\x0d\x9e\x00\x01\x00\x00\x00\x01\x00\x03\xa9\x57\
-\x00\x00\x0d\xca\x00\x00\x00\x00\x00\x01\x00\x03\xd6\xd6\
-\x00\x00\x0d\xea\x00\x00\x00\x00\x00\x01\x00\x03\xdb\x8d\
-\x00\x00\x0e\x1c\x00\x01\x00\x00\x00\x01\x00\x04\x34\x86\
-\x00\x00\x0e\x4e\x00\x00\x00\x00\x00\x01\x00\x04\x69\x20\
-\x00\x00\x0e\x68\x00\x00\x00\x00\x00\x01\x00\x04\x6e\x6a\
-\x00\x00\x0e\x82\x00\x00\x00\x00\x00\x01\x00\x04\x73\xf9\
-\x00\x00\x0e\x9c\x00\x00\x00\x00\x00\x01\x00\x04\x79\x62\
-\x00\x00\x0e\xb4\x00\x00\x00\x00\x00\x01\x00\x04\x85\x40\
-\x00\x00\x0e\xd2\x00\x00\x00\x00\x00\x01\x00\x04\x8b\x44\
+\x00\x00\x0d\x42\x00\x00\x00\x00\x00\x01\x00\x03\x9a\xe7\
+\x00\x00\x0d\x70\x00\x00\x00\x00\x00\x01\x00\x03\x9d\x8e\
+\x00\x00\x0d\x9e\x00\x01\x00\x00\x00\x01\x00\x03\xaa\x0b\
+\x00\x00\x0d\xca\x00\x00\x00\x00\x00\x01\x00\x03\xd7\x8a\
+\x00\x00\x0d\xea\x00\x00\x00\x00\x00\x01\x00\x03\xdc\x41\
+\x00\x00\x0e\x1c\x00\x01\x00\x00\x00\x01\x00\x04\x35\x3a\
+\x00\x00\x0e\x4e\x00\x00\x00\x00\x00\x01\x00\x04\x69\xd4\
+\x00\x00\x0e\x68\x00\x00\x00\x00\x00\x01\x00\x04\x6f\x1e\
+\x00\x00\x0e\x82\x00\x00\x00\x00\x00\x01\x00\x04\x74\xad\
+\x00\x00\x0e\x9c\x00\x00\x00\x00\x00\x01\x00\x04\x7a\x16\
+\x00\x00\x0e\xb4\x00\x00\x00\x00\x00\x01\x00\x04\x85\xf4\
+\x00\x00\x0e\xd2\x00\x00\x00\x00\x00\x01\x00\x04\x8b\xf8\
\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x79\
\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x04\x00\x00\x00\x7a\
-\x00\x00\x0e\xfa\x00\x00\x00\x00\x00\x01\x00\x04\x90\x79\
-\x00\x00\x0f\x0e\x00\x00\x00\x00\x00\x01\x00\x04\x96\x76\
-\x00\x00\x0f\x20\x00\x00\x00\x00\x00\x01\x00\x04\x97\xfc\
-\x00\x00\x0f\x32\x00\x00\x00\x00\x00\x01\x00\x04\x9d\xf6\
+\x00\x00\x0e\xfa\x00\x00\x00\x00\x00\x01\x00\x04\x91\x2d\
+\x00\x00\x0f\x0e\x00\x00\x00\x00\x00\x01\x00\x04\x97\x2a\
+\x00\x00\x0f\x20\x00\x00\x00\x00\x00\x01\x00\x04\x98\xb0\
+\x00\x00\x0f\x32\x00\x00\x00\x00\x00\x01\x00\x04\x9e\xaa\
\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x7f\
\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x02\x00\x00\x00\x80\
-\x00\x00\x0f\x46\x00\x00\x00\x00\x00\x01\x00\x04\xa0\x4c\
-\x00\x00\x0f\x72\x00\x00\x00\x00\x00\x01\x00\x04\xa7\x33\
+\x00\x00\x0f\x46\x00\x00\x00\x00\x00\x01\x00\x04\xa1\x00\
+\x00\x00\x0f\x72\x00\x00\x00\x00\x00\x01\x00\x04\xa7\xe7\
\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x83\
\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x04\x00\x00\x00\x84\
\x00\x00\x00\x46\x00\x02\x00\x00\x00\x0c\x00\x00\x00\x88\
-\x00\x00\x0f\x96\x00\x01\x00\x00\x00\x01\x00\x04\xb0\x97\
-\x00\x00\x0f\xba\x00\x00\x00\x00\x00\x01\x00\x04\xbb\xe8\
-\x00\x00\x0f\xdc\x00\x00\x00\x00\x00\x01\x00\x04\xc3\x8d\
-\x00\x00\x0f\xf8\x00\x00\x00\x00\x00\x01\x00\x04\xd4\x8d\
-\x00\x00\x10\x18\x00\x00\x00\x00\x00\x01\x00\x04\xda\x26\
-\x00\x00\x10\x38\x00\x00\x00\x00\x00\x01\x00\x04\xe0\x4d\
-\x00\x00\x10\x58\x00\x00\x00\x00\x00\x01\x00\x04\xe4\xa2\
-\x00\x00\x10\x78\x00\x00\x00\x00\x00\x01\x00\x04\xea\x26\
-\x00\x00\x10\x98\x00\x00\x00\x00\x00\x01\x00\x04\xef\xbf\
-\x00\x00\x10\xca\x00\x00\x00\x00\x00\x01\x00\x04\xf4\xc1\
-\x00\x00\x10\xea\x00\x00\x00\x00\x00\x01\x00\x04\xfa\x5a\
-\x00\x00\x11\x1e\x00\x00\x00\x00\x00\x01\x00\x05\x04\xce\
-\x00\x00\x11\x52\x00\x00\x00\x00\x00\x01\x00\x05\x0f\x48\
-\x00\x00\x11\x86\x00\x00\x00\x00\x00\x01\x00\x05\x19\xa7\
-\x00\x00\x11\xba\x00\x00\x00\x00\x00\x01\x00\x05\x24\xf2\
+\x00\x00\x0f\x96\x00\x01\x00\x00\x00\x01\x00\x04\xb1\x4b\
+\x00\x00\x0f\xba\x00\x00\x00\x00\x00\x01\x00\x04\xbc\x9c\
+\x00\x00\x0f\xdc\x00\x00\x00\x00\x00\x01\x00\x04\xc4\x41\
+\x00\x00\x0f\xf8\x00\x00\x00\x00\x00\x01\x00\x04\xd5\x41\
+\x00\x00\x10\x18\x00\x00\x00\x00\x00\x01\x00\x04\xda\xda\
+\x00\x00\x10\x38\x00\x00\x00\x00\x00\x01\x00\x04\xe1\x01\
+\x00\x00\x10\x58\x00\x00\x00\x00\x00\x01\x00\x04\xe5\x56\
+\x00\x00\x10\x78\x00\x00\x00\x00\x00\x01\x00\x04\xea\xda\
+\x00\x00\x10\x98\x00\x00\x00\x00\x00\x01\x00\x04\xf0\x73\
+\x00\x00\x10\xca\x00\x00\x00\x00\x00\x01\x00\x04\xf5\x75\
+\x00\x00\x10\xea\x00\x00\x00\x00\x00\x01\x00\x04\xfb\x0e\
+\x00\x00\x11\x1e\x00\x00\x00\x00\x00\x01\x00\x05\x05\x82\
+\x00\x00\x11\x52\x00\x00\x00\x00\x00\x01\x00\x05\x0f\xfc\
+\x00\x00\x11\x86\x00\x00\x00\x00\x00\x01\x00\x05\x1a\x5b\
+\x00\x00\x11\xba\x00\x00\x00\x00\x00\x01\x00\x05\x25\xa6\
\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x95\
\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x0a\x00\x00\x00\x96\
-\x00\x00\x11\xee\x00\x00\x00\x00\x00\x01\x00\x05\x2f\x66\
-\x00\x00\x12\x1e\x00\x00\x00\x00\x00\x01\x00\x05\x38\xfc\
-\x00\x00\x12\x4e\x00\x00\x00\x00\x00\x01\x00\x05\x44\xd8\
-\x00\x00\x12\x72\x00\x00\x00\x00\x00\x01\x00\x05\x4b\x1c\
-\x00\x00\x12\x9c\x00\x00\x00\x00\x00\x01\x00\x05\x52\xa5\
-\x00\x00\x12\xc8\x00\x00\x00\x00\x00\x01\x00\x05\x59\x03\
-\x00\x00\x12\xfe\x00\x00\x00\x00\x00\x01\x00\x05\x60\xf2\
-\x00\x00\x09\x66\x00\x00\x00\x00\x00\x01\x00\x05\x6e\x95\
-\x00\x00\x09\x7a\x00\x00\x00\x00\x00\x01\x00\x05\x74\x76\
-\x00\x00\x13\x1c\x00\x00\x00\x00\x00\x01\x00\x05\x80\x09\
+\x00\x00\x11\xee\x00\x00\x00\x00\x00\x01\x00\x05\x30\x1a\
+\x00\x00\x12\x1e\x00\x00\x00\x00\x00\x01\x00\x05\x39\xb0\
+\x00\x00\x12\x4e\x00\x00\x00\x00\x00\x01\x00\x05\x45\x8c\
+\x00\x00\x12\x72\x00\x00\x00\x00\x00\x01\x00\x05\x4b\xd0\
+\x00\x00\x12\x9c\x00\x00\x00\x00\x00\x01\x00\x05\x53\x59\
+\x00\x00\x12\xc8\x00\x00\x00\x00\x00\x01\x00\x05\x59\xb7\
+\x00\x00\x12\xfe\x00\x00\x00\x00\x00\x01\x00\x05\x61\xa6\
+\x00\x00\x09\x66\x00\x00\x00\x00\x00\x01\x00\x05\x6f\x49\
+\x00\x00\x09\x7a\x00\x00\x00\x00\x00\x01\x00\x05\x75\x1c\
+\x00\x00\x13\x1c\x00\x00\x00\x00\x00\x01\x00\x05\x80\x96\
\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\xa1\
\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x01\x00\x00\x00\xa2\
-\x00\x00\x13\x44\x00\x00\x00\x00\x00\x01\x00\x05\x87\xd3\
+\x00\x00\x13\x44\x00\x00\x00\x00\x00\x01\x00\x05\x88\x60\
\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\xa4\
\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x2b\x00\x00\x00\xa5\
-\x00\x00\x13\x64\x00\x00\x00\x00\x00\x01\x00\x05\x8c\x99\
-\x00\x00\x13\x7a\x00\x00\x00\x00\x00\x01\x00\x05\x94\x4d\
-\x00\x00\x13\x92\x00\x00\x00\x00\x00\x01\x00\x05\x96\x72\
-\x00\x00\x13\xca\x00\x00\x00\x00\x00\x01\x00\x05\x97\xf2\
-\x00\x00\x13\xe6\x00\x00\x00\x00\x00\x01\x00\x05\x9f\x9f\
-\x00\x00\x14\x06\x00\x00\x00\x00\x00\x01\x00\x05\xa4\x74\
-\x00\x00\x14\x1c\x00\x00\x00\x00\x00\x01\x00\x05\xa5\x64\
-\x00\x00\x14\x42\x00\x00\x00\x00\x00\x01\x00\x05\xa8\x90\
-\x00\x00\x14\x58\x00\x00\x00\x00\x00\x01\x00\x05\xac\xb9\
-\x00\x00\x14\x7e\x00\x00\x00\x00\x00\x01\x00\x05\xb2\xf0\
-\x00\x00\x14\x98\x00\x00\x00\x00\x00\x01\x00\x05\xc7\xfd\
-\x00\x00\x14\xba\x00\x00\x00\x00\x00\x01\x00\x05\xcc\xed\
-\x00\x00\x14\xd0\x00\x00\x00\x00\x00\x01\x00\x05\xcf\xec\
-\x00\x00\x14\xe6\x00\x00\x00\x00\x00\x01\x00\x05\xd5\xf6\
-\x00\x00\x15\x18\x00\x00\x00\x00\x00\x01\x00\x05\xd9\x45\
-\x00\x00\x15\x30\x00\x00\x00\x00\x00\x01\x00\x05\xdc\x37\
-\x00\x00\x15\x46\x00\x00\x00\x00\x00\x01\x00\x05\xe2\x2e\
-\x00\x00\x15\x5a\x00\x00\x00\x00\x00\x01\x00\x05\xe4\x3f\
-\x00\x00\x15\x72\x00\x00\x00\x00\x00\x01\x00\x05\xe8\x02\
-\x00\x00\x15\x98\x00\x00\x00\x00\x00\x01\x00\x05\xf1\xb6\
-\x00\x00\x15\xb6\x00\x00\x00\x00\x00\x01\x00\x05\xf7\x7e\
-\x00\x00\x15\xe0\x00\x00\x00\x00\x00\x01\x00\x05\xfa\xcc\
-\x00\x00\x16\x02\x00\x00\x00\x00\x00\x01\x00\x05\xfe\x5a\
-\x00\x00\x16\x28\x00\x00\x00\x00\x00\x01\x00\x06\x02\xfb\
-\x00\x00\x16\x3c\x00\x00\x00\x00\x00\x01\x00\x06\x0c\xcd\
-\x00\x00\x16\x68\x00\x00\x00\x00\x00\x01\x00\x06\x12\x17\
-\x00\x00\x16\x90\x00\x00\x00\x00\x00\x01\x00\x06\x18\x1e\
-\x00\x00\x16\xa6\x00\x00\x00\x00\x00\x01\x00\x06\x19\x02\
-\x00\x00\x16\xd2\x00\x00\x00\x00\x00\x01\x00\x06\x1b\x4b\
-\x00\x00\x16\xe8\x00\x00\x00\x00\x00\x01\x00\x06\x21\xfb\
-\x00\x00\x17\x04\x00\x00\x00\x00\x00\x01\x00\x06\x25\x3f\
-\x00\x00\x17\x38\x00\x00\x00\x00\x00\x01\x00\x06\x2c\x25\
-\x00\x00\x17\x50\x00\x00\x00\x00\x00\x01\x00\x06\x2d\x51\
-\x00\x00\x17\x6a\x00\x00\x00\x00\x00\x01\x00\x06\x33\x12\
-\x00\x00\x17\x8c\x00\x00\x00\x00\x00\x01\x00\x06\x34\x34\
-\x00\x00\x17\xaa\x00\x00\x00\x00\x00\x01\x00\x06\x3a\x27\
-\x00\x00\x17\xca\x00\x00\x00\x00\x00\x01\x00\x06\x3d\x2b\
-\x00\x00\x17\xec\x00\x00\x00\x00\x00\x01\x00\x06\x3e\x4c\
-\x00\x00\x18\x0c\x00\x00\x00\x00\x00\x01\x00\x06\x41\x20\
-\x00\x00\x18\x3a\x00\x00\x00\x00\x00\x01\x00\x06\x49\x8c\
-\x00\x00\x18\x5e\x00\x00\x00\x00\x00\x01\x00\x06\x51\x4c\
-\x00\x00\x18\x82\x00\x00\x00\x00\x00\x01\x00\x06\x56\x67\
-\x00\x00\x18\xaa\x00\x00\x00\x00\x00\x01\x00\x06\x57\xba\
+\x00\x00\x13\x64\x00\x00\x00\x00\x00\x01\x00\x05\x8d\x26\
+\x00\x00\x13\x7a\x00\x00\x00\x00\x00\x01\x00\x05\x94\xda\
+\x00\x00\x13\x92\x00\x00\x00\x00\x00\x01\x00\x05\x96\xff\
+\x00\x00\x13\xca\x00\x00\x00\x00\x00\x01\x00\x05\x98\x7f\
+\x00\x00\x13\xe6\x00\x00\x00\x00\x00\x01\x00\x05\xa0\x2c\
+\x00\x00\x14\x06\x00\x00\x00\x00\x00\x01\x00\x05\xa5\x01\
+\x00\x00\x14\x1c\x00\x00\x00\x00\x00\x01\x00\x05\xa5\xf1\
+\x00\x00\x14\x42\x00\x00\x00\x00\x00\x01\x00\x05\xa9\x1d\
+\x00\x00\x14\x58\x00\x00\x00\x00\x00\x01\x00\x05\xad\x46\
+\x00\x00\x14\x7e\x00\x00\x00\x00\x00\x01\x00\x05\xb3\x7d\
+\x00\x00\x14\x98\x00\x00\x00\x00\x00\x01\x00\x05\xc8\x8a\
+\x00\x00\x14\xba\x00\x00\x00\x00\x00\x01\x00\x05\xcd\x7a\
+\x00\x00\x14\xd0\x00\x00\x00\x00\x00\x01\x00\x05\xd0\x79\
+\x00\x00\x14\xe6\x00\x00\x00\x00\x00\x01\x00\x05\xd6\x83\
+\x00\x00\x15\x18\x00\x00\x00\x00\x00\x01\x00\x05\xd9\xd2\
+\x00\x00\x15\x30\x00\x00\x00\x00\x00\x01\x00\x05\xdc\xc4\
+\x00\x00\x15\x46\x00\x00\x00\x00\x00\x01\x00\x05\xe2\xbb\
+\x00\x00\x15\x5a\x00\x00\x00\x00\x00\x01\x00\x05\xe4\xcc\
+\x00\x00\x15\x72\x00\x00\x00\x00\x00\x01\x00\x05\xe8\x8f\
+\x00\x00\x15\x98\x00\x00\x00\x00\x00\x01\x00\x05\xf2\x43\
+\x00\x00\x15\xb6\x00\x00\x00\x00\x00\x01\x00\x05\xf8\x0b\
+\x00\x00\x15\xe0\x00\x00\x00\x00\x00\x01\x00\x05\xfb\x59\
+\x00\x00\x16\x02\x00\x00\x00\x00\x00\x01\x00\x05\xfe\xe7\
+\x00\x00\x16\x28\x00\x00\x00\x00\x00\x01\x00\x06\x03\x88\
+\x00\x00\x16\x3c\x00\x00\x00\x00\x00\x01\x00\x06\x0d\x5a\
+\x00\x00\x16\x68\x00\x00\x00\x00\x00\x01\x00\x06\x12\xa4\
+\x00\x00\x16\x90\x00\x00\x00\x00\x00\x01\x00\x06\x18\xab\
+\x00\x00\x16\xa6\x00\x00\x00\x00\x00\x01\x00\x06\x19\x8f\
+\x00\x00\x16\xd2\x00\x00\x00\x00\x00\x01\x00\x06\x1b\xd8\
+\x00\x00\x16\xe8\x00\x00\x00\x00\x00\x01\x00\x06\x22\x88\
+\x00\x00\x17\x04\x00\x00\x00\x00\x00\x01\x00\x06\x25\xcc\
+\x00\x00\x17\x38\x00\x00\x00\x00\x00\x01\x00\x06\x2c\xb2\
+\x00\x00\x17\x50\x00\x00\x00\x00\x00\x01\x00\x06\x2d\xde\
+\x00\x00\x17\x6a\x00\x00\x00\x00\x00\x01\x00\x06\x33\x9f\
+\x00\x00\x17\x8c\x00\x00\x00\x00\x00\x01\x00\x06\x34\xc1\
+\x00\x00\x17\xaa\x00\x00\x00\x00\x00\x01\x00\x06\x3a\xb4\
+\x00\x00\x17\xca\x00\x00\x00\x00\x00\x01\x00\x06\x3d\xb8\
+\x00\x00\x17\xec\x00\x00\x00\x00\x00\x01\x00\x06\x3e\xd9\
+\x00\x00\x18\x0c\x00\x00\x00\x00\x00\x01\x00\x06\x41\xad\
+\x00\x00\x18\x3a\x00\x00\x00\x00\x00\x01\x00\x06\x4a\x19\
+\x00\x00\x18\x5e\x00\x00\x00\x00\x00\x01\x00\x06\x51\xd9\
+\x00\x00\x18\x82\x00\x00\x00\x00\x00\x01\x00\x06\x56\xf4\
+\x00\x00\x18\xaa\x00\x00\x00\x00\x00\x01\x00\x06\x58\x47\
"
qt_resource_struct_v2 = b"\
@@ -27592,87 +27602,87 @@
\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x0d\x00\x00\x00\x13\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x01\xc8\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
\x00\x00\x01\xe8\x00\x00\x00\x00\x00\x01\x00\x00\x08\x66\
-\x00\x00\x01\x98\x55\x96\x0d\x2b\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
\x00\x00\x02\x10\x00\x00\x00\x00\x00\x01\x00\x00\x09\x52\
-\x00\x00\x01\x98\x55\x96\x0d\x2b\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
\x00\x00\x02\x38\x00\x00\x00\x00\x00\x01\x00\x00\x0a\x35\
-\x00\x00\x01\x98\x55\x96\x0d\x2b\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
\x00\x00\x02\x60\x00\x00\x00\x00\x00\x01\x00\x00\x0b\x18\
-\x00\x00\x01\x98\x55\x96\x0d\x2b\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
\x00\x00\x02\x88\x00\x00\x00\x00\x00\x01\x00\x00\x0c\x06\
-\x00\x00\x01\x98\x55\x96\x0d\x35\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
\x00\x00\x02\xbc\x00\x00\x00\x00\x00\x01\x00\x00\x14\x42\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
\x00\x00\x02\xe8\x00\x00\x00\x00\x00\x01\x00\x00\x1d\xf7\
-\x00\x00\x01\x98\x55\x96\x0d\x2b\
+\x00\x00\x01\x9a\x72\xe1\x94\x4b\
\x00\x00\x03\x12\x00\x00\x00\x00\x00\x01\x00\x00\x22\x41\
-\x00\x00\x01\x98\x55\x96\x0d\x2b\
+\x00\x00\x01\x9a\x72\xe1\x94\x4b\
\x00\x00\x03\x32\x00\x00\x00\x00\x00\x01\x00\x00\x26\x90\
-\x00\x00\x01\x98\x55\x96\x0d\x2b\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
\x00\x00\x03\x4e\x00\x00\x00\x00\x00\x01\x00\x00\x2c\x6e\
-\x00\x00\x01\x98\x55\x96\x0d\x35\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
\x00\x00\x03\x6a\x00\x00\x00\x00\x00\x01\x00\x00\x30\xca\
-\x00\x00\x01\x98\x55\x96\x0d\x2b\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
\x00\x00\x03\x92\x00\x00\x00\x00\x00\x01\x00\x00\x32\xb1\
-\x00\x00\x01\x98\x55\x96\x0d\x35\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x21\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x06\x00\x00\x00\x22\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x03\xb2\x00\x00\x00\x00\x00\x01\x00\x00\x3a\x55\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
+\x00\x00\x01\x9a\x72\xe1\x94\x57\
\x00\x00\x03\xd6\x00\x00\x00\x00\x00\x01\x00\x00\x3c\xcf\
-\x00\x00\x01\x98\x55\x96\x0d\x35\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
\x00\x00\x03\xf8\x00\x00\x00\x00\x00\x01\x00\x00\x3f\x43\
-\x00\x00\x01\x9c\xd4\xa5\xd3\x9b\
+\x00\x00\x01\x9c\xe2\x99\x3f\x98\
\x00\x00\x04\x1a\x00\x00\x00\x00\x00\x01\x00\x00\x40\xdd\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
+\x00\x00\x01\x9a\x72\xe1\x94\x5b\
\x00\x00\x04\x38\x00\x00\x00\x00\x00\x01\x00\x00\x43\x59\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
\x00\x00\x04\x5a\x00\x00\x00\x00\x00\x01\x00\x00\x45\xd5\
-\x00\x00\x01\x9c\xd4\xa5\xd3\x9b\
+\x00\x00\x01\x9c\xe2\x99\x3f\x98\
\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x29\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x0a\x00\x00\x00\x2a\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x04\x7e\x00\x00\x00\x00\x00\x01\x00\x00\x47\x6d\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
\x00\x00\x04\x98\x00\x00\x00\x00\x00\x01\x00\x00\x48\x6e\
-\x00\x00\x01\x98\x55\x96\x0d\x35\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
\x00\x00\x04\xc8\x00\x00\x00\x00\x00\x01\x00\x00\x4d\x86\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
+\x00\x00\x01\x9a\x72\xe1\x94\x57\
\x00\x00\x04\xf8\x00\x00\x00\x00\x00\x01\x00\x00\x59\x48\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
\x00\x00\x05\x22\x00\x00\x00\x00\x00\x01\x00\x00\x5b\xb8\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
+\x00\x00\x01\x9a\x72\xe1\x94\x57\
\x00\x00\x05\x48\x00\x00\x00\x00\x00\x01\x00\x00\x61\x50\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
+\x00\x00\x01\x9a\x72\xe1\x94\x5b\
\x00\x00\x05\x6c\x00\x00\x00\x00\x00\x01\x00\x00\x65\xac\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
\x00\x00\x05\xa6\x00\x00\x00\x00\x00\x01\x00\x00\x6c\x39\
-\x00\x00\x01\x98\x55\x96\x0d\x35\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
\x00\x00\x05\xc2\x00\x00\x00\x00\x00\x01\x00\x00\x6f\x35\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
\x00\x00\x05\xea\x00\x00\x00\x00\x00\x01\x00\x00\x70\x33\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x35\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x02\x00\x00\x00\x36\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x06\x1c\x00\x01\x00\x00\x00\x01\x00\x00\x7a\x9d\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
+\x00\x00\x01\x9a\x72\xe1\x94\x57\
\x00\x00\x06\x2e\x00\x00\x00\x00\x00\x01\x00\x02\x3d\xf2\
-\x00\x00\x01\x98\x55\x96\x0d\x35\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x39\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x02\x00\x00\x00\x3a\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x06\x42\x00\x00\x00\x00\x00\x01\x00\x02\x42\x78\
-\x00\x00\x01\x9b\xc6\xe6\xb1\x6c\
+\x00\x00\x01\x9c\xe2\x99\x3f\x9c\
\x00\x00\x06\x70\x00\x00\x00\x00\x00\x01\x00\x02\x44\xa3\
-\x00\x00\x01\x9b\xc6\xe6\xb1\x6c\
+\x00\x00\x01\x9c\xe2\x99\x3f\x9c\
\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x02\x00\x00\x00\x3d\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x06\x00\x00\x00\x46\
@@ -27680,295 +27690,295 @@
\x00\x00\x06\xa0\x00\x02\x00\x00\x00\x07\x00\x00\x00\x3f\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x06\xb2\x00\x00\x00\x00\x00\x01\x00\x02\x46\xa9\
-\x00\x00\x01\x98\x55\x96\x0d\x7b\
+\x00\x00\x01\x9a\x72\xe1\x95\x8f\
\x00\x00\x06\xe6\x00\x00\x00\x00\x00\x01\x00\x02\x51\x0d\
-\x00\x00\x01\x98\x55\x96\x0d\x7b\
+\x00\x00\x01\x9a\x72\xe1\x95\x93\
\x00\x00\x07\x1a\x00\x00\x00\x00\x00\x01\x00\x02\x5b\x36\
-\x00\x00\x01\x98\x55\x96\x0d\x7b\
+\x00\x00\x01\x9a\x72\xe1\x95\x8f\
\x00\x00\x07\x54\x00\x00\x00\x00\x00\x01\x00\x02\x65\x8e\
-\x00\x00\x01\x98\x55\x96\x0d\x7b\
+\x00\x00\x01\x9a\x72\xe1\x95\x93\
\x00\x00\x07\x88\x00\x00\x00\x00\x00\x01\x00\x02\x6f\xa6\
-\x00\x00\x01\x98\x55\x96\x0d\x7b\
+\x00\x00\x01\x9a\x72\xe1\x95\x8f\
\x00\x00\x07\xbe\x00\x00\x00\x00\x00\x01\x00\x02\x79\xcf\
-\x00\x00\x01\x98\x55\x96\x0d\x7b\
+\x00\x00\x01\x9a\x72\xe1\x95\x93\
\x00\x00\x07\xf6\x00\x00\x00\x00\x00\x01\x00\x02\x83\xe5\
-\x00\x00\x01\x98\x55\x96\x0d\x7b\
+\x00\x00\x01\x9a\x72\xe1\x95\x93\
\x00\x00\x08\x2c\x00\x00\x00\x00\x00\x01\x00\x02\x8e\x4b\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
\x00\x00\x08\x54\x00\x00\x00\x00\x00\x01\x00\x02\x98\x7a\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
+\x00\x00\x01\x9a\x72\xe1\x94\x5b\
\x00\x00\x08\x80\x00\x00\x00\x00\x00\x01\x00\x02\xa2\xc1\
-\x00\x00\x01\x98\x55\x96\x0d\x35\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
\x00\x00\x08\xbc\x00\x00\x00\x00\x00\x01\x00\x02\xa4\xc2\
-\x00\x00\x01\x98\x55\x96\x0d\x35\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
\x00\x00\x08\xe8\x00\x00\x00\x00\x00\x01\x00\x02\xc0\x07\
-\x00\x00\x01\x98\x55\x96\x0d\x35\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
\x00\x00\x09\x14\x00\x00\x00\x00\x00\x01\x00\x02\xc5\x50\
-\x00\x00\x01\x98\x55\x96\x0d\x35\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x4d\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x03\x00\x00\x00\x4e\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x09\x48\x00\x00\x00\x00\x00\x01\x00\x02\xc8\xc8\
-\x00\x00\x01\x98\x55\x96\x0d\x35\
-\x00\x00\x09\x66\x00\x00\x00\x00\x00\x01\x00\x02\xd1\xaa\
-\x00\x00\x01\x9c\xd4\xa5\xd3\x9b\
-\x00\x00\x09\x7a\x00\x00\x00\x00\x00\x01\x00\x02\xd7\x8b\
-\x00\x00\x01\x9c\xd4\xa5\xd3\x9b\
+\x00\x00\x01\x9d\x06\xcd\x70\x83\
+\x00\x00\x09\x66\x00\x00\x00\x00\x00\x01\x00\x02\xd2\x85\
+\x00\x00\x01\x9d\x06\xcd\x70\x8f\
+\x00\x00\x09\x7a\x00\x00\x00\x00\x00\x01\x00\x02\xd8\x58\
+\x00\x00\x01\x9d\x06\xcd\x70\xa3\
\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x52\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x0d\x00\x00\x00\x53\
\x00\x00\x00\x00\x00\x00\x00\x00\
-\x00\x00\x09\x94\x00\x00\x00\x00\x00\x01\x00\x02\xe3\x1e\
-\x00\x00\x01\x98\x55\x96\x0d\x2b\
-\x00\x00\x09\xbc\x00\x00\x00\x00\x00\x01\x00\x02\xe8\x19\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
-\x00\x00\x09\xf4\x00\x00\x00\x00\x00\x01\x00\x02\xf0\xed\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
-\x00\x00\x0a\x2c\x00\x00\x00\x00\x00\x01\x00\x02\xf9\x91\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
-\x00\x00\x0a\x5c\x00\x00\x00\x00\x00\x01\x00\x03\x01\x30\
-\x00\x00\x01\x98\x55\x96\x0d\x35\
-\x00\x00\x0a\x80\x00\x00\x00\x00\x00\x01\x00\x03\x09\x0c\
-\x00\x00\x01\x98\x55\x96\x0d\x35\
-\x00\x00\x0a\xa4\x00\x00\x00\x00\x00\x01\x00\x03\x10\xc2\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
-\x00\x00\x0a\xd8\x00\x00\x00\x00\x00\x01\x00\x03\x18\xd0\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
-\x00\x00\x0b\x0c\x00\x00\x00\x00\x00\x01\x00\x03\x20\xb2\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
-\x00\x00\x0b\x40\x00\x00\x00\x00\x00\x01\x00\x03\x28\x7c\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
-\x00\x00\x0b\x7a\x00\x00\x00\x00\x00\x01\x00\x03\x2f\xe3\
-\x00\x00\x01\x98\x55\x96\x0d\x2b\
-\x00\x00\x0b\x9e\x00\x00\x00\x00\x00\x01\x00\x03\x33\xa0\
-\x00\x00\x01\x98\x55\x96\x0d\x2b\
-\x00\x00\x0b\xcc\x00\x00\x00\x00\x00\x01\x00\x03\x37\x79\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
+\x00\x00\x09\x94\x00\x00\x00\x00\x00\x01\x00\x02\xe3\xd2\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
+\x00\x00\x09\xbc\x00\x00\x00\x00\x00\x01\x00\x02\xe8\xcd\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
+\x00\x00\x09\xf4\x00\x00\x00\x00\x00\x01\x00\x02\xf1\xa1\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
+\x00\x00\x0a\x2c\x00\x00\x00\x00\x00\x01\x00\x02\xfa\x45\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
+\x00\x00\x0a\x5c\x00\x00\x00\x00\x00\x01\x00\x03\x01\xe4\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
+\x00\x00\x0a\x80\x00\x00\x00\x00\x00\x01\x00\x03\x09\xc0\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
+\x00\x00\x0a\xa4\x00\x00\x00\x00\x00\x01\x00\x03\x11\x76\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
+\x00\x00\x0a\xd8\x00\x00\x00\x00\x00\x01\x00\x03\x19\x84\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
+\x00\x00\x0b\x0c\x00\x00\x00\x00\x00\x01\x00\x03\x21\x66\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
+\x00\x00\x0b\x40\x00\x00\x00\x00\x00\x01\x00\x03\x29\x30\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
+\x00\x00\x0b\x7a\x00\x00\x00\x00\x00\x01\x00\x03\x30\x97\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
+\x00\x00\x0b\x9e\x00\x00\x00\x00\x00\x01\x00\x03\x34\x54\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
+\x00\x00\x0b\xcc\x00\x00\x00\x00\x00\x01\x00\x03\x38\x2d\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x61\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x08\x00\x00\x00\x62\
\x00\x00\x00\x00\x00\x00\x00\x00\
-\x00\x00\x0b\xf2\x00\x00\x00\x00\x00\x01\x00\x03\x3d\x82\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
-\x00\x00\x0c\x1e\x00\x00\x00\x00\x00\x01\x00\x03\x60\x06\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
-\x00\x00\x0c\x4c\x00\x00\x00\x00\x00\x01\x00\x03\x65\xf3\
-\x00\x00\x01\x98\x55\x96\x0d\x35\
-\x00\x00\x0c\x76\x00\x00\x00\x00\x00\x01\x00\x03\x68\x13\
-\x00\x00\x01\x98\x55\x96\x0d\x35\
-\x00\x00\x0c\x9e\x00\x00\x00\x00\x00\x01\x00\x03\x70\xab\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
-\x00\x00\x0c\xb8\x00\x00\x00\x00\x00\x01\x00\x03\x7f\xf4\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
-\x00\x00\x0c\xe4\x00\x00\x00\x00\x00\x01\x00\x03\x86\x72\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
-\x00\x00\x0d\x0c\x00\x00\x00\x00\x00\x01\x00\x03\x90\xec\
-\x00\x00\x01\x9a\x27\x73\xa6\xfc\
+\x00\x00\x0b\xf2\x00\x00\x00\x00\x00\x01\x00\x03\x3e\x36\
+\x00\x00\x01\x9a\x72\xe1\x94\x57\
+\x00\x00\x0c\x1e\x00\x00\x00\x00\x00\x01\x00\x03\x60\xba\
+\x00\x00\x01\x9a\x72\xe1\x94\x57\
+\x00\x00\x0c\x4c\x00\x00\x00\x00\x00\x01\x00\x03\x66\xa7\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
+\x00\x00\x0c\x76\x00\x00\x00\x00\x00\x01\x00\x03\x68\xc7\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
+\x00\x00\x0c\x9e\x00\x00\x00\x00\x00\x01\x00\x03\x71\x5f\
+\x00\x00\x01\x9a\x72\xe1\x94\x57\
+\x00\x00\x0c\xb8\x00\x00\x00\x00\x00\x01\x00\x03\x80\xa8\
+\x00\x00\x01\x9a\x72\xe1\x94\x57\
+\x00\x00\x0c\xe4\x00\x00\x00\x00\x00\x01\x00\x03\x87\x26\
+\x00\x00\x01\x9a\x72\xe1\x94\x5b\
+\x00\x00\x0d\x0c\x00\x00\x00\x00\x00\x01\x00\x03\x91\xa0\
+\x00\x00\x01\x9a\x72\xe1\x94\x5b\
\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x6b\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x0c\x00\x00\x00\x6c\
\x00\x00\x00\x00\x00\x00\x00\x00\
-\x00\x00\x0d\x42\x00\x00\x00\x00\x00\x01\x00\x03\x9a\x33\
-\x00\x00\x01\x98\x55\x96\x0d\x2b\
-\x00\x00\x0d\x70\x00\x00\x00\x00\x00\x01\x00\x03\x9c\xda\
-\x00\x00\x01\x98\x55\x96\x0d\x35\
-\x00\x00\x0d\x9e\x00\x01\x00\x00\x00\x01\x00\x03\xa9\x57\
-\x00\x00\x01\x98\x55\x96\x0d\x2b\
-\x00\x00\x0d\xca\x00\x00\x00\x00\x00\x01\x00\x03\xd6\xd6\
-\x00\x00\x01\x98\x55\x96\x0d\x2b\
-\x00\x00\x0d\xea\x00\x00\x00\x00\x00\x01\x00\x03\xdb\x8d\
-\x00\x00\x01\x98\x55\x96\x0d\x2b\
-\x00\x00\x0e\x1c\x00\x01\x00\x00\x00\x01\x00\x04\x34\x86\
-\x00\x00\x01\x98\x55\x96\x0d\x2b\
-\x00\x00\x0e\x4e\x00\x00\x00\x00\x00\x01\x00\x04\x69\x20\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
-\x00\x00\x0e\x68\x00\x00\x00\x00\x00\x01\x00\x04\x6e\x6a\
-\x00\x00\x01\x98\x55\x96\x0d\x35\
-\x00\x00\x0e\x82\x00\x00\x00\x00\x00\x01\x00\x04\x73\xf9\
-\x00\x00\x01\x98\x55\x96\x0d\x35\
-\x00\x00\x0e\x9c\x00\x00\x00\x00\x00\x01\x00\x04\x79\x62\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
-\x00\x00\x0e\xb4\x00\x00\x00\x00\x00\x01\x00\x04\x85\x40\
-\x00\x00\x01\x98\x55\x96\x0d\x35\
-\x00\x00\x0e\xd2\x00\x00\x00\x00\x00\x01\x00\x04\x8b\x44\
-\x00\x00\x01\x98\x55\x96\x0d\x35\
+\x00\x00\x0d\x42\x00\x00\x00\x00\x00\x01\x00\x03\x9a\xe7\
+\x00\x00\x01\x9a\x72\xe1\x94\x4b\
+\x00\x00\x0d\x70\x00\x00\x00\x00\x00\x01\x00\x03\x9d\x8e\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
+\x00\x00\x0d\x9e\x00\x01\x00\x00\x00\x01\x00\x03\xaa\x0b\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
+\x00\x00\x0d\xca\x00\x00\x00\x00\x00\x01\x00\x03\xd7\x8a\
+\x00\x00\x01\x9a\x72\xe1\x94\x4b\
+\x00\x00\x0d\xea\x00\x00\x00\x00\x00\x01\x00\x03\xdc\x41\
+\x00\x00\x01\x9a\x72\xe1\x94\x4b\
+\x00\x00\x0e\x1c\x00\x01\x00\x00\x00\x01\x00\x04\x35\x3a\
+\x00\x00\x01\x9a\x72\xe1\x94\x4b\
+\x00\x00\x0e\x4e\x00\x00\x00\x00\x00\x01\x00\x04\x69\xd4\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
+\x00\x00\x0e\x68\x00\x00\x00\x00\x00\x01\x00\x04\x6f\x1e\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
+\x00\x00\x0e\x82\x00\x00\x00\x00\x00\x01\x00\x04\x74\xad\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
+\x00\x00\x0e\x9c\x00\x00\x00\x00\x00\x01\x00\x04\x7a\x16\
+\x00\x00\x01\x9a\x72\xe1\x94\x57\
+\x00\x00\x0e\xb4\x00\x00\x00\x00\x00\x01\x00\x04\x85\xf4\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
+\x00\x00\x0e\xd2\x00\x00\x00\x00\x00\x01\x00\x04\x8b\xf8\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x79\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x04\x00\x00\x00\x7a\
\x00\x00\x00\x00\x00\x00\x00\x00\
-\x00\x00\x0e\xfa\x00\x00\x00\x00\x00\x01\x00\x04\x90\x79\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
-\x00\x00\x0f\x0e\x00\x00\x00\x00\x00\x01\x00\x04\x96\x76\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
-\x00\x00\x0f\x20\x00\x00\x00\x00\x00\x01\x00\x04\x97\xfc\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
-\x00\x00\x0f\x32\x00\x00\x00\x00\x00\x01\x00\x04\x9d\xf6\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
+\x00\x00\x0e\xfa\x00\x00\x00\x00\x00\x01\x00\x04\x91\x2d\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
+\x00\x00\x0f\x0e\x00\x00\x00\x00\x00\x01\x00\x04\x97\x2a\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
+\x00\x00\x0f\x20\x00\x00\x00\x00\x00\x01\x00\x04\x98\xb0\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
+\x00\x00\x0f\x32\x00\x00\x00\x00\x00\x01\x00\x04\x9e\xaa\
+\x00\x00\x01\x9a\x72\xe1\x94\x5b\
\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x7f\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x02\x00\x00\x00\x80\
\x00\x00\x00\x00\x00\x00\x00\x00\
-\x00\x00\x0f\x46\x00\x00\x00\x00\x00\x01\x00\x04\xa0\x4c\
-\x00\x00\x01\x98\x55\x96\x0d\x2b\
-\x00\x00\x0f\x72\x00\x00\x00\x00\x00\x01\x00\x04\xa7\x33\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
+\x00\x00\x0f\x46\x00\x00\x00\x00\x00\x01\x00\x04\xa1\x00\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
+\x00\x00\x0f\x72\x00\x00\x00\x00\x00\x01\x00\x04\xa7\xe7\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x83\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x04\x00\x00\x00\x84\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x46\x00\x02\x00\x00\x00\x0c\x00\x00\x00\x88\
\x00\x00\x00\x00\x00\x00\x00\x00\
-\x00\x00\x0f\x96\x00\x01\x00\x00\x00\x01\x00\x04\xb0\x97\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
-\x00\x00\x0f\xba\x00\x00\x00\x00\x00\x01\x00\x04\xbb\xe8\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
-\x00\x00\x0f\xdc\x00\x00\x00\x00\x00\x01\x00\x04\xc3\x8d\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
-\x00\x00\x0f\xf8\x00\x00\x00\x00\x00\x01\x00\x04\xd4\x8d\
-\x00\x00\x01\x9c\xd4\xa5\xd3\x9f\
-\x00\x00\x10\x18\x00\x00\x00\x00\x00\x01\x00\x04\xda\x26\
-\x00\x00\x01\x9c\xd4\xa5\xd3\x9b\
-\x00\x00\x10\x38\x00\x00\x00\x00\x00\x01\x00\x04\xe0\x4d\
-\x00\x00\x01\x9c\xd4\xa5\xd3\x9f\
-\x00\x00\x10\x58\x00\x00\x00\x00\x00\x01\x00\x04\xe4\xa2\
-\x00\x00\x01\x9c\xd4\xa5\xd3\x9f\
-\x00\x00\x10\x78\x00\x00\x00\x00\x00\x01\x00\x04\xea\x26\
-\x00\x00\x01\x9c\xd4\xa5\xd3\x9f\
-\x00\x00\x10\x98\x00\x00\x00\x00\x00\x01\x00\x04\xef\xbf\
-\x00\x00\x01\x9c\xd4\xa5\xd3\x9f\
-\x00\x00\x10\xca\x00\x00\x00\x00\x00\x01\x00\x04\xf4\xc1\
-\x00\x00\x01\x9c\xd4\xa5\xd3\x9f\
-\x00\x00\x10\xea\x00\x00\x00\x00\x00\x01\x00\x04\xfa\x5a\
-\x00\x00\x01\x9c\xd4\xa5\xd3\x9f\
-\x00\x00\x11\x1e\x00\x00\x00\x00\x00\x01\x00\x05\x04\xce\
-\x00\x00\x01\x9c\xd4\xa5\xd3\x9f\
-\x00\x00\x11\x52\x00\x00\x00\x00\x00\x01\x00\x05\x0f\x48\
-\x00\x00\x01\x9c\xd4\xa5\xd3\x9f\
-\x00\x00\x11\x86\x00\x00\x00\x00\x00\x01\x00\x05\x19\xa7\
-\x00\x00\x01\x9c\xd4\xa5\xd3\x9f\
-\x00\x00\x11\xba\x00\x00\x00\x00\x00\x01\x00\x05\x24\xf2\
-\x00\x00\x01\x9c\xd4\xa5\xd3\x9f\
+\x00\x00\x0f\x96\x00\x01\x00\x00\x00\x01\x00\x04\xb1\x4b\
+\x00\x00\x01\x9a\x72\xe1\x94\x5b\
+\x00\x00\x0f\xba\x00\x00\x00\x00\x00\x01\x00\x04\xbc\x9c\
+\x00\x00\x01\x9a\x72\xe1\x94\x57\
+\x00\x00\x0f\xdc\x00\x00\x00\x00\x00\x01\x00\x04\xc4\x41\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
+\x00\x00\x0f\xf8\x00\x00\x00\x00\x00\x01\x00\x04\xd5\x41\
+\x00\x00\x01\x9c\xe2\x99\x3f\x9c\
+\x00\x00\x10\x18\x00\x00\x00\x00\x00\x01\x00\x04\xda\xda\
+\x00\x00\x01\x9c\xe2\x99\x3f\x9c\
+\x00\x00\x10\x38\x00\x00\x00\x00\x00\x01\x00\x04\xe1\x01\
+\x00\x00\x01\x9c\xe2\x99\x3f\x9c\
+\x00\x00\x10\x58\x00\x00\x00\x00\x00\x01\x00\x04\xe5\x56\
+\x00\x00\x01\x9c\xe2\x99\x3f\x9c\
+\x00\x00\x10\x78\x00\x00\x00\x00\x00\x01\x00\x04\xea\xda\
+\x00\x00\x01\x9c\xe2\x99\x3f\x9c\
+\x00\x00\x10\x98\x00\x00\x00\x00\x00\x01\x00\x04\xf0\x73\
+\x00\x00\x01\x9c\xe2\x99\x3f\x9c\
+\x00\x00\x10\xca\x00\x00\x00\x00\x00\x01\x00\x04\xf5\x75\
+\x00\x00\x01\x9c\xe2\x99\x3f\x9c\
+\x00\x00\x10\xea\x00\x00\x00\x00\x00\x01\x00\x04\xfb\x0e\
+\x00\x00\x01\x9c\xe2\x99\x3f\x9c\
+\x00\x00\x11\x1e\x00\x00\x00\x00\x00\x01\x00\x05\x05\x82\
+\x00\x00\x01\x9c\xe2\x99\x3f\x9c\
+\x00\x00\x11\x52\x00\x00\x00\x00\x00\x01\x00\x05\x0f\xfc\
+\x00\x00\x01\x9c\xe2\x99\x3f\x9c\
+\x00\x00\x11\x86\x00\x00\x00\x00\x00\x01\x00\x05\x1a\x5b\
+\x00\x00\x01\x9c\xe2\x99\x3f\x9c\
+\x00\x00\x11\xba\x00\x00\x00\x00\x00\x01\x00\x05\x25\xa6\
+\x00\x00\x01\x9c\xe2\x99\x3f\x9c\
\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\x95\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x0a\x00\x00\x00\x96\
\x00\x00\x00\x00\x00\x00\x00\x00\
-\x00\x00\x11\xee\x00\x00\x00\x00\x00\x01\x00\x05\x2f\x66\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
-\x00\x00\x12\x1e\x00\x00\x00\x00\x00\x01\x00\x05\x38\xfc\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
-\x00\x00\x12\x4e\x00\x00\x00\x00\x00\x01\x00\x05\x44\xd8\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
-\x00\x00\x12\x72\x00\x00\x00\x00\x00\x01\x00\x05\x4b\x1c\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
-\x00\x00\x12\x9c\x00\x00\x00\x00\x00\x01\x00\x05\x52\xa5\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
-\x00\x00\x12\xc8\x00\x00\x00\x00\x00\x01\x00\x05\x59\x03\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
-\x00\x00\x12\xfe\x00\x00\x00\x00\x00\x01\x00\x05\x60\xf2\
-\x00\x00\x01\x98\x55\x96\x0d\x35\
-\x00\x00\x09\x66\x00\x00\x00\x00\x00\x01\x00\x05\x6e\x95\
-\x00\x00\x01\x9c\xd4\xa5\xd3\x9b\
-\x00\x00\x09\x7a\x00\x00\x00\x00\x00\x01\x00\x05\x74\x76\
-\x00\x00\x01\x9c\xd4\xa5\xd3\x9b\
-\x00\x00\x13\x1c\x00\x00\x00\x00\x00\x01\x00\x05\x80\x09\
-\x00\x00\x01\x9a\x27\x73\xa6\xfc\
+\x00\x00\x11\xee\x00\x00\x00\x00\x00\x01\x00\x05\x30\x1a\
+\x00\x00\x01\x9a\x72\xe1\x94\x5b\
+\x00\x00\x12\x1e\x00\x00\x00\x00\x00\x01\x00\x05\x39\xb0\
+\x00\x00\x01\x9a\x72\xe1\x94\x5b\
+\x00\x00\x12\x4e\x00\x00\x00\x00\x00\x01\x00\x05\x45\x8c\
+\x00\x00\x01\x9a\x72\xe1\x94\x5b\
+\x00\x00\x12\x72\x00\x00\x00\x00\x00\x01\x00\x05\x4b\xd0\
+\x00\x00\x01\x9a\x72\xe1\x94\x5b\
+\x00\x00\x12\x9c\x00\x00\x00\x00\x00\x01\x00\x05\x53\x59\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
+\x00\x00\x12\xc8\x00\x00\x00\x00\x00\x01\x00\x05\x59\xb7\
+\x00\x00\x01\x9a\x72\xe1\x94\x57\
+\x00\x00\x12\xfe\x00\x00\x00\x00\x00\x01\x00\x05\x61\xa6\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
+\x00\x00\x09\x66\x00\x00\x00\x00\x00\x01\x00\x05\x6f\x49\
+\x00\x00\x01\x9d\x06\xcd\x70\x8f\
+\x00\x00\x09\x7a\x00\x00\x00\x00\x00\x01\x00\x05\x75\x1c\
+\x00\x00\x01\x9d\x06\xcd\x70\xa3\
+\x00\x00\x13\x1c\x00\x00\x00\x00\x00\x01\x00\x05\x80\x96\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\xa1\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x01\x00\x00\x00\xa2\
\x00\x00\x00\x00\x00\x00\x00\x00\
-\x00\x00\x13\x44\x00\x00\x00\x00\x00\x01\x00\x05\x87\xd3\
-\x00\x00\x01\x98\x55\x96\x0d\x35\
+\x00\x00\x13\x44\x00\x00\x00\x00\x00\x01\x00\x05\x88\x60\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
\x00\x00\x01\xa0\x00\x02\x00\x00\x00\x01\x00\x00\x00\xa4\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x01\xb0\x00\x02\x00\x00\x00\x2b\x00\x00\x00\xa5\
\x00\x00\x00\x00\x00\x00\x00\x00\
-\x00\x00\x13\x64\x00\x00\x00\x00\x00\x01\x00\x05\x8c\x99\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
-\x00\x00\x13\x7a\x00\x00\x00\x00\x00\x01\x00\x05\x94\x4d\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
-\x00\x00\x13\x92\x00\x00\x00\x00\x00\x01\x00\x05\x96\x72\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
-\x00\x00\x13\xca\x00\x00\x00\x00\x00\x01\x00\x05\x97\xf2\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
-\x00\x00\x13\xe6\x00\x00\x00\x00\x00\x01\x00\x05\x9f\x9f\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
-\x00\x00\x14\x06\x00\x00\x00\x00\x00\x01\x00\x05\xa4\x74\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
-\x00\x00\x14\x1c\x00\x00\x00\x00\x00\x01\x00\x05\xa5\x64\
-\x00\x00\x01\x9c\xd4\xa5\xd3\x9f\
-\x00\x00\x14\x42\x00\x00\x00\x00\x00\x01\x00\x05\xa8\x90\
-\x00\x00\x01\x9b\xc6\xe6\xb1\x6c\
-\x00\x00\x14\x58\x00\x00\x00\x00\x00\x01\x00\x05\xac\xb9\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
-\x00\x00\x14\x7e\x00\x00\x00\x00\x00\x01\x00\x05\xb2\xf0\
-\x00\x00\x01\x99\x96\xf9\x85\x6f\
-\x00\x00\x14\x98\x00\x00\x00\x00\x00\x01\x00\x05\xc7\xfd\
-\x00\x00\x01\x9a\x27\x73\xa6\xf8\
-\x00\x00\x14\xba\x00\x00\x00\x00\x00\x01\x00\x05\xcc\xed\
-\x00\x00\x01\x98\x55\x96\x0d\x2b\
-\x00\x00\x14\xd0\x00\x00\x00\x00\x00\x01\x00\x05\xcf\xec\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
-\x00\x00\x14\xe6\x00\x00\x00\x00\x00\x01\x00\x05\xd5\xf6\
-\x00\x00\x01\x98\x55\x96\x0d\x35\
-\x00\x00\x15\x18\x00\x00\x00\x00\x00\x01\x00\x05\xd9\x45\
-\x00\x00\x01\x9c\xd4\xa5\xd3\x9b\
-\x00\x00\x15\x30\x00\x00\x00\x00\x00\x01\x00\x05\xdc\x37\
-\x00\x00\x01\x98\x55\x96\x0d\x2b\
-\x00\x00\x15\x46\x00\x00\x00\x00\x00\x01\x00\x05\xe2\x2e\
-\x00\x00\x01\x99\x96\xf9\x85\x6f\
-\x00\x00\x15\x5a\x00\x00\x00\x00\x00\x01\x00\x05\xe4\x3f\
-\x00\x00\x01\x99\x96\xf9\x85\x6f\
-\x00\x00\x15\x72\x00\x00\x00\x00\x00\x01\x00\x05\xe8\x02\
-\x00\x00\x01\x98\x55\x96\x0d\x2b\
-\x00\x00\x15\x98\x00\x00\x00\x00\x00\x01\x00\x05\xf1\xb6\
-\x00\x00\x01\x9c\xd4\xa5\xd3\x9f\
-\x00\x00\x15\xb6\x00\x00\x00\x00\x00\x01\x00\x05\xf7\x7e\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
-\x00\x00\x15\xe0\x00\x00\x00\x00\x00\x01\x00\x05\xfa\xcc\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
-\x00\x00\x16\x02\x00\x00\x00\x00\x00\x01\x00\x05\xfe\x5a\
-\x00\x00\x01\x9a\x27\x73\xa6\xf8\
-\x00\x00\x16\x28\x00\x00\x00\x00\x00\x01\x00\x06\x02\xfb\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
-\x00\x00\x16\x3c\x00\x00\x00\x00\x00\x01\x00\x06\x0c\xcd\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
-\x00\x00\x16\x68\x00\x00\x00\x00\x00\x01\x00\x06\x12\x17\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
-\x00\x00\x16\x90\x00\x00\x00\x00\x00\x01\x00\x06\x18\x1e\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
-\x00\x00\x16\xa6\x00\x00\x00\x00\x00\x01\x00\x06\x19\x02\
-\x00\x00\x01\x98\x55\x96\x0d\x2b\
-\x00\x00\x16\xd2\x00\x00\x00\x00\x00\x01\x00\x06\x1b\x4b\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
-\x00\x00\x16\xe8\x00\x00\x00\x00\x00\x01\x00\x06\x21\xfb\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
-\x00\x00\x17\x04\x00\x00\x00\x00\x00\x01\x00\x06\x25\x3f\
-\x00\x00\x01\x9c\xd4\xa5\xd3\x9f\
-\x00\x00\x17\x38\x00\x00\x00\x00\x00\x01\x00\x06\x2c\x25\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
-\x00\x00\x17\x50\x00\x00\x00\x00\x00\x01\x00\x06\x2d\x51\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
-\x00\x00\x17\x6a\x00\x00\x00\x00\x00\x01\x00\x06\x33\x12\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
-\x00\x00\x17\x8c\x00\x00\x00\x00\x00\x01\x00\x06\x34\x34\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
-\x00\x00\x17\xaa\x00\x00\x00\x00\x00\x01\x00\x06\x3a\x27\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
-\x00\x00\x17\xca\x00\x00\x00\x00\x00\x01\x00\x06\x3d\x2b\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
-\x00\x00\x17\xec\x00\x00\x00\x00\x00\x01\x00\x06\x3e\x4c\
-\x00\x00\x01\x98\x55\x96\x0d\x49\
-\x00\x00\x18\x0c\x00\x00\x00\x00\x00\x01\x00\x06\x41\x20\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
-\x00\x00\x18\x3a\x00\x00\x00\x00\x00\x01\x00\x06\x49\x8c\
-\x00\x00\x01\x99\x7d\x04\xc3\x11\
-\x00\x00\x18\x5e\x00\x00\x00\x00\x00\x01\x00\x06\x51\x4c\
-\x00\x00\x01\x98\x55\x96\x0d\x3f\
-\x00\x00\x18\x82\x00\x00\x00\x00\x00\x01\x00\x06\x56\x67\
-\x00\x00\x01\x98\x55\x96\x0d\x35\
-\x00\x00\x18\xaa\x00\x00\x00\x00\x00\x01\x00\x06\x57\xba\
-\x00\x00\x01\x98\x55\x96\x0d\x2b\
+\x00\x00\x13\x64\x00\x00\x00\x00\x00\x01\x00\x05\x8d\x26\
+\x00\x00\x01\x9a\x72\xe1\x94\x5b\
+\x00\x00\x13\x7a\x00\x00\x00\x00\x00\x01\x00\x05\x94\xda\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
+\x00\x00\x13\x92\x00\x00\x00\x00\x00\x01\x00\x05\x96\xff\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
+\x00\x00\x13\xca\x00\x00\x00\x00\x00\x01\x00\x05\x98\x7f\
+\x00\x00\x01\x9a\x72\xe1\x94\x57\
+\x00\x00\x13\xe6\x00\x00\x00\x00\x00\x01\x00\x05\xa0\x2c\
+\x00\x00\x01\x9a\x72\xe1\x94\x57\
+\x00\x00\x14\x06\x00\x00\x00\x00\x00\x01\x00\x05\xa5\x01\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
+\x00\x00\x14\x1c\x00\x00\x00\x00\x00\x01\x00\x05\xa5\xf1\
+\x00\x00\x01\x9c\xe2\x99\x3f\x9c\
+\x00\x00\x14\x42\x00\x00\x00\x00\x00\x01\x00\x05\xa9\x1d\
+\x00\x00\x01\x9c\xe2\x99\x3f\x9c\
+\x00\x00\x14\x58\x00\x00\x00\x00\x00\x01\x00\x05\xad\x46\
+\x00\x00\x01\x9a\x72\xe1\x94\x5b\
+\x00\x00\x14\x7e\x00\x00\x00\x00\x00\x01\x00\x05\xb3\x7d\
+\x00\x00\x01\x9a\x72\xe1\x94\x57\
+\x00\x00\x14\x98\x00\x00\x00\x00\x00\x01\x00\x05\xc8\x8a\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
+\x00\x00\x14\xba\x00\x00\x00\x00\x00\x01\x00\x05\xcd\x7a\
+\x00\x00\x01\x9a\x72\xe1\x94\x4b\
+\x00\x00\x14\xd0\x00\x00\x00\x00\x00\x01\x00\x05\xd0\x79\
+\x00\x00\x01\x9a\x72\xe1\x94\x57\
+\x00\x00\x14\xe6\x00\x00\x00\x00\x00\x01\x00\x05\xd6\x83\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
+\x00\x00\x15\x18\x00\x00\x00\x00\x00\x01\x00\x05\xd9\xd2\
+\x00\x00\x01\x9c\xe2\x99\x3f\x98\
+\x00\x00\x15\x30\x00\x00\x00\x00\x00\x01\x00\x05\xdc\xc4\
+\x00\x00\x01\x9a\x72\xe1\x94\x4b\
+\x00\x00\x15\x46\x00\x00\x00\x00\x00\x01\x00\x05\xe2\xbb\
+\x00\x00\x01\x9a\x72\xe1\x94\x57\
+\x00\x00\x15\x5a\x00\x00\x00\x00\x00\x01\x00\x05\xe4\xcc\
+\x00\x00\x01\x9a\x72\xe1\x94\x5b\
+\x00\x00\x15\x72\x00\x00\x00\x00\x00\x01\x00\x05\xe8\x8f\
+\x00\x00\x01\x9a\x72\xe1\x94\x4b\
+\x00\x00\x15\x98\x00\x00\x00\x00\x00\x01\x00\x05\xf2\x43\
+\x00\x00\x01\x9c\xe2\x99\x3f\x9c\
+\x00\x00\x15\xb6\x00\x00\x00\x00\x00\x01\x00\x05\xf8\x0b\
+\x00\x00\x01\x9a\x72\xe1\x94\x57\
+\x00\x00\x15\xe0\x00\x00\x00\x00\x00\x01\x00\x05\xfb\x59\
+\x00\x00\x01\x9a\x72\xe1\x94\x57\
+\x00\x00\x16\x02\x00\x00\x00\x00\x00\x01\x00\x05\xfe\xe7\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
+\x00\x00\x16\x28\x00\x00\x00\x00\x00\x01\x00\x06\x03\x88\
+\x00\x00\x01\x9a\x72\xe1\x94\x57\
+\x00\x00\x16\x3c\x00\x00\x00\x00\x00\x01\x00\x06\x0d\x5a\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
+\x00\x00\x16\x68\x00\x00\x00\x00\x00\x01\x00\x06\x12\xa4\
+\x00\x00\x01\x9a\x72\xe1\x94\x57\
+\x00\x00\x16\x90\x00\x00\x00\x00\x00\x01\x00\x06\x18\xab\
+\x00\x00\x01\x9a\x72\xe1\x94\x57\
+\x00\x00\x16\xa6\x00\x00\x00\x00\x00\x01\x00\x06\x19\x8f\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
+\x00\x00\x16\xd2\x00\x00\x00\x00\x00\x01\x00\x06\x1b\xd8\
+\x00\x00\x01\x9a\x72\xe1\x94\x5b\
+\x00\x00\x16\xe8\x00\x00\x00\x00\x00\x01\x00\x06\x22\x88\
+\x00\x00\x01\x9a\x72\xe1\x94\x57\
+\x00\x00\x17\x04\x00\x00\x00\x00\x00\x01\x00\x06\x25\xcc\
+\x00\x00\x01\x9c\xe2\x99\x3f\x9c\
+\x00\x00\x17\x38\x00\x00\x00\x00\x00\x01\x00\x06\x2c\xb2\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
+\x00\x00\x17\x50\x00\x00\x00\x00\x00\x01\x00\x06\x2d\xde\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
+\x00\x00\x17\x6a\x00\x00\x00\x00\x00\x01\x00\x06\x33\x9f\
+\x00\x00\x01\x9a\x72\xe1\x94\x57\
+\x00\x00\x17\x8c\x00\x00\x00\x00\x00\x01\x00\x06\x34\xc1\
+\x00\x00\x01\x9a\x72\xe1\x94\x57\
+\x00\x00\x17\xaa\x00\x00\x00\x00\x00\x01\x00\x06\x3a\xb4\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
+\x00\x00\x17\xca\x00\x00\x00\x00\x00\x01\x00\x06\x3d\xb8\
+\x00\x00\x01\x9a\x72\xe1\x94\x57\
+\x00\x00\x17\xec\x00\x00\x00\x00\x00\x01\x00\x06\x3e\xd9\
+\x00\x00\x01\x9a\x72\xe1\x94\x57\
+\x00\x00\x18\x0c\x00\x00\x00\x00\x00\x01\x00\x06\x41\xad\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
+\x00\x00\x18\x3a\x00\x00\x00\x00\x00\x01\x00\x06\x4a\x19\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
+\x00\x00\x18\x5e\x00\x00\x00\x00\x00\x01\x00\x06\x51\xd9\
+\x00\x00\x01\x9a\x72\xe1\x94\x53\
+\x00\x00\x18\x82\x00\x00\x00\x00\x00\x01\x00\x06\x56\xf4\
+\x00\x00\x01\x9a\x72\xe1\x94\x4f\
+\x00\x00\x18\xaa\x00\x00\x00\x00\x00\x01\x00\x06\x58\x47\
+\x00\x00\x01\x9a\x72\xe1\x94\x4b\
"
qt_version = [int(v) for v in QtCore.qVersion().split('.')]
diff --git a/BlocksScreen/lib/ui/resources/media/btn_icons/blower.svg b/BlocksScreen/lib/ui/resources/media/btn_icons/blower.svg
index 027ea01c..eb090a6f 100644
--- a/BlocksScreen/lib/ui/resources/media/btn_icons/blower.svg
+++ b/BlocksScreen/lib/ui/resources/media/btn_icons/blower.svg
@@ -7,9 +7,9 @@
}
-
-
-
-
-
+
+
+
+
+
\ No newline at end of file
diff --git a/BlocksScreen/lib/ui/resources/media/btn_icons/fan.svg b/BlocksScreen/lib/ui/resources/media/btn_icons/fan.svg
index 46bccde3..8384f84e 100644
--- a/BlocksScreen/lib/ui/resources/media/btn_icons/fan.svg
+++ b/BlocksScreen/lib/ui/resources/media/btn_icons/fan.svg
@@ -7,5 +7,5 @@
}
-
+
\ No newline at end of file
diff --git a/BlocksScreen/lib/ui/resources/media/btn_icons/fan_cage.svg b/BlocksScreen/lib/ui/resources/media/btn_icons/fan_cage.svg
index 59a32e1b..5a63dbc9 100644
--- a/BlocksScreen/lib/ui/resources/media/btn_icons/fan_cage.svg
+++ b/BlocksScreen/lib/ui/resources/media/btn_icons/fan_cage.svg
@@ -1 +1,12 @@
-
\ No newline at end of file
+
+
\ No newline at end of file
diff --git a/BlocksScreen/lib/utils/RepeatedTimer.py b/BlocksScreen/lib/utils/RepeatedTimer.py
index 42b42aa0..698c2ce8 100644
--- a/BlocksScreen/lib/utils/RepeatedTimer.py
+++ b/BlocksScreen/lib/utils/RepeatedTimer.py
@@ -10,6 +10,7 @@ def __init__(
*args,
**kwargs,
):
+ """Initialize a repeating timer that invokes callback every timeout seconds."""
super().__init__(daemon=True)
self.name = name
self._timeout = timeout
@@ -17,6 +18,7 @@ def __init__(
self._args = args
self._kwargs = kwargs
+ self._lock = threading.Lock()
self.running = False
self.timeoutEvent = threading.Event()
self.stopEvent = threading.Event()
@@ -24,36 +26,42 @@ def __init__(
self.startTimer()
def _run(self):
- self.running = False
- self.startTimer()
- self.stopEvent.wait()
+ """Invoke the callback and restart the timer loop, unless stopped."""
+ with self._lock:
+ self.running = False
+ if self.stopEvent.is_set():
+ return
if callable(self._function):
self._function(*self._args, **self._kwargs)
+ self.startTimer()
def startTimer(self):
"""Start timer"""
- if self.running is False:
+ with self._lock:
+ if self.running:
+ return
+ self.stopEvent.clear()
try:
- self._timer = threading.Timer(self._timeout, self._run)
- self._timer.daemon = True
- self._timer.start()
- if not self.stopEvent.is_set():
- self.stopEvent.set()
+ timer = threading.Timer(self._timeout, self._run)
+ timer.daemon = True
+ self._timer = timer
+ self.running = True
except Exception as e:
+ self.running = False
raise Exception(
f"RepeatedTimer {self.name} error while starting timer, error: {e}"
- )
- finally:
- self.running = False
- self.running = True
+ ) from e
+ # Start outside the lock to avoid holding it during thread creation
+ timer.start()
def stopTimer(self):
"""Stop timer"""
- if self._timer is None:
- return
- if self.running:
- self._timer.cancel()
- self._timer.join()
+ with self._lock:
+ if self._timer is None or not self.running:
+ return
+ timer = self._timer
self._timer = None
- self.stopEvent.clear()
self.running = False
+ self.stopEvent.set()
+ timer.cancel()
+ timer.join()
diff --git a/BlocksScreen/lib/utils/blocks_button.py b/BlocksScreen/lib/utils/blocks_button.py
index 292b5125..ec6b24c7 100644
--- a/BlocksScreen/lib/utils/blocks_button.py
+++ b/BlocksScreen/lib/utils/blocks_button.py
@@ -1,5 +1,6 @@
-import typing
import enum
+import typing
+
from PyQt6 import QtCore, QtGui, QtWidgets
@@ -106,7 +107,7 @@ def setProperty(self, name: str, value: typing.Any):
self.text_color = QtGui.QColor(value)
self.update()
- def paintEvent(self, e: typing.Optional[QtGui.QPaintEvent]):
+ def paintEvent(self, e: QtGui.QPaintEvent | None):
"""Re-implemented method, paint widget"""
painter = QtGui.QPainter(self)
painter.setRenderHint(painter.RenderHint.Antialiasing, True)
@@ -116,17 +117,7 @@ def paintEvent(self, e: typing.Optional[QtGui.QPaintEvent]):
_style = self.style()
if not _style or not _rect:
return
- # Flat button control
opt = QtWidgets.QStyleOptionButton()
- draw_frame = (
- not self._is_flat
- or self.underMouse()
- or opt.state & QtWidgets.QStyle.StateFlag.State_Sunken
- )
- if draw_frame:
- _style.drawControl(
- QtWidgets.QStyle.ControlElement.CE_PushButtonLabel, opt, painter, self
- )
_style.drawControl(
QtWidgets.QStyle.ControlElement.CE_PushButtonLabel, opt, painter, self
)
diff --git a/BlocksScreen/lib/utils/blocks_frame.py b/BlocksScreen/lib/utils/blocks_frame.py
index 7de7514e..8bd2d6b8 100644
--- a/BlocksScreen/lib/utils/blocks_frame.py
+++ b/BlocksScreen/lib/utils/blocks_frame.py
@@ -1,6 +1,7 @@
-from PyQt6 import QtCore, QtGui, QtWidgets
import typing
+from PyQt6 import QtCore, QtGui, QtWidgets
+
class BlocksCustomFrame(QtWidgets.QFrame):
def __init__(self, parent=None):
diff --git a/BlocksScreen/lib/utils/blocks_progressbar.py b/BlocksScreen/lib/utils/blocks_progressbar.py
index 414097bb..60950b67 100644
--- a/BlocksScreen/lib/utils/blocks_progressbar.py
+++ b/BlocksScreen/lib/utils/blocks_progressbar.py
@@ -1,5 +1,6 @@
import typing
-from PyQt6 import QtWidgets, QtGui, QtCore
+
+from PyQt6 import QtCore, QtGui, QtWidgets
class CustomProgressBar(QtWidgets.QProgressBar):
@@ -30,6 +31,12 @@ def __init__(self, parent=None):
self.setMinimumSize(100, 100)
self._inner_rect: QtCore.QRectF = QtCore.QRectF()
+ def reset(self) -> None:
+ """Reset progress to zero."""
+ self.progress_value = 0
+ super().reset()
+ self.update()
+
def set_padding(self, value) -> None:
"""Set widget padding"""
self._padding = value
@@ -93,8 +100,8 @@ def setValue(self, value: float) -> None:
Raises:
ValueError: If provided value in not between 0.0 and 1.0
"""
- if not (0 <= value <= 100):
- raise ValueError("Argument `value` expected value between 0.0 and 1.0 ")
+ if not (0.0 <= value <= 1.0):
+ raise ValueError("Argument `value` expected value between 0.0 and 1.0")
value *= 100
self.progress_value = value
self.update()
@@ -159,7 +166,7 @@ def _draw_circular_bar(
bg_pen.setCapStyle(QtCore.Qt.PenCapStyle.RoundCap)
painter.setPen(bg_pen)
painter.drawArc(arc_rect, arc_start, arc_span)
- if self.progress_value is not None:
+ if self.progress_value is not None and self.progress_value > 0:
gradient = QtGui.QConicalGradient(arc_rect.center(), -90)
gradient.setColorAt(0.0, self._bar_color)
gradient.setColorAt(1.0, QtGui.QColor(100, 100, 100))
diff --git a/BlocksScreen/lib/utils/blocks_tabwidget.py b/BlocksScreen/lib/utils/blocks_tabwidget.py
index 4696d967..cc403bd0 100644
--- a/BlocksScreen/lib/utils/blocks_tabwidget.py
+++ b/BlocksScreen/lib/utils/blocks_tabwidget.py
@@ -1,4 +1,4 @@
-from PyQt6 import QtWidgets, QtGui, QtCore
+from PyQt6 import QtCore, QtGui, QtWidgets
class NotificationTabBar(QtWidgets.QTabBar):
diff --git a/BlocksScreen/lib/utils/check_button.py b/BlocksScreen/lib/utils/check_button.py
index e5b184d5..79c998c1 100644
--- a/BlocksScreen/lib/utils/check_button.py
+++ b/BlocksScreen/lib/utils/check_button.py
@@ -1,4 +1,3 @@
-import typing
from PyQt6 import QtCore, QtGui, QtWidgets
@@ -39,7 +38,7 @@ def setText(self, text: str | None) -> None:
self.update()
return
- def paintEvent(self, e: typing.Optional[QtGui.QPaintEvent]):
+ def paintEvent(self, e: QtGui.QPaintEvent | None):
"""Re-implemented method, paint widget, optimized for performance."""
painter = QtGui.QPainter(self)
diff --git a/BlocksScreen/lib/utils/icon_button.py b/BlocksScreen/lib/utils/icon_button.py
index 3880d285..55d9ebc1 100644
--- a/BlocksScreen/lib/utils/icon_button.py
+++ b/BlocksScreen/lib/utils/icon_button.py
@@ -1,4 +1,5 @@
import typing
+
from PyQt6 import QtCore, QtGui, QtWidgets
@@ -135,7 +136,7 @@ def setProperty(self, name: str, value: typing.Any) -> bool:
elif name == "has_text":
self.has_text = value
elif name == "name":
- self._name = name
+ self._name = value
elif name == "text_color":
self.text_color = value
return super().setProperty(name, value)
diff --git a/BlocksScreen/lib/utils/toggleAnimatedButton.py b/BlocksScreen/lib/utils/toggleAnimatedButton.py
index b9555876..371802eb 100644
--- a/BlocksScreen/lib/utils/toggleAnimatedButton.py
+++ b/BlocksScreen/lib/utils/toggleAnimatedButton.py
@@ -1,5 +1,6 @@
import enum
import typing
+
from PyQt6 import QtCore, QtGui, QtWidgets
@@ -33,6 +34,7 @@ def __init__(self, parent) -> None:
- self.handle_radius * 2
)
+ self.trailPath: QtGui.QPainterPath | None = None
self.icon_pixmap: QtGui.QPixmap = QtGui.QPixmap()
self._backgroundColor: QtGui.QColor = QtGui.QColor(223, 223, 223)
self._handleColor: QtGui.QColor = QtGui.QColor(255, 100, 10)
@@ -179,7 +181,7 @@ def setup_animation(self) -> None:
def mousePressEvent(self, e: QtGui.QMouseEvent) -> None:
"""Re-implemented method, handle mouse press events"""
- if self.trailPath:
+ if self.trailPath is not None:
if self.trailPath.contains(e.pos().toPointF()) and self.underMouse():
if not self.slide_animation.state == self.slide_animation.State.Running:
self._state = ToggleAnimatedButton.State(not self._state.value)
@@ -214,7 +216,10 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None:
rect_norm = _rect.toRectF().normalized()
min_x = rect_norm.x()
max_x = rect_norm.x() + rect_norm.width() - rect_norm.height() * 0.80
- progress = (self._handle_position - min_x) / (max_x - min_x)
+ denominator = max_x - min_x
+ if denominator == 0:
+ return
+ progress = (self._handle_position - min_x) / denominator
progress = max(0.0, min(1.0, progress))
# Inline color interpolation (no separate functions)
@@ -237,6 +242,8 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None:
self.handleColor = QtGui.QColor(int(r), int(g), int(b), int(a))
+ if self.trailPath is None:
+ return
painter.fillPath(
self.trailPath,
bg_color if self.isEnabled() else self.disable_bg_color,
diff --git a/BlocksScreen/screensaver.py b/BlocksScreen/screensaver.py
index de02ba02..20cda0cf 100644
--- a/BlocksScreen/screensaver.py
+++ b/BlocksScreen/screensaver.py
@@ -3,15 +3,22 @@
class ScreenSaver(QtCore.QObject):
+ """Screensaver that uses X11 DPMS to blank the display after inactivity."""
+
timer = QtCore.QTimer()
- dpms_off_timeout = helper_methods.get_dpms_timeouts().get("off_timeout")
- dpms_suspend_timeout = helper_methods.get_dpms_timeouts().get("suspend_timeout")
- dpms_standby_timeout = helper_methods.get_dpms_timeouts().get("standby_timeout")
touch_blocked: bool = False
+ _dpms_available: bool = hasattr(helper_methods, "get_dpms_timeouts")
def __init__(self, parent) -> None:
super().__init__()
+ dpms_timeouts = (
+ helper_methods.get_dpms_timeouts() if self._dpms_available else {}
+ )
+ self.dpms_off_timeout = dpms_timeouts.get("off_timeout")
+ self.dpms_suspend_timeout = dpms_timeouts.get("suspend_timeout")
+ self.dpms_standby_timeout = dpms_timeouts.get("standby_timeout")
+
self.screensaver_config = parent.config.get_section(
"screensaver", fallback=None
)
@@ -29,9 +36,11 @@ def __init__(self, parent) -> None:
self.timer.start()
def eventFilter(self, object, event) -> bool:
- """Filter touch events considering DPMS Screen state"""
+ """Filter touch events considering DPMS screen state."""
+ if not self._dpms_available:
+ return False
- if event.type() in ( # Block Touch Filter and Wake Touch Filter
+ if event.type() in (
QtCore.QEvent.Type.TouchBegin,
QtCore.QEvent.Type.TouchUpdate,
QtCore.QEvent.Type.TouchEnd,
@@ -52,14 +61,15 @@ def eventFilter(self, object, event) -> bool:
self.touch_blocked = False
helper_methods.set_dpms_mode(helper_methods.DPMSState.ON)
self.timer.start()
- return True # filter out the event, block touch events on the application
+ return True
else:
self.timer.stop()
self.timer.start()
return False
def check_dpms(self) -> None:
- """Checks the X11 extension dpms for the status of the screen"""
+ """Blank the display via DPMS standby."""
self.touch_blocked = True
- helper_methods.set_dpms_mode(helper_methods.DPMSState.STANDBY)
+ if self._dpms_available:
+ helper_methods.set_dpms_mode(helper_methods.DPMSState.STANDBY)
self.timer.stop()
diff --git a/tests/network/test_network_ui.py b/tests/network/test_network_ui.py
index 466ec14a..a60a330a 100644
--- a/tests/network/test_network_ui.py
+++ b/tests/network/test_network_ui.py
@@ -693,7 +693,7 @@ def test_transient_mismatch_retries(self, win, qapp):
message="not compatible with device",
error_code="nm_error",
)
- with patch("BlocksScreen.lib.panels.networkWindow.QTimer") as mock_timer:
+ with patch("BlocksScreen.lib.panels.networkWindow.QtCore.QTimer") as mock_timer:
w._on_operation_complete(result)
mock_timer.singleShot.assert_called_once()
# Loading should still be visible — retry is pending
@@ -740,7 +740,7 @@ def test_wifi_on_with_saved_networks_starts_connect(self, win):
)
]
nm.saved_networks = saved
- with patch("BlocksScreen.lib.panels.networkWindow.QTimer") as mock_timer:
+ with patch("BlocksScreen.lib.panels.networkWindow.QtCore.QTimer") as mock_timer:
w._handle_wifi_toggle(True)
mock_timer.singleShot.assert_called()
assert w._pending_operation == PendingOperation.WIFI_ON