Added models to process the data from the api results

Added code to update the UI models
This commit is contained in:
Marijn Deé 2018-11-29 17:03:11 +01:00
parent 8a72ba3cfa
commit 763291821f
3 changed files with 246 additions and 31 deletions

View file

@ -1,18 +1,23 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import json
import os
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 UM import i18nCatalog
from UM.FileHandler.FileHandler import FileHandler
from UM.Logger import Logger
from UM.Scene.SceneNode import SceneNode
from UM.Settings import ContainerRegistry
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.PrinterOutputModel import PrinterOutputModel
from .Models import CloudClusterPrinter, CloudClusterPrinterConfiguration, CloudClusterPrinterConfigurationMaterial, CloudClusterPrintJob, CloudClusterPrintJobConstraint
from .CloudOutputController import CloudOutputController
from ..UM3PrintJobOutputModel import UM3PrintJobOutputModel
@ -38,17 +43,23 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
# Signal triggered when the print jobs in the queue were changed.
printJobsChanged = pyqtSignal()
def __init__(self, device_id: str, parent: QObject = None):
super().__init__(device_id = device_id, address = "", properties = {}, parent = parent)
self._setInterfaceElements()
self._device_id = device_id
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.
self._printers = []
self._print_jobs = []
self._printers = {} # type: Dict[str, PrinterOutputModel]
self._print_jobs = {} # type: Dict[str, PrintJobOutputModel]
self._number_of_extruders = 2 # All networked printers are dual-extrusion Ultimaker machines.
## We need to override _createEmptyRequest to work for the cloud.
@ -90,8 +101,8 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
## Get remote print jobs.
@pyqtProperty("QVariantList", notify = printJobsChanged)
def printJobs(self) -> List[UM3PrintJobOutputModel]:
return self._print_jobs
def queuedPrintJobs(self) -> List[UM3PrintJobOutputModel]:
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.
def connect(self) -> None:
@ -111,41 +122,182 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
.format(status_code, reply.readAll()))
return
data = self._parseStatusResponse(reply)
if data is None:
printers, print_jobs = self._parseStatusResponse(reply)
if not printers and not print_jobs:
return
# Update all data from the cluster.
self._updatePrinters(data.get("printers", []))
self._updatePrintJobs(data.get("print_jobs", []))
self._updatePrinters(printers)
self._updatePrintJobs(print_jobs)
@staticmethod
def _parseStatusResponse(reply: QNetworkReply) -> Optional[dict]:
def _parseStatusResponse(reply: QNetworkReply): # Optional[(CloudClusterPrinter, CloudClusterPrintJob)] doesn't work
printers = []
print_jobs = []
s = ''
try:
result = json.loads(bytes(reply.readAll()).decode("utf-8"))
# TODO: use model or named tuple here.
return result
s = json.loads(bytes(reply.readAll()).decode("utf-8"))
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:
Logger.logException("w", "Unable to decode JSON from reply.")
return None
def _updatePrinters(self, remote_printers: List[Dict[str, any]]) -> None:
# TODO: use model or tuple for remote_printers data
for printer in remote_printers:
# If the printer does not exist yet, create it.
if not self._getPrinterByKey(printer["uuid"]):
self._printers.append(PrinterOutputModel(
output_controller = CloudOutputController(self),
number_of_extruders = self._number_of_extruders
))
return printers, print_jobs
def _updatePrinters(self, printers: List[CloudClusterPrinter]) -> None:
remote_printers = {p.uuid: p for p in printers}
removed_printers = set(self._printers.keys()).difference(set(remote_printers.keys()))
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
self.printersChanged.emit()
def _updatePrintJobs(self, remote_print_jobs: List[Dict[str, any]]) -> None:
# TODO: use model or tuple for remote_print_jobs data
pass
def _addPrinter(self, printer):
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):
# TODO: implement this