Merge branch '2.3' of github.com:Ultimaker/JediWifiPrintingPlugin

This commit is contained in:
Jaime van Kessel 2016-09-07 15:12:08 +02:00
commit 7e12bcec7f

View file

@ -8,6 +8,7 @@ from UM.Message import Message
import UM.Settings import UM.Settings
from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
import cura.Settings.ExtruderManager
from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply
from PyQt5.QtCore import QUrl, QTimer, pyqtSignal, pyqtProperty, pyqtSlot from PyQt5.QtCore import QUrl, QTimer, pyqtSignal, pyqtProperty, pyqtSlot
@ -154,6 +155,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
def _onAuthenticationRequired(self, reply, authenticator): def _onAuthenticationRequired(self, reply, authenticator):
if self._authentication_id is not None and self._authentication_key is not None: 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.setUser(self._authentication_id)
authenticator.setPassword(self._authentication_key) authenticator.setPassword(self._authentication_key)
@ -193,11 +195,13 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
# \param auth_state \type{AuthState} Enum value representing the new auth state # \param auth_state \type{AuthState} Enum value representing the new auth state
def setAuthenticationState(self, auth_state): def setAuthenticationState(self, auth_state):
if auth_state == AuthState.AuthenticationRequested: if auth_state == AuthState.AuthenticationRequested:
Logger.log("d", "Authentication state changed to authentication requested.")
self.setAcceptsCommands(False) 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.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_requested_message.show()
self._authentication_timer.start() # Start timer so auth will fail after a while. self._authentication_timer.start() # Start timer so auth will fail after a while.
elif auth_state == AuthState.Authenticated: elif auth_state == AuthState.Authenticated:
Logger.log("d", "Authentication state changed to authenticated")
self.setAcceptsCommands(True) self.setAcceptsCommands(True)
self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network to {0}.").format(self.name)) self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network to {0}.").format(self.name))
self._authentication_requested_message.hide() self._authentication_requested_message.hide()
@ -210,6 +214,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
# Once we are authenticated we need to send all material profiles. # Once we are authenticated we need to send all material profiles.
self.sendMaterialProfiles() self.sendMaterialProfiles()
elif auth_state == AuthState.AuthenticationDenied: elif auth_state == AuthState.AuthenticationDenied:
Logger.log("d", "Authentication state changed to authentication denied")
self.setAcceptsCommands(False) self.setAcceptsCommands(False)
self._authentication_requested_message.hide() self._authentication_requested_message.hide()
self._authentication_failed_message.show() self._authentication_failed_message.show()
@ -227,16 +232,25 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
self._authentication_requested_message.setProgress(0) self._authentication_requested_message.setProgress(0)
self._authentication_id = None self._authentication_id = None
self._authentication_key = None self._authentication_key = None
self._createNetworkManager() # Re-create network manager to force re-authentication.
## Request data from the connected device. ## Request data from the connected device.
def _update(self): 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. # 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. # Sometimes the qNetwork manager incorrectly reports the network status on Mac & Windows.
# Re-creating the QNetworkManager seems to fix this issue. # Re-creating the QNetworkManager seems to fix this issue.
if self._last_response_time and self._connection_state_before_timeout: 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: if time_since_last_response > self._recreate_network_manager_time * self._recreate_network_manager_count:
self._recreate_network_manager_count += 1 # It can happen that we had a very long timeout (multiple times the recreate time).
Logger.log("d", "Timeout lasted over 30 seconds, re-checking connection.") # 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() self._createNetworkManager()
return return
@ -251,34 +265,40 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
self._connection_message.show() self._connection_message.show()
# Check if we were uploading something. Abort if this is the case. # Check if we were uploading something. Abort if this is the case.
# Some operating systems handle this themselves, others give weird issues. # Some operating systems handle this themselves, others give weird issues.
if self._post_reply: try:
self._post_reply.abort() if self._post_reply:
try: self._post_reply.abort()
self._post_reply.uploadProgress.disconnect(self._onUploadProgress) try:
except TypeError: self._post_reply.uploadProgress.disconnect(self._onUploadProgress)
pass # The disconnection can fail on mac in some cases. Ignore that. except TypeError:
self._progress_message.hide() 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 return
else: else:
self._recreate_network_manager_count = 1 self._recreate_network_manager_count = 1
# Check that we aren't in a timeout state # Check that we aren't in a timeout state
if self._last_response_time and not self._connection_state_before_timeout: 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. # 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_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 = 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() self._connection_message.show()
# Check if we were uploading something. Abort if this is the case. # Check if we were uploading something. Abort if this is the case.
# Some operating systems handle this themselves, others give weird issues. # Some operating systems handle this themselves, others give weird issues.
if self._post_reply: try:
self._post_reply.abort() if self._post_reply:
try: self._post_reply.abort()
self._post_reply.uploadProgress.disconnect(self._onUploadProgress) try:
except TypeError: self._post_reply.uploadProgress.disconnect(self._onUploadProgress)
pass # The disconnection can fail on mac in some cases. Ignore that. except TypeError:
self._progress_message.hide() 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) self.setConnectionState(ConnectionState.error)
return return
@ -363,35 +383,81 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
def requestWrite(self, node, file_name = None, filter_by_machine = False): def requestWrite(self, node, file_name = None, filter_by_machine = False):
if self._progress != 0: 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() self._error_message.show()
return return
if self._json_printer_state["status"] != "idle": if self._json_printer_state["status"] != "idle":
self._error_message = Message( 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() self._error_message.show()
return return
elif self._authentication_state != AuthState.Authenticated: elif self._authentication_state != AuthState.Authenticated:
self._not_authenticated_message = Message(i18n_catalog.i18nc("@info:status", self._not_authenticated_message = Message(i18n_catalog.i18nc("@info:status",
"Not authenticated to print with this machine. Unable to start a new job.")) "Not authenticated to print with this machine. Unable to start a new job."))
self._not_authenticated_message.show() self._not_authenticated_message.show()
Logger.log("d", "Attempting to perform an action without authentication. Auth state is %s", self._authentication_state)
return return
Application.getInstance().showPrintMonitor.emit(True) Application.getInstance().showPrintMonitor.emit(True)
self._print_finished = True self._print_finished = True
self._gcode = getattr(Application.getInstance().getController().getScene(), "gcode_list") self._gcode = getattr(Application.getInstance().getController().getScene(), "gcode_list")
# TODO: Implement all checks. print_information = Application.getInstance().getPrintInformation()
# Check if cartridges are loaded at all (Error)
#self._json_printer_state["heads"][0]["extruders"][0]["hotend"]["id"] != ""
# Check if there is material loaded at all (Error) # Check if PrintCores / materials are loaded at all. Any failure in these results in an Error.
#self._json_printer_state["heads"][0]["extruders"][0]["active_material"]["GUID"] != "" 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) warnings = [] # There might be multiple things wrong. Keep a list of all the stuff we need to warn about.
#self._json_printer_state["heads"][0]["extruders"][0]["active_material"]["length_remaining"]
#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 = "<ul>"
for warning in warnings:
detailed_text += "<li>" + warning + "</li>"
detailed_text += "</ul>"
Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Mismatched configuration"),
text,
detailed_text,
buttons=QMessageBox.Yes + QMessageBox.No,
icon=QMessageBox.Question,
callback=self._configurationCallback
)
return
self.startPrint() self.startPrint()
@ -460,7 +526,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
self._send_gcode_start = time() self._send_gcode_start = time()
self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), 0, False, -1) self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), 0, False, -1)
self._progress_message.show() self._progress_message.show()
Logger.log("d", "Started sending g-code to remote printer.")
## Mash the data into single string ## Mash the data into single string
single_string_file_data = "" single_string_file_data = ""
for line in self._gcode: for line in self._gcode:
@ -503,6 +569,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
## Check if the authentication request was allowed by the printer. ## Check if the authentication request was allowed by the printer.
def _checkAuthentication(self): def _checkAuthentication(self):
Logger.log("d", "Checking if authentication is correct.")
self._manager.get(QNetworkRequest(QUrl("http://" + self._address + self._api_prefix + "auth/check/" + str(self._authentication_id)))) self._manager.get(QNetworkRequest(QUrl("http://" + self._address + self._api_prefix + "auth/check/" + str(self._authentication_id))))
## Request a authentication key from the printer so we can be authenticated ## Request a authentication key from the printer so we can be authenticated
@ -639,7 +706,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
Logger.log("i", "Not authenticated. Attempting to request authentication") Logger.log("i", "Not authenticated. Attempting to request authentication")
self._requestAuthentication() self._requestAuthentication()
elif status_code == 403: elif status_code == 403:
pass # If we already had an auth (eg; didn't request one), we only need a single 403 to see it as denied.
if self._authentication_state != AuthState.AuthenticationRequested:
self.setAuthenticationState(AuthState.AuthenticationDenied)
elif status_code == 200: elif status_code == 200:
self.setAuthenticationState(AuthState.Authenticated) self.setAuthenticationState(AuthState.Authenticated)
global_container_stack = Application.getInstance().getGlobalContainerStack() global_container_stack = Application.getInstance().getGlobalContainerStack()
@ -698,6 +767,9 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
def _onUploadProgress(self, bytes_sent, bytes_total): def _onUploadProgress(self, bytes_sent, bytes_total):
if bytes_total > 0: if bytes_total > 0:
new_progress = bytes_sent / bytes_total * 100 new_progress = bytes_sent / bytes_total * 100
# Treat upload progress as response. Uploading can take more than 10 seconds, so if we don't, we can get
# timeout responses if this happens.
self._last_response_time = time()
if new_progress > self._progress_message.getProgress(): if new_progress > self._progress_message.getProgress():
self._progress_message.show() # Ensure that the message is visible. self._progress_message.show() # Ensure that the message is visible.
self._progress_message.setProgress(bytes_sent / bytes_total * 100) self._progress_message.setProgress(bytes_sent / bytes_total * 100)
@ -715,4 +787,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
buttons=QMessageBox.Yes + QMessageBox.No, buttons=QMessageBox.Yes + QMessageBox.No,
icon=QMessageBox.Question, icon=QMessageBox.Question,
callback=callback callback=callback
) )
def _configurationCallback(self, button):
if button == QMessageBox.Yes:
self.startPrint()