From a56a21cf9350ef03410467af92b4c059bf61d324 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 2 Sep 2022 17:49:39 +0200 Subject: [PATCH 01/18] W.I.P.: Press print on abstract cloud printer. User should see dialog. Start of implementation. When printing on an abstract printer, a user should see a dialog with the matching concrete cloud printers to pick from to actually print. Names are not final. Very much a work in progress. Very not finished also. start of implementation for CURA-9278 --- cura/CuraApplication.py | 2 + .../Machines/Models/CompatibleMachineModel.py | 73 +++++++++++++++++++ cura/Machines/Models/MachineListModel.py | 20 +++-- .../src/Cloud/AbstractCloudOutputDevice.py | 31 +++++++- resources/qml/Dialogs/ChoosePrinterDialog.qml | 34 +++++++++ 5 files changed, 151 insertions(+), 9 deletions(-) create mode 100644 cura/Machines/Models/CompatibleMachineModel.py create mode 100644 resources/qml/Dialogs/ChoosePrinterDialog.qml diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index f690456913..cc38149c03 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -115,6 +115,7 @@ from . import CuraActions from . import PlatformPhysics from . import PrintJobPreviewImageProvider from .AutoSave import AutoSave +from .Machines.Models.CompatibleMachineModel import CompatibleMachineModel from .Machines.Models.MachineListModel import MachineListModel from .Machines.Models.ActiveIntentQualitiesModel import ActiveIntentQualitiesModel from .Machines.Models.IntentSelectionModel import IntentSelectionModel @@ -1191,6 +1192,7 @@ class CuraApplication(QtApplication): qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel") qmlRegisterType(GlobalStacksModel, "Cura", 1, 0, "GlobalStacksModel") qmlRegisterType(MachineListModel, "Cura", 1, 0, "MachineListModel") + qmlRegisterType(CompatibleMachineModel, "Cura", 1, 0, "CompatibleMachineModel") self.processEvents() qmlRegisterType(FavoriteMaterialsModel, "Cura", 1, 0, "FavoriteMaterialsModel") diff --git a/cura/Machines/Models/CompatibleMachineModel.py b/cura/Machines/Models/CompatibleMachineModel.py new file mode 100644 index 0000000000..a0a6fd8bb2 --- /dev/null +++ b/cura/Machines/Models/CompatibleMachineModel.py @@ -0,0 +1,73 @@ +# Copyright (c) 2022 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +# TODO?: documentation + +from typing import Optional + +from PyQt6.QtCore import Qt, QTimer, QObject, pyqtSlot, pyqtProperty, pyqtSignal + +from UM.Qt.ListModel import ListModel +from UM.Settings.ContainerStack import ContainerStack +from UM.i18n import i18nCatalog +from UM.Util import parseBool + +from cura.PrinterOutput.PrinterOutputDevice import ConnectionType +from cura.Settings.CuraContainerRegistry import CuraContainerRegistry + + +class CompatibleMachineModel(ListModel): + NameRole = Qt.ItemDataRole.UserRole + 1 + IdRole = Qt.ItemDataRole.UserRole + 2 + ExtrudersRole = Qt.ItemDataRole.UserRole + 3 + + def __init__(self, parent: Optional[QObject] = None) -> None: + super().__init__(parent) + + self._filter_on_definition_id: Optional[str] = None + + self._catalog = i18nCatalog("cura") + + self.addRoleName(self.NameRole, "name") + self.addRoleName(self.IdRole, "id") + self.addRoleName(self.ExtrudersRole, "extruders") + + filterChanged = pyqtSignal(str) + + @pyqtSlot(str) + def setFilter(self, abstract_machine_id: str) -> None: + # TODO??: defensive coding; check if machine is abstract & abort/log if not + self._filter_on_definition_id = abstract_machine_id + + # Don't need a delayed update, since it's fire once on user click (either on 'print to cloud' or 'refresh'). + # So, no signals that could come in (too) quickly. + self.filterChanged.emit(self._filter_on_definition_id) + self._update() + + @pyqtProperty(str, fset=setFilter, notify=filterChanged) + def filter(self) -> str: + return self._filter_on_definition_id + + def _update(self) -> None: + self.clear() + if not self._filter_on_definition_id or self._filter_on_definition_id == "": + # TODO?: log + return + + from cura.CuraApplication import CuraApplication + machine_manager = CuraApplication.getInstance().getMachineManager() + compatible_machines = machine_manager.getMachinesWithDefinition(self._filter_on_definition_id, online_only = True) + # TODO: Handle 0 compatible machines -> option to close window? Message in card? (remember the design has a refresh button!) + + for container_stack in compatible_machines: + if parseBool(container_stack.getMetaDataEntry("hidden", False)) or parseBool(container_stack.getMetaDataEntry("is_abstract_machine", False)): + continue + self.addItem(container_stack) + + def addItem(self, container_stack: ContainerStack, machine_count: int = 0) -> None: + extruders = CuraContainerRegistry.getInstance().findContainerStacks(type="extruder_train", machine=container_stack.getId()) + self.appendItem({ + "name": container_stack.getName(), + "id": container_stack.getId(), + "extruders": [extruder.getMetaData().copy() for extruder in extruders] + }) diff --git a/cura/Machines/Models/MachineListModel.py b/cura/Machines/Models/MachineListModel.py index 55db072180..bc071e226f 100644 --- a/cura/Machines/Models/MachineListModel.py +++ b/cura/Machines/Models/MachineListModel.py @@ -5,10 +5,13 @@ # online cloud connected printers are represented within this ListModel. Additional information such as the number of # connected printers for each printer type is gathered. -from PyQt6.QtCore import Qt, QTimer, pyqtSlot, pyqtProperty, pyqtSignal +from typing import Optional + +from PyQt6.QtCore import Qt, QTimer, QObject, pyqtSlot, pyqtProperty, pyqtSignal from UM.Qt.ListModel import ListModel from UM.Settings.ContainerStack import ContainerStack +from UM.Settings.Interfaces import ContainerInterface from UM.i18n import i18nCatalog from UM.Util import parseBool from cura.PrinterOutput.PrinterOutputDevice import ConnectionType @@ -27,7 +30,7 @@ class MachineListModel(ListModel): IsAbstractMachineRole = Qt.ItemDataRole.UserRole + 7 ComponentTypeRole = Qt.ItemDataRole.UserRole + 8 - def __init__(self, parent=None) -> None: + def __init__(self, parent: Optional[QObject] = None) -> None: super().__init__(parent) self._show_cloud_printers = False @@ -66,7 +69,7 @@ class MachineListModel(ListModel): self._updateDelayed() self.showCloudPrintersChanged.emit(show_cloud_printers) - def _onContainerChanged(self, container) -> None: + def _onContainerChanged(self, container: ContainerInterface) -> None: """Handler for container added/removed events from registry""" # We only need to update when the added / removed container GlobalStack @@ -79,14 +82,15 @@ class MachineListModel(ListModel): def _update(self) -> None: self.clear() + from cura.CuraApplication import CuraApplication + machines_manager = CuraApplication.getInstance().getMachineManager() + other_machine_stacks = CuraContainerRegistry.getInstance().findContainerStacks(type="machine") abstract_machine_stacks = CuraContainerRegistry.getInstance().findContainerStacks(is_abstract_machine = "True") abstract_machine_stacks.sort(key = lambda machine: machine.getName(), reverse = True) for abstract_machine in abstract_machine_stacks: definition_id = abstract_machine.definition.getId() - from cura.CuraApplication import CuraApplication - machines_manager = CuraApplication.getInstance().getMachineManager() online_machine_stacks = machines_manager.getMachinesWithDefinition(definition_id, online_only = True) # Create a list item for abstract machine @@ -132,11 +136,11 @@ class MachineListModel(ListModel): has_connection |= connection_type in container_stack.configuredConnectionTypes self.appendItem({ - "componentType": "MACHINE", - "name": container_stack.getName(), + "componentType": "MACHINE", + "name": container_stack.getName(), "id": container_stack.getId(), "metadata": container_stack.getMetaData().copy(), "isOnline": parseBool(container_stack.getMetaDataEntry("is_online", False)) and has_connection, "isAbstractMachine": parseBool(container_stack.getMetaDataEntry("is_abstract_machine", False)), "machineCount": machine_count, - }) + }) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/AbstractCloudOutputDevice.py b/plugins/UM3NetworkPrinting/src/Cloud/AbstractCloudOutputDevice.py index 8448c095c8..e8f652064d 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/AbstractCloudOutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/AbstractCloudOutputDevice.py @@ -1,11 +1,16 @@ from time import time -from typing import List +from typing import List, Optional from PyQt6.QtCore import QObject from PyQt6.QtNetwork import QNetworkReply from UM import i18nCatalog from UM.Logger import Logger +from UM.FileHandler.FileHandler import FileHandler +from UM.Resources import Resources +from UM.Scene.SceneNode import SceneNode + +from cura.CuraApplication import CuraApplication from cura.PrinterOutput.NetworkedPrinterOutputDevice import AuthState from cura.PrinterOutput.PrinterOutputDevice import ConnectionType from .CloudApiClient import CloudApiClient @@ -31,6 +36,8 @@ class AbstractCloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): parent=parent ) + self._on_print_dialog: Optional[QObject] = None + self._setInterfaceElements() def connect(self) -> None: @@ -84,4 +91,26 @@ class AbstractCloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): self._updatePrinters(all_configurations) def _onError(self, reply: QNetworkReply, error: QNetworkReply.NetworkError) -> None: + # TODO! pass + + def _openChoosePrinterDialog(self, machine_filter_id: str) -> None: + if self._on_print_dialog is None: + qml_path = Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Dialogs", "ChoosePrinterDialog.qml") + self._on_print_dialog = CuraApplication.getInstance().createQmlComponent(qml_path, {}) + if self._on_print_dialog is None: # Failed to load QML file. + return + self._on_print_dialog.setProperty("machine_id_filter", machine_filter_id) + self._on_print_dialog.show() + + def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs) -> None: + + # TODO: + # - Prettify (and make usable) dialog. + # (Including extruders... their metadata is already in the model. Is that enough though. Does that contain configurations as well?) + # - On button clicked, fetch/push to here selected printer, hide dialog + # - Find correct output-device for selected printer maybe via `CuraApplication.getInstance().getOutputDeviceManager().getOutputDevices()` + # Call 'requestWrite' of the selected output-device. + + self._openChoosePrinterDialog(CuraApplication.getInstance().getGlobalContainerStack().definition.getId()) + diff --git a/resources/qml/Dialogs/ChoosePrinterDialog.qml b/resources/qml/Dialogs/ChoosePrinterDialog.qml new file mode 100644 index 0000000000..cddf38d347 --- /dev/null +++ b/resources/qml/Dialogs/ChoosePrinterDialog.qml @@ -0,0 +1,34 @@ +// Copyright (c) 2022 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.2 +import QtQuick.Controls 2.9 + +import UM 1.5 as UM +import Cura 1.0 as Cura + +UM.Dialog +{ + id: base + + property string machine_id_filter: "" + + Column + { + anchors.fill: parent + + Repeater + { + id: contents + + model: Cura.CompatibleMachineModel + { + filter: machine_id_filter + } + delegate: UM.Label + { + text: model.name + } + } + } +} From d3ee9c4c247815e0235b837936683b311067418f Mon Sep 17 00:00:00 2001 From: Joey de l'Arago Date: Mon, 5 Sep 2022 15:32:05 +0200 Subject: [PATCH 02/18] Fix extruderIcon sizing CURA-9278 --- resources/qml/ExtruderIcon.qml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/resources/qml/ExtruderIcon.qml b/resources/qml/ExtruderIcon.qml index fb3269ca78..718f1bcd87 100644 --- a/resources/qml/ExtruderIcon.qml +++ b/resources/qml/ExtruderIcon.qml @@ -8,16 +8,16 @@ Item { id: extruderIconItem - implicitWidth: UM.Theme.getSize("extruder_icon").width - implicitHeight: UM.Theme.getSize("extruder_icon").height - property bool checked: true property color materialColor property alias textColor: extruderNumberText.color property bool extruderEnabled: true - property var iconSize + property var iconSize: UM.Theme.getSize("extruder_icon").width property string iconVariant: "medium" + implicitWidth: iconSize + implicitHeight: iconSize + Item { opacity: extruderEnabled ? 1 : UM.Theme.getColor("extruder_disabled").a @@ -27,8 +27,8 @@ Item UM.ColorImage { anchors.fill: parent - width: mainIcon.width - height: mainIcon.height + width: iconSize + height: iconSize source: UM.Theme.getIcon("ExtruderColor", iconVariant) color: materialColor @@ -37,8 +37,8 @@ Item { id: mainIcon anchors.fill: parent - width: UM.Theme.getSize("extruder_icon").width - height: UM.Theme.getSize("extruder_icon").height + width: iconSize + height: iconSize source: UM.Theme.getIcon("Extruder", iconVariant) color: extruderNumberText.color From 7f98ef70f05034ba0f86a212f5181996dc932519 Mon Sep 17 00:00:00 2001 From: Joey de l'Arago Date: Mon, 5 Sep 2022 17:38:21 +0200 Subject: [PATCH 03/18] Add printer cards CURA-9278 --- .../Machines/Models/CompatibleMachineModel.py | 18 ++- resources/qml/Dialogs/ChoosePrinterDialog.qml | 31 ++-- .../qml/PrinterSelector/PrintSelectorCard.qml | 134 ++++++++++++++++++ resources/qml/qmldir | 1 + 4 files changed, 168 insertions(+), 16 deletions(-) create mode 100644 resources/qml/PrinterSelector/PrintSelectorCard.qml diff --git a/cura/Machines/Models/CompatibleMachineModel.py b/cura/Machines/Models/CompatibleMachineModel.py index a0a6fd8bb2..c87245e495 100644 --- a/cura/Machines/Models/CompatibleMachineModel.py +++ b/cura/Machines/Models/CompatibleMachineModel.py @@ -3,16 +3,15 @@ # TODO?: documentation -from typing import Optional +from typing import Optional, Dict -from PyQt6.QtCore import Qt, QTimer, QObject, pyqtSlot, pyqtProperty, pyqtSignal +from PyQt6.QtCore import Qt, QObject, pyqtSlot, pyqtProperty, pyqtSignal from UM.Qt.ListModel import ListModel from UM.Settings.ContainerStack import ContainerStack from UM.i18n import i18nCatalog from UM.Util import parseBool -from cura.PrinterOutput.PrinterOutputDevice import ConnectionType from cura.Settings.CuraContainerRegistry import CuraContainerRegistry @@ -64,10 +63,19 @@ class CompatibleMachineModel(ListModel): continue self.addItem(container_stack) - def addItem(self, container_stack: ContainerStack, machine_count: int = 0) -> None: + def addItem(self, container_stack: ContainerStack) -> None: extruders = CuraContainerRegistry.getInstance().findContainerStacks(type="extruder_train", machine=container_stack.getId()) self.appendItem({ "name": container_stack.getName(), "id": container_stack.getId(), - "extruders": [extruder.getMetaData().copy() for extruder in extruders] + "extruders": [self.getExtruderModel(extruder) for extruder in extruders] }) + + def getExtruderModel(self, extruders: ContainerStack) -> Dict: + # Temp Dummy Data + extruder_model = { + "core": "AA 0.4", + "materials": [{"name": "Ultimaker Blue", "color": "blue"}, {"name": "Ultimaker Red", "color": "red"}, {"name": "Ultimaker Orange", "color": "orange"}] + } + return extruder_model + diff --git a/resources/qml/Dialogs/ChoosePrinterDialog.qml b/resources/qml/Dialogs/ChoosePrinterDialog.qml index cddf38d347..2bfdc79b23 100644 --- a/resources/qml/Dialogs/ChoosePrinterDialog.qml +++ b/resources/qml/Dialogs/ChoosePrinterDialog.qml @@ -3,6 +3,7 @@ import QtQuick 2.2 import QtQuick.Controls 2.9 +import QtQuick.Layouts 2.10 import UM 1.5 as UM import Cura 1.0 as Cura @@ -11,23 +12,31 @@ UM.Dialog { id: base - property string machine_id_filter: "" + backgroundColor: UM.Theme.getColor("background_2") - Column + property string machine_id_filter: "" + ScrollView { anchors.fill: parent - - Repeater + Column { - id: contents + anchors.fill: parent + spacing: UM.Theme.getSize("default_margin").height - model: Cura.CompatibleMachineModel + Repeater { - filter: machine_id_filter - } - delegate: UM.Label - { - text: model.name + id: contents + + model: Cura.CompatibleMachineModel + { + filter: machine_id_filter + } + + delegate: Cura.PrintSelectorCard + { + name: model.name + extruders: model.extruders + } } } } diff --git a/resources/qml/PrinterSelector/PrintSelectorCard.qml b/resources/qml/PrinterSelector/PrintSelectorCard.qml new file mode 100644 index 0000000000..98927040f0 --- /dev/null +++ b/resources/qml/PrinterSelector/PrintSelectorCard.qml @@ -0,0 +1,134 @@ +import QtQuick 2.2 +import QtQuick.Controls 2.9 +import QtQuick.Layouts 2.10 + +import UM 1.5 as UM +import Cura 1.0 as Cura + +Rectangle { + property alias name: printerTitle.text + property var extruders + + width: parent.width + height: childrenRect.height + 2 * UM.Theme.getSize("default_margin").height + + color: UM.Theme.getColor("background_1") + border.color: UM.Theme.getColor("border_main") + border.width: UM.Theme.getSize("default_lining").width + + RowLayout + { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: UM.Theme.getSize("default_margin").width + + Cura.IconWithText + { + id: printerTitle + + Layout.preferredWidth: parent.width / 3 + Layout.fillWidth: true + Layout.alignment: Qt.AlignTop + Layout.fillHeight: false + + source: UM.Theme.getIcon("Printer") + spacing: UM.Theme.getSize("thin_margin").width + iconSize: UM.Theme.getSize("medium_button_icon").width + font: UM.Theme.getFont("medium_bold") + } + + ColumnLayout + { + id: extruderInformation + Layout.fillWidth: true + Layout.preferredWidth: parent.width / 2 + Layout.alignment: Qt.AlignTop + spacing: UM.Theme.getSize("default_margin").width + + Repeater + { + model: extruders + + Item + { + height: childrenRect.height + + Cura.ExtruderIcon + { + id: extruderIcon + anchors.top: parent.top + anchors.left: parent.left + materialColor: modelData.materials.length == 1 ? modelData.materials[0].color : "white" + iconSize: UM.Theme.getSize("medium_button_icon").width + } + + UM.Label + { + id: extruderCore + anchors.verticalCenter: extruderIcon.verticalCenter + anchors.left: extruderIcon.right + anchors.leftMargin: UM.Theme.getSize("default_margin").width + text: modelData.core + font: UM.Theme.getFont("default_bold") + } + + UM.Label + { + id: singleMaterialText + anchors.left: extruderCore.right + anchors.verticalCenter: extruderCore.verticalCenter + text: modelData.materials.length == 1 ? modelDatamaterials[0].name : "test" + visible: modelData.materials.length == 1 + } + + ColumnLayout + { + id: multiMaterialText + anchors.top: extruderCore.bottom + anchors.left: extruderCore.left + anchors.topMargin: UM.Theme.getSize("narrow_margin").height + Repeater + { + model: modelData.materials + visible: modelData.materials.length > 1 + UM.Label + { + text: modelData.name + } + } + } + } + } + } + + Button + { + id: PrintButton + + implicitWidth: UM.Theme.getSize("medium_button").width + implicitHeight: implicitWidth + Layout.alignment: Qt.AlignTop + padding: 0 + + background: Rectangle + { + border.width: UM.Theme.getSize("default_lining").width + border.color: UM.Theme.getColor("border_accent_1") + color: control.hovered ? UM.Theme.getColor("toolbar_button_hover"): UM.Theme.getColor("background_1") + } + + contentItem: Item + { + UM.ColorImage + { + anchors.centerIn: parent + source: UM.Theme.getIcon("Printer") + color: UM.Theme.getColor("border_accent_1") + width: UM.Theme.getSize("small_button_icon").width + height: width + } + } + } + } +} \ No newline at end of file diff --git a/resources/qml/qmldir b/resources/qml/qmldir index 6ec3ca91c8..d5184f1d8c 100644 --- a/resources/qml/qmldir +++ b/resources/qml/qmldir @@ -17,6 +17,7 @@ PrinterTypeLabel 1.0 PrinterTypeLabel.qml ViewsSelector 1.0 ViewsSelector.qml SettingView 1.0 SettingView.qml ProfileMenu 1.0 ProfileMenu.qml +PrintSelectorCard 1.0 PrintSelectorCard.qml # Cura/WelcomePages From 9c1e624b432fca93d2c2a35f61e0acb8dd18643c Mon Sep 17 00:00:00 2001 From: Joey de l'Arago Date: Mon, 5 Sep 2022 17:58:33 +0200 Subject: [PATCH 04/18] typo CURA-9278 --- resources/qml/PrinterSelector/PrintSelectorCard.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/PrinterSelector/PrintSelectorCard.qml b/resources/qml/PrinterSelector/PrintSelectorCard.qml index 98927040f0..91c9e92abf 100644 --- a/resources/qml/PrinterSelector/PrintSelectorCard.qml +++ b/resources/qml/PrinterSelector/PrintSelectorCard.qml @@ -104,7 +104,7 @@ Rectangle { Button { - id: PrintButton + id: printButton implicitWidth: UM.Theme.getSize("medium_button").width implicitHeight: implicitWidth From ea596bdd905961f328f80893c6d4a73062d5bab9 Mon Sep 17 00:00:00 2001 From: Joey de l'Arago Date: Mon, 5 Sep 2022 17:59:01 +0200 Subject: [PATCH 05/18] Add some extrudare information fetching. CURA-9278 --- cura/Machines/Models/CompatibleMachineModel.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cura/Machines/Models/CompatibleMachineModel.py b/cura/Machines/Models/CompatibleMachineModel.py index c87245e495..9c4d407cad 100644 --- a/cura/Machines/Models/CompatibleMachineModel.py +++ b/cura/Machines/Models/CompatibleMachineModel.py @@ -3,7 +3,7 @@ # TODO?: documentation -from typing import Optional, Dict +from typing import Optional, Dict, cast from PyQt6.QtCore import Qt, QObject, pyqtSlot, pyqtProperty, pyqtSignal @@ -13,6 +13,7 @@ from UM.i18n import i18nCatalog from UM.Util import parseBool from cura.Settings.CuraContainerRegistry import CuraContainerRegistry +from cura.Settings.ExtruderStack import ExtruderStack class CompatibleMachineModel(ListModel): @@ -68,13 +69,14 @@ class CompatibleMachineModel(ListModel): self.appendItem({ "name": container_stack.getName(), "id": container_stack.getId(), - "extruders": [self.getExtruderModel(extruder) for extruder in extruders] + "extruders": [self.getExtruderModel(cast(ExtruderStack, extruder)) for extruder in extruders] }) - def getExtruderModel(self, extruders: ContainerStack) -> Dict: + def getExtruderModel(self, extruder: ExtruderStack) -> Dict: # Temp Dummy Data + # ExtruderConfigrationModel does what we want here extruder_model = { - "core": "AA 0.4", + "core": extruder.quality.getMetaDataEntry("variant", ""), "materials": [{"name": "Ultimaker Blue", "color": "blue"}, {"name": "Ultimaker Red", "color": "red"}, {"name": "Ultimaker Orange", "color": "orange"}] } return extruder_model From 04cd76cb3bd260a63ec58f5838770f726fe813b1 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 8 Sep 2022 13:36:42 +0200 Subject: [PATCH 06/18] Get extruder configs to frontend. When choosing the printer, all possible (single) extruder configs need to be shown for that printer. CURA-9278 --- .../Machines/Models/CompatibleMachineModel.py | 85 ++++++++++--------- resources/qml/Dialogs/ChoosePrinterDialog.qml | 5 +- .../qml/PrinterSelector/PrintSelectorCard.qml | 14 +-- 3 files changed, 53 insertions(+), 51 deletions(-) diff --git a/cura/Machines/Models/CompatibleMachineModel.py b/cura/Machines/Models/CompatibleMachineModel.py index 9c4d407cad..5526c67331 100644 --- a/cura/Machines/Models/CompatibleMachineModel.py +++ b/cura/Machines/Models/CompatibleMachineModel.py @@ -24,60 +24,61 @@ class CompatibleMachineModel(ListModel): def __init__(self, parent: Optional[QObject] = None) -> None: super().__init__(parent) - self._filter_on_definition_id: Optional[str] = None - self._catalog = i18nCatalog("cura") self.addRoleName(self.NameRole, "name") self.addRoleName(self.IdRole, "id") self.addRoleName(self.ExtrudersRole, "extruders") - filterChanged = pyqtSignal(str) - - @pyqtSlot(str) - def setFilter(self, abstract_machine_id: str) -> None: - # TODO??: defensive coding; check if machine is abstract & abort/log if not - self._filter_on_definition_id = abstract_machine_id - - # Don't need a delayed update, since it's fire once on user click (either on 'print to cloud' or 'refresh'). - # So, no signals that could come in (too) quickly. - self.filterChanged.emit(self._filter_on_definition_id) self._update() - @pyqtProperty(str, fset=setFilter, notify=filterChanged) - def filter(self) -> str: - return self._filter_on_definition_id - - def _update(self) -> None: - self.clear() - if not self._filter_on_definition_id or self._filter_on_definition_id == "": - # TODO?: log - return - from cura.CuraApplication import CuraApplication machine_manager = CuraApplication.getInstance().getMachineManager() - compatible_machines = machine_manager.getMachinesWithDefinition(self._filter_on_definition_id, online_only = True) - # TODO: Handle 0 compatible machines -> option to close window? Message in card? (remember the design has a refresh button!) + machine_manager.globalContainerChanged.connect(self._update) - for container_stack in compatible_machines: - if parseBool(container_stack.getMetaDataEntry("hidden", False)) or parseBool(container_stack.getMetaDataEntry("is_abstract_machine", False)): - continue - self.addItem(container_stack) + def _update(self) -> None: + self.clear() - def addItem(self, container_stack: ContainerStack) -> None: - extruders = CuraContainerRegistry.getInstance().findContainerStacks(type="extruder_train", machine=container_stack.getId()) - self.appendItem({ - "name": container_stack.getName(), - "id": container_stack.getId(), - "extruders": [self.getExtruderModel(cast(ExtruderStack, extruder)) for extruder in extruders] + from cura.CuraApplication import CuraApplication + machine_manager = CuraApplication.getInstance().getMachineManager() + + # Need to loop over the output-devices, not the stacks, since we need all applicable configurations, not just the current loaded one. + for output_device in machine_manager.printerOutputDevices: + for printer in output_device.printers: + extruder_configs = dict() + + # initialize & add current active material: + for extruder in printer.extruders: + materials = [] if not extruder.activeMaterial else [{ + "brand": extruder.activeMaterial.brand, + "name": extruder.activeMaterial.name, + "hexcolor": extruder.activeMaterial.color + }] + extruder_configs[extruder.getPosition()] = { + "position": extruder.getPosition(), + "core": extruder.hotendID, + "materials": materials + } + + # add currently inactive, but possible materials: + for configuration in printer.availableConfigurations: + print(" CONFIG !") + for extruder in configuration.extruderConfigurations: + + if not extruder.position in extruder_configs: + # TODO: log -- all extruders should be present in the init round, regardless of if a material was active + continue + + extruder_configs[extruder.position]["materials"].append({ + "brand": extruder.material.brand, + "name": extruder.material.name, + "hexcolor": extruder.material.color }) - def getExtruderModel(self, extruder: ExtruderStack) -> Dict: - # Temp Dummy Data - # ExtruderConfigrationModel does what we want here - extruder_model = { - "core": extruder.quality.getMetaDataEntry("variant", ""), - "materials": [{"name": "Ultimaker Blue", "color": "blue"}, {"name": "Ultimaker Red", "color": "red"}, {"name": "Ultimaker Orange", "color": "orange"}] - } - return extruder_model + self.appendItem({ + "name": printer.name, + "id": printer.uniqueName, + "extruders": [extruder for extruder in extruder_configs.values()] + }) + # TODO: Handle 0 compatible machines -> option to close window? Message in card? (remember the design has a refresh button!) diff --git a/resources/qml/Dialogs/ChoosePrinterDialog.qml b/resources/qml/Dialogs/ChoosePrinterDialog.qml index 2bfdc79b23..fbaf9a61bb 100644 --- a/resources/qml/Dialogs/ChoosePrinterDialog.qml +++ b/resources/qml/Dialogs/ChoosePrinterDialog.qml @@ -27,10 +27,7 @@ UM.Dialog { id: contents - model: Cura.CompatibleMachineModel - { - filter: machine_id_filter - } + model: Cura.CompatibleMachineModel {} delegate: Cura.PrintSelectorCard { diff --git a/resources/qml/PrinterSelector/PrintSelectorCard.qml b/resources/qml/PrinterSelector/PrintSelectorCard.qml index 91c9e92abf..040f12642b 100644 --- a/resources/qml/PrinterSelector/PrintSelectorCard.qml +++ b/resources/qml/PrinterSelector/PrintSelectorCard.qml @@ -1,3 +1,6 @@ +// Copyright (c) 2022 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + import QtQuick 2.2 import QtQuick.Controls 2.9 import QtQuick.Layouts 2.10 @@ -5,7 +8,8 @@ import QtQuick.Layouts 2.10 import UM 1.5 as UM import Cura 1.0 as Cura -Rectangle { +Rectangle +{ property alias name: printerTitle.text property var extruders @@ -78,7 +82,7 @@ Rectangle { id: singleMaterialText anchors.left: extruderCore.right anchors.verticalCenter: extruderCore.verticalCenter - text: modelData.materials.length == 1 ? modelDatamaterials[0].name : "test" + text: modelData.materials.length == 1 ? modelData.materials[0].brand + " " + modelData.materials[0].name : "" visible: modelData.materials.length == 1 } @@ -88,13 +92,13 @@ Rectangle { anchors.top: extruderCore.bottom anchors.left: extruderCore.left anchors.topMargin: UM.Theme.getSize("narrow_margin").height + visible: modelData.materials.length > 1 Repeater { model: modelData.materials - visible: modelData.materials.length > 1 UM.Label { - text: modelData.name + text: modelData.brand + " " + modelData.name } } } @@ -131,4 +135,4 @@ Rectangle { } } } -} \ No newline at end of file +} From 6adf05bbf054a87316fa283e10ee7d8367bb787e Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 9 Sep 2022 11:05:05 +0200 Subject: [PATCH 07/18] Complete interface for selecting cloud printer. CURA-9278 --- .../Machines/Models/CompatibleMachineModel.py | 21 ++----- .../src/Cloud/AbstractCloudOutputDevice.py | 29 +++++----- resources/qml/Dialogs/ChoosePrinterDialog.qml | 56 ++++++++++++++++++- .../qml/PrinterSelector/PrintSelectorCard.qml | 9 ++- 4 files changed, 83 insertions(+), 32 deletions(-) diff --git a/cura/Machines/Models/CompatibleMachineModel.py b/cura/Machines/Models/CompatibleMachineModel.py index 5526c67331..1ac13d4ad4 100644 --- a/cura/Machines/Models/CompatibleMachineModel.py +++ b/cura/Machines/Models/CompatibleMachineModel.py @@ -1,24 +1,18 @@ # Copyright (c) 2022 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -# TODO?: documentation - -from typing import Optional, Dict, cast +from typing import Optional from PyQt6.QtCore import Qt, QObject, pyqtSlot, pyqtProperty, pyqtSignal +from UM.Logger import Logger from UM.Qt.ListModel import ListModel -from UM.Settings.ContainerStack import ContainerStack from UM.i18n import i18nCatalog -from UM.Util import parseBool - -from cura.Settings.CuraContainerRegistry import CuraContainerRegistry -from cura.Settings.ExtruderStack import ExtruderStack class CompatibleMachineModel(ListModel): NameRole = Qt.ItemDataRole.UserRole + 1 - IdRole = Qt.ItemDataRole.UserRole + 2 + UniqueIdRole = Qt.ItemDataRole.UserRole + 2 ExtrudersRole = Qt.ItemDataRole.UserRole + 3 def __init__(self, parent: Optional[QObject] = None) -> None: @@ -27,7 +21,7 @@ class CompatibleMachineModel(ListModel): self._catalog = i18nCatalog("cura") self.addRoleName(self.NameRole, "name") - self.addRoleName(self.IdRole, "id") + self.addRoleName(self.UniqueIdRole, "unique_id") self.addRoleName(self.ExtrudersRole, "extruders") self._update() @@ -62,11 +56,10 @@ class CompatibleMachineModel(ListModel): # add currently inactive, but possible materials: for configuration in printer.availableConfigurations: - print(" CONFIG !") for extruder in configuration.extruderConfigurations: if not extruder.position in extruder_configs: - # TODO: log -- all extruders should be present in the init round, regardless of if a material was active + Logger.log("w", f"No active extruder for position {extruder.position}.") continue extruder_configs[extruder.position]["materials"].append({ @@ -77,8 +70,6 @@ class CompatibleMachineModel(ListModel): self.appendItem({ "name": printer.name, - "id": printer.uniqueName, + "unique_id": printer.uniqueName, "extruders": [extruder for extruder in extruder_configs.values()] }) - - # TODO: Handle 0 compatible machines -> option to close window? Message in card? (remember the design has a refresh button!) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/AbstractCloudOutputDevice.py b/plugins/UM3NetworkPrinting/src/Cloud/AbstractCloudOutputDevice.py index e8f652064d..7384cf784f 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/AbstractCloudOutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/AbstractCloudOutputDevice.py @@ -1,7 +1,7 @@ from time import time from typing import List, Optional -from PyQt6.QtCore import QObject +from PyQt6.QtCore import QObject, pyqtSlot from PyQt6.QtNetwork import QNetworkReply from UM import i18nCatalog @@ -48,7 +48,6 @@ class AbstractCloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): Logger.log("i", "Attempting to connect AbstractCloudOutputDevice %s", self.key) super().connect() - #CuraApplication.getInstance().getBackend().backendStateChange.connect(self._onBackendStateChange) self._update() def disconnect(self) -> None: @@ -91,26 +90,28 @@ class AbstractCloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): self._updatePrinters(all_configurations) def _onError(self, reply: QNetworkReply, error: QNetworkReply.NetworkError) -> None: - # TODO! + Logger.log("w", f"Failed to get clusters by machine type: {str(error)}.") + + @pyqtSlot(str) + def printerSelected(self, unique_id: str): + print(unique_id) + if self._on_print_dialog: + self._on_print_dialog.close() + + @pyqtSlot() + def refresh(self): + print("-REFRESH-") pass - def _openChoosePrinterDialog(self, machine_filter_id: str) -> None: + def _openChoosePrinterDialog(self) -> None: if self._on_print_dialog is None: qml_path = Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Dialogs", "ChoosePrinterDialog.qml") self._on_print_dialog = CuraApplication.getInstance().createQmlComponent(qml_path, {}) if self._on_print_dialog is None: # Failed to load QML file. return - self._on_print_dialog.setProperty("machine_id_filter", machine_filter_id) + self._on_print_dialog.setProperty("manager", self) self._on_print_dialog.show() def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs) -> None: - - # TODO: - # - Prettify (and make usable) dialog. - # (Including extruders... their metadata is already in the model. Is that enough though. Does that contain configurations as well?) - # - On button clicked, fetch/push to here selected printer, hide dialog - # - Find correct output-device for selected printer maybe via `CuraApplication.getInstance().getOutputDeviceManager().getOutputDevices()` - # Call 'requestWrite' of the selected output-device. - - self._openChoosePrinterDialog(CuraApplication.getInstance().getGlobalContainerStack().definition.getId()) + self._openChoosePrinterDialog() diff --git a/resources/qml/Dialogs/ChoosePrinterDialog.qml b/resources/qml/Dialogs/ChoosePrinterDialog.qml index fbaf9a61bb..b7079fcabd 100644 --- a/resources/qml/Dialogs/ChoosePrinterDialog.qml +++ b/resources/qml/Dialogs/ChoosePrinterDialog.qml @@ -10,19 +10,65 @@ import Cura 1.0 as Cura UM.Dialog { + property var manager + id: base + title: catalog.i18nc("@title:window", "Select Printer") + backgroundColor: UM.Theme.getColor("background_2") - property string machine_id_filter: "" + width: minimumWidth + minimumWidth: 550 * screenScaleFactor + height: minimumHeight + minimumHeight: 550 * screenScaleFactor + + modality: Qt.ApplicationModal + ScrollView { + // Workaround for Windowing bugs in Qt: + width: 550 * screenScaleFactor - 3 * UM.Theme.getSize("default_margin").width + height: 550 * screenScaleFactor - 3 * UM.Theme.getSize("default_margin").height + + UM.I18nCatalog + { + id: catalog + name: "cura" + } + anchors.fill: parent Column { anchors.fill: parent spacing: UM.Theme.getSize("default_margin").height + Item + { + width: parent.width + height: childrenRect.height + + UM.Label + { + anchors.left: parent.left + text: catalog.i18nc("@title:label", "Compatible Printers") + font: UM.Theme.getFont("large") + } + + UM.SimpleButton + { + anchors.right: parent.right + + width: UM.Theme.getSize("small_button").width + height: UM.Theme.getSize("small_button").height + iconSource: UM.Theme.getIcon("ArrowDoubleCircleRight") + color: UM.Theme.getColor("text_link") + hoverColor: UM.Theme.getColor("text_scene_hover") + + onClicked: manager.refresh() + } + } + Repeater { id: contents @@ -32,9 +78,17 @@ UM.Dialog delegate: Cura.PrintSelectorCard { name: model.name + unique_id: model.unique_id extruders: model.extruders + manager: base.manager } } + + UM.Label + { + visible: contents.count < 1 + text: catalog.i18nc("@description", "No compatible printers, that are currently online, where found.") + } } } } diff --git a/resources/qml/PrinterSelector/PrintSelectorCard.qml b/resources/qml/PrinterSelector/PrintSelectorCard.qml index 040f12642b..29c17bc61e 100644 --- a/resources/qml/PrinterSelector/PrintSelectorCard.qml +++ b/resources/qml/PrinterSelector/PrintSelectorCard.qml @@ -11,7 +11,9 @@ import Cura 1.0 as Cura Rectangle { property alias name: printerTitle.text + property string unique_id property var extruders + property var manager width: parent.width height: childrenRect.height + 2 * UM.Theme.getSize("default_margin").height @@ -63,7 +65,7 @@ Rectangle id: extruderIcon anchors.top: parent.top anchors.left: parent.left - materialColor: modelData.materials.length == 1 ? modelData.materials[0].color : "white" + materialColor: modelData.materials.length == 1 ? modelData.materials[0].hexcolor : "white" iconSize: UM.Theme.getSize("medium_button_icon").width } @@ -82,6 +84,7 @@ Rectangle id: singleMaterialText anchors.left: extruderCore.right anchors.verticalCenter: extruderCore.verticalCenter + anchors.leftMargin: UM.Theme.getSize("default_margin").width text: modelData.materials.length == 1 ? modelData.materials[0].brand + " " + modelData.materials[0].name : "" visible: modelData.materials.length == 1 } @@ -119,7 +122,7 @@ Rectangle { border.width: UM.Theme.getSize("default_lining").width border.color: UM.Theme.getColor("border_accent_1") - color: control.hovered ? UM.Theme.getColor("toolbar_button_hover"): UM.Theme.getColor("background_1") + color: printButton.hovered ? UM.Theme.getColor("toolbar_button_hover"): UM.Theme.getColor("background_1") } contentItem: Item @@ -133,6 +136,8 @@ Rectangle height: width } } + + onClicked: manager.printerSelected(unique_id) } } } From 2b1909b7b54fdc17f60e96b285cabe2e77f50f3e Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 9 Sep 2022 13:17:41 +0200 Subject: [PATCH 08/18] Don't show machines with only incompatible configurations. part of CURA-9278 --- .../Machines/Models/CompatibleMachineModel.py | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/cura/Machines/Models/CompatibleMachineModel.py b/cura/Machines/Models/CompatibleMachineModel.py index 1ac13d4ad4..766a8ac755 100644 --- a/cura/Machines/Models/CompatibleMachineModel.py +++ b/cura/Machines/Models/CompatibleMachineModel.py @@ -36,14 +36,17 @@ class CompatibleMachineModel(ListModel): from cura.CuraApplication import CuraApplication machine_manager = CuraApplication.getInstance().getMachineManager() - # Need to loop over the output-devices, not the stacks, since we need all applicable configurations, not just the current loaded one. + # Loop over the output-devices, not the stacks; need all applicable configurations, not just the current loaded one. for output_device in machine_manager.printerOutputDevices: for printer in output_device.printers: extruder_configs = dict() # initialize & add current active material: for extruder in printer.extruders: - materials = [] if not extruder.activeMaterial else [{ + compatible_type = machine_manager.activeMachine.extruderList[extruder.getPosition()].material.getMetaDataEntry("material", "") + has_compatible_material = extruder.activeMaterial and compatible_type in [extruder.activeMaterial.type, None, "None", "", "empty"] + + materials = [] if not has_compatible_material else [{ "brand": extruder.activeMaterial.brand, "name": extruder.activeMaterial.name, "hexcolor": extruder.activeMaterial.color @@ -57,6 +60,9 @@ class CompatibleMachineModel(ListModel): # add currently inactive, but possible materials: for configuration in printer.availableConfigurations: for extruder in configuration.extruderConfigurations: + compatible_type = machine_manager.activeMachine.extruderList[extruder.position].material.getMetaDataEntry("material", "") + if compatible_type not in [extruder.material.type, None, "None", "", "empty"]: + continue if not extruder.position in extruder_configs: Logger.log("w", f"No active extruder for position {extruder.position}.") @@ -68,8 +74,9 @@ class CompatibleMachineModel(ListModel): "hexcolor": extruder.material.color }) - self.appendItem({ - "name": printer.name, - "unique_id": printer.uniqueName, - "extruders": [extruder for extruder in extruder_configs.values()] - }) + if all([len(extruder["materials"]) > 0 for extruder in extruder_configs.values()]): + self.appendItem({ + "name": printer.name, + "unique_id": printer.uniqueName, + "extruders": [extruder for extruder in extruder_configs.values()] + }) From b7611da95e7cc77b44bfb728a213f6868704be4a Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 9 Sep 2022 15:14:29 +0200 Subject: [PATCH 09/18] Connect buttons for selected printers: Print and refresh. part of CURA-9278 --- .../Machines/Models/CompatibleMachineModel.py | 3 ++- .../src/Cloud/AbstractCloudOutputDevice.py | 19 ++++++++++++------- .../src/Cloud/CloudOutputDeviceManager.py | 9 ++++++++- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/cura/Machines/Models/CompatibleMachineModel.py b/cura/Machines/Models/CompatibleMachineModel.py index 766a8ac755..fbccf92fc3 100644 --- a/cura/Machines/Models/CompatibleMachineModel.py +++ b/cura/Machines/Models/CompatibleMachineModel.py @@ -29,6 +29,7 @@ class CompatibleMachineModel(ListModel): from cura.CuraApplication import CuraApplication machine_manager = CuraApplication.getInstance().getMachineManager() machine_manager.globalContainerChanged.connect(self._update) + machine_manager.outputDevicesChanged.connect(self._update) def _update(self) -> None: self.clear() @@ -77,6 +78,6 @@ class CompatibleMachineModel(ListModel): if all([len(extruder["materials"]) > 0 for extruder in extruder_configs.values()]): self.appendItem({ "name": printer.name, - "unique_id": printer.uniqueName, + "unique_id": printer.name, # <- Can assume the cloud doesn't have duplicate names? "extruders": [extruder for extruder in extruder_configs.values()] }) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/AbstractCloudOutputDevice.py b/plugins/UM3NetworkPrinting/src/Cloud/AbstractCloudOutputDevice.py index 7384cf784f..4ee74550a4 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/AbstractCloudOutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/AbstractCloudOutputDevice.py @@ -1,5 +1,5 @@ from time import time -from typing import List, Optional +from typing import Callable, List, Optional from PyQt6.QtCore import QObject, pyqtSlot from PyQt6.QtNetwork import QNetworkReply @@ -14,7 +14,6 @@ from cura.CuraApplication import CuraApplication from cura.PrinterOutput.NetworkedPrinterOutputDevice import AuthState from cura.PrinterOutput.PrinterOutputDevice import ConnectionType from .CloudApiClient import CloudApiClient -from ..Models.Http.CloudClusterResponse import CloudClusterResponse from ..Models.Http.CloudClusterWithConfigResponse import CloudClusterWithConfigResponse from ..UltimakerNetworkedPrinterOutputDevice import UltimakerNetworkedPrinterOutputDevice @@ -24,7 +23,7 @@ I18N_CATALOG = i18nCatalog("cura") class AbstractCloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): API_CHECK_INTERVAL = 10.0 # seconds - def __init__(self, api_client: CloudApiClient, printer_type: str, parent: QObject = None) -> None: + def __init__(self, api_client: CloudApiClient, printer_type: str, request_write_callback: Callable, refresh_callback: Callable, parent: QObject = None) -> None: self._api = api_client properties = {b"printer_type": printer_type.encode()} @@ -37,6 +36,9 @@ class AbstractCloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): ) self._on_print_dialog: Optional[QObject] = None + self._nodes: List[SceneNode] = None + self._request_write_callback = request_write_callback + self._refresh_callback = refresh_callback self._setInterfaceElements() @@ -94,14 +96,14 @@ class AbstractCloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): @pyqtSlot(str) def printerSelected(self, unique_id: str): - print(unique_id) + self._request_write_callback(unique_id, self._nodes) if self._on_print_dialog: self._on_print_dialog.close() @pyqtSlot() def refresh(self): - print("-REFRESH-") - pass + self._refresh_callback() + self._update() def _openChoosePrinterDialog(self) -> None: if self._on_print_dialog is None: @@ -113,5 +115,8 @@ class AbstractCloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): self._on_print_dialog.show() def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs) -> None: + if not nodes or len(nodes) < 1: + Logger.log("w", "Nothing to print.") + return + self._nodes = nodes self._openChoosePrinterDialog() - diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index bdae34a860..abfe863749 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -172,6 +172,13 @@ class CloudOutputDeviceManager: self._syncing = False self._account.setSyncState(self.SYNC_SERVICE_NAME, SyncState.ERROR) + def _requestWrite(self, unique_id: str, nodes: List["SceneNode"]): + for remote in self._remote_clusters.values(): + if unique_id == remote.name: # No other id-type would match. Assume cloud doesn't have duplicate names. + remote.requestWrite(nodes) + return + Logger.log("e", f"Failed writing to specific cloud printer: {unique_id} not in remote clusters.") + def _createMachineStacksForDiscoveredClusters(self, discovered_clusters: List[CloudClusterResponse]) -> None: """**Synchronously** create machines for discovered devices @@ -193,7 +200,7 @@ class CloudOutputDeviceManager: output_device = CloudOutputDevice(self._api, cluster_data) if cluster_data.printer_type not in self._abstract_clusters: - self._abstract_clusters[cluster_data.printer_type] = AbstractCloudOutputDevice(self._api, cluster_data.printer_type) + self._abstract_clusters[cluster_data.printer_type] = AbstractCloudOutputDevice(self._api, cluster_data.printer_type, self._requestWrite, self.refreshConnections) # Ensure that the abstract machine is added (either because it was never added, or it somehow got # removed) _abstract_machine = CuraStackBuilder.createAbstractMachine(cluster_data.printer_type) From 0f36f833731838d3716398251271450c62b6b781 Mon Sep 17 00:00:00 2001 From: Casper Lamboo Date: Mon, 12 Sep 2022 14:19:08 +0200 Subject: [PATCH 10/18] Simplify list notation CURA-9278 --- cura/Machines/Models/CompatibleMachineModel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/Machines/Models/CompatibleMachineModel.py b/cura/Machines/Models/CompatibleMachineModel.py index fbccf92fc3..c4143be228 100644 --- a/cura/Machines/Models/CompatibleMachineModel.py +++ b/cura/Machines/Models/CompatibleMachineModel.py @@ -79,5 +79,5 @@ class CompatibleMachineModel(ListModel): self.appendItem({ "name": printer.name, "unique_id": printer.name, # <- Can assume the cloud doesn't have duplicate names? - "extruders": [extruder for extruder in extruder_configs.values()] + "extruders": extruder_configs.values() }) From e3fc73f17d878c46727ffb190a3a46c1f205b782 Mon Sep 17 00:00:00 2001 From: Casper Lamboo Date: Mon, 12 Sep 2022 14:20:06 +0200 Subject: [PATCH 11/18] Simplify if notation CURA-9278 --- cura/Machines/Models/CompatibleMachineModel.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cura/Machines/Models/CompatibleMachineModel.py b/cura/Machines/Models/CompatibleMachineModel.py index c4143be228..80fa78a64a 100644 --- a/cura/Machines/Models/CompatibleMachineModel.py +++ b/cura/Machines/Models/CompatibleMachineModel.py @@ -47,11 +47,13 @@ class CompatibleMachineModel(ListModel): compatible_type = machine_manager.activeMachine.extruderList[extruder.getPosition()].material.getMetaDataEntry("material", "") has_compatible_material = extruder.activeMaterial and compatible_type in [extruder.activeMaterial.type, None, "None", "", "empty"] - materials = [] if not has_compatible_material else [{ + materials = [] + if has_compatible_material: + materials.append({ "brand": extruder.activeMaterial.brand, "name": extruder.activeMaterial.name, - "hexcolor": extruder.activeMaterial.color - }] + "hexcolor": extruder.activeMaterial.color, + }) extruder_configs[extruder.getPosition()] = { "position": extruder.getPosition(), "core": extruder.hotendID, From 96bc5ed6023fab1bc90dbd9fbf51b00a382d4457 Mon Sep 17 00:00:00 2001 From: Casper Lamboo Date: Mon, 12 Sep 2022 14:20:28 +0200 Subject: [PATCH 12/18] Simplify string notation CURA-9278 --- resources/qml/PrinterSelector/PrintSelectorCard.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/qml/PrinterSelector/PrintSelectorCard.qml b/resources/qml/PrinterSelector/PrintSelectorCard.qml index 29c17bc61e..517a0e164a 100644 --- a/resources/qml/PrinterSelector/PrintSelectorCard.qml +++ b/resources/qml/PrinterSelector/PrintSelectorCard.qml @@ -85,7 +85,7 @@ Rectangle anchors.left: extruderCore.right anchors.verticalCenter: extruderCore.verticalCenter anchors.leftMargin: UM.Theme.getSize("default_margin").width - text: modelData.materials.length == 1 ? modelData.materials[0].brand + " " + modelData.materials[0].name : "" + text: modelData.materials.length == 1 ? `${modelData.materials[0].brand} ${modelData.materials[0].name}` : "" visible: modelData.materials.length == 1 } @@ -101,7 +101,7 @@ Rectangle model: modelData.materials UM.Label { - text: modelData.brand + " " + modelData.name + text: `${modelData.brand} ${modelData.name}` } } } From 8d9e9a9dbfda03f2f510df16a8ef415f4244993c Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Mon, 12 Sep 2022 17:04:40 +0200 Subject: [PATCH 13/18] Use modern python typing CURA-9278 --- cura/PrinterOutput/Models/ExtruderConfigurationModel.py | 6 +++--- cura/Settings/MachineManager.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cura/PrinterOutput/Models/ExtruderConfigurationModel.py b/cura/PrinterOutput/Models/ExtruderConfigurationModel.py index 4cc3e81f56..d54092b8c9 100644 --- a/cura/PrinterOutput/Models/ExtruderConfigurationModel.py +++ b/cura/PrinterOutput/Models/ExtruderConfigurationModel.py @@ -13,9 +13,9 @@ class ExtruderConfigurationModel(QObject): def __init__(self, position: int = -1) -> None: super().__init__() - self._position = position # type: int - self._material = None # type: Optional[MaterialOutputModel] - self._hotend_id = None # type: Optional[str] + self._position: int = position + self._material: Optional[MaterialOutputModel] = None + self._hotend_id: Optional[str] = None def setPosition(self, position: int) -> None: self._position = position diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index ca45cdc4b9..2051ce1b99 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -99,7 +99,7 @@ class MachineManager(QObject): self._application.getPreferences().addPreference("cura/active_machine", "") - self._printer_output_devices = [] # type: List[PrinterOutputDevice] + self._printer_output_devices: List[PrinterOutputDevice] = [] self._application.getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged) # There might already be some output devices by the time the signal is connected self._onOutputDevicesChanged() @@ -112,7 +112,7 @@ class MachineManager(QObject): self._application.callLater(self.setInitialActiveMachine) - containers = CuraContainerRegistry.getInstance().findInstanceContainers(id = self.activeMaterialId) # type: List[InstanceContainer] + containers: List[InstanceContainer] = CuraContainerRegistry.getInstance().findInstanceContainers(id = self.activeMaterialId) if containers: containers[0].nameChanged.connect(self._onMaterialNameChanged) From 99136fd20991d567bfe1289c2ad7e7779b8c6a9c Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Mon, 12 Sep 2022 17:24:55 +0200 Subject: [PATCH 14/18] Fix bug in displaying extruders CURA-9278 --- cura/Machines/Models/CompatibleMachineModel.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cura/Machines/Models/CompatibleMachineModel.py b/cura/Machines/Models/CompatibleMachineModel.py index 80fa78a64a..782c5dcd2b 100644 --- a/cura/Machines/Models/CompatibleMachineModel.py +++ b/cura/Machines/Models/CompatibleMachineModel.py @@ -48,7 +48,7 @@ class CompatibleMachineModel(ListModel): has_compatible_material = extruder.activeMaterial and compatible_type in [extruder.activeMaterial.type, None, "None", "", "empty"] materials = [] - if has_compatible_material: + if has_compatible_material: materials.append({ "brand": extruder.activeMaterial.brand, "name": extruder.activeMaterial.name, @@ -77,9 +77,9 @@ class CompatibleMachineModel(ListModel): "hexcolor": extruder.material.color }) - if all([len(extruder["materials"]) > 0 for extruder in extruder_configs.values()]): + if any([len(extruder["materials"]) > 0 for extruder in extruder_configs.values()]): self.appendItem({ "name": printer.name, "unique_id": printer.name, # <- Can assume the cloud doesn't have duplicate names? - "extruders": extruder_configs.values() + "extruders": list(extruder_configs.values()) }) From 92b371cd508ebf5102aa5ae979865d2bac591b7e Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Mon, 12 Sep 2022 17:34:44 +0200 Subject: [PATCH 15/18] More agressivly check online printers CURA-9278 --- cura/Settings/MachineManager.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 2051ce1b99..699942ebaf 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -205,11 +205,7 @@ class MachineManager(QObject): ConnectionType.NetworkConnection in machine.configuredConnectionTypes, machines) if online_only: - # LAN printers can have is_online = False but should still be included, - # their online status is only checked when they are the active printer. - machines = filter(lambda machine: parseBool(machine.getMetaDataEntry("is_online", False) or - ConnectionType.NetworkConnection in machine.configuredConnectionTypes), - machines) + machines = filter(lambda machine: parseBool(machine.getMetaDataEntry("is_online", False)), machines) return list(machines) From 8935ab4a2da8fe134e1b821ac77640b89ef8b34f Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 13 Sep 2022 10:12:32 +0200 Subject: [PATCH 16/18] Defensive coding: What if no extruder position. part of CURA-9278 --- cura/Machines/Models/CompatibleMachineModel.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cura/Machines/Models/CompatibleMachineModel.py b/cura/Machines/Models/CompatibleMachineModel.py index 782c5dcd2b..ed1f508ad0 100644 --- a/cura/Machines/Models/CompatibleMachineModel.py +++ b/cura/Machines/Models/CompatibleMachineModel.py @@ -44,7 +44,10 @@ class CompatibleMachineModel(ListModel): # initialize & add current active material: for extruder in printer.extruders: - compatible_type = machine_manager.activeMachine.extruderList[extruder.getPosition()].material.getMetaDataEntry("material", "") + if extruder.getPosition() in machine_manager.activeMachine.extruderList: + compatible_type = machine_manager.activeMachine.extruderList[extruder.getPosition()].material.getMetaDataEntry("material", "") + else: + compatible_type = "" has_compatible_material = extruder.activeMaterial and compatible_type in [extruder.activeMaterial.type, None, "None", "", "empty"] materials = [] @@ -63,7 +66,10 @@ class CompatibleMachineModel(ListModel): # add currently inactive, but possible materials: for configuration in printer.availableConfigurations: for extruder in configuration.extruderConfigurations: - compatible_type = machine_manager.activeMachine.extruderList[extruder.position].material.getMetaDataEntry("material", "") + if extruder.position in machine_manager.activeMachine.extruderList: + compatible_type = machine_manager.activeMachine.extruderList[extruder.position].material.getMetaDataEntry("material", "") + else: + compatible_type = "" if compatible_type not in [extruder.material.type, None, "None", "", "empty"]: continue From ad14e60d26c7dbcfe701ba3485921d1ce3a1cc8b Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Tue, 13 Sep 2022 10:34:33 +0200 Subject: [PATCH 17/18] Show printer even if the configuration does not match CURA-9278 --- .../Machines/Models/CompatibleMachineModel.py | 25 ++++--------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/cura/Machines/Models/CompatibleMachineModel.py b/cura/Machines/Models/CompatibleMachineModel.py index ed1f508ad0..029567cdec 100644 --- a/cura/Machines/Models/CompatibleMachineModel.py +++ b/cura/Machines/Models/CompatibleMachineModel.py @@ -44,19 +44,11 @@ class CompatibleMachineModel(ListModel): # initialize & add current active material: for extruder in printer.extruders: - if extruder.getPosition() in machine_manager.activeMachine.extruderList: - compatible_type = machine_manager.activeMachine.extruderList[extruder.getPosition()].material.getMetaDataEntry("material", "") - else: - compatible_type = "" - has_compatible_material = extruder.activeMaterial and compatible_type in [extruder.activeMaterial.type, None, "None", "", "empty"] - - materials = [] - if has_compatible_material: - materials.append({ - "brand": extruder.activeMaterial.brand, - "name": extruder.activeMaterial.name, - "hexcolor": extruder.activeMaterial.color, - }) + materials = [{ + "brand": extruder.activeMaterial.brand, + "name": extruder.activeMaterial.name, + "hexcolor": extruder.activeMaterial.color, + }] extruder_configs[extruder.getPosition()] = { "position": extruder.getPosition(), "core": extruder.hotendID, @@ -66,13 +58,6 @@ class CompatibleMachineModel(ListModel): # add currently inactive, but possible materials: for configuration in printer.availableConfigurations: for extruder in configuration.extruderConfigurations: - if extruder.position in machine_manager.activeMachine.extruderList: - compatible_type = machine_manager.activeMachine.extruderList[extruder.position].material.getMetaDataEntry("material", "") - else: - compatible_type = "" - if compatible_type not in [extruder.material.type, None, "None", "", "empty"]: - continue - if not extruder.position in extruder_configs: Logger.log("w", f"No active extruder for position {extruder.position}.") continue From d97dddcfe2db72e9397a3ab2b3e078e4e7cba001 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Tue, 13 Sep 2022 10:41:46 +0200 Subject: [PATCH 18/18] Revert "More agressivly check online printers" This reverts commit 92b371cd508ebf5102aa5ae979865d2bac591b7e. --- cura/Settings/MachineManager.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 699942ebaf..2051ce1b99 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -205,7 +205,11 @@ class MachineManager(QObject): ConnectionType.NetworkConnection in machine.configuredConnectionTypes, machines) if online_only: - machines = filter(lambda machine: parseBool(machine.getMetaDataEntry("is_online", False)), machines) + # LAN printers can have is_online = False but should still be included, + # their online status is only checked when they are the active printer. + machines = filter(lambda machine: parseBool(machine.getMetaDataEntry("is_online", False) or + ConnectionType.NetworkConnection in machine.configuredConnectionTypes), + machines) return list(machines)