From acbc2a4cc01f7c039304015d1df6134f08919a5f Mon Sep 17 00:00:00 2001 From: Serge Bakharev Date: Sat, 11 Jul 2026 23:57:21 +1000 Subject: [PATCH 1/2] Elapsed and remaining job timers now pause while playback is paused --- CHANGELOG.md | 1 + carveracontroller/main.py | 49 ++++++++++++++++++++++++++++++++------- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e9a3a71..550d0998 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ - Fixed: Fix incorrect "No Pendant" in UI when pendant is working - Fixed: Fix invalid initial coordinates when resuming in the middle of a modal command - Fixed: 3D Visualisation of GCode movement would always show the initial movement as originating from the WCS Origin, this doesn't match reality. Now the Visualisation correctly shows the line as originating from above the first movement command at the configured clearance_z (default of MCS Z-3) +- Fixed: Elapsed and remaining job timers now pause while playback is paused - Change: Remove remaining "Can not load config, Key:" messages from the MDI - Change: Resume playback will now use gcode loaded in the controller instead of cached local file diff --git a/carveracontroller/main.py b/carveracontroller/main.py index 3b35b8b9..ba04bb47 100644 --- a/carveracontroller/main.py +++ b/carveracontroller/main.py @@ -2919,6 +2919,9 @@ class Makera(RelativeLayout): _remaining_anchor_sec = 0.0 _remaining_anchor_time = 0.0 _progress_smooth_clock = None + _progress_timers_paused = False + _progress_remaining_frozen = 0.0 + _progress_elapsed_frozen = 0 show_update = True instantFSoverride = True @@ -5442,6 +5445,27 @@ def _on_time_estimate_progress(self, state, percent): self.progressFinish() # --------------------------------------------------------------`--------- + _PROGRESS_TIMER_PAUSED_STATES = frozenset({"Hold", "Pause", "Wait", "Tool"}) + + def _job_progress_timers_paused(self, app): + return app.playing and app.state in self._PROGRESS_TIMER_PAUSED_STATES + + def _current_remaining_sec(self): + return max(0.0, self._remaining_anchor_sec - (time.time() - self._remaining_anchor_time)) + + def _sync_progress_timer_pause(self, app): + """Freeze or resume elapsed/remaining timers when job playback is paused.""" + timers_paused = self._job_progress_timers_paused(app) + if timers_paused: + if not self._progress_timers_paused: + self._progress_timers_paused = True + self._progress_remaining_frozen = self._current_remaining_sec() + self._progress_elapsed_frozen = CNC.vars["playedseconds"] + elif self._progress_timers_paused: + self._progress_timers_paused = False + self._remaining_anchor_sec = self._progress_remaining_frozen + self._remaining_anchor_time = time.time() + def _update_progress_smooth(self, dt): """Refresh elapsed/remaining display every second while playing.""" app = App.get_running_app() @@ -5451,16 +5475,15 @@ def _update_progress_smooth(self, dt): or not self.selected_file_line_count ): return - remaining_display = max(0.0, self._remaining_anchor_sec - (time.time() - self._remaining_anchor_time)) + self._sync_progress_timer_pause(app) + if self._progress_timers_paused: + remaining_display = self._progress_remaining_frozen + elapsed_display = self._progress_elapsed_frozen + else: + remaining_display = self._current_remaining_sec() + elapsed_display = CNC.vars["playedseconds"] filename = os.path.basename(app.selected_remote_filename or app.selected_local_filename) - self.progress_info = " {} ( {}/{} - {}%, {} elapsed, {} to go )".format( - filename, - self.played_lines, - self.selected_file_line_count, - int(self.wpb_play.value), - Utils.second2hour(CNC.vars["playedseconds"]), - Utils.second2hour(int(remaining_display)), - ) + self.progress_info = f" {filename} ( {self.played_lines}/{self.selected_file_line_count} - {int(self.wpb_play.value)}%, {Utils.second2hour(elapsed_display)} elapsed, {Utils.second2hour(int(remaining_display))} to go )" # --------------------------------------------------------------`--------- def updateCompressProgress(self, value): @@ -5497,6 +5520,9 @@ def updateStatus(self, *args): self.pausing = 1 if app.state == "Pause" else 0 self.waiting = 1 if app.state == "Wait" else 0 self.tooling = 1 if app.state == "Tool" else 0 + if app.playing: + self._sync_progress_timer_pause(app) + self._update_progress_smooth(0) # update status self.status_data_view.main_text = app.state if app.state == NOT_CONNECTED: @@ -5824,6 +5850,7 @@ def updateStatus(self, *args): self.played_lines = 0 # Reset after updating app.playing = False + self._progress_timers_paused = False self.wpb_margin.value = 0 self.wpb_zprobe.value = 0 self.wpb_leveling.value = 0 @@ -5868,12 +5895,16 @@ def updateStatus(self, *args): else: self._remaining_anchor_sec = 0.0 self._remaining_anchor_time = now + if self._progress_timers_paused: + self._progress_remaining_frozen = self._current_remaining_sec() + self._progress_elapsed_frozen = CNC.vars["playedseconds"] if ( (app.selected_remote_filename != "" or app.selected_local_filename != "") and self.selected_file_line_count > 0 and self._progress_smooth_clock is None ): self._progress_smooth_clock = Clock.schedule_interval(self._update_progress_smooth, 1.0) + self._sync_progress_timer_pause(app) # playing margin if CNC.vars["atc_state"] == 4: self.wpb_margin.value += 14 From 4bb75c1bf5517b46fa506560f81a7400f57effe9 Mon Sep 17 00:00:00 2001 From: Serge Bakharev Date: Sat, 18 Jul 2026 20:36:10 +1000 Subject: [PATCH 2/2] simplify pause state tracking to just use machine states --- carveracontroller/main.py | 47 +++++++++++---------------------------- 1 file changed, 13 insertions(+), 34 deletions(-) diff --git a/carveracontroller/main.py b/carveracontroller/main.py index ba04bb47..12b7f704 100644 --- a/carveracontroller/main.py +++ b/carveracontroller/main.py @@ -2919,9 +2919,6 @@ class Makera(RelativeLayout): _remaining_anchor_sec = 0.0 _remaining_anchor_time = 0.0 _progress_smooth_clock = None - _progress_timers_paused = False - _progress_remaining_frozen = 0.0 - _progress_elapsed_frozen = 0 show_update = True instantFSoverride = True @@ -5447,25 +5444,9 @@ def _on_time_estimate_progress(self, state, percent): # --------------------------------------------------------------`--------- _PROGRESS_TIMER_PAUSED_STATES = frozenset({"Hold", "Pause", "Wait", "Tool"}) - def _job_progress_timers_paused(self, app): - return app.playing and app.state in self._PROGRESS_TIMER_PAUSED_STATES - def _current_remaining_sec(self): return max(0.0, self._remaining_anchor_sec - (time.time() - self._remaining_anchor_time)) - def _sync_progress_timer_pause(self, app): - """Freeze or resume elapsed/remaining timers when job playback is paused.""" - timers_paused = self._job_progress_timers_paused(app) - if timers_paused: - if not self._progress_timers_paused: - self._progress_timers_paused = True - self._progress_remaining_frozen = self._current_remaining_sec() - self._progress_elapsed_frozen = CNC.vars["playedseconds"] - elif self._progress_timers_paused: - self._progress_timers_paused = False - self._remaining_anchor_sec = self._progress_remaining_frozen - self._remaining_anchor_time = time.time() - def _update_progress_smooth(self, dt): """Refresh elapsed/remaining display every second while playing.""" app = App.get_running_app() @@ -5473,17 +5454,13 @@ def _update_progress_smooth(self, dt): not app.playing or (not app.selected_remote_filename and not app.selected_local_filename) or not self.selected_file_line_count + or app.state in self._PROGRESS_TIMER_PAUSED_STATES ): + # While held/paused, leave the last progress_info unchanged so both timers freeze. return - self._sync_progress_timer_pause(app) - if self._progress_timers_paused: - remaining_display = self._progress_remaining_frozen - elapsed_display = self._progress_elapsed_frozen - else: - remaining_display = self._current_remaining_sec() - elapsed_display = CNC.vars["playedseconds"] + remaining_display = self._current_remaining_sec() filename = os.path.basename(app.selected_remote_filename or app.selected_local_filename) - self.progress_info = f" {filename} ( {self.played_lines}/{self.selected_file_line_count} - {int(self.wpb_play.value)}%, {Utils.second2hour(elapsed_display)} elapsed, {Utils.second2hour(int(remaining_display))} to go )" + self.progress_info = f" {filename} ( {self.played_lines}/{self.selected_file_line_count} - {int(self.wpb_play.value)}%, {Utils.second2hour(CNC.vars['playedseconds'])} elapsed, {Utils.second2hour(int(remaining_display))} to go )" # --------------------------------------------------------------`--------- def updateCompressProgress(self, value): @@ -5513,6 +5490,7 @@ def updateStatus(self, *args): return if app.state != CNC.vars["state"]: + prev_state = app.state app.state = CNC.vars["state"] CNC.vars["color"] = STATECOLOR[app.state] self.status_data_view.color = CNC.vars["color"] @@ -5521,8 +5499,14 @@ def updateStatus(self, *args): self.waiting = 1 if app.state == "Wait" else 0 self.tooling = 1 if app.state == "Tool" else 0 if app.playing: - self._sync_progress_timer_pause(app) - self._update_progress_smooth(0) + was_paused = prev_state in self._PROGRESS_TIMER_PAUSED_STATES + now_paused = app.state in self._PROGRESS_TIMER_PAUSED_STATES + if now_paused and not was_paused: + # Park remaining so pause duration is not subtracted on resume. + self._remaining_anchor_sec = self._current_remaining_sec() + self._remaining_anchor_time = now + elif was_paused and not now_paused: + self._remaining_anchor_time = now # update status self.status_data_view.main_text = app.state if app.state == NOT_CONNECTED: @@ -5850,7 +5834,6 @@ def updateStatus(self, *args): self.played_lines = 0 # Reset after updating app.playing = False - self._progress_timers_paused = False self.wpb_margin.value = 0 self.wpb_zprobe.value = 0 self.wpb_leveling.value = 0 @@ -5895,16 +5878,12 @@ def updateStatus(self, *args): else: self._remaining_anchor_sec = 0.0 self._remaining_anchor_time = now - if self._progress_timers_paused: - self._progress_remaining_frozen = self._current_remaining_sec() - self._progress_elapsed_frozen = CNC.vars["playedseconds"] if ( (app.selected_remote_filename != "" or app.selected_local_filename != "") and self.selected_file_line_count > 0 and self._progress_smooth_clock is None ): self._progress_smooth_clock = Clock.schedule_interval(self._update_progress_smooth, 1.0) - self._sync_progress_timer_pause(app) # playing margin if CNC.vars["atc_state"] == 4: self.wpb_margin.value += 14