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
@ -46,9 +51,15 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
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:
return printers, print_jobs
# 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
))
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

View file

@ -124,8 +124,8 @@ class CloudOutputDeviceManager(NetworkClient):
local_device_id = active_machine.getMetaDataEntry("um_network_key")
if local_device_id:
active_output_device = CuraApplication.getInstance().getOutputDeviceManager().getActiveDevice()
active_output_device.id
active_output_device = self._output_device_manager.getActiveDevice()
# We must find a match for the active machine and a cloud device
stored_cluster_id = active_machine.getMetaDataEntry("um_cloud_cluster_id")
if stored_cluster_id not in self._remote_clusters.keys():

View file

@ -16,3 +16,66 @@ class CloudCluster(BaseModel):
def validate(self):
if not self.cluster_id:
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)