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/helper_methods.py b/BlocksScreen/helper_methods.py
index 25b76cac..114ff5f1 100644
--- a/BlocksScreen/helper_methods.py
+++ b/BlocksScreen/helper_methods.py
@@ -9,10 +9,10 @@
import ctypes
import enum
import logging
+import math
import os
import pathlib
import struct
-import typing
logger = logging.getLogger(__name__)
@@ -21,7 +21,7 @@
libxext = ctypes.CDLL("libXext.so.6")
class DPMSState(enum.Enum):
- """Available DPMS states"""
+ """Available DPMS states."""
FAIL = -1
ON = 0
@@ -86,7 +86,7 @@ def set_dpms_mode(mode: DPMSState) -> None:
finally:
libxext.XCloseDisplay(display)
- def get_dpms_timeouts() -> typing.Dict:
+ def get_dpms_timeouts() -> dict:
"""Get current DPMS timeouts"""
_display_name = ctypes.c_char_p(b":0")
libxext.XOpenDisplay.restype = ctypes.c_void_p
@@ -118,9 +118,7 @@ def get_dpms_timeouts() -> typing.Dict:
"off_seconds": _off_timeout,
}
- def set_dpms_timeouts(
- suspend: int = 0, standby: int = 0, off: int = 0
- ) -> typing.Dict:
+ def set_dpms_timeouts(suspend: int = 0, standby: int = 0, off: int = 0) -> dict:
"""Set DPMS timeout"""
_display_name = ctypes.c_char_p(b":0")
libxext.XOpenDisplay.restype = ctypes.c_void_p
@@ -155,11 +153,11 @@ def set_dpms_timeouts(
"off_seconds": _off_timeout,
}
- def get_dpms_info() -> typing.Dict:
+ def get_dpms_info() -> dict:
"""Get DPMS information
Returns:
- typing.Dict: Dpms state
+ dict: Dpms state
"""
_dpms_state = DPMSState.FAIL
onoff = 0
@@ -227,133 +225,122 @@ def disable_dpms() -> None:
logger.exception(f"Unexpected exception occurred {e}")
-def convert_bytes_to_mb(self, bytes: int | float) -> float:
- """Converts byte size to megabyte size
+def convert_bytes_to_mb(size_bytes: int | float) -> float:
+ """Converts byte size to megabyte size.
Args:
- bytes (int | float): bytes
+ size_bytes: Value in bytes.
Returns:
- mb: float that represents the number of mb
+ Equivalent value in megabytes.
"""
_relation = 2 ** (-20)
- return bytes * _relation
+ return size_bytes * _relation
def calculate_current_layer(
z_position: float,
- object_height: float,
layer_height: float,
first_layer_height: float,
+ max_layers: int = 0,
) -> int:
- """Calculated the current printing layer given the GCODE z position received by the
- gcode_move object update.
- Also updates the label where the current layer should be displayed
+ """Calculate current layer from Z position (fallback when Klipper
+ does not provide ``print_stats.info.current_layer``).
+
+ Formula ported from Mainsail ``getPrintCurrentLayer`` getter:
+ ``src/store/printer/getters.ts`` in ``mainsail-crew/mainsail``.
+
+ Uses ``ceil((z - first_layer_height) / layer_height + 1)``
+ and clamps the result to ``[0, max_layers]``.
Returns:
- int: Current layer
+ int: Current layer number (0 when not yet printing).
"""
- if z_position == 0:
- return -1
- if z_position <= first_layer_height:
- return 1
+ if layer_height <= 0 or first_layer_height < 0:
+ return 0
- _current_layer = (z_position) / layer_height
+ layer = math.ceil((z_position - first_layer_height) / layer_height + 1)
+ if max_layers > 0 and layer > max_layers:
+ return max_layers
+ return layer if layer > 0 else 0
- return int(_current_layer)
+def calculate_max_layers(
+ object_height: float,
+ layer_height: float,
+ first_layer_height: float,
+) -> int:
+ """Calculate total layers from metadata dimensions (fallback when
+ Klipper does not provide ``print_stats.info.total_layer``).
-def estimate_print_time(seconds: int) -> list:
- """Convert time in seconds format to days, hours, minutes, seconds.
+ Formula ported from Mainsail ``getPrintMaxLayers`` getter:
+ ``src/store/printer/getters.ts`` in ``mainsail-crew/mainsail``.
- Args:
- seconds (int): Seconds
+ Uses ``ceil((object_height - first_layer_height) / layer_height + 1)``.
Returns:
- list: list that contains the converted information [days, hours, minutes, seconds]
+ int: Total layer count, or 0 if metadata is insufficient.
"""
- num_min, seconds = divmod(seconds, 60)
- num_hours, minutes = divmod(num_min, 60)
- days, hours = divmod(num_hours, 24)
- return [days, hours, minutes, seconds]
-
+ if layer_height <= 0 or object_height <= 0:
+ return 0
+ return max(1, math.ceil((object_height - first_layer_height) / layer_height + 1))
-def normalize(value, r_min=0.0, r_max=1.0, t_min=0.0, t_max=100):
- """Normalize values between a rage"""
- # https://stats.stackexchange.com/questions/281162/scale-a-number-between-a-range
- c1 = (value - r_min) / (r_max - r_min)
- c2 = (t_max - t_min) + t_min
- return c1 * c2
+def estimate_print_time(seconds: int) -> list[int]:
+ """Convert *seconds* to ``[days, hours, minutes, seconds]``."""
+ num_min, secs = divmod(seconds, 60)
+ num_hours, mins = divmod(num_min, 60)
+ days, hours = divmod(num_hours, 24)
+ return [days, hours, mins, secs]
-def check_filepath_permission(filepath, access_type: int = os.R_OK) -> bool:
- # if not isinstance(filepath, pathlib.Path):
- """Checks for file path access
- Args:
- filepath (str | pathlib.Path): path to file
- access_type (int, optional): _description_. Defaults to os.R_OK.
+def normalize(
+ value: float,
+ r_min: float = 0.0,
+ r_max: float = 1.0,
+ t_min: float = 0.0,
+ t_max: float = 100.0,
+) -> float:
+ """Scale *value* from range [r_min, r_max] into [t_min, t_max]."""
+ return (value - r_min) / (r_max - r_min) * (t_max - t_min) + t_min
- ***
- #### **Access type can be:**
+def check_filepath_permission(
+ filepath: str | pathlib.Path, access_type: int = os.R_OK
+) -> bool:
+ """Check whether *filepath* exists and has the requested access.
- - F_OK -> Checks file existence on path
- - R_OK -> Checks if file is readable
- - W_OK -> Checks if file is Writable
- - X_OK -> Checks if file can be executed
+ Args:
+ filepath: Path to file.
+ access_type: ``os.F_OK`` (existence), ``os.R_OK`` (read),
+ ``os.W_OK`` (write), or ``os.X_OK`` (execute).
- ***
Returns:
- bool: _description_
- """ # return False
- if not os.path.isfile(filepath):
- return False
- return os.access(filepath, access_type)
+ ``True`` if the file exists and satisfies *access_type*.
+ """
+ path = pathlib.Path(filepath)
+ return path.is_file() and os.access(path, access_type)
-def check_dir_existence(
- directory: typing.Union[str, pathlib.Path],
-) -> bool:
- """Check if a directory exists. Returns a true if it exists"""
- if isinstance(directory, pathlib.Path):
- return bool(directory.is_dir())
- return bool(os.path.isdir(directory))
+def check_dir_existence(directory: str | pathlib.Path) -> bool:
+ """Return ``True`` if *directory* exists and is a directory."""
+ return pathlib.Path(directory).is_dir()
def check_file_on_path(
- path: typing.Union[typing.LiteralString, pathlib.Path],
- filename: typing.Union[typing.LiteralString, pathlib.Path],
+ path: str | pathlib.Path,
+ filename: str | pathlib.Path,
) -> bool:
- """Check if file exists on path. Returns true if file exists on that specified directory"""
- _filepath = os.path.join(path, filename)
- return os.path.exists(_filepath)
-
+ """Return ``True`` if *filename* exists under *path*."""
+ return (pathlib.Path(path) / filename).exists()
-def get_file_loc(filename) -> pathlib.Path: ...
+def get_file_name(filename: str | None) -> str:
+ """Extract the basename from a file path (handles ``/`` and ``\\``).
-def get_file_name(filename: typing.Optional[str]) -> str:
- # If filename is None or empty, return empty string instead of None
+ Returns:
+ The last path component, or ``""`` if *filename* is falsy.
+ """
if not filename:
return ""
- # Remove trailing slashes or backslashes
- filename = filename.rstrip("/\\")
-
- # Normalize Windows backslashes to forward slashes
- filename = filename.replace("\\", "/")
-
- parts = filename.split("/")
-
- # Split and return the last path component
- return parts[-1] if filename else ""
-
-
-# def get_hash(data) -> hashlib._Hash:
-# hash = hashlib.sha256()
-# hash.update(data.encode())
-# hash.digest()
-# return hash
-
-
-def digest_hash() -> None: ...
+ return pathlib.PurePosixPath(filename.replace("\\", "/")).name
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..fdecefd3 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,28 +352,37 @@ 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
+ if not _connected or self.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))
return True
@@ -329,7 +390,7 @@ def send_request(self, method: str, params: dict = {}) -> bool:
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 +507,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 +666,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 +680,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 +793,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 +824,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 +850,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 be84c8bc..1568c449 100644
--- a/BlocksScreen/lib/panels/controlTab.py
+++ b/BlocksScreen/lib/panels/controlTab.py
@@ -1,10 +1,10 @@
from __future__ import annotations
+import logging
import re
import typing
from functools import partial
-from helper_methods import normalize
from lib.moonrakerComm import MoonWebSocket
from lib.panels.widgets.numpadPage import CustomNumpad
from lib.panels.widgets.optionCardWidget import OptionCard
@@ -17,6 +17,8 @@
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"""
@@ -386,6 +388,11 @@ def on_slidePage_request(
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))
@@ -395,18 +402,16 @@ def on_slidePage_request(
@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
+ """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
@@ -427,19 +432,20 @@ def create_display_button(self, name: str) -> DisplayButton:
return display_button
def handle_printcoreupdate(self, value: dict):
- if value["swapping"] == "idle":
+ _swapping = value.get("swapping")
+ if _swapping is None or _swapping == "idle":
return
- if value["swapping"] == "in_pos":
+ 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 value["swapping"] == "unloading":
+ if _swapping == "unloading":
self.call_load_panel.emit(True, "Unloading print core")
- if value["swapping"] == "cleaning":
+ if _swapping == "cleaning":
self.call_load_panel.emit(True, "Cleaning print core")
def _handle_gcode_response(self, messages: list):
@@ -508,6 +514,10 @@ def on_numpad_request(
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)
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..cbfb0348 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,7 @@ def _handle_notify_service_state_changed_message(
if self._popup_toggle:
return
service_entry: dict = entry[0]
- service_name, service_info = service_entry.popitem()
+ service_name, service_info = next(iter(service_entry.items()))
self.show_notifications.emit(
"mainwindow",
str(
@@ -703,12 +727,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 +780,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 +857,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 +887,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 +914,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 65927aae..c4ea5f53 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(
@@ -267,7 +268,6 @@ def __init__(
self.confirmPage_widget.on_delete.connect(self.delete_file)
self.change_page(self.indexOf(self.print_page)) # force set the initial page
self.save_config_btn.clicked.connect(self.save_config)
- self.BasePopup_z_offset.accepted.connect(self.update_configuration_file)
@QtCore.pyqtSlot(str, dict, name="on_print_stats_update")
@QtCore.pyqtSlot(str, float, name="on_print_stats_update")
@@ -292,6 +292,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 +315,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))
@@ -321,8 +329,12 @@ def on_slidePage_request(
@QtCore.pyqtSlot(str, str, name="delete_file")
@QtCore.pyqtSlot(str, name="delete_file")
def delete_file(self, filename: str, directory: str = "gcodes") -> None:
- """Handle Delete file signal, shows confirmation dialog"""
+ """Handle Delete file signal, shows confirmation dialog."""
self.BasePopup.set_message("Are you sure you want to delete this file?")
+ try:
+ self.BasePopup.accepted.disconnect()
+ except (RuntimeError, TypeError):
+ pass
self.BasePopup.accepted.connect(
lambda: self._on_delete_file_confirmed(filename, directory)
)
@@ -342,13 +354,21 @@ def save_config(self) -> None:
"The machine will restart."
)
self.BasePopup_z_offset.cancel_button_text("Later")
+ try:
+ self.BasePopup_z_offset.accepted.disconnect(self.update_configuration_file)
+ except (RuntimeError, TypeError):
+ pass
+ self.BasePopup_z_offset.accepted.connect(self.update_configuration_file)
self.BasePopup_z_offset.open()
- def update_configuration_file(self):
- """Runs the `SAVE_CONFIG` gcode"""
+ def update_configuration_file(self) -> None:
+ """Run ``Z_OFFSET_APPLY_PROBE`` followed by ``SAVE_CONFIG``."""
+ try:
+ self.BasePopup_z_offset.accepted.disconnect(self.update_configuration_file)
+ except (RuntimeError, TypeError):
+ pass
self.run_gcode_signal.emit("Z_OFFSET_APPLY_PROBE")
self.run_gcode_signal.emit("SAVE_CONFIG")
- self.BasePopup_z_offset.disconnect()
@QtCore.pyqtSlot(str, list, name="activate_save_button")
def activate_save_button(self, name: str, value: list) -> None:
@@ -357,15 +377,19 @@ def activate_save_button(self, name: str, value: list) -> None:
return
if name == "homing_origin":
- self._active_z_offset = value[2]
- self.save_config_btn.setVisible(value[2] != 0)
+ if len(value) > 2:
+ self._active_z_offset = value[2]
+ self.save_config_btn.setVisible(value[2] != 0)
def _on_delete_file_confirmed(self, filename: str, directory: str) -> None:
- """Handle confirmed file deletion after user accepted the dialog"""
+ """Handle confirmed file deletion after user accepted the dialog."""
self.file_data.on_request_delete_file(filename, directory)
self.request_back.emit()
self.filesPage_widget.reset_dir()
- self.BasePopup.disconnect()
+ try:
+ self.BasePopup.accepted.disconnect()
+ except (RuntimeError, TypeError):
+ pass
def setProperty(self, name: str, value: typing.Any) -> bool:
"""Intercept the set property method
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/babystepPage.py b/BlocksScreen/lib/panels/widgets/babystepPage.py
index 273e8f9c..24e04b20 100644
--- a/BlocksScreen/lib/panels/widgets/babystepPage.py
+++ b/BlocksScreen/lib/panels/widgets/babystepPage.py
@@ -1,3 +1,4 @@
+import logging
import typing
from lib.utils.blocks_label import BlocksLabel
@@ -5,8 +6,20 @@
from lib.utils.icon_button import IconButton
from PyQt6 import QtCore, QtGui, QtWidgets
+logger = logging.getLogger(__name__)
+
+# Button definitions: (label, value, object_name, initially_checked)
+_OFFSET_STEPS: list[tuple[str, float, str, bool]] = [
+ ("0.1 mm", 0.1, "bbp_nozzle_offset_1", True),
+ ("0.05 mm", 0.05, "bbp_nozzle_offset_05", False),
+ ("0.025 mm", 0.025, "bbp_nozzle_offset_025", False),
+ ("0.01 mm", 0.01, "bbp_nozzle_offset_01", False),
+]
+
class BabystepPage(QtWidgets.QWidget):
+ """Page for adjusting Z offset in small increments during a print."""
+
request_back: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
name="request_back"
)
@@ -24,19 +37,18 @@ def __init__(self, parent) -> None:
self.setTabletTracking(True)
self.setMouseTracking(True)
- self.setupUI()
+ self._baby_stepchange = False
+ self._z_offset_text: float = 0.0
+ self._pending_z_offset: float = 0.0
+
+ self._setupUI()
self.bbp_mvup.clicked.connect(self.on_move_nozzle_close)
self.bbp_mvdown.clicked.connect(self.on_move_nozzle_away)
self.babystep_back_btn.clicked.connect(self.request_back.emit)
- self.bbp_nozzle_offset_01.toggled.connect(self.handle_z_offset_change)
- self.bbp_nozzle_offset_025.toggled.connect(self.handle_z_offset_change)
- self.bbp_nozzle_offset_05.toggled.connect(self.handle_z_offset_change)
- self.bbp_nozzle_offset_1.toggled.connect(self.handle_z_offset_change)
- self._baby_stepchange = False
@property
def baby_stepchange(self):
- """Returns if the babystep was changed during print"""
+ """Returns if the babystep was changed during print."""
return self._baby_stepchange
@baby_stepchange.setter
@@ -47,84 +59,107 @@ def baby_stepchange(self, value: bool) -> None:
@QtCore.pyqtSlot(name="on_move_nozzle_close")
def on_move_nozzle_close(self) -> None:
- """Move the nozzle closer to the print plate
- by the amount set in **` self._z_offset`**
- """
- self.run_gcode.emit(
- f"SET_GCODE_OFFSET Z_ADJUST=-{self._z_offset} MOVE=1" # Z_ADJUST adds the value to the existing offset
- )
+ """Move the nozzle closer to the print plate."""
+ self.run_gcode.emit(f"SET_GCODE_OFFSET Z_ADJUST=-{self._z_offset} MOVE=1")
+ self._pending_z_offset -= self._z_offset
+ self.bbp_z_offset_current_value.setText(f"Z: {self._pending_z_offset:.3f}mm")
self._baby_stepchange = True
@QtCore.pyqtSlot(name="on_move_nozzle_away")
def on_move_nozzle_away(self) -> None:
- """Slot for Babystep button to get far from the
- bed by **` self._z_offset`** amount
- """
- self.run_gcode.emit(
- f"SET_GCODE_OFFSET Z_ADJUST=+{self._z_offset} MOVE=1" # Z_ADJUST adds the value to the existing offset
- )
+ """Move the nozzle away from the print plate."""
+ self.run_gcode.emit(f"SET_GCODE_OFFSET Z_ADJUST=+{self._z_offset} MOVE=1")
+ self._pending_z_offset += self._z_offset
+ self.bbp_z_offset_current_value.setText(f"Z: {self._pending_z_offset:.3f}mm")
self._baby_stepchange = True
@QtCore.pyqtSlot(name="handle_z_offset_change")
def handle_z_offset_change(self) -> None:
- """Helper method for changing the value for Babystep.
-
- When a button is clicked, and the button has the mm value i the text,
- it'll change the internal value **z_offset** to the same has the button
-
- ***
-
- Possible values are: 0.01, 0.025, 0.05, 0.1 **mm**
- """
+ """Update step size from the clicked offset button text."""
_sender: QtCore.QObject | None = self.sender()
- if self._z_offset == float(_sender.text()[:-3]):
+ if _sender is None:
+ return
+ if not isinstance(_sender, QtWidgets.QAbstractButton):
+ return
+ try:
+ _value = float(_sender.text()[:-3])
+ except ValueError:
+ logger.warning(
+ "handle_z_offset_change: could not parse button text %r",
+ _sender.text(),
+ )
+ return
+ if self._z_offset == _value:
return
- self._z_offset = float(_sender.text()[:-3])
+ self._z_offset = _value
def on_gcode_move_update(self, name: str, value: list) -> None:
- """Handle gcode move updates"""
+ """Handle gcode move updates from Klipper."""
if not value:
return
- if name == "homing_origin":
- self._z_offset_text = value[2]
- self.bbp_z_offset_current_value.setText(f"Z: {self._z_offset_text:.3f}mm")
-
- def setupUI(self):
- """Setup babystep page ui"""
- self.bbp_offset_value_selector_group = QtWidgets.QButtonGroup(self)
- self.bbp_offset_value_selector_group.setExclusive(True)
- sizePolicy = QtWidgets.QSizePolicy(
+ if name == "homing_origin" and len(value) > 2:
+ confirmed = value[2]
+ self._z_offset_text = confirmed
+ self.bbp_z_offset_title_label.setText(f"Z: {confirmed:.3f}mm")
+ # Always sync pending offset to Klipper's confirmed value
+ self._pending_z_offset = confirmed
+ self.bbp_z_offset_current_value.setText(f"Z: {confirmed:.3f}mm")
+
+ def _create_offset_button(
+ self,
+ parent: QtWidgets.QWidget,
+ label: str,
+ obj_name: str,
+ checked: bool,
+ font: QtGui.QFont,
+ ) -> BlocksCustomCheckButton:
+ """Create a single offset-step check button."""
+ btn = BlocksCustomCheckButton(parent=parent)
+ btn.setMinimumSize(QtCore.QSize(100, 70))
+ btn.setMaximumSize(QtCore.QSize(100, 70))
+ btn.setText(label)
+ btn.setFont(font)
+ btn.setCheckable(True)
+ btn.setChecked(checked)
+ btn.setFlat(True)
+ btn.setProperty("button_type", "")
+ btn.setObjectName(obj_name)
+ btn.toggled.connect(self.handle_z_offset_change)
+ return btn
+
+ def _setupUI(self) -> None:
+ """Setup babystep page UI."""
+ btn_group = QtWidgets.QButtonGroup(self)
+ btn_group.setExclusive(True)
+
+ size_policy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Policy.MinimumExpanding,
QtWidgets.QSizePolicy.Policy.MinimumExpanding,
)
- sizePolicy.setHorizontalStretch(1)
- sizePolicy.setVerticalStretch(1)
- sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
- self.setSizePolicy(sizePolicy)
+ size_policy.setHorizontalStretch(1)
+ size_policy.setVerticalStretch(1)
+ size_policy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
+ self.setSizePolicy(size_policy)
self.setMinimumSize(QtCore.QSize(710, 400))
- self.setMaximumSize(
- QtCore.QSize(720, 420)
- ) # This sets the maximum width of the entire page
+ self.setMaximumSize(QtCore.QSize(720, 420))
self.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight)
- # Main Vertical Layout for the entire page
- self.verticalLayout = QtWidgets.QVBoxLayout(self)
- self.verticalLayout.setObjectName("verticalLayout")
+ main_vlayout = QtWidgets.QVBoxLayout(self)
+
+ header = QtWidgets.QHBoxLayout()
- # Header Layout
- self.bbp_header_layout = QtWidgets.QHBoxLayout()
- self.bbp_header_layout.setObjectName("bbp_header_layout")
- self.bbp_header_title = QtWidgets.QLabel(parent=self)
- sizePolicy.setHeightForWidth(
- self.bbp_header_title.sizePolicy().hasHeightForWidth()
+ header.addItem(
+ QtWidgets.QSpacerItem(
+ 60,
+ 20,
+ QtWidgets.QSizePolicy.Policy.Expanding,
+ QtWidgets.QSizePolicy.Policy.Minimum,
+ )
)
- self.bbp_header_title.setSizePolicy(sizePolicy)
- self.bbp_header_title.setMinimumSize(QtCore.QSize(200, 60))
- self.bbp_header_title.setMaximumSize(QtCore.QSize(16777215, 60))
- font = QtGui.QFont()
- font.setPointSize(22)
- self.bbp_header_title.setFont(font)
+
+ title_font = QtGui.QFont()
+ title_font.setPointSize(22)
palette = QtGui.QPalette()
palette.setColor(
palette.ColorGroup.All,
@@ -136,187 +171,68 @@ def setupUI(self):
palette.ColorRole.WindowText,
QtGui.QColor("#FFFFFF"),
)
+
+ self.bbp_header_title = QtWidgets.QLabel("Babystep", parent=self)
+ self.bbp_header_title.setSizePolicy(size_policy)
+ self.bbp_header_title.setMinimumSize(QtCore.QSize(200, 60))
+ self.bbp_header_title.setMaximumSize(QtCore.QSize(16777215, 60))
+ self.bbp_header_title.setFont(title_font)
self.bbp_header_title.setAutoFillBackground(True)
self.bbp_header_title.setBackgroundRole(palette.ColorRole.Window)
self.bbp_header_title.setPalette(palette)
- self.bbp_header_title.setText("Babystep")
self.bbp_header_title.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
- self.bbp_header_title.setObjectName("bbp_header_title")
+ header.addWidget(self.bbp_header_title, 0, QtCore.Qt.AlignmentFlag.AlignCenter)
- spacerItem = QtWidgets.QSpacerItem(
- 60,
- 20,
- QtWidgets.QSizePolicy.Policy.Expanding,
- QtWidgets.QSizePolicy.Policy.Minimum,
- )
- self.bbp_header_layout.addItem(spacerItem)
-
- self.bbp_header_layout.addWidget(
- self.bbp_header_title,
- 0,
- QtCore.Qt.AlignmentFlag.AlignCenter,
- )
self.babystep_back_btn = IconButton(parent=self)
- sizePolicy.setHeightForWidth(
- self.babystep_back_btn.sizePolicy().hasHeightForWidth()
- )
- self.babystep_back_btn.setSizePolicy(sizePolicy)
+ self.babystep_back_btn.setSizePolicy(size_policy)
self.babystep_back_btn.setMinimumSize(QtCore.QSize(60, 60))
self.babystep_back_btn.setMaximumSize(QtCore.QSize(60, 60))
- self.babystep_back_btn.setText("")
self.babystep_back_btn.setFlat(True)
self.babystep_back_btn.setPixmap(QtGui.QPixmap(":/ui/media/btn_icons/back.svg"))
- self.babystep_back_btn.setObjectName("babystep_back_btn")
-
- self.bbp_header_layout.addWidget(
+ header.addWidget(
self.babystep_back_btn,
0,
QtCore.Qt.AlignmentFlag.AlignRight | QtCore.Qt.AlignmentFlag.AlignVCenter,
)
- self.bbp_header_layout.setStretch(0, 1)
- self.verticalLayout.addLayout(self.bbp_header_layout)
-
- self.main_content_horizontal_layout = QtWidgets.QHBoxLayout()
- self.main_content_horizontal_layout.setObjectName(
- "main_content_horizontal_layout"
- )
-
- # Offset Steps Buttons Group Box (LEFT side of main_content_horizontal_layout)
- self.bbp_offset_steps_buttons_group_box = QtWidgets.QGroupBox(self)
- font = QtGui.QFont()
- font.setPointSize(14)
- self.bbp_offset_steps_buttons_group_box.setFont(font)
- self.bbp_offset_steps_buttons_group_box.setFlat(True)
- # Add stylesheet to explicitly remove any border from the QGroupBox
- self.bbp_offset_steps_buttons_group_box.setStyleSheet(
- "QGroupBox { border: none; }"
- )
- self.bbp_offset_steps_buttons_group_box.setObjectName(
- "bbp_offset_steps_buttons_group_box"
- )
-
- self.bbp_offset_steps_buttons = QtWidgets.QVBoxLayout(
- self.bbp_offset_steps_buttons_group_box
- )
- self.bbp_offset_steps_buttons.setContentsMargins(9, 9, 9, 9)
- self.bbp_offset_steps_buttons.setObjectName("bbp_offset_steps_buttons")
-
- # 0.1mm button
- self.bbp_nozzle_offset_1 = BlocksCustomCheckButton(
- parent=self.bbp_offset_steps_buttons_group_box
- )
- self.bbp_nozzle_offset_1.setMinimumSize(QtCore.QSize(100, 70))
- self.bbp_nozzle_offset_1.setMaximumSize(QtCore.QSize(100, 70))
- self.bbp_nozzle_offset_1.setText("0.1 mm")
-
- font = QtGui.QFont()
- font.setPointSize(14)
- self.bbp_nozzle_offset_1.setFont(font)
- self.bbp_nozzle_offset_1.setCheckable(True)
- self.bbp_nozzle_offset_1.setChecked(True) # Set as initially checked
- self.bbp_nozzle_offset_1.setFlat(True)
- self.bbp_nozzle_offset_1.setProperty("button_type", "")
- self.bbp_nozzle_offset_1.setObjectName("bbp_nozzle_offset_1")
- self.bbp_offset_value_selector_group.addButton(self.bbp_nozzle_offset_1)
- self.bbp_offset_steps_buttons.addWidget(
- self.bbp_nozzle_offset_1,
- 0,
- QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter,
- )
-
- # 0.05mm button
- self.bbp_nozzle_offset_05 = BlocksCustomCheckButton(
- parent=self.bbp_offset_steps_buttons_group_box
- )
- self.bbp_nozzle_offset_05.setMinimumSize(QtCore.QSize(100, 70))
- self.bbp_nozzle_offset_05.setMaximumSize(
- QtCore.QSize(100, 70)
- ) # Increased max width by 5 pixels
- self.bbp_nozzle_offset_05.setText("0.05 mm")
-
- font = QtGui.QFont()
- font.setPointSize(14)
- self.bbp_nozzle_offset_05.setFont(font)
- self.bbp_nozzle_offset_05.setCheckable(True)
- self.bbp_nozzle_offset_05.setFlat(True)
- self.bbp_nozzle_offset_05.setProperty("button_type", "")
- self.bbp_nozzle_offset_05.setObjectName("bbp_nozzle_offset_05")
- self.bbp_offset_value_selector_group.addButton(self.bbp_nozzle_offset_05)
- self.bbp_offset_steps_buttons.addWidget(
- self.bbp_nozzle_offset_05,
- 0,
- QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter,
- )
-
- # Line separator for 0.1mm - set size policy to expanding horizontally
-
- # 0.01mm button
- self.bbp_nozzle_offset_01 = BlocksCustomCheckButton(
- parent=self.bbp_offset_steps_buttons_group_box
- )
- self.bbp_nozzle_offset_01.setMinimumSize(QtCore.QSize(100, 70))
- self.bbp_nozzle_offset_01.setMaximumSize(
- QtCore.QSize(100, 70)
- ) # Increased max width by 5 pixels
- self.bbp_nozzle_offset_01.setText("0.01 mm")
-
- font = QtGui.QFont()
- font.setPointSize(14)
- self.bbp_nozzle_offset_01.setFont(font)
- self.bbp_nozzle_offset_01.setCheckable(True)
- self.bbp_nozzle_offset_01.setFlat(True)
- self.bbp_nozzle_offset_01.setProperty("button_type", "")
- self.bbp_nozzle_offset_01.setObjectName("bbp_nozzle_offset_01")
- self.bbp_offset_value_selector_group.addButton(self.bbp_nozzle_offset_01)
- self.bbp_offset_steps_buttons.addWidget(
- self.bbp_nozzle_offset_01,
- 0,
- QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter,
- )
-
- # 0.025mm button
- self.bbp_nozzle_offset_025 = BlocksCustomCheckButton(
- parent=self.bbp_offset_steps_buttons_group_box
- )
- self.bbp_nozzle_offset_025.setMinimumSize(QtCore.QSize(100, 70))
- self.bbp_nozzle_offset_025.setMaximumSize(
- QtCore.QSize(100, 70)
- ) # Increased max width by 5 pixels
- self.bbp_nozzle_offset_025.setText("0.025 mm")
-
- font = QtGui.QFont()
- font.setPointSize(14)
- self.bbp_nozzle_offset_025.setFont(font)
- self.bbp_nozzle_offset_025.setCheckable(True)
- self.bbp_nozzle_offset_025.setFlat(True)
- self.bbp_nozzle_offset_025.setProperty("button_type", "")
- self.bbp_nozzle_offset_025.setObjectName("bbp_nozzle_offset_025")
- self.bbp_offset_value_selector_group.addButton(self.bbp_nozzle_offset_025)
- self.bbp_offset_steps_buttons.addWidget(
- self.bbp_nozzle_offset_025,
- 0,
- QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter,
- )
-
- # Line separator for 0.025mm - set size policy to expanding horizontally
-
- # Set the layout for the group box
- self.bbp_offset_steps_buttons_group_box.setLayout(self.bbp_offset_steps_buttons)
- # Add the group box to the main content horizontal layout FIRST for left placement
- self.main_content_horizontal_layout.addWidget(
- self.bbp_offset_steps_buttons_group_box
- )
-
- # Graphic and Current Value Frame (This will now be in the MIDDLE)
- self.frame_2 = QtWidgets.QFrame(parent=self)
- sizePolicy.setHeightForWidth(self.frame_2.sizePolicy().hasHeightForWidth())
- self.frame_2.setSizePolicy(sizePolicy)
- self.frame_2.setMinimumSize(QtCore.QSize(350, 160))
- self.frame_2.setMaximumSize(QtCore.QSize(350, 160))
- self.frame_2.setFrameShape(QtWidgets.QFrame.Shape.NoFrame)
- self.frame_2.setFrameShadow(QtWidgets.QFrame.Shadow.Raised)
- self.frame_2.setObjectName("frame_2")
- self.bbp_babystep_graphic = QtWidgets.QLabel(parent=self.frame_2)
+ header.setStretch(0, 1)
+ main_vlayout.addLayout(header)
+
+ # --- Main content (3 columns) ---
+ content = QtWidgets.QHBoxLayout()
+
+ # Column 1: offset step buttons (highest → lowest)
+ group_box = QtWidgets.QGroupBox(self)
+ btn_font = QtGui.QFont()
+ btn_font.setPointSize(14)
+ group_box.setFont(btn_font)
+ group_box.setFlat(True)
+ group_box.setStyleSheet("QGroupBox { border: none; }")
+
+ steps_layout = QtWidgets.QVBoxLayout(group_box)
+ steps_layout.setContentsMargins(9, 9, 9, 9)
+
+ center = (
+ QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter
+ )
+ for label, _value, obj_name, checked in _OFFSET_STEPS:
+ btn = self._create_offset_button(
+ group_box, label, obj_name, checked, btn_font
+ )
+ setattr(self, obj_name, btn)
+ btn_group.addButton(btn)
+ steps_layout.addWidget(btn, 0, center)
+
+ content.addWidget(group_box)
+
+ # Column 2: graphic + Z offset labels
+ frame = QtWidgets.QFrame(parent=self)
+ frame.setSizePolicy(size_policy)
+ frame.setMinimumSize(QtCore.QSize(350, 160))
+ frame.setMaximumSize(QtCore.QSize(350, 160))
+ frame.setFrameShape(QtWidgets.QFrame.Shape.NoFrame)
+ frame.setFrameShadow(QtWidgets.QFrame.Shadow.Raised)
+
+ self.bbp_babystep_graphic = QtWidgets.QLabel(parent=frame)
self.bbp_babystep_graphic.setGeometry(QtCore.QRect(0, 30, 371, 121))
self.bbp_babystep_graphic.setLayoutDirection(
QtCore.Qt.LayoutDirection.RightToLeft
@@ -326,38 +242,25 @@ def setupUI(self):
)
self.bbp_babystep_graphic.setScaledContents(False)
self.bbp_babystep_graphic.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
- self.bbp_babystep_graphic.setObjectName("bbp_babystep_graphic")
- # === NEW LABEL ADDED HERE ===
- # This is the title label that appears above the red value box.
+ grey_font = QtGui.QFont()
+ grey_font.setPointSize(12)
self.bbp_z_offset_title_label = QtWidgets.QLabel(parent=self)
- # Position it just above the red box. Red box is at y=70, so y=40 is appropriate.
- self.bbp_z_offset_title_label.setGeometry(QtCore.QRect(100, 40, 200, 30))
- font = QtGui.QFont()
- font.setPointSize(12)
-
- self.bbp_z_offset_title_label.setFont(font)
- # Set color to white to be visible on the dark background
+ self.bbp_z_offset_title_label.setFont(grey_font)
self.bbp_z_offset_title_label.setStyleSheet(
"color: gray; background: transparent;"
)
- self.bbp_z_offset_title_label.setObjectName("bbp_z_offset_title_label")
- self.bbp_z_offset_title_label.setText("Z: 0.000mm")
+ self.bbp_z_offset_title_label.setText(f"Z: {self._z_offset_text:.3f}mm")
self.bbp_z_offset_title_label.setGeometry(420, 270, 200, 30)
- # === END OF NEW LABEL ===
-
- self.bbp_z_offset_current_value = BlocksLabel(parent=self.frame_2)
+ white_font = QtGui.QFont()
+ white_font.setPointSize(14)
+ self.bbp_z_offset_current_value = BlocksLabel(parent=frame)
self.bbp_z_offset_current_value.setGeometry(QtCore.QRect(100, 70, 200, 60))
- sizePolicy.setHeightForWidth(
- self.bbp_z_offset_current_value.sizePolicy().hasHeightForWidth()
- )
- self.bbp_z_offset_current_value.setSizePolicy(sizePolicy)
+ self.bbp_z_offset_current_value.setSizePolicy(size_policy)
self.bbp_z_offset_current_value.setMinimumSize(QtCore.QSize(150, 60))
self.bbp_z_offset_current_value.setMaximumSize(QtCore.QSize(200, 60))
- font = QtGui.QFont()
- font.setPointSize(14)
- self.bbp_z_offset_current_value.setFont(font)
+ self.bbp_z_offset_current_value.setFont(white_font)
self.bbp_z_offset_current_value.setStyleSheet(
"background: transparent; color: white;"
)
@@ -368,87 +271,59 @@ def setupUI(self):
self.bbp_z_offset_current_value.setAlignment(
QtCore.Qt.AlignmentFlag.AlignCenter
)
- self.bbp_z_offset_current_value.setObjectName("bbp_z_offset_current_value")
- # Add graphic frame AFTER the offset buttons group box
- self.main_content_horizontal_layout.addWidget(
- self.frame_2,
- 0,
- QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter,
+
+ content.addWidget(frame, 0, center)
+
+ # Spacer before move buttons
+ content.addItem(
+ QtWidgets.QSpacerItem(
+ 40,
+ 20,
+ QtWidgets.QSizePolicy.Policy.Expanding,
+ QtWidgets.QSizePolicy.Policy.Minimum,
+ )
)
- # Move Buttons Layout (This will now be on the RIGHT)
- self.bbp_buttons_layout = QtWidgets.QVBoxLayout()
- self.bbp_buttons_layout.setContentsMargins(5, 5, 5, 5)
- self.bbp_buttons_layout.setObjectName("bbp_buttons_layout")
+ # Column 3: move up/down buttons
+ move_layout = QtWidgets.QVBoxLayout()
+ move_layout.setContentsMargins(5, 5, 5, 5)
+
self.bbp_mvup = IconButton(parent=self)
- sizePolicy.setHeightForWidth(self.bbp_mvup.sizePolicy().hasHeightForWidth())
- self.bbp_mvup.setSizePolicy(sizePolicy)
+ self.bbp_mvup.setSizePolicy(size_policy)
self.bbp_mvup.setMinimumSize(QtCore.QSize(80, 80))
self.bbp_mvup.setMaximumSize(QtCore.QSize(80, 80))
- self.bbp_mvup.setText("")
self.bbp_mvup.setFlat(True)
self.bbp_mvup.setPixmap(
QtGui.QPixmap(":/baby_step/media/btn_icons/move_nozzle_close.svg")
)
- self.bbp_mvup.setObjectName("bbp_away_from_bed")
- self.bbp_option_button_group = QtWidgets.QButtonGroup(self)
- self.bbp_option_button_group.setObjectName("bbp_option_button_group")
- self.bbp_option_button_group.addButton(self.bbp_mvup)
- self.bbp_buttons_layout.addWidget(
- self.bbp_mvup, 0, QtCore.Qt.AlignmentFlag.AlignRight
- )
+ move_layout.addWidget(self.bbp_mvup, 0, QtCore.Qt.AlignmentFlag.AlignRight)
+
self.bbp_mvdown = IconButton(parent=self)
- sizePolicy.setHeightForWidth(self.bbp_mvdown.sizePolicy().hasHeightForWidth())
- self.bbp_mvdown.setSizePolicy(sizePolicy)
+ self.bbp_mvdown.setSizePolicy(size_policy)
self.bbp_mvdown.setMinimumSize(QtCore.QSize(80, 80))
self.bbp_mvdown.setMaximumSize(QtCore.QSize(80, 80))
- self.bbp_mvdown.setText("")
self.bbp_mvdown.setFlat(True)
self.bbp_mvdown.setPixmap(
QtGui.QPixmap(":/baby_step/media/btn_icons/move_nozzle_away.svg")
)
- self.bbp_mvdown.setObjectName("bbp_close_to_bed")
- self.bbp_option_button_group.addButton(self.bbp_mvdown)
- self.bbp_buttons_layout.addWidget(
- self.bbp_mvdown, 0, QtCore.Qt.AlignmentFlag.AlignRight
- )
- spacerItem = QtWidgets.QSpacerItem(
- 40,
- 20,
- QtWidgets.QSizePolicy.Policy.Expanding,
- QtWidgets.QSizePolicy.Policy.Minimum,
- )
- self.main_content_horizontal_layout.addItem(spacerItem)
+ move_layout.addWidget(self.bbp_mvdown, 0, QtCore.Qt.AlignmentFlag.AlignRight)
- # Add move buttons layout LAST for right placement
- self.main_content_horizontal_layout.addLayout(self.bbp_buttons_layout)
+ content.addLayout(move_layout)
- spacerItem = QtWidgets.QSpacerItem(
- 40,
- 20,
- QtWidgets.QSizePolicy.Policy.Expanding,
- QtWidgets.QSizePolicy.Policy.Minimum,
+ # Trailing spacer
+ content.addItem(
+ QtWidgets.QSpacerItem(
+ 40,
+ 20,
+ QtWidgets.QSizePolicy.Policy.Expanding,
+ QtWidgets.QSizePolicy.Policy.Minimum,
+ )
)
- self.main_content_horizontal_layout.addItem(spacerItem)
-
- # Set stretch factors for main content horizontal layout
- # This will distribute space: offset buttons, graphic frame, move buttons
- self.main_content_horizontal_layout.setStretch(
- 0, 1
- ) # offset_steps_buttons_group_box
- self.main_content_horizontal_layout.setStretch(
- 1, 2
- ) # frame_2 (graphic and current value)
- self.main_content_horizontal_layout.setStretch(
- 2, 0
- ) # bbp_buttons_layout (move buttons)
-
- # Add the main content horizontal layout to the vertical layout
- self.verticalLayout.addLayout(self.main_content_horizontal_layout)
-
- # Set stretch factors for vertical layout (adjust as needed for overall sizing)
- self.verticalLayout.setStretch(
- 1, 1
- ) # This stretch applies to main_content_horizontal_layout
-
- self.setLayout(self.verticalLayout)
+
+ content.setStretch(0, 1) # offset buttons
+ content.setStretch(1, 2) # graphic frame
+ content.setStretch(2, 0) # move buttons
+
+ main_vlayout.addLayout(content)
+ main_vlayout.setStretch(1, 1)
+ self.setLayout(main_vlayout)
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/jobStatusPage.py b/BlocksScreen/lib/panels/widgets/jobStatusPage.py
index 67add6b9..198b670e 100644
--- a/BlocksScreen/lib/panels/widgets/jobStatusPage.py
+++ b/BlocksScreen/lib/panels/widgets/jobStatusPage.py
@@ -2,7 +2,11 @@
import typing
import events
-from helper_methods import calculate_current_layer, estimate_print_time
+from helper_methods import (
+ calculate_current_layer,
+ calculate_max_layers,
+ estimate_print_time,
+)
from lib.panels.widgets.basePopup import BasePopup
from lib.utils.blocks_button import BlocksCustomButton
from lib.utils.blocks_label import BlocksLabel
@@ -57,8 +61,9 @@ class JobStatusWidget(QtWidgets.QWidget):
_internal_print_status: str = ""
_current_file_name: str = ""
- file_metadata: dict = {}
+ file_metadata: dict | None = None
total_layers = "?"
+ _print_duration: float = 0.0
def __init__(self, parent) -> None:
super().__init__(parent)
@@ -110,20 +115,19 @@ def eventFilter(self, sender_obj: QtCore.QObject, event: events.QEvent) -> bool:
return super().eventFilter(sender_obj, event)
def _load_thumbnails(self, *thumbnails) -> None:
- """Pre-load available thumbnails for the current print object"""
- self.thumbnail_graphics = list(
- filter(
- lambda thumb: not thumb.isNull(),
- [QtGui.QPixmap(thumb) for thumb in thumbnails],
- )
- )
+ """Pre-load available thumbnails for the current print object."""
+ loaded = []
+ for thumb in thumbnails:
+ px = QtGui.QPixmap(thumb)
+ if not px.isNull():
+ loaded.append(px)
+ self.thumbnail_graphics = loaded
if not self.thumbnail_graphics:
logger.debug("Unable to load thumbnails, no thumbnails provided")
return
- self.create_thumbnail_widget()
- self.thumbnail_view.installEventFilter(self)
- scene = QtWidgets.QGraphicsScene()
+ self._ensure_thumbnail_widget()
_biggest_thumb = self.thumbnail_graphics[-1]
+ scene = QtWidgets.QGraphicsScene()
self.thumbnail_view.setSceneRect(
QtCore.QRectF(
self.rect().x(),
@@ -147,9 +151,6 @@ def _load_thumbnails(self, *thumbnails) -> None:
)
self.thumbnail_view.setScene(scene)
self.printing_progress_bar.set_inner_pixmap(self.thumbnail_graphics[-1])
- self.printing_progress_bar.thumbnail_clicked.connect(
- self.toggle_thumbnail_expansion
- )
@QtCore.pyqtSlot(name="handle-cancel")
def handleCancel(self) -> None:
@@ -157,6 +158,10 @@ def handleCancel(self) -> None:
self.cancel_print_dialog.set_message(
"Are you sure you \n want to cancel \n the current print job?"
)
+ try:
+ self.cancel_print_dialog.accepted.disconnect(self.print_cancel)
+ except TypeError:
+ pass
self.cancel_print_dialog.accepted.connect(self.print_cancel)
self.cancel_print_dialog.open()
@@ -165,9 +170,10 @@ def on_print_start(self, file: str) -> None:
"""Start a print job, show job status page"""
self._current_file_name = file
self.js_file_name_label.setText(self._current_file_name)
- self.layer_display_button.setText("?")
+ self.layer_display_button.setText("0")
self.print_time_display_button.setText("?")
self.printing_progress_bar.reset()
+ self._print_duration = 0.0
self._internal_print_status = "printing"
self.request_file_info.emit(file)
self.print_start.emit(file)
@@ -184,15 +190,18 @@ def on_print_start(self, file: str) -> None:
logger.debug("Unexpected error while posting print job start event: %s", e)
@QtCore.pyqtSlot(dict, name="on_fileinfo")
- def on_fileinfo(self, fileinfo: dict) -> None:
- """Handle received file information/metadata"""
- if not self.isVisible():
- return
- self.total_layers = str(fileinfo.get("layer_count", "---"))
- self.layer_display_button.setText("---")
+ def on_fileinfo(self, metadata: dict) -> None:
+ """Handle received file information/metadata.
+
+ Loads thumbnail and layer count regardless of visibility so they
+ are ready when the widget is shown.
+ """
+ layer_count = metadata.get("layer_count", -1)
+ self.total_layers = str(layer_count) if layer_count >= 0 else "---"
+ self.layer_display_button.setText("0")
self.layer_display_button.secondary_text = str(self.total_layers)
- self.file_metadata = fileinfo
- self._load_thumbnails(*fileinfo.get("thumbnail_images", []))
+ self.file_metadata = metadata
+ self._load_thumbnails(*metadata.get("thumbnail_images", ()))
@QtCore.pyqtSlot(name="pause_resume_print")
def pause_resume_print(self) -> None:
@@ -212,6 +221,7 @@ def _handle_print_state(self, state: str) -> None:
valid_states = {"printing", "paused"}
invalid_states = {"cancelled", "complete", "error", "standby"}
lstate = state.lower()
+ event_state = lstate
if lstate in valid_states:
self._internal_print_status = lstate
if lstate == "paused":
@@ -219,50 +229,54 @@ def _handle_print_state(self, state: str) -> None:
self.pause_printing_btn.setPixmap(
QtGui.QPixmap(":/ui/media/btn_icons/play.svg")
)
+ event_state = "pause"
elif lstate == "printing":
self.pause_printing_btn.setText("Pause")
self.pause_printing_btn.setPixmap(
QtGui.QPixmap(":/ui/media/btn_icons/pause.svg")
)
+ event_state = "start"
self.pause_printing_btn.setEnabled(True)
self.request_query_print_stats.emit({"print_stats": ["filename"]})
self.call_cancel_panel.emit(False)
self.show_request.emit()
- lstate = "start"
elif lstate in invalid_states:
- if lstate != "standby":
+ if lstate == "complete":
self.print_finish.emit()
+ self.hide_request.emit()
+ # Capture state before clearing so the event carries the real data.
+ _event_file = self._current_file_name
+ _event_meta = self.file_metadata
+ if lstate in invalid_states:
self._internal_print_status = ""
self._current_file_name = ""
self.total_layers = "?"
- self.file_metadata.clear()
- self.hide_request.emit()
- # if hasattr(self, "thumbnail_view"):
- # getattr(self, "thumbnail_view").deleteLater()
+ self._print_duration = 0.0
+ self.file_metadata = None
# Send Event on Print state
- if hasattr(events, str("Print" + lstate.capitalize())):
- event_obj = getattr(events, str("Print" + lstate.capitalize()))
- event = event_obj(self._current_file_name, self.file_metadata)
+ event_class_name = "Print" + event_state.capitalize()
+ if hasattr(events, event_class_name):
+ event_obj = getattr(events, event_class_name)
+ event = event_obj(_event_file, _event_meta)
instance = QtWidgets.QApplication.instance()
if instance:
instance.postEvent(self.window(), event)
return
logger.error(
- "QApplication.instance expected non None value,\
- Unable to post event %s",
- str("Print" + lstate.capitalize()),
+ "QApplication.instance expected non None value,"
+ " Unable to post event %s",
+ event_class_name,
)
@QtCore.pyqtSlot(str, dict, name="on_print_stats_update")
@QtCore.pyqtSlot(str, float, name="on_print_stats_update")
@QtCore.pyqtSlot(str, str, name="on_print_stats_update")
def on_print_stats_update(self, field: str, value: dict | float | str) -> None:
- """Processes the information that comes from the printer object "print_stats"
- Displays information on the ui accordingly.
+ """Process updates from the ``print_stats`` printer object.
Args:
- field (str): The name of the updated field.
- value (dict | float | str): The value for the field.
+ field: The name of the updated field.
+ value: The value for the field.
"""
if isinstance(value, str):
if "state" in field:
@@ -273,29 +287,29 @@ def on_print_stats_update(self, field: str, value: dict | float | str) -> None:
self.js_file_name_label.setText(self._current_file_name)
if self.isVisible():
self.request_file_info.emit(value)
- if not self.file_metadata:
- return
- if not self.isVisible():
- return
+ # Layer info must be processed regardless of visibility so
+ # Klipper's runtime values always override metadata defaults.
if isinstance(value, dict):
- self.layer_fallback = False
- if "total_layer" in value.keys():
- self.total_layers = value["total_layer"]
+ if "total_layer" in value:
if value["total_layer"] is not None:
+ self.total_layers = value["total_layer"]
self.layer_display_button.secondary_text = str(self.total_layers)
-
else:
self.total_layers = "---"
- self.layer_fallback = True
- if "current_layer" in value.keys():
+ if "current_layer" in value:
if value["current_layer"] is not None:
- _current_layer = value["current_layer"]
- self.layer_display_button.setText(f"{int(_current_layer)}")
+ self.layer_display_button.setText(f"{int(value['current_layer'])}")
+ self.layer_fallback = False
else:
self.layer_display_button.setText("---")
self.layer_fallback = True
- elif isinstance(value, float):
+ # print_duration is tracked regardless of visibility (gates Z fallback)
+ if isinstance(value, float) and "print_duration" in field:
+ self._print_duration = value
+ if not self.isVisible():
+ return
+ if isinstance(value, float):
if "total_duration" in field:
_time = estimate_print_time(int(value))
_print_time_string = (
@@ -307,33 +321,49 @@ def on_print_stats_update(self, field: str, value: dict | float | str) -> None:
@QtCore.pyqtSlot(str, list, name="on_gcode_move_update")
def on_gcode_move_update(self, field: str, value: list) -> None:
- """Handle gcode move"""
+ """Z-position fallback for layer count display.
+
+ Only runs when Klipper does not provide
+ ``print_stats.info.current_layer`` (``layer_fallback`` is True)
+ AND ``print_duration > 0``. The ``print_duration`` gate
+ matches Mainsail's ``getPrintCurrentLayer`` getter which
+ prevents layer updates during pre-print procedures (heating,
+ nozzle cleaning).
+ """
if not self.isVisible():
return
- if "gcode_position" in field:
- if self._internal_print_status == "printing":
- if self.layer_fallback:
- object_height = float(self.file_metadata.get("object_height", -1.0))
- layer_height = float(self.file_metadata.get("layer_height", -1.0))
- first_layer_height = float(
- self.file_metadata.get("first_layer_height", -1.0)
- )
- _current_layer = calculate_current_layer(
- z_position=value[2],
- object_height=object_height,
- layer_height=layer_height,
- first_layer_height=first_layer_height,
- )
-
- total_layer = (
- (object_height) / layer_height if layer_height > 0 else -1
- )
- self.layer_display_button.secondary_text = (
- f"{int(total_layer)}" if total_layer != -1 else "---"
- )
- self.layer_display_button.setText(
- f"{int(_current_layer)}" if _current_layer != -1 else "---"
- )
+ if "gcode_position" not in field:
+ return
+ if self._internal_print_status != "printing":
+ return
+ if not self.layer_fallback:
+ return
+ # Mainsail: only calculate layers when print_duration > 0
+ if self._print_duration <= 0:
+ return
+ if len(value) <= 2:
+ return
+ meta = self.file_metadata
+ if not meta:
+ return
+ object_height = float(meta.get("object_height", 0))
+ layer_height = float(meta.get("layer_height", 0))
+ first_layer_height = float(meta.get("first_layer_height", 0))
+ if layer_height <= 0:
+ return
+ # Mainsail getPrintMaxLayers fallback
+ _max_layers = calculate_max_layers(
+ object_height, layer_height, first_layer_height
+ )
+ if _max_layers > 0:
+ self.layer_display_button.secondary_text = str(_max_layers)
+ _current_layer = calculate_current_layer(
+ z_position=value[2],
+ layer_height=layer_height,
+ first_layer_height=first_layer_height,
+ max_layers=_max_layers,
+ )
+ self.layer_display_button.setText(str(_current_layer))
@QtCore.pyqtSlot(str, float, name="virtual_sdcard_update")
@QtCore.pyqtSlot(str, bool, name="virtual_sdcard_update")
@@ -488,17 +518,20 @@ def _setupUI(self) -> None:
)
self.job_content_layout.addLayout(self.job_stats_display_layout)
- def create_thumbnail_widget(self) -> None:
- """Create thumbnail graphics view widget"""
+ def _ensure_thumbnail_widget(self) -> None:
+ """Create thumbnail graphics view widget (once)."""
+ if hasattr(self, "thumbnail_view"):
+ return
self.thumbnail_view = QtWidgets.QGraphicsView()
self.thumbnail_view.setMinimumSize(QtCore.QSize(48, 48))
self.thumbnail_view.setAttribute(
QtCore.Qt.WidgetAttribute.WA_TranslucentBackground, True
)
+ self.thumbnail_view.setStyleSheet(
+ "QGraphicsView { background: transparent; border: none; }"
+ )
self.thumbnail_view.setFrameShape(QtWidgets.QFrame.Shape.NoFrame)
self.thumbnail_view.setFrameShadow(QtWidgets.QFrame.Shadow.Plain)
- self.thumbnail_view.setWindowFlags(QtCore.Qt.WindowType.FramelessWindowHint)
- self.thumbnail_view.setObjectName("thumbnail_scene")
_thumbnail_palette = QtGui.QPalette()
_thumbnail_palette.setColor(
QtGui.QPalette.ColorRole.Window, QtGui.QColor(0, 0, 0, 0)
@@ -507,9 +540,14 @@ def create_thumbnail_widget(self) -> None:
QtGui.QPalette.ColorRole.Base, QtGui.QColor(0, 0, 0, 0)
)
self.thumbnail_view.setPalette(_thumbnail_palette)
+ self.thumbnail_view.setAutoFillBackground(False)
_thumbnail_brush = QtGui.QBrush(QtGui.QColor(0, 0, 0, 0))
_thumbnail_brush.setStyle(QtCore.Qt.BrushStyle.NoBrush)
self.thumbnail_view.setBackgroundBrush(_thumbnail_brush)
+ # Use a transparent viewport widget to prevent black background on eglfs
+ viewport = QtWidgets.QWidget()
+ viewport.setAttribute(QtCore.Qt.WidgetAttribute.WA_TranslucentBackground, True)
+ self.thumbnail_view.setViewport(viewport)
self.thumbnail_view.setRenderHints(
QtGui.QPainter.RenderHint.Antialiasing
| QtGui.QPainter.RenderHint.SmoothPixmapTransform
@@ -521,4 +559,8 @@ def create_thumbnail_widget(self) -> None:
self.thumbnail_view.setObjectName("thumbnail_scene")
self.thumbnail_view_layout = QtWidgets.QHBoxLayout(self)
self.thumbnail_view_layout.addWidget(self.thumbnail_view)
+ self.thumbnail_view.installEventFilter(self)
+ self.printing_progress_bar.thumbnail_clicked.connect(
+ self.toggle_thumbnail_expansion
+ )
self.thumbnail_view.hide()
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 fe2fa180..9253858c 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,18 +424,20 @@ 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)
+ _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")
@@ -466,19 +463,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")
@@ -522,29 +519,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", False)
- if is_active and 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 "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 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 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:
@@ -554,6 +554,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 301c5cd1..6c09a4d1 100644
--- a/BlocksScreen/lib/panels/widgets/tunePage.py
+++ b/BlocksScreen/lib/panels/widgets/tunePage.py
@@ -1,7 +1,6 @@
import re
import typing
-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
@@ -67,7 +66,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,
)
@@ -75,30 +74,26 @@ def __init__(self, parent) -> 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")
@@ -113,6 +108,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