Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 2 additions & 0 deletions BlocksScreen/devices/amu/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class MMUState:
filament: str # "Loaded" | "Unloaded" | "Unknown"
filament_pos: FilamentPos
action: str
bowden_progress: float
print_state: str
reason_for_pause: str
has_bypass: bool
Expand Down Expand Up @@ -142,6 +143,7 @@ def from_status(cls, data: dict) -> "MMUState":
filament=data.get("filament", "Unknown"),
filament_pos=FilamentPos(data.get("filament_pos", FilamentPos.UNKNOWN)),
action=data.get("action", ""),
bowden_progress=data.get("bowden_progress", -1),
print_state=data.get("print_state", ""),
reason_for_pause=data.get("reason_for_pause", ""),
has_bypass=data.get("has_bypass", False),
Expand Down
102 changes: 86 additions & 16 deletions BlocksScreen/lib/panels/filamentTab.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import logging
from collections import deque
from typing import Deque

from devices.amu import AMUManager
from devices.amu.models import GateStatus
Expand All @@ -20,6 +19,7 @@
from lib.utils.blocks_linedit import BlocksCustomLinEdit
from lib.utils.icon_button import IconButton
from lib.utils.list_model import EntryDelegate, EntryListModel, ListItem
from lib.utils.toolmap import MmuToolmapWidget
from PyQt6 import QtCore, QtGui, QtWidgets

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -52,14 +52,19 @@ def __init__(

self._previous_gate_states: dict[int, bool] = {}
self.pre_gate_idx = {}
self.popup_gates: Deque = deque()
self.popup_gates: deque = deque()
self._spool_id_map: dict[str, dict] = {}
self._current_field: QtWidgets.QLineEdit | None = None
self._color_target_field = None
self.moonraker_run = True

self.amu_manager.mmu_state_changed.connect(self.on_mmu_state_changed)
self._setup_pre_gate_popup()
self._setup_load_popup()
self.amu_manager.mmu_state_changed.connect(self.on_mmu_state_changed)

self._extruder_current_temp = 0.0
self._extruder_target_temp = 0.0
self.printer.extruder_update.connect(self.on_extruder_update)

self.spoolmanPanel = SpoolmanPage(self)
self.addWidget(self.spoolmanPanel)
Expand Down Expand Up @@ -97,7 +102,9 @@ def __init__(
)
)

self._basic_panel = BasicFilamentPanel(self.printer, self.cfg, parent=self)
self._basic_panel = BasicFilamentPanel(
self.printer, self.cfg, parent=self, load_popup=self.load_popup
)
self._basic_panel.run_gcode.connect(self.run_gcode)
self._basic_panel.call_load_panel.connect(self.call_load_panel)
self._basic_panel.request_back.connect(self.request_back)
Expand All @@ -116,6 +123,7 @@ def __init__(
self.run_gcode.connect(self.ws.api.run_gcode)

def handle_moonraker_components(self):
"""Build the pre-gate popup pages once, choosing spoolman vs. manual-entry order."""
if self.moonraker_run:
components = self.ws._moonRest.get_server_info()
if "spoolman" not in components["result"].get("components", []):
Expand All @@ -137,6 +145,43 @@ def change_page(self, index: int) -> None:
"""
self.request_change_page.emit(1, index)

def _setup_load_popup(self) -> None:
self.load_popup = BasePopup(self, floating=False, dialog=False)
load_container = QtWidgets.QWidget(self.load_popup)
load_layout = QtWidgets.QVBoxLayout(load_container)

self.load_status_label = QtWidgets.QLabel(load_container)
self.load_status_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
label_font = QtGui.QFont()
label_font.setPointSize(20)
self.load_status_label.setFont(label_font)
self.load_status_label.setStyleSheet("color: #ffffff; background: transparent;")
self.load_status_label.setMaximumHeight(100)
load_layout.addWidget(self.load_status_label)

self.load_status_widget = MmuToolmapWidget(load_container)
self.load_status_widget.set_left_text("Auxiliar Extruder")
load_layout.addWidget(self.load_status_widget)

self.load_popup.add_widget(load_container)

@QtCore.pyqtSlot(str, str, float, name="on-extruder-update")
def on_extruder_update(
self, extruder_name: str, field: str, new_value: float
) -> None:
"""Track the extruder's current/target temp and feed it to the load status widget."""
if extruder_name != "extruder":
return
if field == "temperature":
self._extruder_current_temp = new_value
elif field == "target":
self._extruder_target_temp = new_value
else:
return
self.load_status_widget.set_temps(
self._extruder_current_temp, self._extruder_target_temp
)

def _setup_pre_gate_popup(self) -> None:
self._numpad = CustomNumpad(self)
self._numpad.hide()
Expand Down Expand Up @@ -511,6 +556,7 @@ def _add_row(title_text: str) -> QtWidgets.QLabel:
return page

def handle_skip_button(self):
"""Map the pending pre-gate gate to an empty spool and dismiss the popup."""
gate = self.pre_gate_idx.get("gate", 0)
self.popup.hide()
self._reset_popup()
Expand All @@ -520,7 +566,7 @@ def handle_skip_button(self):
if self._popup_callback is not None:
try:
self._popup_callback()
except Exception as e:
except Exception as e: # noqa: BLE001 - arbitrary caller-supplied callback
logger.error(f"Error executing pre-gate accept callback: {e}")
finally:
self._popup_callback = None
Expand Down Expand Up @@ -557,6 +603,7 @@ def handle_popup(self, force=False):

@QtCore.pyqtSlot(int, str, str, "PyQt_PyObject", name="open-pregate-popup")
def open_pregate_popup(self, temp, material, name, callback=None):
"""Open the pre-gate popup pre-filled with a detected filament's info and *callback*."""
self._popup_name.setText(name)
self._popup_material.setText(material)
self._popup_temp.setText(str(temp))
Expand Down Expand Up @@ -595,7 +642,7 @@ def on_popup_accept(self):
if self._popup_callback is not None:
try:
self._popup_callback()
except Exception as e:
except Exception as e: # noqa: BLE001 - arbitrary caller-supplied callback
logger.error(f"Error executing pre-gate accept callback: {e}")
finally:
self._popup_callback = None
Expand Down Expand Up @@ -708,12 +755,13 @@ def _on_spool_selected(self) -> None:
if self._popup_callback is not None:
try:
self._popup_callback()
except Exception as e:
except Exception as e: # noqa: BLE001 - arbitrary caller-supplied callback
logger.error(f"Error executing pre-gate accept callback: {e}")
finally:
self._popup_callback = None

def reset_spool_info(self):
"""Clear the selected-spool detail labels back to their placeholder state."""
self.filament_name_label.setText("N/A")
self.material_label.setText("N/A")
self.weight_label.setText("N/A")
Expand Down Expand Up @@ -836,7 +884,10 @@ def on_mmu_state_changed(self, mmu_state):

if not self.amu_configured:
if len(mmu_state.gates) > 1:
self.amupage = AMUpage(self.amu_manager, parent=self)
self.load_status_widget.set_left_text("AMU")
self.amupage = AMUpage(
self.amu_manager, parent=self, load_popup=self.load_popup
)
self.addWidget(self.amupage)
try:
self.removeWidget(self._basic_panel)
Expand All @@ -862,24 +913,43 @@ def on_mmu_state_changed(self, mmu_state):
self.printer.print_stats_update[str, float].connect(
self.amupage.on_print_stats_update
)
self.amupage.call_load_panel.connect(self.call_load_panel)
self.amupage.request_keyboard.connect(self._on_show_keyboard)
self.amupage.request_color_wheel.connect(self._open_color_wheel)

else:
self.load_status_widget.set_left_text("Auxiliar Extruder")
self.amu_configured = True

self.load_status_widget.set_filament_pos(
mmu_state.filament_pos, mmu_state.bowden_progress
)
for sensor_name in ("mmu_pre_gate", "mmu_gate", "toolhead"):
self.load_status_widget.set_sensor(
sensor_name, bool(mmu_state.sensors.get(sensor_name))
)
self.load_status_widget.set_action(mmu_state.action)

gate_info = mmu_state.current_gate_info
raw_color = (
str(gate_info.color).lstrip("#")[:6]
if gate_info and gate_info.color
else ""
)
parsed_color = QtGui.QColor("#" + raw_color) if raw_color else QtGui.QColor()
if parsed_color.isValid():
self.load_status_widget.set_gate_color(parsed_color)

self.load_status_label.setText(mmu_state.action)

if self.load_state:
if mmu_state.action == "Idle":
self.load_state = False
self.call_load_panel.emit(False, "", True)
return
self.call_load_panel.emit(True, mmu_state.action, True)

if mmu_state.action == "Loading" or mmu_state.action == "Unloading":
self.load_popup.hide()
elif mmu_state.action in ("Loading", "Unloading"):
self.load_state = True
self.call_load_panel.emit(True, mmu_state.action, True)
self.load_popup.show()

def setupUi(self):
"""Build the tab's landing page (title + Filament Control / Spoolman buttons)."""
self.resize(710, 410)
self.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight)
widget = QtWidgets.QWidget()
Expand Down
51 changes: 26 additions & 25 deletions BlocksScreen/lib/panels/widgets/amuPage.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from devices.amu import AMUManager
from devices.amu.models import GateInfo
from lib.panels.widgets.amuWidgets import SpoolCarousel, SpoolInfoPanel
from lib.panels.widgets.basePopup import BasePopup
from lib.utils.blocks_frame import BlocksCustomFrame
from lib.utils.icon_button import IconButton
from PyQt6 import QtCore, QtGui, QtWidgets
Expand All @@ -29,13 +30,13 @@ class AMUpage(QtWidgets.QStackedWidget):
request_change_tab: typing.ClassVar[QtCore.pyqtSignal] = QtCore.pyqtSignal(
int, name="request_change_tab"
)
call_load_panel = QtCore.pyqtSignal(bool, str, bool, name="call-load-panel")

def __init__(self, amu_manager, parent=None):
def __init__(self, amu_manager, parent=None, load_popup: BasePopup | None = None):
super().__init__(parent)
self.current_index = -1
self.amu_manager: AMUManager = amu_manager
self._build_ui()
self.load_popup = load_popup

self.main_back_button.clicked.connect(self.request_back)

Expand Down Expand Up @@ -84,13 +85,13 @@ def __init__(self, amu_manager, parent=None):
self.info_panel.loadRequested.connect(
lambda: {
self.amu_manager.load_gate(),
self.call_load_panel.emit(True, "Loading", True),
self.load_popup.show(),
}
)
self.info_panel.unloadRequested.connect(
lambda: {
self.amu_manager.unload(),
self.call_load_panel.emit(True, "Unloading", True),
self.load_popup.show(),
}
)
self.info_panel.ejectRequested.connect(self.amu_manager.eject_gate)
Expand All @@ -102,29 +103,28 @@ def __init__(self, amu_manager, parent=None):
@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:
if isinstance(value, str):
if "state" in field:
self.state = value
if value in ("printing", "pausing", "paused", "resuming"):
try:
self.main_back_button.clicked.disconnect()
except TypeError:
pass

self.main_back_button.clicked.connect(
lambda: self.request_change_tab.emit(0)
)

else:
try:
self.main_back_button.clicked.disconnect()
except TypeError:
pass
self.main_back_button.clicked.connect(
lambda: self.request_back.emit()
)
"""Rewire the back button between "request_back" and "change to tab 0" based on print state."""
if isinstance(value, str) and "state" in field:
self.state = value
if value in ("printing", "pausing", "paused", "resuming"):
try:
self.main_back_button.clicked.disconnect()
except TypeError:
pass

self.main_back_button.clicked.connect(
lambda: self.request_change_tab.emit(0)
)

else:
try:
self.main_back_button.clicked.disconnect()
except TypeError:
pass
self.main_back_button.clicked.connect(lambda: self.request_back.emit())

def on_mmu_state_changed(self, mmu_state):
"""Refresh the carousel and the info panel's selected gate from live MMU state."""
if mmu_state is None:
return
self.status = mmu_state
Expand All @@ -134,6 +134,7 @@ def on_mmu_state_changed(self, mmu_state):
self._on_selection(mmu_state.gate)

def addSpool(self, gate_info: GateInfo):
"""Add or refresh a gate's carousel button from its GateInfo."""
self.carousel.addSpool(
QtGui.QColor("#" + str(gate_info.color)[:6]),
gate_info.index,
Expand Down
Loading
Loading