diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py index f273f537e3..5f9748c36d 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py @@ -10,7 +10,6 @@ from UM import i18nCatalog from UM.Backend.Backend import BackendState from UM.FileHandler.FileHandler import FileHandler from UM.Logger import Logger -from UM.Message import Message from UM.Scene.SceneNode import SceneNode from UM.Version import Version from cura.CuraApplication import CuraApplication @@ -20,6 +19,9 @@ from cura.PrinterOutput.PrinterOutputDevice import ConnectionType from .CloudApiClient import CloudApiClient from ..ExportFileJob import ExportFileJob from ..UltimakerNetworkedPrinterOutputDevice import UltimakerNetworkedPrinterOutputDevice +from ..Messages.PrintJobUploadBlockedMessage import PrintJobUploadBlockedMessage +from ..Messages.PrintJobUploadErrorMessage import PrintJobUploadErrorMessage +from ..Messages.PrintJobUploadSuccessMessage import PrintJobUploadSuccessMessage from ..Models.Http.CloudClusterResponse import CloudClusterResponse from ..Models.Http.CloudClusterStatus import CloudClusterStatus from ..Models.Http.CloudPrintJobUploadRequest import CloudPrintJobUploadRequest @@ -134,7 +136,6 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): ## Set all the interface elements and texts for this output device. def _setInterfaceElements(self) -> None: self.setPriority(2) # Make sure we end up below the local networking and above 'save to file'. - self.setName(self._id) self.setShortDescription(I18N_CATALOG.i18nc("@action:button", "Print via Cloud")) self.setDescription(I18N_CATALOG.i18nc("@properties:tooltip", "Print via Cloud")) self.setConnectionText(I18N_CATALOG.i18nc("@info:status", "Connected via Cloud")) @@ -169,20 +170,18 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): # Show an error message if we're already sending a job. if self._progress.visible: - return Message( - text=I18N_CATALOG.i18nc("@info:status", "Please wait until the current job has been sent."), - title=I18N_CATALOG.i18nc("@info:title", "Print error"), - lifetime=10 - ).show() - - if self._uploaded_print_job: - # The mesh didn't change, let's not upload it again - self._api.requestPrint(self.key, self._uploaded_print_job.job_id, self._onPrintUploadCompleted) + PrintJobUploadBlockedMessage().show() return # Indicate we have started sending a job. self.writeStarted.emit(self) + # The mesh didn't change, let's not upload it to the cloud again. + # Note that self.writeFinished is called in _onPrintUploadCompleted as well. + if self._uploaded_print_job: + self._api.requestPrint(self.key, self._uploaded_print_job.job_id, self._onPrintUploadCompleted) + return + # Export the scene to the correct file type. job = ExportFileJob(file_handler=file_handler, nodes=nodes, firmware_version=self.firmwareVersion) job.finished.connect(self._onPrintJobCreated) @@ -216,29 +215,21 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): print_job = cast(CloudPrintJobResponse, self._uploaded_print_job) self._api.requestPrint(self.key, print_job.job_id, self._onPrintUploadCompleted) + ## Shows a message when the upload has succeeded + # \param response: The response from the cloud API. + def _onPrintUploadCompleted(self, response: CloudPrintResponse) -> None: + self._progress.hide() + PrintJobUploadSuccessMessage().show() + self.writeFinished.emit() + ## Displays the given message if uploading the mesh has failed # \param message: The message to display. def _onUploadError(self, message: str = None) -> None: self._progress.hide() self._uploaded_print_job = None - Message( - text=message or I18N_CATALOG.i18nc("@info:text", "Could not upload the data to the printer."), - title=I18N_CATALOG.i18nc("@info:title", "Cloud error"), - lifetime=10 - ).show() + PrintJobUploadErrorMessage(message).show() self.writeError.emit() - ## Shows a message when the upload has succeeded - # \param response: The response from the cloud API. - def _onPrintUploadCompleted(self, response: CloudPrintResponse) -> None: - self._progress.hide() - Message( - text=I18N_CATALOG.i18nc("@info:status", "Print job was successfully sent to the printer."), - title=I18N_CATALOG.i18nc("@info:title", "Data Sent"), - lifetime=5 - ).show() - self.writeFinished.emit() - ## Whether the printer that this output device represents supports print job actions via the cloud. @pyqtProperty(bool, notify=_clusterPrintersChanged) def supportsPrintJobActions(self) -> bool: diff --git a/plugins/UM3NetworkPrinting/src/Messages/PrintJobUploadBlockedMessage.py b/plugins/UM3NetworkPrinting/src/Messages/PrintJobUploadBlockedMessage.py new file mode 100644 index 0000000000..be00292559 --- /dev/null +++ b/plugins/UM3NetworkPrinting/src/Messages/PrintJobUploadBlockedMessage.py @@ -0,0 +1,18 @@ +# Copyright (c) 2019 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. +from UM import i18nCatalog +from UM.Message import Message + + +I18N_CATALOG = i18nCatalog("cura") + + +## Message shown when uploading a print job to a cluster is blocked because another upload is already in progress. +class PrintJobUploadBlockedMessage(Message): + + def __init__(self) -> None: + super().__init__( + text = I18N_CATALOG.i18nc("@info:status", "Please wait until the current job has been sent."), + title = I18N_CATALOG.i18nc("@info:title", "Print error"), + lifetime = 10 + ) diff --git a/plugins/UM3NetworkPrinting/src/Messages/PrintJobUploadErrorMessage.py b/plugins/UM3NetworkPrinting/src/Messages/PrintJobUploadErrorMessage.py new file mode 100644 index 0000000000..bb26a84953 --- /dev/null +++ b/plugins/UM3NetworkPrinting/src/Messages/PrintJobUploadErrorMessage.py @@ -0,0 +1,18 @@ +# Copyright (c) 2019 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. +from UM import i18nCatalog +from UM.Message import Message + + +I18N_CATALOG = i18nCatalog("cura") + + +## Message shown when uploading a print job to a cluster failed. +class PrintJobUploadErrorMessage(Message): + + def __init__(self, message: str = None) -> None: + super().__init__( + text = message or I18N_CATALOG.i18nc("@info:text", "Could not upload the data to the printer."), + title = I18N_CATALOG.i18nc("@info:title", "Network error"), + lifetime = 10 + ) diff --git a/plugins/UM3NetworkPrinting/src/PrintJobUploadProgressMessage.py b/plugins/UM3NetworkPrinting/src/Messages/PrintJobUploadProgressMessage.py similarity index 100% rename from plugins/UM3NetworkPrinting/src/PrintJobUploadProgressMessage.py rename to plugins/UM3NetworkPrinting/src/Messages/PrintJobUploadProgressMessage.py diff --git a/plugins/UM3NetworkPrinting/src/Messages/PrintJobUploadSuccessMessage.py b/plugins/UM3NetworkPrinting/src/Messages/PrintJobUploadSuccessMessage.py new file mode 100644 index 0000000000..c9be28d57f --- /dev/null +++ b/plugins/UM3NetworkPrinting/src/Messages/PrintJobUploadSuccessMessage.py @@ -0,0 +1,18 @@ +# Copyright (c) 2019 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. +from UM import i18nCatalog +from UM.Message import Message + + +I18N_CATALOG = i18nCatalog("cura") + + +## Message shown when uploading a print job to a cluster succeeded. +class PrintJobUploadSuccessMessage(Message): + + def __init__(self) -> None: + super().__init__( + text = I18N_CATALOG.i18nc("@info:status", "Print job was successfully sent to the printer."), + title = I18N_CATALOG.i18nc("@info:title", "Data Sent"), + lifetime = 5 + ) diff --git a/plugins/UM3NetworkPrinting/src/Messages/__init__.py b/plugins/UM3NetworkPrinting/src/Messages/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/UM3NetworkPrinting/src/Network/ClusterApiClient.py b/plugins/UM3NetworkPrinting/src/Network/ClusterApiClient.py index 1025e384d6..495e75cb23 100644 --- a/plugins/UM3NetworkPrinting/src/Network/ClusterApiClient.py +++ b/plugins/UM3NetworkPrinting/src/Network/ClusterApiClient.py @@ -39,7 +39,7 @@ class ClusterApiClient: ## Get printer system information. # \param on_finished: The callback in case the response is successful. def getSystem(self, on_finished: Callable) -> None: - url = "{}/system/".format(self.PRINTER_API_PREFIX) + url = "{}/system".format(self.PRINTER_API_PREFIX) reply = self._manager.get(self._createEmptyRequest(url)) self._addCallback(reply, on_finished, PrinterSystemStatus) @@ -59,17 +59,17 @@ class ClusterApiClient: ## Move a print job to the top of the queue. def movePrintJobToTop(self, print_job_uuid: str) -> None: - url = "{}/print_jobs/{}/action/move".format(self.CLUSTER_API_PREFIX, print_job_uuid) + url = "{}/print_jobs/{}/action/move/".format(self.CLUSTER_API_PREFIX, print_job_uuid) self._manager.post(self._createEmptyRequest(url), json.dumps({"to_position": 0, "list": "queued"}).encode()) ## Delete a print job from the queue. def deletePrintJob(self, print_job_uuid: str) -> None: - url = "{}/print_jobs/{}".format(self.CLUSTER_API_PREFIX, print_job_uuid) + url = "{}/print_jobs/{}/".format(self.CLUSTER_API_PREFIX, print_job_uuid) self._manager.deleteResource(self._createEmptyRequest(url)) ## Set the state of a print job. def setPrintJobState(self, print_job_uuid: str, state: str) -> None: - url = "{}/print_jobs/{}/action".format(self.CLUSTER_API_PREFIX, print_job_uuid) + url = "{}/print_jobs/{}/action/".format(self.CLUSTER_API_PREFIX, print_job_uuid) # We rewrite 'resume' to 'print' here because we are using the old print job action endpoints. action = "print" if state == "resume" else state self._manager.put(self._createEmptyRequest(url), json.dumps({"action": action}).encode()) @@ -111,14 +111,17 @@ class ClusterApiClient: on_finished: Union[Callable[[ClusterApiClientModel], Any], Callable[[List[ClusterApiClientModel]], Any]], model_class: Type[ClusterApiClientModel]) -> None: - if isinstance(response, list): - results = [model_class(**c) for c in response] # type: List[ClusterApiClientModel] - on_finished_list = cast(Callable[[List[ClusterApiClientModel]], Any], on_finished) - on_finished_list(results) - else: - result = model_class(**response) # type: ClusterApiClientModel - on_finished_item = cast(Callable[[ClusterApiClientModel], Any], on_finished) - on_finished_item(result) + try: + if isinstance(response, list): + results = [model_class(**c) for c in response] # type: List[ClusterApiClientModel] + on_finished_list = cast(Callable[[List[ClusterApiClientModel]], Any], on_finished) + on_finished_list(results) + else: + result = model_class(**response) # type: ClusterApiClientModel + on_finished_item = cast(Callable[[ClusterApiClientModel], Any], on_finished) + on_finished_item(result) + except JSONDecodeError: + Logger.log("e", "Could not parse response from network: %s", str(response)) ## Creates a callback function so that it includes the parsing of the response into the correct model. # The callback is added to the 'finished' signal of the reply. diff --git a/plugins/UM3NetworkPrinting/src/Network/LocalClusterOutputDevice.py b/plugins/UM3NetworkPrinting/src/Network/LocalClusterOutputDevice.py index 758760ce86..ff704945eb 100644 --- a/plugins/UM3NetworkPrinting/src/Network/LocalClusterOutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/Network/LocalClusterOutputDevice.py @@ -7,7 +7,6 @@ from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal, pyqtProperty from PyQt5.QtNetwork import QNetworkReply from UM.FileHandler.FileHandler import FileHandler -from UM.Message import Message from UM.i18n import i18nCatalog from UM.Scene.SceneNode import SceneNode from cura.PrinterOutput.NetworkedPrinterOutputDevice import AuthState @@ -17,6 +16,9 @@ from .ClusterApiClient import ClusterApiClient from ..ExportFileJob import ExportFileJob from ..SendMaterialJob import SendMaterialJob from ..UltimakerNetworkedPrinterOutputDevice import UltimakerNetworkedPrinterOutputDevice +from ..Messages.PrintJobUploadBlockedMessage import PrintJobUploadBlockedMessage +from ..Messages.PrintJobUploadErrorMessage import PrintJobUploadErrorMessage +from ..Messages.PrintJobUploadSuccessMessage import PrintJobUploadSuccessMessage I18N_CATALOG = i18nCatalog("cura") @@ -46,7 +48,6 @@ class LocalClusterOutputDevice(UltimakerNetworkedPrinterOutputDevice): ## Set all the interface elements and texts for this output device. def _setInterfaceElements(self) -> None: self.setPriority(3) # Make sure the output device gets selected above local file output - self.setName(self._id) self.setShortDescription(I18N_CATALOG.i18nc("@action:button Preceded by 'Ready to'.", "Print over network")) self.setDescription(I18N_CATALOG.i18nc("@properties:tooltip", "Print over network")) self.setConnectionText(I18N_CATALOG.i18nc("@info:status", "Connected over the network")) @@ -111,11 +112,8 @@ class LocalClusterOutputDevice(UltimakerNetworkedPrinterOutputDevice): # Show an error message if we're already sending a job. if self._progress.visible: - return Message( - text=I18N_CATALOG.i18nc("@info:status", "Please wait until the current job has been sent."), - title=I18N_CATALOG.i18nc("@info:title", "Print error"), - lifetime=10 - ).show() + PrintJobUploadBlockedMessage().show() + return self.writeStarted.emit(self) @@ -147,22 +145,14 @@ class LocalClusterOutputDevice(UltimakerNetworkedPrinterOutputDevice): ## Handler for when the print job was fully uploaded to the cluster. def _onPrintUploadCompleted(self, _: QNetworkReply) -> None: self._progress.hide() - Message( - text=I18N_CATALOG.i18nc("@info:status", "Print job was successfully sent to the printer."), - title=I18N_CATALOG.i18nc("@info:title", "Data Sent"), - lifetime=5 - ).show() + PrintJobUploadSuccessMessage().show() self.writeFinished.emit() ## Displays the given message if uploading the mesh has failed # \param message: The message to display. def _onUploadError(self, message: str = None) -> None: self._progress.hide() - Message( - text=message or I18N_CATALOG.i18nc("@info:text", "Could not upload the data to the printer."), - title=I18N_CATALOG.i18nc("@info:title", "Network error"), - lifetime=10 - ).show() + PrintJobUploadErrorMessage(message).show() self.writeError.emit() ## Download all the images from the cluster and load their data in the print job models. diff --git a/plugins/UM3NetworkPrinting/src/UltimakerNetworkedPrinterOutputDevice.py b/plugins/UM3NetworkPrinting/src/UltimakerNetworkedPrinterOutputDevice.py index 70e85879cf..3d5e231088 100644 --- a/plugins/UM3NetworkPrinting/src/UltimakerNetworkedPrinterOutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/UltimakerNetworkedPrinterOutputDevice.py @@ -14,7 +14,7 @@ from cura.PrinterOutput.PrinterOutputDevice import ConnectionType from .Utils import formatTimeCompleted, formatDateCompleted from .ClusterOutputController import ClusterOutputController -from .PrintJobUploadProgressMessage import PrintJobUploadProgressMessage +from plugins.UM3NetworkPrinting.src.Messages.PrintJobUploadProgressMessage import PrintJobUploadProgressMessage from .Models.UM3PrintJobOutputModel import UM3PrintJobOutputModel from .Models.Http.ClusterPrinterStatus import ClusterPrinterStatus from .Models.Http.ClusterPrintJobStatus import ClusterPrintJobStatus @@ -45,6 +45,9 @@ class UltimakerNetworkedPrinterOutputDevice(NetworkedPrinterOutputDevice): # Trigger the printersChanged signal when the private signal is triggered. self.printersChanged.connect(self._clusterPrintersChanged) + + # Set the display name from the properties + self.setName(self.getProperty("name")) # Keeps track of all printers in the cluster. self._printers = [] # type: List[PrinterOutputModel]