diff --git a/cura/API/Account.py b/cura/API/Account.py index 9864de1aaa..7273479de4 100644 --- a/cura/API/Account.py +++ b/cura/API/Account.py @@ -29,7 +29,6 @@ class Account(QObject): # Signal emitted when user logged in or out. loginStateChanged = pyqtSignal(bool) accessTokenChanged = pyqtSignal() - cloudPrintersDetectedChanged = pyqtSignal(bool) def __init__(self, application: "CuraApplication", parent = None) -> None: super().__init__(parent) @@ -76,10 +75,6 @@ class Account(QObject): def isLoggedIn(self) -> bool: 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: if error_message: if self._error_message: diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 993bb15ae2..67a9451282 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -56,6 +56,7 @@ from cura.Machines.MachineErrorChecker import MachineErrorChecker from cura.Machines.Models.BuildPlateModel import BuildPlateModel from cura.Machines.Models.CustomQualityProfilesDropDownMenuModel import CustomQualityProfilesDropDownMenuModel 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.FavoriteMaterialsModel import FavoriteMaterialsModel from cura.Machines.Models.FirstStartMachineActionsModel import FirstStartMachineActionsModel @@ -201,6 +202,7 @@ class CuraApplication(QtApplication): self._quality_management_model = None 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._welcome_pages_model = WelcomePagesModel(self, parent = self) self._add_printer_pages_model = AddPrinterPagesModel(self, parent = self) @@ -886,6 +888,10 @@ class CuraApplication(QtApplication): def getDiscoveredPrintersModel(self, *args) -> "DiscoveredPrintersModel": return self._discovered_printer_model + @pyqtSlot(result=QObject) + def getDiscoveredCloudPrintersModel(self, *args) -> "DiscoveredCloudPrintersModel": + return self._discovered_cloud_printers_model + @pyqtSlot(result = QObject) def getFirstStartMachineActionsModel(self, *args) -> "FirstStartMachineActionsModel": if self._first_start_machine_actions_model is None: @@ -1084,6 +1090,7 @@ class CuraApplication(QtApplication): self.processEvents() qmlRegisterType(DiscoveredPrintersModel, "Cura", 1, 0, "DiscoveredPrintersModel") + qmlRegisterType(DiscoveredCloudPrintersModel, "Cura", 1, 7, "DiscoveredCloudPrintersModel") qmlRegisterSingletonType(QualityProfilesDropDownMenuModel, "Cura", 1, 0, "QualityProfilesDropDownMenuModel", self.getQualityProfilesDropDownMenuModel) qmlRegisterSingletonType(CustomQualityProfilesDropDownMenuModel, "Cura", 1, 0, diff --git a/cura/Machines/Models/DiscoveredCloudPrintersModel.py b/cura/Machines/Models/DiscoveredCloudPrintersModel.py new file mode 100644 index 0000000000..23dcba6de7 --- /dev/null +++ b/cura/Machines/Models/DiscoveredCloudPrintersModel.py @@ -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) diff --git a/cura/PickingPass.py b/cura/PickingPass.py index 75ee21ef41..ea0d05ab4f 100644 --- a/cura/PickingPass.py +++ b/cura/PickingPass.py @@ -1,14 +1,16 @@ -# Copyright (c) 2018 Ultimaker B.V. +# Copyright (c) 2020 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from typing import Optional, TYPE_CHECKING from UM.Qt.QtApplication import QtApplication +from UM.Logger import Logger from UM.Math.Vector import Vector from UM.Resources import Resources from UM.View.RenderPass import RenderPass from UM.View.GL.OpenGL import OpenGL +from UM.View.GL.ShaderProgram import InvalidShaderProgramError from UM.View.RenderBatch import RenderBatch from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator @@ -31,7 +33,11 @@ class PickingPass(RenderPass): def render(self) -> None: if not self._shader: - self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "camera_distance.shader")) + try: + self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "camera_distance.shader")) + except InvalidShaderProgramError: + Logger.error("Unable to compile shader program: camera_distance.shader") + return width, height = self.getSize() self._gl.glViewport(0, 0, width, height) diff --git a/cura/PreviewPass.py b/cura/PreviewPass.py index 5e3982e1f3..7fcc4eb6cd 100644 --- a/cura/PreviewPass.py +++ b/cura/PreviewPass.py @@ -1,10 +1,11 @@ -# Copyright (c) 2018 Ultimaker B.V. +# Copyright (c) 2020 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from typing import Optional, TYPE_CHECKING, cast from UM.Application import Application +from UM.Logger import Logger from UM.Resources import Resources from UM.View.RenderPass import RenderPass @@ -63,6 +64,8 @@ class PreviewPass(RenderPass): self._shader.setUniformValue("u_shininess", 20.0) self._shader.setUniformValue("u_renderError", 0.0) # We don't want any error markers!. self._shader.setUniformValue("u_faceId", -1) # Don't render any selected faces in the preview. + else: + Logger.error("Unable to compile shader program: overhang.shader") if not self._non_printing_shader: if self._non_printing_shader: diff --git a/cura/UI/AddPrinterPagesModel.py b/cura/UI/AddPrinterPagesModel.py index d40da59b2a..b06f220374 100644 --- a/cura/UI/AddPrinterPagesModel.py +++ b/cura/UI/AddPrinterPagesModel.py @@ -21,6 +21,11 @@ class AddPrinterPagesModel(WelcomePagesModel): "page_url": self._getBuiltinWelcomePagePath("AddPrinterByIpContent.qml"), "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", "page_url": self._getBuiltinWelcomePagePath("FirstStartMachineActionsContent.qml"), "should_show_function": self.shouldShowMachineActions, diff --git a/cura/UI/WelcomePagesModel.py b/cura/UI/WelcomePagesModel.py index 611e62cfd6..b816833d67 100644 --- a/cura/UI/WelcomePagesModel.py +++ b/cura/UI/WelcomePagesModel.py @@ -119,8 +119,10 @@ class WelcomePagesModel(ListModel): return 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 next_page_index == len(self._items): + if next_page_index == len(self._items) or is_final_page: self.atEnd() return @@ -255,6 +257,11 @@ class WelcomePagesModel(ListModel): "page_url": self._getBuiltinWelcomePagePath("AddPrinterByIpContent.qml"), "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", "page_url": self._getBuiltinWelcomePagePath("FirstStartMachineActionsContent.qml"), "should_show_function": self.shouldShowMachineActions, diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index 6a8a4a7347..0952315235 100755 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -489,7 +489,7 @@ class CuraEngineBackend(QObject, Backend): # # \param source The scene node that was changed. def _onSceneChanged(self, source: SceneNode) -> None: - if not source.callDecoration("isSliceable"): + if not source.callDecoration("isSliceable") and source != self._scene.getRoot(): return # This case checks if the source node is a node that contains GCode. In this case the diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index 46136e3a1b..1ed765d154 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -106,10 +106,6 @@ class CloudOutputDeviceManager: 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()) for device_id in removed_device_keys: self._onDiscoveredDeviceRemoved(device_id) @@ -141,10 +137,20 @@ class CloudOutputDeviceManager: 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. new_devices.append(device) + elif device.getId() not in self._remote_clusters: self._remote_clusters[device.getId()] = device 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 remote_clusters_added: self._connectToActiveMachine() diff --git a/resources/definitions/geeetech_A10M.def.json b/resources/definitions/geeetech_A10M.def.json index 1b0f46ad98..c8f0d16712 100644 --- a/resources/definitions/geeetech_A10M.def.json +++ b/resources/definitions/geeetech_A10M.def.json @@ -22,10 +22,10 @@ "default_value": 220 }, "machine_height": { - "default_value": 220 + "default_value": 260 }, "machine_depth": { - "default_value": 260 + "default_value": 220 }, "machine_center_is_zero": { "default_value": false }, diff --git a/resources/definitions/uni_base.def.json b/resources/definitions/uni_base.def.json index 6f40abfbee..6cdbc198a5 100644 --- a/resources/definitions/uni_base.def.json +++ b/resources/definitions/uni_base.def.json @@ -32,13 +32,6 @@ "emotiontech_pva-oks", "emotiontech_pva-s", "emotiontech_tpu98a", - "eSUN_PETG_Black", - "eSUN_PETG_Grey", - "eSUN_PETG_Purple", - "eSUN_PLA_PRO_Black", - "eSUN_PLA_PRO_Grey", - "eSUN_PLA_PRO_Purple", - "eSUN_PLA_PRO_White", "fabtotum_abs", "fabtotum_nylon", "fabtotum_pla", @@ -47,24 +40,6 @@ "filo3d_pla", "filo3d_pla_green", "filo3d_pla_red", - "generic_abs", - "generic_bam", - "generic_cffcpe", - "generic_cffpa", - "generic_cpe", - "generic_cpe_175", - "generic_cpe_plus", - "generic_gffcpe", - "generic_gffpa", - "generic_hips", - "generic_nylon", - "generic_pc", - "generic_petg", - "generic_pla", - "generic_pp", - "generic_pva", - "generic_tough_pla", - "generic_tpu", "imade3d_petg_175", "imade3d_pla_175", "innofill_innoflex60_175", @@ -76,6 +51,13 @@ "polymax_pla", "polyplus_pla", "polywood_pla", + "redd_abs", + "redd_asa", + "redd_hips", + "redd_nylon", + "redd_petg", + "redd_pla", + "redd_tpe", "structur3d_dap100silicone", "tizyx_abs", "tizyx_flex", @@ -146,8 +128,8 @@ }, "overrides": { "machine_name": {"default_value": "UNI Base Printer"}, - "machine_start_gcode": {"default_value": "G90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 Z0 ;move Z to min endstops\nG28 X0 Y0 ;move X/Y to min endstops\nG92 E0 ;zero the extruded length\nG1 F5000 ;set speed\nG1 Z15 F5000 ;move to start position Y\nM117 Printing..."}, - "machine_end_gcode": {"default_value": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off\nG91 ;relative positioning\nG1 E-4 F300 ;retract the filament a bit before lifting the nozzle to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F5000 ;move Z up a bit and retract filament even more\nG28 Z0 ;move bed down\nG28 X0 Y0 ;move X/Y to min endstops so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning\nM107 ;switch off cooling fan"}, + "machine_start_gcode": {"default_value": ";Sliced at: {day} {date} {time}\nG21\nG90\nM82\nM220 S100\nM221 S100\nG28\nM190 S{material_bed_temperature_layer_0}\nM109 S{material_print_temperature_layer_0}\nG1 F600 Z15\nG92 E0\nG1 F300 E10\nM117 Printing...\n;\n"}, + "machine_end_gcode": {"default_value": "G91\nG1 E-1 F600\nG1 Z+1 E-3 X-20 Y-20 F4800\nM104 S0\nM106 S0\nM140 S0\nM220 S100\nM221 S100\nG28\nG90\nM84\n;\n"}, "machine_heated_bed": {"default_value": true}, "material_diameter": {"default_value": 1.75}, "machine_shape": {"default_value": "rectangular"}, diff --git a/resources/definitions/uni_mini.def.json b/resources/definitions/uni_mini.def.json index 138e2ee183..c2b67039ea 100644 --- a/resources/definitions/uni_mini.def.json +++ b/resources/definitions/uni_mini.def.json @@ -11,8 +11,8 @@ }, "overrides": { "machine_name": {"default_value": "uni_mini"}, - "machine_width": {"default_value": 180}, - "machine_depth": {"default_value": 180}, + "machine_width": {"default_value": 160}, + "machine_depth": {"default_value": 160}, "machine_height": {"default_value": 175} } } diff --git a/resources/extruders/uni_extruder_1st.def.json b/resources/extruders/uni_extruder_1st.def.json index 5f6b819599..d11fc9d60d 100644 --- a/resources/extruders/uni_extruder_1st.def.json +++ b/resources/extruders/uni_extruder_1st.def.json @@ -15,11 +15,11 @@ "machine_nozzle_offset_y": { "default_value": 0.0 }, "machine_extruder_start_code": { - "default_value": "T0 ;switch to extruder 1\nG92 E0 ;reset extruder distance\nG1 F2000 E93 ;load filament\nG92 E0 ;reset extruder distance\nM104 S{material_print_temperature}\n;" + "default_value": ";T0 ;switch to extruder 1\n;G92 E0 ;reset extruder distance\n;G1 F2000 E93 ;load filament\n;G92 E0 ;reset extruder distance\n;M104 S{material_print_temperature}\n;\n" }, "machine_extruder_end_code": { - "default_value": "G92 E0 ;reset extruder distance\nG1 F800 E-5 ;short retract\nG1 F2400 X5 Y5 ;move near prime tower\nG1 F2000 E-93 ;long retract for filament removal\nG92 E0 ;reset extruder distance\nG90 ;absolute coordinate\n;" + "default_value": ";G92 E0 ;reset extruder distance\n;G1 F800 E-5 ;short retract\n;G1 F2400 X5 Y5 ;move near prime tower\n;G1 F2000 E-93 ;long retract for filament removal\n;G92 E0 ;reset extruder distance\n;G90 ;absolute coordinate\n;\n" } } } diff --git a/resources/extruders/uni_extruder_2nd.def.json b/resources/extruders/uni_extruder_2nd.def.json index 7b92b2f987..2fc2f054ab 100644 --- a/resources/extruders/uni_extruder_2nd.def.json +++ b/resources/extruders/uni_extruder_2nd.def.json @@ -11,15 +11,15 @@ "extruder_nr": { "default_value": 1, "maximum_value": "1" }, "machine_nozzle_size": { "default_value": 0.4 }, "material_diameter": { "default_value": 1.75 }, - "machine_nozzle_offset_x": { "default_value": 17.7 }, + "machine_nozzle_offset_x": { "default_value": 0.0 }, "machine_nozzle_offset_y": { "default_value": 0.0 }, "machine_extruder_start_code": { - "default_value": "T1 ;switch to extruder 2\nG92 E0 ;reset extruder distance\nG1 F2000 E93 ;load filament\nG92 E0 ;reset extruder distance\nM104 S{material_print_temperature}\n;" + "default_value": ";T1 ;switch to extruder 2\n;G92 E0 ;reset extruder distance\n;G1 F2000 E93 ;load filament\n;G92 E0 ;reset extruder distance\n;M104 S{material_print_temperature}\n;\n" }, "machine_extruder_end_code": { - "default_value": "G92 E0 ;reset extruder distance\nG1 F800 E-5 ;short retract\nG1 F2400 X5 Y5 ;move near prime tower\nG1 F2000 E-93 ;long retract for filament removal\nG92 E0 ;reset extruder distance\nG90 ;absolute coordinate\n;" + "default_value": ";G92 E0 ;reset extruder distance\n;G1 F800 E-5 ;short retract\n;G1 F2400 X5 Y5 ;move near prime tower\n;G1 F2000 E-93 ;long retract for filament removal\n;G92 E0 ;reset extruder distance\n;G90 ;absolute coordinate\n;\n" } } } diff --git a/resources/qml/WelcomePages/AddCloudPrintersView.qml b/resources/qml/WelcomePages/AddCloudPrintersView.qml new file mode 100644 index 0000000000..f97d68f776 --- /dev/null +++ b/resources/qml/WelcomePages/AddCloudPrintersView.qml @@ -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 + } +} diff --git a/resources/qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml b/resources/qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml index 6a2accf97d..9e892e5521 100644 --- a/resources/qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml +++ b/resources/qml/WelcomePages/AddNetworkOrLocalPrinterContent.qml @@ -65,6 +65,20 @@ Item { 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") + } + + } } } } diff --git a/resources/qml/WelcomePages/AddNetworkPrinterScrollView.qml b/resources/qml/WelcomePages/AddNetworkPrinterScrollView.qml index b4d4fee42c..af60c9c723 100644 --- a/resources/qml/WelcomePages/AddNetworkPrinterScrollView.qml +++ b/resources/qml/WelcomePages/AddNetworkPrinterScrollView.qml @@ -24,6 +24,7 @@ Item signal refreshButtonClicked() signal addByIpButtonClicked() + signal addCloudPrinterButtonClicked() Item { @@ -193,6 +194,20 @@ Item 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 { id: troubleshootingButton diff --git a/resources/qml/WelcomePages/CloudContent.qml b/resources/qml/WelcomePages/CloudContent.qml index 36a7b9a923..48410f7f12 100644 --- a/resources/qml/WelcomePages/CloudContent.qml +++ b/resources/qml/WelcomePages/CloudContent.qml @@ -15,17 +15,18 @@ Item { UM.I18nCatalog { id: catalog; name: "cura" } - property bool newCloudPrintersDetected: Cura.API.account.newCloudPrintersDetected signal cloudPrintersDetected(bool newCloudPrintersDetected) - Component.onCompleted: Cura.API.account.cloudPrintersDetectedChanged.connect(cloudPrintersDetected) + Component.onCompleted: CuraApplication.getDiscoveredCloudPrintersModel().cloudPrintersDetectedChanged.connect(cloudPrintersDetected) + onCloudPrintersDetected: { // 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) { - base.endWizard() + base.goToPage("add_cloud_printers") } else { diff --git a/resources/qml/WelcomePages/WelcomeDialogItem.qml b/resources/qml/WelcomePages/WelcomeDialogItem.qml index 7da4c6e897..5b90a8732e 100644 --- a/resources/qml/WelcomePages/WelcomeDialogItem.qml +++ b/resources/qml/WelcomePages/WelcomeDialogItem.qml @@ -21,8 +21,8 @@ Item anchors.centerIn: parent - width: 580 * screenScaleFactor - height: 600 * screenScaleFactor + width: UM.Theme.getSize("welcome_wizard_window").width + height: UM.Theme.getSize("welcome_wizard_window").height property int shadowOffset: 1 * screenScaleFactor diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index 1640395c0b..b870603bd9 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -573,6 +573,7 @@ "monitor_preheat_temperature_control": [4.5, 2.0], + "welcome_wizard_window": [46.0, 45], "modal_window_minimum": [60.0, 45], "license_window_minimum": [45, 45], "wizard_progress": [10.0, 0.0], diff --git a/tests/Machines/Models/TestDiscoveredCloudPrintersModel.py b/tests/Machines/Models/TestDiscoveredCloudPrintersModel.py new file mode 100644 index 0000000000..5b19178531 --- /dev/null +++ b/tests/Machines/Models/TestDiscoveredCloudPrintersModel.py @@ -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)