diff --git a/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py b/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py index b69508ee75..44a2e8b743 100644 --- a/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py +++ b/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py @@ -252,6 +252,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: diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index abdb568cb2..77f45ee6d4 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 @@ -52,6 +53,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() @@ -60,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 @@ -125,6 +128,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): @@ -164,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() @@ -447,7 +472,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. @@ -463,6 +487,9 @@ class USBPrinterOutputDevice(PrinterOutputDevice): self._error_message.show() 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() @@ -483,6 +510,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: @@ -512,16 +540,40 @@ 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*): ?([\d\.]+) ?\/?([\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 extruder_nr >= container_stack.getProperty("machine_extruder_count", "value"): + continue + 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[2])) except: - pass - if b"B:" in line: # Check if it's a bed temperature + 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: ?([\d\.]+) ?\/?([\d\.]+)?", line) + if container_stack.getProperty("machine_heated_bed", "value") and len(temperature_matches) > 0: + match = temperature_matches[0] try: - self._setBedTemperature(float(re.search(b"B: *([0-9\.]*)", line).group(1))) - except Exception as e: - pass - #TODO: temperature changed callback + 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)) @@ -560,8 +612,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: @@ -585,8 +635,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. @@ -601,6 +661,13 @@ class USBPrinterOutputDevice(PrinterOutputDevice): elif job_state == "abort": self.cancelPrint() + def _onJobStateChanged(self): + # 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 def setProgress(self, progress, max_progress = 100): diff --git a/resources/qml/Sidebar.qml b/resources/qml/Sidebar.qml index 31bda45b56..bed5b4c873 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 + ":  %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 }