From 1244e305008b4bb50b84dc5ac72c26f7d0be75cd Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Fri, 1 Feb 2019 15:50:49 +0100 Subject: [PATCH 01/21] Add cloud-flow-is-possible checking Contributes to CL-1222 --- .../src/Cloud/CloudOutputDeviceManager.py | 49 ++++++++++++++++--- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index 78944d954b..505b7fb419 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -7,6 +7,7 @@ from PyQt5.QtCore import QTimer from UM import i18nCatalog from UM.Logger import Logger from UM.Message import Message +from UM.Signal import Signal, signalemitter from cura.API import Account from cura.CuraApplication import CuraApplication from cura.Settings.GlobalStack import GlobalStack @@ -31,16 +32,21 @@ class CloudOutputDeviceManager: # The translation catalog for this device. I18N_CATALOG = i18nCatalog("cura") + cloudFlowIsPossible = Signal() + def __init__(self) -> None: # Persistent dict containing the remote clusters for the authenticated user. self._remote_clusters = {} # type: Dict[str, CloudOutputDevice] - application = CuraApplication.getInstance() - self._output_device_manager = application.getOutputDeviceManager() + self._application = CuraApplication.getInstance() + self._output_device_manager = self._application.getOutputDeviceManager() - self._account = application.getCuraAPI().account # type: Account + self._account = self._application.getCuraAPI().account # type: Account self._api = CloudApiClient(self._account, self._onApiError) + self._account.loginStateChanged.connect(self.checkCloudFlowIsPossible) + self.cloudFlowIsPossible.connect(self._onCloudFlowPossible) + # Create a timer to update the remote cluster list self._update_timer = QTimer() self._update_timer.setInterval(int(self.CHECK_CLUSTER_INTERVAL * 1000)) @@ -152,10 +158,9 @@ class CloudOutputDeviceManager: def start(self): if self._running: return - application = CuraApplication.getInstance() self._account.loginStateChanged.connect(self._onLoginStateChanged) # When switching machines we check if we have to activate a remote cluster. - application.globalContainerStackChanged.connect(self._connectToActiveMachine) + self._application.globalContainerStackChanged.connect(self._connectToActiveMachine) self._update_timer.timeout.connect(self._getRemoteClusters) self._onLoginStateChanged(is_logged_in = self._account.isLoggedIn) @@ -163,9 +168,39 @@ class CloudOutputDeviceManager: def stop(self): if not self._running: return - application = CuraApplication.getInstance() self._account.loginStateChanged.disconnect(self._onLoginStateChanged) # When switching machines we check if we have to activate a remote cluster. - application.globalContainerStackChanged.disconnect(self._connectToActiveMachine) + self._application.globalContainerStackChanged.disconnect(self._connectToActiveMachine) self._update_timer.timeout.disconnect(self._getRemoteClusters) self._onLoginStateChanged(is_logged_in = False) + + ## Check if the prerequsites are in place to start the cloud flow + def checkCloudFlowIsPossible(self): + Logger.log("d", "Checking if cloud connection is possible...") + + # Check #1: User is logged in with an Ultimaker account + if not self._account.isLoggedIn: + Logger.log("d", "Cloud Flow not possible: User not logged in!") + return + + # Check #2: Machine has a network connection + if not self._application.getMachineManager().activeMachineHasActiveNetworkConnection: + Logger.log("d", "Cloud Flow not possible: Machine is not connected!") + # TODO: This should only be network connections, not cloud connections + return + + # Check #3: Machine has correct firmware version + + # Logger.log("d", "Cloud Flow not possible: Machine does not have necessary firmware!") + # return + + # TODO: Check if machine is already set up to be cloud + + self.cloudFlowIsPossible.emit() + Logger.log("d", "Cloud flow is ready to go!") + + def _onCloudFlowPossible(self): + # Cloud flow is possible, so show the message + self._start_cloud_flow_message = Message(self.I18N_CATALOG.i18nc("@info:status", "Chain so thin when a breeze roll by, man it flow... man it flow...")) + self._start_cloud_flow_message.show() + return From 2a3a1d6e357a83a4f19f343d664b99ec5f30d5be Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Mon, 4 Feb 2019 11:51:27 +0100 Subject: [PATCH 02/21] Move cloud flow checks back to UM3OutputDevice Contributes to CL-1222 --- .../src/Cloud/CloudOutputDeviceManager.py | 36 --------------- .../src/ClusterUM3OutputDevice.py | 45 +++++++++++++++++++ 2 files changed, 45 insertions(+), 36 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index 505b7fb419..69147bc17f 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -32,8 +32,6 @@ class CloudOutputDeviceManager: # The translation catalog for this device. I18N_CATALOG = i18nCatalog("cura") - cloudFlowIsPossible = Signal() - def __init__(self) -> None: # Persistent dict containing the remote clusters for the authenticated user. self._remote_clusters = {} # type: Dict[str, CloudOutputDevice] @@ -44,9 +42,6 @@ class CloudOutputDeviceManager: self._account = self._application.getCuraAPI().account # type: Account self._api = CloudApiClient(self._account, self._onApiError) - self._account.loginStateChanged.connect(self.checkCloudFlowIsPossible) - self.cloudFlowIsPossible.connect(self._onCloudFlowPossible) - # Create a timer to update the remote cluster list self._update_timer = QTimer() self._update_timer.setInterval(int(self.CHECK_CLUSTER_INTERVAL * 1000)) @@ -173,34 +168,3 @@ class CloudOutputDeviceManager: self._application.globalContainerStackChanged.disconnect(self._connectToActiveMachine) self._update_timer.timeout.disconnect(self._getRemoteClusters) self._onLoginStateChanged(is_logged_in = False) - - ## Check if the prerequsites are in place to start the cloud flow - def checkCloudFlowIsPossible(self): - Logger.log("d", "Checking if cloud connection is possible...") - - # Check #1: User is logged in with an Ultimaker account - if not self._account.isLoggedIn: - Logger.log("d", "Cloud Flow not possible: User not logged in!") - return - - # Check #2: Machine has a network connection - if not self._application.getMachineManager().activeMachineHasActiveNetworkConnection: - Logger.log("d", "Cloud Flow not possible: Machine is not connected!") - # TODO: This should only be network connections, not cloud connections - return - - # Check #3: Machine has correct firmware version - - # Logger.log("d", "Cloud Flow not possible: Machine does not have necessary firmware!") - # return - - # TODO: Check if machine is already set up to be cloud - - self.cloudFlowIsPossible.emit() - Logger.log("d", "Cloud flow is ready to go!") - - def _onCloudFlowPossible(self): - # Cloud flow is possible, so show the message - self._start_cloud_flow_message = Message(self.I18N_CATALOG.i18nc("@info:status", "Chain so thin when a breeze roll by, man it flow... man it flow...")) - self._start_cloud_flow_message.show() - return diff --git a/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py b/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py index 052dd0b979..71d7b928c8 100644 --- a/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py @@ -45,6 +45,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): activePrinterChanged = pyqtSignal() activeCameraUrlChanged = pyqtSignal() receivedPrintJobsChanged = pyqtSignal() + cloudFlowIsPossible = pyqtSignal() # Notify can only use signals that are defined by the class that they are in, not inherited ones. # Therefore we create a private signal used to trigger the printersChanged signal. @@ -54,6 +55,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): super().__init__(device_id = device_id, address = address, properties=properties, connection_type = ConnectionType.NetworkConnection, parent = parent) self._api_prefix = "/cluster-api/v1/" + self._application = CuraApplication.getInstance() + self._number_of_extruders = 2 self._dummy_lambdas = ( @@ -65,9 +68,18 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../resources/qml/MonitorStage.qml") + self._account = self._application.getCuraAPI().account + # Trigger the printersChanged signal when the private signal is triggered self.printersChanged.connect(self._clusterPrintersChanged) + # Check if cloud flow is possible when user logs in + self._account.loginStateChanged.connect(self.checkCloudFlowIsPossible) + + # Listen for when Cloud Flow is possible + self.cloudFlowIsPossible.connect(self._onCloudFlowPossible) + + self._accepts_commands = True # type: bool # Cluster does not have authentication, so default to authenticated @@ -665,6 +677,39 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): job = SendMaterialJob(device = self) job.run() + ## Check if the prerequsites are in place to start the cloud flow + def checkCloudFlowIsPossible(self): + Logger.log("d", "Checking if cloud connection is possible...") + + # Check #1: User is logged in with an Ultimaker account + if not self._account.isLoggedIn: + Logger.log("d", "Cloud Flow not possible: User not logged in!") + return + + # Check #2: Machine has a network connection + if not self._application.getMachineManager().activeMachineHasActiveNetworkConnection: + Logger.log("d", "Cloud Flow not possible: Machine is not connected!") + # TODO: This should only be network connections, not cloud connections + return + + # Check #3: Machine has correct firmware version + + + + # Logger.log("d", "Cloud Flow not possible: Machine does not have necessary firmware!") + # return + + # TODO: Check if machine is already set up to be cloud + + self.cloudFlowIsPossible.emit() + Logger.log("d", "Cloud flow is ready to go!") + + def _onCloudFlowPossible(self): + # Cloud flow is possible, so show the message + self._start_cloud_flow_message = Message(i18n_catalog.i18nc("@info:status", "Chain so thin when a breeze roll by, man it flow... man it flow...")) + self._start_cloud_flow_message.show() + return + def loadJsonFromReply(reply: QNetworkReply) -> Optional[List[Dict[str, Any]]]: try: From f2ddb2808fdc76c53a286b89a7d8da525b532526 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Mon, 4 Feb 2019 16:02:38 +0100 Subject: [PATCH 03/21] Add firmware check Contributes to CL-1222 --- cura/Settings/MachineManager.py | 6 ++ .../src/ClusterUM3OutputDevice.py | 44 --------------- .../src/UM3OutputDevicePlugin.py | 55 ++++++++++++++++++- 3 files changed, 59 insertions(+), 46 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index fbeb2f71e4..b3f73bf7b3 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -517,6 +517,12 @@ class MachineManager(QObject): return self._global_container_stack.getId() return "" + @pyqtProperty(str, notify = globalContainerChanged) + def activeMachineFirmwareVersion(self) -> str: + if not self._printer_output_devices[0]: + return "" + return self._printer_output_devices[0].firmwareVersion + @pyqtProperty(bool, notify = printerConnectedStatusChanged) def printerConnected(self) -> bool: return bool(self._printer_output_devices) diff --git a/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py b/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py index 71d7b928c8..9f79041863 100644 --- a/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py @@ -45,7 +45,6 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): activePrinterChanged = pyqtSignal() activeCameraUrlChanged = pyqtSignal() receivedPrintJobsChanged = pyqtSignal() - cloudFlowIsPossible = pyqtSignal() # Notify can only use signals that are defined by the class that they are in, not inherited ones. # Therefore we create a private signal used to trigger the printersChanged signal. @@ -68,18 +67,9 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../resources/qml/MonitorStage.qml") - self._account = self._application.getCuraAPI().account - # Trigger the printersChanged signal when the private signal is triggered self.printersChanged.connect(self._clusterPrintersChanged) - # Check if cloud flow is possible when user logs in - self._account.loginStateChanged.connect(self.checkCloudFlowIsPossible) - - # Listen for when Cloud Flow is possible - self.cloudFlowIsPossible.connect(self._onCloudFlowPossible) - - self._accepts_commands = True # type: bool # Cluster does not have authentication, so default to authenticated @@ -677,40 +667,6 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): job = SendMaterialJob(device = self) job.run() - ## Check if the prerequsites are in place to start the cloud flow - def checkCloudFlowIsPossible(self): - Logger.log("d", "Checking if cloud connection is possible...") - - # Check #1: User is logged in with an Ultimaker account - if not self._account.isLoggedIn: - Logger.log("d", "Cloud Flow not possible: User not logged in!") - return - - # Check #2: Machine has a network connection - if not self._application.getMachineManager().activeMachineHasActiveNetworkConnection: - Logger.log("d", "Cloud Flow not possible: Machine is not connected!") - # TODO: This should only be network connections, not cloud connections - return - - # Check #3: Machine has correct firmware version - - - - # Logger.log("d", "Cloud Flow not possible: Machine does not have necessary firmware!") - # return - - # TODO: Check if machine is already set up to be cloud - - self.cloudFlowIsPossible.emit() - Logger.log("d", "Cloud flow is ready to go!") - - def _onCloudFlowPossible(self): - # Cloud flow is possible, so show the message - self._start_cloud_flow_message = Message(i18n_catalog.i18nc("@info:status", "Chain so thin when a breeze roll by, man it flow... man it flow...")) - self._start_cloud_flow_message.show() - return - - def loadJsonFromReply(reply: QNetworkReply) -> Optional[List[Dict[str, Any]]]: try: result = json.loads(bytes(reply.readAll()).decode("utf-8")) diff --git a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py index 74d2d87b3b..40c6ee3e21 100644 --- a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py +++ b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py @@ -7,17 +7,20 @@ from time import time from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange, ServiceInfo from PyQt5.QtNetwork import QNetworkRequest, QNetworkAccessManager -from PyQt5.QtCore import QUrl +from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal, pyqtProperty, QObject from cura.CuraApplication import CuraApplication from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin from UM.Logger import Logger from UM.Signal import Signal, signalemitter from UM.Version import Version +from UM.Message import Message +from UM.i18n import i18nCatalog from . import ClusterUM3OutputDevice, LegacyUM3OutputDevice from .Cloud.CloudOutputDeviceManager import CloudOutputDeviceManager +i18n_catalog = i18nCatalog("cura") ## This plugin handles the connection detection & creation of output device objects for the UM3 printer. # Zero-Conf is used to detect printers, which are saved in a dict. @@ -27,6 +30,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): addDeviceSignal = Signal() removeDeviceSignal = Signal() discoveredDevicesChanged = Signal() + cloudFlowIsPossible = Signal() def __init__(self): super().__init__() @@ -34,6 +38,8 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self._zero_conf = None self._zero_conf_browser = None + self._application = CuraApplication.getInstance() + # Create a cloud output device manager that abstracts all cloud connection logic away. self._cloud_output_device_manager = CloudOutputDeviceManager() @@ -41,7 +47,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self.addDeviceSignal.connect(self._onAddDevice) self.removeDeviceSignal.connect(self._onRemoveDevice) - CuraApplication.getInstance().globalContainerStackChanged.connect(self.reCheckConnections) + self._application.globalContainerStackChanged.connect(self.reCheckConnections) self._discovered_devices = {} @@ -49,6 +55,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self._network_manager.finished.connect(self._onNetworkRequestFinished) self._min_cluster_version = Version("4.0.0") + self._min_cloud_version = Version("5.1.5") self._api_version = "1" self._api_prefix = "/api/v" + self._api_version + "/" @@ -74,6 +81,14 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self._service_changed_request_thread = Thread(target=self._handleOnServiceChangedRequests, daemon=True) self._service_changed_request_thread.start() + self._account = self._application.getCuraAPI().account + + # Check if cloud flow is possible when user logs in + self._account.loginStateChanged.connect(self.checkCloudFlowIsPossible) + + # Listen for when Cloud Flow is possible + self.cloudFlowIsPossible.connect(self._onCloudFlowPossible) + def getDiscoveredDevices(self): return self._discovered_devices @@ -292,6 +307,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): if global_container_stack and device.getId() == global_container_stack.getMetaDataEntry("um_network_key"): # Ensure that the configured connection type is set. global_container_stack.addConfiguredConnectionType(device.connectionType.value) + # global_container_stack.setFirmwareVersion(device.firmwareVersion) device.connect() device.connectionStateChanged.connect(self._onDeviceConnectionStateChanged) @@ -370,3 +386,38 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self.removeDeviceSignal.emit(str(name)) return True + + ## Check if the prerequsites are in place to start the cloud flow + def checkCloudFlowIsPossible(self): + Logger.log("d", "Checking if cloud connection is possible...") + + # TODO: Skip if already using cloud connection + + # Check #1: User is logged in with an Ultimaker account + if not self._account.isLoggedIn: + Logger.log("d", "Cloud Flow not possible: User not logged in!") + return + + # Check #2: Machine has a network connection + if not self._application.getMachineManager().activeMachineHasActiveNetworkConnection: + Logger.log("d", "Cloud Flow not possible: Machine is not connected!") + # TODO: This should only be network connections, not cloud connections + return + + # Check #3: Machine has correct firmware version + firmware_version = self._application.getMachineManager().activeMachineFirmwareVersion + if not Version(firmware_version) > self._min_cloud_version: + Logger.log("d", + "Cloud Flow not possible: Machine firmware (%s) is too low! (Requires version %s)", + firmware_version, + self._min_cloud_version) + return + + self.cloudFlowIsPossible.emit() + Logger.log("d", "Cloud flow is ready to go!") + + def _onCloudFlowPossible(self): + # Cloud flow is possible, so show the message + self._start_cloud_flow_message = Message(i18n_catalog.i18nc("@info:status", "Chain so thin when a breeze roll by, man it flow... man it flow...")) + self._start_cloud_flow_message.show() + return From 4c1131a7b4d4a1a4737d3d16d734744ea2e904f8 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Mon, 4 Feb 2019 16:11:48 +0100 Subject: [PATCH 04/21] Remove some redundant calls Contributes to CL-1222 --- .../UM3NetworkPrinting/src/ClusterUM3OutputDevice.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py b/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py index 9f79041863..e490011456 100644 --- a/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py @@ -127,7 +127,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): def _spawnPrinterSelectionDialog(self): if self._printer_selection_dialog is None: path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../resources/qml/PrintWindow.qml") - self._printer_selection_dialog = CuraApplication.getInstance().createQmlComponent(path, {"OutputDevice": self}) + self._printer_selection_dialog = self._application.createQmlComponent(path, {"OutputDevice": self}) if self._printer_selection_dialog is not None: self._printer_selection_dialog.show() @@ -213,7 +213,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): # Add user name to the print_job parts.append(self._createFormPart("name=owner", bytes(self._getUserName(), "utf-8"), "text/plain")) - file_name = CuraApplication.getInstance().getPrintInformation().jobName + "." + preferred_format["extension"] + file_name = self._application.getPrintInformation().jobName + "." + preferred_format["extension"] output = stream.getvalue() # Either str or bytes depending on the output mode. if isinstance(stream, io.StringIO): @@ -286,7 +286,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): self._progress_message.hide() self._compressing_gcode = False self._sending_gcode = False - CuraApplication.getInstance().getController().setActiveStage("PrepareStage") + self._application.getController().setActiveStage("PrepareStage") # After compressing the sliced model Cura sends data to printer, to stop receiving updates from the request # the "reply" should be disconnected @@ -296,7 +296,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): def _successMessageActionTriggered(self, message_id: Optional[str] = None, action_id: Optional[str] = None) -> None: if action_id == "View": - CuraApplication.getInstance().getController().setActiveStage("MonitorStage") + self._application.getController().setActiveStage("MonitorStage") @pyqtSlot() def openPrintJobControlPanel(self) -> None: @@ -554,7 +554,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): return result def _createMaterialOutputModel(self, material_data: Dict[str, Any]) -> "MaterialOutputModel": - material_manager = CuraApplication.getInstance().getMaterialManager() + material_manager = self._application.getMaterialManager() material_group_list = None # Avoid crashing if there is no "guid" field in the metadata From 9c82cfd6faee3917cc74c0e0c57956d129db149d Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Mon, 4 Feb 2019 16:52:01 +0100 Subject: [PATCH 05/21] Comment out firmware check for dev purposes Contributes to CL-1222 --- .../src/UM3OutputDevicePlugin.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py index 40c6ee3e21..71b27eaab6 100644 --- a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py +++ b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py @@ -401,20 +401,19 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): # Check #2: Machine has a network connection if not self._application.getMachineManager().activeMachineHasActiveNetworkConnection: Logger.log("d", "Cloud Flow not possible: Machine is not connected!") - # TODO: This should only be network connections, not cloud connections return # Check #3: Machine has correct firmware version - firmware_version = self._application.getMachineManager().activeMachineFirmwareVersion - if not Version(firmware_version) > self._min_cloud_version: - Logger.log("d", - "Cloud Flow not possible: Machine firmware (%s) is too low! (Requires version %s)", - firmware_version, - self._min_cloud_version) - return + # firmware_version = self._application.getMachineManager().activeMachineFirmwareVersion + # if not Version(firmware_version) > self._min_cloud_version: + # Logger.log("d", "Cloud Flow not possible: Machine firmware (%s) is too low! (Requires version %s)", + # firmware_version, + # self._min_cloud_version) + # return + # TODO: Un-comment out, only by-passed for development purposes - self.cloudFlowIsPossible.emit() Logger.log("d", "Cloud flow is ready to go!") + self.cloudFlowIsPossible.emit() def _onCloudFlowPossible(self): # Cloud flow is possible, so show the message From d022672bcf2b153e79a5f693d83ff95ee6d8d29b Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Tue, 5 Feb 2019 16:49:54 +0100 Subject: [PATCH 06/21] Add signal to CloudOutputDeviceManager for added cloud device Contributes to CL-1222 --- .../UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index 69147bc17f..8c9600cfb7 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -32,6 +32,8 @@ class CloudOutputDeviceManager: # The translation catalog for this device. I18N_CATALOG = i18nCatalog("cura") + addedCloudCluster = Signal() + def __init__(self) -> None: # Persistent dict containing the remote clusters for the authenticated user. self._remote_clusters = {} # type: Dict[str, CloudOutputDevice] @@ -90,6 +92,7 @@ class CloudOutputDeviceManager: for added_cluster in added_clusters: device = CloudOutputDevice(self._api, added_cluster) self._remote_clusters[added_cluster.cluster_id] = device + self.addedCloudCluster.emit() for device, cluster in updates: device.clusterData = cluster From c41e81a6b9f5ccdbf59afd8dc30c940207334589 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Tue, 5 Feb 2019 16:50:36 +0100 Subject: [PATCH 07/21] Set required firmware version for cloud to 5.2.0 Contributes to CL-1222 --- plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py index 71b27eaab6..9b30933ec9 100644 --- a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py +++ b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py @@ -55,7 +55,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self._network_manager.finished.connect(self._onNetworkRequestFinished) self._min_cluster_version = Version("4.0.0") - self._min_cloud_version = Version("5.1.5") + self._min_cloud_version = Version("5.2.0") self._api_version = "1" self._api_prefix = "/api/v" + self._api_version + "/" From b29f3d3bd0cd6c8a8d2d5f2d18b21fe73176ff85 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Tue, 5 Feb 2019 16:50:50 +0100 Subject: [PATCH 08/21] Improve comment Contributes to CL-1222 --- plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py index 9b30933ec9..601d5ae955 100644 --- a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py +++ b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py @@ -86,7 +86,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): # Check if cloud flow is possible when user logs in self._account.loginStateChanged.connect(self.checkCloudFlowIsPossible) - # Listen for when Cloud Flow is possible + # Listen for when cloud flow is possible self.cloudFlowIsPossible.connect(self._onCloudFlowPossible) def getDiscoveredDevices(self): From 70c384bd4e50c7c99c2e896e9a475128cfe931d3 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Tue, 5 Feb 2019 16:51:47 +0100 Subject: [PATCH 09/21] Finish adding cloud flow checks Contributes to CL-1222 --- .../src/UM3OutputDevicePlugin.py | 53 +++++++++++-------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py index 601d5ae955..1caf1d3132 100644 --- a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py +++ b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py @@ -389,31 +389,40 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): ## Check if the prerequsites are in place to start the cloud flow def checkCloudFlowIsPossible(self): - Logger.log("d", "Checking if cloud connection is possible...") + active_machine = self._application.getMachineManager().activeMachine - # TODO: Skip if already using cloud connection + if active_machine: + # Skip if already complete + # if active_machine.getMetaDataEntry("cloud_flow_complete", "value") is not None: + # return - # Check #1: User is logged in with an Ultimaker account - if not self._account.isLoggedIn: - Logger.log("d", "Cloud Flow not possible: User not logged in!") - return + # # Skip if user said don't remind me + # if active_machine.getMetaDataEntry("show_cloud_message", "value") is False: + # return - # Check #2: Machine has a network connection - if not self._application.getMachineManager().activeMachineHasActiveNetworkConnection: - Logger.log("d", "Cloud Flow not possible: Machine is not connected!") - return - - # Check #3: Machine has correct firmware version - # firmware_version = self._application.getMachineManager().activeMachineFirmwareVersion - # if not Version(firmware_version) > self._min_cloud_version: - # Logger.log("d", "Cloud Flow not possible: Machine firmware (%s) is too low! (Requires version %s)", - # firmware_version, - # self._min_cloud_version) - # return - # TODO: Un-comment out, only by-passed for development purposes - - Logger.log("d", "Cloud flow is ready to go!") - self.cloudFlowIsPossible.emit() + Logger.log("d", "Checking if cloud connection is possible...") + + # Check #1: User is logged in with an Ultimaker account + if not self._account.isLoggedIn: + Logger.log("d", "Cloud Flow not possible: User not logged in!") + return + + # Check #2: Machine has a network connection + if not self._application.getMachineManager().activeMachineHasActiveNetworkConnection: + Logger.log("d", "Cloud Flow not possible: Machine is not connected!") + return + + # Check #3: Machine has correct firmware version + # firmware_version = self._application.getMachineManager().activeMachineFirmwareVersion + # if not Version(firmware_version) > self._min_cloud_version: + # Logger.log("d", "Cloud Flow not possible: Machine firmware (%s) is too low! (Requires version %s)", + # firmware_version, + # self._min_cloud_version) + # return + # TODO: Un-comment out, only by-passed for development purposes + + Logger.log("d", "Cloud flow is possible!") + self.cloudFlowIsPossible.emit() def _onCloudFlowPossible(self): # Cloud flow is possible, so show the message From 5c0c17673092782feb9bc530b64634f51d8351b8 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Tue, 5 Feb 2019 16:52:15 +0100 Subject: [PATCH 10/21] Show confirmation pop-up for cloud flow Contributes to CL-1222 --- .../UM3NetworkPrinting/src/UM3OutputDevicePlugin.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py index 1caf1d3132..884d04add9 100644 --- a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py +++ b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py @@ -43,6 +43,8 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): # Create a cloud output device manager that abstracts all cloud connection logic away. self._cloud_output_device_manager = CloudOutputDeviceManager() + self._cloud_output_device_manager.addedCloudCluster.connect(self._onCloudPrintingConfigured) + # Because the model needs to be created in the same thread as the QMLEngine, we use a signal. self.addDeviceSignal.connect(self._onAddDevice) self.removeDeviceSignal.connect(self._onRemoveDevice) @@ -429,3 +431,12 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self._start_cloud_flow_message = Message(i18n_catalog.i18nc("@info:status", "Chain so thin when a breeze roll by, man it flow... man it flow...")) self._start_cloud_flow_message.show() return + + def _onCloudPrintingConfigured(self): + # Show the successful pop-up + self._cloud_flow_complete_message = Message(i18n_catalog.i18nc("@info:status", "A winner is me!")) + self._cloud_flow_complete_message.show() + active_machine = self._application.getMachineManager().activeMachine + if active_machine: + active_machine.setMetaDataEntry("cloud_flow_complete", True) + return From 348689eace671895eb049e72ccdc0fbb32e16ca7 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Wed, 6 Feb 2019 11:58:51 +0100 Subject: [PATCH 11/21] Add cloud flow graphics Contributes to CL-1222 --- .../resources/svg/cloud-flow-completed.svg | 27 +++++++++++++++++++ .../resources/svg/cloud-flow-start.svg | 13 +++++++++ 2 files changed, 40 insertions(+) create mode 100644 plugins/UM3NetworkPrinting/resources/svg/cloud-flow-completed.svg create mode 100644 plugins/UM3NetworkPrinting/resources/svg/cloud-flow-start.svg diff --git a/plugins/UM3NetworkPrinting/resources/svg/cloud-flow-completed.svg b/plugins/UM3NetworkPrinting/resources/svg/cloud-flow-completed.svg new file mode 100644 index 0000000000..8eba62ecc8 --- /dev/null +++ b/plugins/UM3NetworkPrinting/resources/svg/cloud-flow-completed.svg @@ -0,0 +1,27 @@ + + + + Group 2 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/UM3NetworkPrinting/resources/svg/cloud-flow-start.svg b/plugins/UM3NetworkPrinting/resources/svg/cloud-flow-start.svg new file mode 100644 index 0000000000..746dc269fd --- /dev/null +++ b/plugins/UM3NetworkPrinting/resources/svg/cloud-flow-start.svg @@ -0,0 +1,13 @@ + + + + Cloud_connection-icon + Created with Sketch. + + + + + + + + \ No newline at end of file From 01ca3546aab21ec679daf36b9819fc3f65b7a054 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Wed, 6 Feb 2019 11:59:39 +0100 Subject: [PATCH 12/21] Add activeMachineAddress to MachineManager for easy access Contributes to CL-1222 --- cura/Settings/MachineManager.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index b3f73bf7b3..d436657d08 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -523,6 +523,12 @@ class MachineManager(QObject): return "" return self._printer_output_devices[0].firmwareVersion + @pyqtProperty(str, notify = globalContainerChanged) + def activeMachineAddress(self) -> str: + if not self._printer_output_devices[0]: + return "" + return self._printer_output_devices[0].address + @pyqtProperty(bool, notify = printerConnectedStatusChanged) def printerConnected(self) -> bool: return bool(self._printer_output_devices) From b1104124f38f7f84ed29ed137d1d8867833ab6e8 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Wed, 6 Feb 2019 12:01:42 +0100 Subject: [PATCH 13/21] Add address property to ClusterUM3OutputDevice This was necessary because it was the only way to get the address (NetworkPrinterDevice and PrinterOutputDevice returned empty strings) Contributes to CL-1222 --- plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py b/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py index e490011456..790d0c430b 100644 --- a/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py @@ -252,6 +252,11 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): self._compressing_gcode = False self._sending_gcode = False + ## The IP address of the printer. + @pyqtProperty(str, constant = True) + def address(self) -> str: + return self._address + def _onUploadPrintJobProgress(self, bytes_sent: int, bytes_total: int) -> None: if bytes_total > 0: new_progress = bytes_sent / bytes_total * 100 From ae7936a82307769a54c1bae85302dd6ab5e52354 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Wed, 6 Feb 2019 12:01:52 +0100 Subject: [PATCH 14/21] Update theme.json Contributes to CL-1222 --- resources/themes/cura-light/theme.json | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index 4a984a9252..33a8dd12cf 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -562,6 +562,7 @@ "message_close": [1, 1], "message_radius": [0.25, 0.25], "message_action_button": [0, 2.0], + "message_image": [15.0, 5.0], "infill_button_margin": [0.5, 0.5], From 3a4fcb354a955b2d3ede29ffd1ad4816e7ec3cc5 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Wed, 6 Feb 2019 12:04:15 +0100 Subject: [PATCH 15/21] Show cloud connection flow pop-up Contributes to CL-1222 --- .../src/UM3OutputDevicePlugin.py | 81 +++++++++++++++---- 1 file changed, 66 insertions(+), 15 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py index 884d04add9..c0af8e92d7 100644 --- a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py +++ b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py @@ -8,6 +8,7 @@ from time import time from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange, ServiceInfo from PyQt5.QtNetwork import QNetworkRequest, QNetworkAccessManager from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal, pyqtProperty, QObject +from PyQt5.QtGui import QDesktopServices from cura.CuraApplication import CuraApplication from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin @@ -309,7 +310,6 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): if global_container_stack and device.getId() == global_container_stack.getMetaDataEntry("um_network_key"): # Ensure that the configured connection type is set. global_container_stack.addConfiguredConnectionType(device.connectionType.value) - # global_container_stack.setFirmwareVersion(device.firmwareVersion) device.connect() device.connectionStateChanged.connect(self._onDeviceConnectionStateChanged) @@ -395,12 +395,12 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): if active_machine: # Skip if already complete - # if active_machine.getMetaDataEntry("cloud_flow_complete", "value") is not None: - # return + if active_machine.getMetaDataEntry("cloud_flow_complete", "value") is not None: + return - # # Skip if user said don't remind me - # if active_machine.getMetaDataEntry("show_cloud_message", "value") is False: - # return + # Skip if user said don't remind me + if active_machine.getMetaDataEntry("show_cloud_message", "value") is False: + return Logger.log("d", "Checking if cloud connection is possible...") @@ -415,28 +415,79 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): return # Check #3: Machine has correct firmware version - # firmware_version = self._application.getMachineManager().activeMachineFirmwareVersion - # if not Version(firmware_version) > self._min_cloud_version: - # Logger.log("d", "Cloud Flow not possible: Machine firmware (%s) is too low! (Requires version %s)", - # firmware_version, - # self._min_cloud_version) - # return - # TODO: Un-comment out, only by-passed for development purposes + firmware_version = self._application.getMachineManager().activeMachineFirmwareVersion + if not Version(firmware_version) > self._min_cloud_version: + Logger.log("d", "Cloud Flow not possible: Machine firmware (%s) is too low! (Requires version %s)", + firmware_version, + self._min_cloud_version) + return Logger.log("d", "Cloud flow is possible!") self.cloudFlowIsPossible.emit() def _onCloudFlowPossible(self): # Cloud flow is possible, so show the message - self._start_cloud_flow_message = Message(i18n_catalog.i18nc("@info:status", "Chain so thin when a breeze roll by, man it flow... man it flow...")) + self._start_cloud_flow_message = Message( + i18n_catalog.i18nc("@info:status", "Pair your printer to your Ultimaker account and start print jobs from anywhere."), + 0, # Lifetime + True, # Dismissable? + None, # Progress + "", # Title + None, # Parent + True, # Use inactivity timer + "../../../../../Cura/plugins/UM3NetworkPrinting/resources/svg/cloud-flow-start.svg", # Image souce + i18n_catalog.i18nc("@info:status", "Connect to cloud"), # image caption + i18n_catalog.i18nc("@action", "Don't ask me again for this printer."), # Toggle text + False # Toggle default state + ) + self._start_cloud_flow_message.addAction("", i18n_catalog.i18nc("@action", "Get started"), "", "") + self._start_cloud_flow_message.optionToggled.connect(self._onDontAskMeAgain) + self._start_cloud_flow_message.actionTriggered.connect(self._onCloudFlowStarted) self._start_cloud_flow_message.show() return def _onCloudPrintingConfigured(self): + if self._start_cloud_flow_message: + self._start_cloud_flow_message.hide() # Show the successful pop-up - self._cloud_flow_complete_message = Message(i18n_catalog.i18nc("@info:status", "A winner is me!")) + self._cloud_flow_complete_message = Message( + i18n_catalog.i18nc("@info:status", "You can now send and monitor print jobs from anywhere using your Ultimaker account."), + 30, # Lifetime + True, # Dismissable? + None, # Progress + "", # Title + None, # Parent + True, # Use inactivity timer + "../../../../../Cura/plugins/UM3NetworkPrinting/resources/svg/cloud-flow-completed.svg", # Image souce + i18n_catalog.i18nc("@info:status", "Connected!") # image caption + ) + self._cloud_flow_complete_message.addAction("", i18n_catalog.i18nc("@action", "Review your connection"), "", "", 1) # TODO: Icon + self._start_cloud_flow_message.actionTriggered.connect(self._onReviewCloudConnection) self._cloud_flow_complete_message.show() + + # Set the machine's cloud flow as complete so we don't ask the user again and again for cloud connected printers active_machine = self._application.getMachineManager().activeMachine if active_machine: active_machine.setMetaDataEntry("cloud_flow_complete", True) return + + def _onDontAskMeAgain(self, message): + active_machine = self._application.getMachineManager().activeMachine + if active_machine: + active_machine.setMetaDataEntry("show_cloud_message", False) + Logger.log("d", "Will not ask the user again to cloud connect for current printer.") + return + + def _onCloudFlowStarted(self, message, action): + address = self._application.getMachineManager().activeMachineAddress + if address: + QDesktopServices.openUrl(QUrl("http://" + address + "/cloud_connect")) + if self._start_cloud_flow_message: + self._start_cloud_flow_message.hide() + return + + def _onReviewCloudConnection(self, message, action): + address = self._application.getMachineManager().activeMachineAddress + if address: + QDesktopServices.openUrl(QUrl("http://" + address + "/settings")) + return From 162fdad4e4f60bdb488045afc27c8bff610ac995 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Wed, 6 Feb 2019 13:58:33 +0100 Subject: [PATCH 16/21] Improve conditions for starting cloud flow Contributes to CL-1222 --- .../src/UM3OutputDevicePlugin.py | 134 +++++++++++------- 1 file changed, 80 insertions(+), 54 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py index c0af8e92d7..0a0315b26c 100644 --- a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py +++ b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py @@ -11,6 +11,7 @@ from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal, pyqtProperty, QObject from PyQt5.QtGui import QDesktopServices from cura.CuraApplication import CuraApplication +from cura.PrinterOutputDevice import ConnectionType from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin from UM.Logger import Logger from UM.Signal import Signal, signalemitter @@ -89,9 +90,15 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): # Check if cloud flow is possible when user logs in self._account.loginStateChanged.connect(self.checkCloudFlowIsPossible) + # Check if cloud flow is possible when user switches machines + self._application.globalContainerStackChanged.connect(self._onMachineSwitched) + # Listen for when cloud flow is possible self.cloudFlowIsPossible.connect(self._onCloudFlowPossible) + self._start_cloud_flow_message = None # type: Optional[Message] + self._cloud_flow_complete_message = None # type: Optional[Message] + def getDiscoveredDevices(self): return self._discovered_devices @@ -156,6 +163,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): um_network_key = CuraApplication.getInstance().getGlobalContainerStack().getMetaDataEntry("um_network_key") if key == um_network_key: self.getOutputDeviceManager().addOutputDevice(self._discovered_devices[key]) + self.checkCloudFlowIsPossible() else: self.getOutputDeviceManager().removeOutputDevice(key) @@ -391,85 +399,92 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): ## Check if the prerequsites are in place to start the cloud flow def checkCloudFlowIsPossible(self): + Logger.log("d", "Checking if cloud connection is possible...") + + # Pre-Check: Skip if active machine already has been cloud connected or you said don't ask again active_machine = self._application.getMachineManager().activeMachine - if active_machine: - # Skip if already complete - if active_machine.getMetaDataEntry("cloud_flow_complete", "value") is not None: + + # Check 1: Printer isn't already configured for cloud + if ConnectionType.CloudConnection.value in active_machine.configuredConnectionTypes: + Logger.log("d", "Active machine was already configured for cloud.") return - # Skip if user said don't remind me + # Check 2: User did not already say "Don't ask me again" if active_machine.getMetaDataEntry("show_cloud_message", "value") is False: + Logger.log("d", "Active machine shouldn't ask about cloud anymore.") return - - Logger.log("d", "Checking if cloud connection is possible...") - - # Check #1: User is logged in with an Ultimaker account + + # Check 3: User is logged in with an Ultimaker account if not self._account.isLoggedIn: Logger.log("d", "Cloud Flow not possible: User not logged in!") return - # Check #2: Machine has a network connection + # Check 4: Machine is configured for network connectivity if not self._application.getMachineManager().activeMachineHasActiveNetworkConnection: Logger.log("d", "Cloud Flow not possible: Machine is not connected!") return - # Check #3: Machine has correct firmware version - firmware_version = self._application.getMachineManager().activeMachineFirmwareVersion - if not Version(firmware_version) > self._min_cloud_version: - Logger.log("d", "Cloud Flow not possible: Machine firmware (%s) is too low! (Requires version %s)", - firmware_version, - self._min_cloud_version) - return + # Check 5: Machine has correct firmware version + # firmware_version = self._application.getMachineManager().activeMachineFirmwareVersion + # if not Version(firmware_version) > self._min_cloud_version: + # Logger.log("d", "Cloud Flow not possible: Machine firmware (%s) is too low! (Requires version %s)", + # firmware_version, + # self._min_cloud_version) + # return Logger.log("d", "Cloud flow is possible!") self.cloudFlowIsPossible.emit() def _onCloudFlowPossible(self): # Cloud flow is possible, so show the message - self._start_cloud_flow_message = Message( - i18n_catalog.i18nc("@info:status", "Pair your printer to your Ultimaker account and start print jobs from anywhere."), - 0, # Lifetime - True, # Dismissable? - None, # Progress - "", # Title - None, # Parent - True, # Use inactivity timer - "../../../../../Cura/plugins/UM3NetworkPrinting/resources/svg/cloud-flow-start.svg", # Image souce - i18n_catalog.i18nc("@info:status", "Connect to cloud"), # image caption - i18n_catalog.i18nc("@action", "Don't ask me again for this printer."), # Toggle text - False # Toggle default state - ) - self._start_cloud_flow_message.addAction("", i18n_catalog.i18nc("@action", "Get started"), "", "") - self._start_cloud_flow_message.optionToggled.connect(self._onDontAskMeAgain) - self._start_cloud_flow_message.actionTriggered.connect(self._onCloudFlowStarted) - self._start_cloud_flow_message.show() - return + if not self._start_cloud_flow_message: + self._start_cloud_flow_message = Message( + i18n_catalog.i18nc("@info:status", "Pair your printer to your Ultimaker account and start print jobs from anywhere."), + 0, # Lifetime + True, # Dismissable? + None, # Progress + "", # Title + None, # Parent + True, # Use inactivity timer + "../../../../../Cura/plugins/UM3NetworkPrinting/resources/svg/cloud-flow-start.svg", # Image souce + i18n_catalog.i18nc("@info:status", "Connect to cloud"), # image caption + i18n_catalog.i18nc("@action", "Don't ask me again for this printer."), # Toggle text + False # Toggle default state + ) + self._start_cloud_flow_message.addAction("", i18n_catalog.i18nc("@action", "Get started"), "", "") + self._start_cloud_flow_message.optionToggled.connect(self._onDontAskMeAgain) + self._start_cloud_flow_message.actionTriggered.connect(self._onCloudFlowStarted) + self._start_cloud_flow_message.show() + return def _onCloudPrintingConfigured(self): if self._start_cloud_flow_message: - self._start_cloud_flow_message.hide() + self._start_cloud_flow_message.hide() + self._start_cloud_flow_message = None + # Show the successful pop-up - self._cloud_flow_complete_message = Message( - i18n_catalog.i18nc("@info:status", "You can now send and monitor print jobs from anywhere using your Ultimaker account."), - 30, # Lifetime - True, # Dismissable? - None, # Progress - "", # Title - None, # Parent - True, # Use inactivity timer - "../../../../../Cura/plugins/UM3NetworkPrinting/resources/svg/cloud-flow-completed.svg", # Image souce - i18n_catalog.i18nc("@info:status", "Connected!") # image caption - ) - self._cloud_flow_complete_message.addAction("", i18n_catalog.i18nc("@action", "Review your connection"), "", "", 1) # TODO: Icon - self._start_cloud_flow_message.actionTriggered.connect(self._onReviewCloudConnection) - self._cloud_flow_complete_message.show() + if not self._start_cloud_flow_message: + self._cloud_flow_complete_message = Message( + i18n_catalog.i18nc("@info:status", "You can now send and monitor print jobs from anywhere using your Ultimaker account."), + 30, # Lifetime + True, # Dismissable? + None, # Progress + "", # Title + None, # Parent + True, # Use inactivity timer + "../../../../../Cura/plugins/UM3NetworkPrinting/resources/svg/cloud-flow-completed.svg", # Image souce + i18n_catalog.i18nc("@info:status", "Connected!") # image caption + ) + self._cloud_flow_complete_message.addAction("", i18n_catalog.i18nc("@action", "Review your connection"), "", "", 1) # TODO: Icon + self._cloud_flow_complete_message.actionTriggered.connect(self._onReviewCloudConnection) + self._cloud_flow_complete_message.show() - # Set the machine's cloud flow as complete so we don't ask the user again and again for cloud connected printers - active_machine = self._application.getMachineManager().activeMachine - if active_machine: - active_machine.setMetaDataEntry("cloud_flow_complete", True) - return + # Set the machine's cloud flow as complete so we don't ask the user again and again for cloud connected printers + active_machine = self._application.getMachineManager().activeMachine + if active_machine: + active_machine.setMetaDataEntry("cloud_flow_complete", True) + return def _onDontAskMeAgain(self, message): active_machine = self._application.getMachineManager().activeMachine @@ -484,6 +499,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): QDesktopServices.openUrl(QUrl("http://" + address + "/cloud_connect")) if self._start_cloud_flow_message: self._start_cloud_flow_message.hide() + self._start_cloud_flow_message = None return def _onReviewCloudConnection(self, message, action): @@ -491,3 +507,13 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): if address: QDesktopServices.openUrl(QUrl("http://" + address + "/settings")) return + + def _onMachineSwitched(self): + if self._start_cloud_flow_message is not None: + self._start_cloud_flow_message.hide() + self._start_cloud_flow_message = None + if self._cloud_flow_complete_message is not None: + self._cloud_flow_complete_message.hide() + self._cloud_flow_complete_message = None + + self.checkCloudFlowIsPossible() \ No newline at end of file From fcb52dd1cf69924387ec06abc7aa34588cb712c2 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Wed, 6 Feb 2019 14:26:11 +0100 Subject: [PATCH 17/21] Show cloud flow pop-up if machine is removed from cloud Contributes to CL-1222 --- .../src/Cloud/CloudOutputDeviceManager.py | 2 ++ plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index 8c9600cfb7..e081beb99c 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -33,6 +33,7 @@ class CloudOutputDeviceManager: I18N_CATALOG = i18nCatalog("cura") addedCloudCluster = Signal() + removedCloudCluster = Signal() def __init__(self) -> None: # Persistent dict containing the remote clusters for the authenticated user. @@ -85,6 +86,7 @@ class CloudOutputDeviceManager: removed_cluster.disconnect() removed_cluster.close() self._output_device_manager.removeOutputDevice(removed_cluster.key) + self.removedCloudCluster.emit() del self._remote_clusters[removed_cluster.key] # Add an output device for each new remote cluster. diff --git a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py index 0a0315b26c..1631159020 100644 --- a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py +++ b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py @@ -45,8 +45,6 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): # Create a cloud output device manager that abstracts all cloud connection logic away. self._cloud_output_device_manager = CloudOutputDeviceManager() - self._cloud_output_device_manager.addedCloudCluster.connect(self._onCloudPrintingConfigured) - # Because the model needs to be created in the same thread as the QMLEngine, we use a signal. self.addDeviceSignal.connect(self._onAddDevice) self.removeDeviceSignal.connect(self._onRemoveDevice) @@ -96,6 +94,12 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): # Listen for when cloud flow is possible self.cloudFlowIsPossible.connect(self._onCloudFlowPossible) + # Listen if cloud cluster was added + self._cloud_output_device_manager.addedCloudCluster.connect(self._onCloudPrintingConfigured) + + # Listen if cloud cluster was removed + self._cloud_output_device_manager.removedCloudCluster.connect(self.checkCloudFlowIsPossible) + self._start_cloud_flow_message = None # type: Optional[Message] self._cloud_flow_complete_message = None # type: Optional[Message] From 2b6bab955245e7b1dcfe40ef419cd81d8f868c6c Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Wed, 6 Feb 2019 16:42:26 +0100 Subject: [PATCH 18/21] Re-enable firmware check Contributes to CL-1222 --- .../UM3NetworkPrinting/src/UM3OutputDevicePlugin.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py index 1631159020..d81fcd5c1e 100644 --- a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py +++ b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py @@ -430,12 +430,12 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): return # Check 5: Machine has correct firmware version - # firmware_version = self._application.getMachineManager().activeMachineFirmwareVersion - # if not Version(firmware_version) > self._min_cloud_version: - # Logger.log("d", "Cloud Flow not possible: Machine firmware (%s) is too low! (Requires version %s)", - # firmware_version, - # self._min_cloud_version) - # return + firmware_version = self._application.getMachineManager().activeMachineFirmwareVersion + if not Version(firmware_version) > self._min_cloud_version: + Logger.log("d", "Cloud Flow not possible: Machine firmware (%s) is too low! (Requires version %s)", + firmware_version, + self._min_cloud_version) + return Logger.log("d", "Cloud flow is possible!") self.cloudFlowIsPossible.emit() From 2d777929889f67d59950efc941e738aa1b5c61d9 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Fri, 8 Feb 2019 12:54:10 +0100 Subject: [PATCH 19/21] Add some typings Contributes to CL-1222 --- .../src/UM3OutputDevicePlugin.py | 53 ++++++++----------- 1 file changed, 21 insertions(+), 32 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py index d81fcd5c1e..a748de8cf2 100644 --- a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py +++ b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py @@ -402,11 +402,11 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): return True ## Check if the prerequsites are in place to start the cloud flow - def checkCloudFlowIsPossible(self): + def checkCloudFlowIsPossible(self) -> None: Logger.log("d", "Checking if cloud connection is possible...") # Pre-Check: Skip if active machine already has been cloud connected or you said don't ask again - active_machine = self._application.getMachineManager().activeMachine + active_machine = self._application.getMachineManager().activeMachine # type: Optional["GlobalStack"] if active_machine: # Check 1: Printer isn't already configured for cloud @@ -430,7 +430,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): return # Check 5: Machine has correct firmware version - firmware_version = self._application.getMachineManager().activeMachineFirmwareVersion + firmware_version = self._application.getMachineManager().activeMachineFirmwareVersion # type: str if not Version(firmware_version) > self._min_cloud_version: Logger.log("d", "Cloud Flow not possible: Machine firmware (%s) is too low! (Requires version %s)", firmware_version, @@ -440,21 +440,15 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): Logger.log("d", "Cloud flow is possible!") self.cloudFlowIsPossible.emit() - def _onCloudFlowPossible(self): + def _onCloudFlowPossible(self) -> None: # Cloud flow is possible, so show the message if not self._start_cloud_flow_message: self._start_cloud_flow_message = Message( - i18n_catalog.i18nc("@info:status", "Pair your printer to your Ultimaker account and start print jobs from anywhere."), - 0, # Lifetime - True, # Dismissable? - None, # Progress - "", # Title - None, # Parent - True, # Use inactivity timer - "../../../../../Cura/plugins/UM3NetworkPrinting/resources/svg/cloud-flow-start.svg", # Image souce - i18n_catalog.i18nc("@info:status", "Connect to cloud"), # image caption - i18n_catalog.i18nc("@action", "Don't ask me again for this printer."), # Toggle text - False # Toggle default state + text = i18n_catalog.i18nc("@info:status", "Pair your printer to your Ultimaker account and start print jobs from anywhere."), + image_source = "../../../../../Cura/plugins/UM3NetworkPrinting/resources/svg/cloud-flow-start.svg", + image_caption = i18n_catalog.i18nc("@info:status", "Connect to cloud"), + option_text = i18n_catalog.i18nc("@action", "Don't ask me again for this printer."), + option_state = False ) self._start_cloud_flow_message.addAction("", i18n_catalog.i18nc("@action", "Get started"), "", "") self._start_cloud_flow_message.optionToggled.connect(self._onDontAskMeAgain) @@ -462,7 +456,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self._start_cloud_flow_message.show() return - def _onCloudPrintingConfigured(self): + def _onCloudPrintingConfigured(self) -> None: if self._start_cloud_flow_message: self._start_cloud_flow_message.hide() self._start_cloud_flow_message = None @@ -470,15 +464,10 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): # Show the successful pop-up if not self._start_cloud_flow_message: self._cloud_flow_complete_message = Message( - i18n_catalog.i18nc("@info:status", "You can now send and monitor print jobs from anywhere using your Ultimaker account."), - 30, # Lifetime - True, # Dismissable? - None, # Progress - "", # Title - None, # Parent - True, # Use inactivity timer - "../../../../../Cura/plugins/UM3NetworkPrinting/resources/svg/cloud-flow-completed.svg", # Image souce - i18n_catalog.i18nc("@info:status", "Connected!") # image caption + text = i18n_catalog.i18nc("@info:status", "You can now send and monitor print jobs from anywhere using your Ultimaker account."), + lifetime = 30, + image_source = "../../../../../Cura/plugins/UM3NetworkPrinting/resources/svg/cloud-flow-completed.svg", + image_caption = i18n_catalog.i18nc("@info:status", "Connected!") ) self._cloud_flow_complete_message.addAction("", i18n_catalog.i18nc("@action", "Review your connection"), "", "", 1) # TODO: Icon self._cloud_flow_complete_message.actionTriggered.connect(self._onReviewCloudConnection) @@ -490,15 +479,15 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): active_machine.setMetaDataEntry("cloud_flow_complete", True) return - def _onDontAskMeAgain(self, message): - active_machine = self._application.getMachineManager().activeMachine + def _onDontAskMeAgain(self, messageId: str, checked: bool) -> None: + active_machine = self._application.getMachineManager().activeMachine # type: Optional["GlobalStack"] if active_machine: active_machine.setMetaDataEntry("show_cloud_message", False) Logger.log("d", "Will not ask the user again to cloud connect for current printer.") return - def _onCloudFlowStarted(self, message, action): - address = self._application.getMachineManager().activeMachineAddress + def _onCloudFlowStarted(self, messageId: str, actionId: str) -> None: + address = self._application.getMachineManager().activeMachineAddress # type: str if address: QDesktopServices.openUrl(QUrl("http://" + address + "/cloud_connect")) if self._start_cloud_flow_message: @@ -506,13 +495,13 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self._start_cloud_flow_message = None return - def _onReviewCloudConnection(self, message, action): - address = self._application.getMachineManager().activeMachineAddress + def _onReviewCloudConnection(self, messageId: str, actionId: str) -> None: + address = self._application.getMachineManager().activeMachineAddress # type: str if address: QDesktopServices.openUrl(QUrl("http://" + address + "/settings")) return - def _onMachineSwitched(self): + def _onMachineSwitched(self) -> None: if self._start_cloud_flow_message is not None: self._start_cloud_flow_message.hide() self._start_cloud_flow_message = None From 53e07eadd3cd921d290812e65467cab91e424da5 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Fri, 8 Feb 2019 13:03:10 +0100 Subject: [PATCH 20/21] Allow for optional typing Contributes to CL-1222 --- plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py index a748de8cf2..2d74c8acbb 100644 --- a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py +++ b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py @@ -22,6 +22,8 @@ from UM.i18n import i18nCatalog from . import ClusterUM3OutputDevice, LegacyUM3OutputDevice from .Cloud.CloudOutputDeviceManager import CloudOutputDeviceManager +from typing import Optional + i18n_catalog = i18nCatalog("cura") ## This plugin handles the connection detection & creation of output device objects for the UM3 printer. From 0dfcaadc49c152827f21cce65007fc0fdf4e8f67 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Fri, 8 Feb 2019 13:13:26 +0100 Subject: [PATCH 21/21] Typecheck improvements Contributes to CL-1222 --- plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py index 2d74c8acbb..3735ac2c79 100644 --- a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py +++ b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py @@ -12,6 +12,7 @@ from PyQt5.QtGui import QDesktopServices from cura.CuraApplication import CuraApplication from cura.PrinterOutputDevice import ConnectionType +from cura.Settings.GlobalStack import GlobalStack # typing from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin from UM.Logger import Logger from UM.Signal import Signal, signalemitter