Merge pull request #7624 from Ultimaker/CURA-7022_Add_cloud_printer_within_add_a_connected_printer

Cura 7022 add cloud printer within add a connected printer
This commit is contained in:
Remco Burema 2020-05-06 11:13:27 +02:00 committed by GitHub
commit 2687578a86
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 364 additions and 16 deletions

View file

@ -29,7 +29,6 @@ class Account(QObject):
# Signal emitted when user logged in or out. # Signal emitted when user logged in or out.
loginStateChanged = pyqtSignal(bool) loginStateChanged = pyqtSignal(bool)
accessTokenChanged = pyqtSignal() accessTokenChanged = pyqtSignal()
cloudPrintersDetectedChanged = pyqtSignal(bool)
def __init__(self, application: "CuraApplication", parent = None) -> None: def __init__(self, application: "CuraApplication", parent = None) -> None:
super().__init__(parent) super().__init__(parent)
@ -76,10 +75,6 @@ class Account(QObject):
def isLoggedIn(self) -> bool: def isLoggedIn(self) -> bool:
return self._logged_in return self._logged_in
@pyqtProperty(bool, notify=cloudPrintersDetectedChanged)
def newCloudPrintersDetected(self) -> bool:
return self._new_cloud_printers_detected
def _onLoginStateChanged(self, logged_in: bool = False, error_message: Optional[str] = None) -> None: def _onLoginStateChanged(self, logged_in: bool = False, error_message: Optional[str] = None) -> None:
if error_message: if error_message:
if self._error_message: if self._error_message:

View file

@ -56,6 +56,7 @@ from cura.Machines.MachineErrorChecker import MachineErrorChecker
from cura.Machines.Models.BuildPlateModel import BuildPlateModel from cura.Machines.Models.BuildPlateModel import BuildPlateModel
from cura.Machines.Models.CustomQualityProfilesDropDownMenuModel import CustomQualityProfilesDropDownMenuModel from cura.Machines.Models.CustomQualityProfilesDropDownMenuModel import CustomQualityProfilesDropDownMenuModel
from cura.Machines.Models.DiscoveredPrintersModel import DiscoveredPrintersModel from cura.Machines.Models.DiscoveredPrintersModel import DiscoveredPrintersModel
from cura.Machines.Models.DiscoveredCloudPrintersModel import DiscoveredCloudPrintersModel
from cura.Machines.Models.ExtrudersModel import ExtrudersModel from cura.Machines.Models.ExtrudersModel import ExtrudersModel
from cura.Machines.Models.FavoriteMaterialsModel import FavoriteMaterialsModel from cura.Machines.Models.FavoriteMaterialsModel import FavoriteMaterialsModel
from cura.Machines.Models.FirstStartMachineActionsModel import FirstStartMachineActionsModel from cura.Machines.Models.FirstStartMachineActionsModel import FirstStartMachineActionsModel
@ -201,6 +202,7 @@ class CuraApplication(QtApplication):
self._quality_management_model = None self._quality_management_model = None
self._discovered_printer_model = DiscoveredPrintersModel(self, parent = self) self._discovered_printer_model = DiscoveredPrintersModel(self, parent = self)
self._discovered_cloud_printers_model = DiscoveredCloudPrintersModel(self, parent = self)
self._first_start_machine_actions_model = None self._first_start_machine_actions_model = None
self._welcome_pages_model = WelcomePagesModel(self, parent = self) self._welcome_pages_model = WelcomePagesModel(self, parent = self)
self._add_printer_pages_model = AddPrinterPagesModel(self, parent = self) self._add_printer_pages_model = AddPrinterPagesModel(self, parent = self)
@ -886,6 +888,10 @@ class CuraApplication(QtApplication):
def getDiscoveredPrintersModel(self, *args) -> "DiscoveredPrintersModel": def getDiscoveredPrintersModel(self, *args) -> "DiscoveredPrintersModel":
return self._discovered_printer_model return self._discovered_printer_model
@pyqtSlot(result=QObject)
def getDiscoveredCloudPrintersModel(self, *args) -> "DiscoveredCloudPrintersModel":
return self._discovered_cloud_printers_model
@pyqtSlot(result = QObject) @pyqtSlot(result = QObject)
def getFirstStartMachineActionsModel(self, *args) -> "FirstStartMachineActionsModel": def getFirstStartMachineActionsModel(self, *args) -> "FirstStartMachineActionsModel":
if self._first_start_machine_actions_model is None: if self._first_start_machine_actions_model is None:
@ -1084,6 +1090,7 @@ class CuraApplication(QtApplication):
self.processEvents() self.processEvents()
qmlRegisterType(DiscoveredPrintersModel, "Cura", 1, 0, "DiscoveredPrintersModel") qmlRegisterType(DiscoveredPrintersModel, "Cura", 1, 0, "DiscoveredPrintersModel")
qmlRegisterType(DiscoveredCloudPrintersModel, "Cura", 1, 7, "DiscoveredCloudPrintersModel")
qmlRegisterSingletonType(QualityProfilesDropDownMenuModel, "Cura", 1, 0, qmlRegisterSingletonType(QualityProfilesDropDownMenuModel, "Cura", 1, 0,
"QualityProfilesDropDownMenuModel", self.getQualityProfilesDropDownMenuModel) "QualityProfilesDropDownMenuModel", self.getQualityProfilesDropDownMenuModel)
qmlRegisterSingletonType(CustomQualityProfilesDropDownMenuModel, "Cura", 1, 0, qmlRegisterSingletonType(CustomQualityProfilesDropDownMenuModel, "Cura", 1, 0,

View file

@ -0,0 +1,71 @@
from typing import Optional, TYPE_CHECKING, List, Dict
from PyQt5.QtCore import QObject, pyqtSlot, Qt, pyqtSignal, pyqtProperty
from UM.Qt.ListModel import ListModel
if TYPE_CHECKING:
from cura.CuraApplication import CuraApplication
class DiscoveredCloudPrintersModel(ListModel):
"""
Model used to inform the application about newly added cloud printers, which are discovered from the user's account
"""
DeviceKeyRole = Qt.UserRole + 1
DeviceNameRole = Qt.UserRole + 2
DeviceTypeRole = Qt.UserRole + 3
DeviceFirmwareVersionRole = Qt.UserRole + 4
cloudPrintersDetectedChanged = pyqtSignal(bool)
def __init__(self, application: "CuraApplication", parent: Optional["QObject"] = None) -> None:
super().__init__(parent)
self.addRoleName(self.DeviceKeyRole, "key")
self.addRoleName(self.DeviceNameRole, "name")
self.addRoleName(self.DeviceTypeRole, "machine_type")
self.addRoleName(self.DeviceFirmwareVersionRole, "firmware_version")
self._discovered_cloud_printers_list = [] # type: List[Dict[str, str]]
self._application = application # type: CuraApplication
def addDiscoveredCloudPrinters(self, new_devices: List[Dict[str, str]]) -> None:
"""
Adds all the newly discovered cloud printers into the DiscoveredCloudPrintersModel.
:param new_devices: List of dictionaries which contain information about added cloud printers. Example:
{
"key": "YjW8pwGYcaUvaa0YgVyWeFkX3z",
"name": "NG 001",
"machine_type": "Ultimaker S5",
"firmware_version": "5.5.12.202001"
}
:return: None
"""
self._discovered_cloud_printers_list.extend(new_devices)
self._update()
# Inform whether new cloud printers have been detected. If they have, the welcome wizard can close.
self.cloudPrintersDetectedChanged.emit(len(new_devices) > 0)
@pyqtSlot()
def clear(self) -> None:
"""
Clears the contents of the DiscoveredCloudPrintersModel.
:return: None
"""
self._discovered_cloud_printers_list = []
self._update()
self.cloudPrintersDetectedChanged.emit(False)
def _update(self) -> None:
"""
Sorts the newly discovered cloud printers by name and then updates the ListModel.
:return: None
"""
items = self._discovered_cloud_printers_list[:]
items.sort(key = lambda k: k["name"])
self.setItems(items)

View file

@ -21,6 +21,11 @@ class AddPrinterPagesModel(WelcomePagesModel):
"page_url": self._getBuiltinWelcomePagePath("AddPrinterByIpContent.qml"), "page_url": self._getBuiltinWelcomePagePath("AddPrinterByIpContent.qml"),
"next_page_id": "machine_actions", "next_page_id": "machine_actions",
}) })
self._pages.append({"id": "add_cloud_printers",
"page_url": self._getBuiltinWelcomePagePath("AddCloudPrintersView.qml"),
"is_final_page": True,
"next_page_button_text": self._catalog.i18nc("@action:button", "Finish"),
})
self._pages.append({"id": "machine_actions", self._pages.append({"id": "machine_actions",
"page_url": self._getBuiltinWelcomePagePath("FirstStartMachineActionsContent.qml"), "page_url": self._getBuiltinWelcomePagePath("FirstStartMachineActionsContent.qml"),
"should_show_function": self.shouldShowMachineActions, "should_show_function": self.shouldShowMachineActions,

View file

@ -119,8 +119,10 @@ class WelcomePagesModel(ListModel):
return return
next_page_index = idx next_page_index = idx
is_final_page = page_item.get("is_final_page")
# If we have reached the last page, emit allFinished signal and reset. # If we have reached the last page, emit allFinished signal and reset.
if next_page_index == len(self._items): if next_page_index == len(self._items) or is_final_page:
self.atEnd() self.atEnd()
return return
@ -255,6 +257,11 @@ class WelcomePagesModel(ListModel):
"page_url": self._getBuiltinWelcomePagePath("AddPrinterByIpContent.qml"), "page_url": self._getBuiltinWelcomePagePath("AddPrinterByIpContent.qml"),
"next_page_id": "machine_actions", "next_page_id": "machine_actions",
}, },
{"id": "add_cloud_printers",
"page_url": self._getBuiltinWelcomePagePath("AddCloudPrintersView.qml"),
"is_final_page": True, # If we end up in this page, the next button will close the dialog
"next_page_button_text": self._catalog.i18nc("@action:button", "Finish"),
},
{"id": "machine_actions", {"id": "machine_actions",
"page_url": self._getBuiltinWelcomePagePath("FirstStartMachineActionsContent.qml"), "page_url": self._getBuiltinWelcomePagePath("FirstStartMachineActionsContent.qml"),
"should_show_function": self.shouldShowMachineActions, "should_show_function": self.shouldShowMachineActions,

View file

@ -106,10 +106,6 @@ class CloudOutputDeviceManager:
self._onDevicesDiscovered(new_clusters) self._onDevicesDiscovered(new_clusters)
# Inform whether new cloud printers have been detected. If they have, the welcome wizard can close.
self._account._new_cloud_printers_detected = len(new_clusters) > 0
self._account.cloudPrintersDetectedChanged.emit(len(new_clusters) > 0)
removed_device_keys = set(self._remote_clusters.keys()) - set(online_clusters.keys()) removed_device_keys = set(self._remote_clusters.keys()) - set(online_clusters.keys())
for device_id in removed_device_keys: for device_id in removed_device_keys:
self._onDiscoveredDeviceRemoved(device_id) self._onDiscoveredDeviceRemoved(device_id)
@ -141,10 +137,20 @@ class CloudOutputDeviceManager:
if machine_manager.getMachine(device.printerType, {self.META_CLUSTER_ID: device.key}) is None \ if machine_manager.getMachine(device.printerType, {self.META_CLUSTER_ID: device.key}) is None \
and machine_manager.getMachine(device.printerType, {self.META_NETWORK_KEY: cluster_data.host_name + "*"}) is None: # The host name is part of the network key. and machine_manager.getMachine(device.printerType, {self.META_NETWORK_KEY: cluster_data.host_name + "*"}) is None: # The host name is part of the network key.
new_devices.append(device) new_devices.append(device)
elif device.getId() not in self._remote_clusters: elif device.getId() not in self._remote_clusters:
self._remote_clusters[device.getId()] = device self._remote_clusters[device.getId()] = device
remote_clusters_added = True remote_clusters_added = True
# Inform the Cloud printers model about new devices.
new_devices_list_of_dicts = [{
"key": d.getId(),
"name": d.name,
"machine_type": d.printerTypeName,
"firmware_version": d.firmwareVersion} for d in new_devices]
discovered_cloud_printers_model = CuraApplication.getInstance().getDiscoveredCloudPrintersModel()
discovered_cloud_printers_model.addDiscoveredCloudPrinters(new_devices_list_of_dicts)
if not new_devices: if not new_devices:
if remote_clusters_added: if remote_clusters_added:
self._connectToActiveMachine() self._connectToActiveMachine()

View file

@ -0,0 +1,192 @@
// Copyright (c) 2019 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
import UM 1.3 as UM
import Cura 1.7 as Cura
//
// This component gets activated when the user presses the "Add cloud printers" button from the "Add a Printer" page.
// It contains a busy indicator that remains active until the user logs in and adds a cloud printer in his/her account.
// Once a cloud printer is added in mycloud.ultimaker.com, Cura discovers it (in a time window of 30 sec) and displays
// the newly added printers in this page.
//
Item
{
UM.I18nCatalog { id: catalog; name: "cura" }
property bool searchingForCloudPrinters: true
property var discoveredCloudPrintersModel: CuraApplication.getDiscoveredCloudPrintersModel()
// The area where either the discoveredCloudPrintersScrollView or the busyIndicator will be displayed
Rectangle
{
id: cloudPrintersContent
width: parent.width
height: parent.height
anchors
{
top: parent.top
left: parent.left
leftMargin: UM.Theme.getSize("default_margin").width
right: parent.right
bottom: finishButton.top
bottomMargin: UM.Theme.getSize("default_margin").height
}
Label
{
id: titleLabel
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Text.AlignHCenter
text: catalog.i18nc("@label", "Add a Cloud printer")
color: UM.Theme.getColor("primary_button")
font: UM.Theme.getFont("huge")
renderType: Text.NativeRendering
}
// Component that contains a busy indicator and a message, while it waits for Cura to discover a cloud printer
Rectangle
{
id: waitingContent
width: parent.width
height: waitingIndicator.height + waitingLabel.height
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
BusyIndicator
{
id: waitingIndicator
anchors.horizontalCenter: parent.horizontalCenter
running: searchingForCloudPrinters
}
Label
{
id: waitingLabel
anchors.top: waitingIndicator.bottom
anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Text.AlignHCenter
text: catalog.i18nc("@label", "Waiting for Cloud response")
font: UM.Theme.getFont("large")
renderType: Text.NativeRendering
}
visible: discoveredCloudPrintersModel.count == 0
}
// Label displayed when a new cloud printer is discovered
Label
{
anchors.top: titleLabel.bottom
anchors.topMargin: 2 * UM.Theme.getSize("default_margin").height
id: cloudPrintersAddedTitle
font: UM.Theme.getFont("medium")
text: catalog.i18nc("@label", "The following printers in your account have been added in Cura:")
height: contentHeight + 2 * UM.Theme.getSize("default_margin").height
visible: discoveredCloudPrintersModel.count > 0
}
// The scrollView that contains the list of newly discovered Ultimaker Cloud printers. Visible only when
// there is at least a new cloud printer.
ScrollView
{
id: discoveredCloudPrintersScrollView
width: parent.width
clip : true
ScrollBar.horizontal.policy: ScrollBar.AsNeeded
ScrollBar.vertical.policy: ScrollBar.AsNeeded
visible: discoveredCloudPrintersModel.count > 0
anchors
{
top: cloudPrintersAddedTitle.bottom
topMargin: UM.Theme.getSize("default_margin").height
left: parent.left
leftMargin: UM.Theme.getSize("default_margin").width
right: parent.right
bottom: parent.bottom
}
Column
{
id: discoveredPrintersColumn
spacing: 2 * UM.Theme.getSize("default_margin").height
Repeater
{
id: discoveredCloudPrintersRepeater
model: discoveredCloudPrintersModel
delegate: Item
{
width: discoveredCloudPrintersScrollView.width
height: contentColumn.height
Column
{
id: contentColumn
Label
{
id: cloudPrinterNameLabel
leftPadding: UM.Theme.getSize("default_margin").width
text: model.name
font: UM.Theme.getFont("large_bold")
color: UM.Theme.getColor("text")
elide: Text.ElideRight
}
Label
{
id: cloudPrinterTypeLabel
leftPadding: 2 * UM.Theme.getSize("default_margin").width
topPadding: UM.Theme.getSize("thin_margin").height
text: {"Type: " + model.machine_type}
font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("text")
elide: Text.ElideRight
}
Label
{
id: cloudPrinterFirmwareVersionLabel
leftPadding: 2 * UM.Theme.getSize("default_margin").width
text: {"Firmware version: " + model.firmware_version}
font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("text")
elide: Text.ElideRight
}
}
}
}
}
}
}
Cura.SecondaryButton
{
id: backButton
anchors.left: parent.left
anchors.bottom: parent.bottom
text: catalog.i18nc("@button", "Add printer manually")
onClicked:
{
discoveredCloudPrintersModel.clear()
base.showPreviousPage()
}
visible: discoveredCloudPrintersModel.count == 0
}
Cura.PrimaryButton
{
id: finishButton
anchors.right: parent.right
anchors.bottom: parent.bottom
text: catalog.i18nc("@button", "Finish")
onClicked:
{
discoveredCloudPrintersModel.clear()
base.showNextPage()
}
enabled: !waitingContent.visible
}
}

View file

@ -65,6 +65,20 @@ Item
{ {
base.goToPage("add_printer_by_ip") base.goToPage("add_printer_by_ip")
} }
onAddCloudPrinterButtonClicked:
{
base.goToPage("add_cloud_printers")
if (!Cura.API.account.isLoggedIn)
{
Cura.API.account.login()
}
else
{
Qt.openUrlExternally("https://mycloud.ultimaker.com/app/manage/printers")
}
}
} }
} }
} }

View file

@ -24,6 +24,7 @@ Item
signal refreshButtonClicked() signal refreshButtonClicked()
signal addByIpButtonClicked() signal addByIpButtonClicked()
signal addCloudPrinterButtonClicked()
Item Item
{ {
@ -193,6 +194,20 @@ Item
onClicked: base.addByIpButtonClicked() onClicked: base.addByIpButtonClicked()
} }
Cura.SecondaryButton
{
id: addCloudPrinterButton
anchors.left: addPrinterByIpButton.right
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.verticalCenter: parent.verticalCenter
text: catalog.i18nc("@label", "Add cloud printer")
height: UM.Theme.getSize("message_action_button").height
onClicked: {
CuraApplication.getDiscoveredCloudPrintersModel().clear()
base.addCloudPrinterButtonClicked()
}
}
Item Item
{ {
id: troubleshootingButton id: troubleshootingButton

View file

@ -15,17 +15,18 @@ Item
{ {
UM.I18nCatalog { id: catalog; name: "cura" } UM.I18nCatalog { id: catalog; name: "cura" }
property bool newCloudPrintersDetected: Cura.API.account.newCloudPrintersDetected
signal cloudPrintersDetected(bool newCloudPrintersDetected) signal cloudPrintersDetected(bool newCloudPrintersDetected)
Component.onCompleted: Cura.API.account.cloudPrintersDetectedChanged.connect(cloudPrintersDetected) Component.onCompleted: CuraApplication.getDiscoveredCloudPrintersModel().cloudPrintersDetectedChanged.connect(cloudPrintersDetected)
onCloudPrintersDetected: onCloudPrintersDetected:
{ {
// When the user signs in successfully, it will be checked whether he/she has cloud printers connected to // When the user signs in successfully, it will be checked whether he/she has cloud printers connected to
// the account. If he/she does, then the welcome wizard can close. If not, then proceed to the next page (if any) // the account. If he/she does, then the welcome wizard will show a summary of the Cloud printers linked to the
// account. If there are no cloud printers, then proceed to the next page (if any)
if(newCloudPrintersDetected) if(newCloudPrintersDetected)
{ {
base.endWizard() base.goToPage("add_cloud_printers")
} }
else else
{ {

View file

@ -21,8 +21,8 @@ Item
anchors.centerIn: parent anchors.centerIn: parent
width: 580 * screenScaleFactor width: UM.Theme.getSize("welcome_wizard_window").width
height: 600 * screenScaleFactor height: UM.Theme.getSize("welcome_wizard_window").height
property int shadowOffset: 1 * screenScaleFactor property int shadowOffset: 1 * screenScaleFactor

View file

@ -573,6 +573,7 @@
"monitor_preheat_temperature_control": [4.5, 2.0], "monitor_preheat_temperature_control": [4.5, 2.0],
"welcome_wizard_window": [46.0, 45],
"modal_window_minimum": [60.0, 45], "modal_window_minimum": [60.0, 45],
"license_window_minimum": [45, 45], "license_window_minimum": [45, 45],
"wizard_progress": [10.0, 0.0], "wizard_progress": [10.0, 0.0],

View file

@ -0,0 +1,34 @@
from unittest.mock import MagicMock
import pytest
from cura.Machines.Models.DiscoveredCloudPrintersModel import DiscoveredCloudPrintersModel
@pytest.fixture()
def discovered_cloud_printers_model(application) -> DiscoveredCloudPrintersModel:
return DiscoveredCloudPrintersModel(application)
def test_discoveredCloudPrinters(discovered_cloud_printers_model):
new_devices = [{
"key": "Bite my shiny metal a$$",
"name": "Bender",
"machine_type": "Bender robot",
"firmware_version": "8.0.0.8.5"
}]
discovered_cloud_printers_model.cloudPrintersDetectedChanged = MagicMock()
# Test if adding a cloud printer in the model works
discovered_cloud_printers_model.addDiscoveredCloudPrinters(new_devices)
assert len(discovered_cloud_printers_model._discovered_cloud_printers_list) == 1
assert discovered_cloud_printers_model.cloudPrintersDetectedChanged.emit.call_count == 1
# Make sure that the signal was called with "True" as input
discovered_cloud_printers_model.cloudPrintersDetectedChanged.emit.assert_called_with(True)
# Test if clearing the model works
discovered_cloud_printers_model.clear()
assert len(discovered_cloud_printers_model._discovered_cloud_printers_list) == 0
assert discovered_cloud_printers_model.cloudPrintersDetectedChanged.emit.call_count == 2
# Make sure that the signal was called with "False" as input
discovered_cloud_printers_model.cloudPrintersDetectedChanged.emit.assert_called_with(False)