Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions BlocksScreen/BlocksScreen.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import logging
import sys
import typing

from logger import CrashHandler, LogManager, install_crash_handler, setup_logging

Expand Down Expand Up @@ -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)
Expand Down
175 changes: 81 additions & 94 deletions BlocksScreen/helper_methods.py
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separate this changes into a new PR

Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
import ctypes
import enum
import logging
import math
import os
import pathlib
import struct
import typing

logger = logging.getLogger(__name__)

Expand All @@ -21,7 +21,7 @@
libxext = ctypes.CDLL("libXext.so.6")

class DPMSState(enum.Enum):
"""Available DPMS states"""
"""Available DPMS states."""

FAIL = -1
ON = 0
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
31 changes: 15 additions & 16 deletions BlocksScreen/lib/filament.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Class that represents a filament spool

from typing import Optional
from __future__ import annotations

import enum


Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Loading
Loading