mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-13 09:47:50 -06:00
Added models to process the data from the api results
Added code to update the UI models
This commit is contained in:
parent
8a72ba3cfa
commit
763291821f
3 changed files with 246 additions and 31 deletions
|
@ -1,18 +1,23 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
from typing import List, Optional, Dict
|
from typing import List, Optional, Dict
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, QUrl
|
from PyQt5.QtCore import QObject, pyqtSignal, QUrl, pyqtProperty
|
||||||
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
|
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
|
||||||
|
|
||||||
from UM import i18nCatalog
|
from UM import i18nCatalog
|
||||||
from UM.FileHandler.FileHandler import FileHandler
|
from UM.FileHandler.FileHandler import FileHandler
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Scene.SceneNode import SceneNode
|
from UM.Scene.SceneNode import SceneNode
|
||||||
|
from UM.Settings import ContainerRegistry
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
|
from cura.PrinterOutput import PrinterOutputController, PrintJobOutputModel
|
||||||
|
from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
|
||||||
from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState
|
from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState
|
||||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||||
|
from .Models import CloudClusterPrinter, CloudClusterPrinterConfiguration, CloudClusterPrinterConfigurationMaterial, CloudClusterPrintJob, CloudClusterPrintJobConstraint
|
||||||
|
|
||||||
from .CloudOutputController import CloudOutputController
|
from .CloudOutputController import CloudOutputController
|
||||||
from ..UM3PrintJobOutputModel import UM3PrintJobOutputModel
|
from ..UM3PrintJobOutputModel import UM3PrintJobOutputModel
|
||||||
|
@ -46,9 +51,15 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
||||||
self._device_id = device_id
|
self._device_id = device_id
|
||||||
self._account = CuraApplication.getInstance().getCuraAPI().account
|
self._account = CuraApplication.getInstance().getCuraAPI().account
|
||||||
|
|
||||||
|
# We re-use the Cura Connect monitor tab to get the most functionality right away.
|
||||||
|
self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||||
|
"../../resources/qml/ClusterMonitorItem.qml")
|
||||||
|
self._control_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||||
|
"../../resources/qml/ClusterControlItem.qml")
|
||||||
|
|
||||||
# Properties to populate later on with received cloud data.
|
# Properties to populate later on with received cloud data.
|
||||||
self._printers = []
|
self._printers = {} # type: Dict[str, PrinterOutputModel]
|
||||||
self._print_jobs = []
|
self._print_jobs = {} # type: Dict[str, PrintJobOutputModel]
|
||||||
self._number_of_extruders = 2 # All networked printers are dual-extrusion Ultimaker machines.
|
self._number_of_extruders = 2 # All networked printers are dual-extrusion Ultimaker machines.
|
||||||
|
|
||||||
## We need to override _createEmptyRequest to work for the cloud.
|
## We need to override _createEmptyRequest to work for the cloud.
|
||||||
|
@ -90,8 +101,8 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
||||||
|
|
||||||
## Get remote print jobs.
|
## Get remote print jobs.
|
||||||
@pyqtProperty("QVariantList", notify = printJobsChanged)
|
@pyqtProperty("QVariantList", notify = printJobsChanged)
|
||||||
def printJobs(self) -> List[UM3PrintJobOutputModel]:
|
def queuedPrintJobs(self) -> List[UM3PrintJobOutputModel]:
|
||||||
return self._print_jobs
|
return [print_job for print_job in self._print_jobs if print_job.state == "queued" or print_job.state == "error"]
|
||||||
|
|
||||||
## Called when the connection to the cluster changes.
|
## Called when the connection to the cluster changes.
|
||||||
def connect(self) -> None:
|
def connect(self) -> None:
|
||||||
|
@ -111,41 +122,182 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
||||||
.format(status_code, reply.readAll()))
|
.format(status_code, reply.readAll()))
|
||||||
return
|
return
|
||||||
|
|
||||||
data = self._parseStatusResponse(reply)
|
printers, print_jobs = self._parseStatusResponse(reply)
|
||||||
if data is None:
|
if not printers and not print_jobs:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Update all data from the cluster.
|
# Update all data from the cluster.
|
||||||
self._updatePrinters(data.get("printers", []))
|
self._updatePrinters(printers)
|
||||||
self._updatePrintJobs(data.get("print_jobs", []))
|
self._updatePrintJobs(print_jobs)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parseStatusResponse(reply: QNetworkReply) -> Optional[dict]:
|
def _parseStatusResponse(reply: QNetworkReply): # Optional[(CloudClusterPrinter, CloudClusterPrintJob)] doesn't work
|
||||||
|
|
||||||
|
printers = []
|
||||||
|
print_jobs = []
|
||||||
|
s = ''
|
||||||
try:
|
try:
|
||||||
result = json.loads(bytes(reply.readAll()).decode("utf-8"))
|
s = json.loads(bytes(reply.readAll()).decode("utf-8"))
|
||||||
# TODO: use model or named tuple here.
|
|
||||||
return result
|
for p in s["printers"]:
|
||||||
|
printer = CloudClusterPrinter(**p)
|
||||||
|
configuration = printer.configuration
|
||||||
|
printer.configuration = []
|
||||||
|
for c in configuration:
|
||||||
|
extruder = CloudClusterPrinterConfiguration(**c)
|
||||||
|
extruder.material = CloudClusterPrinterConfigurationMaterial(extruder.material)
|
||||||
|
printer.configuration.append(extruder)
|
||||||
|
|
||||||
|
printers.append(printer)
|
||||||
|
|
||||||
|
for j in s["print_jobs"]:
|
||||||
|
job = CloudClusterPrintJob(**j)
|
||||||
|
constraints = job.constraints
|
||||||
|
job.constraints = []
|
||||||
|
for c in constraints:
|
||||||
|
job.constraints.append(CloudClusterPrintJobConstraint(**c))
|
||||||
|
|
||||||
|
configuration = job.configuration
|
||||||
|
job.configuration = []
|
||||||
|
for c in configuration:
|
||||||
|
configuration = CloudClusterPrinterConfiguration(**c)
|
||||||
|
configuration.material = CloudClusterPrinterConfigurationMaterial(configuration.material)
|
||||||
|
job.configuration.append(configuration)
|
||||||
|
|
||||||
|
print_jobs.append(job)
|
||||||
|
|
||||||
except json.decoder.JSONDecodeError:
|
except json.decoder.JSONDecodeError:
|
||||||
Logger.logException("w", "Unable to decode JSON from reply.")
|
Logger.logException("w", "Unable to decode JSON from reply.")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _updatePrinters(self, remote_printers: List[Dict[str, any]]) -> None:
|
return printers, print_jobs
|
||||||
# TODO: use model or tuple for remote_printers data
|
|
||||||
for printer in remote_printers:
|
|
||||||
|
|
||||||
# If the printer does not exist yet, create it.
|
def _updatePrinters(self, printers: List[CloudClusterPrinter]) -> None:
|
||||||
if not self._getPrinterByKey(printer["uuid"]):
|
remote_printers = {p.uuid: p for p in printers}
|
||||||
self._printers.append(PrinterOutputModel(
|
|
||||||
output_controller = CloudOutputController(self),
|
removed_printers = set(self._printers.keys()).difference(set(remote_printers.keys()))
|
||||||
number_of_extruders = self._number_of_extruders
|
new_printers = set(remote_printers.keys()).difference(set(self._printers.keys()))
|
||||||
))
|
updated_printers = set(self._printers.keys()).intersection(set(remote_printers.keys()))
|
||||||
|
|
||||||
|
for p in removed_printers:
|
||||||
|
self._removePrinter(p)
|
||||||
|
|
||||||
|
for p in new_printers:
|
||||||
|
self._addPrinter(printers[p])
|
||||||
|
self._updatePrinter(printers[p])
|
||||||
|
|
||||||
|
for p in updated_printers:
|
||||||
|
self._updatePrinter(printers[p])
|
||||||
|
|
||||||
# TODO: properly handle removed and updated printers
|
# TODO: properly handle removed and updated printers
|
||||||
self.printersChanged.emit()
|
self.printersChanged.emit()
|
||||||
|
|
||||||
def _updatePrintJobs(self, remote_print_jobs: List[Dict[str, any]]) -> None:
|
|
||||||
# TODO: use model or tuple for remote_print_jobs data
|
def _addPrinter(self, printer):
|
||||||
pass
|
self._printers[printer.uuid] = self._createPrinterOutputModel(self, printer)
|
||||||
|
|
||||||
|
def _createPrinterOutputModel(self, printer: CloudClusterPrinter) -> PrinterOutputModel:
|
||||||
|
return PrinterOutputModel(PrinterOutputController(self), len(printer.configuration),
|
||||||
|
firmware_version=printer.firmware_version)
|
||||||
|
|
||||||
|
def _updatePrinter(self, guid : str, printer : CloudClusterPrinter):
|
||||||
|
model = self._printers[guid]
|
||||||
|
self._printers[guid] = self._updatePrinterOutputModel(self, printer)
|
||||||
|
|
||||||
|
def _updatePrinterOutputModel(self, printer: CloudClusterPrinter, model : PrinterOutputModel) -> PrinterOutputModel:
|
||||||
|
model.updateKey(printer.uuid)
|
||||||
|
model.updateName(printer.friendly_name)
|
||||||
|
model.updateType(printer.machine_variant)
|
||||||
|
model.updateState(printer.status if printer.enabled else "disabled")
|
||||||
|
|
||||||
|
for index in range(0, len(printer.configuration)):
|
||||||
|
try:
|
||||||
|
extruder = model.extruders[index]
|
||||||
|
extruder_data = printer.configuration[index]
|
||||||
|
except IndexError:
|
||||||
|
break
|
||||||
|
|
||||||
|
extruder.updateHotendID(extruder_data.print_core_id)
|
||||||
|
|
||||||
|
material_data = extruder_data.material
|
||||||
|
if extruder.activeMaterial is None or extruder.activeMaterial.guid != material.guid:
|
||||||
|
material = self._createMaterialOutputModel(material_data)
|
||||||
|
extruder.updateActiveMaterial(material)
|
||||||
|
|
||||||
|
def _createMaterialOutputModel(self, material: CloudClusterPrinterConfigurationMaterial) -> MaterialOutputModel:
|
||||||
|
material_manager = CuraApplication.getInstance().getMaterialManager()
|
||||||
|
material_group_list = material_manager.getMaterialGroupListByGUID(material.guid) or []
|
||||||
|
|
||||||
|
# Sort the material groups by "is_read_only = True" first, and then the name alphabetically.
|
||||||
|
read_only_material_group_list = list(filter(lambda x: x.is_read_only, material_group_list))
|
||||||
|
non_read_only_material_group_list = list(filter(lambda x: not x.is_read_only, material_group_list))
|
||||||
|
material_group = None
|
||||||
|
if read_only_material_group_list:
|
||||||
|
read_only_material_group_list = sorted(read_only_material_group_list, key=lambda x: x.name)
|
||||||
|
material_group = read_only_material_group_list[0]
|
||||||
|
elif non_read_only_material_group_list:
|
||||||
|
non_read_only_material_group_list = sorted(non_read_only_material_group_list, key=lambda x: x.name)
|
||||||
|
material_group = non_read_only_material_group_list[0]
|
||||||
|
|
||||||
|
if material_group:
|
||||||
|
container = material_group.root_material_node.getContainer()
|
||||||
|
color = container.getMetaDataEntry("color_code")
|
||||||
|
brand = container.getMetaDataEntry("brand")
|
||||||
|
material_type = container.getMetaDataEntry("material")
|
||||||
|
name = container.getName()
|
||||||
|
else:
|
||||||
|
Logger.log("w",
|
||||||
|
"Unable to find material with guid {guid}. Using data as provided by cluster".format(
|
||||||
|
guid=material.guid))
|
||||||
|
color = material.color
|
||||||
|
brand = material.brand
|
||||||
|
material_type = material.material
|
||||||
|
name = "Empty" if material.material == "empty" else "Unknown"
|
||||||
|
|
||||||
|
return MaterialOutputModel(guid=material.guid, type=material_type, brand=brand, color=color, name=name)
|
||||||
|
|
||||||
|
|
||||||
|
def _removePrinter(self, guid):
|
||||||
|
del self._printers[guid]
|
||||||
|
|
||||||
|
def _updatePrintJobs(self, jobs: List[CloudClusterPrintJob]) -> None:
|
||||||
|
remote_jobs = {j.uuid: j for j in jobs}
|
||||||
|
|
||||||
|
removed_jobs = set(self._print_jobs.keys()).difference(set(remote_jobs.keys()))
|
||||||
|
new_jobs = set(remote_jobs.keys()).difference(set(self._print_jobs.keys()))
|
||||||
|
updated_jobs = set(self._print_jobs.keys()).intersection(set(remote_jobs.keys()))
|
||||||
|
|
||||||
|
for j in removed_jobs:
|
||||||
|
self._removePrintJob(j)
|
||||||
|
|
||||||
|
for j in new_jobs:
|
||||||
|
self._addPrintJob(jobs[j])
|
||||||
|
|
||||||
|
for j in updated_jobs:
|
||||||
|
self._updatePrintJob(jobs[j])
|
||||||
|
|
||||||
|
# TODO: properly handle removed and updated printers
|
||||||
|
self.printJobsChanged()
|
||||||
|
|
||||||
|
def _addPrintJob(self, job: CloudClusterPrintJob):
|
||||||
|
self._print_jobs[job.uuid] = self._createPrintJobOutputModel(job)
|
||||||
|
|
||||||
|
def _createPrintJobOutputModel(self, job:CloudClusterPrintJob) -> PrintJobOutputModel:
|
||||||
|
controller = self._printers[job.printer_uuid]._controller # TODO: Can we access this property?
|
||||||
|
model = PrintJobOutputModel(controller, job.uuid, job.name)
|
||||||
|
assigned_printer = self._printes[job.printer_uuid] # TODO: Or do we have to use the assigned_to field?
|
||||||
|
model.updateAssignedPrinter(assigned_printer)
|
||||||
|
|
||||||
|
def _updatePrintJobOutputModel(self, guid: str, job:CloudClusterPrintJob):
|
||||||
|
model =self._print_jobs[guid]
|
||||||
|
|
||||||
|
model.updateTimeTotal(job.time_total)
|
||||||
|
model.updateTimeElapsed(job.time_elapsed)
|
||||||
|
model.updateOwner(job.owner)
|
||||||
|
model.updateState(job.status)
|
||||||
|
|
||||||
|
def _removePrintJob(self, guid:str):
|
||||||
|
del self._print_jobs[guid]
|
||||||
|
|
||||||
def _addPrintJobToQueue(self):
|
def _addPrintJobToQueue(self):
|
||||||
# TODO: implement this
|
# TODO: implement this
|
||||||
|
|
|
@ -124,8 +124,8 @@ class CloudOutputDeviceManager(NetworkClient):
|
||||||
|
|
||||||
local_device_id = active_machine.getMetaDataEntry("um_network_key")
|
local_device_id = active_machine.getMetaDataEntry("um_network_key")
|
||||||
if local_device_id:
|
if local_device_id:
|
||||||
active_output_device = CuraApplication.getInstance().getOutputDeviceManager().getActiveDevice()
|
active_output_device = self._output_device_manager.getActiveDevice()
|
||||||
active_output_device.id
|
# We must find a match for the active machine and a cloud device
|
||||||
|
|
||||||
stored_cluster_id = active_machine.getMetaDataEntry("um_cloud_cluster_id")
|
stored_cluster_id = active_machine.getMetaDataEntry("um_cloud_cluster_id")
|
||||||
if stored_cluster_id not in self._remote_clusters.keys():
|
if stored_cluster_id not in self._remote_clusters.keys():
|
||||||
|
|
|
@ -16,3 +16,66 @@ class CloudCluster(BaseModel):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
if not self.cluster_id:
|
if not self.cluster_id:
|
||||||
raise ValueError("cluster_id is required on CloudCluster")
|
raise ValueError("cluster_id is required on CloudCluster")
|
||||||
|
|
||||||
|
|
||||||
|
## Class representing a cloud cluster printer configuration
|
||||||
|
class CloudClusterPrinterConfigurationMaterial(BaseModel):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.guid = None # type: str
|
||||||
|
self.brand = None # type: str
|
||||||
|
self.color = None # type: str
|
||||||
|
self.material = None # type: str
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
## Class representing a cloud cluster printer configuration
|
||||||
|
class CloudClusterPrinterConfiguration(BaseModel):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.extruder_index = None # type: str
|
||||||
|
self.material = None # type: CloudClusterPrinterConfigurationMaterial
|
||||||
|
self.nozzle_diameter = None # type: str
|
||||||
|
self.printer_core_id = None # type: str
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
## Class representing a cluster printer
|
||||||
|
class CloudClusterPrinter(BaseModel):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.configuration = None # type: CloudClusterPrinterConfiguration
|
||||||
|
self.enabled = None # type: str
|
||||||
|
self.firmware_version = None # type: str
|
||||||
|
self.friendly_name = None # type: str
|
||||||
|
self.ip_address = None # type: str
|
||||||
|
self.machine_variant = None # type: str
|
||||||
|
self.status = None # type: str
|
||||||
|
self.unique_name = None # type: str
|
||||||
|
self.uuid = None # type: str
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
## Class representing a cloud cluster print job constraint
|
||||||
|
class CloudClusterPrintJobConstraint(BaseModel):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.require_printer_name: None # type: str
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
## Class representing a print job
|
||||||
|
class CloudClusterPrintJob(BaseModel):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.assigned_to = None # type: str
|
||||||
|
self.configuration = None # type: str
|
||||||
|
self.constraints = None # type: str
|
||||||
|
self.created_at = None # type: str
|
||||||
|
self.force = None # type: str
|
||||||
|
self.last_seen = None # type: str
|
||||||
|
self.machine_variant = None # type: str
|
||||||
|
self.name = None # type: str
|
||||||
|
self.network_error_count = None # type: str
|
||||||
|
self.owner = None # type: str
|
||||||
|
self.printer_uuid = None # type: str
|
||||||
|
self.started = None # type: str
|
||||||
|
self.status = None # type: str
|
||||||
|
self.time_elapsed = None # type: str
|
||||||
|
self.time_total = None # type: str
|
||||||
|
self.uuid = None # type: str
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue