diff --git a/cura/Machines/Models/DiscoveredPrintersModel.py b/cura/Machines/Models/DiscoveredPrintersModel.py index 803e1d21ba..33e0b7a4d9 100644 --- a/cura/Machines/Models/DiscoveredPrintersModel.py +++ b/cura/Machines/Models/DiscoveredPrintersModel.py @@ -75,7 +75,7 @@ class DiscoveredPrinter(QObject): def readableMachineType(self) -> str: from cura.CuraApplication import CuraApplication machine_manager = CuraApplication.getInstance().getMachineManager() - # In ClusterUM3OutputDevice, when it updates a printer information, it updates the machine type using the field + # In LocalClusterOutputDevice, when it updates a printer information, it updates the machine type using the field # "machine_variant", and for some reason, it's not the machine type ID/codename/... but a human-readable string # like "Ultimaker 3". The code below handles this case. if self._hasHumanReadableMachineTypeName(self._machine_type): diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py index bd61b945cf..12079dc497 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py @@ -101,14 +101,9 @@ class CloudApiClient: # \param cluster_id: The ID of the cluster. # \param cluster_job_id: The ID of the print job within the cluster. # \param action: The name of the action to execute. - def doPrintJobAction(self, cluster_id: str, cluster_job_id: str, action: str, data: Optional[Dict[str, Any]] = None) -> None: - body = b"" - if data: - try: - body = json.dumps({"data": data}).encode() - except JSONDecodeError as err: - Logger.log("w", "Could not encode body: %s", err) - return + def doPrintJobAction(self, cluster_id: str, cluster_job_id: str, action: str, data: Optional[Dict[str, Any]] = None + ) -> None: + body = json.dumps({"data": data}).encode() if data else b"" url = "{}/clusters/{}/print_jobs/{}/action/{}".format(self.CLUSTER_API_ROOT, cluster_id, cluster_job_id, action) self._manager.post(self._createEmptyRequest(url), body) diff --git a/plugins/UM3NetworkPrinting/src/Models/BaseModel.py b/plugins/UM3NetworkPrinting/src/Models/BaseModel.py index 2c5c667f89..131b71ab43 100644 --- a/plugins/UM3NetworkPrinting/src/Models/BaseModel.py +++ b/plugins/UM3NetworkPrinting/src/Models/BaseModel.py @@ -3,6 +3,10 @@ from datetime import datetime, timezone from typing import TypeVar, Dict, List, Any, Type, Union +# Type variable used in the parse methods below, which should be a subclass of BaseModel. +T = TypeVar("T", bound="BaseModel") + + class BaseModel: def __init__(self, **kwargs) -> None: @@ -29,9 +33,6 @@ class BaseModel: def toDict(self) -> Dict[str, Any]: return self.__dict__ - # Type variable used in the parse methods below, which should be a subclass of BaseModel. - T = TypeVar("T", bound="BaseModel") - ## Parses a single model. # \param model_class: The model class. # \param values: The value of the model, which is usually a dictionary, but may also be already parsed. diff --git a/plugins/UM3NetworkPrinting/src/Models/ClusterMaterial.py b/plugins/UM3NetworkPrinting/src/Models/ClusterMaterial.py index 37e4ed390f..8687b015ce 100644 --- a/plugins/UM3NetworkPrinting/src/Models/ClusterMaterial.py +++ b/plugins/UM3NetworkPrinting/src/Models/ClusterMaterial.py @@ -1,5 +1,5 @@ ## Class representing a material that was fetched from the cluster API. -from plugins.UM3NetworkPrinting.src.Models.BaseModel import BaseModel +from .BaseModel import BaseModel class ClusterMaterial(BaseModel): diff --git a/plugins/UM3NetworkPrinting/src/Models/ConfigurationChangeModel.py b/plugins/UM3NetworkPrinting/src/Models/ConfigurationChangeModel.py index 3521b55f63..7b81f6e431 100644 --- a/plugins/UM3NetworkPrinting/src/Models/ConfigurationChangeModel.py +++ b/plugins/UM3NetworkPrinting/src/Models/ConfigurationChangeModel.py @@ -1,8 +1,8 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. - from PyQt5.QtCore import pyqtProperty, QObject + BLOCKING_CHANGE_TYPES = [ "material_insert", "buildplate_change" ] @@ -11,8 +11,7 @@ BLOCKING_CHANGE_TYPES = [ class ConfigurationChangeModel(QObject): def __init__(self, type_of_change: str, index: int, target_name: str, origin_name: str) -> None: super().__init__() - self._type_of_change = type_of_change - # enum = ["material", "print_core_change"] + self._type_of_change = type_of_change # enum = ["material", "print_core_change"] self._can_override = self._type_of_change not in BLOCKING_CHANGE_TYPES self._index = index self._target_name = target_name diff --git a/plugins/UM3NetworkPrinting/src/Models/LocalMaterial.py b/plugins/UM3NetworkPrinting/src/Models/LocalMaterial.py index db9672cc29..9e606fd1c4 100644 --- a/plugins/UM3NetworkPrinting/src/Models/LocalMaterial.py +++ b/plugins/UM3NetworkPrinting/src/Models/LocalMaterial.py @@ -1,5 +1,5 @@ ## Class representing a local material that was fetched from the container registry. -from plugins.UM3NetworkPrinting.src.Models.BaseModel import BaseModel +from .BaseModel import BaseModel class LocalMaterial(BaseModel): diff --git a/plugins/UM3NetworkPrinting/src/Models/UM3PrintJobOutputModel.py b/plugins/UM3NetworkPrinting/src/Models/UM3PrintJobOutputModel.py index 0112ab94eb..3462fc4a54 100644 --- a/plugins/UM3NetworkPrinting/src/Models/UM3PrintJobOutputModel.py +++ b/plugins/UM3NetworkPrinting/src/Models/UM3PrintJobOutputModel.py @@ -6,13 +6,14 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal from cura.PrinterOutput.Models.PrintJobOutputModel import PrintJobOutputModel from cura.PrinterOutput.PrinterOutputController import PrinterOutputController -from plugins.UM3NetworkPrinting.src.Models.ConfigurationChangeModel import ConfigurationChangeModel + +from .ConfigurationChangeModel import ConfigurationChangeModel class UM3PrintJobOutputModel(PrintJobOutputModel): configurationChangesChanged = pyqtSignal() - def __init__(self, output_controller: "PrinterOutputController", key: str = "", name: str = "", parent=None) -> None: + def __init__(self, output_controller: PrinterOutputController, key: str = "", name: str = "", parent=None) -> None: super().__init__(output_controller, key, name, parent) self._configuration_changes = [] # type: List[ConfigurationChangeModel] diff --git a/plugins/UM3NetworkPrinting/src/Network/ClusterApiClient.py b/plugins/UM3NetworkPrinting/src/Network/ClusterApiClient.py index 182e837091..e6a816abcd 100644 --- a/plugins/UM3NetworkPrinting/src/Network/ClusterApiClient.py +++ b/plugins/UM3NetworkPrinting/src/Network/ClusterApiClient.py @@ -8,12 +8,13 @@ from PyQt5.QtCore import QUrl from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply from UM.Logger import Logger -from plugins.UM3NetworkPrinting.src.Models.BaseModel import BaseModel + +from ..Models.BaseModel import BaseModel +from ..Models.Http.ClusterPrintJobStatus import ClusterPrintJobStatus +from ..Models.Http.ClusterPrinterStatus import ClusterPrinterStatus ## The generic type variable used to document the methods below. -from plugins.UM3NetworkPrinting.src.Models.Http.ClusterPrinterStatus import ClusterPrinterStatus - ClusterApiClientModel = TypeVar("ClusterApiClientModel", bound=BaseModel) @@ -53,13 +54,23 @@ class ClusterApiClient: ## Get the print jobs in the cluster. # \param on_finished: The callback in case the response is successful. - def getPrintJobs(self, on_finished: Callable) -> None: + def getPrintJobs(self, on_finished: Callable[[List[ClusterPrintJobStatus]], Any]) -> None: url = f"{self.CLUSTER_API_PREFIX}/print_jobs/" - # reply = self._manager.get(self._createEmptyRequest(url)) - # self._addCallback(reply, on_finished) + reply = self._manager.get(self._createEmptyRequest(url)) + self._addCallback(reply, on_finished, ClusterPrintJobStatus) def requestPrint(self) -> None: - pass + pass # TODO + + ## Move a print job to the top of the queue. + def movePrintJobToTop(self, print_job_uuid: str) -> None: + url = f"{self.CLUSTER_API_PREFIX}/print_jobs/{print_job_uuid}/action/move" + self._manager.post(self._createEmptyRequest(url), json.dumps({"to_position": 0, "list": "queued"}).encode()) + + ## Delete a print job from the queue. + def deletePrintJob(self, print_job_uuid: str) -> None: + url = f"{self.CLUSTER_API_PREFIX}/print_jobs/{print_job_uuid}" + self._manager.deleteResource(self._createEmptyRequest(url)) ## Send a print job action to the cluster. # \param print_job_uuid: The UUID of the print job to perform the action on. @@ -68,7 +79,7 @@ class ClusterApiClient: def doPrintJobAction(self, print_job_uuid: str, action: str, data: Optional[Dict[str, Union[str, int]]] = None ) -> None: url = f"{self.CLUSTER_API_PREFIX}/print_jobs/{print_job_uuid}/action/{action}/" - body = json.loads(data).encode() if data else b"" + body = json.dumps(data).encode() if data else b"" self._manager.put(self._createEmptyRequest(url), body) ## We override _createEmptyRequest in order to add the user credentials. diff --git a/plugins/UM3NetworkPrinting/src/Network/ClusterUM3OutputDevice.py b/plugins/UM3NetworkPrinting/src/Network/LocalClusterOutputDevice.py similarity index 98% rename from plugins/UM3NetworkPrinting/src/Network/ClusterUM3OutputDevice.py rename to plugins/UM3NetworkPrinting/src/Network/LocalClusterOutputDevice.py index 4cbe8b9194..ff57719105 100644 --- a/plugins/UM3NetworkPrinting/src/Network/ClusterUM3OutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/Network/LocalClusterOutputDevice.py @@ -20,7 +20,7 @@ from ..UltimakerNetworkedPrinterOutputDevice import UltimakerNetworkedPrinterOut I18N_CATALOG = i18nCatalog("cura") -class ClusterUM3OutputDevice(UltimakerNetworkedPrinterOutputDevice): +class LocalClusterOutputDevice(UltimakerNetworkedPrinterOutputDevice): activeCameraUrlChanged = pyqtSignal() @@ -88,11 +88,11 @@ class ClusterUM3OutputDevice(UltimakerNetworkedPrinterOutputDevice): @pyqtSlot(str, name="sendJobToTop") def sendJobToTop(self, print_job_uuid: str) -> None: - self._cluster_api.doPrintJobAction(print_job_uuid, "move", {"to_position": 0, "list": "queued"}) + self._cluster_api.movePrintJobToTop(print_job_uuid) @pyqtSlot(str, name="deleteJobFromQueue") def deleteJobFromQueue(self, print_job_uuid: str) -> None: - self._cluster_api.doPrintJobAction(print_job_uuid, "delete") + self._cluster_api.deletePrintJob(print_job_uuid) @pyqtSlot(str, name="forceSendJob") def forceSendJob(self, print_job_uuid: str) -> None: diff --git a/plugins/UM3NetworkPrinting/src/Network/NetworkOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Network/NetworkOutputDeviceManager.py index e4f61ba091..a60fbfa664 100644 --- a/plugins/UM3NetworkPrinting/src/Network/NetworkOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Network/NetworkOutputDeviceManager.py @@ -15,9 +15,10 @@ from UM.Version import Version from cura.CuraApplication import CuraApplication from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice -from plugins.UM3NetworkPrinting.src.Network.ClusterApiClient import ClusterApiClient -from plugins.UM3NetworkPrinting.src.Network.ClusterUM3OutputDevice import ClusterUM3OutputDevice -from plugins.UM3NetworkPrinting.src.Network.ManualPrinterRequest import ManualPrinterRequest + +from .ClusterApiClient import ClusterApiClient +from .LocalClusterOutputDevice import LocalClusterOutputDevice +from .ManualPrinterRequest import ManualPrinterRequest ## The NetworkOutputDeviceManager is responsible for discovering and managing local networked clusters. @@ -37,7 +38,7 @@ class NetworkOutputDeviceManager: def __init__(self) -> None: # Persistent dict containing the networked clusters. - self._discovered_devices = {} # type: Dict[str, ClusterUM3OutputDevice] + self._discovered_devices = {} # type: Dict[str, LocalClusterOutputDevice] self._output_device_manager = CuraApplication.getInstance().getOutputDeviceManager() self._zero_conf = None # type: Optional[Zeroconf] @@ -211,7 +212,7 @@ class NetworkOutputDeviceManager: if cluster_size == -1: return - device = ClusterUM3OutputDevice(key, address, properties) + device = LocalClusterOutputDevice(key, address, properties) CuraApplication.getInstance().getDiscoveredPrintersModel().addDiscoveredPrinter( ip_address=address, diff --git a/plugins/UM3NetworkPrinting/src/SendMaterialJob.py b/plugins/UM3NetworkPrinting/src/SendMaterialJob.py index 8509b3aab3..697ba33a6b 100644 --- a/plugins/UM3NetworkPrinting/src/SendMaterialJob.py +++ b/plugins/UM3NetworkPrinting/src/SendMaterialJob.py @@ -1,6 +1,5 @@ # Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. - import json import os from typing import Dict, TYPE_CHECKING, Set, Optional @@ -9,11 +8,12 @@ from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest from UM.Job import Job from UM.Logger import Logger from cura.CuraApplication import CuraApplication -from plugins.UM3NetworkPrinting.src.Models.ClusterMaterial import ClusterMaterial -from plugins.UM3NetworkPrinting.src.Models.LocalMaterial import LocalMaterial + +from .Models.ClusterMaterial import ClusterMaterial +from .Models.LocalMaterial import LocalMaterial if TYPE_CHECKING: - from plugins.UM3NetworkPrinting.src.Network.ClusterUM3OutputDevice import ClusterUM3OutputDevice + from .Network.LocalClusterOutputDevice import LocalClusterOutputDevice ## Asynchronous job to send material profiles to the printer. @@ -21,9 +21,9 @@ if TYPE_CHECKING: # This way it won't freeze up the interface while sending those materials. class SendMaterialJob(Job): - def __init__(self, device: "ClusterUM3OutputDevice") -> None: + def __init__(self, device: "LocalClusterOutputDevice") -> None: super().__init__() - self.device = device # type: ClusterUM3OutputDevice + self.device = device # type: LocalClusterOutputDevice ## Send the request to the printer and register a callback def run(self) -> None: diff --git a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py index 7f7f69a241..fad2fc46f3 100644 --- a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py +++ b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py @@ -6,8 +6,8 @@ from cura.CuraApplication import CuraApplication from UM.OutputDevice.OutputDeviceManager import ManualDeviceAdditionAttempt from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin -from plugins.UM3NetworkPrinting.src.Network.NetworkOutputDeviceManager import NetworkOutputDeviceManager +from .Network.NetworkOutputDeviceManager import NetworkOutputDeviceManager from .Cloud.CloudOutputDeviceManager import CloudOutputDeviceManager @@ -73,16 +73,6 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): def removeManualDevice(self, key: str, address: Optional[str] = None) -> None: self._network_output_device_manager.removeManualDevice(key, address) - # ## Get the last manual device attempt. - # # Used by the DiscoverUM3Action. - # def getLastManualDevice(self) -> str: - # return self._network_output_device_manager.getLastManualDevice() - - # ## Reset the last manual device attempt. - # # Used by the DiscoverUM3Action. - # def resetLastManualDevice(self) -> None: - # self._network_output_device_manager.resetLastManualDevice() - # ## Check if the prerequsites are in place to start the cloud flow # def checkCloudFlowIsPossible(self, cluster: Optional[CloudOutputDevice]) -> None: # Logger.log("d", "Checking if cloud connection is possible...") diff --git a/plugins/UM3NetworkPrinting/src/UltimakerNetworkedPrinterOutputDevice.py b/plugins/UM3NetworkPrinting/src/UltimakerNetworkedPrinterOutputDevice.py index 0298518ba9..b098be629e 100644 --- a/plugins/UM3NetworkPrinting/src/UltimakerNetworkedPrinterOutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/UltimakerNetworkedPrinterOutputDevice.py @@ -11,12 +11,12 @@ from cura.CuraApplication import CuraApplication from cura.PrinterOutput.Models.PrinterOutputModel import PrinterOutputModel from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState from cura.PrinterOutput.PrinterOutputDevice import ConnectionType -from plugins.UM3NetworkPrinting.src.Models.Http.ClusterPrintJobStatus import ClusterPrintJobStatus from .Utils import formatTimeCompleted, formatDateCompleted from .ClusterOutputController import ClusterOutputController from .Models.UM3PrintJobOutputModel import UM3PrintJobOutputModel from .Models.Http.ClusterPrinterStatus import ClusterPrinterStatus +from .Models.Http.ClusterPrintJobStatus import ClusterPrintJobStatus ## Output device class that forms the basis of Ultimaker networked printer output devices. @@ -211,8 +211,8 @@ class UltimakerNetworkedPrinterOutputDevice(NetworkedPrinterOutputDevice): self.printersChanged.emit() - ## Updates the local list of print jobs with the list received from the cloud. - # \param remote_jobs: The print jobs received from the cloud. + ## Updates the local list of print jobs with the list received from the cluster. + # \param remote_jobs: The print jobs received from the cluster. def _updatePrintJobs(self, remote_jobs: List[ClusterPrintJobStatus]) -> None: # Keep track of the new print jobs to show. @@ -251,6 +251,9 @@ class UltimakerNetworkedPrinterOutputDevice(NetworkedPrinterOutputDevice): self._updateAssignedPrinter(model, remote_job.printer_uuid) return model + def _onPrintJobStateChanged(self) -> None: + pass + ## Updates the printer assignment for the given print job model. def _updateAssignedPrinter(self, model: UM3PrintJobOutputModel, printer_uuid: str) -> None: printer = next((p for p in self._printers if printer_uuid == p.key), None) diff --git a/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudApiClient.py b/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudApiClient.py index c1de91cf7c..442c422f95 100644 --- a/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudApiClient.py +++ b/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudApiClient.py @@ -6,12 +6,14 @@ from unittest import TestCase from unittest.mock import patch, MagicMock from cura.UltimakerCloudAuthentication import CuraCloudAPIRoot + from ...src.Cloud import CloudApiClient -from plugins.UM3NetworkPrinting.src.Models.CloudClusterResponse import CloudClusterResponse -from plugins.UM3NetworkPrinting.src.Models.CloudClusterStatus import CloudClusterStatus -from plugins.UM3NetworkPrinting.src.Models.CloudPrintJobResponse import CloudPrintJobResponse -from plugins.UM3NetworkPrinting.src.Models.CloudPrintJobUploadRequest import CloudPrintJobUploadRequest -from plugins.UM3NetworkPrinting.src.Models.CloudError import CloudError +from ...src.Models.Http.CloudClusterResponse import CloudClusterResponse +from ...src.Models.Http.CloudClusterStatus import CloudClusterStatus +from ...src.Models.Http.CloudPrintJobResponse import CloudPrintJobResponse +from ...src.Models.Http.CloudPrintJobUploadRequest import CloudPrintJobUploadRequest +from ...src.Models.Http.CloudError import CloudError + from .Fixtures import readFixture, parseFixture from .NetworkManagerMock import NetworkManagerMock diff --git a/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDevice.py b/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDevice.py index 46a2414005..07c44753c4 100644 --- a/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDevice.py +++ b/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDevice.py @@ -9,7 +9,7 @@ from cura.UltimakerCloudAuthentication import CuraCloudAPIRoot from cura.PrinterOutput.Models.PrinterOutputModel import PrinterOutputModel from ...src.Cloud import CloudApiClient from ...src.Cloud.CloudOutputDevice import CloudOutputDevice -from plugins.UM3NetworkPrinting.src.Models.CloudClusterResponse import CloudClusterResponse +from ...src.Models.Http.CloudClusterResponse import CloudClusterResponse from .Fixtures import readFixture, parseFixture from .NetworkManagerMock import NetworkManagerMock diff --git a/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDeviceManager.py index b0d1c83f8d..105f42c798 100644 --- a/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/tests/Cloud/TestCloudOutputDeviceManager.py @@ -5,10 +5,12 @@ from unittest.mock import patch, MagicMock from UM.OutputDevice.OutputDeviceManager import OutputDeviceManager from cura.UltimakerCloudAuthentication import CuraCloudAPIRoot + from ...src.Cloud import CloudApiClient from ...src.Cloud import CloudOutputDeviceManager -from plugins.UM3NetworkPrinting.src.Models.CloudClusterResponse import CloudClusterResponse +from ...src.Models.Http.CloudClusterResponse import CloudClusterResponse from .Fixtures import parseFixture, readFixture + from .NetworkManagerMock import NetworkManagerMock, FakeSignal