1919from lib .utils .blocks_linedit import BlocksCustomLinEdit
2020from lib .utils .icon_button import IconButton
2121from lib .utils .list_model import EntryDelegate , EntryListModel , ListItem
22+ from lib .utils .toolmap import MmuToolmapWidget
2223from PyQt6 import QtCore , QtGui , QtWidgets
2324
2425logger = logging .getLogger (__name__ )
@@ -58,8 +59,13 @@ def __init__(
5859 self ._material_filter : str | None = None
5960 self .moonraker_run = True
6061
61- self .amu_manager .mmu_state_changed .connect (self .on_mmu_state_changed )
6262 self ._setup_pre_gate_popup ()
63+ self ._setup_load_popup ()
64+ self .amu_manager .mmu_state_changed .connect (self .on_mmu_state_changed )
65+
66+ self ._extruder_current_temp = 0.0
67+ self ._extruder_target_temp = 0.0
68+ self .printer .extruder_update .connect (self .on_extruder_update )
6369
6470 self .spoolmanPanel = SpoolmanPage (self )
6571 self .addWidget (self .spoolmanPanel )
@@ -96,8 +102,15 @@ def __init__(
96102 body , callback = self .spoolmanPanel .on_add_filament_result
97103 )
98104 )
105+ self .spoolmanPanel .request_add_manufacturer .connect (
106+ lambda body : self .ws .api .add_manufacturer (
107+ body , callback = self .spoolmanPanel .on_add_manufacturer_result
108+ )
109+ )
99110
100- self ._basic_panel = BasicFilamentPanel (self .printer , self .cfg , parent = self )
111+ self ._basic_panel = BasicFilamentPanel (
112+ self .printer , self .cfg , parent = self , load_popup = self .load_popup
113+ )
101114 self ._basic_panel .run_gcode .connect (self .run_gcode )
102115 self ._basic_panel .call_load_panel .connect (self .call_load_panel )
103116 self ._basic_panel .request_back .connect (self .request_back )
@@ -115,7 +128,17 @@ def __init__(
115128
116129 self .run_gcode .connect (self .ws .api .run_gcode )
117130
131+ def in_case_error (self ):
132+ """Reset the popup and spool info in case of an error."""
133+ self ._reset_popup ()
134+ self .reset_spool_info ()
135+ self ._add_spool_page .setFilter (None )
136+ self ._add_filament_page .setData ("---" , 0 )
137+ self .load_state = False
138+ self .load_popup .hide ()
139+
118140 def handle_moonraker_components (self ):
141+ """Build the pre-gate popup pages once, choosing spoolman vs. manual-entry order."""
119142 if self .moonraker_run :
120143 components = self .ws ._moonRest .get_server_info ()
121144 if "spoolman" not in components ["result" ].get ("components" , []):
@@ -137,6 +160,43 @@ def change_page(self, index: int) -> None:
137160 """
138161 self .request_change_page .emit (1 , index )
139162
163+ def _setup_load_popup (self ) -> None :
164+ self .load_popup = BasePopup (self , floating = False , dialog = False )
165+ load_container = QtWidgets .QWidget (self .load_popup )
166+ load_layout = QtWidgets .QVBoxLayout (load_container )
167+
168+ self .load_status_label = QtWidgets .QLabel (load_container )
169+ self .load_status_label .setAlignment (QtCore .Qt .AlignmentFlag .AlignCenter )
170+ label_font = QtGui .QFont ()
171+ label_font .setPointSize (20 )
172+ self .load_status_label .setFont (label_font )
173+ self .load_status_label .setStyleSheet ("color: #ffffff; background: transparent;" )
174+ self .load_status_label .setMaximumHeight (100 )
175+ load_layout .addWidget (self .load_status_label )
176+
177+ self .load_status_widget = MmuToolmapWidget (load_container )
178+ self .load_status_widget .set_left_text ("Auxiliary Extruder" )
179+ load_layout .addWidget (self .load_status_widget )
180+
181+ self .load_popup .add_widget (load_container )
182+
183+ @QtCore .pyqtSlot (str , str , float , name = "on-extruder-update" )
184+ def on_extruder_update (
185+ self , extruder_name : str , field : str , new_value : float
186+ ) -> None :
187+ """Track the extruder's current/target temp and feed it to the load status widget."""
188+ if extruder_name != "extruder" :
189+ return
190+ if field == "temperature" :
191+ self ._extruder_current_temp = new_value
192+ elif field == "target" :
193+ self ._extruder_target_temp = new_value
194+ else :
195+ return
196+ self .load_status_widget .set_temps (
197+ self ._extruder_current_temp , self ._extruder_target_temp
198+ )
199+
140200 def _setup_pre_gate_popup (self ) -> None :
141201 self ._numpad = CustomNumpad (self )
142202 self ._numpad .hide ()
@@ -382,6 +442,16 @@ def _build_spool_page(self) -> QtWidgets.QWidget:
382442 frame_lay .addWidget (self ._spool_load_widget , 1 )
383443 self ._spool_list_view .hide ()
384444
445+ self ._no_spools_label = QtWidgets .QLabel ("" , frame )
446+ self ._no_spools_label .setWordWrap (True )
447+ self ._no_spools_label .setAlignment (QtCore .Qt .AlignmentFlag .AlignCenter )
448+ self ._no_spools_label .setStyleSheet ("color: gray;" )
449+ no_spools_font = QtGui .QFont ()
450+ no_spools_font .setPointSize (13 )
451+ self ._no_spools_label .setFont (no_spools_font )
452+ self ._no_spools_label .hide ()
453+ frame_lay .addWidget (self ._no_spools_label )
454+
385455 self ._add_spool_page = AddSpoolPage (self )
386456 self ._add_filament_page = AddFilamentPage (self )
387457
@@ -511,6 +581,7 @@ def _add_row(title_text: str) -> QtWidgets.QLabel:
511581 return page
512582
513583 def handle_skip_button (self ):
584+ """Map the pending pre-gate gate to an empty spool and dismiss the popup."""
514585 gate = self .pre_gate_idx .get ("gate" , 0 )
515586 self ._reset_popup ()
516587 self .run_gcode .emit (
@@ -519,7 +590,7 @@ def handle_skip_button(self):
519590 if self ._popup_callback is not None :
520591 try :
521592 self ._popup_callback ()
522- except Exception as e :
593+ except Exception as e : # noqa: BLE001 - arbitrary caller-supplied callback
523594 logger .error (f"Error executing pre-gate accept callback: { e } " )
524595 finally :
525596 self ._popup_callback = None
@@ -562,6 +633,7 @@ def handle_popup(self, force=False):
562633
563634 @QtCore .pyqtSlot (int , str , str , "PyQt_PyObject" , name = "open-pregate-popup" )
564635 def open_pregate_popup (self , temp , material , name , callback = None ):
636+ """Open the pre-gate popup pre-filled with a detected filament's info and *callback*."""
565637 self ._popup_name .setText (name )
566638 self ._popup_material .setText (material )
567639 self ._popup_temp .setText (str (temp ))
@@ -600,7 +672,7 @@ def on_popup_accept(self):
600672 if self ._popup_callback is not None :
601673 try :
602674 self ._popup_callback ()
603- except Exception as e :
675+ except Exception as e : # noqa: BLE001 - arbitrary caller-supplied callback
604676 logger .error (f"Error executing pre-gate accept callback: { e } " )
605677 finally :
606678 self ._popup_callback = None
@@ -620,13 +692,12 @@ def _reset_popup(self):
620692
621693 @QtCore .pyqtSlot (dict , name = "on-spools-received" )
622694 def on_spools_received (self , result : dict ) -> None :
623- """Handles the result from the API call to get spools for the spoolman page."""
624695 self ._spool_load_widget .hide ()
625696 self ._spool_list_view .show ()
626-
627697 self .reset_spool_info ()
628-
629698 if result .get ("error" ) is not None :
699+ self ._no_spools_label .setText ("Could not reach Spoolman" )
700+ self ._no_spools_label .show ()
630701 return
631702 spools = result .get ("response" )
632703 if not isinstance (spools , list ):
@@ -666,6 +737,16 @@ def on_spools_received(self, result: dict) -> None:
666737 self ._spool_id_map [name ] = spool
667738 self .update ()
668739
740+ if self ._spool_id_map :
741+ self ._no_spools_label .hide ()
742+ else :
743+ self ._no_spools_label .setText (
744+ f'No spools found for "{ self ._material_filter } "'
745+ if self ._material_filter
746+ else "No spools found"
747+ )
748+ self ._no_spools_label .show ()
749+
669750 def _on_spool_selected (self ) -> None :
670751 item = self ._spool_model .get_selected_item ()
671752 if item is None :
@@ -715,14 +796,15 @@ def _on_spool_selected(self) -> None:
715796 if self ._popup_callback is not None :
716797 try :
717798 self ._popup_callback ()
718- except Exception as e :
799+ except Exception as e : # noqa: BLE001 - arbitrary caller-supplied callback
719800 logger .error (f"Error executing pre-gate accept callback: { e } " )
720801 finally :
721802 self ._popup_callback = None
722803
723804 self .handle_popup ()
724805
725806 def reset_spool_info (self ):
807+ """Clear the selected-spool detail labels back to their placeholder state."""
726808 self .filament_name_label .setText ("N/A" )
727809 self .material_label .setText ("N/A" )
728810 self .weight_label .setText ("N/A" )
@@ -854,7 +936,10 @@ def on_mmu_state_changed(self, mmu_state):
854936
855937 if not self .amu_configured :
856938 if len (mmu_state .gates ) > 1 :
857- self .amupage = AMUpage (self .amu_manager , parent = self )
939+ self .load_status_widget .set_left_text ("AMU" )
940+ self .amupage = AMUpage (
941+ self .amu_manager , parent = self , load_popup = self .load_popup
942+ )
858943 self .addWidget (self .amupage )
859944 try :
860945 self .removeWidget (self ._basic_panel )
@@ -880,26 +965,50 @@ def on_mmu_state_changed(self, mmu_state):
880965 self .printer .print_stats_update [str , float ].connect (
881966 self .amupage .on_print_stats_update
882967 )
883- self .amupage .call_load_panel .connect (self .call_load_panel )
884968 self .amupage .request_keyboard .connect (self ._on_show_keyboard )
885969 self .amupage .request_color_wheel .connect (self ._open_color_wheel )
886-
970+ else :
971+ self .load_status_widget .set_left_text ("Auxiliary Extruder" )
887972 self .amu_configured = True
888973
889974 if self .load_state :
890975 if mmu_state .action == "Idle" :
891976 self .load_state = False
892- self .call_load_panel . emit ( False , "" , True )
977+ self .load_popup . hide ( )
893978 if not len (mmu_state .gates ) > 1 :
894979 self ._basic_panel .change_page (0 )
895- return
896- self .call_load_panel .emit (True , mmu_state .action , True )
897-
898- if mmu_state .action == "Loading" or mmu_state .action == "Unloading" :
980+ elif mmu_state .action in ("Loading" , "Unloading" ):
899981 self .load_state = True
900- self .call_load_panel .emit (True , mmu_state .action , True )
982+ self .load_popup .show ()
983+
984+ if not self .load_popup .isVisible ():
985+ return
986+ self .load_status_widget .set_filament_pos (
987+ mmu_state .filament_pos , mmu_state .bowden_progress
988+ )
989+
990+ for sensor_name in ("mmu_pre_gate" , "mmu_gate" , "toolhead" ):
991+ self .load_status_widget .set_sensor (
992+ sensor_name , bool (mmu_state .sensors .get (sensor_name ))
993+ )
994+ self .load_status_widget .set_action (mmu_state .action )
995+
996+ gate_info = mmu_state .current_gate_info
997+ raw_color = (
998+ str (gate_info .color ).lstrip ("#" )[:6 ]
999+ if gate_info and gate_info .color
1000+ else ""
1001+ )
1002+ parsed_color = QtGui .QColor ("#" + raw_color ) if raw_color else QtGui .QColor ()
1003+ if parsed_color .isValid ():
1004+ self .load_status_widget .set_gate_color (parsed_color )
1005+ else :
1006+ self .load_status_widget .set_gate_color (QtGui .QColor ("#ffffff" ))
1007+
1008+ self .load_status_label .setText (mmu_state .action )
9011009
9021010 def setupUi (self ):
1011+ """Build the tab's landing page (title + Filament Control / Spoolman buttons)."""
9031012 self .resize (710 , 410 )
9041013 self .setLayoutDirection (QtCore .Qt .LayoutDirection .LeftToRight )
9051014 widget = QtWidgets .QWidget ()
0 commit comments