diff --git a/ks_includes/KlippyGtk.py b/ks_includes/KlippyGtk.py index c73260f2..d40e7afe 100644 --- a/ks_includes/KlippyGtk.py +++ b/ks_includes/KlippyGtk.py @@ -333,9 +333,14 @@ class KlippyGtk: def formatTimeString(self, seconds): time = int(seconds) text = "" + if int(time/86400) != 0: + text += str(int(time/86400))+"d " if int(time/3600) != 0: - text += str(int(time/3600))+"h " - text += str(int(time/60) % 60)+"m "+str(time % 60)+"s" + text += str(int(time/3600) % 24)+"h " + if int(time/60) != 0: + text += str(int(time/60) % 60)+"m " + else: + text = str(time % 60)+"s" return text def formatTemperatureString(self, temp, target): diff --git a/ks_includes/config.py b/ks_includes/config.py index 0fa87623..91891de4 100644 --- a/ks_includes/config.py +++ b/ks_includes/config.py @@ -124,8 +124,8 @@ class KlipperScreenConfig: {"print_estimate_method": { "section": "main", "name": _("Estimated Time Method"), "type": "dropdown", "value": "file", "options": [ - {"name": _("File") + " " + _("(default)"), "value": "file"}, - {"name": _("Duration Only"), "value": "duration"}, + {"name": _("Auto") + " " + _("(default)"), "value": "auto"}, + {"name": _("File"), "value": "file"}, {"name": _("Filament Used"), "value": "filament"}, {"name": _("Slicer"), "value": "slicer"}]}}, {"screen_blanking": { @@ -153,6 +153,9 @@ class KlipperScreenConfig: "value": "False", "callback": screen.restart_warning}}, {"use_dpms": {"section": "main", "name": _("Screen DPMS"), "type": "binary", "value": "True", "callback": screen.set_dpms}}, + {"print_estimate_compensation": { + "section": "main", "name": _("Slicer Time correction (%)"), "type": "scale", "value": "100", + "range": [50, 150], "step": 1}}, # {"": {"section": "main", "name": _(""), "type": ""}} ] diff --git a/panels/job_status.py b/panels/job_status.py index 303b38a1..1eb34e0b 100644 --- a/panels/job_status.py +++ b/panels/job_status.py @@ -130,14 +130,21 @@ class JobStatusPanel(ScreenPanel): self.labels['elapsed'].get_style_context().add_class("printing-info") self.labels['duration'] = Gtk.Label(label="0s") self.labels['duration'].get_style_context().add_class("printing-info") - self.labels['est_time'] = Gtk.Label(label="/ 0s") + self.labels['total'] = Gtk.Label(label=_("Total:")) + self.labels['total'].get_style_context().add_class("printing-info") + self.labels['est_time'] = Gtk.Label(label="0s") self.labels['est_time'].get_style_context().add_class("printing-info") - it_box = Gtk.Box(spacing=0) - it_box.add(clock) - it_box.add(self.labels['elapsed']) - it_box.add(self.labels['duration']) - it_box.add(self.labels['est_time']) - self.labels['it_box'] = it_box + timegrid = Gtk.Grid() + it1_box = Gtk.Box(spacing=0) + it1_box.add(self.labels['elapsed']) + it1_box.add(self.labels['duration']) + it2_box = Gtk.Box(spacing=0) + it2_box.add(self.labels['total']) + it2_box.add(self.labels['est_time']) + timegrid.attach(clock, 0, 0, 1, 2) + timegrid.attach(it1_box, 1, 0, 1, 1) + timegrid.attach(it2_box, 1, 1, 1, 1) + self.labels['timegrid'] = timegrid position = self._gtk.Image("move.svg", None, .6, .6) self.labels['pos_x'] = Gtk.Label(label="X: 0") @@ -241,7 +248,7 @@ class JobStatusPanel(ScreenPanel): self.labels['i2_box'].add(self.labels['temp_grid']) self.labels['i2_box'].add(self.labels['pos_box']) self.labels['i2_box'].add(self.labels['sfe_grid']) - self.labels['i2_box'].add(self.labels['it_box']) + self.labels['i2_box'].add(self.labels['timegrid']) self.labels['i2_box'].add(self.labels['itl_box']) @@ -412,29 +419,57 @@ class JobStatusPanel(ScreenPanel): self.update_percent_complete() self.update_text("duration", str(self._gtk.formatTimeString(ps['print_duration']))) + self.update_text("time_left", self.calculate_time_left(ps['print_duration'], ps['filament_used'])) - timeleft_type = self._config.get_config()['main'].get('print_estimate_method', 'file') - if timeleft_type != self.timeleft_type: - if self.timeleft_type == "duration": - self.labels['it_box'].add(self.labels['est_time']) - elif timeleft_type == "duration": - self.labels['it_box'].remove(self.labels['est_time']) - self.timeleft_type = timeleft_type + def calculate_time_left(self, duration=0, filament_used=0): + total_duration = None + if self.progress < 1: + slicer_time = filament_time = file_time = None + timeleft_type = self._config.get_config()['main'].get('print_estimate_method', 'auto') + slicer_correction = (self._config.get_config()['main'].getint('print_estimate_compensation', 100) / 100) + # speed_factor compensation based on empirical testing + spdcomp = math.sqrt(self.speed / 100) - if timeleft_type in ['filament', 'file', 'slicer']: - duration = ps['print_duration'] - if timeleft_type == "filament": - estimated_filament = (self.file_metadata['filament_total'] if "filament_total" in self.file_metadata - else 1) - total_duration = duration / (max(ps['filament_used'], 0.0001) / max(estimated_filament, 0.0001)) - elif timeleft_type == "file": - total_duration = duration / max(self.progress, 0.0001) - elif timeleft_type == "slicer": - total_duration = (self.file_metadata['estimated_time'] if "estimated_time" in self.file_metadata - else duration) - time_left = max(total_duration - duration, 0) - self.update_text("time_left", str(self._gtk.formatTimeString(time_left))) - self.update_text("est_time", "/ %s" % str(self._gtk.formatTimeString(total_duration))) + if "estimated_time" in self.file_metadata: + if self.file_metadata['estimated_time'] > 0: + slicer_time = (self.file_metadata['estimated_time'] * slicer_correction) / spdcomp + if slicer_time < duration: + slicer_time = None + + if "filament_total" in self.file_metadata: + if self.file_metadata['filament_total'] > 0 and filament_used > 0: + if self.file_metadata['filament_total'] > filament_used: + filament_time = duration / (filament_used / self.file_metadata['filament_total']) + if filament_time < duration: + filament_time = None + + if self.progress > 0: + file_time = duration / self.progress + + if timeleft_type == "file" and file_time is not None: + total_duration = file_time + elif timeleft_type == "filament" and filament_time is not None: + total_duration = filament_time + elif slicer_time is not None: + if timeleft_type == "slicer": + total_duration = slicer_time + else: + if filament_time is not None and self.progress > 0.14: + # Weighted arithmetic mean (Slicer is the most accurate) + total_duration = (slicer_time * 3 + filament_time + file_time) / 5 + else: + # At the begining file and filament are innacurate + total_duration = slicer_time + elif file_time is not None: + if filament_time is not None: + total_duration = (filament_time + file_time) / 2 + else: + total_duration = file_time + + if total_duration is None: + return "-" + self.update_text("est_time", str(self._gtk.formatTimeString(total_duration))) + return str(self._gtk.formatTimeString((total_duration - duration))) def state_check(self): ps = self._printer.get_stat("print_stats") @@ -541,7 +576,7 @@ class JobStatusPanel(ScreenPanel): self.file_metadata = self._files.get_file_info(self.filename) logging.info("Update Metadata. File: %s Size: %s" % (self.filename, self.file_metadata['size'])) if "estimated_time" in self.file_metadata and self.timeleft_type == "slicer": - self.update_text("est_time", "/ %s" % + self.update_text("est_time", str(self._gtk.formatTimeString(self.file_metadata['estimated_time']))) if "thumbnails" in self.file_metadata: tmp = self.file_metadata['thumbnails'].copy() @@ -578,7 +613,7 @@ class JobStatusPanel(ScreenPanel): self.labels[label].set_text(text) def update_progress(self): - self.labels['progress_text'].set_text("%s%%" % (str(min(int(round(self.progress, 2)*100), 100)))) + self.labels['progress_text'].set_text("%s%%" % (str(min(int(self.progress*100), 100)))) def update_message(self): msg = self._printer.get_stat("display_status", "message")