diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 9dd1168135..76efba20ab 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -216,7 +216,7 @@ class CuraApplication(QtApplication): self._machine_settings_manager = MachineSettingsManager(self, parent = self) - self._discovered_printer_model = DiscoveredPrintersModel(parent = self) + self._discovered_printer_model = DiscoveredPrintersModel(self, parent = self) self._first_start_machine_actions_model = FirstStartMachineActionsModel(self, parent = self) self._welcome_pages_model = WelcomePagesModel(self, parent = self) self._add_printer_pages_model = AddPrinterPagesModel(self, parent = self) diff --git a/cura/Machines/Models/DiscoveredPrintersModel.py b/cura/Machines/Models/DiscoveredPrintersModel.py index 7e0721fcbe..205b3be785 100644 --- a/cura/Machines/Models/DiscoveredPrintersModel.py +++ b/cura/Machines/Models/DiscoveredPrintersModel.py @@ -8,10 +8,11 @@ from PyQt5.QtCore import pyqtSlot, pyqtProperty, pyqtSignal, QObject from UM.i18n import i18nCatalog from UM.Logger import Logger from UM.Util import parseBool +from UM.OutputDevice.OutputDeviceManager import ManualDeviceAdditionAttempt if TYPE_CHECKING: from PyQt5.QtCore import QObject - + from cura.CuraApplication import CuraApplication from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice @@ -45,6 +46,10 @@ class DiscoveredPrinter(QObject): self._name = name self.nameChanged.emit() + @pyqtProperty(str, constant = True) + def address(self) -> str: + return self._ip_address + machineTypeChanged = pyqtSignal() @pyqtProperty(str, notify = machineTypeChanged) @@ -94,13 +99,72 @@ class DiscoveredPrinter(QObject): # class DiscoveredPrintersModel(QObject): - def __init__(self, parent: Optional["QObject"] = None) -> None: + def __init__(self, application: "CuraApplication", parent: Optional["QObject"] = None) -> None: super().__init__(parent) + self._application = application self._discovered_printer_by_ip_dict = dict() # type: Dict[str, DiscoveredPrinter] + self._plugin_for_manual_device = None + self._manual_device_address = "" + discoveredPrintersChanged = pyqtSignal() + @pyqtSlot(str) + def checkManualDevice(self, address: str) -> None: + if self.hasManualDeviceRequestInProgress: + Logger.log("i", "A manual device request for address [%s] is still in progress, do nothing", + self._manual_device_address) + return + + priority_order = [ + ManualDeviceAdditionAttempt.PRIORITY, + ManualDeviceAdditionAttempt.POSSIBLE, + ] # type: List[ManualDeviceAdditionAttempt] + + all_plugins_dict = self._application.getOutputDeviceManager().getAllOutputDevicePlugins() + + can_add_manual_plugins = [item for item in filter( + lambda plugin_item: plugin_item.canAddManualDevice(address) in priority_order, + all_plugins_dict.values())] + + if not can_add_manual_plugins: + Logger.log("d", "Could not find a plugin to accept adding %s manually via address.", address) + return + + plugin = max(can_add_manual_plugins, key = lambda p: priority_order.index(p.canAddManualDevice(address))) + self._plugin_for_manual_device = plugin + self._plugin_for_manual_device.addManualDevice(address, callback = self._onManualDeviceRequestFinished) + self._manual_device_address = address + self.hasManualDeviceRequestInProgressChanged.emit() + + @pyqtSlot() + def cancelCurrentManualDeviceRequest(self) -> None: + if self._manual_device_address: + self._plugin_for_manual_device.removeManualDevice(self._manual_device_address, address = self._manual_device_address) + self._manual_device_address = "" + self._plugin_for_manual_device = None + self.hasManualDeviceRequestInProgressChanged.emit() + self.manualDeviceRequestFinished.emit(False) + + hasManualDeviceRequestInProgressChanged = pyqtSignal() + + @pyqtProperty(bool, notify = hasManualDeviceRequestInProgressChanged) + def hasManualDeviceRequestInProgress(self) -> bool: + return self._manual_device_address != "" + + manualDeviceRequestFinished = pyqtSignal(bool, arguments = ["success"]) + + def _onManualDeviceRequestFinished(self, success: bool, address: str) -> None: + if address == self._manual_device_address: + self._manual_device_address = "" + self.hasManualDeviceRequestInProgressChanged.emit() + self.manualDeviceRequestFinished.emit(success) + + @pyqtProperty("QVariantMap", notify = discoveredPrintersChanged) + def discoveredPrintersByAddress(self) -> Dict[str, DiscoveredPrinter]: + return self._discovered_printer_by_ip_dict + @pyqtProperty(list, notify = discoveredPrintersChanged) def discoveredPrinters(self) -> List["DiscoveredPrinter"]: item_list = list( @@ -157,11 +221,3 @@ class DiscoveredPrintersModel(QObject): @pyqtSlot("QVariant") def createMachineFromDiscoveredPrinter(self, discovered_printer: "DiscoveredPrinter") -> None: discovered_printer.create_callback(discovered_printer.getKey()) - - @pyqtSlot(str) - def createMachineFromDiscoveredPrinterAddress(self, ip_address: str) -> None: - if ip_address not in self._discovered_printer_by_ip_dict: - Logger.log("i", "Key [%s] does not exist in the discovered printers list.", ip_address) - return - - self.createMachineFromDiscoveredPrinter(self._discovered_printer_by_ip_dict[ip_address]) diff --git a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py index 0c205b7b3c..c08b71993b 100644 --- a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py +++ b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py @@ -5,7 +5,7 @@ import os from queue import Queue from threading import Event, Thread from time import time -from typing import Optional, TYPE_CHECKING, Dict +from typing import Optional, TYPE_CHECKING, Dict, Callable from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange, ServiceInfo @@ -39,6 +39,19 @@ if TYPE_CHECKING: i18n_catalog = i18nCatalog("cura") +# +# Represents a request for adding a manual printer. It has the following fields: +# - address: The string of the (IP) address of the manual printer +# - callback: (Optional) Once the HTTP request to the printer to get printer information is done, whether successful +# or not, this callback will be invoked to notify about the result. The callback must have a signature of +# func(success: bool, address: str) -> None +# +class ManualPrinterRequest: + def __init__(self, address: str, callback: Optional[Callable[[bool, str], None]] = None) -> None: + self.address = address + self.callback = callback + + ## This plugin handles the connection detection & creation of output device objects for the UM3 printer. # Zero-Conf is used to detect printers, which are saved in a dict. # If we discover a printer that has the same key as the active machine instance a connection is made. @@ -84,10 +97,12 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self._preferences.addPreference("um3networkprinting/manual_instances", "") # A comma-separated list of ip adresses or hostnames - self._manual_instances = self._preferences.getValue("um3networkprinting/manual_instances").split(",") + manual_instances = self._preferences.getValue("um3networkprinting/manual_instances").split(",") + self._manual_instances = {address: ManualPrinterRequest(address) + for address in manual_instances} # type: Dict[str, ManualPrinterRequest] # Store the last manual entry key - self._last_manual_entry_key = "" # type: str + self._last_manual_entry_key = "" # type: str # The zero-conf service changed requests are handled in a separate thread, so we can re-schedule the requests # which fail to get detailed service info. @@ -185,8 +200,6 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self.checkCloudFlowIsPossible(None) else: self.getOutputDeviceManager().removeOutputDevice(key) - if key.startswith("manual:"): - self.removeManualDeviceSignal.emit(self.getPluginId(), key, self._discovered_devices[key].address) def stop(self): if self._zero_conf is not None: @@ -198,7 +211,10 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): # This plugin should always be the fallback option (at least try it): return ManualDeviceAdditionAttempt.POSSIBLE - def removeManualDevice(self, key, address = None): + def removeManualDevice(self, key: str, address: Optional[str] = None) -> None: + if key not in self._discovered_devices and address is not None: + key = "manual:%s" % address + if key in self._discovered_devices: if not address: address = self._discovered_devices[key].ipAddress @@ -206,15 +222,19 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self.resetLastManualDevice() if address in self._manual_instances: - self._manual_instances.remove(address) - self._preferences.setValue("um3networkprinting/manual_instances", ",".join(self._manual_instances)) + manual_printer_request = self._manual_instances.pop(address) + self._preferences.setValue("um3networkprinting/manual_instances", ",".join(self._manual_instances.keys())) - self.removeManualDeviceSignal.emit(self.getPluginId(), key, address) + if manual_printer_request.callback is not None: + self._application.callLater(manual_printer_request.callback, False, address) - def addManualDevice(self, address): - if address not in self._manual_instances: - self._manual_instances.append(address) - self._preferences.setValue("um3networkprinting/manual_instances", ",".join(self._manual_instances)) + def addManualDevice(self, address: str, callback: Optional[Callable[[bool, str], None]] = None) -> None: + if address in self._manual_instances: + Logger.log("i", "Manual printer with address [%s] has already been added, do nothing", address) + return + + self._manual_instances[address] = ManualPrinterRequest(address, callback = callback) + self._preferences.setValue("um3networkprinting/manual_instances", ",".join(self._manual_instances.keys())) instance_name = "manual:%s" % address properties = { @@ -319,6 +339,11 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): Logger.log("e", "Something went wrong converting the JSON.") return + if address in self._manual_instances: + manual_printer_request = self._manual_instances[address] + if manual_printer_request.callback is not None: + self._application.callLater(manual_printer_request.callback, True, address) + has_cluster_capable_firmware = Version(system_info["firmware"]) > self._min_cluster_version instance_name = "manual:%s" % address properties = { @@ -362,10 +387,6 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self._onRemoveDevice(instance_name) self._onAddDevice(instance_name, address, properties) - if device and address in self._manual_instances: - self.getOutputDeviceManager().addOutputDevice(device) - self.addManualDeviceSignal.emit(self.getPluginId(), device.getId(), address, properties) - def _onRemoveDevice(self, device_id: str) -> None: device = self._discovered_devices.pop(device_id, None) if device: @@ -401,7 +422,9 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): device = ClusterUM3OutputDevice.ClusterUM3OutputDevice(name, address, properties) else: device = LegacyUM3OutputDevice.LegacyUM3OutputDevice(name, address, properties) - self._application.getDiscoveredPrintersModel().addDiscoveredPrinter(address, device.getId(), name, self._createMachineFromDiscoveredPrinter, properties[b"printer_type"].decode("utf-8"), device) + self._application.getDiscoveredPrintersModel().addDiscoveredPrinter( + address, device.getId(), properties[b"name"].decode("utf-8"), self._createMachineFromDiscoveredPrinter, + properties[b"printer_type"].decode("utf-8"), device) self._discovered_devices[device.getId()] = device self.discoveredDevicesChanged.emit() diff --git a/resources/qml/WelcomePages/AddPrinterByIpContent.qml b/resources/qml/WelcomePages/AddPrinterByIpContent.qml index f1b315697a..663d2fd12a 100644 --- a/resources/qml/WelcomePages/AddPrinterByIpContent.qml +++ b/resources/qml/WelcomePages/AddPrinterByIpContent.qml @@ -18,12 +18,13 @@ Item id: addPrinterByIpScreen - // Whether an IP address is currently being resolved. - property bool hasSentRequest: false - // Whether the IP address user entered can be resolved as a recognizable printer. - property bool haveConnection: false - // True when a request comes back, but the device hasn't responded. - property bool deviceUnresponsive: false + // If there's a manual address resolve request in progress. + property bool hasRequestInProgress: CuraApplication.getDiscoveredPrintersModel().hasManualDeviceRequestInProgress + // Indicates if a request has finished. + property bool hasRequestFinished: false + + property var discoveredPrinter: null + property var isPrinterDiscovered: discoveredPrinter != null Label { @@ -88,7 +89,7 @@ Item regExp: /[a-fA-F0-9\.\:]*/ } - enabled: { ! (addPrinterByIpScreen.hasSentRequest || addPrinterByIpScreen.haveConnection) } + enabled: { ! (addPrinterByIpScreen.hasRequestInProgress || addPrinterByIpScreen.isPrinterDiscovered) } onAccepted: addPrinterButton.clicked() } @@ -99,22 +100,25 @@ Item anchors.left: hostnameField.right anchors.leftMargin: UM.Theme.getSize("default_margin").width text: catalog.i18nc("@button", "Add") + enabled: !addPrinterByIpScreen.hasRequestInProgress && !addPrinterByIpScreen.hasRequestFinished onClicked: { - if (hostnameField.text.trim() != "") + const address = hostnameField.text.trim() + if (address == "") { - enabled = false; - addPrinterByIpScreen.deviceUnresponsive = false; - UM.OutputDeviceManager.addManualDevice(hostnameField.text, hostnameField.text); + return } - } - busy: !enabled && !addPrinterByIpScreen.hasSentRequest && !addPrinterByIpScreen.haveConnection - Connections - { - target: UM.OutputDeviceManager - onManualDeviceChanged: { addPrinterButton.enabled = ! UM.OutputDeviceManager.hasManualDevice } + // This address is already in the discovered printer model, no need to add a manual discovery. + if (CuraApplication.getDiscoveredPrintersModel().discoveredPrintersByAddress[address]) + { + addPrinterByIpScreen.discoveredPrinter = CuraApplication.getDiscoveredPrintersModel().discoveredPrintersByAddress[address] + return + } + + CuraApplication.getDiscoveredPrintersModel().checkManualDevice(address) } + busy: addPrinterByIpScreen.hasRequestInProgress } } @@ -133,14 +137,10 @@ Item color: UM.Theme.getColor("text") renderType: Text.NativeRendering - visible: - { - (addPrinterByIpScreen.hasSentRequest && ! addPrinterByIpScreen.haveConnection) - || addPrinterByIpScreen.deviceUnresponsive - } + visible: addPrinterByIpScreen.hasRequestInProgress || (addPrinterByIpScreen.hasRequestFinished && !addPrinterByIpScreen.isPrinterDiscovered) text: { - if (addPrinterByIpScreen.deviceUnresponsive) + if (addPrinterByIpScreen.hasRequestFinished) { catalog.i18nc("@label", "Could not connect to device.") } @@ -157,7 +157,7 @@ Item anchors.top: parent.top anchors.margins: UM.Theme.getSize("default_margin").width - visible: addPrinterByIpScreen.haveConnection && ! addPrinterByIpScreen.deviceUnresponsive + visible: addPrinterByIpScreen.isPrinterDiscovered Label { @@ -167,7 +167,7 @@ Item color: UM.Theme.getColor("text") renderType: Text.NativeRendering - text: "???" + text: !addPrinterByIpScreen.isPrinterDiscovered ? "???" : addPrinterByIpScreen.discoveredPrinter.name } GridLayout @@ -188,7 +188,7 @@ Item Label { id: typeText - text: "?" + text: !addPrinterByIpScreen.isPrinterDiscovered ? "?" : addPrinterByIpScreen.discoveredPrinter.readableMachineType font: UM.Theme.getFont("default") color: UM.Theme.getColor("text") renderType: Text.NativeRendering @@ -204,7 +204,7 @@ Item Label { id: firmwareText - text: "0.0.0.0" + text: !addPrinterByIpScreen.isPrinterDiscovered ? "0.0.0.0" : addPrinterByIpScreen.discoveredPrinter.device.getProperty("firmware_version") font: UM.Theme.getFont("default") color: UM.Theme.getColor("text") renderType: Text.NativeRendering @@ -220,52 +220,25 @@ Item Label { id: addressText - text: "0.0.0.0" + text: !addPrinterByIpScreen.isPrinterDiscovered ? "0.0.0.0" : addPrinterByIpScreen.discoveredPrinter.address font: UM.Theme.getFont("default") color: UM.Theme.getColor("text") renderType: Text.NativeRendering } - - Connections - { - target: UM.OutputDeviceManager - onManualDeviceChanged: - { - if (UM.OutputDeviceManager.hasManualDevice) - { - const type_id = UM.OutputDeviceManager.manualDeviceProperty("printer_type") - var readable_type = Cura.MachineManager.getMachineTypeNameFromId(type_id) - readable_type = (readable_type != "") ? readable_type : catalog.i18nc("@label", "Unknown") - typeText.text = readable_type - firmwareText.text = UM.OutputDeviceManager.manualDeviceProperty("firmware_version") - addressText.text = UM.OutputDeviceManager.manualDeviceProperty("address") - } - else - { - typeText.text = "" - firmwareText.text = "" - addressText.text = "" - } - } - } } Connections { - target: UM.OutputDeviceManager - onManualDeviceChanged: + target: CuraApplication.getDiscoveredPrintersModel() + onManualDeviceRequestFinished: { - if (UM.OutputDeviceManager.hasManualDevice) + var discovered_printers_model = CuraApplication.getDiscoveredPrintersModel() + var printer = discovered_printers_model.discoveredPrintersByAddress[hostnameField.text] + if (printer) { - printerNameLabel.text = UM.OutputDeviceManager.manualDeviceProperty("name") - addPrinterByIpScreen.haveConnection = true - } - else - { - addPrinterByIpScreen.hasSentRequest = false - addPrinterByIpScreen.haveConnection = false - addPrinterByIpScreen.deviceUnresponsive = true + addPrinterByIpScreen.discoveredPrinter = printer } + addPrinterByIpScreen.hasRequestFinished = true } } } @@ -279,7 +252,11 @@ Item anchors.left: parent.left anchors.bottom: parent.bottom text: catalog.i18nc("@button", "Back") - onClicked: base.showPreviousPage() + onClicked: + { + CuraApplication.getDiscoveredPrintersModel().cancelCurrentManualDeviceRequest() + base.showPreviousPage() + } } Cura.PrimaryButton @@ -290,12 +267,10 @@ Item text: catalog.i18nc("@button", "Connect") onClicked: { - CuraApplication.getDiscoveredPrintersModel().createMachineFromDiscoveredPrinterAddress( - UM.OutputDeviceManager.manualDeviceProperty("address")) - UM.OutputDeviceManager.setActiveDevice(UM.OutputDeviceManager.manualDeviceProperty("device_id")) + CuraApplication.getDiscoveredPrintersModel().createMachineFromDiscoveredPrinter(discoveredPrinter) base.showNextPage() } - enabled: addPrinterByIpScreen.haveConnection + enabled: addPrinterByIpScreen.hasRequestFinished && addPrinterByIpScreen.isPrinterDiscovered } }