diff --git a/cura/PrinterOutput/Models/PrinterOutputModel.py b/cura/PrinterOutput/Models/PrinterOutputModel.py index a1a23201fb..37135bf663 100644 --- a/cura/PrinterOutput/Models/PrinterOutputModel.py +++ b/cura/PrinterOutput/Models/PrinterOutputModel.py @@ -35,6 +35,7 @@ class PrinterOutputModel(QObject): self._target_bed_temperature = 0 # type: float self._name = "" self._key = "" # Unique identifier + self._unique_name = "" # Unique name (used in Connect) self._controller = output_controller self._controller.canUpdateFirmwareChanged.connect(self._onControllerCanUpdateFirmwareChanged) self._extruders = [ExtruderOutputModel(printer = self, position = i) for i in range(number_of_extruders)] @@ -190,6 +191,15 @@ class PrinterOutputModel(QObject): self._name = name self.nameChanged.emit() + @pyqtProperty(str, notify = nameChanged) + def uniqueName(self) -> str: + return self._unique_name + + def updateUniqueName(self, unique_name: str) -> None: + if self._unique_name != unique_name: + self._unique_name = unique_name + self.nameChanged.emit() + ## Update the bed temperature. This only changes it locally. def updateBedTemperature(self, temperature: float) -> None: if self._bed_temperature != temperature: diff --git a/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml b/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml index bcba60352c..6d9f375788 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/PrintWindow.qml @@ -1,52 +1,57 @@ // Copyright (c) 2019 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. - import QtQuick 2.2 import QtQuick.Window 2.2 import QtQuick.Controls 1.2 import UM 1.1 as UM UM.Dialog { + id: base; + title: catalog.i18nc("@title:window", "Print over network"); + width: minimumWidth; height: minimumHeight; - leftButtons: [ - Button { - enabled: true; - onClicked: { - base.visible = false; - printerSelectionCombobox.currentIndex = 0; - OutputDevice.cancelPrintSelection(); - } - text: catalog.i18nc("@action:button","Cancel"); - } - ] maximumHeight: minimumHeight; maximumWidth: minimumWidth; minimumHeight: 140 * screenScaleFactor; minimumWidth: 500 * screenScaleFactor; modality: Qt.ApplicationModal; - onVisibleChanged: { - if (visible) { - resetPrintersModel(); - } else { - OutputDevice.cancelPrintSelection(); + + Component.onCompleted: { + populateComboBox() + } + + // populates the combo box with the correct printer values + function populateComboBox() { + comboBoxPrintersModel.clear(); + comboBoxPrintersModel.append({ name: "Automatic", key: "" }); // Connect will just do it's thing + for (var i in OutputDevice.printers) { + comboBoxPrintersModel.append({ + name: OutputDevice.printers[i].name, + key: OutputDevice.printers[i].uniqueName + }); } } + + leftButtons: [ + Button { + enabled: true; + onClicked: { + base.close(); + } + text: catalog.i18nc("@action:button","Cancel"); + } + ] rightButtons: [ Button { enabled: true; onClicked: { - base.visible = false; - OutputDevice.selectPrinter(printerSelectionCombobox.model.get(printerSelectionCombobox.currentIndex).key); - // reset to defaults - printerSelectionCombobox.currentIndex = 0; + OutputDevice.selectTargetPrinter(printerComboBox.model.get(printerComboBox.currentIndex).key); + base.close(); } text: catalog.i18nc("@action:button","Print"); } ] - title: catalog.i18nc("@title:window", "Print over network"); - visible: true; - width: minimumWidth; Column { id: printerSelection; @@ -59,10 +64,6 @@ UM.Dialog { } height: 50 * screenScaleFactor; - SystemPalette { - id: palette; - } - UM.I18nCatalog { id: catalog; name: "cura"; @@ -82,23 +83,14 @@ UM.Dialog { } ComboBox { - id: printerSelectionCombobox; + id: printerComboBox; Behavior on height { NumberAnimation { duration: 100 } } height: 40 * screenScaleFactor; model: ListModel { - id: printersModel; + id: comboBoxPrintersModel; } textRole: "name"; width: parent.width; } } - - // Utils - function resetPrintersModel() { - printersModel.clear(); - printersModel.append({ name: "Automatic", key: ""}); - for (var index in OutputDevice.printers) { - printersModel.append({name: OutputDevice.printers[index].name, key: OutputDevice.printers[index].key}); - } - } } diff --git a/plugins/UM3NetworkPrinting/src/Models/Http/ClusterPrinterStatus.py b/plugins/UM3NetworkPrinting/src/Models/Http/ClusterPrinterStatus.py index 69daa1f08b..2e0912f057 100644 --- a/plugins/UM3NetworkPrinting/src/Models/Http/ClusterPrinterStatus.py +++ b/plugins/UM3NetworkPrinting/src/Models/Http/ClusterPrinterStatus.py @@ -79,6 +79,7 @@ class ClusterPrinterStatus(BaseModel): def updateOutputModel(self, model: PrinterOutputModel) -> None: model.updateKey(self.uuid) model.updateName(self.friendly_name) + model.updateUniqueName(self.unique_name) model.updateType(self.machine_variant) model.updateState(self.status if self.enabled else "disabled") model.updateBuildplate(self.build_plate.type if self.build_plate else "glass") diff --git a/plugins/UM3NetworkPrinting/src/Network/LocalClusterOutputDevice.py b/plugins/UM3NetworkPrinting/src/Network/LocalClusterOutputDevice.py index dd9c0a7d2a..1266afcca8 100644 --- a/plugins/UM3NetworkPrinting/src/Network/LocalClusterOutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/Network/LocalClusterOutputDevice.py @@ -1,16 +1,17 @@ # Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. - +import os from typing import Optional, Dict, List, Callable, Any from PyQt5.QtGui import QDesktopServices -from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal, pyqtProperty +from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal, pyqtProperty, QObject from PyQt5.QtNetwork import QNetworkReply from UM.FileHandler.FileHandler import FileHandler from UM.i18n import i18nCatalog from UM.Logger import Logger from UM.Scene.SceneNode import SceneNode +from cura.CuraApplication import CuraApplication from cura.PrinterOutput.NetworkedPrinterOutputDevice import AuthState from cura.PrinterOutput.PrinterOutputDevice import ConnectionType @@ -42,6 +43,8 @@ class LocalClusterOutputDevice(UltimakerNetworkedPrinterOutputDevice): ) self._cluster_api = None # type: Optional[ClusterApiClient] + self._active_exported_job = None # type: Optional[ExportFileJob] + self._printer_select_dialog = None # type: Optional[QObject] # We don't have authentication over local networking, so we're always authenticated. self.setAuthenticationState(AuthState.Authenticated) @@ -129,17 +132,50 @@ class LocalClusterOutputDevice(UltimakerNetworkedPrinterOutputDevice): job.finished.connect(self._onPrintJobCreated) job.start() + ## Allows the user to choose a printer to print with from the printer selection dialogue. + # \param unique_name: The unique name of the printer to target. + @pyqtSlot(str, name="selectTargetPrinter") + def selectTargetPrinter(self, unique_name: str = "") -> None: + self._startPrintJobUpload(unique_name if unique_name != "" else None) + ## Handler for when the print job was created locally. # It can now be sent over the network. def _onPrintJobCreated(self, job: ExportFileJob) -> None: + self._active_exported_job = job + # TODO: add preference to enable/disable this feature? + if self.clusterSize > 1: + self._showPrinterSelectionDialog() # self._startPrintJobUpload will be triggered from this dialog + return + self._startPrintJobUpload() + + ## Shows a dialog allowing the user to select which printer in a group to send a job to. + def _showPrinterSelectionDialog(self) -> None: + if not self._printer_select_dialog: + plugin_path = CuraApplication.getInstance().getPluginRegistry().getPluginPath("UM3NetworkPrinting") or "" + path = os.path.join(plugin_path, "resources", "qml", "PrintWindow.qml") + self._printer_select_dialog = CuraApplication.getInstance().createQmlComponent(path, {"OutputDevice": self}) + if self._printer_select_dialog is not None: + self._printer_select_dialog.show() + + ## Upload the print job to the group. + def _startPrintJobUpload(self, unique_name: str = None) -> None: + if not self._active_exported_job: + Logger.log("e", "No active exported job to upload!") + return self._progress.show() parts = [ self._createFormPart("name=owner", bytes(self._getUserName(), "utf-8"), "text/plain"), - self._createFormPart("name=\"file\"; filename=\"%s\"" % job.getFileName(), job.getOutput()) + self._createFormPart("name=\"file\"; filename=\"%s\"" % self._active_exported_job.getFileName(), + self._active_exported_job.getOutput()) ] + # If a specific printer was selected we include the name in the request. + # FIXME: Connect should allow the printer UUID here instead of the 'unique_name'. + if unique_name is not None: + parts.append(self._createFormPart("name=require_printer_name", bytes(unique_name, "utf-8"), "text/plain")) # FIXME: move form posting to API client self.postFormWithParts("/cluster-api/v1/print_jobs/", parts, on_finished=self._onPrintUploadCompleted, on_progress=self._onPrintJobUploadProgress) + self._active_exported_job = None ## Handler for print job upload progress. def _onPrintJobUploadProgress(self, bytes_sent: int, bytes_total: int) -> None: