diff --git a/cura/API/Machines.py b/cura/API/Machines.py new file mode 100644 index 0000000000..de9f378d79 --- /dev/null +++ b/cura/API/Machines.py @@ -0,0 +1,139 @@ +# Copyright (c) 2019 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from typing import Optional, Dict, List, TYPE_CHECKING, Any +from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty +from UM.i18n import i18nCatalog +from UM.Logger import Logger +if TYPE_CHECKING: + from cura.CuraApplication import CuraApplication + from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice + +i18n_catalog = i18nCatalog("cura") + +## The account API provides a version-proof bridge to use Ultimaker Accounts +# +# Usage: +# ``` +# from cura.API import CuraAPI +# api = CuraAPI() +# api.machines.addOutputDeviceToCurrentMachine() +# ``` + +## Since Cura doesn't have a machine class, we're going to make a fake one to make our lives a +# little bit easier. +class Machine(): + def __init__(self) -> None: + self.hostname = "" # type: str + self.group_id = "" # type: str + self.group_name = "" # type: str + self.um_network_key = "" # type: str + self.configuration = {} # type: Dict[str, Any] + self.connection_types = [] # type: List[int] + +class Machines(QObject): + + def __init__(self, application: "CuraApplication", parent = None) -> None: + super().__init__(parent) + self._application = application + + @pyqtSlot(result="QVariantMap") + def getCurrentMachine(self) -> Machine: + fake_machine = Machine() # type: Machine + global_stack = self._application.getGlobalContainerStack() + if global_stack: + metadata = global_stack.getMetaData() + if "group_id" in metadata: + fake_machine.group_id = global_stack.getMetaDataEntry("group_id") + if "group_name" in metadata: + fake_machine.group_name = global_stack.getMetaDataEntry("group_name") + if "um_network_key" in metadata: + fake_machine.um_network_key = global_stack.getMetaDataEntry("um_network_key") + + fake_machine.connection_types = global_stack.configuredConnectionTypes + + return fake_machine + + ## Set the current machine's friendy name. + # This is the same as "group name" since we use "group" and "current machine" interchangeably. + # TODO: Maybe make this "friendly name" to distinguish from "hostname"? + @pyqtSlot(str) + def setCurrentMachineGroupName(self, group_name: str) -> None: + Logger.log("d", "Attempting to set the group name of the active machine to %s", group_name) + global_stack = self._application.getGlobalContainerStack() + if global_stack: + # Update a GlobalStacks in the same group with the new group name. + group_id = global_stack.getMetaDataEntry("group_id") + machine_manager = self._application.getMachineManager() + for machine in machine_manager.getMachinesInGroup(group_id): + machine.setMetaDataEntry("group_name", group_name) + + # Set the default value for "hidden", which is used when you have a group with multiple types of printers + global_stack.setMetaDataEntry("hidden", False) + + ## Set the current machine's configuration from an (optional) output device. + # If no output device is given, the first one available on the machine will be used. + # NOTE: Group and machine are used interchangeably. + # NOTE: This doesn't seem to be used anywhere. Maybe delete? + @pyqtSlot(QObject) + def updateCurrentMachineConfiguration(self, output_device: Optional["PrinterOutputDevice"]) -> None: + + if output_device is None: + machine_manager = self._application.getMachineManager() + output_device = machine_manager.printerOutputDevices[0] + + hotend_ids = output_device.hotendIds + for index in range(len(hotend_ids)): + output_device.hotendIdChanged.emit(index, hotend_ids[index]) + + material_ids = output_device.materialIds + for index in range(len(material_ids)): + output_device.materialIdChanged.emit(index, material_ids[index]) + + ## Add an output device to the current machine. + # In practice, this means: + # - Setting the output device's network key in the current machine's metadata + # - Adding the output device's connection type to the current machine's configured connection + # types. + # TODO: CHANGE TO HOSTNAME + @pyqtSlot(QObject) + def addOutputDeviceToCurrentMachine(self, output_device: "PrinterOutputDevice") -> None: + if not output_device: + return + Logger.log("d", + "Attempting to set the network key of the active machine to %s", + output_device.key) + global_stack = self._application.getGlobalContainerStack() + if not global_stack: + return + metadata = global_stack.getMetaData() + + # Global stack already had a connection, but it's changed. + if "um_network_key" in metadata: + old_network_key = metadata["um_network_key"] + + # Since we might have a bunch of hidden stacks, we also need to change it there. + metadata_filter = {"um_network_key": old_network_key} + containers = self._application.getContainerRegistry().findContainerStacks( + type = "machine", **metadata_filter) + for container in containers: + container.setMetaDataEntry("um_network_key", output_device.key) + + # Delete old authentication data. + Logger.log("d", "Removing old authentication id %s for device %s", + global_stack.getMetaDataEntry("network_authentication_id", None), + output_device.key) + + container.removeMetaDataEntry("network_authentication_id") + container.removeMetaDataEntry("network_authentication_key") + + # Ensure that these containers do know that they are configured for the given + # connection type (can be more than one type; e.g. LAN & Cloud) + container.addConfiguredConnectionType(output_device.connectionType.value) + + else: # Global stack didn't have a connection yet, configure it. + global_stack.setMetaDataEntry("um_network_key", output_device.key) + global_stack.addConfiguredConnectionType(output_device.connectionType.value) + + return None + diff --git a/cura/API/__init__.py b/cura/API/__init__.py index b3e702263a..0a5eab9535 100644 --- a/cura/API/__init__.py +++ b/cura/API/__init__.py @@ -6,6 +6,7 @@ from PyQt5.QtCore import QObject, pyqtProperty from cura.API.Backups import Backups from cura.API.Interface import Interface +from cura.API.Machines import Machines from cura.API.Account import Account if TYPE_CHECKING: @@ -44,6 +45,9 @@ class CuraAPI(QObject): # Backups API self._backups = Backups(self._application) + # Machines API + self._machines = Machines(self._application) + # Interface API self._interface = Interface(self._application) @@ -58,6 +62,10 @@ class CuraAPI(QObject): def backups(self) -> "Backups": return self._backups + @pyqtProperty(QObject) + def machines(self) -> "Machines": + return self._machines + @property def interface(self) -> "Interface": return self._interface diff --git a/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml b/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml index ecec87ef02..f65f2526dd 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml @@ -27,13 +27,14 @@ Cura.MachineAction { var printerKey = base.selectedDevice.key var printerName = base.selectedDevice.name // TODO To change when the groups have a name - if (manager.getStoredKey() != printerKey) + if (Cura.API.machines.getCurrentMachine().um_network_key != printerKey) // TODO: change to hostname { // Check if there is another instance with the same key if (!manager.existsKey(printerKey)) { - manager.associateActiveMachineWithPrinterDevice(base.selectedDevice) - manager.setGroupName(printerName) // TODO To change when the groups have a name + Cura.API.machines.addOutputDeviceToCurrentMachine(base.selectedDevice) + Cura.API.machines.setCurrentMachineGroupName(printerName) // TODO To change when the groups have a name + manager.refreshConnections() completed() } else @@ -156,7 +157,7 @@ Cura.MachineAction var selectedKey = manager.getLastManualEntryKey() // If there is no last manual entry key, then we select the stored key (if any) if (selectedKey == "") - selectedKey = manager.getStoredKey() + selectedKey = Cura.API.machines.getCurrentMachine().um_network_key // TODO: change to host name for(var i = 0; i < model.length; i++) { if(model[i].key == selectedKey) { diff --git a/plugins/UM3NetworkPrinting/src/DiscoverUM3Action.py b/plugins/UM3NetworkPrinting/src/DiscoverUM3Action.py index b67f4d7185..5d608ed546 100644 --- a/plugins/UM3NetworkPrinting/src/DiscoverUM3Action.py +++ b/plugins/UM3NetworkPrinting/src/DiscoverUM3Action.py @@ -34,7 +34,10 @@ class DiscoverUM3Action(MachineAction): self.__additional_components_view = None #type: Optional[QObject] - CuraApplication.getInstance().engineCreatedSignal.connect(self._createAdditionalComponentsView) + self._application = CuraApplication.getInstance() + self._api = self._application.getCuraAPI() + + self._application.engineCreatedSignal.connect(self._createAdditionalComponentsView) self._last_zero_conf_event_time = time.time() #type: float @@ -50,7 +53,7 @@ class DiscoverUM3Action(MachineAction): def startDiscovery(self): if not self._network_plugin: Logger.log("d", "Starting device discovery.") - self._network_plugin = CuraApplication.getInstance().getOutputDeviceManager().getOutputDevicePlugin("UM3NetworkPrinting") + self._network_plugin = self._application.getOutputDeviceManager().getOutputDevicePlugin("UM3NetworkPrinting") self._network_plugin.discoveredDevicesChanged.connect(self._onDeviceDiscoveryChanged) self.discoveredDevicesChanged.emit() @@ -105,63 +108,27 @@ class DiscoverUM3Action(MachineAction): else: return [] - @pyqtSlot(str) - def setGroupName(self, group_name: str) -> None: - Logger.log("d", "Attempting to set the group name of the active machine to %s", group_name) - global_container_stack = CuraApplication.getInstance().getGlobalContainerStack() - if global_container_stack: - # Update a GlobalStacks in the same group with the new group name. - group_id = global_container_stack.getMetaDataEntry("group_id") - machine_manager = CuraApplication.getInstance().getMachineManager() - for machine in machine_manager.getMachinesInGroup(group_id): - machine.setMetaDataEntry("group_name", group_name) - - # Set the default value for "hidden", which is used when you have a group with multiple types of printers - global_container_stack.setMetaDataEntry("hidden", False) - + @pyqtSlot() + def refreshConnections(self) -> None: if self._network_plugin: - # Ensure that the connection states are refreshed. self._network_plugin.refreshConnections() - # Associates the currently active machine with the given printer device. The network connection information will be - # stored into the metadata of the currently active machine. - @pyqtSlot(QObject) - def associateActiveMachineWithPrinterDevice(self, printer_device: Optional["PrinterOutputDevice"]) -> None: - if self._network_plugin: - self._network_plugin.associateActiveMachineWithPrinterDevice(printer_device) - - @pyqtSlot(result = str) - def getStoredKey(self) -> str: - global_container_stack = CuraApplication.getInstance().getGlobalContainerStack() - if global_container_stack: - meta_data = global_container_stack.getMetaData() - if "um_network_key" in meta_data: - return global_container_stack.getMetaDataEntry("um_network_key") - - return "" - + # TODO: Improve naming + # TODO: CHANGE TO HOSTNAME @pyqtSlot(result = str) def getLastManualEntryKey(self) -> str: if self._network_plugin: return self._network_plugin.getLastManualDevice() return "" + # TODO: Better naming needed. Exists where? On the current machine? On all machines? + # TODO: CHANGE TO HOSTNAME @pyqtSlot(str, result = bool) def existsKey(self, key: str) -> bool: metadata_filter = {"um_network_key": key} containers = CuraContainerRegistry.getInstance().findContainerStacks(type="machine", **metadata_filter) return bool(containers) - @pyqtSlot() - def loadConfigurationFromPrinter(self) -> None: - machine_manager = CuraApplication.getInstance().getMachineManager() - hotend_ids = machine_manager.printerOutputDevices[0].hotendIds - for index in range(len(hotend_ids)): - machine_manager.printerOutputDevices[0].hotendIdChanged.emit(index, hotend_ids[index]) - material_ids = machine_manager.printerOutputDevices[0].materialIds - for index in range(len(material_ids)): - machine_manager.printerOutputDevices[0].materialIdChanged.emit(index, material_ids[index]) - def _createAdditionalComponentsView(self) -> None: Logger.log("d", "Creating additional ui components for UM3.") @@ -170,10 +137,10 @@ class DiscoverUM3Action(MachineAction): if not plugin_path: return path = os.path.join(plugin_path, "resources/qml/UM3InfoComponents.qml") - self.__additional_components_view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self}) + self.__additional_components_view = self._application.createQmlComponent(path, {"manager": self}) if not self.__additional_components_view: Logger.log("w", "Could not create ui components for UM3.") return # Create extra components - CuraApplication.getInstance().addAdditionalComponent("monitorButtons", self.__additional_components_view.findChild(QObject, "networkPrinterConnectButton")) + self._application.addAdditionalComponent("monitorButtons", self.__additional_components_view.findChild(QObject, "networkPrinterConnectButton")) diff --git a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py index 41c76dc4c0..fb2c6e9f35 100644 --- a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py +++ b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py @@ -67,11 +67,11 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): def __init__(self): super().__init__() - self._zero_conf = None self._zero_conf_browser = None self._application = CuraApplication.getInstance() + self._api = self._application.getCuraAPI() # Create a cloud output device manager that abstracts all cloud connection logic away. self._cloud_output_device_manager = CloudOutputDeviceManager() @@ -96,7 +96,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self._cluster_api_prefix = "/cluster-api/v" + self._cluster_api_version + "/" # Get list of manual instances from preferences - self._preferences = CuraApplication.getInstance().getPreferences() + self._preferences = self._application.getPreferences() self._preferences.addPreference("um3networkprinting/manual_instances", "") # A comma-separated list of ip adresses or hostnames @@ -116,7 +116,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self._service_changed_request_thread = Thread(target=self._handleOnServiceChangedRequests, daemon=True) self._service_changed_request_thread.start() - self._account = self._application.getCuraAPI().account + self._account = self._api.account # Check if cloud flow is possible when user logs in self._account.loginStateChanged.connect(self.checkCloudFlowIsPossible) @@ -167,10 +167,11 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): for address in self._manual_instances: if address: self.addManualDevice(address) - self.resetLastManualDevice() - + self.resetLastManu + + # TODO: CHANGE TO HOSTNAME def refreshConnections(self): - active_machine = CuraApplication.getInstance().getGlobalContainerStack() + active_machine = self._application.getGlobalContainerStack() if not active_machine: return @@ -197,7 +198,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): return if self._discovered_devices[key].isConnected(): # Sometimes the status changes after changing the global container and maybe the device doesn't belong to this machine - um_network_key = CuraApplication.getInstance().getGlobalContainerStack().getMetaDataEntry("um_network_key") + um_network_key = self._application.getGlobalContainerStack().getMetaDataEntry("um_network_key") if key == um_network_key: self.getOutputDeviceManager().addOutputDevice(self._discovered_devices[key]) self.checkCloudFlowIsPossible(None) @@ -272,39 +273,14 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): key, group_name, machine_type_id) self._application.getMachineManager().addMachine(machine_type_id, group_name) + # connect the new machine to that network printer - self.associateActiveMachineWithPrinterDevice(discovered_device) + self._api.machines.addOutputDeviceToCurrentMachine(discovered_device) + # ensure that the connection states are refreshed. self.refreshConnections() - def associateActiveMachineWithPrinterDevice(self, printer_device: Optional["PrinterOutputDevice"]) -> None: - if not printer_device: - return - - Logger.log("d", "Attempting to set the network key of the active machine to %s", printer_device.key) - - machine_manager = CuraApplication.getInstance().getMachineManager() - global_container_stack = machine_manager.activeMachine - if not global_container_stack: - return - - for machine in machine_manager.getMachinesInGroup(global_container_stack.getMetaDataEntry("group_id")): - machine.setMetaDataEntry("um_network_key", printer_device.key) - machine.setMetaDataEntry("group_name", printer_device.name) - - # Delete old authentication data. - Logger.log("d", "Removing old authentication id %s for device %s", - global_container_stack.getMetaDataEntry("network_authentication_id", None), printer_device.key) - - machine.removeMetaDataEntry("network_authentication_id") - machine.removeMetaDataEntry("network_authentication_key") - - # Ensure that these containers do know that they are configured for network connection - machine.addConfiguredConnectionType(printer_device.connectionType.value) - - self.refreshConnections() - - def _checkManualDevice(self, address: str) -> "QNetworkReply": + def _checkManualDevice(self, address: str) -> Optional[QNetworkReply]: # Check if a UM3 family device exists at this address. # If a printer responds, it will replace the preliminary printer created above # origin=manual is for tracking back the origin of the call @@ -312,6 +288,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): name_request = QNetworkRequest(url) return self._network_manager.get(name_request) + ## This is the function which handles the above network request's reply when it comes back. def _onNetworkRequestFinished(self, reply: "QNetworkReply") -> None: reply_url = reply.url().toString() @@ -426,7 +403,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self._discovered_devices[device.getId()] = device self.discoveredDevicesChanged.emit() - global_container_stack = CuraApplication.getInstance().getGlobalContainerStack() + global_container_stack = self._application.getGlobalContainerStack() if global_container_stack and device.getId() == global_container_stack.getMetaDataEntry("um_network_key"): # Ensure that the configured connection type is set. global_container_stack.addConfiguredConnectionType(device.connectionType.value) @@ -446,7 +423,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self._service_changed_request_event.wait(timeout = 5.0) # Stop if the application is shutting down - if CuraApplication.getInstance().isShuttingDown(): + if self._application.isShuttingDown(): return self._service_changed_request_event.clear()