diff --git a/NetworkPrinterOutputDevice.py b/NetworkPrinterOutputDevice.py index a1708f395e..de78a03467 100644 --- a/NetworkPrinterOutputDevice.py +++ b/NetworkPrinterOutputDevice.py @@ -8,6 +8,7 @@ from UM.Message import Message import UM.Settings from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState +import cura.Settings.ExtruderManager from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply from PyQt5.QtCore import QUrl, QTimer, pyqtSignal, pyqtProperty, pyqtSlot @@ -154,6 +155,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def _onAuthenticationRequired(self, reply, authenticator): if self._authentication_id is not None and self._authentication_key is not None: + Logger.log("d", "Authentication was required. Setting up authenticator.") authenticator.setUser(self._authentication_id) authenticator.setPassword(self._authentication_key) @@ -193,11 +195,13 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # \param auth_state \type{AuthState} Enum value representing the new auth state def setAuthenticationState(self, auth_state): if auth_state == AuthState.AuthenticationRequested: + Logger.log("d", "Authentication state changed to authentication requested.") self.setAcceptsCommands(False) self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network to {0} without access to control the printer.").format(self.name)) self._authentication_requested_message.show() self._authentication_timer.start() # Start timer so auth will fail after a while. elif auth_state == AuthState.Authenticated: + Logger.log("d", "Authentication state changed to authenticated") self.setAcceptsCommands(True) self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network to {0}.").format(self.name)) self._authentication_requested_message.hide() @@ -210,6 +214,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): # Once we are authenticated we need to send all material profiles. self.sendMaterialProfiles() elif auth_state == AuthState.AuthenticationDenied: + Logger.log("d", "Authentication state changed to authentication denied") self.setAcceptsCommands(False) self._authentication_requested_message.hide() self._authentication_failed_message.show() @@ -227,16 +232,25 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._authentication_requested_message.setProgress(0) self._authentication_id = None self._authentication_key = None + self._createNetworkManager() # Re-create network manager to force re-authentication. ## Request data from the connected device. def _update(self): + if self._last_response_time: + time_since_last_response = time() - self._last_response_time + else: + time_since_last_response = 0 + # Connection is in timeout, check if we need to re-start the connection. # Sometimes the qNetwork manager incorrectly reports the network status on Mac & Windows. # Re-creating the QNetworkManager seems to fix this issue. if self._last_response_time and self._connection_state_before_timeout: - if time() - self._last_response_time > self._recreate_network_manager_time * self._recreate_network_manager_count: - self._recreate_network_manager_count += 1 - Logger.log("d", "Timeout lasted over 30 seconds, re-checking connection.") + if time_since_last_response > self._recreate_network_manager_time * self._recreate_network_manager_count: + # It can happen that we had a very long timeout (multiple times the recreate time). + # In that case we should jump through the point that the next update won't be right away. + while time_since_last_response - self._recreate_network_manager_time * self._recreate_network_manager_count > self._recreate_network_manager_time: + self._recreate_network_manager_count += 1 + Logger.log("d", "Timeout lasted over 30 seconds (%.1fs), re-checking connection.", time_since_last_response) self._createNetworkManager() return @@ -251,34 +265,40 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): self._connection_message.show() # Check if we were uploading something. Abort if this is the case. # Some operating systems handle this themselves, others give weird issues. - if self._post_reply: - self._post_reply.abort() - try: - self._post_reply.uploadProgress.disconnect(self._onUploadProgress) - except TypeError: - pass # The disconnection can fail on mac in some cases. Ignore that. - self._progress_message.hide() + try: + if self._post_reply: + self._post_reply.abort() + try: + self._post_reply.uploadProgress.disconnect(self._onUploadProgress) + except TypeError: + pass # The disconnection can fail on mac in some cases. Ignore that. + self._progress_message.hide() + except RuntimeError: + self._post_reply = None # It can happen that the wrapped c++ object is already deleted. return else: self._recreate_network_manager_count = 1 # Check that we aren't in a timeout state if self._last_response_time and not self._connection_state_before_timeout: - if time() - self._last_response_time > self._response_timeout_time: + if time_since_last_response > self._response_timeout_time: # Go into timeout state. - Logger.log("d", "We did not receive a response for %0.1f seconds, so it seems the printer is no longer accessible.", time() - self._last_response_time) + Logger.log("d", "We did not receive a response for %0.1f seconds, so it seems the printer is no longer accessible.", time_since_last_response) self._connection_state_before_timeout = self._connection_state self._connection_message = Message(i18n_catalog.i18nc("@info:status", "The connection with the printer was lost. Check your printer to see if it is connected.")) self._connection_message.show() # Check if we were uploading something. Abort if this is the case. # Some operating systems handle this themselves, others give weird issues. - if self._post_reply: - self._post_reply.abort() - try: - self._post_reply.uploadProgress.disconnect(self._onUploadProgress) - except TypeError: - pass # The disconnection can fail on mac in some cases. Ignore that. - self._progress_message.hide() + try: + if self._post_reply: + self._post_reply.abort() + try: + self._post_reply.uploadProgress.disconnect(self._onUploadProgress) + except TypeError: + pass # The disconnection can fail on mac in some cases. Ignore that. + self._progress_message.hide() + except RuntimeError: + self._post_reply = None # It can happen that the wrapped c++ object is already deleted. self.setConnectionState(ConnectionState.error) return @@ -363,35 +383,81 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): def requestWrite(self, node, file_name = None, filter_by_machine = False): if self._progress != 0: - self._error_message = Message(i18n_catalog.i18nc("@info:status", "Printer is still printing. Unable to start a new job.")) + self._error_message = Message(i18n_catalog.i18nc("@info:status", "Unable to start a new print job because the printer is busy. Please check the printer.")) self._error_message.show() return if self._json_printer_state["status"] != "idle": self._error_message = Message( - i18n_catalog.i18nc("@info:status", "Unable to start a new print job, printer is not idle. Current printer status is %s.") % self._json_printer_state["status"]) + i18n_catalog.i18nc("@info:status", "Unable to start a new print job, printer is busy. Current printer status is %s.") % self._json_printer_state["status"]) self._error_message.show() return elif self._authentication_state != AuthState.Authenticated: self._not_authenticated_message = Message(i18n_catalog.i18nc("@info:status", "Not authenticated to print with this machine. Unable to start a new job.")) self._not_authenticated_message.show() + Logger.log("d", "Attempting to perform an action without authentication. Auth state is %s", self._authentication_state) return Application.getInstance().showPrintMonitor.emit(True) self._print_finished = True self._gcode = getattr(Application.getInstance().getController().getScene(), "gcode_list") - # TODO: Implement all checks. - # Check if cartridges are loaded at all (Error) - #self._json_printer_state["heads"][0]["extruders"][0]["hotend"]["id"] != "" + print_information = Application.getInstance().getPrintInformation() - # Check if there is material loaded at all (Error) - #self._json_printer_state["heads"][0]["extruders"][0]["active_material"]["GUID"] != "" + # Check if PrintCores / materials are loaded at all. Any failure in these results in an Error. + for index in range(0, self._num_extruders): + if print_information.materialLengths[index] != 0: + if self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"] == "": + Logger.log("e", "No cartridge loaded in slot %s, unable to start print", index + 1) + self._error_message = Message( + i18n_catalog.i18nc("@info:status", "Unable to start a new print job; no PrinterCore loaded in slot {0}".format(index + 1))) + self._error_message.show() + return + if self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["GUID"] == "": + Logger.log("e", "No material loaded in slot %s, unable to start print", index + 1) + self._error_message = Message( + i18n_catalog.i18nc("@info:status", + "Unable to start a new print job; no material loaded in slot {0}".format(index + 1))) + self._error_message.show() + return - # Check if there is enough material (Warning) - #self._json_printer_state["heads"][0]["extruders"][0]["active_material"]["length_remaining"] + warnings = [] # There might be multiple things wrong. Keep a list of all the stuff we need to warn about. - #TODO: Check if the cartridge is the right ID (give warning otherwise) + for index in range(0, self._num_extruders): + # Check if there is enough material. Any failure in these results in a warning. + material_length = self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["length_remaining"] + if material_length != -1 and print_information.materialLengths[index] > material_length: + warnings.append(i18n_catalog.i18nc("@label", "Not enough material for spool {0}.").format(index+1)) + + # Check if the right cartridges are loaded. Any failure in these results in a warning. + extruder_manager = cura.Settings.ExtruderManager.getInstance() + if print_information.materialLengths[index] != 0: + variant = extruder_manager.getExtruderStack(0).findContainer({"type": "variant"}) + if variant: + if variant.getName() != self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"]: + warnings.append(i18n_catalog.i18nc("@label", "Different PrintCore selected for extruder {0}".format(index + 1))) + + material = extruder_manager.getExtruderStack(0).findContainer({"type": "material"}) + if material: + if material.getMetaDataEntry("GUID") != self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["GUID"]: + warnings.append(i18n_catalog.i18nc("@label", "Different material selected for extruder {0}").format(index + 1)) + + if warnings: + text = i18n_catalog.i18nc("@label", "A number of configurations are mismatched. Are you sure you wish to print with the selected configuration?") + detailed_text = "