From 36d3a92fc068dbfda2341f5ffc3d3aa991fbd35a Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 26 Aug 2022 13:27:58 +0200 Subject: [PATCH 01/32] Fix gramar mistake in documentation CURA-8463 --- .../src/Models/Http/ClusterPrinterStatus.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/Models/Http/ClusterPrinterStatus.py b/plugins/UM3NetworkPrinting/src/Models/Http/ClusterPrinterStatus.py index 16b4b6d656..925b4844c1 100644 --- a/plugins/UM3NetworkPrinting/src/Models/Http/ClusterPrinterStatus.py +++ b/plugins/UM3NetworkPrinting/src/Models/Http/ClusterPrinterStatus.py @@ -20,7 +20,6 @@ from ..BaseModel import BaseModel class ClusterPrinterStatus(BaseModel): """Class representing a cluster printer""" - def __init__(self, enabled: bool, firmware_version: str, friendly_name: str, ip_address: str, machine_variant: str, status: str, unique_name: str, uuid: str, configuration: List[Union[Dict[str, Any], ClusterPrintCoreConfiguration]], @@ -28,9 +27,9 @@ class ClusterPrinterStatus(BaseModel): firmware_update_status: Optional[str] = None, latest_available_firmware: Optional[str] = None, build_plate: Union[Dict[str, Any], ClusterBuildPlate] = None, material_station: Union[Dict[str, Any], ClusterPrinterMaterialStation] = None, **kwargs) -> None: - """Creates a new cluster printer status - - :param enabled: A printer can be disabled if it should not receive new jobs. By default every printer is enabled. + """ + Creates a new cluster printer status + :param enabled: A printer can be disabled if it should not receive new jobs. By default, every printer is enabled. :param firmware_version: Firmware version installed on the printer. Can differ for each printer in a cluster. :param friendly_name: Human readable name of the printer. Can be used for identification purposes. :param ip_address: The IP address of the printer in the local network. From d842013a7689d7cbaf1a24c6b746888b2eb66a2c Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 26 Aug 2022 13:31:48 +0200 Subject: [PATCH 02/32] Simplify onCompleted call in qml CURA-8463 --- resources/qml/Menus/ConfigurationMenu/ConfigurationItem.qml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/resources/qml/Menus/ConfigurationMenu/ConfigurationItem.qml b/resources/qml/Menus/ConfigurationMenu/ConfigurationItem.qml index e8917517dd..bcbb6d7679 100644 --- a/resources/qml/Menus/ConfigurationMenu/ConfigurationItem.qml +++ b/resources/qml/Menus/ConfigurationMenu/ConfigurationItem.qml @@ -221,10 +221,7 @@ Button } } - Component.onCompleted: - { - configurationItem.checked = Cura.MachineManager.matchesConfiguration(configuration) - } + Component.onCompleted: configurationItem.checked = Cura.MachineManager.matchesConfiguration(configuration) onClicked: { From 0516b27f2bdf73a4ee7ab41db5ef8a21fbe31ea1 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 26 Aug 2022 13:34:40 +0200 Subject: [PATCH 03/32] Clean up formatting of documentation Boyscouting! CURA-8463 --- cura/PrinterOutput/PrinterOutputDevice.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/cura/PrinterOutput/PrinterOutputDevice.py b/cura/PrinterOutput/PrinterOutputDevice.py index d3a5e252d3..add561fcb1 100644 --- a/cura/PrinterOutput/PrinterOutputDevice.py +++ b/cura/PrinterOutput/PrinterOutputDevice.py @@ -50,13 +50,12 @@ class PrinterOutputDevice(QObject, OutputDevice): The assumption is made the printer is a FDM printer. Note that a number of settings are marked as "final". This is because decorators - are not inherited by children. To fix this we use the private counter part of those + are not inherited by children. To fix this we use the private counterpart of those functions to actually have the implementation. For all other uses it should be used in the same way as a "regular" OutputDevice. """ - printersChanged = pyqtSignal() connectionStateChanged = pyqtSignal(str) acceptsCommandsChanged = pyqtSignal() @@ -183,8 +182,8 @@ class PrinterOutputDevice(QObject, OutputDevice): @pyqtProperty(QObject, constant = True) def monitorItem(self) -> QObject: # Note that we specifically only check if the monitor component is created. - # It could be that it failed to actually create the qml item! If we check if the item was created, it will try to - # create the item (and fail) every time. + # It could be that it failed to actually create the qml item! If we check if the item was created, it will try + # to create the item (and fail) every time. if not self._monitor_component: self._createMonitorViewFromQML() return self._monitor_item @@ -237,9 +236,9 @@ class PrinterOutputDevice(QObject, OutputDevice): self.acceptsCommandsChanged.emit() - # Returns the unique configurations of the printers within this output device @pyqtProperty("QVariantList", notify = uniqueConfigurationsChanged) def uniqueConfigurations(self) -> List["PrinterConfigurationModel"]: + """ Returns the unique configurations of the printers within this output device """ return self._unique_configurations def _updateUniqueConfigurations(self) -> None: @@ -248,7 +247,9 @@ class PrinterOutputDevice(QObject, OutputDevice): if printer.printerConfiguration is not None and printer.printerConfiguration.hasAnyMaterialLoaded(): all_configurations.add(printer.printerConfiguration) all_configurations.update(printer.availableConfigurations) - if None in all_configurations: # Shouldn't happen, but it does. I don't see how it could ever happen. Skip adding that configuration. List could end up empty! + if None in all_configurations: + # Shouldn't happen, but it does. I don't see how it could ever happen. Skip adding that configuration. + # List could end up empty! Logger.log("e", "Found a broken configuration in the synced list!") all_configurations.remove(None) new_configurations = sorted(all_configurations, key = lambda config: config.printerType or "") @@ -256,9 +257,9 @@ class PrinterOutputDevice(QObject, OutputDevice): self._unique_configurations = new_configurations self.uniqueConfigurationsChanged.emit() - # Returns the unique configurations of the printers within this output device @pyqtProperty("QStringList", notify = uniqueConfigurationsChanged) def uniquePrinterTypes(self) -> List[str]: + """ Returns the unique configurations of the printers within this output device """ return list(sorted(set([configuration.printerType or "" for configuration in self._unique_configurations]))) def _onPrintersChanged(self) -> None: From 46532828a402fd2ce4b925b6dac85da57a9c8af4 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 26 Aug 2022 13:46:01 +0200 Subject: [PATCH 04/32] Add logging for when setting the active machine failed CURA-8463 --- cura/Settings/MachineManager.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 389c5ded75..a8c27cc434 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -359,6 +359,7 @@ class MachineManager(QObject): extruder_manager = ExtruderManager.getInstance() extruder_manager.fixSingleExtrusionMachineExtruderDefinition(global_stack) if not global_stack.isValid(): + Logger.warning("Global stack isn't valid, adding it to faulty container list") # Mark global stack as invalid ConfigurationErrorMessage.getInstance().addFaultyContainers(global_stack.getId()) return # We're done here From 506f2b982075c3a525fbba0ab088cf35771c688c Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 26 Aug 2022 14:08:03 +0200 Subject: [PATCH 05/32] Remove code duplication in createAbstractMachine This also caused a crash when an abstract machine with multiple extruders was selected CURA-8463 --- cura/Machines/Models/MachineListModel.py | 3 +- cura/Settings/CuraStackBuilder.py | 47 ++++++++---------------- cura/Settings/GlobalStack.py | 1 - 3 files changed, 18 insertions(+), 33 deletions(-) diff --git a/cura/Machines/Models/MachineListModel.py b/cura/Machines/Models/MachineListModel.py index a758060384..b3e3bd4f71 100644 --- a/cura/Machines/Models/MachineListModel.py +++ b/cura/Machines/Models/MachineListModel.py @@ -81,7 +81,8 @@ class MachineListModel(ListModel): for stack in online_machine_stacks: self.addItem(stack) # Remove this machine from the other stack list - other_machine_stacks.remove(stack) + if stack in other_machine_stacks: + other_machine_stacks.remove(stack) for stack in other_machine_stacks: self.addItem(stack) diff --git a/cura/Settings/CuraStackBuilder.py b/cura/Settings/CuraStackBuilder.py index 5a745f8f0a..813b3f7d2e 100644 --- a/cura/Settings/CuraStackBuilder.py +++ b/cura/Settings/CuraStackBuilder.py @@ -1,7 +1,7 @@ # Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import Optional +from typing import Optional, cast from UM.ConfigurationErrorMessage import ConfigurationErrorMessage from UM.Logger import Logger @@ -275,41 +275,26 @@ class CuraStackBuilder: :return: The new Abstract Machine or None if an error occurred. """ abstract_machine_id = f"{definition_id}_abstract_machine" - from cura.CuraApplication import CuraApplication application = CuraApplication.getInstance() registry = application.getContainerRegistry() - container_tree = ContainerTree.getInstance() - if registry.findContainerStacks(is_abstract_machine = "True", id = abstract_machine_id): - # This abstract machine already exists + abstract_machines = registry.findContainerStacks(id = abstract_machine_id) + if abstract_machines: + return cast(GlobalStack, abstract_machines[0]) + definitions = registry.findDefinitionContainers(id=definition_id) + + name = "" + + if definitions: + name = definitions[0].getName() + stack = cls.createMachine(abstract_machine_id, definition_id) + if not stack: return None - match registry.findDefinitionContainers(type = "machine", id = definition_id): - case []: - # It should not be possible for the definition to be missing since an abstract machine will only - # be created as a result of a machine with definition_id being created. - Logger.error(f"Definition {definition_id} was not found!") - return None - case [machine_definition, *_definitions]: - machine_node = container_tree.machines[machine_definition.getId()] - name = machine_definition.getName() + stack.setName(name) - stack = GlobalStack(abstract_machine_id) - stack.setMetaDataEntry("is_abstract_machine", True) - stack.setMetaDataEntry("is_online", True) - stack.setDefinition(machine_definition) - cls.createUserContainer( - name, - machine_definition, - stack, - application.empty_variant_container, - application.empty_material_container, - machine_node.preferredGlobalQuality().container, - ) + stack.setMetaDataEntry("is_abstract_machine", True) + stack.setMetaDataEntry("is_online", True) - stack.setName(name) - - registry.addContainer(stack) - - return stack + return stack \ No newline at end of file diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py index 3c13f236ab..43232da725 100755 --- a/cura/Settings/GlobalStack.py +++ b/cura/Settings/GlobalStack.py @@ -292,7 +292,6 @@ class GlobalStack(CuraContainerStack): for extruder_train in extruder_trains: extruder_position = extruder_train.getMetaDataEntry("position") extruder_check_position.add(extruder_position) - for check_position in range(machine_extruder_count): if str(check_position) not in extruder_check_position: return False From 4ac8229c33d4724bdf0dbcf8b1aaa5b51d2a9f93 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 30 Aug 2022 10:28:22 +0200 Subject: [PATCH 06/32] Clean up codestyle violations Boyscouting! CURA-8463 --- .../src/Cloud/CloudOutputDevice.py | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py index 6431d09b7b..23ea5cf9aa 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py @@ -41,7 +41,7 @@ I18N_CATALOG = i18nCatalog("cura") class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): """The cloud output device is a network output device that works remotely but has limited functionality. - Currently it only supports viewing the printer and print job status and adding a new job to the queue. + Currently, it only supports viewing the printer and print job status and adding a new job to the queue. As such, those methods have been implemented here. Note that this device represents a single remote cluster, not a list of multiple clusters. """ @@ -58,7 +58,7 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): PRINT_JOB_ACTIONS_MIN_VERSION = Version("5.2.12") # 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. + # Therefore, we create a private signal used to trigger the printersChanged signal. _cloudClusterPrintersChanged = pyqtSignal() def __init__(self, api_client: CloudApiClient, cluster: CloudClusterResponse, parent: QObject = None) -> None: @@ -202,7 +202,8 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): # Note that self.writeFinished is called in _onPrintUploadCompleted as well. if self._uploaded_print_job: Logger.log("i", "Current mesh is already attached to a print-job, immediately request reprint.") - self._api.requestPrint(self.key, self._uploaded_print_job.job_id, self._onPrintUploadCompleted, self._onPrintUploadSpecificError) + self._api.requestPrint(self.key, self._uploaded_print_job.job_id, self._onPrintUploadCompleted, + self._onPrintUploadSpecificError) return # Export the scene to the correct file type. @@ -244,12 +245,15 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): self._progress.update(100) print_job = cast(CloudPrintJobResponse, self._pre_upload_print_job) - if not print_job: # It's possible that another print job is requested in the meanwhile, which then fails to upload with an error, which sets self._pre_uploaded_print_job to `None`. + if not print_job: + # It's possible that another print job is requested in the meanwhile, which then fails to upload with an + # error, which sets self._pre_uploaded_print_job to `None`. self._pre_upload_print_job = None self._uploaded_print_job = None Logger.log("w", "Interference from another job uploaded at roughly the same time, not uploading print!") return # Prevent a crash. - self._api.requestPrint(self.key, print_job.job_id, self._onPrintUploadCompleted, self._onPrintUploadSpecificError) + self._api.requestPrint(self.key, print_job.job_id, self._onPrintUploadCompleted, + self._onPrintUploadSpecificError) def _onPrintUploadCompleted(self, response: CloudPrintResponse) -> None: """Shows a message when the upload has succeeded @@ -262,10 +266,13 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): message.addAction("monitor print", name=I18N_CATALOG.i18nc("@action:button", "Monitor print"), icon="", - description=I18N_CATALOG.i18nc("@action:tooltip", "Track the print in Ultimaker Digital Factory"), + description=I18N_CATALOG.i18nc("@action:tooltip", + "Track the print in Ultimaker Digital Factory"), button_align=message.ActionButtonAlignment.ALIGN_RIGHT) - df_url = f"https://digitalfactory.ultimaker.com/app/jobs/{self._cluster.cluster_id}?utm_source=cura&utm_medium=software&utm_campaign=message-printjob-sent" - message.pyQtActionTriggered.connect(lambda message, action: (QDesktopServices.openUrl(QUrl(df_url)), message.hide())) + df_url = f"https://digitalfactory.ultimaker.com/app/jobs/{self._cluster.cluster_id}" \ + f"?utm_source=cura&utm_medium=software&utm_campaign=message-printjob-sent" + message.pyQtActionTriggered.connect(lambda message, action: (QDesktopServices.openUrl(QUrl(df_url)), + message.hide())) message.show() self.writeFinished.emit() @@ -278,7 +285,9 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): if error_code == 409: PrintJobUploadQueueFullMessage().show() else: - PrintJobUploadErrorMessage(I18N_CATALOG.i18nc("@error:send", "Unknown error code when uploading print job: {0}", error_code)).show() + PrintJobUploadErrorMessage(I18N_CATALOG.i18nc("@error:send", + "Unknown error code when uploading print job: {0}", + error_code)).show() Logger.log("w", "Upload of print job failed specifically with error code {}".format(error_code)) @@ -336,11 +345,13 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): @pyqtSlot(name="openPrintJobControlPanel") def openPrintJobControlPanel(self) -> None: - QDesktopServices.openUrl(QUrl(self.clusterCloudUrl + "?utm_source=cura&utm_medium=software&utm_campaign=monitor-manage-browser")) + QDesktopServices.openUrl(QUrl(f"{self.clusterCloudUrl}?utm_source=cura&utm_medium=software&" + f"utm_campaign=monitor-manage-browser")) @pyqtSlot(name="openPrinterControlPanel") def openPrinterControlPanel(self) -> None: - QDesktopServices.openUrl(QUrl(self.clusterCloudUrl + "?utm_source=cura&utm_medium=software&utm_campaign=monitor-manage-printer")) + QDesktopServices.openUrl(QUrl(f"{self.clusterCloudUrl}?utm_source=cura&utm_medium=software" + f"&utm_campaign=monitor-manage-printer")) permissionsChanged = pyqtSignal() @@ -362,7 +373,7 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): @pyqtProperty(bool, notify = permissionsChanged) def canWriteOwnPrintJobs(self) -> bool: """ - Whether this user can change things about print jobs made by themself. + Whether this user can change things about print jobs made by them. """ return "digital-factory.print-job.write.own" in self._account.permissions @@ -390,4 +401,4 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): """Gets the URL on which to monitor the cluster via the cloud.""" root_url_prefix = "-staging" if self._account.is_staging else "" - return "https://digitalfactory{}.ultimaker.com/app/jobs/{}".format(root_url_prefix, self.clusterData.cluster_id) + return f"https://digitalfactory{root_url_prefix}.ultimaker.com/app/jobs/{self.clusterData.cluster_id}" From f8ebf98df327ca44009d2c970f12b9eadf6913f6 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 30 Aug 2022 10:43:53 +0200 Subject: [PATCH 07/32] Update typing hints to new style More boyscouting as I try to understand this code CURA-8463 --- .../src/Cloud/CloudOutputDeviceManager.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index 30bbf68f6c..8da82a7e3d 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -46,15 +46,15 @@ class CloudOutputDeviceManager: def __init__(self) -> None: # Persistent dict containing the remote clusters for the authenticated user. - self._remote_clusters = {} # type: Dict[str, CloudOutputDevice] + self._remote_clusters: Dict[str, CloudOutputDevice] = {} # Dictionary containing all the cloud printers loaded in Cura - self._um_cloud_printers = {} # type: Dict[str, GlobalStack] + self._um_cloud_printers: Dict[str, GlobalStack] = {} - self._account = CuraApplication.getInstance().getCuraAPI().account # type: Account + self._account: Account = CuraApplication.getInstance().getCuraAPI().account self._api = CloudApiClient(CuraApplication.getInstance(), on_error = lambda error: Logger.log("e", str(error))) self._account.loginStateChanged.connect(self._onLoginStateChanged) - self._removed_printers_message = None # type: Optional[Message] + self._removed_printers_message: Optional[Message] = None # Ensure we don't start twice. self._running = False @@ -113,8 +113,8 @@ class CloudOutputDeviceManager: CuraApplication.getInstance().getContainerRegistry().findContainerStacks( type = "machine") if m.getMetaDataEntry(self.META_CLUSTER_ID, None)} new_clusters = [] - all_clusters = {c.cluster_id: c for c in clusters} # type: Dict[str, CloudClusterResponse] - online_clusters = {c.cluster_id: c for c in clusters if c.is_online} # type: Dict[str, CloudClusterResponse] + all_clusters: Dict[str, CloudClusterResponse] = {c.cluster_id: c for c in clusters} + online_clusters: Dict[str, CloudClusterResponse] = {c.cluster_id: c for c in clusters if c.is_online} # Add the new printers in Cura. for device_id, cluster_data in all_clusters.items(): @@ -369,7 +369,7 @@ class CloudOutputDeviceManager: # Remove the output device from the printers for device_id in removed_device_ids: - device = self._um_cloud_printers.get(device_id, None) # type: Optional[GlobalStack] + device: Optional[GlobalStack] = self._um_cloud_printers.get(device_id, None) if not device: continue if device_id in output_device_manager.getOutputDeviceIds(): @@ -383,7 +383,7 @@ class CloudOutputDeviceManager: self._removed_printers_message.show() def _onDiscoveredDeviceRemoved(self, device_id: str) -> None: - device = self._remote_clusters.pop(device_id, None) # type: Optional[CloudOutputDevice] + device: Optional[CloudOutputDevice] = self._remote_clusters.pop(device_id, None) if not device: return device.close() @@ -397,7 +397,7 @@ class CloudOutputDeviceManager: return False # Create a new machine. - # We do not use use MachineManager.addMachine here because we need to set the cluster ID before activating it. + # We do not use MachineManager.addMachine here because we need to set the cluster ID before activating it. new_machine = CuraStackBuilder.createMachine(device.name, device.printerType, show_warning_message=False) if not new_machine: Logger.error(f"Failed creating a new machine for {device.name}") From ed335963573950007a480c2688776025cf998654 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 30 Aug 2022 10:46:10 +0200 Subject: [PATCH 08/32] Rename device to global_stack Device was used in the rest of this class for the output device, not for a machine. This was a bit confusing CURA-8463 --- .../src/Cloud/CloudOutputDeviceManager.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index 8da82a7e3d..aa81b8f9bb 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -369,8 +369,8 @@ class CloudOutputDeviceManager: # Remove the output device from the printers for device_id in removed_device_ids: - device: Optional[GlobalStack] = self._um_cloud_printers.get(device_id, None) - if not device: + global_stack: Optional[GlobalStack] = self._um_cloud_printers.get(device_id, None) + if not global_stack: continue if device_id in output_device_manager.getOutputDeviceIds(): output_device_manager.removeOutputDevice(device_id) @@ -378,7 +378,7 @@ class CloudOutputDeviceManager: del self._remote_clusters[device_id] # Update the printer's metadata to mark it as not linked to the account - device.setMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, False) + global_stack.setMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, False) self._removed_printers_message.show() From 24f85bae064d33243939e9de1755fbf0b102af54 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 30 Aug 2022 10:52:21 +0200 Subject: [PATCH 09/32] Use new style string formating Moar boyscouting CURA-8463 --- .../src/Cloud/CloudOutputDeviceManager.py | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index aa81b8f9bb..2c192699cb 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -257,13 +257,13 @@ class CloudOutputDeviceManager: max_disp_devices = 3 if len(new_devices_added) > max_disp_devices: num_hidden = len(new_devices_added) - max_disp_devices - device_name_list = ["
  • {} ({})
  • ".format(device.name, device.printerTypeName) for device in new_devices[0:max_disp_devices]] + device_name_list = ["
  • {} ({})
  • ".format(device.name, device.printerTypeName) for device in new_devices[0: max_disp_devices]] device_name_list.append("
  • " + self.i18n_catalog.i18ncp("info:{0} gets replaced by a number of printers", "... and {0} other", "... and {0} others", num_hidden) + "
  • ") device_names = "".join(device_name_list) else: device_names = "".join(["
  • {} ({})
  • ".format(device.name, device.printerTypeName) for device in new_devices_added]) if new_devices_added: - message_text = self.i18n_catalog.i18nc("info:status", "Printers added from Digital Factory:") + "
      " + device_names + "
    " + message_text = self.i18n_catalog.i18nc("info:status", "Printers added from Digital Factory:") + f"
      {device_names}
    " message.setText(message_text) else: message.hide() @@ -291,7 +291,8 @@ class CloudOutputDeviceManager: old_cluster_id = outdated_machine.getMetaDataEntry(self.META_CLUSTER_ID) outdated_machine.setMetaDataEntry(self.META_CLUSTER_ID, new_cloud_output_device.key) outdated_machine.setMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, True) - # Cleanup the remainings of the old CloudOutputDevice(old_cluster_id) + + # Cleanup the remains of the old CloudOutputDevice(old_cluster_id) self._um_cloud_printers[new_cloud_output_device.key] = self._um_cloud_printers.pop(old_cluster_id) output_device_manager = CuraApplication.getInstance().getOutputDeviceManager() if old_cluster_id in output_device_manager.getOutputDeviceIds(): @@ -337,7 +338,8 @@ class CloudOutputDeviceManager: ), message_type = Message.MessageType.WARNING ) - device_names = "".join(["
  • {} ({})
  • ".format(self._um_cloud_printers[device].name, self._um_cloud_printers[device].definition.name) for device in self.reported_device_ids]) + device_names = "".join(["
  • {} ({})
  • ".format(self._um_cloud_printers[device].name, + self._um_cloud_printers[device].definition.name) for device in self.reported_device_ids]) message_text = self.i18n_catalog.i18ncp( "info:status", "This printer is not linked to the Digital Factory:", @@ -346,10 +348,11 @@ class CloudOutputDeviceManager: ) message_text += "
      {}

    ".format(device_names) digital_factory_string = self.i18n_catalog.i18nc("info:name", "Ultimaker Digital Factory") - + website_link = f"{digital_factory_string}." message_text += self.i18n_catalog.i18nc( "info:status", - "To establish a connection, please visit the {website_link}".format(website_link = "{}.".format(digital_factory_string)) + f"To establish a connection, please visit the {website_link}" ) self._removed_printers_message.setText(message_text) self._removed_printers_message.addAction("keep_printer_configurations_action", @@ -440,7 +443,8 @@ class CloudOutputDeviceManager: machine.setMetaDataEntry("group_name", device.name) machine.setMetaDataEntry("group_size", device.clusterSize) digital_factory_string = self.i18n_catalog.i18nc("info:name", "Ultimaker Digital Factory") - digital_factory_link = "{digital_factory_string}".format(digital_factory_string = digital_factory_string) + digital_factory_link = f"{digital_factory_string}" removal_warning_string = self.i18n_catalog.i18nc("@message {printer_name} is replaced with the name of the printer", "{printer_name} will be removed until the next account sync.").format(printer_name = device.name) \ + "
    " + self.i18n_catalog.i18nc("@message {printer_name} is replaced with the name of the printer", "To remove {printer_name} permanently, visit {digital_factory_link}").format(printer_name = device.name, digital_factory_link = digital_factory_link) \ + "

    " + self.i18n_catalog.i18nc("@message {printer_name} is replaced with the name of the printer", "Are you sure you want to remove {printer_name} temporarily?").format(printer_name = device.name) @@ -483,12 +487,16 @@ class CloudOutputDeviceManager: question_title = self.i18n_catalog.i18nc("@title:window", "Remove printers?") question_content = self.i18n_catalog.i18ncp( "@label", - "You are about to remove {0} printer from Cura. This action cannot be undone.\nAre you sure you want to continue?", - "You are about to remove {0} printers from Cura. This action cannot be undone.\nAre you sure you want to continue?", + "You are about to remove {0} printer from Cura. This action cannot be undone.\n" + "Are you sure you want to continue?", + "You are about to remove {0} printers from Cura. This action cannot be undone.\n" + "Are you sure you want to continue?", len(remove_printers_ids) ) if remove_printers_ids == all_ids: - question_content = self.i18n_catalog.i18nc("@label", "You are about to remove all printers from Cura. This action cannot be undone.\nAre you sure you want to continue?") + question_content = self.i18n_catalog.i18nc("@label", "You are about to remove all printers from Cura. " + "This action cannot be undone.\n" + "Are you sure you want to continue?") result = QMessageBox.question(None, question_title, question_content) if result == QMessageBox.StandardButton.No: return From 21b8f083f26bc3d2cf5fa35c7a698321ad7bc629 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 30 Aug 2022 11:03:04 +0200 Subject: [PATCH 10/32] Ensure that UI actually updates when adding remote printers More boyscouting. The documentation that it had before was actually false... CURA-8463 --- .../src/Cloud/CloudOutputDeviceManager.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index 2c192699cb..2058e77645 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -169,15 +169,15 @@ class CloudOutputDeviceManager: """**Synchronously** create machines for discovered devices Any new machines are made available to the user. - May take a long time to complete. As this code needs access to the Application - and blocks the GIL, creating a Job for this would not make sense. - Shows a Message informing the user of progress. + May take a long time to complete. This currently forcefully calls the "processEvents", which isn't + the nicest solution out there. We might need to consider moving this into a job later! """ new_devices = [] remote_clusters_added = False host_guid_map = {machine.getMetaDataEntry(self.META_HOST_GUID): device_cluster_id for device_cluster_id, machine in self._um_cloud_printers.items() if machine.getMetaDataEntry(self.META_HOST_GUID)} + machine_manager = CuraApplication.getInstance().getMachineManager() for cluster_data in clusters: @@ -202,6 +202,8 @@ class CloudOutputDeviceManager: # from the account elif not parseBool(self._um_cloud_printers[device.key].getMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, "true")): self._um_cloud_printers[device.key].setMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, True) + # As adding a lot of machines might take some time, ensure that the GUI (and progress message) is updated + CuraApplication.getInstance().processEvents() # Inform the Cloud printers model about new devices. new_devices_list_of_dicts = [{ From 30bc0d04bfbdf372f68224a1a34fd20e44c05fc8 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 30 Aug 2022 11:04:15 +0200 Subject: [PATCH 11/32] Add more typing & documentation Even more boyscouting. This code seems to be far too complex for what it should be... CURA-8463 --- .../src/Cloud/CloudOutputDeviceManager.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index 2058e77645..777e637d5f 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -172,12 +172,14 @@ class CloudOutputDeviceManager: May take a long time to complete. This currently forcefully calls the "processEvents", which isn't the nicest solution out there. We might need to consider moving this into a job later! """ - new_devices = [] + new_devices: List[CloudOutputDevice] = [] remote_clusters_added = False - host_guid_map = {machine.getMetaDataEntry(self.META_HOST_GUID): device_cluster_id - for device_cluster_id, machine in self._um_cloud_printers.items() - if machine.getMetaDataEntry(self.META_HOST_GUID)} - + + # Create a map that maps the HOST_GUID to the DEVICE_CLUSTER_ID + host_guid_map: Dict[str, str] = {machine.getMetaDataEntry(self.META_HOST_GUID): device_cluster_id + for device_cluster_id, machine in self._um_cloud_printers.items() + if machine.getMetaDataEntry(self.META_HOST_GUID)} + machine_manager = CuraApplication.getInstance().getMachineManager() for cluster_data in clusters: From d35441603b17f6e2fd3e103ef2713548107498e9 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 30 Aug 2022 11:06:09 +0200 Subject: [PATCH 12/32] Rename variables so it's easier to understand what is what CURA-8463 --- .../src/Cloud/CloudOutputDeviceManager.py | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index 777e637d5f..d2899e61e1 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -165,14 +165,14 @@ class CloudOutputDeviceManager: self._syncing = False self._account.setSyncState(self.SYNC_SERVICE_NAME, SyncState.ERROR) - def _onDevicesDiscovered(self, clusters: List[CloudClusterResponse]) -> None: + def _onDevicesDiscovered(self, discovered_clusters: List[CloudClusterResponse]) -> None: """**Synchronously** create machines for discovered devices Any new machines are made available to the user. May take a long time to complete. This currently forcefully calls the "processEvents", which isn't the nicest solution out there. We might need to consider moving this into a job later! """ - new_devices: List[CloudOutputDevice] = [] + new_output_devices: List[CloudOutputDevice] = [] remote_clusters_added = False # Create a map that maps the HOST_GUID to the DEVICE_CLUSTER_ID @@ -182,28 +182,28 @@ class CloudOutputDeviceManager: machine_manager = CuraApplication.getInstance().getMachineManager() - for cluster_data in clusters: - device = CloudOutputDevice(self._api, cluster_data) + for cluster_data in discovered_clusters: + output_device = CloudOutputDevice(self._api, cluster_data) # If the machine already existed before, it will be present in the host_guid_map if cluster_data.host_guid in host_guid_map: - machine = machine_manager.getMachine(device.printerType, {self.META_HOST_GUID: cluster_data.host_guid}) - if machine and machine.getMetaDataEntry(self.META_CLUSTER_ID) != device.key: + machine = machine_manager.getMachine(output_device.printerType, {self.META_HOST_GUID: cluster_data.host_guid}) + if machine and machine.getMetaDataEntry(self.META_CLUSTER_ID) != output_device.key: # If the retrieved device has a different cluster_id than the existing machine, bring the existing # machine up-to-date. - self._updateOutdatedMachine(outdated_machine = machine, new_cloud_output_device = device) + self._updateOutdatedMachine(outdated_machine = machine, new_cloud_output_device = output_device) # Create a machine if we don't already have it. Do not make it the active machine. # We only need to add it if it wasn't already added by "local" network or by cloud. - if machine_manager.getMachine(device.printerType, {self.META_CLUSTER_ID: device.key}) is None \ - and machine_manager.getMachine(device.printerType, {self.META_NETWORK_KEY: cluster_data.host_name + "*"}) is None: # The host name is part of the network key. - new_devices.append(device) - elif device.getId() not in self._remote_clusters: - self._remote_clusters[device.getId()] = device + if machine_manager.getMachine(output_device.printerType, {self.META_CLUSTER_ID: output_device.key}) is None \ + and machine_manager.getMachine(output_device.printerType, {self.META_NETWORK_KEY: cluster_data.host_name + "*"}) is None: # The host name is part of the network key. + new_output_devices.append(output_device) + elif output_device.getId() not in self._remote_clusters: + self._remote_clusters[output_device.getId()] = output_device remote_clusters_added = True # If a printer that was removed from the account is re-added, change its metadata to mark it not removed # from the account - elif not parseBool(self._um_cloud_printers[device.key].getMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, "true")): - self._um_cloud_printers[device.key].setMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, True) + elif not parseBool(self._um_cloud_printers[output_device.key].getMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, "true")): + self._um_cloud_printers[output_device.key].setMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, True) # As adding a lot of machines might take some time, ensure that the GUI (and progress message) is updated CuraApplication.getInstance().processEvents() @@ -212,11 +212,11 @@ class CloudOutputDeviceManager: "key": d.getId(), "name": d.name, "machine_type": d.printerTypeName, - "firmware_version": d.firmwareVersion} for d in new_devices] + "firmware_version": d.firmwareVersion} for d in new_output_devices] discovered_cloud_printers_model = CuraApplication.getInstance().getDiscoveredCloudPrintersModel() discovered_cloud_printers_model.addDiscoveredCloudPrinters(new_devices_list_of_dicts) - if not new_devices: + if not new_output_devices: if remote_clusters_added: self._connectToActiveMachine() return @@ -224,15 +224,15 @@ class CloudOutputDeviceManager: # Sort new_devices on online status first, alphabetical second. # Since the first device might be activated in case there is no active printer yet, # it would be nice to prioritize online devices - online_cluster_names = {c.friendly_name.lower() for c in clusters if c.is_online and not c.friendly_name is None} - new_devices.sort(key = lambda x: ("a{}" if x.name.lower() in online_cluster_names else "b{}").format(x.name.lower())) + online_cluster_names = {c.friendly_name.lower() for c in discovered_clusters if c.is_online and not c.friendly_name is None} + new_output_devices.sort(key = lambda x: ("a{}" if x.name.lower() in online_cluster_names else "b{}").format(x.name.lower())) message = Message( title = self.i18n_catalog.i18ncp( "info:status", "New printer detected from your Ultimaker account", "New printers detected from your Ultimaker account", - len(new_devices) + len(new_output_devices) ), progress = 0, lifetime = 0, @@ -242,26 +242,26 @@ class CloudOutputDeviceManager: new_devices_added = [] - for idx, device in enumerate(new_devices): - message_text = self.i18n_catalog.i18nc("info:status Filled in with printer name and printer model.", "Adding printer {name} ({model}) from your account").format(name = device.name, model = device.printerTypeName) + for idx, output_device in enumerate(new_output_devices): + message_text = self.i18n_catalog.i18nc("info:status Filled in with printer name and printer model.", "Adding printer {name} ({model}) from your account").format(name = output_device.name, model = output_device.printerTypeName) message.setText(message_text) - if len(new_devices) > 1: - message.setProgress((idx / len(new_devices)) * 100) + if len(new_output_devices) > 1: + message.setProgress((idx / len(new_output_devices)) * 100) CuraApplication.getInstance().processEvents() - self._remote_clusters[device.getId()] = device + self._remote_clusters[output_device.getId()] = output_device # If there is no active machine, activate the first available cloud printer activate = not CuraApplication.getInstance().getMachineManager().activeMachine - if self._createMachineFromDiscoveredDevice(device.getId(), activate = activate): - new_devices_added.append(device) + if self._createMachineFromDiscoveredDevice(output_device.getId(), activate = activate): + new_devices_added.append(output_device) message.setProgress(None) max_disp_devices = 3 if len(new_devices_added) > max_disp_devices: num_hidden = len(new_devices_added) - max_disp_devices - device_name_list = ["
  • {} ({})
  • ".format(device.name, device.printerTypeName) for device in new_devices[0: max_disp_devices]] + device_name_list = ["
  • {} ({})
  • ".format(device.name, device.printerTypeName) for device in new_output_devices[0: max_disp_devices]] device_name_list.append("
  • " + self.i18n_catalog.i18ncp("info:{0} gets replaced by a number of printers", "... and {0} other", "... and {0} others", num_hidden) + "
  • ") device_names = "".join(device_name_list) else: From 8e9056df71fff59fdea89d4b3ff063706020488a Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 30 Aug 2022 11:11:00 +0200 Subject: [PATCH 13/32] Rename function to better reflect what it does CURA-8463 --- .../src/Cloud/CloudOutputDeviceManager.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index d2899e61e1..90b16d8f78 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -130,8 +130,11 @@ class CloudOutputDeviceManager: self._um_cloud_printers[device_id].setMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, True) if not self._um_cloud_printers[device_id].getMetaDataEntry(META_CAPABILITIES, None): self._um_cloud_printers[device_id].setMetaDataEntry(META_CAPABILITIES, ",".join(cluster_data.capabilities)) - self._onDevicesDiscovered(new_clusters) + # We want a machine stack per remote printer that we discovered. Create them now! + self._createMachineStacksForDiscoveredClusters(new_clusters) + + # Update the online vs offline status for all found devices self._updateOnlinePrinters(all_clusters) # Hide the current removed_printers_message, if there is any @@ -165,7 +168,7 @@ class CloudOutputDeviceManager: self._syncing = False self._account.setSyncState(self.SYNC_SERVICE_NAME, SyncState.ERROR) - def _onDevicesDiscovered(self, discovered_clusters: List[CloudClusterResponse]) -> None: + def _createMachineStacksForDiscoveredClusters(self, discovered_clusters: List[CloudClusterResponse]) -> None: """**Synchronously** create machines for discovered devices Any new machines are made available to the user. From a429a93e9401358614df17679c26ddec215484a2 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 30 Aug 2022 11:26:09 +0200 Subject: [PATCH 14/32] Move new printers detected message to it's own class CURA-8463 --- .../src/Cloud/CloudOutputDeviceManager.py | 20 +++--------- .../src/Messages/NewPrinterDetectedMessage.py | 31 +++++++++++++++++++ 2 files changed, 35 insertions(+), 16 deletions(-) create mode 100644 plugins/UM3NetworkPrinting/src/Messages/NewPrinterDetectedMessage.py diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index 90b16d8f78..dc1167065a 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -23,6 +23,7 @@ from cura.UltimakerCloud.UltimakerCloudConstants import META_CAPABILITIES, META_ from .CloudApiClient import CloudApiClient from .CloudOutputDevice import CloudOutputDevice from ..Models.Http.CloudClusterResponse import CloudClusterResponse +from ..Messages.NewPrinterDetectedMessage import NewPrinterDetectedMessage class CloudOutputDeviceManager: @@ -230,27 +231,14 @@ class CloudOutputDeviceManager: online_cluster_names = {c.friendly_name.lower() for c in discovered_clusters if c.is_online and not c.friendly_name is None} new_output_devices.sort(key = lambda x: ("a{}" if x.name.lower() in online_cluster_names else "b{}").format(x.name.lower())) - message = Message( - title = self.i18n_catalog.i18ncp( - "info:status", - "New printer detected from your Ultimaker account", - "New printers detected from your Ultimaker account", - len(new_output_devices) - ), - progress = 0, - lifetime = 0, - message_type = Message.MessageType.POSITIVE - ) + message = NewPrinterDetectedMessage(num_printers_found = len(new_output_devices)) message.show() new_devices_added = [] for idx, output_device in enumerate(new_output_devices): - message_text = self.i18n_catalog.i18nc("info:status Filled in with printer name and printer model.", "Adding printer {name} ({model}) from your account").format(name = output_device.name, model = output_device.printerTypeName) - message.setText(message_text) - if len(new_output_devices) > 1: - message.setProgress((idx / len(new_output_devices)) * 100) - CuraApplication.getInstance().processEvents() + message.updateDisplayText(output_device) + self._remote_clusters[output_device.getId()] = output_device # If there is no active machine, activate the first available cloud printer diff --git a/plugins/UM3NetworkPrinting/src/Messages/NewPrinterDetectedMessage.py b/plugins/UM3NetworkPrinting/src/Messages/NewPrinterDetectedMessage.py new file mode 100644 index 0000000000..026251f972 --- /dev/null +++ b/plugins/UM3NetworkPrinting/src/Messages/NewPrinterDetectedMessage.py @@ -0,0 +1,31 @@ +# Copyright (c) 2022 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. +from UM import i18nCatalog +from UM.Message import Message +from cura.CuraApplication import CuraApplication + + +class NewPrinterDetectedMessage(Message): + i18n_catalog = i18nCatalog("cura") + + def __init__(self, num_printers_found: int) -> None: + super().__init__(title = self.i18n_catalog.i18ncp("info:status", + "New printer detected from your Ultimaker account", + "New printers detected from your Ultimaker account", + num_printers_found), + progress = 0, + lifetime = 0, + message_type = Message.MessageType.POSITIVE) + self._printers_added = 0 + self._num_printers_found = num_printers_found + + def updateDisplayText(self, output_device): + message_text = self.i18n_catalog.i18nc("info:status Filled in with printer name and printer model.", + "Adding printer {name} ({model}) from your account").format( + name=output_device.name, model=output_device.printerTypeName) + self.setText(message_text) + if self._num_printers_found > 1: + self.setProgress((self._printers_added / self._num_printers_found) * 100) + self._printers_added += 1 + + CuraApplication.getInstance().processEvents() From 7f9984cd16f348595e321686aec75c96524f0d89 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 30 Aug 2022 11:33:20 +0200 Subject: [PATCH 15/32] Also move remainder of NewPrinterDetected logic to it's own class This should make the CloudOutputDeviceManager a bit more readable CURA-8463 --- .../src/Cloud/CloudOutputDeviceManager.py | 17 ++-------- .../src/Messages/NewPrinterDetectedMessage.py | 31 ++++++++++++++++++- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index dc1167065a..3d5d43b131 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -237,7 +237,7 @@ class CloudOutputDeviceManager: new_devices_added = [] for idx, output_device in enumerate(new_output_devices): - message.updateDisplayText(output_device) + message.updateProgressText(output_device) self._remote_clusters[output_device.getId()] = output_device @@ -247,21 +247,8 @@ class CloudOutputDeviceManager: if self._createMachineFromDiscoveredDevice(output_device.getId(), activate = activate): new_devices_added.append(output_device) - message.setProgress(None) + message.finalize(new_devices_added, new_output_devices) - max_disp_devices = 3 - if len(new_devices_added) > max_disp_devices: - num_hidden = len(new_devices_added) - max_disp_devices - device_name_list = ["
  • {} ({})
  • ".format(device.name, device.printerTypeName) for device in new_output_devices[0: max_disp_devices]] - device_name_list.append("
  • " + self.i18n_catalog.i18ncp("info:{0} gets replaced by a number of printers", "... and {0} other", "... and {0} others", num_hidden) + "
  • ") - device_names = "".join(device_name_list) - else: - device_names = "".join(["
  • {} ({})
  • ".format(device.name, device.printerTypeName) for device in new_devices_added]) - if new_devices_added: - message_text = self.i18n_catalog.i18nc("info:status", "Printers added from Digital Factory:") + f"
      {device_names}
    " - message.setText(message_text) - else: - message.hide() def _updateOnlinePrinters(self, printer_responses: Dict[str, CloudClusterResponse]) -> None: """ diff --git a/plugins/UM3NetworkPrinting/src/Messages/NewPrinterDetectedMessage.py b/plugins/UM3NetworkPrinting/src/Messages/NewPrinterDetectedMessage.py index 026251f972..d85ade9dce 100644 --- a/plugins/UM3NetworkPrinting/src/Messages/NewPrinterDetectedMessage.py +++ b/plugins/UM3NetworkPrinting/src/Messages/NewPrinterDetectedMessage.py @@ -19,7 +19,12 @@ class NewPrinterDetectedMessage(Message): self._printers_added = 0 self._num_printers_found = num_printers_found - def updateDisplayText(self, output_device): + def updateProgressText(self, output_device): + """ + While the progress of adding printers is running, update the text displayed. + :param output_device: The output device that is being added. + :return: + """ message_text = self.i18n_catalog.i18nc("info:status Filled in with printer name and printer model.", "Adding printer {name} ({model}) from your account").format( name=output_device.name, model=output_device.printerTypeName) @@ -29,3 +34,27 @@ class NewPrinterDetectedMessage(Message): self._printers_added += 1 CuraApplication.getInstance().processEvents() + + def finalize(self, new_devices_added, new_output_devices): + self.setProgress(None) + num_devices_added = len(new_devices_added) + max_disp_devices = 3 + + if num_devices_added > max_disp_devices: + num_hidden = num_devices_added - max_disp_devices + device_name_list = ["
  • {} ({})
  • ".format(device.name, device.printerTypeName) for device in + new_output_devices[0: max_disp_devices]] + device_name_list.append( + "
  • " + self.i18n_catalog.i18ncp("info:{0} gets replaced by a number of printers", "... and {0} other", + "... and {0} others", num_hidden) + "
  • ") + device_names = "".join(device_name_list) + else: + device_names = "".join( + ["
  • {} ({})
  • ".format(device.name, device.printerTypeName) for device in new_devices_added]) + + if new_devices_added: + message_text = self.i18n_catalog.i18nc("info:status", + "Printers added from Digital Factory:") + f"
      {device_names}
    " + self.setText(message_text) + else: + self.hide() From 45e7d15c805a23cadba3eb8de7a66df9a8b1ecb4 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 30 Aug 2022 11:36:47 +0200 Subject: [PATCH 16/32] Add documentation for _onDiscoveredDeviceRemoved --- .../UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index 3d5d43b131..b290095be7 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -249,7 +249,6 @@ class CloudOutputDeviceManager: message.finalize(new_devices_added, new_output_devices) - def _updateOnlinePrinters(self, printer_responses: Dict[str, CloudClusterResponse]) -> None: """ Update the metadata of the printers to store whether they are online or not. @@ -368,6 +367,7 @@ class CloudOutputDeviceManager: self._removed_printers_message.show() def _onDiscoveredDeviceRemoved(self, device_id: str) -> None: + """ Remove the CloudOutputDevices for printers that are offline""" device: Optional[CloudOutputDevice] = self._remote_clusters.pop(device_id, None) if not device: return From ce3ab62c916cf64875af9f7937f76e3c21109af6 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 30 Aug 2022 11:45:30 +0200 Subject: [PATCH 17/32] Move RemovedprintersMessage to it's own class The OutputDeviceManager is just too bloated to properly make sense of. So I'm trying to move as much code out of it so I can start to make sense of it CURA-8463 --- .../src/Cloud/CloudOutputDeviceManager.py | 48 +++-------------- .../src/Messages/RemovedPrintersMessage.py | 52 +++++++++++++++++++ 2 files changed, 60 insertions(+), 40 deletions(-) create mode 100644 plugins/UM3NetworkPrinting/src/Messages/RemovedPrintersMessage.py diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index b290095be7..8665497229 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -22,6 +22,7 @@ from cura.Settings.GlobalStack import GlobalStack from cura.UltimakerCloud.UltimakerCloudConstants import META_CAPABILITIES, META_UM_LINKED_TO_ACCOUNT from .CloudApiClient import CloudApiClient from .CloudOutputDevice import CloudOutputDevice +from ..Messages.RemovedPrintersMessage import RemovedPrintersMessage from ..Models.Http.CloudClusterResponse import CloudClusterResponse from ..Messages.NewPrinterDetectedMessage import NewPrinterDetectedMessage @@ -303,52 +304,13 @@ class CloudOutputDeviceManager: for device_id in removed_device_ids: if not parseBool(self._um_cloud_printers[device_id].getMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, "true")): ignored_device_ids.add(device_id) + # Keep the reported_device_ids list in a class variable, so that the message button actions can access it and # take the necessary steps to fulfill their purpose. self.reported_device_ids = removed_device_ids - ignored_device_ids if not self.reported_device_ids: return - # Generate message - self._removed_printers_message = Message( - title = self.i18n_catalog.i18ncp( - "info:status", - "A cloud connection is not available for a printer", - "A cloud connection is not available for some printers", - len(self.reported_device_ids) - ), - message_type = Message.MessageType.WARNING - ) - device_names = "".join(["
  • {} ({})
  • ".format(self._um_cloud_printers[device].name, - self._um_cloud_printers[device].definition.name) for device in self.reported_device_ids]) - message_text = self.i18n_catalog.i18ncp( - "info:status", - "This printer is not linked to the Digital Factory:", - "These printers are not linked to the Digital Factory:", - len(self.reported_device_ids) - ) - message_text += "
      {}

    ".format(device_names) - digital_factory_string = self.i18n_catalog.i18nc("info:name", "Ultimaker Digital Factory") - website_link = f"{digital_factory_string}." - message_text += self.i18n_catalog.i18nc( - "info:status", - f"To establish a connection, please visit the {website_link}" - ) - self._removed_printers_message.setText(message_text) - self._removed_printers_message.addAction("keep_printer_configurations_action", - name = self.i18n_catalog.i18nc("@action:button", "Keep printer configurations"), - icon = "", - description = "Keep cloud printers in Ultimaker Cura when not connected to your account.", - button_align = Message.ActionButtonAlignment.ALIGN_RIGHT) - self._removed_printers_message.addAction("remove_printers_action", - name = self.i18n_catalog.i18nc("@action:button", "Remove printers"), - icon = "", - description = "Remove cloud printer(s) which aren't linked to your account.", - button_style = Message.ActionButtonStyle.SECONDARY, - button_align = Message.ActionButtonAlignment.ALIGN_LEFT) - self._removed_printers_message.actionTriggered.connect(self._onRemovedPrintersMessageActionTriggered) - output_device_manager = CuraApplication.getInstance().getOutputDeviceManager() # Remove the output device from the printers @@ -364,6 +326,12 @@ class CloudOutputDeviceManager: # Update the printer's metadata to mark it as not linked to the account global_stack.setMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, False) + # Generate message to show + device_names = "".join(["
  • {} ({})
  • ".format(self._um_cloud_printers[device].name, + self._um_cloud_printers[device].definition.name) for device in + self.reported_device_ids]) + self._removed_printers_message = RemovedPrintersMessage(self.reported_device_ids, device_names) + self._removed_printers_message.actionTriggered.connect(self._onRemovedPrintersMessageActionTriggered) self._removed_printers_message.show() def _onDiscoveredDeviceRemoved(self, device_id: str) -> None: diff --git a/plugins/UM3NetworkPrinting/src/Messages/RemovedPrintersMessage.py b/plugins/UM3NetworkPrinting/src/Messages/RemovedPrintersMessage.py new file mode 100644 index 0000000000..c047fee4f2 --- /dev/null +++ b/plugins/UM3NetworkPrinting/src/Messages/RemovedPrintersMessage.py @@ -0,0 +1,52 @@ +# Copyright (c) 2022 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. +from UM import i18nCatalog +from UM.Message import Message +from cura.CuraApplication import CuraApplication + + +class RemovedPrintersMessage(Message): + i18n_catalog = i18nCatalog("cura") + + def __init__(self, removed_devices, device_names) -> None: + self._removed_devices = removed_devices + + message_text = self.i18n_catalog.i18ncp( + "info:status", + "This printer is not linked to the Digital Factory:", + "These printers are not linked to the Digital Factory:", + len(self.removed_devices) + ) + message_text += "
      {}

    ".format(device_names) + + digital_factory_string = self.i18n_catalog.i18nc("info:name", "Ultimaker Digital Factory") + website_link = f"{digital_factory_string}." + + message_text += self.i18n_catalog.i18nc( + "info:status", + f"To establish a connection, please visit the {website_link}" + ) + + super().__init__(title=self.i18n_catalog.i18ncp("info:status", + "A cloud connection is not available for a printer", + "A cloud connection is not available for some printers", + len(self.removed_devices)), + message_type=Message.MessageType.WARNING, + text = message_text) + + self.addAction("keep_printer_configurations_action", + name=self.i18n_catalog.i18nc("@action:button", + "Keep printer configurations"), + icon="", + description="Keep cloud printers in Ultimaker Cura when not connected to your account.", + button_align=Message.ActionButtonAlignment.ALIGN_RIGHT) + self.addAction("remove_printers_action", + name=self.i18n_catalog.i18nc("@action:button", "Remove printers"), + icon="", + description="Remove cloud printer(s) which aren't linked to your account.", + button_style=Message.ActionButtonStyle.SECONDARY, + button_align=Message.ActionButtonAlignment.ALIGN_LEFT) + + + From f4c4b52d9b938b4c3b114fba56dfff68996f9e88 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 30 Aug 2022 11:49:09 +0200 Subject: [PATCH 18/32] Update typing to reflect new message that is being used CURA-8463 --- .../UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index 8665497229..0d2e678e8b 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -9,7 +9,6 @@ from PyQt6.QtWidgets import QMessageBox from UM import i18nCatalog from UM.Logger import Logger # To log errors talking to the API. -from UM.Message import Message from UM.Settings.Interfaces import ContainerInterface from UM.Signal import Signal from UM.Util import parseBool @@ -56,7 +55,7 @@ class CloudOutputDeviceManager: self._account: Account = CuraApplication.getInstance().getCuraAPI().account self._api = CloudApiClient(CuraApplication.getInstance(), on_error = lambda error: Logger.log("e", str(error))) self._account.loginStateChanged.connect(self._onLoginStateChanged) - self._removed_printers_message: Optional[Message] = None + self._removed_printers_message: Optional[RemovedPrintersMessage] = None # Ensure we don't start twice. self._running = False @@ -426,7 +425,7 @@ class CloudOutputDeviceManager: if container_cluster_id in self._remote_clusters.keys(): del self._remote_clusters[container_cluster_id] - def _onRemovedPrintersMessageActionTriggered(self, removed_printers_message: Message, action: str) -> None: + def _onRemovedPrintersMessageActionTriggered(self, removed_printers_message: RemovedPrintersMessage, action: str) -> None: if action == "keep_printer_configurations_action": removed_printers_message.hide() elif action == "remove_printers_action": From 7eabbd7b5cfc64b7219fde96ffed09e13f559af0 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 30 Aug 2022 11:52:13 +0200 Subject: [PATCH 19/32] Convert _updateOnlinePrinters to static CURA-8463 --- .../UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index 0d2e678e8b..7d8656dcd9 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -249,7 +249,8 @@ class CloudOutputDeviceManager: message.finalize(new_devices_added, new_output_devices) - def _updateOnlinePrinters(self, printer_responses: Dict[str, CloudClusterResponse]) -> None: + @staticmethod + def _updateOnlinePrinters(printer_responses: Dict[str, CloudClusterResponse]) -> None: """ Update the metadata of the printers to store whether they are online or not. :param printer_responses: The responses received from the API about the printer statuses. From 718d3790e20c397e0fd7f0f2091063b1862e74cf Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 30 Aug 2022 13:07:19 +0200 Subject: [PATCH 20/32] Clean up iterating over cluster list CURA-8463 --- .../src/Cloud/CloudOutputDeviceManager.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index 7d8656dcd9..f6df780206 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -156,6 +156,7 @@ class CloudOutputDeviceManager: if new_clusters or offline_device_keys or removed_device_keys: self.discoveredDevicesChanged.emit() + if offline_device_keys: # If the removed device was active we should connect to the new active device self._connectToActiveMachine() @@ -375,7 +376,10 @@ class CloudOutputDeviceManager: output_device_manager = CuraApplication.getInstance().getOutputDeviceManager() stored_cluster_id = active_machine.getMetaDataEntry(self.META_CLUSTER_ID) local_network_key = active_machine.getMetaDataEntry(self.META_NETWORK_KEY) - for device in list(self._remote_clusters.values()): # Make a copy of the remote devices list, to prevent modifying the list while iterating, if a device gets added asynchronously. + + # Copy of the device list, to prevent modifying the list while iterating, if a device gets added asynchronously. + remote_cluster_copy = list(self._remote_clusters.values()) + for device in remote_cluster_copy: if device.key == stored_cluster_id: # Connect to it if the stored ID matches. self._connectToOutputDevice(device, active_machine) From b689a84bcb0e6e69b5dc97aa8b0c2b1cb8c2a76c Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 30 Aug 2022 13:09:53 +0200 Subject: [PATCH 21/32] Add explicit typing --- .../UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index f6df780206..5206abcc34 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -378,7 +378,7 @@ class CloudOutputDeviceManager: local_network_key = active_machine.getMetaDataEntry(self.META_NETWORK_KEY) # Copy of the device list, to prevent modifying the list while iterating, if a device gets added asynchronously. - remote_cluster_copy = list(self._remote_clusters.values()) + remote_cluster_copy: List[CloudOutputDevice] = list(self._remote_clusters.values()) for device in remote_cluster_copy: if device.key == stored_cluster_id: # Connect to it if the stored ID matches. From 6d947963a4910062d0508be570dda4bfe2eb9ecd Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 30 Aug 2022 13:11:17 +0200 Subject: [PATCH 22/32] Remove stray whitespace CURA-8463 --- .../src/Network/LocalClusterOutputDeviceManager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/UM3NetworkPrinting/src/Network/LocalClusterOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Network/LocalClusterOutputDeviceManager.py index bddd383b23..0b6eb028a1 100644 --- a/plugins/UM3NetworkPrinting/src/Network/LocalClusterOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Network/LocalClusterOutputDeviceManager.py @@ -234,7 +234,6 @@ class LocalClusterOutputDeviceManager: _abstract_machine = CuraStackBuilder.createAbstractMachine(device.printerType) - def _storeManualAddress(self, address: str) -> None: """Add an address to the stored preferences.""" From 587d71bb57238c99e532af7af148dc9d114df085 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 30 Aug 2022 13:11:34 +0200 Subject: [PATCH 23/32] Fix typo CURA-8463 --- .../src/Network/LocalClusterOutputDeviceManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/UM3NetworkPrinting/src/Network/LocalClusterOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Network/LocalClusterOutputDeviceManager.py index 0b6eb028a1..d35e409086 100644 --- a/plugins/UM3NetworkPrinting/src/Network/LocalClusterOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Network/LocalClusterOutputDeviceManager.py @@ -221,7 +221,7 @@ class LocalClusterOutputDeviceManager: return # Create a new machine and activate it. - # We do not use use MachineManager.addMachine here because we need to set the network key before activating it. + # We do not use MachineManager.addMachine here because we need to set the network key before activating it. # If we do not do this the auto-pairing with the cloud-equivalent device will not work. new_machine = CuraStackBuilder.createMachine(device.name, device.printerType) if not new_machine: From 143f796ba6a8ffb6da2818254083edc92074d669 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 30 Aug 2022 13:13:32 +0200 Subject: [PATCH 24/32] Use get instead of direct [] The next line (checking if the result is None) doesn't make sense otherwise CURA-8463 --- .../UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index 5206abcc34..245914d1b9 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -346,7 +346,7 @@ class CloudOutputDeviceManager: output_device_manager.removeOutputDevice(device.key) def _createMachineFromDiscoveredDevice(self, key: str, activate: bool = True) -> bool: - device = self._remote_clusters[key] + device = self._remote_clusters.get(key) if not device: return False From 65a0a2f67b1c485228f7dbdcf42eb4f0dcd39ea0 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 30 Aug 2022 13:23:01 +0200 Subject: [PATCH 25/32] Update formatting of CloudAPIClient CURA-8463 --- .../src/Cloud/CloudApiClient.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py index 470e57947e..80029414d9 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py @@ -48,7 +48,6 @@ class CloudApiClient: """Initializes a new cloud API client. :param app: - :param account: The user's account object :param on_error: The callback to be called whenever we receive errors from the server. """ super().__init__() @@ -57,7 +56,7 @@ class CloudApiClient: self._scope = JsonDecoratorScope(UltimakerCloudScope(app)) self._http = HttpRequestManager.getInstance() self._on_error = on_error - self._upload = None # type: Optional[ToolPathUploader] + self._upload: Optional[ToolPathUploader] = None @property def account(self) -> Account: @@ -71,7 +70,7 @@ class CloudApiClient: :param on_finished: The function to be called after the result is parsed. """ - url = "{}/clusters?status=active".format(self.CLUSTER_API_ROOT) + url = f"{self.CLUSTER_API_ROOT}/clusters?status=active" self._http.get(url, scope = self._scope, callback = self._parseCallback(on_finished, CloudClusterResponse, failed), @@ -85,7 +84,7 @@ class CloudApiClient: :param on_finished: The function to be called after the result is parsed. """ - url = "{}/clusters/{}/status".format(self.CLUSTER_API_ROOT, cluster_id) + url = f"{self.CLUSTER_API_ROOT}/clusters/{cluster_id}/status" self._http.get(url, scope = self._scope, callback = self._parseCallback(on_finished, CloudClusterStatus), @@ -100,7 +99,7 @@ class CloudApiClient: :param on_finished: The function to be called after the result is parsed. """ - url = "{}/jobs/upload".format(self.CURA_API_ROOT) + url = f"{self.CURA_API_ROOT}/jobs/upload" data = json.dumps({"data": request.toDict()}).encode() self._http.put(url, @@ -131,7 +130,7 @@ class CloudApiClient: # specific to sending print jobs) such as lost connection, unparsable responses, etc. are not returned here, but # handled in a generic way by the CloudApiClient. def requestPrint(self, cluster_id: str, job_id: str, on_finished: Callable[[CloudPrintResponse], Any], on_error) -> None: - url = "{}/clusters/{}/print/{}".format(self.CLUSTER_API_ROOT, cluster_id, job_id) + url = f"{self.CLUSTER_API_ROOT}/clusters/{cluster_id}/print/{job_id}" self._http.post(url, scope = self._scope, data = b"", @@ -150,7 +149,7 @@ class CloudApiClient: """ body = json.dumps({"data": data}).encode() if data else b"" - url = "{}/clusters/{}/print_jobs/{}/action/{}".format(self.CLUSTER_API_ROOT, cluster_id, cluster_job_id, action) + url = f"{self.CLUSTER_API_ROOT}/clusters/{cluster_id}/print_jobs/{cluster_job_id}/action/{action}" self._http.post(url, scope = self._scope, data = body, @@ -159,7 +158,7 @@ class CloudApiClient: def _createEmptyRequest(self, path: str, content_type: Optional[str] = "application/json") -> QNetworkRequest: """We override _createEmptyRequest in order to add the user credentials. - :param url: The URL to request + :param path: The URL to request :param content_type: The type of the body contents. """ @@ -168,7 +167,7 @@ class CloudApiClient: request.setHeader(QNetworkRequest.KnownHeaders.ContentTypeHeader, content_type) access_token = self._account.accessToken if access_token: - request.setRawHeader(b"Authorization", "Bearer {}".format(access_token).encode()) + request.setRawHeader(b"Authorization", f"Bearer {access_token}".encode()) return request @staticmethod From 38e4ca1e0f2dcf90212e5aba4ab7be67ab6c44a4 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 30 Aug 2022 13:30:42 +0200 Subject: [PATCH 26/32] Updated documentation link CURA-8463 --- .../UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index 245914d1b9..d7f67498f3 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -30,7 +30,7 @@ class CloudOutputDeviceManager: """The cloud output device manager is responsible for using the Ultimaker Cloud APIs to manage remote clusters. Keeping all cloud related logic in this class instead of the UM3OutputDevicePlugin results in more readable code. - API spec is available on https://api.ultimaker.com/docs/connect/spec/. + API spec is available on https://docs.api.ultimaker.com/connect/index.html. """ META_CLUSTER_ID = "um_cloud_cluster_id" From 6fed6b824c104deb722bc430a8073d69acd76e09 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 30 Aug 2022 15:48:10 +0200 Subject: [PATCH 27/32] Added stub for AbstractCloudOutputDevice It doesn't actually allow you to send a print, but it does ask some info from the API CURA-8463 --- .../src/Cloud/AbstractCloudOutputDevice.py | 87 +++++++++++++++++++ .../src/Cloud/CloudApiClient.py | 19 +++- .../src/Cloud/CloudOutputDeviceManager.py | 55 +++++++++--- .../src/Models/Http/CloudClusterResponse.py | 1 - 4 files changed, 146 insertions(+), 16 deletions(-) create mode 100644 plugins/UM3NetworkPrinting/src/Cloud/AbstractCloudOutputDevice.py diff --git a/plugins/UM3NetworkPrinting/src/Cloud/AbstractCloudOutputDevice.py b/plugins/UM3NetworkPrinting/src/Cloud/AbstractCloudOutputDevice.py new file mode 100644 index 0000000000..566eae2ed9 --- /dev/null +++ b/plugins/UM3NetworkPrinting/src/Cloud/AbstractCloudOutputDevice.py @@ -0,0 +1,87 @@ +from time import time +from typing import List + +from PyQt6.QtCore import QObject +from PyQt6.QtNetwork import QNetworkReply + +from UM import i18nCatalog +from UM.Logger import Logger +from cura.PrinterOutput.NetworkedPrinterOutputDevice import AuthState +from cura.PrinterOutput.PrinterOutputDevice import ConnectionType +from .CloudApiClient import CloudApiClient +from ..Models.Http.CloudClusterResponse import CloudClusterResponse +from ..UltimakerNetworkedPrinterOutputDevice import UltimakerNetworkedPrinterOutputDevice + +I18N_CATALOG = i18nCatalog("cura") + + +class AbstractCloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): + API_CHECK_INTERVAL = 10.0 # seconds + + def __init__(self, api_client: CloudApiClient, printer_type: str, parent: QObject = None) -> None: + + self._api = api_client + properties = { + #b"address": cluster.host_internal_ip.encode() if cluster.host_internal_ip else b"", + # b"name": cluster.friendly_name.encode() if cluster.friendly_name else b"", + ##b"firmware_version": cluster.host_version.encode() if cluster.host_version else b"", + b"printer_type": printer_type.encode() + #b"cluster_size": str(cluster.printer_count).encode() if cluster.printer_count else b"1" + } + super().__init__( + device_id=f"ABSTRACT_{printer_type}", + address="", + connection_type=ConnectionType.CloudConnection, + properties=properties, + parent=parent + ) + + print("CREATING ABSTRACT CLOUD OUTPUT DEVIIICEEEEEE") + self._setInterfaceElements() + + def connect(self) -> None: + """Connects this device.""" + + if self.isConnected(): + return + Logger.log("i", "Attempting to connect AbstractCloudOutputDevice %s", self.key) + super().connect() + + #CuraApplication.getInstance().getBackend().backendStateChange.connect(self._onBackendStateChange) + self._update() + + def disconnect(self) -> None: + """Disconnects the device""" + + if not self.isConnected(): + return + super().disconnect() + + def _update(self) -> None: + """Called when the network data should be updated.""" + + super()._update() + if time() - self._time_of_last_request < self.API_CHECK_INTERVAL: + return # avoid calling the cloud too often + self._time_of_last_request = time() + if self._api.account.isLoggedIn: + self.setAuthenticationState(AuthState.Authenticated) + self._last_request_time = time() + self._api.getClustersByMachineType(self.printerType, self._onCompleted, self._onError) + else: + self.setAuthenticationState(AuthState.NotAuthenticated) + + def _setInterfaceElements(self) -> None: + """Set all the interface elements and texts for this output device.""" + + self.setPriority(2) # Make sure we end up below the local networking and above 'save to file'. + 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")) + + def _onCompleted(self, clusters: List[CloudClusterResponse]) -> None: + self._responseReceived() + # Todo: actually handle the data that we get back! + + def _onError(self, reply: QNetworkReply, error: QNetworkReply.NetworkError) -> None: + pass diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py index 80029414d9..89f9e9d449 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py @@ -61,7 +61,6 @@ class CloudApiClient: @property def account(self) -> Account: """Gets the account used for the API.""" - return self._account def getClusters(self, on_finished: Callable[[List[CloudClusterResponse]], Any], failed: Callable) -> None: @@ -77,6 +76,24 @@ class CloudApiClient: error_callback = failed, timeout = self.DEFAULT_REQUEST_TIMEOUT) + def getClustersByMachineType(self, machine_type, on_finished: Callable[[List[CloudClusterResponse]], Any], failed: Callable) -> None: + # HACK: There is something weird going on with the API, as it reports printer types in formats like + # "ultimaker_s3", but wants "Ultimaker S3" when using the machine_variant filter query. So we need to do some + # conversion! + + machine_type = machine_type.replace("_plus", "+") + machine_type = machine_type.replace("_", " ") + machine_type = machine_type.replace("ultimaker", "ultimaker ") + machine_type = machine_type.replace(" ", " ") + machine_type = machine_type.title() + + url = f"{self.CLUSTER_API_ROOT}/clusters?machine_variant={machine_type}" + self._http.get(url, + scope=self._scope, + callback=self._parseCallback(on_finished, CloudClusterResponse, failed), + error_callback=failed, + timeout=self.DEFAULT_REQUEST_TIMEOUT) + def getClusterStatus(self, cluster_id: str, on_finished: Callable[[CloudClusterStatus], Any]) -> None: """Retrieves the status of the given cluster. diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index d7f67498f3..846ef0a8ec 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -19,6 +19,7 @@ from cura.Settings.CuraContainerRegistry import CuraContainerRegistry # To upda from cura.Settings.CuraStackBuilder import CuraStackBuilder from cura.Settings.GlobalStack import GlobalStack from cura.UltimakerCloud.UltimakerCloudConstants import META_CAPABILITIES, META_UM_LINKED_TO_ACCOUNT +from .AbstractCloudOutputDevice import AbstractCloudOutputDevice from .CloudApiClient import CloudApiClient from .CloudOutputDevice import CloudOutputDevice from ..Messages.RemovedPrintersMessage import RemovedPrintersMessage @@ -49,6 +50,8 @@ class CloudOutputDeviceManager: # Persistent dict containing the remote clusters for the authenticated user. self._remote_clusters: Dict[str, CloudOutputDevice] = {} + self._abstract_clusters: Dict[str, AbstractCloudOutputDevice] = {} + # Dictionary containing all the cloud printers loaded in Cura self._um_cloud_printers: Dict[str, GlobalStack] = {} @@ -189,6 +192,10 @@ class CloudOutputDeviceManager: for cluster_data in discovered_clusters: output_device = CloudOutputDevice(self._api, cluster_data) + + if cluster_data.printer_type not in self._abstract_clusters: + self._abstract_clusters[cluster_data.printer_type] = AbstractCloudOutputDevice(self._api, cluster_data.printer_type) + # If the machine already existed before, it will be present in the host_guid_map if cluster_data.host_guid in host_guid_map: machine = machine_manager.getMachine(output_device.printerType, {self.META_HOST_GUID: cluster_data.host_guid}) @@ -373,22 +380,33 @@ class CloudOutputDeviceManager: if not active_machine: return + # Check if we should directly connect with a "normal" CloudOutputDevice or that we should connect to an + # 'abstract' one output_device_manager = CuraApplication.getInstance().getOutputDeviceManager() - stored_cluster_id = active_machine.getMetaDataEntry(self.META_CLUSTER_ID) - local_network_key = active_machine.getMetaDataEntry(self.META_NETWORK_KEY) + if active_machine.getMetaDataEntry("is_abstract_machine") != "True": + stored_cluster_id = active_machine.getMetaDataEntry(self.META_CLUSTER_ID) + local_network_key = active_machine.getMetaDataEntry(self.META_NETWORK_KEY) - # Copy of the device list, to prevent modifying the list while iterating, if a device gets added asynchronously. - remote_cluster_copy: List[CloudOutputDevice] = list(self._remote_clusters.values()) - for device in remote_cluster_copy: - if device.key == stored_cluster_id: - # Connect to it if the stored ID matches. - self._connectToOutputDevice(device, active_machine) - elif local_network_key and device.matchesNetworkKey(local_network_key): - # Connect to it if we can match the local network key that was already present. - self._connectToOutputDevice(device, active_machine) - elif device.key in output_device_manager.getOutputDeviceIds(): - # Remove device if it is not meant for the active machine. - output_device_manager.removeOutputDevice(device.key) + # Copy of the device list, to prevent modifying the list while iterating, if a device gets added asynchronously. + remote_cluster_copy: List[CloudOutputDevice] = list(self._remote_clusters.values()) + for device in remote_cluster_copy: + if device.key == stored_cluster_id: + # Connect to it if the stored ID matches. + self._connectToOutputDevice(device, active_machine) + elif local_network_key and device.matchesNetworkKey(local_network_key): + # Connect to it if we can match the local network key that was already present. + self._connectToOutputDevice(device, active_machine) + elif device.key in output_device_manager.getOutputDeviceIds(): + # Remove device if it is not meant for the active machine. + output_device_manager.removeOutputDevice(device.key) + else: # Abstract it is! + remote_abstract_cluster_copy: List[CloudOutputDevice] = list(self._abstract_clusters.values()) + for device in remote_abstract_cluster_copy: + if device.printerType == active_machine.definition.getId(): + print("Found the device to activate", device) + self._connectToAbstractOutputDevice(device, active_machine) + else: + output_device_manager.removeOutputDevice(device.key) def _setOutputDeviceMetadata(self, device: CloudOutputDevice, machine: GlobalStack): machine.setName(device.name) @@ -405,6 +423,15 @@ class CloudOutputDeviceManager: machine.setMetaDataEntry("removal_warning", removal_warning_string) machine.addConfiguredConnectionType(device.connectionType.value) + def _connectToAbstractOutputDevice(self, device: AbstractCloudOutputDevice, machine: GlobalStack) -> None: + if not device.isConnected(): + device.connect() + machine.addConfiguredConnectionType(device.connectionType.value) + + output_device_manager = CuraApplication.getInstance().getOutputDeviceManager() + if device.key not in output_device_manager.getOutputDeviceIds(): + output_device_manager.addOutputDevice(device) + def _connectToOutputDevice(self, device: CloudOutputDevice, machine: GlobalStack) -> None: """Connects to an output device and makes sure it is registered in the output device manager.""" diff --git a/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterResponse.py b/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterResponse.py index ce6dd1de4d..c8f3be282e 100644 --- a/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterResponse.py +++ b/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterResponse.py @@ -8,7 +8,6 @@ from ..BaseModel import BaseModel class CloudClusterResponse(BaseModel): """Class representing a cloud connected cluster.""" - def __init__(self, cluster_id: str, host_guid: str, host_name: str, is_online: bool, status: str, host_internal_ip: Optional[str] = None, host_version: Optional[str] = None, friendly_name: Optional[str] = None, printer_type: str = "ultimaker3", printer_count: int = 1, From e2ba110cf7afbf3cd89204efda84ea78d0ce8fe2 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 30 Aug 2022 16:33:29 +0200 Subject: [PATCH 28/32] Ensure that all material configs are added to the list CURA-8463 --- .../src/Cloud/AbstractCloudOutputDevice.py | 12 +++++++++--- .../UM3NetworkPrinting/src/Cloud/CloudApiClient.py | 5 +++-- .../src/Cloud/CloudOutputDeviceManager.py | 1 - .../Models/Http/CloudClusterWithConfigResponse.py | 14 ++++++++++++++ 4 files changed, 26 insertions(+), 6 deletions(-) create mode 100644 plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterWithConfigResponse.py diff --git a/plugins/UM3NetworkPrinting/src/Cloud/AbstractCloudOutputDevice.py b/plugins/UM3NetworkPrinting/src/Cloud/AbstractCloudOutputDevice.py index 566eae2ed9..bfda093d2e 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/AbstractCloudOutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/AbstractCloudOutputDevice.py @@ -10,6 +10,7 @@ from cura.PrinterOutput.NetworkedPrinterOutputDevice import AuthState from cura.PrinterOutput.PrinterOutputDevice import ConnectionType from .CloudApiClient import CloudApiClient from ..Models.Http.CloudClusterResponse import CloudClusterResponse +from ..Models.Http.CloudClusterWithConfigResponse import CloudClusterWithConfigResponse from ..UltimakerNetworkedPrinterOutputDevice import UltimakerNetworkedPrinterOutputDevice I18N_CATALOG = i18nCatalog("cura") @@ -36,7 +37,6 @@ class AbstractCloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): parent=parent ) - print("CREATING ABSTRACT CLOUD OUTPUT DEVIIICEEEEEE") self._setInterfaceElements() def connect(self) -> None: @@ -79,9 +79,15 @@ class AbstractCloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): self.setDescription(I18N_CATALOG.i18nc("@properties:tooltip", "Print via cloud")) self.setConnectionText(I18N_CATALOG.i18nc("@info:status", "Connected via cloud")) - def _onCompleted(self, clusters: List[CloudClusterResponse]) -> None: + def _onCompleted(self, clusters: List[CloudClusterWithConfigResponse]) -> None: self._responseReceived() - # Todo: actually handle the data that we get back! + + all_configurations = [] + for resp in clusters: + if resp.configuration is not None: + # Usually when the printer is offline, it doesn't have a configuration... + all_configurations.append(resp.configuration) + self._updatePrinters(all_configurations) def _onError(self, reply: QNetworkReply, error: QNetworkReply.NetworkError) -> None: pass diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py index 89f9e9d449..fbeef51f55 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py @@ -17,6 +17,7 @@ from cura.UltimakerCloud import UltimakerCloudConstants from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope from .ToolPathUploader import ToolPathUploader from ..Models.BaseModel import BaseModel +from ..Models.Http.CloudClusterWithConfigResponse import CloudClusterWithConfigResponse from ..Models.Http.CloudClusterResponse import CloudClusterResponse from ..Models.Http.CloudClusterStatus import CloudClusterStatus from ..Models.Http.CloudError import CloudError @@ -76,7 +77,7 @@ class CloudApiClient: error_callback = failed, timeout = self.DEFAULT_REQUEST_TIMEOUT) - def getClustersByMachineType(self, machine_type, on_finished: Callable[[List[CloudClusterResponse]], Any], failed: Callable) -> None: + def getClustersByMachineType(self, machine_type, on_finished: Callable[[List[CloudClusterWithConfigResponse]], Any], failed: Callable) -> None: # HACK: There is something weird going on with the API, as it reports printer types in formats like # "ultimaker_s3", but wants "Ultimaker S3" when using the machine_variant filter query. So we need to do some # conversion! @@ -90,7 +91,7 @@ class CloudApiClient: url = f"{self.CLUSTER_API_ROOT}/clusters?machine_variant={machine_type}" self._http.get(url, scope=self._scope, - callback=self._parseCallback(on_finished, CloudClusterResponse, failed), + callback=self._parseCallback(on_finished, CloudClusterWithConfigResponse, failed), error_callback=failed, timeout=self.DEFAULT_REQUEST_TIMEOUT) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index 846ef0a8ec..a905cc8791 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -403,7 +403,6 @@ class CloudOutputDeviceManager: remote_abstract_cluster_copy: List[CloudOutputDevice] = list(self._abstract_clusters.values()) for device in remote_abstract_cluster_copy: if device.printerType == active_machine.definition.getId(): - print("Found the device to activate", device) self._connectToAbstractOutputDevice(device, active_machine) else: output_device_manager.removeOutputDevice(device.key) diff --git a/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterWithConfigResponse.py b/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterWithConfigResponse.py new file mode 100644 index 0000000000..eb6389910c --- /dev/null +++ b/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterWithConfigResponse.py @@ -0,0 +1,14 @@ +# Copyright (c) 2019 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. +from typing import Optional, List + +from .CloudClusterResponse import CloudClusterResponse +from .ClusterPrinterStatus import ClusterPrinterStatus + + +class CloudClusterWithConfigResponse(CloudClusterResponse): + """Class representing a cloud connected cluster.""" + + def __init__(self, **kwargs) -> None: + self.configuration = self.parseModel(ClusterPrinterStatus, kwargs.get("host_printer")) + super().__init__(**kwargs) From feadbf04da2c6a4e5c75fdd140ad92ecc1c372bb Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 30 Aug 2022 16:42:15 +0200 Subject: [PATCH 29/32] Fix status request for Ultimaker 2+ Connect CURA-8463 --- plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py index fbeef51f55..d496435188 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py @@ -1,6 +1,7 @@ # Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. import json +import urllib.parse from json import JSONDecodeError from time import time from typing import Callable, List, Type, TypeVar, Union, Optional, Tuple, Dict, Any, cast @@ -87,7 +88,7 @@ class CloudApiClient: machine_type = machine_type.replace("ultimaker", "ultimaker ") machine_type = machine_type.replace(" ", " ") machine_type = machine_type.title() - + machine_type = urllib.parse.quote_plus(machine_type) url = f"{self.CLUSTER_API_ROOT}/clusters?machine_variant={machine_type}" self._http.get(url, scope=self._scope, From dd5981d85e4b9ac3580c6164e731fc76fcc5ce6b Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 31 Aug 2022 10:09:47 +0200 Subject: [PATCH 30/32] Only remove abstract output device if it's actually added CURA-8463 --- .../UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index a905cc8791..1105a8b356 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -404,7 +404,7 @@ class CloudOutputDeviceManager: for device in remote_abstract_cluster_copy: if device.printerType == active_machine.definition.getId(): self._connectToAbstractOutputDevice(device, active_machine) - else: + elif device.key in output_device_manager.getOutputDeviceIds(): output_device_manager.removeOutputDevice(device.key) def _setOutputDeviceMetadata(self, device: CloudOutputDevice, machine: GlobalStack): @@ -423,6 +423,7 @@ class CloudOutputDeviceManager: machine.addConfiguredConnectionType(device.connectionType.value) def _connectToAbstractOutputDevice(self, device: AbstractCloudOutputDevice, machine: GlobalStack) -> None: + Logger.debug(f"Attempting to connect to abstract machine {machine.id}") if not device.isConnected(): device.connect() machine.addConfiguredConnectionType(device.connectionType.value) From 5ff07b00d029a0b6652dc6dcdd2992d162adaae8 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 31 Aug 2022 10:27:52 +0200 Subject: [PATCH 31/32] Fix case where both a "normal" and an "abstract" ouput device could be active CURA-8463 --- .../src/Cloud/CloudOutputDeviceManager.py | 46 +++++++++---------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index 1105a8b356..4082431cd9 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -64,7 +64,6 @@ class CloudOutputDeviceManager: self._running = False self._syncing = False - CuraApplication.getInstance().getContainerRegistry().containerRemoved.connect(self._printerRemoved) def start(self): @@ -375,7 +374,6 @@ class CloudOutputDeviceManager: def _connectToActiveMachine(self) -> None: """Callback for when the active machine was changed by the user""" - active_machine = CuraApplication.getInstance().getGlobalContainerStack() if not active_machine: return @@ -383,29 +381,29 @@ class CloudOutputDeviceManager: # Check if we should directly connect with a "normal" CloudOutputDevice or that we should connect to an # 'abstract' one output_device_manager = CuraApplication.getInstance().getOutputDeviceManager() - if active_machine.getMetaDataEntry("is_abstract_machine") != "True": - stored_cluster_id = active_machine.getMetaDataEntry(self.META_CLUSTER_ID) - local_network_key = active_machine.getMetaDataEntry(self.META_NETWORK_KEY) + stored_cluster_id = active_machine.getMetaDataEntry(self.META_CLUSTER_ID) + local_network_key = active_machine.getMetaDataEntry(self.META_NETWORK_KEY) - # Copy of the device list, to prevent modifying the list while iterating, if a device gets added asynchronously. - remote_cluster_copy: List[CloudOutputDevice] = list(self._remote_clusters.values()) - for device in remote_cluster_copy: - if device.key == stored_cluster_id: - # Connect to it if the stored ID matches. - self._connectToOutputDevice(device, active_machine) - elif local_network_key and device.matchesNetworkKey(local_network_key): - # Connect to it if we can match the local network key that was already present. - self._connectToOutputDevice(device, active_machine) - elif device.key in output_device_manager.getOutputDeviceIds(): - # Remove device if it is not meant for the active machine. - output_device_manager.removeOutputDevice(device.key) - else: # Abstract it is! - remote_abstract_cluster_copy: List[CloudOutputDevice] = list(self._abstract_clusters.values()) - for device in remote_abstract_cluster_copy: - if device.printerType == active_machine.definition.getId(): - self._connectToAbstractOutputDevice(device, active_machine) - elif device.key in output_device_manager.getOutputDeviceIds(): - output_device_manager.removeOutputDevice(device.key) + # Copy of the device list, to prevent modifying the list while iterating, if a device gets added asynchronously. + remote_cluster_copy: List[CloudOutputDevice] = list(self._remote_clusters.values()) + for device in remote_cluster_copy: + if device.key == stored_cluster_id: + # Connect to it if the stored ID matches. + self._connectToOutputDevice(device, active_machine) + elif local_network_key and device.matchesNetworkKey(local_network_key): + # Connect to it if we can match the local network key that was already present. + self._connectToOutputDevice(device, active_machine) + elif device.key in output_device_manager.getOutputDeviceIds(): + # Remove device if it is not meant for the active machine. + output_device_manager.removeOutputDevice(device.key) + + # Update state of all abstract output devices + remote_abstract_cluster_copy: List[CloudOutputDevice] = list(self._abstract_clusters.values()) + for device in remote_abstract_cluster_copy: + if device.printerType == active_machine.definition.getId() and parseBool(active_machine.getMetaDataEntry("is_abstract_machine", False)): + self._connectToAbstractOutputDevice(device, active_machine) + elif device.key in output_device_manager.getOutputDeviceIds(): + output_device_manager.removeOutputDevice(device.key) def _setOutputDeviceMetadata(self, device: CloudOutputDevice, machine: GlobalStack): machine.setName(device.name) From b9e8bca01298ecc7f1708ea811f2eb6eb929895e Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 31 Aug 2022 10:33:46 +0200 Subject: [PATCH 32/32] Remove commented out properties CURA-8463 --- .../src/Cloud/AbstractCloudOutputDevice.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/AbstractCloudOutputDevice.py b/plugins/UM3NetworkPrinting/src/Cloud/AbstractCloudOutputDevice.py index bfda093d2e..8448c095c8 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/AbstractCloudOutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/AbstractCloudOutputDevice.py @@ -22,13 +22,7 @@ class AbstractCloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): def __init__(self, api_client: CloudApiClient, printer_type: str, parent: QObject = None) -> None: self._api = api_client - properties = { - #b"address": cluster.host_internal_ip.encode() if cluster.host_internal_ip else b"", - # b"name": cluster.friendly_name.encode() if cluster.friendly_name else b"", - ##b"firmware_version": cluster.host_version.encode() if cluster.host_version else b"", - b"printer_type": printer_type.encode() - #b"cluster_size": str(cluster.printer_count).encode() if cluster.printer_count else b"1" - } + properties = {b"printer_type": printer_type.encode()} super().__init__( device_id=f"ABSTRACT_{printer_type}", address="",