diff --git a/cura/Machines/Models/GlobalStacksModel.py b/cura/Machines/Models/GlobalStacksModel.py index 586bd11819..f27a1ec00b 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) @@ -50,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: @@ -65,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: @@ -76,6 +84,20 @@ class GlobalStacksModel(ListModel): """ return self._filter_online_only + def setFilterCapabilities(self, new_filter: List[str]) -> None: + 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]: + """ + 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 +130,10 @@ class GlobalStacksModel(ListModel): if self._filter_online_only and not is_online: continue + capabilities = set(container_stack.getMetaDataEntry(META_CAPABILITIES, "").split(",")) + 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/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/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) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index 5b1844e7cb..8eecafd49c 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, ",".join(cluster_data.capabilities)) self._onDevicesDiscovered(new_clusters) self._updateOnlinePrinters(all_clusters) diff --git a/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterResponse.py b/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterResponse.py index 1af3d83964..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. @@ -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"}}) 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..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 @@ -737,6 +737,7 @@ Window id: cloudPrinterList filterConnectionType: 3 //Only show cloud connections. filterOnlineOnly: true //Only show printers that are online. + filterCapabilities: ["import_material"] //Only show printers that can receive the material profiles. } Cura.GlobalStacksModel {