From 826f438c7219cfa777675bfd2ec03848db794084 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Sun, 12 Nov 2017 15:29:31 +0100 Subject: [PATCH 1/7] Get target temperatures from M105 responses Contributes to #2760 --- plugins/USBPrinting/USBPrinterOutputDevice.py | 59 ++++++++++++++++--- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index 3b9603cc1b..26a09242b1 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -125,6 +125,29 @@ class USBPrinterOutputDevice(PrinterOutputDevice): def _homeBed(self): self._sendCommand("G28 Z") + ## Updates the target bed temperature from the printer, and emit a signal if it was changed. + # + # /param temperature The new target temperature of the bed. + # /return boolean, True if the temperature was changed, false if the new temperature has the same value as the already stored temperature + def _updateTargetBedTemperature(self, temperature): + if self._target_bed_temperature == temperature: + return False + self._target_bed_temperature = temperature + self.targetBedTemperatureChanged.emit() + return True + + ## Updates the target hotend temperature from the printer, and emit a signal if it was changed. + # + # /param index The index of the hotend. + # /param temperature The new target temperature of the hotend. + # /return boolean, True if the temperature was changed, false if the new temperature has the same value as the already stored temperature + def _updateTargetHotendTemperature(self, index, temperature): + if self._target_hotend_temperatures[index] == temperature: + return False + self._target_hotend_temperatures[index] = temperature + self.targetHotendTemperaturesChanged.emit() + return True + ## A name for the device. @pyqtProperty(str, constant = True) def name(self): @@ -511,16 +534,36 @@ class USBPrinterOutputDevice(PrinterOutputDevice): self._setErrorState(line[6:]) elif b" T:" in line or line.startswith(b"T:"): # Temperature message + temperature_matches = re.findall(b"T(\d*):s?*([\d\.]+)\s?\/?([\d\.]+)?", line) + temperature_set = False try: - self._setHotendTemperature(self._temperature_requested_extruder_index, float(re.search(b"T: *([0-9\.]*)", line).group(1))) + for match in temperature_matches: + if match[0]: + extruder_nr = int(match[0]) + if match[1]: + self._setHotendTemperature(extruder_nr, float(match[1])) + temperature_set = True + if match[2]: + self._updateTargetHotendTemperature(extruder_nr, float(match[2])) + else: + requested_temperatures = match + if not temperature_set and requested_temperatures: + if requested_temperatures[1]: + self._setHotendTemperature(self._temperature_requested_extruder_index, float(requested_temperatures[1])) + if requested_temperatures[2]: + self._updateTargetHotendTemperature(self._temperature_requested_extruder_index, float(requested_temperatures[1])) except: - pass - if b"B:" in line: # Check if it's a bed temperature - try: - self._setBedTemperature(float(re.search(b"B: *([0-9\.]*)", line).group(1))) - except Exception as e: - pass - #TODO: temperature changed callback + Logger.log("w", "Could not parse hotend temperatures from response: %s", line) + # Check if there's also a bed temperature + temperature_matches = re.findall(b"B:\s?*([\d\.]+)\s?\/?([\d\.]+)?", line) + try: + if match[0]: + self._setBedTemperature(float(match[0])) + if match[1]: + self._updateTargetBedTemperature(float(match[1])) + except: + Logger.log("w", "Could not parse bed temperature from response: %s", line) + elif b"_min" in line or b"_max" in line: tag, value = line.split(b":", 1) self._setEndstopState(tag,(b"H" in value or b"TRIGGERED" in value)) From 698f42b837cc7404e022cd820d668a72c8525d78 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Sun, 12 Nov 2017 15:37:43 +0100 Subject: [PATCH 2/7] Set job name when starting a print and clear it after printing --- plugins/USBPrinting/USBPrinterOutputDevice.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index 26a09242b1..19e19e6487 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -52,6 +52,8 @@ class USBPrinterOutputDevice(PrinterOutputDevice): self._heatup_wait_start_time = time.time() + self.jobStateChanged.connect(self._onJobStateChanged) + ## Queue for commands that need to be send. Used when command is sent when a print is active. self._command_queue = queue.Queue() @@ -469,7 +471,6 @@ class USBPrinterOutputDevice(PrinterOutputDevice): # # \param nodes A collection of scene nodes to send. This is ignored. # \param file_name \type{string} A suggestion for a file name to write. - # This is ignored. # \param filter_by_machine Whether to filter MIME types by machine. This # is ignored. # \param kwargs Keyword arguments. @@ -485,6 +486,8 @@ class USBPrinterOutputDevice(PrinterOutputDevice): self._error_message.show() return + self.setJobName(file_name) + Application.getInstance().showPrintMonitor.emit(True) self.startPrint() @@ -643,6 +646,11 @@ class USBPrinterOutputDevice(PrinterOutputDevice): elif job_state == "abort": self.cancelPrint() + def _onJobStateChanged(self): + # clear the job name when printing is done or aborted + if self._job_state == "ready": + self.setJobName("") + ## Set the progress of the print. # It will be normalized (based on max_progress) to range 0 - 100 def setProgress(self, progress, max_progress = 100): From e0c37cc56eeaf23fe1046f23eb1c9e733fcddf30 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Sun, 12 Nov 2017 20:59:31 +0100 Subject: [PATCH 3/7] Add elapsed time and total time --- plugins/USBPrinting/USBPrinterOutputDevice.py | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index 19e19e6487..b9388808ae 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -13,6 +13,7 @@ from UM.Application import Application from UM.Logger import Logger from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState from UM.Message import Message +from UM.Qt.Duration import DurationFormat from PyQt5.QtCore import QUrl, pyqtSlot, pyqtSignal, pyqtProperty @@ -62,7 +63,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice): ## Set when print is started in order to check running time. self._print_start_time = None - self._print_start_time_100 = None + self._print_estimated_time = None ## Keep track where in the provided g-code the print is self._gcode_position = 0 @@ -189,7 +190,6 @@ class USBPrinterOutputDevice(PrinterOutputDevice): # Reset line number. If this is not done, first line is sometimes ignored self._gcode.insert(0, "M110") self._gcode_position = 0 - self._print_start_time_100 = None self._is_printing = True self._print_start_time = time.time() @@ -487,6 +487,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice): return self.setJobName(file_name) + self._print_estimated_time = int(Application.getInstance().getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.Seconds)) Application.getInstance().showPrintMonitor.emit(True) self.startPrint() @@ -605,8 +606,6 @@ class USBPrinterOutputDevice(PrinterOutputDevice): def _sendNextGcodeLine(self): if self._gcode_position >= len(self._gcode): return - if self._gcode_position == 100: - self._print_start_time_100 = time.time() line = self._gcode[self._gcode_position] if ";" in line: @@ -630,8 +629,18 @@ class USBPrinterOutputDevice(PrinterOutputDevice): checksum = functools.reduce(lambda x,y: x^y, map(ord, "N%d%s" % (self._gcode_position, line))) self._sendCommand("N%d%s*%d" % (self._gcode_position, line, checksum)) + + progress = (self._gcode_position / len(self._gcode)) + + elapsed_time = int(time.time() - self._print_start_time) + self.setTimeElapsed(elapsed_time) + estimated_time = self._print_estimated_time + if progress > .1: + estimated_time = self._print_estimated_time * (1-progress) + elapsed_time + self.setTimeTotal(estimated_time) + self._gcode_position += 1 - self.setProgress((self._gcode_position / len(self._gcode)) * 100) + self.setProgress(progress * 100) self.progressChanged.emit() ## Set the state of the print. @@ -647,9 +656,11 @@ class USBPrinterOutputDevice(PrinterOutputDevice): self.cancelPrint() def _onJobStateChanged(self): - # clear the job name when printing is done or aborted + # clear the job name & times when printing is done or aborted if self._job_state == "ready": self.setJobName("") + self.setTimeElapsed(0) + self.setTimeTotal(0) ## Set the progress of the print. # It will be normalized (based on max_progress) to range 0 - 100 From 2d2b943d073c56a8ada45a21ed695c4fa5e318b6 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Mon, 13 Nov 2017 10:44:49 +0100 Subject: [PATCH 4/7] Fix getting temperatures --- plugins/USBPrinting/USBPrinterOutputDevice.py | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index b9388808ae..dd8a2f0c4c 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -509,6 +509,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice): ## Listen thread function. def _listen(self): Logger.log("i", "Printer connection listen thread started for %s" % self._serial_port) + container_stack = Application.getInstance().getGlobalContainerStack() temperature_request_timeout = time.time() ok_timeout = time.time() while self._connection_state == ConnectionState.connected: @@ -538,12 +539,14 @@ class USBPrinterOutputDevice(PrinterOutputDevice): self._setErrorState(line[6:]) elif b" T:" in line or line.startswith(b"T:"): # Temperature message - temperature_matches = re.findall(b"T(\d*):s?*([\d\.]+)\s?\/?([\d\.]+)?", line) + temperature_matches = re.findall(b"T(\d*): ?([\d\.]+) ?\/?([\d\.]+)?", line) temperature_set = False try: for match in temperature_matches: if match[0]: extruder_nr = int(match[0]) + if extruder_nr >= container_stack.getProperty("machine_extruder_count", "value"): + continue if match[1]: self._setHotendTemperature(extruder_nr, float(match[1])) temperature_set = True @@ -555,18 +558,20 @@ class USBPrinterOutputDevice(PrinterOutputDevice): if requested_temperatures[1]: self._setHotendTemperature(self._temperature_requested_extruder_index, float(requested_temperatures[1])) if requested_temperatures[2]: - self._updateTargetHotendTemperature(self._temperature_requested_extruder_index, float(requested_temperatures[1])) + self._updateTargetHotendTemperature(self._temperature_requested_extruder_index, float(requested_temperatures[2])) except: Logger.log("w", "Could not parse hotend temperatures from response: %s", line) # Check if there's also a bed temperature - temperature_matches = re.findall(b"B:\s?*([\d\.]+)\s?\/?([\d\.]+)?", line) - try: - if match[0]: - self._setBedTemperature(float(match[0])) - if match[1]: - self._updateTargetBedTemperature(float(match[1])) - except: - Logger.log("w", "Could not parse bed temperature from response: %s", line) + temperature_matches = re.findall(b"B: ?([\d\.]+) ?\/?([\d\.]+)?", line) + if container_stack.getProperty("machine_heated_bed", "value") and len(temperature_matches) > 0: + match = temperature_matches[0] + try: + if match[0]: + self._setBedTemperature(float(match[0])) + if match[1]: + self._updateTargetBedTemperature(float(match[1])) + except: + Logger.log("w", "Could not parse bed temperature from response: %s", line) elif b"_min" in line or b"_max" in line: tag, value = line.split(b":", 1) From bc9d31ccbc9d4522927fa97bead9f1224e5bd72c Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Sun, 26 Nov 2017 21:44:09 +0100 Subject: [PATCH 5/7] Allow postprocessing before sending data to the printer Fixes https://github.com/Ultimaker/Cura/issues/2855 Also see https://github.com/Ultimaker/Cura/commit/91e8ac6868762595a73363230234d223c1270753 --- plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py b/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py index b6e94121f8..de1aab434d 100644 --- a/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py +++ b/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py @@ -263,6 +263,8 @@ class NetworkClusterPrinterOutputDevice(NetworkPrinterOutputDevice.NetworkPrinte self._error_message.show() return + self.writeStarted.emit(self) # Allow postprocessing before sending data to the printer + if len(self._printers) > 1: self.spawnPrintView() # Ask user how to print it. elif len(self._printers) == 1: From 4303278602d2d93f68342e3d9939c193814ad015 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 30 Nov 2017 10:35:31 +0100 Subject: [PATCH 6/7] Tweak layout and alignment of print time and (material) cost estimation... improving readability * table takes the full width and values are right-aligned * time estimates are shortened * values are right aligned * feature/time/percentage are on a single line again * handle long feature & material names gracefully Fixes #2712 * totals are only shown when there are multiple extruders --- resources/qml/Sidebar.qml | 69 +++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 29 deletions(-) diff --git a/resources/qml/Sidebar.qml b/resources/qml/Sidebar.qml index 31bda45b56..e02bb00c0a 100644 --- a/resources/qml/Sidebar.qml +++ b/resources/qml/Sidebar.qml @@ -350,21 +350,20 @@ Rectangle var total_seconds = parseInt(base.printDuration.getDisplayString(UM.DurationFormat.Seconds)) // A message is created and displayed when the user hover the time label - var content = catalog.i18nc("@tooltip", "Time specification
"); + var tooltip_html = "%1
".arg(catalog.i18nc("@tooltip", "Time specification")); for(var feature in print_time) { if(!print_time[feature].isTotalDurationZero) { - content += "" + - "" + - "" + - "" + + "".arg(print_time[feature].getDisplayString(UM.DurationFormat.ISO8601).slice(0,-3)) + + "".arg(Math.round(100 * parseInt(print_time[feature].getDisplayString(UM.DurationFormat.Seconds)) / total_seconds)) + ""; } } - content += "
" + feature + "
" + print_time[feature].getDisplayString(UM.DurationFormat.Short) + "  " + Math.round(100 * parseInt(print_time[feature].getDisplayString(UM.DurationFormat.Seconds)) / total_seconds) + "%" + + tooltip_html += "
" + feature + " (in a more verbose language):  %1  %1%
"; + tooltip_html += ""; - base.showTooltip(parent, Qt.point(-UM.Theme.getSize("sidebar_margin").width, 0), content); + base.showTooltip(parent, Qt.point(-UM.Theme.getSize("sidebar_margin").width, 0), tooltip_html); } } onExited: @@ -376,9 +375,26 @@ Rectangle Label { + function formatRow(items) + { + var row_html = ""; + for(var item = 0; item < items.length; item++) + { + if (item == 0) + { + row_html += "%1".arg(items[item]); + } + else + { + row_html += "  %1".arg(items[item]); + } + } + row_html += ""; + return row_html; + } - function getSpecsData(){ - + function getSpecsData() + { var lengths = []; var total_length = 0; var weights = []; @@ -387,7 +403,8 @@ Rectangle var total_cost = 0; var some_costs_known = false; var names = []; - if(base.printMaterialLengths) { + if(base.printMaterialLengths) + { for(var index = 0; index < base.printMaterialLengths.length; index++) { if(base.printMaterialLengths[index] > 0) @@ -415,34 +432,28 @@ Rectangle costs = ["0.00"]; } - var tooltip_html = "%1
".arg(catalog.i18nc("@label", "Cost specification")); + var tooltip_html = "%1
".arg(catalog.i18nc("@label", "Cost specification")); for(var index = 0; index < lengths.length; index++) { - var item_strings = [ + tooltip_html += formatRow([ "%1:".arg(names[index]), catalog.i18nc("@label m for meter", "%1m").arg(lengths[index]), catalog.i18nc("@label g for grams", "%1g").arg(weights[index]), - "%1 %2".arg(UM.Preferences.getValue("cura/currency")).arg(costs[index]), - ]; - tooltip_html += ""; - for(var item = 0; item < item_strings.length; item++) { - tooltip_html += "".arg(item_strings[item]); - } + "%1 %2".arg(UM.Preferences.getValue("cura/currency")).arg(costs[index]), + ]); } - var item_strings = [ - catalog.i18nc("@label", "Total:"), - catalog.i18nc("@label m for meter", "%1m").arg(total_length.toFixed(2)), - catalog.i18nc("@label g for grams", "%1g").arg(Math.round(total_weight)), - "%1 %2".arg(UM.Preferences.getValue("cura/currency")).arg(total_cost.toFixed(2)), - ]; - tooltip_html += ""; - for(var item = 0; item < item_strings.length; item++) { - tooltip_html += "".arg(item_strings[item]); + if(lengths.length > 1) + { + tooltip_html += formatRow([ + catalog.i18nc("@label", "Total:"), + catalog.i18nc("@label m for meter", "%1m").arg(total_length.toFixed(2)), + catalog.i18nc("@label g for grams", "%1g").arg(Math.round(total_weight)), + "%1 %2".arg(UM.Preferences.getValue("cura/currency")).arg(total_cost.toFixed(2)), + ]); } - tooltip_html += "
%1  
%1  
"; + tooltip_html += ""; tooltipText = tooltip_html; - return tooltipText } From 9564bb645f8c0ca54c9e8ea950ced71320f2cd58 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 30 Nov 2017 15:54:24 +0100 Subject: [PATCH 7/7] Remove test code --- resources/qml/Sidebar.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/qml/Sidebar.qml b/resources/qml/Sidebar.qml index e02bb00c0a..bed5b4c873 100644 --- a/resources/qml/Sidebar.qml +++ b/resources/qml/Sidebar.qml @@ -350,12 +350,12 @@ Rectangle var total_seconds = parseInt(base.printDuration.getDisplayString(UM.DurationFormat.Seconds)) // A message is created and displayed when the user hover the time label - var tooltip_html = "%1
".arg(catalog.i18nc("@tooltip", "Time specification")); + var tooltip_html = "%1
".arg(catalog.i18nc("@tooltip", "Time specification")); for(var feature in print_time) { if(!print_time[feature].isTotalDurationZero) { - tooltip_html += "" + + tooltip_html += "" + "".arg(print_time[feature].getDisplayString(UM.DurationFormat.ISO8601).slice(0,-3)) + "".arg(Math.round(100 * parseInt(print_time[feature].getDisplayString(UM.DurationFormat.Seconds)) / total_seconds)) + "";
" + feature + " (in a more verbose language):
" + feature + ":  %1  %1%