From 1253b41537ce94254abccf4b54270e9b23497770 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 2 Dec 2021 12:22:55 +0100 Subject: [PATCH 1/9] Only perform materials-sync-job for capable printers. part of CURA-8671 --- cura/PrinterOutput/UploadMaterialsJob.py | 8 ++++++++ cura/UltimakerCloud/UltimakerCloudConstants.py | 3 +++ .../src/Cloud/CloudOutputDeviceManager.py | 4 +++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/cura/PrinterOutput/UploadMaterialsJob.py b/cura/PrinterOutput/UploadMaterialsJob.py index 166b692ea5..7a08a198c1 100644 --- a/cura/PrinterOutput/UploadMaterialsJob.py +++ b/cura/PrinterOutput/UploadMaterialsJob.py @@ -83,6 +83,14 @@ class UploadMaterialsJob(Job): host_guid = "*", # Required metadata field. Otherwise we get a KeyError. um_cloud_cluster_id = "*" # Required metadata field. Otherwise we get a KeyError. ) + + # Filter out any printer not capable of the 'import_material' capability. Needs FW 7.0.1-RC at the least! + self._printer_metadata = [ printer_data for printer_data in self._printer_metadata if ( + UltimakerCloudConstants.META_CAPABILITIES in printer_data and + "import_material" in printer_data[UltimakerCloudConstants.META_CAPABILITIES] + ) + ] + for printer in self._printer_metadata: self._printer_sync_status[printer["host_guid"]] = self.PrinterStatus.UPLOADING.value diff --git a/cura/UltimakerCloud/UltimakerCloudConstants.py b/cura/UltimakerCloud/UltimakerCloudConstants.py index 0c8ea0c9c7..f65d500fb7 100644 --- a/cura/UltimakerCloud/UltimakerCloudConstants.py +++ b/cura/UltimakerCloud/UltimakerCloudConstants.py @@ -13,6 +13,9 @@ DEFAULT_DIGITAL_FACTORY_URL = "https://digitalfactory.ultimaker.com" # type: st META_UM_LINKED_TO_ACCOUNT = "um_linked_to_account" """(bool) Whether a cloud printer is linked to an Ultimaker account""" +META_CAPABILITIES = "capabilities" +"""(list[str]) a list of capabilities this printer supports""" + try: from cura.CuraVersion import CuraCloudAPIRoot # type: ignore if CuraCloudAPIRoot == "": diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index 5b1844e7cb..862c583f79 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -19,7 +19,7 @@ from cura.CuraApplication import CuraApplication from cura.Settings.CuraContainerRegistry import CuraContainerRegistry # To update printer metadata with information received about cloud printers. from cura.Settings.CuraStackBuilder import CuraStackBuilder from cura.Settings.GlobalStack import GlobalStack -from cura.UltimakerCloud.UltimakerCloudConstants import META_UM_LINKED_TO_ACCOUNT +from cura.UltimakerCloud.UltimakerCloudConstants import META_CAPABILITIES, META_UM_LINKED_TO_ACCOUNT from .CloudApiClient import CloudApiClient from .CloudOutputDevice import CloudOutputDevice from ..Models.Http.CloudClusterResponse import CloudClusterResponse @@ -128,6 +128,8 @@ class CloudOutputDeviceManager: # to the current account if not parseBool(self._um_cloud_printers[device_id].getMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, "true")): 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, cluster_data.capabilities) self._onDevicesDiscovered(new_clusters) self._updateOnlinePrinters(all_clusters) From f5604dfb1e2b95742214797e7f2828b67911e8f2 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 2 Dec 2021 12:23:57 +0100 Subject: [PATCH 2/9] Add 'capabilities' to ignored metadata. part of CURA-8671 --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 4 +++- plugins/3MFWriter/ThreeMFWorkspaceWriter.py | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index ee8652839d..0578fd51c3 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -49,7 +49,9 @@ _ignored_machine_network_metadata = { "removal_warning", "group_name", "group_size", - "connection_type" + "connection_type", + "capabilities", + "octoprint_api_key", } # type: Set[str] diff --git a/plugins/3MFWriter/ThreeMFWorkspaceWriter.py b/plugins/3MFWriter/ThreeMFWorkspaceWriter.py index 7f39d300b7..d6cc6ea159 100644 --- a/plugins/3MFWriter/ThreeMFWorkspaceWriter.py +++ b/plugins/3MFWriter/ThreeMFWorkspaceWriter.py @@ -154,7 +154,8 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter): "group_name", "group_size", "connection_type", - "octoprint_api_key" + "capabilities", + "octoprint_api_key", } serialized_data = container.serialize(ignored_metadata_keys = ignore_keys) From b76df21b4b0f8ed734369317e31cd70a4bc73785 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 2 Dec 2021 17:55:53 +0100 Subject: [PATCH 3/9] Filter printer list by capabilities And an example of such usage: In the material sync via cloud we only want to sync with printers that can receive those materials. We might want to add a message for the user to also make sure the firmware is up to date. Because if the firmware is not up to date now it will show no printers and instruct the user how to connect the printer to the cloud. Contributes to issue CURA-8671. --- cura/Machines/Models/GlobalStacksModel.py | 21 ++++++++++++++++++- .../ultimaker2_plus_connect.def.json | 2 +- .../Materials/MaterialsSyncDialog.qml | 1 + 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/cura/Machines/Models/GlobalStacksModel.py b/cura/Machines/Models/GlobalStacksModel.py index 586bd11819..cc750f2244 100644 --- a/cura/Machines/Models/GlobalStacksModel.py +++ b/cura/Machines/Models/GlobalStacksModel.py @@ -2,7 +2,7 @@ # Cura is released under the terms of the LGPLv3 or higher. from PyQt5.QtCore import Qt, QTimer, pyqtProperty, pyqtSignal -from typing import Optional +from typing import List, Optional from UM.Qt.ListModel import ListModel from UM.i18n import i18nCatalog @@ -11,6 +11,7 @@ from UM.Util import parseBool from cura.PrinterOutput.PrinterOutputDevice import ConnectionType from cura.Settings.CuraContainerRegistry import CuraContainerRegistry from cura.Settings.GlobalStack import GlobalStack +from cura.UltimakerCloud.UltimakerCloudConstants import META_CAPABILITIES # To filter on the printer's capabilities. class GlobalStacksModel(ListModel): @@ -42,6 +43,7 @@ class GlobalStacksModel(ListModel): self._filter_connection_type = None # type: Optional[ConnectionType] self._filter_online_only = False + self._filter_capabilities: List[str] = [] # Required capabilities that all listed printers must have. # Listen to changes CuraContainerRegistry.getInstance().containerAdded.connect(self._onContainerChanged) @@ -76,6 +78,19 @@ class GlobalStacksModel(ListModel): """ return self._filter_online_only + filterCapabilitiesChanged = pyqtSignal() + def setFilterCapabilities(self, new_filter: List[str]) -> None: + self._filter_capabilities = new_filter + + @pyqtProperty("QStringList", fset = setFilterCapabilities, notify = filterCapabilitiesChanged) + def filterCapabilities(self) -> List[str]: + """ + Capabilities to require on the list of printers. + + Only printers that have all of these capabilities will be shown in this model. + """ + return self._filter_capabilities + def _onContainerChanged(self, container) -> None: """Handler for container added/removed events from registry""" @@ -108,6 +123,10 @@ class GlobalStacksModel(ListModel): if self._filter_online_only and not is_online: continue + capabilities = set(container_stack.getMetaDataEntry(META_CAPABILITIES, set())) + if set(self._filter_capabilities) - capabilities: # Not all required capabilities are met. + continue + device_name = container_stack.getMetaDataEntry("group_name", container_stack.getName()) section_name = "Connected printers" if has_remote_connection else "Preset printers" section_name = self._catalog.i18nc("@info:title", section_name) diff --git a/resources/definitions/ultimaker2_plus_connect.def.json b/resources/definitions/ultimaker2_plus_connect.def.json index 1a4b68cf24..fd1f77d573 100644 --- a/resources/definitions/ultimaker2_plus_connect.def.json +++ b/resources/definitions/ultimaker2_plus_connect.def.json @@ -81,7 +81,7 @@ "material_bed_temperature_layer_0": { "maximum_value": 110 }, "material_print_temperature": { "maximum_value": 260 }, "meshfix_maximum_resolution": { "value": "(speed_wall_0 + speed_wall_x) / 60" }, - "meshfix_maximum_deviation": { "value": "layer_height / 4" }, + "meshfix_maximum_deviation": { "value": "layer_height / 4" }, "meshfix_maximum_travel_resolution": { "value": 0.5 }, "prime_blob_enable": { "enabled": true, "default_value": true, "value": "resolveOrValue('print_sequence') != 'one_at_a_time'" } } diff --git a/resources/qml/Preferences/Materials/MaterialsSyncDialog.qml b/resources/qml/Preferences/Materials/MaterialsSyncDialog.qml index d0cf9fafd6..9eaaa7bd6f 100644 --- a/resources/qml/Preferences/Materials/MaterialsSyncDialog.qml +++ b/resources/qml/Preferences/Materials/MaterialsSyncDialog.qml @@ -737,6 +737,7 @@ Window id: cloudPrinterList filterConnectionType: 3 //Only show cloud connections. filterOnlineOnly: true //Only show printers that are online. + filterCapabilities: ["import_materials"] //Only show printers that can receive the material profiles. } Cura.GlobalStacksModel { From 0477ba44b2fb8e154c9fda145ae36245a4c31d40 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 3 Dec 2021 13:25:41 +0100 Subject: [PATCH 4/9] Encode capabilities as comma-separated list Previously it was encoded as a stringified Python list of strings, which is much harder to parse. This would go wrong if any of these capabilities have a comma in them, but I think that would be bad practice for keywords like this anyway. Contributes to issue CURA-8671. --- cura/Machines/Models/GlobalStacksModel.py | 2 +- .../UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cura/Machines/Models/GlobalStacksModel.py b/cura/Machines/Models/GlobalStacksModel.py index cc750f2244..e0dd298b98 100644 --- a/cura/Machines/Models/GlobalStacksModel.py +++ b/cura/Machines/Models/GlobalStacksModel.py @@ -123,7 +123,7 @@ class GlobalStacksModel(ListModel): if self._filter_online_only and not is_online: continue - capabilities = set(container_stack.getMetaDataEntry(META_CAPABILITIES, set())) + capabilities = set(container_stack.getMetaDataEntry(META_CAPABILITIES, "").split(",")) if set(self._filter_capabilities) - capabilities: # Not all required capabilities are met. continue diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index 862c583f79..8eecafd49c 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -129,7 +129,7 @@ class CloudOutputDeviceManager: if not parseBool(self._um_cloud_printers[device_id].getMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, "true")): 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, cluster_data.capabilities) + self._um_cloud_printers[device_id].setMetaDataEntry(META_CAPABILITIES, ",".join(cluster_data.capabilities)) self._onDevicesDiscovered(new_clusters) self._updateOnlinePrinters(all_clusters) From 6d9142579a4ad0fef058b34027d33a86e4e1f58a Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 3 Dec 2021 13:26:29 +0100 Subject: [PATCH 5/9] Add debuging repr to print more nicely Otherwise you just see when you try to print this. Contributes to issue CURA-8671. --- .../src/Models/Http/CloudClusterResponse.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterResponse.py b/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterResponse.py index 1af3d83964..1676fe1c2e 100644 --- a/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterResponse.py +++ b/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterResponse.py @@ -45,3 +45,10 @@ class CloudClusterResponse(BaseModel): super().validate() if not self.cluster_id: raise ValueError("cluster_id is required on CloudCluster") + + def __repr__(self) -> str: + """ + Convenience function for printing when debugging. + :return: A human-readable representation of the data in this object. + """ + return str({k: v for k, v in self.__dict__.items() if k in {"cluster_id", "host_guid", "host_name", "status", "is_online", "host_version", "host_internal_ip", "friendly_name", "printer_type", "printer_count", "capabilities"}}) From 6f8a4e93e74631e4ac4e779dbfc18aed203f1649 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 3 Dec 2021 13:26:51 +0100 Subject: [PATCH 6/9] Fix name of capability to filter on Contributes to issue CURA-8671. --- resources/qml/Preferences/Materials/MaterialsSyncDialog.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/Preferences/Materials/MaterialsSyncDialog.qml b/resources/qml/Preferences/Materials/MaterialsSyncDialog.qml index 9eaaa7bd6f..67d810e8f2 100644 --- a/resources/qml/Preferences/Materials/MaterialsSyncDialog.qml +++ b/resources/qml/Preferences/Materials/MaterialsSyncDialog.qml @@ -737,7 +737,7 @@ Window id: cloudPrinterList filterConnectionType: 3 //Only show cloud connections. filterOnlineOnly: true //Only show printers that are online. - filterCapabilities: ["import_materials"] //Only show printers that can receive the material profiles. + filterCapabilities: ["import_material"] //Only show printers that can receive the material profiles. } Cura.GlobalStacksModel { From 770adb2c020b16d38d35e3aedc61190fc925b978 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 3 Dec 2021 13:36:06 +0100 Subject: [PATCH 7/9] Don't store None as capabilities, but empty list This makes a few things easier. You could see None as being a signalling value to indicate that we don't know capabilities, but this signalling would get lost anyway in the serialising to the metadata. So just an empty list is fine for now. Contributes to issue CURA-8671. --- .../UM3NetworkPrinting/src/Models/Http/CloudClusterResponse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterResponse.py b/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterResponse.py index 1676fe1c2e..ce6dd1de4d 100644 --- a/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterResponse.py +++ b/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterResponse.py @@ -37,7 +37,7 @@ class CloudClusterResponse(BaseModel): self.friendly_name = friendly_name self.printer_type = printer_type self.printer_count = printer_count - self.capabilities = capabilities + self.capabilities = capabilities if capabilities is not None else [] super().__init__(**kwargs) # Validates the model, raising an exception if the model is invalid. From f0dc7a0eea918982f9a59f7d5d694716e88fc410 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 3 Dec 2021 13:59:01 +0100 Subject: [PATCH 8/9] Adjust message to include suggestion to update firmware Because the issue could also be that their firmware is outdated and doesn't yet support syncing material. Contributes to issue CURA-8671. --- resources/qml/Preferences/Materials/MaterialsSyncDialog.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/Preferences/Materials/MaterialsSyncDialog.qml b/resources/qml/Preferences/Materials/MaterialsSyncDialog.qml index 67d810e8f2..56873f3234 100644 --- a/resources/qml/Preferences/Materials/MaterialsSyncDialog.qml +++ b/resources/qml/Preferences/Materials/MaterialsSyncDialog.qml @@ -579,7 +579,7 @@ Window } Label { - text: catalog.i18nc("@text", "It seems like you don't have access to any printers connected to Digital Factory.") + text: catalog.i18nc("@text", "It seems like you don't have any compatible printers connected to Digital Factory. Make sure your printer is connected and it's running the latest firmware.") width: parent.width horizontalAlignment: Text.AlignHCenter wrapMode: Text.Wrap From 8f92f049d166f17fa70f66f15bef4f556e81def0 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 6 Dec 2021 09:43:53 +0100 Subject: [PATCH 9/9] Emit signal when property changed The signals weren't being emitted when the property was set. CURA-8671 --- cura/Machines/Models/GlobalStacksModel.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/cura/Machines/Models/GlobalStacksModel.py b/cura/Machines/Models/GlobalStacksModel.py index e0dd298b98..f27a1ec00b 100644 --- a/cura/Machines/Models/GlobalStacksModel.py +++ b/cura/Machines/Models/GlobalStacksModel.py @@ -52,8 +52,13 @@ class GlobalStacksModel(ListModel): self._updateDelayed() filterConnectionTypeChanged = pyqtSignal() + filterCapabilitiesChanged = pyqtSignal() + filterOnlineOnlyChanged = pyqtSignal() + def setFilterConnectionType(self, new_filter: Optional[ConnectionType]) -> None: - self._filter_connection_type = new_filter + if self._filter_connection_type != new_filter: + self._filter_connection_type = new_filter + self.filterConnectionTypeChanged.emit() @pyqtProperty(int, fset = setFilterConnectionType, notify = filterConnectionTypeChanged) def filterConnectionType(self) -> int: @@ -67,9 +72,10 @@ class GlobalStacksModel(ListModel): return -1 return self._filter_connection_type.value - filterOnlineOnlyChanged = pyqtSignal() def setFilterOnlineOnly(self, new_filter: bool) -> None: - self._filter_online_only = new_filter + if self._filter_online_only != new_filter: + self._filter_online_only = new_filter + self.filterOnlineOnlyChanged.emit() @pyqtProperty(bool, fset = setFilterOnlineOnly, notify = filterOnlineOnlyChanged) def filterOnlineOnly(self) -> bool: @@ -78,9 +84,10 @@ class GlobalStacksModel(ListModel): """ return self._filter_online_only - filterCapabilitiesChanged = pyqtSignal() def setFilterCapabilities(self, new_filter: List[str]) -> None: - self._filter_capabilities = new_filter + if self._filter_capabilities != new_filter: + self._filter_capabilities = new_filter + self.filterCapabilitiesChanged.emit() @pyqtProperty("QStringList", fset = setFilterCapabilities, notify = filterCapabilitiesChanged) def filterCapabilities(self) -> List[str]: