Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
3c0c0e4
ADD: added fans page
Robert0Mart Nov 12, 2025
640f810
add: added fans into fans page
Robert0Mart Nov 12, 2025
829776c
ADD: added line to fans page
Robert0Mart Nov 12, 2025
8d1aa9b
Refacotr: changed layout type
Robert0Mart Nov 13, 2025
f7cfbfc
UPD: updated options card to have 2 text . added cards into controlta…
Robert0Mart Nov 13, 2025
aa03b9e
Rev: removed line from fans page
Robert0Mart Nov 13, 2025
c7fd6b6
Refactor: refactor on_fan_object_update
Robert0Mart Nov 13, 2025
a93fbe2
Refactor: Ran ruff formatter
RobeMartins Dec 10, 2025
db4285c
Merge branch 'dev' into work-fan-page
HugoCLSC Dec 11, 2025
dcc470e
Fix incorrect method name
HugoCLSC Dec 11, 2025
cb64db0
Fix incorrect method name
HugoCLSC Dec 11, 2025
35f91ba
bugfix: option card clicklable area
RobeMartins Dec 11, 2025
51bd573
Ran: ruff formatter
RobeMartins Dec 11, 2025
9bd70f4
Bugfix: names being wrong and slider spaming gcodes on change
RobeMartins Dec 11, 2025
75ad7ec
ADD: color degrade when ON/OFF (#120)
Robert0Mart Dec 11, 2025
417ab01
Del: deleted some prints
RobeMartins Dec 11, 2025
8354b9b
Bugfix: fan not updating when slider updates
RobeMartins Dec 11, 2025
c438de5
Bugfix: label size
RobeMartins Dec 11, 2025
3096aa9
Merge branch 'dev' into work-fan-page
HugoCLSC Dec 11, 2025
64b8df4
Bugfix: Delete file handling and QDialog class refactoring (#128)
HugoCLSC Dec 12, 2025
dc8377d
bugfix: option card centering
RobeMartins Dec 12, 2025
ab187df
Merge branch 'dev' into work-fan-page
HugoCLSC Dec 12, 2025
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
145 changes: 143 additions & 2 deletions BlocksScreen/lib/panels/controlTab.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
from PyQt6 import QtCore, QtGui, QtWidgets

from lib.panels.widgets.popupDialogWidget import Popup
from lib.utils.display_button import DisplayButton
from lib.panels.widgets.slider_selector_page import SliderPage

from lib.panels.widgets.optionCardWidget import OptionCard
from helper_methods import normalize


class ControlTab(QtWidgets.QStackedWidget):
Expand Down Expand Up @@ -42,6 +47,8 @@ class ControlTab(QtWidgets.QStackedWidget):
request_file_info: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
str, name="request-file-info"
)
tune_display_buttons: dict = {}
card_options: dict = {}

def __init__(
self,
Expand Down Expand Up @@ -74,6 +81,11 @@ def __init__(
self.addWidget(self.printcores_page)
self.loadpage = LoadScreen(self, LoadScreen.AnimationGIF.DEFAULT)
self.addWidget(self.loadpage)

self.sliderPage = SliderPage(self)
self.addWidget(self.sliderPage)
self.sliderPage.request_back.connect(self.back_button)

self.probe_helper_page.request_page_view.connect(
partial(self.change_page, self.indexOf(self.probe_helper_page))
)
Expand Down Expand Up @@ -108,6 +120,10 @@ def __init__(
self.panel.cp_temperature_btn.clicked.connect(
partial(self.change_page, self.indexOf(self.panel.temperature_page))
)
self.panel.cp_fans_btn.clicked.connect(
partial(self.change_page, self.indexOf(self.panel.fans_page))
)
self.panel.fans_back_btn.clicked.connect(self.back_button)
self.panel.cp_switch_print_core_btn.clicked.connect(self.show_swapcore)
self.panel.cp_nozzles_calibration_btn.clicked.connect(
partial(self.change_page, self.indexOf(self.probe_helper_page))
Expand Down Expand Up @@ -267,6 +283,133 @@ def __init__(
self.panel.cooldown_btn.hide()
self.panel.cp_switch_print_core_btn.hide()

self.printer.fan_update[str, str, float].connect(self.on_fan_object_update)
self.printer.fan_update[str, str, int].connect(self.on_fan_object_update)

@QtCore.pyqtSlot(str, str, float, name="on_fan_update")
@QtCore.pyqtSlot(str, str, int, name="on_fan_update")
def on_fan_object_update(
self, name: str, field: str, new_value: int | float
) -> None:
"""Slot that receives updates from fan objects.

Args:
name (str): Fan object name
field (str): Field name
new_value (int | float): New value for the field
"""
if "speed" not in field:
return

if name == "fan_generic Auxiliary_Cooling_Fans":
name = "Auxiliary\ncooling fans"
elif name == "fan_generic CHAMBER_EXHAUST":
name = "Exhaust Fan"
elif name == "fan_generic Part_Cooling_Fan":
name = "Cooling fan"
else:
name = name.removeprefix("fan_generic")
fan_card = self.tune_display_buttons.get(name)

if fan_card is None:
icon_path = (
":/temperature_related/media/btn_icons/blower.svg"
if "blower" in name.lower()
else ":/temperature_related/media/btn_icons/fan.svg"
)
icon = QtGui.QPixmap(icon_path)

card = OptionCard(self, name, str(name), icon) # type: ignore
card.setObjectName(str(name))

# Add card to layout and record reference
self.card_options[name] = card
self.panel.fans_content_layout.addWidget(card)

# If the card doesn't have expected UI properties, discard it
if not hasattr(card, "continue_clicked"):
del card
self.card_options.pop(name, None)
return

card.setMode(True)
card.secondtext.setText(f"{new_value}%")
card.continue_clicked.connect(
lambda: self.on_slidePage_request(
str(name),
card.secondtext.text().replace("%", ""),
self.on_slider_change,
0,
100,
)
)

self.tune_display_buttons[name] = card
self.update()
fan_card = card

if fan_card:
value_percent = new_value * 100 if new_value <= 1 else new_value
fan_card.secondtext.setText(f"{value_percent:.0f}%")

@QtCore.pyqtSlot(str, int, "PyQt_PyObject", name="on_slidePage_request")
@QtCore.pyqtSlot(str, int, "PyQt_PyObject", int, int, name="on_slidePage_request")
def on_slidePage_request(
self,
name: str,
current_value: int,
callback,
min_value: int = 0,
max_value: int = 100,
) -> None:
self.sliderPage.value_selected.connect(callback)
self.sliderPage.set_name(name)
self.sliderPage.set_slider_position(int(current_value))
self.sliderPage.set_slider_minimum(min_value)
self.sliderPage.set_slider_maximum(max_value)
self.change_page(self.indexOf(self.sliderPage))

@QtCore.pyqtSlot(str, int, name="on_slider_change")
def on_slider_change(self, name: str, new_value: int) -> None:
if "speed" in name.lower():
self.speed_factor_override = new_value / 100
self.run_gcode_signal.emit(f"M220 S{new_value}")

if name == "Auxiliary\ncooling fans":
name = "Auxiliary_Cooling_Fans"
elif name == "Exhaust Fan":
name = "CHAMBER_EXHAUST"
elif name == "Cooling fan":
name = "Part_Cooling_Fan"
else:
...
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:
self.run_gcode_signal.emit(
f'SET_FAN_SPEED FAN="{name}" SPEED={float(new_value / 100.00)}'
) # [0.0, 1.0] Range

def create_display_button(self, name: str) -> DisplayButton:
"""Create and return a DisplayButton

Args:
name (str): Name for the display button

Returns:
DisplayButton: The created DisplayButton object
"""
display_button = DisplayButton()
display_button.setObjectName(str(name + "_display"))
display_button.setMinimumSize(QtCore.QSize(150, 50))
display_button.setMaximumSize(QtCore.QSize(150, 80))
font = QtGui.QFont()
font.setPointSize(16)
display_button.setFont(font)
return display_button

def handle_printcoreupdate(self, value: dict):
if value["swapping"] == "idle":
return
Expand Down Expand Up @@ -297,9 +440,7 @@ def _handle_gcode_response(self, messages: list):
and "range:" in msg_list
and "tolerance:" in msg_list
):
print("Match candidate:", msg_list)
match = re.search(pattern, msg_list)
print("Regex match:", match)

if match:
retries_done = int(match.group(1))
Expand Down
106 changes: 86 additions & 20 deletions BlocksScreen/lib/panels/widgets/optionCardWidget.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import typing

from PyQt6 import QtCore, QtGui, QtWidgets
from lib.utils.blocks_label import BlocksLabel
from lib.utils.icon_button import IconButton


class OptionCard(QtWidgets.QFrame):
continue_clicked: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
class OptionCard(QtWidgets.QAbstractButton):
clicked: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
"PyQt_PyObject", name="continue_clicked"
)

Expand All @@ -25,8 +24,22 @@ def __init__(
self.icon_background_color = QtGui.QColor(150, 150, 130, 80)
self.name = name
self.card_text = text
self.doubleT: bool = False
self._setupUi(self)
self.continue_button.clicked.connect(lambda: self.continue_clicked.emit(self))
self.option_icon.setAttribute(
QtCore.Qt.WidgetAttribute.WA_TransparentForMouseEvents
)
self.option_text.setAttribute(
QtCore.Qt.WidgetAttribute.WA_TransparentForMouseEvents
)
self.secondtext.setAttribute(
QtCore.Qt.WidgetAttribute.WA_TransparentForMouseEvents
)
self.line_separator.setAttribute(
QtCore.Qt.WidgetAttribute.WA_TransparentForMouseEvents
)

self.setMode(False)
self.set_card_icon(icon)
self.set_card_text(text)

Expand All @@ -42,7 +55,13 @@ def enable_button(self) -> None:

def set_card_icon(self, pixmap: QtGui.QPixmap) -> None:
"""Set widget icon"""
self.option_icon.setPixmap(pixmap)
scaled = pixmap.scaled(
300,
300,
QtCore.Qt.AspectRatioMode.IgnoreAspectRatio,
QtCore.Qt.TransformationMode.SmoothTransformation,
)
self.option_icon.setPixmap(scaled)
self.repaint()

def set_card_text(self, text: str) -> None:
Expand Down Expand Up @@ -79,9 +98,49 @@ def leaveEvent(self, a0: QtCore.QEvent) -> None:

def mousePressEvent(self, a0: QtGui.QMouseEvent) -> None:
"""Re-implemented method, handle mouse press event"""
self.clicked.emit(self)
self.update()
return super().mousePressEvent(a0)

def setMode(self, double_mode: bool = False):
"""Set the mode of the layout: single or double text."""
self.doubleT = double_mode

# Clear existing widgets from layout before adding new ones
while self.verticalLayout.count():
item = self.verticalLayout.takeAt(0)
widget = item.widget()
if widget is not None:
widget.setParent(None)

if self.doubleT:
self.verticalLayout.addWidget(
self.option_icon,
0,
QtCore.Qt.AlignmentFlag.AlignHCenter
| QtCore.Qt.AlignmentFlag.AlignBottom,
)
self.verticalLayout.addWidget(
self.secondtext, 0, QtCore.Qt.AlignmentFlag.AlignHCenter
)
self.verticalLayout.addWidget(
self.line_separator, 0, QtCore.Qt.AlignmentFlag.AlignCenter
)
self.verticalLayout.addWidget(self.option_text)
self.verticalLayout.addItem(self.spacer)
self.secondtext.show()
else:
self.verticalLayout.addWidget(
self.option_icon, 0, QtCore.Qt.AlignmentFlag.AlignCenter
)
self.verticalLayout.addWidget(
self.line_separator, 0, QtCore.Qt.AlignmentFlag.AlignCenter
)
self.verticalLayout.addWidget(self.option_text)
self.verticalLayout.addWidget(self.continue_button)

self.update()

def paintEvent(self, a0: QtGui.QPaintEvent) -> None:
"""Re-implemented method, paint widget"""
# Rounded background edges
Expand Down Expand Up @@ -154,12 +213,20 @@ def _setupUi(self, option_card):
self.verticalLayout = QtWidgets.QVBoxLayout(option_card)
self.verticalLayout.setContentsMargins(0, 0, -1, -1)
self.verticalLayout.setObjectName("verticalLayout")
self.option_icon = BlocksLabel(parent=option_card)
self.option_icon = IconButton(parent=option_card)
self.option_icon.setMinimumSize(QtCore.QSize(200, 150))
self.option_icon.setObjectName("option_icon")
self.verticalLayout.addWidget(
self.option_icon, 0, QtCore.Qt.AlignmentFlag.AlignHCenter
)
_button_font = QtGui.QFont()
_button_font.setBold(True)
_button_font.setPointSize(20)
self.secondtext = QtWidgets.QLabel(parent=option_card)
self.secondtext.setText("%")
self.secondtext.setStyleSheet("color:white")
self.secondtext.setFont(_button_font)
self.secondtext.setObjectName("option_text")
self.secondtext.setWordWrap(True)
self.secondtext.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.secondtext.hide()
self.line_separator = QtWidgets.QFrame(parent=option_card)
self.line_separator.setFrameShape(QtWidgets.QFrame.Shape.HLine)
self.line_separator.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
Expand All @@ -174,21 +241,15 @@ def _setupUi(self, option_card):
self.option_text = QtWidgets.QLabel(parent=option_card)
self.option_text.setMinimumSize(QtCore.QSize(200, 50))
self.option_text.setObjectName("option_text")
self.verticalLayout.addWidget(
self.option_text,
)
self.continue_button = IconButton(parent=option_card)
self.option_text.setAlignment(
QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter
)
self.option_text.setWordWrap(True)
_button_font = QtGui.QFont()
_button_font.setBold(True)
self.option_text.setStyleSheet("color:white")
self.option_text.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
_palette = self.option_text.palette()
_palette.setColor(QtGui.QPalette.ColorRole.WindowText, self.text_color)
self.option_text.setPalette(_palette)
_button_font.setPointSize(15)

self.option_text.setFont(_button_font)
self.continue_button = IconButton(parent=option_card)
sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Policy.MinimumExpanding,
QtWidgets.QSizePolicy.Policy.MinimumExpanding,
Expand All @@ -207,8 +268,13 @@ def _setupUi(self, option_card):
QtGui.QPixmap(":/arrow_icons/media/btn_icons/right_arrow.svg"),
)
self.continue_button.setObjectName("continue_button")
self.verticalLayout.addWidget(self.continue_button)

self.spacer = QtWidgets.QSpacerItem(
20,
40,
QtWidgets.QSizePolicy.Policy.Minimum,
QtWidgets.QSizePolicy.Policy.Expanding,
)
self._retranslateUi(option_card)
QtCore.QMetaObject.connectSlotsByName(option_card)

Expand Down
Loading
Loading