Browse Source

Merge pull request #10989 from Ultimaker/CURA-8671_dont_send_materials_to_um2c

[CURA-8671] Don't send materials to printers that can't receive them
Jaime van Kessel 3 years ago
parent
commit
a64aa6ef2d

+ 30 - 4
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)

+ 8 - 0
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
 

+ 3 - 0
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 == "":

+ 3 - 1
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]
 
 

+ 2 - 1
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)
 

+ 3 - 1
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)

+ 8 - 1
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"}})

+ 1 - 1
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'" }
     }

+ 2 - 1
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
     {