Merge branch 'CURA-6483_fix_add_by_ip' of github.com:Ultimaker/Cura into 4.1

This commit is contained in:
Jaime van Kessel 2019-05-03 09:25:30 +02:00
commit 6bbdfbb31d
20 changed files with 248 additions and 91 deletions

View File

@ -117,6 +117,8 @@ from cura.UI.AddPrinterPagesModel import AddPrinterPagesModel
from cura.UI.WelcomePagesModel import WelcomePagesModel from cura.UI.WelcomePagesModel import WelcomePagesModel
from cura.UI.WhatsNewPagesModel import WhatsNewPagesModel from cura.UI.WhatsNewPagesModel import WhatsNewPagesModel
from cura.Utils.NetworkingUtil import NetworkingUtil
from .SingleInstance import SingleInstance from .SingleInstance import SingleInstance
from .AutoSave import AutoSave from .AutoSave import AutoSave
from . import PlatformPhysics from . import PlatformPhysics
@ -1028,6 +1030,8 @@ class CuraApplication(QtApplication):
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager) qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager)
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager) qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
qmlRegisterType(NetworkingUtil, "Cura", 1, 5, "NetworkingUtil")
qmlRegisterType(WelcomePagesModel, "Cura", 1, 0, "WelcomePagesModel") qmlRegisterType(WelcomePagesModel, "Cura", 1, 0, "WelcomePagesModel")
qmlRegisterType(WhatsNewPagesModel, "Cura", 1, 0, "WhatsNewPagesModel") qmlRegisterType(WhatsNewPagesModel, "Cura", 1, 0, "WhatsNewPagesModel")
qmlRegisterType(AddPrinterPagesModel, "Cura", 1, 0, "AddPrinterPagesModel") qmlRegisterType(AddPrinterPagesModel, "Cura", 1, 0, "AddPrinterPagesModel")

View File

@ -192,7 +192,7 @@ class DiscoveredPrintersModel(QObject):
def discoveredPrintersByAddress(self) -> Dict[str, DiscoveredPrinter]: def discoveredPrintersByAddress(self) -> Dict[str, DiscoveredPrinter]:
return self._discovered_printer_by_ip_dict return self._discovered_printer_by_ip_dict
@pyqtProperty(list, notify = discoveredPrintersChanged) @pyqtProperty("QVariantList", notify = discoveredPrintersChanged)
def discoveredPrinters(self) -> List["DiscoveredPrinter"]: def discoveredPrinters(self) -> List["DiscoveredPrinter"]:
item_list = list( item_list = list(
x for x in self._discovered_printer_by_ip_dict.values() if not parseBool(x.device.getProperty("temporary"))) x for x in self._discovered_printer_by_ip_dict.values() if not parseBool(x.device.getProperty("temporary")))

View File

@ -5,6 +5,7 @@ from PyQt5.QtCore import Qt, QTimer
from UM.Qt.ListModel import ListModel from UM.Qt.ListModel import ListModel
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
from UM.Util import parseBool
from cura.PrinterOutput.PrinterOutputDevice import ConnectionType from cura.PrinterOutput.PrinterOutputDevice import ConnectionType
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
@ -54,7 +55,6 @@ class GlobalStacksModel(ListModel):
items = [] items = []
container_stacks = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine") container_stacks = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine")
for container_stack in container_stacks: for container_stack in container_stacks:
has_remote_connection = False has_remote_connection = False
@ -62,7 +62,7 @@ class GlobalStacksModel(ListModel):
has_remote_connection |= connection_type in [ConnectionType.NetworkConnection.value, has_remote_connection |= connection_type in [ConnectionType.NetworkConnection.value,
ConnectionType.CloudConnection.value] ConnectionType.CloudConnection.value]
if container_stack.getMetaDataEntry("hidden", False) in ["True", True]: if parseBool(container_stack.getMetaDataEntry("hidden", False)):
continue continue
section_name = "Network enabled printers" if has_remote_connection else "Local printers" section_name = "Network enabled printers" if has_remote_connection else "Local printers"

View File

@ -4,6 +4,8 @@
from collections import defaultdict from collections import defaultdict
import threading import threading
from typing import Any, Dict, Optional, Set, TYPE_CHECKING, List from typing import Any, Dict, Optional, Set, TYPE_CHECKING, List
import uuid
from PyQt5.QtCore import pyqtProperty, pyqtSlot, pyqtSignal from PyQt5.QtCore import pyqtProperty, pyqtSlot, pyqtSignal
from UM.Decorators import override from UM.Decorators import override
@ -34,6 +36,12 @@ class GlobalStack(CuraContainerStack):
self.setMetaDataEntry("type", "machine") # For backward compatibility 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"] self._extruders = {} # type: Dict[str, "ExtruderStack"]
# This property is used to track which settings we are calculating the "resolve" for # This property is used to track which settings we are calculating the "resolve" for

View File

@ -797,7 +797,6 @@ class MachineManager(QObject):
self.setActiveMachine(other_machine_stacks[0]["id"]) self.setActiveMachine(other_machine_stacks[0]["id"])
metadata = CuraContainerRegistry.getInstance().findContainerStacksMetadata(id = machine_id)[0] metadata = CuraContainerRegistry.getInstance().findContainerStacksMetadata(id = machine_id)[0]
network_key = metadata.get("um_network_key", None)
ExtruderManager.getInstance().removeMachineExtruders(machine_id) ExtruderManager.getInstance().removeMachineExtruders(machine_id)
containers = CuraContainerRegistry.getInstance().findInstanceContainersMetadata(type = "user", machine = machine_id) containers = CuraContainerRegistry.getInstance().findInstanceContainersMetadata(type = "user", machine = machine_id)
for container in containers: for container in containers:
@ -805,8 +804,9 @@ class MachineManager(QObject):
CuraContainerRegistry.getInstance().removeContainer(machine_id) 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 the printer that is being removed is a network printer, the hidden printers have to be also removed
if network_key: group_id = metadata.get("group_id", None)
metadata_filter = {"um_network_key": network_key} if group_id:
metadata_filter = {"group_id": group_id}
hidden_containers = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter) hidden_containers = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
if hidden_containers: if hidden_containers:
# This reuses the method and remove all printers recursively # This reuses the method and remove all printers recursively
@ -1360,21 +1360,24 @@ class MachineManager(QObject):
# Get the definition id corresponding to this machine name # Get the definition id corresponding to this machine name
machine_definition_id = CuraContainerRegistry.getInstance().findDefinitionContainers(name = machine_name)[0].getId() machine_definition_id = CuraContainerRegistry.getInstance().findDefinitionContainers(name = machine_name)[0].getId()
# Try to find a machine with the same network key # 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 there is no machine, then create a new one and set it to the non-hidden instance
if not new_machine: if not new_machine:
new_machine = CuraStackBuilder.createMachine(machine_definition_id + "_sync", machine_definition_id) new_machine = CuraStackBuilder.createMachine(machine_definition_id + "_sync", machine_definition_id)
if not new_machine: if not new_machine:
return return
new_machine.setMetaDataEntry("group_id", self._global_container_stack.getMetaDataEntry("group_id"))
new_machine.setMetaDataEntry("um_network_key", self.activeMachineNetworkKey()) new_machine.setMetaDataEntry("um_network_key", self.activeMachineNetworkKey())
new_machine.setMetaDataEntry("group_name", self.activeMachineNetworkGroupName) new_machine.setMetaDataEntry("group_name", self.activeMachineNetworkGroupName)
new_machine.setMetaDataEntry("hidden", False)
new_machine.setMetaDataEntry("connection_type", self._global_container_stack.getMetaDataEntry("connection_type")) new_machine.setMetaDataEntry("connection_type", self._global_container_stack.getMetaDataEntry("connection_type"))
else: else:
Logger.log("i", "Found a %s with the key %s. Let's use it!", machine_name, self.activeMachineNetworkKey()) 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) # 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._global_container_stack.setMetaDataEntry("hidden", True)
self.setActiveMachine(new_machine.getId()) self.setActiveMachine(new_machine.getId())
@ -1654,3 +1657,7 @@ class MachineManager(QObject):
if results: if results:
machine_type_name = results[0]["name"] machine_type_name = results[0]["name"]
return machine_type_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)

View File

@ -15,6 +15,7 @@ class AddPrinterPagesModel(WelcomePagesModel):
"page_url": self._getBuiltinWelcomePagePath("AddNetworkOrLocalPrinterContent.qml"), "page_url": self._getBuiltinWelcomePagePath("AddNetworkOrLocalPrinterContent.qml"),
"next_page_id": "machine_actions", "next_page_id": "machine_actions",
"next_page_button_text": self._catalog.i18nc("@action:button", "Add"), "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", self._pages.append({"id": "add_printer_by_ip",
"page_url": self._getBuiltinWelcomePagePath("AddPrinterByIpContent.qml"), "page_url": self._getBuiltinWelcomePagePath("AddPrinterByIpContent.qml"),

View File

@ -39,6 +39,7 @@ class WelcomePagesModel(ListModel):
PageUrlRole = Qt.UserRole + 2 # URL to the page's QML file PageUrlRole = Qt.UserRole + 2 # URL to the page's QML file
NextPageIdRole = Qt.UserRole + 3 # The next page ID it should go to NextPageIdRole = Qt.UserRole + 3 # The next page ID it should go to
NextPageButtonTextRole = Qt.UserRole + 4 # The text for the next page button 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: def __init__(self, application: "CuraApplication", parent: Optional["QObject"] = None) -> None:
super().__init__(parent) super().__init__(parent)
@ -47,6 +48,7 @@ class WelcomePagesModel(ListModel):
self.addRoleName(self.PageUrlRole, "page_url") self.addRoleName(self.PageUrlRole, "page_url")
self.addRoleName(self.NextPageIdRole, "next_page_id") self.addRoleName(self.NextPageIdRole, "next_page_id")
self.addRoleName(self.NextPageButtonTextRole, "next_page_button_text") self.addRoleName(self.NextPageButtonTextRole, "next_page_button_text")
self.addRoleName(self.PreviousPageButtonTextRole, "previous_page_button_text")
self._application = application self._application = application
self._catalog = i18nCatalog("cura") self._catalog = i18nCatalog("cura")

View File

@ -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"]

View File

@ -71,7 +71,7 @@ Window
left: parent.left left: parent.left
right: parent.right 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 wrapMode: Text.WordWrap
renderType: Text.NativeRendering renderType: Text.NativeRendering
} }
@ -106,7 +106,7 @@ Window
Cura.RadioButton Cura.RadioButton
{ {
id: dontSendButton 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: onClicked:
{ {
baseDialog.allowSendData = !checked baseDialog.allowSendData = !checked
@ -115,7 +115,7 @@ Window
Cura.RadioButton Cura.RadioButton
{ {
id: allowSendButton 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: onClicked:
{ {
baseDialog.allowSendData = checked baseDialog.allowSendData = checked

View File

@ -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. // Cura is released under the terms of the LGPLv3 or higher.
import UM 1.2 as UM import UM 1.2 as UM
import Cura 1.0 as Cura import Cura 1.5 as Cura
import QtQuick 2.2 import QtQuick 2.2
import QtQuick.Controls 1.1 import QtQuick.Controls 1.1
@ -14,9 +14,13 @@ Cura.MachineAction
{ {
id: base id: base
anchors.fill: parent; anchors.fill: parent;
property alias currentItemIndex: listview.currentIndex
property var selectedDevice: null property var selectedDevice: null
property bool completeProperties: true property bool completeProperties: true
// For validating IP addresses
property var networkingUtil: Cura.NetworkingUtil {}
function connectToPrinter() function connectToPrinter()
{ {
if(base.selectedDevice && base.completeProperties) if(base.selectedDevice && base.completeProperties)
@ -342,6 +346,17 @@ Cura.MachineAction
} }
} }
MessageDialog
{
id: invalidIPAddressMessageDialog
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
standardButtons: StandardButton.Ok
}
UM.Dialog UM.Dialog
{ {
id: manualPrinterDialog id: manualPrinterDialog
@ -404,6 +419,26 @@ Cura.MachineAction
text: catalog.i18nc("@action:button", "OK") text: catalog.i18nc("@action:button", "OK")
onClicked: 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) manager.setManualDevice(manualPrinterDialog.printerKey, manualPrinterDialog.addressText)
manualPrinterDialog.hide() manualPrinterDialog.hide()
} }

View File

@ -110,14 +110,12 @@ class DiscoverUM3Action(MachineAction):
Logger.log("d", "Attempting to set the group name of the active machine to %s", group_name) Logger.log("d", "Attempting to set the group name of the active machine to %s", group_name)
global_container_stack = CuraApplication.getInstance().getGlobalContainerStack() global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
if global_container_stack: if global_container_stack:
meta_data = global_container_stack.getMetaData() # Update a GlobalStacks in the same group with the new group name.
if "group_name" in meta_data: group_id = global_container_stack.getMetaDataEntry("group_id")
previous_connect_group_name = meta_data["group_name"] machine_manager = CuraApplication.getInstance().getMachineManager()
global_container_stack.setMetaDataEntry("group_name", group_name) for machine in machine_manager.getMachinesInGroup(group_id):
# Find all the places where there is the same group name and change it accordingly machine.setMetaDataEntry("group_name", group_name)
self._replaceContainersMetadata(key = "group_name", value = previous_connect_group_name, new_value = group_name)
else:
global_container_stack.setMetaDataEntry("group_name", group_name)
# Set the default value for "hidden", which is used when you have a group with multiple types of printers # 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) global_container_stack.setMetaDataEntry("hidden", False)
@ -125,13 +123,6 @@ class DiscoverUM3Action(MachineAction):
# Ensure that the connection states are refreshed. # Ensure that the connection states are refreshed.
self._network_plugin.refreshConnections() 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 # 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. # stored into the metadata of the currently active machine.
@pyqtSlot(QObject) @pyqtSlot(QObject)

View File

@ -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) 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: if not global_container_stack:
return 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. # Delete old authentication data.
old_network_key = meta_data["um_network_key"] Logger.log("d", "Removing old authentication id %s for device %s",
# Since we might have a bunch of hidden stacks, we also need to change it there. global_container_stack.getMetaDataEntry("network_authentication_id", None), printer_device.key)
metadata_filter = {"um_network_key": old_network_key}
containers = self._application.getContainerRegistry().findContainerStacks(type = "machine", **metadata_filter)
for container in containers: machine.removeMetaDataEntry("network_authentication_id")
container.setMetaDataEntry("um_network_key", printer_device.key) machine.removeMetaDataEntry("network_authentication_key")
# Delete old authentication data. # Ensure that these containers do know that they are configured for network connection
Logger.log("d", "Removing old authentication id %s for device %s", machine.addConfiguredConnectionType(printer_device.connectionType.value)
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)
self.refreshConnections() self.refreshConnections()

View File

@ -3,6 +3,7 @@
import configparser import configparser
import io import io
import uuid
from typing import Dict, List, Tuple from typing import Dict, List, Tuple
from UM.VersionUpgrade import VersionUpgrade from UM.VersionUpgrade import VersionUpgrade
@ -18,6 +19,7 @@ _renamed_quality_profiles = {
"gmax15plus_pla_very_thick": "gmax15plus_global_very_thick" "gmax15plus_pla_very_thick": "gmax15plus_global_very_thick"
} # type: Dict[str, str] } # type: Dict[str, str]
## Upgrades configurations from the state they were in at version 4.0 to the ## Upgrades configurations from the state they were in at version 4.0 to the
# state they should be in at version 4.1. # state they should be in at version 4.1.
class VersionUpgrade40to41(VersionUpgrade): class VersionUpgrade40to41(VersionUpgrade):
@ -95,6 +97,13 @@ class VersionUpgrade40to41(VersionUpgrade):
if parser["containers"]["4"] in _renamed_quality_profiles: if parser["containers"]["4"] in _renamed_quality_profiles:
parser["containers"]["4"] = _renamed_quality_profiles[parser["containers"]["4"]] 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() result = io.StringIO()
parser.write(result) parser.write(result)
return [filename], [result.getvalue()] return [filename], [result.getvalue()]

View File

@ -795,7 +795,6 @@ UM.MainWindow
title: catalog.i18nc("@title:window", "Add Printer") title: catalog.i18nc("@title:window", "Add Printer")
model: CuraApplication.getAddPrinterPagesModel() model: CuraApplication.getAddPrinterPagesModel()
progressBarVisible: false progressBarVisible: false
hasCancelButton: true
} }
Cura.WizardDialog Cura.WizardDialog
@ -804,7 +803,6 @@ UM.MainWindow
title: catalog.i18nc("@title:window", "What's New") title: catalog.i18nc("@title:window", "What's New")
model: CuraApplication.getWhatsNewPagesModel() model: CuraApplication.getWhatsNewPagesModel()
progressBarVisible: false progressBarVisible: false
hasCancelButton: false
} }
Connections Connections

View File

@ -32,16 +32,7 @@ ListView
width: listView.width width: listView.width
outputDevice: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null outputDevice: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null
checked: checked: Cura.MachineManager.activeMachineId == model.id
{
// 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
}
onClicked: onClicked:
{ {

View File

@ -98,6 +98,20 @@ Item
} }
} }
// This "Back" button only shows in the "Add Machine" dialog, which has "previous_page_button_text" set to "Cancel"
Cura.SecondaryButton
{
id: backButton
anchors.left: parent.left
anchors.bottom: parent.bottom
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()
}
}
Cura.PrimaryButton Cura.PrimaryButton
{ {
id: nextButton id: nextButton

View File

@ -76,15 +76,28 @@ Item
Component.onCompleted: 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++) for (var i = 0; i < count; i++)
{ {
if (!model[i].isUnknownMachineType) if (!model[i].isUnknownMachineType && model[i].isHostOfGroup)
{ {
currentIndex = i toSelectIndex = i
break 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 Component

View File

@ -6,7 +6,7 @@ import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
import UM 1.3 as UM import UM 1.3 as UM
import Cura 1.1 as Cura import Cura 1.5 as Cura
// //
@ -22,9 +22,35 @@ Item
property bool hasRequestInProgress: CuraApplication.getDiscoveredPrintersModel().hasManualDeviceRequestInProgress property bool hasRequestInProgress: CuraApplication.getDiscoveredPrintersModel().hasManualDeviceRequestInProgress
// Indicates if a request has finished. // Indicates if a request has finished.
property bool hasRequestFinished: false property bool hasRequestFinished: false
property string currentRequestAddress: ""
property var discoveredPrinter: null 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 {}
// 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. // Make sure to cancel the current request when this page closes.
onVisibleChanged: onVisibleChanged:
@ -93,17 +119,36 @@ Item
anchors.verticalCenter: addPrinterButton.verticalCenter anchors.verticalCenter: addPrinterButton.verticalCenter
anchors.left: parent.left anchors.left: parent.left
signal invalidInputDetected()
onInvalidInputDetected: invalidInputLabel.visible = true
validator: RegExpValidator 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-fA-F0-9.:]+)?/
} }
onTextEdited: invalidInputLabel.visible = false
placeholderText: catalog.i18nc("@text", "Place enter your printer's IP address.") placeholderText: catalog.i18nc("@text", "Place enter your printer's IP address.")
enabled: { ! (addPrinterByIpScreen.hasRequestInProgress || addPrinterByIpScreen.isPrinterDiscovered) } enabled: { ! (addPrinterByIpScreen.hasRequestInProgress || addPrinterByIpScreen.isPrinterDiscovered) }
onAccepted: addPrinterButton.clicked() 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", "Please enter a valid IP address.")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
renderType: Text.NativeRendering
}
Cura.SecondaryButton Cura.SecondaryButton
{ {
id: addPrinterButton id: addPrinterButton
@ -115,14 +160,21 @@ Item
onClicked: onClicked:
{ {
const address = hostnameField.text const address = hostnameField.text
if (!networkingUtil.isValidIP(address))
{
hostnameField.invalidInputDetected()
return
}
// This address is already in the discovered printer model, no need to add a manual discovery. // This address is already in the discovered printer model, no need to add a manual discovery.
if (CuraApplication.getDiscoveredPrintersModel().discoveredPrintersByAddress[address]) if (CuraApplication.getDiscoveredPrintersModel().discoveredPrintersByAddress[address])
{ {
addPrinterByIpScreen.discoveredPrinter = CuraApplication.getDiscoveredPrintersModel().discoveredPrintersByAddress[address] addPrinterByIpScreen.discoveredPrinter = CuraApplication.getDiscoveredPrintersModel().discoveredPrintersByAddress[address]
addPrinterByIpScreen.hasRequestFinished = true
return return
} }
addPrinterByIpScreen.currentRequestAddress = address
CuraApplication.getDiscoveredPrintersModel().checkManualDevice(address) CuraApplication.getDiscoveredPrintersModel().checkManualDevice(address)
} }
busy: addPrinterByIpScreen.hasRequestInProgress busy: addPrinterByIpScreen.hasRequestInProgress
@ -161,6 +213,8 @@ Item
Item Item
{ {
id: printerInfoLabels id: printerInfoLabels
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.margins: UM.Theme.getSize("default_margin").width anchors.margins: UM.Theme.getSize("default_margin").width
@ -177,10 +231,24 @@ Item
text: !addPrinterByIpScreen.isPrinterDiscovered ? "???" : addPrinterByIpScreen.discoveredPrinter.name 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 GridLayout
{ {
id: printerInfoGrid id: printerInfoGrid
anchors.top: printerNameLabel.bottom anchors.top: printerCannotBeAddedLabel ? printerCannotBeAddedLabel.bottom : printerNameLabel.bottom
anchors.margins: UM.Theme.getSize("default_margin").width anchors.margins: UM.Theme.getSize("default_margin").width
columns: 2 columns: 2
columnSpacing: UM.Theme.getSize("default_margin").width columnSpacing: UM.Theme.getSize("default_margin").width
@ -253,7 +321,7 @@ Item
} }
} }
Cura.PrimaryButton Cura.SecondaryButton
{ {
id: backButton id: backButton
anchors.left: parent.left anchors.left: parent.left
@ -278,6 +346,6 @@ Item
base.showNextPage() base.showNextPage()
} }
enabled: addPrinterByIpScreen.isPrinterDiscovered enabled: addPrinterByIpScreen.canAddPrinter
} }
} }

View File

@ -35,7 +35,7 @@ Item
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: catalog.i18nc("@label", "Ultimaker Cloud") text: catalog.i18nc("@label", "Ultimaker Cloud")
color: UM.Theme.getColor("primary_button") color: UM.Theme.getColor("primary_button")
font: UM.Theme.getFont("large_bold") font: UM.Theme.getFont("huge")
renderType: Text.NativeRendering renderType: Text.NativeRendering
} }

View File

@ -31,7 +31,6 @@ Window
property var model: null // Needs to be set by whoever is using this dialog. property var model: null // Needs to be set by whoever is using this dialog.
property alias progressBarVisible: wizardPanel.progressBarVisible property alias progressBarVisible: wizardPanel.progressBarVisible
property alias hasCancelButton: cancelButton.visible
onVisibilityChanged: onVisibilityChanged:
{ {
@ -54,21 +53,4 @@ Window
target: model target: model
onAllFinished: dialog.hide() 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
anchors.leftMargin: UM.Theme.getSize("wide_margin").width
enabled: true
onClicked: dialog.visible = false
}
} }