diff --git a/cura/API/Machines.py b/cura/API/Machines.py deleted file mode 100644 index de9f378d79..0000000000 --- a/cura/API/Machines.py +++ /dev/null @@ -1,139 +0,0 @@ -# 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 0a5eab9535..b3e702263a 100644 --- a/cura/API/__init__.py +++ b/cura/API/__init__.py @@ -6,7 +6,6 @@ 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: @@ -45,9 +44,6 @@ class CuraAPI(QObject): # Backups API self._backups = Backups(self._application) - # Machines API - self._machines = Machines(self._application) - # Interface API self._interface = Interface(self._application) @@ -62,10 +58,6 @@ 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/cura/UI/ObjectsModel.py b/cura/UI/ObjectsModel.py index 446c4c1345..3b38e3b458 100644 --- a/cura/UI/ObjectsModel.py +++ b/cura/UI/ObjectsModel.py @@ -3,7 +3,7 @@ from collections import namedtuple import re -from typing import Any, Dict, List +from typing import Any, Dict, List, Optional, Union from PyQt5.QtCore import QTimer, Qt @@ -18,6 +18,20 @@ from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") +# Simple convenience class to keep stuff together. Since we're still stuck on python 3.5, we can't use the full +# typed named tuple, so we have to do it like this. +# Once we are at python 3.6, feel free to change this to a named tuple. +class _NodeInfo: + def __init__(self, index_to_node: Optional[Dict[int, SceneNode]] = None, nodes_to_rename: Optional[List[SceneNode]] = None, is_group: bool = False) -> None: + if index_to_node is None: + index_to_node = {} + if nodes_to_rename is None: + nodes_to_rename = [] + self.index_to_node = index_to_node # type: Dict[int, SceneNode] + self.nodes_to_rename = nodes_to_rename # type: List[SceneNode] + self.is_group = is_group # type: bool + + ## Keep track of all objects in the project class ObjectsModel(ListModel): NameRole = Qt.UserRole + 1 @@ -64,28 +78,24 @@ class ObjectsModel(ListModel): naming_regex = re.compile("^(.+)\(([0-9]+)\)$") - NodeInfo = namedtuple("NodeInfo", ["index_to_node", "nodes_to_rename", "is_group"]) - name_to_node_info_dict = {} # type: Dict[str, NodeInfo] + name_to_node_info_dict = {} # type: Dict[str, _NodeInfo] group_name_template = catalog.i18nc("@label", "Group #{group_nr}") group_name_prefix = group_name_template.split("#")[0] for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()): # type: ignore - if not isinstance(node, SceneNode): + is_group = bool(node.callDecoration("isGroup")) + if not node.callDecoration("isSliceable") and not is_group: continue - if (not node.getMeshData() and not node.callDecoration("getLayerData")) and not node.callDecoration("isGroup"): - continue - + parent = node.getParent() if parent and parent.callDecoration("isGroup"): continue # Grouped nodes don't need resetting as their parent (the group) is resetted) - if not node.callDecoration("isSliceable") and not node.callDecoration("isGroup"): - continue + node_build_plate_number = node.callDecoration("getBuildPlateNumber") if filter_current_build_plate and node_build_plate_number != active_build_plate_number: continue - is_group = bool(node.callDecoration("isGroup")) force_rename = False if not is_group: # Handle names for individual nodes @@ -116,39 +126,37 @@ class ObjectsModel(ListModel): # Keep track of 2 things: # - known indices for nodes which doesn't need to be renamed # - a list of nodes that need to be renamed. When renaming then, we should avoid using the known indices. - name_to_node_info_dict[original_name] = NodeInfo(index_to_node = {}, - nodes_to_rename = [], - is_group = is_group) - node_info_dict = name_to_node_info_dict[original_name] - if not force_rename and name_index not in node_info_dict.index_to_node: - node_info_dict.index_to_node[name_index] = node + name_to_node_info_dict[original_name] = _NodeInfo(is_group = is_group) + node_info = name_to_node_info_dict[original_name] + if not force_rename and name_index not in node_info.index_to_node: + node_info.index_to_node[name_index] = node else: - node_info_dict.nodes_to_rename.append(node) + node_info.nodes_to_rename.append(node) # Go through all names and rename the nodes that need to be renamed. - node_rename_list = [] # type: List[Dict[str, Any]] - for name, node_info_dict in name_to_node_info_dict.items(): + node_rename_list = [] # type: List[Dict[str, Union[str, SceneNode]]] + for name, node_info in name_to_node_info_dict.items(): # First add the ones that do not need to be renamed. - for node in node_info_dict.index_to_node.values(): + for node in node_info.index_to_node.values(): node_rename_list.append({"node": node}) # Generate new names for the nodes that need to be renamed current_index = 0 - for node in node_info_dict.nodes_to_rename: + for node in node_info.nodes_to_rename: current_index += 1 - while current_index in node_info_dict.index_to_node: + while current_index in node_info.index_to_node: current_index += 1 - if not node_info_dict.is_group: + if not node_info.is_group: new_group_name = "{0}({1})".format(name, current_index) else: new_group_name = "{0}#{1}".format(name, current_index) node_rename_list.append({"node": node, "new_name": new_group_name}) - for node_info in node_rename_list: - node = node_info["node"] - new_name = node_info.get("new_name") + for rename_dict in node_rename_list: + node = rename_dict["node"] + new_name = rename_dict.get("new_name") if hasattr(node, "isOutsideBuildArea"): is_outside_build_area = node.isOutsideBuildArea() # type: ignore @@ -174,5 +182,3 @@ class ObjectsModel(ListModel): nodes = sorted(nodes, key=lambda n: n["name"]) self.setItems(nodes) - - self.itemsChanged.emit() diff --git a/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml b/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml index f65f2526dd..ecec87ef02 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml @@ -27,14 +27,13 @@ Cura.MachineAction { var printerKey = base.selectedDevice.key var printerName = base.selectedDevice.name // TODO To change when the groups have a name - if (Cura.API.machines.getCurrentMachine().um_network_key != printerKey) // TODO: change to hostname + if (manager.getStoredKey() != printerKey) { // Check if there is another instance with the same key if (!manager.existsKey(printerKey)) { - Cura.API.machines.addOutputDeviceToCurrentMachine(base.selectedDevice) - Cura.API.machines.setCurrentMachineGroupName(printerName) // TODO To change when the groups have a name - manager.refreshConnections() + manager.associateActiveMachineWithPrinterDevice(base.selectedDevice) + manager.setGroupName(printerName) // TODO To change when the groups have a name completed() } else @@ -157,7 +156,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 = Cura.API.machines.getCurrentMachine().um_network_key // TODO: change to host name + selectedKey = manager.getStoredKey() 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 5d608ed546..b67f4d7185 100644 --- a/plugins/UM3NetworkPrinting/src/DiscoverUM3Action.py +++ b/plugins/UM3NetworkPrinting/src/DiscoverUM3Action.py @@ -34,10 +34,7 @@ class DiscoverUM3Action(MachineAction): self.__additional_components_view = None #type: Optional[QObject] - self._application = CuraApplication.getInstance() - self._api = self._application.getCuraAPI() - - self._application.engineCreatedSignal.connect(self._createAdditionalComponentsView) + CuraApplication.getInstance().engineCreatedSignal.connect(self._createAdditionalComponentsView) self._last_zero_conf_event_time = time.time() #type: float @@ -53,7 +50,7 @@ class DiscoverUM3Action(MachineAction): def startDiscovery(self): if not self._network_plugin: Logger.log("d", "Starting device discovery.") - self._network_plugin = self._application.getOutputDeviceManager().getOutputDevicePlugin("UM3NetworkPrinting") + self._network_plugin = CuraApplication.getInstance().getOutputDeviceManager().getOutputDevicePlugin("UM3NetworkPrinting") self._network_plugin.discoveredDevicesChanged.connect(self._onDeviceDiscoveryChanged) self.discoveredDevicesChanged.emit() @@ -108,27 +105,63 @@ class DiscoverUM3Action(MachineAction): else: return [] - @pyqtSlot() - def refreshConnections(self) -> None: + @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) + if self._network_plugin: + # Ensure that the connection states are refreshed. self._network_plugin.refreshConnections() - # TODO: Improve naming - # TODO: CHANGE TO HOSTNAME + # 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 "" + @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.") @@ -137,10 +170,10 @@ class DiscoverUM3Action(MachineAction): if not plugin_path: return path = os.path.join(plugin_path, "resources/qml/UM3InfoComponents.qml") - self.__additional_components_view = self._application.createQmlComponent(path, {"manager": self}) + self.__additional_components_view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self}) if not self.__additional_components_view: Logger.log("w", "Could not create ui components for UM3.") return # Create extra components - self._application.addAdditionalComponent("monitorButtons", self.__additional_components_view.findChild(QObject, "networkPrinterConnectButton")) + CuraApplication.getInstance().addAdditionalComponent("monitorButtons", self.__additional_components_view.findChild(QObject, "networkPrinterConnectButton")) diff --git a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py index 99b29cda0a..c38f7130bc 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 = self._application.getPreferences() + self._preferences = CuraApplication.getInstance().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._api.account + self._account = self._application.getCuraAPI().account # Check if cloud flow is possible when user logs in self._account.loginStateChanged.connect(self.checkCloudFlowIsPossible) @@ -171,7 +171,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): # TODO: CHANGE TO HOSTNAME def refreshConnections(self): - active_machine = self._application.getGlobalContainerStack() + active_machine = CuraApplication.getInstance().getGlobalContainerStack() if not active_machine: return @@ -198,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 = self._application.getGlobalContainerStack().getMetaDataEntry("um_network_key") + um_network_key = CuraApplication.getInstance().getGlobalContainerStack().getMetaDataEntry("um_network_key") if key == um_network_key: self.getOutputDeviceManager().addOutputDevice(self._discovered_devices[key]) self.checkCloudFlowIsPossible(None) @@ -273,14 +273,39 @@ 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._api.machines.addOutputDeviceToCurrentMachine(discovered_device) - + self.associateActiveMachineWithPrinterDevice(discovered_device) # ensure that the connection states are refreshed. self.refreshConnections() - def _checkManualDevice(self, address: str) -> Optional[QNetworkReply]: + 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": # 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 @@ -288,7 +313,6 @@ 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() @@ -403,7 +427,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self._discovered_devices[device.getId()] = device self.discoveredDevicesChanged.emit() - global_container_stack = self._application.getGlobalContainerStack() + global_container_stack = CuraApplication.getInstance().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) @@ -423,7 +447,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self._service_changed_request_event.wait(timeout = 5.0) # Stop if the application is shutting down - if self._application.isShuttingDown(): + if CuraApplication.getInstance().isShuttingDown(): return self._service_changed_request_event.clear()