From d16da3da3ad207915b9d55abb37f902461249bf0 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Mon, 29 Apr 2019 14:33:48 +0200 Subject: [PATCH 01/21] Add group_id and fix remove printer CURA-6483 - Added a unique group_id (a GUID) to all created GlobalStack. - Changed version upgrade to generate unique group_ids for GlobalStacks. - RemoveMachine() now uses group_ids to remove hidden GlobalStacks. --- cura/Settings/GlobalStack.py | 3 +++ cura/Settings/MachineManager.py | 7 ++++--- .../VersionUpgrade40to41/VersionUpgrade40to41.py | 9 +++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py index 3ec73972dd..a8e6f2f2c0 100755 --- a/cura/Settings/GlobalStack.py +++ b/cura/Settings/GlobalStack.py @@ -4,6 +4,8 @@ from collections import defaultdict import threading from typing import Any, Dict, Optional, Set, TYPE_CHECKING, List +import uuid + from PyQt5.QtCore import pyqtProperty, pyqtSlot, pyqtSignal from UM.Decorators import override @@ -33,6 +35,7 @@ class GlobalStack(CuraContainerStack): super().__init__(container_id) self.setMetaDataEntry("type", "machine") # For backward compatibility + self.setMetaDataEntry("group_id", str(uuid.uuid4())) # Assign a new GlobalStack to a unique group by default self._extruders = {} # type: Dict[str, "ExtruderStack"] diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 24d63d5164..5db0a702a1 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -797,7 +797,6 @@ class MachineManager(QObject): self.setActiveMachine(other_machine_stacks[0]["id"]) metadata = CuraContainerRegistry.getInstance().findContainerStacksMetadata(id = machine_id)[0] - network_key = metadata.get("um_network_key", None) ExtruderManager.getInstance().removeMachineExtruders(machine_id) containers = CuraContainerRegistry.getInstance().findInstanceContainersMetadata(type = "user", machine = machine_id) for container in containers: @@ -805,8 +804,9 @@ class MachineManager(QObject): CuraContainerRegistry.getInstance().removeContainer(machine_id) # If the printer that is being removed is a network printer, the hidden printers have to be also removed - if network_key: - metadata_filter = {"um_network_key": network_key} + group_id = metadata.get("group_id", None) + if group_id: + metadata_filter = {"group_id": group_id} hidden_containers = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter) if hidden_containers: # This reuses the method and remove all printers recursively @@ -1366,6 +1366,7 @@ class MachineManager(QObject): new_machine = CuraStackBuilder.createMachine(machine_definition_id + "_sync", machine_definition_id) if not new_machine: return + new_machine.setMetaDataEntry("group_id", self._global_container_stack.getMetaDataEntry("group_id")) new_machine.setMetaDataEntry("um_network_key", self.activeMachineNetworkKey()) new_machine.setMetaDataEntry("group_name", self.activeMachineNetworkGroupName) new_machine.setMetaDataEntry("hidden", False) diff --git a/plugins/VersionUpgrade/VersionUpgrade40to41/VersionUpgrade40to41.py b/plugins/VersionUpgrade/VersionUpgrade40to41/VersionUpgrade40to41.py index 03272e63d5..845e9cbb8c 100644 --- a/plugins/VersionUpgrade/VersionUpgrade40to41/VersionUpgrade40to41.py +++ b/plugins/VersionUpgrade/VersionUpgrade40to41/VersionUpgrade40to41.py @@ -3,6 +3,7 @@ import configparser import io +import uuid from typing import Dict, List, Tuple from UM.VersionUpgrade import VersionUpgrade @@ -18,6 +19,7 @@ _renamed_quality_profiles = { "gmax15plus_pla_very_thick": "gmax15plus_global_very_thick" } # type: Dict[str, str] + ## Upgrades configurations from the state they were in at version 4.0 to the # state they should be in at version 4.1. class VersionUpgrade40to41(VersionUpgrade): @@ -95,6 +97,13 @@ class VersionUpgrade40to41(VersionUpgrade): if parser["containers"]["4"] in _renamed_quality_profiles: parser["containers"]["4"] = _renamed_quality_profiles[parser["containers"]["4"]] + # Assign a GlobalStack to a unique group_id. If the GlobalStack has a UM network connection, use the UM network + # key as the group_id. + if "um_network_key" in parser["metadata"]: + parser["metadata"]["group_id"] = parser["metadata"]["um_network_key"] + elif "group_id" not in parser["metadata"]: + parser["metadata"]["group_id"] = str(uuid.uuid4()) + result = io.StringIO() parser.write(result) return [filename], [result.getvalue()] From bed13bf42b27d3858cb469e0e94cd44bba8531be Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Mon, 29 Apr 2019 15:04:09 +0200 Subject: [PATCH 02/21] Cleanup and make sure that group_name is set --- cura/Settings/MachineManager.py | 4 +++ .../src/DiscoverUM3Action.py | 21 ++++-------- .../src/UM3OutputDevicePlugin.py | 34 +++++++------------ 3 files changed, 22 insertions(+), 37 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 5db0a702a1..c40b67c2e4 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -1655,3 +1655,7 @@ class MachineManager(QObject): if results: machine_type_name = results[0]["name"] return machine_type_name + + # Gets all machines that belong to the given group_id. + def getMachinesInGroup(self, group_id: str) -> List["GlobalStack"]: + return self._container_registry.findContainerStacks(type = "machine", group_id = group_id) diff --git a/plugins/UM3NetworkPrinting/src/DiscoverUM3Action.py b/plugins/UM3NetworkPrinting/src/DiscoverUM3Action.py index 28e7b60a0e..b67f4d7185 100644 --- a/plugins/UM3NetworkPrinting/src/DiscoverUM3Action.py +++ b/plugins/UM3NetworkPrinting/src/DiscoverUM3Action.py @@ -110,14 +110,12 @@ class DiscoverUM3Action(MachineAction): 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: - meta_data = global_container_stack.getMetaData() - if "group_name" in meta_data: - previous_connect_group_name = meta_data["group_name"] - global_container_stack.setMetaDataEntry("group_name", group_name) - # Find all the places where there is the same group name and change it accordingly - self._replaceContainersMetadata(key = "group_name", value = previous_connect_group_name, new_value = group_name) - else: - global_container_stack.setMetaDataEntry("group_name", group_name) + # 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) @@ -125,13 +123,6 @@ class DiscoverUM3Action(MachineAction): # Ensure that the connection states are refreshed. self._network_plugin.refreshConnections() - ## Find all container stacks that has the pair 'key = value' in its metadata and replaces the value with 'new_value' - def _replaceContainersMetadata(self, key: str, value: str, new_value: str) -> None: - machines = CuraContainerRegistry.getInstance().findContainerStacks(type="machine") - for machine in machines: - if machine.getMetaDataEntry(key) == value: - machine.setMetaDataEntry(key, new_value) - # 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) diff --git a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py index f820b6244c..41c76dc4c0 100644 --- a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py +++ b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py @@ -283,34 +283,24 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): Logger.log("d", "Attempting to set the network key of the active machine to %s", printer_device.key) - global_container_stack = CuraApplication.getInstance().getGlobalContainerStack() + machine_manager = CuraApplication.getInstance().getMachineManager() + global_container_stack = machine_manager.activeMachine if not global_container_stack: return - meta_data = global_container_stack.getMetaData() + 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) - if "um_network_key" in meta_data: # Global stack already had a connection, but it's changed. - old_network_key = meta_data["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) + # 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) - for container in containers: - container.setMetaDataEntry("um_network_key", printer_device.key) + machine.removeMetaDataEntry("network_authentication_id") + machine.removeMetaDataEntry("network_authentication_key") - # 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) - - container.removeMetaDataEntry("network_authentication_id") - container.removeMetaDataEntry("network_authentication_key") - - # Ensure that these containers do know that they are configured for network connection - container.addConfiguredConnectionType(printer_device.connectionType.value) - - else: # Global stack didn't have a connection yet, configure it. - global_container_stack.setMetaDataEntry("um_network_key", printer_device.key) - global_container_stack.addConfiguredConnectionType(printer_device.connectionType.value) + # Ensure that these containers do know that they are configured for network connection + machine.addConfiguredConnectionType(printer_device.connectionType.value) self.refreshConnections() From defcba692745db35cf2e0b9705ff979e214b8e1a Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Mon, 29 Apr 2019 15:16:40 +0200 Subject: [PATCH 03/21] Fix that you can only cancel in WizardDialog CURA-6483 --- cura/UI/AddPrinterPagesModel.py | 1 + cura/UI/WelcomePagesModel.py | 2 ++ resources/qml/Cura.qml | 2 -- .../AddNetworkOrLocalPrinterContent.qml | 14 ++++++++++++++ resources/qml/WelcomePages/WizardDialog.qml | 17 ----------------- 5 files changed, 17 insertions(+), 19 deletions(-) diff --git a/cura/UI/AddPrinterPagesModel.py b/cura/UI/AddPrinterPagesModel.py index 55bb1500ba..d40da59b2a 100644 --- a/cura/UI/AddPrinterPagesModel.py +++ b/cura/UI/AddPrinterPagesModel.py @@ -15,6 +15,7 @@ class AddPrinterPagesModel(WelcomePagesModel): "page_url": self._getBuiltinWelcomePagePath("AddNetworkOrLocalPrinterContent.qml"), "next_page_id": "machine_actions", "next_page_button_text": self._catalog.i18nc("@action:button", "Add"), + "previous_page_button_text": self._catalog.i18nc("@action:button", "Cancel"), }) self._pages.append({"id": "add_printer_by_ip", "page_url": self._getBuiltinWelcomePagePath("AddPrinterByIpContent.qml"), diff --git a/cura/UI/WelcomePagesModel.py b/cura/UI/WelcomePagesModel.py index 10ae9dabf5..c16ec3763e 100644 --- a/cura/UI/WelcomePagesModel.py +++ b/cura/UI/WelcomePagesModel.py @@ -39,6 +39,7 @@ class WelcomePagesModel(ListModel): PageUrlRole = Qt.UserRole + 2 # URL to the page's QML file NextPageIdRole = Qt.UserRole + 3 # The next page ID it should go to NextPageButtonTextRole = Qt.UserRole + 4 # The text for the next page button + PreviousPageButtonTextRole = Qt.UserRole + 5 # The text for the previous page button def __init__(self, application: "CuraApplication", parent: Optional["QObject"] = None) -> None: super().__init__(parent) @@ -47,6 +48,7 @@ class WelcomePagesModel(ListModel): self.addRoleName(self.PageUrlRole, "page_url") self.addRoleName(self.NextPageIdRole, "next_page_id") self.addRoleName(self.NextPageButtonTextRole, "next_page_button_text") + self.addRoleName(self.PreviousPageButtonTextRole, "previous_page_button_text") self._application = application self._catalog = i18nCatalog("cura") diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index b6d62ec3f0..ab02887774 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -795,7 +795,6 @@ UM.MainWindow title: catalog.i18nc("@title:window", "Add Printer") model: CuraApplication.getAddPrinterPagesModel() progressBarVisible: false - hasCancelButton: true } Cura.WizardDialog @@ -804,7 +803,6 @@ UM.MainWindow title: catalog.i18nc("@title:window", "What's New") model: CuraApplication.getWhatsNewPagesModel() progressBarVisible: false - hasCancelButton: false } Connections diff --git a/resources/qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml b/resources/qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml index 6037868aaa..7ef1941b87 100644 --- a/resources/qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml +++ b/resources/qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml @@ -104,6 +104,20 @@ Item } } + // This "Back" button only shows in the "Add Machine" dialog, which has "back_button_text" set to "Cancel" + Cura.SecondaryButton + { + id: backButton + anchors.left: parent.left + anchors.bottom: parent.bottom + visible: base.currentItem.previous_page_button_text + text: base.currentItem.previous_page_button_text + onClicked: + { + base.endWizard() + } + } + Cura.PrimaryButton { id: nextButton diff --git a/resources/qml/WelcomePages/WizardDialog.qml b/resources/qml/WelcomePages/WizardDialog.qml index 31240b1ef5..c81f9daff0 100644 --- a/resources/qml/WelcomePages/WizardDialog.qml +++ b/resources/qml/WelcomePages/WizardDialog.qml @@ -31,7 +31,6 @@ Window property var model: null // Needs to be set by whoever is using this dialog. property alias progressBarVisible: wizardPanel.progressBarVisible - property alias hasCancelButton: cancelButton.visible onVisibilityChanged: { @@ -54,20 +53,4 @@ Window target: model onAllFinished: dialog.hide() } - - Cura.SecondaryButton - { - id: cancelButton - - text: catalog.i18nc("@button", "Cancel") - - visible: false - - anchors.left: parent.left - anchors.bottom: parent.bottom - anchors.margins: UM.Theme.getSize("default_margin").width - - enabled: true - onClicked: dialog.visible = false - } } From c8872cb4a17b6c859754f00ff8ff1434ff583ea0 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Mon, 29 Apr 2019 15:52:21 +0200 Subject: [PATCH 04/21] Use a separate function to validate IP address CURA-6483 --- cura/CuraApplication.py | 4 +++ cura/Utils/QtUtil.py | 21 ++++++++++++++ cura/Utils/networking.py | 26 +++++++++++++++++ .../WelcomePages/AddPrinterByIpContent.qml | 29 ++++++++++++++++++- 4 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 cura/Utils/QtUtil.py create mode 100644 cura/Utils/networking.py diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 49eec1d778..cc80c6dbfe 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -117,6 +117,8 @@ from cura.UI.AddPrinterPagesModel import AddPrinterPagesModel from cura.UI.WelcomePagesModel import WelcomePagesModel from cura.UI.WhatsNewPagesModel import WhatsNewPagesModel +from cura.Utils.QtUtil import QtUtil + from .SingleInstance import SingleInstance from .AutoSave import AutoSave from . import PlatformPhysics @@ -1028,6 +1030,8 @@ class CuraApplication(QtApplication): qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager) qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager) + qmlRegisterType(QtUtil, "Cura", 1, 0, "QtUtil") + qmlRegisterType(WelcomePagesModel, "Cura", 1, 0, "WelcomePagesModel") qmlRegisterType(WhatsNewPagesModel, "Cura", 1, 0, "WhatsNewPagesModel") qmlRegisterType(AddPrinterPagesModel, "Cura", 1, 0, "AddPrinterPagesModel") diff --git a/cura/Utils/QtUtil.py b/cura/Utils/QtUtil.py new file mode 100644 index 0000000000..60860fcd78 --- /dev/null +++ b/cura/Utils/QtUtil.py @@ -0,0 +1,21 @@ +# Copyright (c) 2019 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from typing import Optional + +from PyQt5.QtCore import QObject, pyqtSlot + +from . import networking + + +# +# Exposes the util functions to QML using a QObject. +# +class QtUtil(QObject): + + def __init__(self, parent: Optional["QObject"] = None) -> None: + super().__init__(parent = parent) + + @pyqtSlot(str, result = bool) + def isValidIP(self, address: str) -> bool: + return networking.isValidIP(address) diff --git a/cura/Utils/networking.py b/cura/Utils/networking.py new file mode 100644 index 0000000000..ba2bbddff6 --- /dev/null +++ b/cura/Utils/networking.py @@ -0,0 +1,26 @@ +# Copyright (c) 2019 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +import re + + +_REGEX_IPV4 = re.compile(r"^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$") +_REGEX_IPV6 = re.compile(r"^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$") + + +# Checks if the given string is a valid IPv4 address. +def isIPv4(address: str) -> bool: + return _REGEX_IPV4.fullmatch(address) is not None + + +# Checks if the given string is a valid IPv6 address. +def isIPv6(address: str) -> bool: + return _REGEX_IPV6.fullmatch(address) is not None + + +# Checks if the given string is a valid IPv4 or IPv6 address. +def isValidIP(address: str) -> bool: + return isIPv4(address) or isIPv6(address) + + +__all__ = ["isIPv4", "isIPv6", "isValidIP"] diff --git a/resources/qml/WelcomePages/AddPrinterByIpContent.qml b/resources/qml/WelcomePages/AddPrinterByIpContent.qml index de9562908c..565fa325cb 100644 --- a/resources/qml/WelcomePages/AddPrinterByIpContent.qml +++ b/resources/qml/WelcomePages/AddPrinterByIpContent.qml @@ -26,6 +26,9 @@ Item property var discoveredPrinter: null property var isPrinterDiscovered: discoveredPrinter != null + // For validating IP address + property var util: Cura.QtUtil{} + // Make sure to cancel the current request when this page closes. onVisibleChanged: { @@ -93,17 +96,36 @@ Item anchors.verticalCenter: addPrinterButton.verticalCenter anchors.left: parent.left + signal invalidInputDetected() + + onInvalidInputDetected: invalidInputLabel.visible = true + validator: RegExpValidator { - regExp: /((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))?/ + regExp: /([a-zA-Z0-9.:]+)?/ } + onTextEdited: invalidInputLabel.visible = false + placeholderText: catalog.i18nc("@text", "Place enter your printer's IP address.") enabled: { ! (addPrinterByIpScreen.hasRequestInProgress || addPrinterByIpScreen.isPrinterDiscovered) } onAccepted: addPrinterButton.clicked() } + Label + { + id: invalidInputLabel + anchors.top: hostnameField.bottom + anchors.topMargin: UM.Theme.getSize("default_margin").height + anchors.left: parent.left + visible: false + text: catalog.i18nc("@text", "Place enter a valid IP address.") + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + renderType: Text.NativeRendering + } + Cura.SecondaryButton { id: addPrinterButton @@ -115,6 +137,11 @@ Item onClicked: { const address = hostnameField.text + if (!util.isValidIP(address)) + { + hostnameField.invalidInputDetected() + return + } // This address is already in the discovered printer model, no need to add a manual discovery. if (CuraApplication.getDiscoveredPrintersModel().discoveredPrintersByAddress[address]) From 8c42ceb8e603151d1132bff24a07e02cf70f1f7d Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 30 Apr 2019 07:40:16 +0200 Subject: [PATCH 05/21] Use socket to validate IP addresses CURA-6483 --- cura/Utils/networking.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/cura/Utils/networking.py b/cura/Utils/networking.py index ba2bbddff6..0456191cee 100644 --- a/cura/Utils/networking.py +++ b/cura/Utils/networking.py @@ -1,21 +1,27 @@ # Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -import re - - -_REGEX_IPV4 = re.compile(r"^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$") -_REGEX_IPV6 = re.compile(r"^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$") +import socket # Checks if the given string is a valid IPv4 address. def isIPv4(address: str) -> bool: - return _REGEX_IPV4.fullmatch(address) is not None + try: + socket.inet_pton(socket.AF_INET, address) + result = True + except: + result = False + return result # Checks if the given string is a valid IPv6 address. def isIPv6(address: str) -> bool: - return _REGEX_IPV6.fullmatch(address) is not None + try: + socket.inet_pton(socket.AF_INET6, address) + result = True + except: + result = False + return result # Checks if the given string is a valid IPv4 or IPv6 address. From bb1442a04e1239693892cbea012bd6b9b7018727 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 30 Apr 2019 07:40:33 +0200 Subject: [PATCH 06/21] Update comments CURA-6483 --- resources/qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml b/resources/qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml index 7ef1941b87..3a19e7807a 100644 --- a/resources/qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml +++ b/resources/qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml @@ -104,7 +104,7 @@ Item } } - // This "Back" button only shows in the "Add Machine" dialog, which has "back_button_text" set to "Cancel" + // This "Back" button only shows in the "Add Machine" dialog, which has "previous_page_button_text" set to "Cancel" Cura.SecondaryButton { id: backButton From ed8127777ce52d39c0871969864b2cd9cf391b7d Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 30 Apr 2019 09:43:48 +0200 Subject: [PATCH 07/21] Make a single NetworkingUtil CURA-6483 --- cura/CuraApplication.py | 4 +- cura/Utils/NetworkingUtil.py | 44 +++++++++++++++++++ cura/Utils/QtUtil.py | 21 --------- cura/Utils/networking.py | 32 -------------- .../WelcomePages/AddPrinterByIpContent.qml | 4 +- 5 files changed, 48 insertions(+), 57 deletions(-) create mode 100644 cura/Utils/NetworkingUtil.py delete mode 100644 cura/Utils/QtUtil.py delete mode 100644 cura/Utils/networking.py diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index cc80c6dbfe..a43af578a1 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -117,7 +117,7 @@ from cura.UI.AddPrinterPagesModel import AddPrinterPagesModel from cura.UI.WelcomePagesModel import WelcomePagesModel from cura.UI.WhatsNewPagesModel import WhatsNewPagesModel -from cura.Utils.QtUtil import QtUtil +from cura.Utils.NetworkingUtil import NetworkingUtil from .SingleInstance import SingleInstance from .AutoSave import AutoSave @@ -1030,7 +1030,7 @@ class CuraApplication(QtApplication): qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager) qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager) - qmlRegisterType(QtUtil, "Cura", 1, 0, "QtUtil") + qmlRegisterType(NetworkingUtil, "Cura", 1, 0, "NetworkingUtil") qmlRegisterType(WelcomePagesModel, "Cura", 1, 0, "WelcomePagesModel") qmlRegisterType(WhatsNewPagesModel, "Cura", 1, 0, "WhatsNewPagesModel") diff --git a/cura/Utils/NetworkingUtil.py b/cura/Utils/NetworkingUtil.py new file mode 100644 index 0000000000..b13f7903b9 --- /dev/null +++ b/cura/Utils/NetworkingUtil.py @@ -0,0 +1,44 @@ +# Copyright (c) 2019 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +import socket +from typing import Optional + +from PyQt5.QtCore import QObject, pyqtSlot + + +# +# This is a QObject because some of the functions can be used (and are useful) in QML. +# +class NetworkingUtil(QObject): + + def __init__(self, parent: Optional["QObject"] = None) -> None: + super().__init__(parent = parent) + + # Checks if the given string is a valid IPv4 address. + @pyqtSlot(str, result = bool) + def isIPv4(self, address: str) -> bool: + try: + socket.inet_pton(socket.AF_INET, address) + result = True + except: + result = False + return result + + # Checks if the given string is a valid IPv6 address. + @pyqtSlot(str, result = bool) + def isIPv6(self, address: str) -> bool: + try: + socket.inet_pton(socket.AF_INET6, address) + result = True + except: + result = False + return result + + # Checks if the given string is a valid IPv4 or IPv6 address. + @pyqtSlot(str, result = bool) + def isValidIP(self, address: str) -> bool: + return self.isIPv4(address) or self.isIPv6(address) + + +__all__ = ["NetworkingUtil"] diff --git a/cura/Utils/QtUtil.py b/cura/Utils/QtUtil.py deleted file mode 100644 index 60860fcd78..0000000000 --- a/cura/Utils/QtUtil.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (c) 2019 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. - -from typing import Optional - -from PyQt5.QtCore import QObject, pyqtSlot - -from . import networking - - -# -# Exposes the util functions to QML using a QObject. -# -class QtUtil(QObject): - - def __init__(self, parent: Optional["QObject"] = None) -> None: - super().__init__(parent = parent) - - @pyqtSlot(str, result = bool) - def isValidIP(self, address: str) -> bool: - return networking.isValidIP(address) diff --git a/cura/Utils/networking.py b/cura/Utils/networking.py deleted file mode 100644 index 0456191cee..0000000000 --- a/cura/Utils/networking.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (c) 2019 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. - -import socket - - -# Checks if the given string is a valid IPv4 address. -def isIPv4(address: str) -> bool: - try: - socket.inet_pton(socket.AF_INET, address) - result = True - except: - result = False - return result - - -# Checks if the given string is a valid IPv6 address. -def isIPv6(address: str) -> bool: - try: - socket.inet_pton(socket.AF_INET6, address) - result = True - except: - result = False - return result - - -# Checks if the given string is a valid IPv4 or IPv6 address. -def isValidIP(address: str) -> bool: - return isIPv4(address) or isIPv6(address) - - -__all__ = ["isIPv4", "isIPv6", "isValidIP"] diff --git a/resources/qml/WelcomePages/AddPrinterByIpContent.qml b/resources/qml/WelcomePages/AddPrinterByIpContent.qml index 565fa325cb..0862727d6e 100644 --- a/resources/qml/WelcomePages/AddPrinterByIpContent.qml +++ b/resources/qml/WelcomePages/AddPrinterByIpContent.qml @@ -27,7 +27,7 @@ Item property var isPrinterDiscovered: discoveredPrinter != null // For validating IP address - property var util: Cura.QtUtil{} + property var networkingUtil: Cura.NetworkingUtil {} // Make sure to cancel the current request when this page closes. onVisibleChanged: @@ -137,7 +137,7 @@ Item onClicked: { const address = hostnameField.text - if (!util.isValidIP(address)) + if (!networkingUtil.isValidIP(address)) { hostnameField.invalidInputDetected() return From d6f4ddc322db3b3cbb6211bca17c4b03d77f9d70 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 30 Apr 2019 12:45:58 +0200 Subject: [PATCH 08/21] Simplify GlobalStacksModel CURA-6483 --- cura/Machines/Models/GlobalStacksModel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cura/Machines/Models/GlobalStacksModel.py b/cura/Machines/Models/GlobalStacksModel.py index f15fe1878b..e4f1cf1177 100644 --- a/cura/Machines/Models/GlobalStacksModel.py +++ b/cura/Machines/Models/GlobalStacksModel.py @@ -5,6 +5,7 @@ from PyQt5.QtCore import Qt, QTimer from UM.Qt.ListModel import ListModel from UM.i18n import i18nCatalog +from UM.Util import parseBool from cura.PrinterOutput.PrinterOutputDevice import ConnectionType from cura.Settings.CuraContainerRegistry import CuraContainerRegistry @@ -54,7 +55,6 @@ class GlobalStacksModel(ListModel): items = [] container_stacks = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine") - for container_stack in container_stacks: has_remote_connection = False @@ -62,7 +62,7 @@ class GlobalStacksModel(ListModel): has_remote_connection |= connection_type in [ConnectionType.NetworkConnection.value, ConnectionType.CloudConnection.value] - if container_stack.getMetaDataEntry("hidden", False) in ["True", True]: + if parseBool(container_stack.getMetaDataEntry("hidden", False)): continue section_name = "Network enabled printers" if has_remote_connection else "Local printers" From d18c11a2c19f6df9964875382f9a541389b650fa Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 30 Apr 2019 12:46:33 +0200 Subject: [PATCH 09/21] Fix filtering in switchPrinterType() CURA-6483 --- cura/Settings/MachineManager.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index c40b67c2e4..d1f5c4c884 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -1360,7 +1360,10 @@ class MachineManager(QObject): # Get the definition id corresponding to this machine name machine_definition_id = CuraContainerRegistry.getInstance().findDefinitionContainers(name = machine_name)[0].getId() # Try to find a machine with the same network key - new_machine = self.getMachine(machine_definition_id, metadata_filter = {"um_network_key": self.activeMachineNetworkKey()}) + metadata_filter = {"group_id": self._global_container_stack.getMetaDataEntry("group_id"), + "um_network_key": self.activeMachineNetworkKey(), + } + new_machine = self.getMachine(machine_definition_id, metadata_filter = metadata_filter) # If there is no machine, then create a new one and set it to the non-hidden instance if not new_machine: new_machine = CuraStackBuilder.createMachine(machine_definition_id + "_sync", machine_definition_id) @@ -1369,13 +1372,12 @@ class MachineManager(QObject): new_machine.setMetaDataEntry("group_id", self._global_container_stack.getMetaDataEntry("group_id")) new_machine.setMetaDataEntry("um_network_key", self.activeMachineNetworkKey()) new_machine.setMetaDataEntry("group_name", self.activeMachineNetworkGroupName) - new_machine.setMetaDataEntry("hidden", False) new_machine.setMetaDataEntry("connection_type", self._global_container_stack.getMetaDataEntry("connection_type")) else: Logger.log("i", "Found a %s with the key %s. Let's use it!", machine_name, self.activeMachineNetworkKey()) - new_machine.setMetaDataEntry("hidden", False) # Set the current printer instance to hidden (the metadata entry must exist) + new_machine.setMetaDataEntry("hidden", False) self._global_container_stack.setMetaDataEntry("hidden", True) self.setActiveMachine(new_machine.getId()) From e1766b72ece5675aa32217acf2a1b6ffef313618 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 30 Apr 2019 12:47:17 +0200 Subject: [PATCH 10/21] Only highlight the currently active machine CURA-6483 --- resources/qml/PrinterSelector/MachineSelectorList.qml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/resources/qml/PrinterSelector/MachineSelectorList.qml b/resources/qml/PrinterSelector/MachineSelectorList.qml index 1043339ae6..9c52c15580 100644 --- a/resources/qml/PrinterSelector/MachineSelectorList.qml +++ b/resources/qml/PrinterSelector/MachineSelectorList.qml @@ -32,16 +32,7 @@ ListView width: listView.width outputDevice: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null - checked: - { - // If the machine has a remote connection - var result = Cura.MachineManager.activeMachineId == model.id - if (Cura.MachineManager.activeMachineHasRemoteConnection) - { - result |= Cura.MachineManager.activeMachineNetworkGroupName == model.metadata["group_name"] - } - return result - } + checked: Cura.MachineManager.activeMachineId == model.id onClicked: { From 40d26bbb6ecff89540bcc2ccbe2797440339df4e Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 30 Apr 2019 12:48:49 +0200 Subject: [PATCH 11/21] NetworkingUtil is included in Cura 1.5 QML CURA-6483 --- cura/CuraApplication.py | 2 +- resources/qml/WelcomePages/AddPrinterByIpContent.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index a43af578a1..29febf2610 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -1030,7 +1030,7 @@ class CuraApplication(QtApplication): qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager) qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager) - qmlRegisterType(NetworkingUtil, "Cura", 1, 0, "NetworkingUtil") + qmlRegisterType(NetworkingUtil, "Cura", 1, 5, "NetworkingUtil") qmlRegisterType(WelcomePagesModel, "Cura", 1, 0, "WelcomePagesModel") qmlRegisterType(WhatsNewPagesModel, "Cura", 1, 0, "WhatsNewPagesModel") diff --git a/resources/qml/WelcomePages/AddPrinterByIpContent.qml b/resources/qml/WelcomePages/AddPrinterByIpContent.qml index 0862727d6e..6fa7097814 100644 --- a/resources/qml/WelcomePages/AddPrinterByIpContent.qml +++ b/resources/qml/WelcomePages/AddPrinterByIpContent.qml @@ -6,7 +6,7 @@ import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 import UM 1.3 as UM -import Cura 1.1 as Cura +import Cura 1.5 as Cura // From 0cc00efa7983f18de6aa472be2f24b9c802a2630 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 30 Apr 2019 12:53:45 +0200 Subject: [PATCH 12/21] Add doc for group_id CURA-6483 --- cura/Settings/GlobalStack.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py index a8e6f2f2c0..15c95c9394 100755 --- a/cura/Settings/GlobalStack.py +++ b/cura/Settings/GlobalStack.py @@ -35,6 +35,11 @@ class GlobalStack(CuraContainerStack): super().__init__(container_id) self.setMetaDataEntry("type", "machine") # For backward compatibility + + # TL;DR: If Cura is looking for printers that belong to the same group, it should use "group_id". + # Each GlobalStack by default belongs to a group which is identified via "group_id". This group_id is used to + # figure out which GlobalStacks are in the printer cluster for example without knowing the implementation + # details such as the um_network_key or some other identifier that's used by the underlying device plugin. self.setMetaDataEntry("group_id", str(uuid.uuid4())) # Assign a new GlobalStack to a unique group by default self._extruders = {} # type: Dict[str, "ExtruderStack"] From 3578482ea5fdb7109e5550097f826eb13c301483 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 30 Apr 2019 15:26:02 +0200 Subject: [PATCH 13/21] Fix connect via network dialog CURA-6483 - Do not add printers that have already been discovered - Add IP address validation check --- .../resources/qml/DiscoverUM3Action.qml | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml b/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml index 2788a35173..1e3da11e0a 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml @@ -1,8 +1,8 @@ -// Copyright (c) 2018 Ultimaker B.V. +// Copyright (c) 2019 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. import UM 1.2 as UM -import Cura 1.0 as Cura +import Cura 1.5 as Cura import QtQuick 2.2 import QtQuick.Controls 1.1 @@ -14,9 +14,13 @@ Cura.MachineAction { id: base anchors.fill: parent; + property alias currentItemIndex: listview.currentIndex property var selectedDevice: null property bool completeProperties: true + // For validating IP addresses + property var networkingUtil: Cura.NetworkingUtil {} + function connectToPrinter() { if(base.selectedDevice && base.completeProperties) @@ -342,6 +346,17 @@ Cura.MachineAction } } + MessageDialog + { + id: invalidIPAddressMessageDialog + x: ((parent.width - width) / 2) | 0 + y: ((parent.height - height) / 2) | 0 + title: catalog.i18nc("@title:window", "Invalid IP address") + text: catalog.i18nc("@text", "Please enter a valid IP address.") + icon: StandardIcon.Warning + standardButtons: StandardButton.Ok + } + UM.Dialog { id: manualPrinterDialog @@ -404,6 +419,26 @@ Cura.MachineAction text: catalog.i18nc("@action:button", "OK") onClicked: { + // Validate the input first + if (!networkingUtil.isValidIP(manualPrinterDialog.addressText)) + { + invalidIPAddressMessageDialog.open() + return + } + + // if the entered IP address has already been discovered, switch the current item to that item + // and do nothing else. + for (var i = 0; i < manager.foundDevices.length; i++) + { + var device = manager.foundDevices[i] + if (device.address == manualPrinterDialog.addressText) + { + currentItemIndex = i + manualPrinterDialog.hide() + return + } + } + manager.setManualDevice(manualPrinterDialog.printerKey, manualPrinterDialog.addressText) manualPrinterDialog.hide() } From 0e2815a7481aa1aff5cb9783cc683a6141f00a8f Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 30 Apr 2019 15:26:56 +0200 Subject: [PATCH 14/21] Fix back button on add printer page CURA-6483 --- .../qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml b/resources/qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml index 3a19e7807a..fcdfa9817c 100644 --- a/resources/qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml +++ b/resources/qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml @@ -110,8 +110,8 @@ Item id: backButton anchors.left: parent.left anchors.bottom: parent.bottom - visible: base.currentItem.previous_page_button_text - text: base.currentItem.previous_page_button_text + visible: base.currentItem.previous_page_button_text ? true : false + text: base.currentItem.previous_page_button_text ? base.currentItem.previous_page_button_text : "" onClicked: { base.endWizard() From 383b474bd79e89a3aad458fbe721c88b819d5031 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 30 Apr 2019 15:27:27 +0200 Subject: [PATCH 15/21] Fix typo CURA-6483 --- resources/qml/WelcomePages/AddPrinterByIpContent.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/WelcomePages/AddPrinterByIpContent.qml b/resources/qml/WelcomePages/AddPrinterByIpContent.qml index 6fa7097814..663cd83de8 100644 --- a/resources/qml/WelcomePages/AddPrinterByIpContent.qml +++ b/resources/qml/WelcomePages/AddPrinterByIpContent.qml @@ -120,7 +120,7 @@ Item anchors.topMargin: UM.Theme.getSize("default_margin").height anchors.left: parent.left visible: false - text: catalog.i18nc("@text", "Place enter a valid IP address.") + text: catalog.i18nc("@text", "Please enter a valid IP address.") font: UM.Theme.getFont("default") color: UM.Theme.getColor("text") renderType: Text.NativeRendering From 2b5d78a01af1ffb0c0ff58efdf81cbb39c1990d6 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 1 May 2019 14:24:58 +0200 Subject: [PATCH 16/21] Do not add unknown or non-host printers in add-by-ip CURA-6483 --- .../WelcomePages/AddPrinterByIpContent.qml | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/resources/qml/WelcomePages/AddPrinterByIpContent.qml b/resources/qml/WelcomePages/AddPrinterByIpContent.qml index 663cd83de8..13c87eccd8 100644 --- a/resources/qml/WelcomePages/AddPrinterByIpContent.qml +++ b/resources/qml/WelcomePages/AddPrinterByIpContent.qml @@ -24,7 +24,9 @@ Item property bool hasRequestFinished: false property var discoveredPrinter: null - property var isPrinterDiscovered: discoveredPrinter != null + property bool isPrinterDiscovered: discoveredPrinter != null + // A printer can only be added if it doesn't have an unknown type and it's the host of a group. + property bool canAddPrinter: isPrinterDiscovered && !discoveredPrinter.isUnknownMachineType && discoveredPrinter.isHostOfGroup // For validating IP address property var networkingUtil: Cura.NetworkingUtil {} @@ -188,6 +190,8 @@ Item Item { id: printerInfoLabels + anchors.left: parent.left + anchors.right: parent.right anchors.top: parent.top anchors.margins: UM.Theme.getSize("default_margin").width @@ -204,10 +208,24 @@ Item text: !addPrinterByIpScreen.isPrinterDiscovered ? "???" : addPrinterByIpScreen.discoveredPrinter.name } + Label + { + id: printerCannotBeAddedLabel + width: parent.width + anchors.top: printerNameLabel.bottom + anchors.topMargin: UM.Theme.getSize("default_margin").height + text: catalog.i18nc("@label", "This printer cannot be added because it's an unknown printer or it's not the host of a group.") + visible: addPrinterByIpScreen.hasRequestFinished && !addPrinterByIpScreen.canAddPrinter + font: UM.Theme.getFont("default_bold") + color: UM.Theme.getColor("text") + renderType: Text.NativeRendering + wrapMode: Text.WordWrap + } + GridLayout { id: printerInfoGrid - anchors.top: printerNameLabel.bottom + anchors.top: printerCannotBeAddedLabel ? printerCannotBeAddedLabel.bottom : printerNameLabel.bottom anchors.margins: UM.Theme.getSize("default_margin").width columns: 2 columnSpacing: UM.Theme.getSize("default_margin").width @@ -305,6 +323,6 @@ Item base.showNextPage() } - enabled: addPrinterByIpScreen.isPrinterDiscovered + enabled: addPrinterByIpScreen.canAddPrinter } } From 0575fd283e750fbc54575dd0ed07e135881a1e61 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 1 May 2019 15:11:00 +0200 Subject: [PATCH 17/21] Fix manual printer device not up-to-date CURA-6483 --- .../WelcomePages/AddPrinterByIpContent.qml | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/resources/qml/WelcomePages/AddPrinterByIpContent.qml b/resources/qml/WelcomePages/AddPrinterByIpContent.qml index 13c87eccd8..6ee59f03f9 100644 --- a/resources/qml/WelcomePages/AddPrinterByIpContent.qml +++ b/resources/qml/WelcomePages/AddPrinterByIpContent.qml @@ -22,6 +22,7 @@ Item property bool hasRequestInProgress: CuraApplication.getDiscoveredPrintersModel().hasManualDeviceRequestInProgress // Indicates if a request has finished. property bool hasRequestFinished: false + property string currentRequestAddress: "" property var discoveredPrinter: null property bool isPrinterDiscovered: discoveredPrinter != null @@ -31,6 +32,26 @@ Item // For validating IP address property var networkingUtil: Cura.NetworkingUtil {} + // CURA-6483 + // For a manually added UM printer, the UM3OutputDevicePlugin will first create a LegacyUM device for it. Later, + // when it gets more info from the printer, it will first REMOVE the LegacyUM device and then add a ClusterUM device. + // The Add-by-IP page needs to make sure that the user do not add an unknown printer or a printer that's not the + // host of a group. Because of the device list change, this page needs to react upon DiscoveredPrintersChanged so + // it has the correct information. + Connections + { + target: CuraApplication.getDiscoveredPrintersModel() + onDiscoveredPrintersChanged: + { + if (hasRequestFinished && currentRequestAddress) + { + var printer = CuraApplication.getDiscoveredPrintersModel().discoveredPrintersByAddress[currentRequestAddress] + printer = printer ? printer : null + discoveredPrinter = printer + } + } + } + // Make sure to cancel the current request when this page closes. onVisibleChanged: { @@ -149,9 +170,11 @@ Item if (CuraApplication.getDiscoveredPrintersModel().discoveredPrintersByAddress[address]) { addPrinterByIpScreen.discoveredPrinter = CuraApplication.getDiscoveredPrintersModel().discoveredPrintersByAddress[address] + addPrinterByIpScreen.hasRequestFinished = true return } + addPrinterByIpScreen.currentRequestAddress = address CuraApplication.getDiscoveredPrintersModel().checkManualDevice(address) } busy: addPrinterByIpScreen.hasRequestInProgress From 1d4ab98ddc12bd4ef76f96ac71ef7a97bff0bc84 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 2 May 2019 08:25:27 +0200 Subject: [PATCH 18/21] Fix binding loop CURA-6483 --- .../UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml b/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml index 1e3da11e0a..ecec87ef02 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/DiscoverUM3Action.qml @@ -349,8 +349,8 @@ Cura.MachineAction MessageDialog { id: invalidIPAddressMessageDialog - x: ((parent.width - width) / 2) | 0 - y: ((parent.height - height) / 2) | 0 + x: (parent.x + (parent.width) / 2) | 0 + y: (parent.y + (parent.height) / 2) | 0 title: catalog.i18nc("@title:window", "Invalid IP address") text: catalog.i18nc("@text", "Please enter a valid IP address.") icon: StandardIcon.Warning From 85822d6e73c21e54b643c56ca895756fd3fc8d5e Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 2 May 2019 15:30:09 +0200 Subject: [PATCH 19/21] Fix add by IP validator regex CURA-6483 --- resources/qml/WelcomePages/AddPrinterByIpContent.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/WelcomePages/AddPrinterByIpContent.qml b/resources/qml/WelcomePages/AddPrinterByIpContent.qml index 6ee59f03f9..a2543d1fb8 100644 --- a/resources/qml/WelcomePages/AddPrinterByIpContent.qml +++ b/resources/qml/WelcomePages/AddPrinterByIpContent.qml @@ -125,7 +125,7 @@ Item validator: RegExpValidator { - regExp: /([a-zA-Z0-9.:]+)?/ + regExp: /([a-fA-F0-9.:]+)?/ } onTextEdited: invalidInputLabel.visible = false From 5b9c4e402e5c4dcdd3cf64666e0b1045e5e047e5 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Thu, 2 May 2019 15:38:19 +0200 Subject: [PATCH 20/21] Change the Back button to be a secondary button Change also some texts in the More information dialog and the font type of one of the titles in the first use flow Contributes to CURA-6483. --- plugins/SliceInfoPlugin/MoreInfoWindow.qml | 6 +++--- resources/qml/WelcomePages/AddPrinterByIpContent.qml | 2 +- resources/qml/WelcomePages/CloudContent.qml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/SliceInfoPlugin/MoreInfoWindow.qml b/plugins/SliceInfoPlugin/MoreInfoWindow.qml index f19e2cce19..50276ec25c 100644 --- a/plugins/SliceInfoPlugin/MoreInfoWindow.qml +++ b/plugins/SliceInfoPlugin/MoreInfoWindow.qml @@ -71,7 +71,7 @@ Window left: parent.left right: parent.right } - text: catalog.i18nc("@text:window", "Ultimaker Cura collects anonymous data in order to improve the print quality and user experience. Below is an example of all the data that is sent:") + text: catalog.i18nc("@text:window", "Ultimaker Cura collects anonymous data in order to improve the print quality and user experience. Below is an example of all the data that is shared:") wrapMode: Text.WordWrap renderType: Text.NativeRendering } @@ -106,7 +106,7 @@ Window Cura.RadioButton { id: dontSendButton - text: catalog.i18nc("@text:window", "I don't want to send this data") + text: catalog.i18nc("@text:window", "I don't want to send anonymous data") onClicked: { baseDialog.allowSendData = !checked @@ -115,7 +115,7 @@ Window Cura.RadioButton { id: allowSendButton - text: catalog.i18nc("@text:window", "Allow sending this data to Ultimaker and help us improve Cura") + text: catalog.i18nc("@text:window", "Allow sending anonymous data") onClicked: { baseDialog.allowSendData = checked diff --git a/resources/qml/WelcomePages/AddPrinterByIpContent.qml b/resources/qml/WelcomePages/AddPrinterByIpContent.qml index a2543d1fb8..4aec5879c1 100644 --- a/resources/qml/WelcomePages/AddPrinterByIpContent.qml +++ b/resources/qml/WelcomePages/AddPrinterByIpContent.qml @@ -321,7 +321,7 @@ Item } } - Cura.PrimaryButton + Cura.SecondaryButton { id: backButton anchors.left: parent.left diff --git a/resources/qml/WelcomePages/CloudContent.qml b/resources/qml/WelcomePages/CloudContent.qml index b0b4d53cf1..e9b6df94e0 100644 --- a/resources/qml/WelcomePages/CloudContent.qml +++ b/resources/qml/WelcomePages/CloudContent.qml @@ -35,7 +35,7 @@ Item horizontalAlignment: Text.AlignHCenter text: catalog.i18nc("@label", "Ultimaker Cloud") color: UM.Theme.getColor("primary_button") - font: UM.Theme.getFont("large_bold") + font: UM.Theme.getFont("huge") renderType: Text.NativeRendering } From 7d4c8215515f8e08113dcb806173e3cce1162188 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 3 May 2019 08:48:35 +0200 Subject: [PATCH 21/21] Prevent auto selecting an invalid printer CURA-6483 --- .../Models/DiscoveredPrintersModel.py | 2 +- .../AddNetworkPrinterScrollView.qml | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/cura/Machines/Models/DiscoveredPrintersModel.py b/cura/Machines/Models/DiscoveredPrintersModel.py index e639ee2d25..a2a1fac3f7 100644 --- a/cura/Machines/Models/DiscoveredPrintersModel.py +++ b/cura/Machines/Models/DiscoveredPrintersModel.py @@ -192,7 +192,7 @@ class DiscoveredPrintersModel(QObject): def discoveredPrintersByAddress(self) -> Dict[str, DiscoveredPrinter]: return self._discovered_printer_by_ip_dict - @pyqtProperty(list, notify = discoveredPrintersChanged) + @pyqtProperty("QVariantList", notify = discoveredPrintersChanged) def discoveredPrinters(self) -> List["DiscoveredPrinter"]: item_list = list( x for x in self._discovered_printer_by_ip_dict.values() if not parseBool(x.device.getProperty("temporary"))) diff --git a/resources/qml/WelcomePages/AddNetworkPrinterScrollView.qml b/resources/qml/WelcomePages/AddNetworkPrinterScrollView.qml index 5f3ceac116..6d8f75c3f5 100644 --- a/resources/qml/WelcomePages/AddNetworkPrinterScrollView.qml +++ b/resources/qml/WelcomePages/AddNetworkPrinterScrollView.qml @@ -76,15 +76,28 @@ Item Component.onCompleted: { - // Select the first one that's not "unknown" by default. + var toSelectIndex = -1 + // Select the first one that's not "unknown" and is the host a group by default. for (var i = 0; i < count; i++) { - if (!model[i].isUnknownMachineType) + if (!model[i].isUnknownMachineType && model[i].isHostOfGroup) { - currentIndex = i + toSelectIndex = i break } } + currentIndex = toSelectIndex + } + + // CURA-6483 For some reason currentIndex can be reset to 0. This check is here to prevent automatically + // selecting an unknown or non-host printer. + onCurrentIndexChanged: + { + var item = model[currentIndex] + if (!item || item.isUnknownMachineType || !item.isHostOfGroup) + { + currentIndex = -1 + } } Component