diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py index 688538522e..fb8e9d9408 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py @@ -58,6 +58,14 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice): # Therefore we create a private signal used to trigger the printersChanged signal. _clusterPrintersChanged = pyqtSignal() + # Map of Cura Connect machine_variant field to Cura machine types. + # Needed for printer discovery stack creation. + _host_machine_variant_to_machine_type_map = { + "Ultimaker 3": "ultimaker3", + "Ultimaker 3 Extended": "ultimaker3_extended", + "Ultimaker S5": "ultimaker_s5" + } + ## Creates a new cloud output device # \param api_client: The client that will run the API calls # \param cluster: The device response received from the cloud API. @@ -68,10 +76,10 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice): # Because the cloud connection does not off all of these, we manually construct this version here. # An example of why this is needed is the selection of the compatible file type when exporting the tool path. properties = { - b"address": b"", - b"name": cluster.host_name.encode() if cluster.host_name else b"", + 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": b"" + b"cluster_size": 1 # cloud devices are always clusters of at least one } super().__init__(device_id = cluster.cluster_id, address = "", @@ -95,6 +103,7 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice): # We keep track of which printer is visible in the monitor page. self._active_printer = None # type: Optional[PrinterOutputModel] + self._host_machine_type = "" # Properties to populate later on with received cloud data. self._print_jobs = [] # type: List[UM3PrintJobOutputModel] @@ -234,6 +243,9 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice): def _updatePrinters(self, printers: List[CloudClusterPrinterStatus]) -> None: previous = {p.key: p for p in self._printers} # type: Dict[str, PrinterOutputModel] received = {p.uuid: p for p in printers} # type: Dict[str, CloudClusterPrinterStatus] + + # We need the machine type of the host (1st list entry) to allow discovery to work. + self._host_machine_type = printers[0].machine_variant removed_printers, added_printers, updated_printers = findChanges(previous, received) @@ -358,6 +370,19 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice): ).show() self.writeFinished.emit() + ## Gets the printer type of the cluster host. Falls back to the printer type in the device properties. + @pyqtProperty(str, notify=_clusterPrintersChanged) + def printerType(self) -> str: + if self._printers and self._host_machine_type in self._host_machine_variant_to_machine_type_map: + return self._host_machine_variant_to_machine_type_map[self._host_machine_type] + return super().printerType + + ## Gets the number of printers in the cluster. + # We use a minimum of 1 because cloud devices are always a cluster and printer discovery needs it. + @pyqtProperty(int, notify = _clusterPrintersChanged) + def clusterSize(self) -> int: + return max(1, len(self._printers)) + ## Gets the remote printers. @pyqtProperty("QVariantList", notify=_clusterPrintersChanged) def printers(self) -> List[PrinterOutputModel]: @@ -375,10 +400,6 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice): self._active_printer = printer self.activePrinterChanged.emit() - @pyqtProperty(int, notify = _clusterPrintersChanged) - def clusterSize(self) -> int: - return len(self._printers) - ## Get remote print jobs. @pyqtProperty("QVariantList", notify = printJobsChanged) def printJobs(self) -> List[UM3PrintJobOutputModel]: diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index 680caa568a..19ec34a6bb 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -7,7 +7,7 @@ from PyQt5.QtCore import QTimer from UM import i18nCatalog from UM.Logger import Logger from UM.Message import Message -from UM.Signal import Signal, signalemitter +from UM.Signal import Signal from cura.API import Account from cura.CuraApplication import CuraApplication from cura.Settings.GlobalStack import GlobalStack @@ -81,25 +81,61 @@ class CloudOutputDeviceManager: Logger.log("d", "Removed: %s, added: %s, updates: %s", len(removed_devices), len(added_clusters), len(updates)) # Remove output devices that are gone - for removed_cluster in removed_devices: - if removed_cluster.isConnected(): - removed_cluster.disconnect() - removed_cluster.close() - self._output_device_manager.removeOutputDevice(removed_cluster.key) - self.removedCloudCluster.emit(removed_cluster) - del self._remote_clusters[removed_cluster.key] + for device in removed_devices: + if device.isConnected(): + device.disconnect() + device.close() + self._output_device_manager.removeOutputDevice(device.key) + self._application.getDiscoveredPrintersModel().removeDiscoveredPrinter(device.key) + self.removedCloudCluster.emit(device) + del self._remote_clusters[device.key] # Add an output device for each new remote cluster. # We only add when is_online as we don't want the option in the drop down if the cluster is not online. - for added_cluster in added_clusters: - device = CloudOutputDevice(self._api, added_cluster) - self._remote_clusters[added_cluster.cluster_id] = device - self.addedCloudCluster.emit(added_cluster) + for cluster in added_clusters: + device = CloudOutputDevice(self._api, cluster) + self._remote_clusters[cluster.cluster_id] = device + self._application.getDiscoveredPrintersModel().addDiscoveredPrinter( + cluster.cluster_id, + device.key, + cluster.friendly_name, + self._createMachineFromDiscoveredPrinter, + device.printerType, + device + ) + self.addedCloudCluster.emit(cluster) + # Update the output devices for device, cluster in updates: device.clusterData = cluster + self._application.getDiscoveredPrintersModel().updateDiscoveredPrinter( + cluster.cluster_id, + cluster.friendly_name, + device.printerType, + ) self._connectToActiveMachine() + + def _createMachineFromDiscoveredPrinter(self, key: str) -> None: + device = self._remote_clusters[key] # type: CloudOutputDevice + if not device: + Logger.log("e", "Could not find discovered device with key [%s]", key) + return + + group_name = device.clusterData.friendly_name + machine_type_id = device.printerType + + Logger.log("i", "Creating machine from cloud device with key = [%s], group name = [%s], printer type = [%s]", + key, group_name, machine_type_id) + + # The newly added machine is automatically activated. + self._application.getMachineManager().addMachine(machine_type_id, group_name) + active_machine = CuraApplication.getInstance().getGlobalContainerStack() + if not active_machine: + return + + active_machine.setMetaDataEntry(self.META_CLUSTER_ID, device.key) + self._connectToOutputDevice(device, active_machine) ## Callback for when the active machine was changed by the user or a new remote cluster was found. def _connectToActiveMachine(self) -> None: diff --git a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterResponse.py b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterResponse.py index 48a4d5f031..5549da02aa 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterResponse.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/Models/CloudClusterResponse.py @@ -16,7 +16,8 @@ class CloudClusterResponse(BaseCloudModel): # \param status: The status of the cluster authentication (active or inactive). # \param host_version: The firmware version of the cluster host. This is where the Stardust client is running on. 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, **kwargs) -> None: + host_internal_ip: Optional[str] = None, host_version: Optional[str] = None, + friendly_name: Optional[str] = None, **kwargs) -> None: self.cluster_id = cluster_id self.host_guid = host_guid self.host_name = host_name @@ -24,6 +25,7 @@ class CloudClusterResponse(BaseCloudModel): self.is_online = is_online self.host_version = host_version self.host_internal_ip = host_internal_ip + self.friendly_name = friendly_name super().__init__(**kwargs) # Validates the model, raising an exception if the model is invalid.