Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
94cf29d
Build(deps): Bump requests from 2.32.3 to 2.32.4 in /scripts (#105)
dependabot[bot] Nov 26, 2025
3e3583b
Hugoclsc/feature/GitHub actions (#107)
HugoCLSC Nov 28, 2025
420a3e1
Hugoclsc/feature/GitHub actions (#113)
HugoCLSC Dec 5, 2025
bc1156c
Hugoclsc/feature/GitHub actions (#114)
HugoCLSC Dec 5, 2025
ca9eb86
Hugoclsc/feature/GitHub actions (#115)
HugoCLSC Dec 5, 2025
69deb3b
Hugoclsc/feature/GitHub actions (#116)
HugoCLSC Dec 5, 2025
8229438
Refactor/tests compliance (#117)
HugoCLSC Dec 10, 2025
8e48a21
ADD: added overriedCursor to blank cursor (#118)
Robert0Mart Dec 11, 2025
e852346
ADD: color degrade when ON/OFF (#120)
Robert0Mart Dec 11, 2025
868d0fd
Bugfix label overlap (#121)
Robert0Mart Dec 11, 2025
eb18055
Bugfix: Delete file handling and QDialog class refactoring (#128)
HugoCLSC Dec 12, 2025
260b126
Work fan page (#119)
Robert0Mart Dec 12, 2025
70e2c43
Fix issues intruduced in Bugfix label overlap #121 (#129)
HugoCLSC Dec 12, 2025
f703fc6
Fix Merge problems introduced on the previous pull requests (#131)
HugoCLSC Dec 15, 2025
ca9b7f0
Added standard pull request template (#133)
HugoCLSC Dec 15, 2025
3b91e46
Bugfix: fixed white dot on list_model.py (#130)
Robert0Mart Dec 15, 2025
6f4c3e3
Bugfix thumbnail not working (#123)
Robert0Mart Dec 18, 2025
6fbc375
Bugfix uninitilized variable access introduced on #123 (#141)
HugoCLSC Jan 2, 2026
de9fe96
Refactor `SensorPanel`: replace `QListWidgetItem` with `EntryListMode…
gmmcosta15 Jan 2, 2026
4d973b6
Refactor `filesPage.py`: Changed Files List `QtWidgets.QListWidgetIte…
gmmcosta15 Jan 2, 2026
d013f12
Work group button refactor (#137)
Robert0Mart Jan 2, 2026
db29de9
Bugfix `tunePage`: Add clickability and distinct icons to controllabl…
gmmcosta15 Jan 2, 2026
c3bfa56
Work display info UI (#140)
Robert0Mart Jan 2, 2026
11c8572
Work input shapper rework (#134)
Robert0Mart Jan 5, 2026
5339d51
Work connnectivity update page (#139)
Robert0Mart Jan 12, 2026
0e7a3e7
Bugfix/tab unlocking (#147)
Robert0Mart Jan 12, 2026
4ece704
jobStatusPage: only load filedata when printer is printing (#150)
gmmcosta15 Jan 12, 2026
fa53e8c
Bugfix/inputshaper page (#148)
Robert0Mart Jan 14, 2026
1465ea1
work popup features (#144)
Robert0Mart Jan 14, 2026
dd13e39
Improvement/Apply Z‑offset changes immediately, with an option to sav…
gmmcosta15 Jan 14, 2026
95d956f
Work network priority (#122)
Robert0Mart Jan 14, 2026
102a746
Bugfix: fixed loadwidget default being placeholder (gif) (#145)
Robert0Mart Jan 14, 2026
a9a486b
Bugfix/after merge fix (#151)
Robert0Mart Jan 14, 2026
646d997
Merge branch 'main' into dev
HugoCLSC Jan 14, 2026
1a3f83e
Fans controlling UI wasnt working (#153)
gmmcosta15 Jan 14, 2026
92e7e54
Bugfix/update page & Popup logic (#154)
Robert0Mart Jan 14, 2026
ca26de5
bugfix: ipv4 ip command error fix (#155)
gmmcosta15 Jan 15, 2026
0dd5d86
bugfix: fans_widget on tunepage are stacked (#156)
gmmcosta15 Jan 16, 2026
25f2afe
Merge branch 'main' into dev
HugoCLSC Jan 16, 2026
c17c7ae
Merge remote-tracking branch 'origin/main' into dev
HugoCLSC Jan 16, 2026
a5f3600
bugfix ztilt loadscreen (#158)
Robert0Mart Jan 19, 2026
7b1fdb7
Merge remote-tracking branch 'origin/main' into dev
HugoCLSC Jan 19, 2026
a17c8eb
bugfix: inputshaper load not hiding (#161)
Robert0Mart Jan 19, 2026
14a6084
bugfix/popup show right arrow (#163)
gmmcosta15 Jan 20, 2026
d0144bf
swap lower and raise nozzle icons (#164)
gmmcosta15 Jan 20, 2026
d62879f
Refactor loadscreen on the project (#165)
Robert0Mart Jan 20, 2026
61ff8fd
Merge remote-tracking branch 'origin/main' into dev
HugoCLSC Jan 21, 2026
5a6732c
Deleted Unused `ztilt_state` variable from control tab (#166)
HugoCLSC Jan 21, 2026
3219f82
Merge remote-tracking branch 'origin/main' into dev
HugoCLSC Jan 21, 2026
8ba3d63
ADD: Additional load messages (#169)
Robert0Mart Jan 28, 2026
72fb8a0
Refactor `NetworkWindow` (#174)
gmmcosta15 Feb 4, 2026
3323adf
Fix incorrect file removal (#177)
gmmcosta15 Feb 4, 2026
1a7e074
uild: overhaul Makefile, and expand dev deps (#183)
gmmcosta15 Mar 3, 2026
89f892e
bugfix: fixed missing home before ztilt (#180)
Robert0Mart Mar 3, 2026
bb6ddea
Feat/cancel page (#170)
Robert0Mart Mar 3, 2026
cc33f7b
Refactor the Logging System (#172)
gmmcosta15 Mar 3, 2026
9b9d5fe
Revert "Refactor the Logging System (#172)"
HugoCLSC Mar 3, 2026
66ef05d
Refactor the File Management System (#171)
gmmcosta15 Mar 3, 2026
1216fb8
Bugfix/filament loadscreen (#179)
Robert0Mart Mar 3, 2026
b73445b
feat(network): NetworkManager refactor and NetworkControlWindow
gmmcosta15 Mar 3, 2026
c3abe11
Feat/notification tab (#160)
Robert0Mart Mar 3, 2026
f9db18b
Del extras directory not needed
HugoCLSC Mar 3, 2026
2655049
Feat/eddy calibration panel (#188)
Robert0Mart Mar 3, 2026
1720ae0
feat(logger): overhaul logging system with module-aware names and c…
gmmcosta15 Mar 3, 2026
2d9d96e
fix(makefile): align clean, lint and format-check with CI (#187)
gmmcosta15 Mar 9, 2026
eea8cc0
Bugfix/nozzle calli hide (#189)
Robert0Mart Mar 9, 2026
f15cea3
feat(network): remove ethernet DHCP, require static IP for VLANs (#190)
gmmcosta15 Mar 9, 2026
fc38dc4
Bugfix/eddy current calibration home (#191)
Robert0Mart Mar 9, 2026
11cbfcf
refactor(keyboard): delete unused keys, always show . in all screens,…
gmmcosta15 Mar 9, 2026
1f8a308
USB tools (#193)
HugoCLSC Mar 11, 2026
53e7c41
Merge remote-tracking branch 'origin/main' into dev
HugoCLSC Mar 12, 2026
c05e39a
Fix merge issues
HugoCLSC Mar 12, 2026
615059e
Fix pyqt version
HugoCLSC Mar 12, 2026
f1478cc
Add '-' to label prefix
HugoCLSC Mar 12, 2026
f2ebd88
bugfix(logger): starts logger before the mainwindow (#196)
gmmcosta15 Mar 12, 2026
3915a39
Add legacy dir cleanup on start
HugoCLSC Mar 12, 2026
ce1c06d
Replace pillow dependency with pure PyQt6
HugoCLSC Mar 12, 2026
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
4 changes: 4 additions & 0 deletions .github/workflows/dev-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ jobs:
cache: pip
cache-dependency-path: scripts/requirements-dev.txt

- name: Install system dependencies
if: matrix.test-type == 'pytest'
run: sudo apt-get install -y libgl1 libglib2.0-0 libegl1

- name: Install dependencies
run: |
echo "Installing dependencies"
Expand Down
5 changes: 4 additions & 1 deletion BlocksScreen.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@ host: localhost
port: 7125

[screensaver]
timeout: 5000
timeout: 5000

[usb_manager]
gcodes_dir: ~/printer_data/gcodes/
51 changes: 38 additions & 13 deletions BlocksScreen/BlocksScreen.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,28 @@
import sys
import typing

import logger
from lib.panels.mainWindow import MainWindow
from PyQt6 import QtCore, QtGui, QtWidgets
from logger import CrashHandler, LogManager, install_crash_handler, setup_logging

install_crash_handler()

from lib.panels.mainWindow import MainWindow # noqa: E402
from PyQt6 import QtCore, QtGui, QtWidgets # noqa: E402


class BlocksScreenApp(QtWidgets.QApplication):
"""QApplication subclass that routes unhandled slot exceptions to CrashHandler."""

def notify(self, a0: QtCore.QObject, a1: QtCore.QEvent) -> bool: # type: ignore[override]
try:
return super().notify(a0, a1)
except Exception:
exc_type, exc_value, exc_tb = sys.exc_info()
handler = CrashHandler._instance
if handler is not None and exc_type is not None and exc_value is not None:
handler._exception_hook(exc_type, exc_value, exc_tb)
return False


_logger = logging.getLogger(name="logs/BlocksScreen.log")
QtGui.QGuiApplication.setAttribute(
QtCore.Qt.ApplicationAttribute.AA_SynthesizeMouseForUnhandledTouchEvents,
True,
Expand All @@ -22,13 +39,6 @@
RESET = "\033[0m"


def setup_app_loggers():
"""Setup logger"""
_ = logger.create_logger(name="logs/BlocksScreen.log", level=logging.DEBUG)
_logger = logging.getLogger(name="logs/BlocksScreen.log")
_logger.info("============ BlocksScreen Initializing ============")


def show_splash(window: typing.Optional[QtWidgets.QWidget] = None):
"""Show splash screen on app initialization"""
logo = QtGui.QPixmap("BlocksScreen/BlocksScreen/lib/ui/resources/logoblocks.png")
Expand All @@ -38,13 +48,28 @@ def show_splash(window: typing.Optional[QtWidgets.QWidget] = None):
splash.finish(window)


def on_quit() -> None:
logging.info("Final exit cleanup")
LogManager.shutdown()


if __name__ == "__main__":
setup_app_loggers()
BlocksScreen = QtWidgets.QApplication([])
setup_logging(
filename="logs/BlocksScreen.log",
level=logging.DEBUG,
console_output=True,
console_level=logging.DEBUG,
capture_stderr=True,
capture_stdout=False,
)
_logger = logging.getLogger(__name__)
_logger.info("============ BlocksScreen Initializing ============")
BlocksScreen = BlocksScreenApp([])
BlocksScreen.setApplicationName("BlocksScreen")
BlocksScreen.setApplicationDisplayName("BlocksScreen")
BlocksScreen.setDesktopFileName("BlocksScreen")
main_window = MainWindow()
BlocksScreen.processEvents()
BlocksScreen.aboutToQuit.connect(on_quit)
main_window.show()
sys.exit(BlocksScreen.exec())
83 changes: 71 additions & 12 deletions BlocksScreen/configfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@

from helper_methods import check_file_on_path

logger = logging.getLogger(__name__)

HOME_DIR = os.path.expanduser("~/")
WORKING_DIR = os.getcwd()
DEFAULT_CONFIGFILE_PATH = pathlib.Path(HOME_DIR, "printer_data", "config")
Expand All @@ -56,11 +58,19 @@ class ConfigError(Exception):
"""Exception raised when Configfile errors exist"""

def __init__(self, msg) -> None:
"""Store the error message on both the exception and the ``msg`` attribute."""
super().__init__(msg)
self.msg = msg


class BlocksScreenConfig:
"""Thread-safe wrapper around :class:`configparser.ConfigParser` with raw-text tracking.

Maintains a ``raw_config`` list that mirrors the on-disk file so that
``add_section``, ``add_option``, and ``update_option`` can write back
changes without losing comments or formatting.
"""

config = configparser.ConfigParser(
allow_no_value=True,
)
Expand All @@ -70,16 +80,19 @@ class BlocksScreenConfig:
def __init__(
self, configfile: typing.Union[str, pathlib.Path], section: str
) -> None:
"""Initialise with the path to the config file and the default section name."""
self.configfile = pathlib.Path(configfile)
self.section = section
self.raw_config: typing.List[str] = []
self.raw_dict_config: typing.Dict = {}
self.file_lock = threading.Lock() # Thread safety for future work

def __getitem__(self, key: str) -> BlocksScreenConfig:
"""Return a :class:`BlocksScreenConfig` for *key* section (same as ``get_section``)."""
return self.get_section(key)

def __contains__(self, key):
"""Return True if *key* is a section in the underlying ConfigParser."""
return key in self.config

def sections(self) -> typing.List[str]:
Expand All @@ -91,7 +104,7 @@ def get_section(
) -> BlocksScreenConfig:
"""Get configfile section"""
if not self.config.has_section(section):
raise configparser.NoSectionError(f"No section with name: {section}")
return fallback
return BlocksScreenConfig(self.configfile, section)

def get_options(self) -> list:
Expand Down Expand Up @@ -193,12 +206,14 @@ def getboolean(
)

def _find_section_index(self, section: str) -> int:
"""Return the index of the ``[section]`` header line in ``raw_config``."""
try:
return self.raw_config.index("[" + section + "]")
except ValueError as e:
raise configparser.Error(f'Section "{section}" does not exist: {e}')

def _find_section_limits(self, section: str) -> typing.Tuple:
"""Return ``(start_index, end_index)`` of *section* in ``raw_config``."""
try:
section_start = self._find_section_index(section)
buffer = self.raw_config[section_start:]
Expand All @@ -212,6 +227,7 @@ def _find_section_limits(self, section: str) -> typing.Tuple:
def _find_option_index(
self, section: str, option: str
) -> typing.Union[Sentinel, int, None]:
"""Return the index of the *option* line within *section* in ``raw_config``."""
try:
start, end = self._find_section_limits(section)
section_buffer = self.raw_config[start:][:end]
Expand Down Expand Up @@ -253,9 +269,9 @@ def add_section(self, section: str) -> None:
self.config.add_section(section)
self.update_pending = True
except configparser.DuplicateSectionError as e:
logging.error(f'Section "{section}" already exists. {e}')
logger.error(f'Section "{section}" already exists. {e}')
except configparser.Error as e:
logging.error(f'Unable to add "{section}" section to configuration: {e}')
logger.error(f'Unable to add "{section}" section to configuration: {e}')

def add_option(
self,
Expand Down Expand Up @@ -283,12 +299,46 @@ def add_option(
self.config.set(section, option, value)
self.update_pending = True
except configparser.DuplicateOptionError as e:
logging.error(f"Option {option} already present on {section}: {e}")
logger.error(f"Option {option} already present on {section}: {e}")
except configparser.Error as e:
logging.error(
logger.error(
f'Unable to add "{option}" option to section "{section}": {e} '
)

def update_option(
self,
section: str,
option: str,
value: typing.Any,
) -> None:
"""Update an existing option's value in both raw tracking and configparser."""
try:
with self.file_lock:
if not self.config.has_section(section):
self.add_section(section)

if not self.config.has_option(section, option):
self.add_option(section, option, str(value))
return

line_idx = self._find_option_line_index(section, option)
self.raw_config[line_idx] = f"{option}: {value}"
self.config.set(section, option, str(value))
self.update_pending = True
except Exception as e:
logger.error(
f'Unable to update option "{option}" in section "{section}": {e}'
)

def _find_option_line_index(self, section: str, option: str) -> int:
"""Find the index of an option line within a specific section."""
start, end = self._find_section_limits(section)
opt_regex = re.compile(rf"^\s*{re.escape(option)}\s*[:=]")
for i in range(start + 1, end):
if opt_regex.match(self.raw_config[i]):
return i
raise configparser.Error(f'Option "{option}" not found in section "{section}"')

def save_configuration(self) -> None:
"""Save teh configuration to file"""
try:
Expand All @@ -301,7 +351,7 @@ def save_configuration(self) -> None:
self.config.write(sio)
sio.close()
except Exception as e:
logging.error(
logger.error(
f"ERROR: Unable to save new configuration, something went wrong while saving updated configuration. {e}"
)
finally:
Expand All @@ -319,6 +369,14 @@ def load_config(self):
raise configparser.Error(f"Error loading configuration file: {e}")

def _parse_file(self) -> typing.Tuple[typing.List[str], typing.Dict]:
"""Read and normalise the config file into a raw line list and a nested dict.

Strips comments, normalises ``=`` to ``:`` separators, deduplicates
sections/options, and ensures the buffer ends with an empty line.

Returns:
A tuple of (raw_lines, dict_representation).
"""
buffer = []
dict_buff: typing.Dict = {}
curr_sec: typing.Union[Sentinel, str] = Sentinel.MISSING
Expand All @@ -336,17 +394,18 @@ def _parse_file(self) -> typing.Tuple[typing.List[str], typing.Dict]:
if not line:
continue
# remove leading and trailing white spaces
line = re.sub(r"\s*([:=])\s*", r"\1", line)
line = re.sub(r"\s*([:=])\s*", r"\1 ", line)
line = re.sub(r"=", r":", line)
# find the beginning of sections
section_match = re.compile(r"[^\s]*\[([^]]+)\]")
match_sec = re.match(section_match, line) #
if match_sec:
sec_name = re.sub(r"[\[*\]]", r"", line)
if sec_name not in dict_buff.keys():
buffer.extend(
[""]
) # REFACTOR: Just add some line separation between sections
if buffer:
buffer.extend(
[""]
) # REFACTOR: Just add some line separation between sections
dict_buff.update({sec_name: {}})
curr_sec = sec_name
else:
Expand Down Expand Up @@ -386,6 +445,6 @@ def get_configparser() -> BlocksScreenConfig:
config_object = BlocksScreenConfig(configfile=configfile, section="server")
config_object.load_config()
if not config_object.has_section("server"):
logging.error("Error loading configuration file for the application.")
logger.error("Error loading configuration file for the application.")
raise ConfigError("Section [server] is missing from configuration")
return BlocksScreenConfig(configfile=configfile, section="server")
return config_object
24 changes: 24 additions & 0 deletions BlocksScreen/devices/storage/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from .usb_controller import USBManager

__doc__ = """

The storage package contains a tool that monitors
pluggable usb devices via python-sdbus library.
While offering an automounting option.
The package is also capable of creating a symlink that
points directly to the mounted usb drive on the gcodes
directory.


There is still a lot of functionality missing, that may
be added in the future, but for now it just automounts,
creates symlinks, cleans up broken symlinks on the
gcodes directory.


All tools related to storage devices should be contained
in this package directory.
"""
__version__ = "0.0.1"
__all__ = ["USBManager"]
__name__ = "storage"
Loading
Loading