@@ -66,9 +66,12 @@ class PrintTab(QtWidgets.QStackedWidget):
6666 on_cancel_print : typing .ClassVar [QtCore .pyqtSignal ] = QtCore .pyqtSignal (
6767 name = "on_cancel_print"
6868 )
69- call_load_panel = QtCore .pyqtSignal (bool , str , name = "call-load-panel" )
70-
71- call_cancel_panel = QtCore .pyqtSignal (bool , name = "call-load-panel" )
69+ call_load_panel : typing .ClassVar [QtCore .pyqtSignal ] = QtCore .pyqtSignal (
70+ bool , str , name = "call-load-panel"
71+ )
72+ call_cancel_panel : typing .ClassVar [QtCore .pyqtSignal ] = QtCore .pyqtSignal (
73+ bool , name = "call-load-panel"
74+ )
7275
7376 def __init__ (
7477 self ,
@@ -79,7 +82,9 @@ def __init__(
7982 ) -> None :
8083 super ().__init__ (parent )
8184 self ._active_z_offset : float = 0.0
85+ self ._pending_save_offset : float = 0.0
8286 self ._finish_print_handled : bool = False
87+ self ._cancel_z_snapshot : float = 0.0
8388 self ._z_apply_command : str = "Z_OFFSET_APPLY_ENDSTOP"
8489
8590 self .setupMainPrintPage ()
@@ -231,6 +236,9 @@ def __init__(
231236 self .printer .gcode_move_update [str , list ].connect (
232237 self .babystepPage .on_gcode_move_update
233238 )
239+ self .printer .print_stats_update [str , str ].connect (
240+ self .babystepPage .on_print_state_update
241+ )
234242 self .printer .gcode_move_update [str , list ].connect (self .activate_save_button )
235243 self .tune_page .run_gcode .connect (self .ws .api .run_gcode )
236244 self .tune_page .request_sliderPage [str , int , "PyQt_PyObject" ].connect (
@@ -275,6 +283,7 @@ def __init__(
275283 self .confirmPage_widget .on_delete .connect (self .delete_file )
276284 self .change_page (self .indexOf (self .print_page )) # force set the initial page
277285 self .save_config_btn .clicked .connect (self .save_config )
286+ self .ws .klippy_state_signal .connect (self .on_klippy_state )
278287
279288 @QtCore .pyqtSlot (str , dict , name = "on_print_stats_update" )
280289 @QtCore .pyqtSlot (str , float , name = "on_print_stats_update" )
@@ -283,10 +292,14 @@ def on_print_stats_update(self, field: str, value: dict | float | str) -> None:
283292 """
284293 unblocks tabs if on standby
285294 """
286- if isinstance (value , str ):
287- if "state" in field :
288- if value in ("standby" ):
289- self .on_cancel_print .emit ()
295+ if isinstance (value , str ) and "state" in field and value == "standby" :
296+ self .on_cancel_print .emit ()
297+ if not self ._finish_print_handled and self ._cancel_z_snapshot != 0 :
298+ self ._active_z_offset = self ._cancel_z_snapshot
299+ self .save_config ()
300+ self ._finish_print_handled = True
301+ self .save_config_btn .setVisible (True )
302+ self ._cancel_z_snapshot = 0.0
290303
291304 @QtCore .pyqtSlot (str , int , "PyQt_PyObject" , name = "on_numpad_request" )
292305 @QtCore .pyqtSlot (str , int , "PyQt_PyObject" , int , int , name = "on_numpad_request" )
@@ -299,6 +312,10 @@ def on_numpad_request(
299312 max_value : int = 100 ,
300313 ) -> None :
301314 """Handle numpad request"""
315+ try :
316+ self .numpadPage .value_selected .disconnect ()
317+ except (RuntimeError , TypeError ):
318+ pass
302319 self .numpadPage .value_selected .connect (callback )
303320 self .numpadPage .set_name (name )
304321 self .numpadPage .set_value (current_value )
@@ -318,6 +335,10 @@ def on_slidePage_request(
318335 max_value : int = 100 ,
319336 ) -> None :
320337 """Handle slider page request"""
338+ try :
339+ self .sliderPage .value_selected .disconnect ()
340+ except (RuntimeError , TypeError ):
341+ pass
321342 self .sliderPage .value_selected .connect (callback )
322343 self .sliderPage .set_name (name )
323344 self .sliderPage .set_slider_position (int (current_value ))
@@ -341,12 +362,9 @@ def delete_file(self, filename: str, directory: str = "gcodes") -> None:
341362
342363 def save_config (self ) -> None :
343364 """Handle Save configuration behaviour, shows confirmation dialog"""
344-
345- self .babystepPage .bbp_z_offset_title_label .setText (
346- f"Z: { self ._active_z_offset :.3f} mm"
347- )
365+ self ._pending_save_offset = self ._active_z_offset
348366 self .BasePopup_z_offset .set_message (
349- f"The Z-Offset is now { self ._active_z_offset :.3f} mm.\n "
367+ f"The Z-Offset is now { self ._pending_save_offset + 0.0 :.3f} mm.\n "
350368 "Would you like to save this change permanently?\n "
351369 "The machine will restart."
352370 )
@@ -359,28 +377,37 @@ def save_config(self) -> None:
359377 self .BasePopup_z_offset .open ()
360378
361379 def update_configuration_file (self ) -> None :
362- """Restore the captured offset, apply it to the probe config, then save. """
380+ """Runs the `SAVE_CONFIG` gcode """
363381 try :
364382 self .BasePopup_z_offset .accepted .disconnect (self .update_configuration_file )
365383 except (RuntimeError , TypeError ):
366384 pass
367385 self .run_gcode_signal .emit (
368- f"SET_GCODE_OFFSET Z={ self ._active_z_offset :.3f} MOVE=0"
386+ f"SET_GCODE_OFFSET Z={ self ._pending_save_offset :.3f} MOVE=0"
369387 )
370388 self .run_gcode_signal .emit (self ._z_apply_command )
371389 self .run_gcode_signal .emit ("SAVE_CONFIG" )
390+ self .babystepPage .bbp_z_offset_title_label .setText (
391+ f"Z: { self ._pending_save_offset + 0.0 :.3f} mm"
392+ )
372393 self .save_config_btn .setVisible (False )
373394
395+ @QtCore .pyqtSlot (str , name = "on_klippy_state" )
396+ def on_klippy_state (self , state : str ) -> None :
397+ """Dismiss the Z-offset save popup and reset save state on unexpected shutdown."""
398+ if state in ("ready" , "startup" ):
399+ return
400+ self .BasePopup_z_offset .reject ()
401+ self .save_config_btn .setVisible (False )
402+ self .babystepPage .baby_stepchange = False
403+
374404 @QtCore .pyqtSlot (str , list , name = "activate_save_button" )
375405 def activate_save_button (self , name : str , value : list ) -> None :
376406 """Sync the `Save config` popup with the save_config_pending state"""
377- if not value :
407+ if not value or name != "homing_origin" or len ( value ) <= 2 :
378408 return
379-
380- if name == "homing_origin" :
381- if len (value ) > 2 :
382- self ._active_z_offset = value [2 ]
383- self .save_config_btn .setVisible (value [2 ] != 0 )
409+ self ._active_z_offset = value [2 ]
410+ self .save_config_btn .setVisible (round (value [2 ], 3 ) != 0 )
384411
385412 def _on_delete_file_confirmed (self , filename : str , directory : str ) -> None :
386413 """Handle confirmed file deletion after user accepted the dialog."""
@@ -408,6 +435,12 @@ def setProperty(self, name: str, value: typing.Any) -> bool:
408435
409436 def handle_cancel_print (self ) -> None :
410437 """Handles the print cancel action"""
438+ if (
439+ not self ._finish_print_handled
440+ and self ._active_z_offset != 0
441+ and self .babystepPage .baby_stepchange
442+ ):
443+ self ._cancel_z_snapshot = self ._active_z_offset
411444 self .ws .api .cancel_print ()
412445 self .call_load_panel .emit (True , "Cancelling print...\n Please wait" )
413446
@@ -429,6 +462,7 @@ def klipper_ready_signal(self) -> None:
429462 """React to klipper ready signal"""
430463 self .babystepPage .baby_stepchange = False
431464 self ._finish_print_handled = False
465+ self ._cancel_z_snapshot = 0.0
432466 self .printer .on_subscribe_config ("stepper_z" , self ._on_stepper_z_config )
433467
434468 def _on_stepper_z_config (self , config : dict | list ) -> None :
@@ -449,6 +483,7 @@ def finish_print_signal(self) -> None:
449483 if self ._active_z_offset != 0 and self .babystepPage .baby_stepchange :
450484 self .save_config ()
451485 self ._finish_print_handled = True
486+ self .save_config_btn .setVisible (round (self ._active_z_offset , 3 ) != 0 )
452487
453488 def setupMainPrintPage (self ) -> None :
454489 """Setup UI for print page"""
0 commit comments