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 + } + } + } +}