STAR-322: Improving configuration models interface

This commit is contained in:
Daniel Schiavini 2018-12-07 12:04:02 +01:00
parent a1c252d3a2
commit 4f82a2759a
7 changed files with 278 additions and 183 deletions

View file

@ -4,6 +4,7 @@
from UM.FileHandler.FileHandler import FileHandler #For typing. from UM.FileHandler.FileHandler import FileHandler #For typing.
from UM.Logger import Logger from UM.Logger import Logger
from UM.Scene.SceneNode import SceneNode #For typing. from UM.Scene.SceneNode import SceneNode #For typing.
from cura.API import Account
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
@ -162,9 +163,15 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
part.setBody(data) part.setBody(data)
return part return part
## Convenience function to get the username from the OS. ## Convenience function to get the username, either from the cloud or from the OS.
# The code was copied from the getpass module, as we try to use as little dependencies as possible.
def _getUserName(self) -> str: def _getUserName(self) -> str:
# check first if we are logged in with the Ultimaker Account
account = CuraApplication.getInstance().getCuraAPI().account # type: Account
if account and account.isLoggedIn:
return account.userName
# Otherwise get the username from the US
# The code below was copied from the getpass module, as we try to use as little dependencies as possible.
for name in ("LOGNAME", "USER", "LNAME", "USERNAME"): for name in ("LOGNAME", "USER", "LNAME", "USERNAME"):
user = os.environ.get(name) user = os.environ.get(name)
if user: if user:

View file

@ -16,7 +16,6 @@ from UM.Qt.Duration import Duration, DurationFormat
from UM.Scene.SceneNode import SceneNode from UM.Scene.SceneNode import SceneNode
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
from cura.PrinterOutput.NetworkedPrinterOutputDevice import AuthState, NetworkedPrinterOutputDevice from cura.PrinterOutput.NetworkedPrinterOutputDevice import AuthState, NetworkedPrinterOutputDevice
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
from plugins.UM3NetworkPrinting.src.Cloud.CloudOutputController import CloudOutputController from plugins.UM3NetworkPrinting.src.Cloud.CloudOutputController import CloudOutputController
from ..MeshFormatHandler import MeshFormatHandler from ..MeshFormatHandler import MeshFormatHandler
@ -28,7 +27,7 @@ from .Models.CloudPrintResponse import CloudPrintResponse
from .Models.CloudJobResponse import CloudJobResponse from .Models.CloudJobResponse import CloudJobResponse
from .Models.CloudClusterPrinter import CloudClusterPrinter from .Models.CloudClusterPrinter import CloudClusterPrinter
from .Models.CloudClusterPrintJob import CloudClusterPrintJob from .Models.CloudClusterPrintJob import CloudClusterPrintJob
from .Utils import findChanges from .Utils import findChanges, formatDateCompleted, formatTimeCompleted
## Class that contains all the translations for this module. ## Class that contains all the translations for this module.
@ -55,6 +54,12 @@ class T:
UPLOAD_SUCCESS_TITLE = _I18N_CATALOG.i18nc("@info:title", "Data Sent") UPLOAD_SUCCESS_TITLE = _I18N_CATALOG.i18nc("@info:title", "Data Sent")
UPLOAD_SUCCESS_TEXT = _I18N_CATALOG.i18nc("@info:status", "Print job was successfully sent to the printer.") UPLOAD_SUCCESS_TEXT = _I18N_CATALOG.i18nc("@info:status", "Print job was successfully sent to the printer.")
JOB_COMPLETED_TITLE = _I18N_CATALOG.i18nc("@info:status", "Print finished")
JOB_COMPLETED_PRINTER = _I18N_CATALOG.i18nc("@info:status",
"Printer '{printer_name}' has finished printing '{job_name}'.")
JOB_COMPLETED_NO_PRINTER = _I18N_CATALOG.i18nc("@info:status", "The print job '{job_name}' was finished.")
## The cloud output device is a network output device that works remotely but has limited functionality. ## The cloud output device is a network output device that works remotely but has limited functionality.
# Currently it only supports viewing the printer and print job status and adding a new job to the queue. # Currently it only supports viewing the printer and print job status and adding a new job to the queue.
@ -65,7 +70,7 @@ class T:
class CloudOutputDevice(NetworkedPrinterOutputDevice): class CloudOutputDevice(NetworkedPrinterOutputDevice):
# The interval with which the remote clusters are checked # The interval with which the remote clusters are checked
CHECK_CLUSTER_INTERVAL = 2.0 # seconds CHECK_CLUSTER_INTERVAL = 4.0 # seconds
# Signal triggered when the print jobs in the queue were changed. # Signal triggered when the print jobs in the queue were changed.
printJobsChanged = pyqtSignal() printJobsChanged = pyqtSignal()
@ -109,6 +114,7 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
# We only allow a single upload at a time. # We only allow a single upload at a time.
self._sending_job = False self._sending_job = False
# TODO: handle progress messages in another class.
self._progress_message = None # type: Optional[Message] self._progress_message = None # type: Optional[Message]
## Gets the host name of this device ## Gets the host name of this device
@ -128,7 +134,7 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
return network_key.startswith(self._host_name) return network_key.startswith(self._host_name)
## Set all the interface elements and texts for this output device. ## Set all the interface elements and texts for this output device.
def _setInterfaceElements(self): def _setInterfaceElements(self) -> None:
self.setPriority(2) # make sure we end up below the local networking and above 'save to file' self.setPriority(2) # make sure we end up below the local networking and above 'save to file'
self.setName(self._id) self.setName(self._id)
self.setShortDescription(T.PRINT_VIA_CLOUD_BUTTON) self.setShortDescription(T.PRINT_VIA_CLOUD_BUTTON)
@ -157,13 +163,192 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
# TODO: Remove extension from the file name, since we are using content types now # TODO: Remove extension from the file name, since we are using content types now
request = CloudJobUploadRequest( request = CloudJobUploadRequest(
job_name = file_name + "." + mesh_format.file_extension, job_name = file_name, ## + "." + mesh_format.file_extension,
file_size = len(mesh_bytes), file_size = len(mesh_bytes),
content_type = mesh_format.mime_type, content_type = mesh_format.mime_type,
) )
self._api.requestUpload(request, lambda response: self._onPrintJobCreated(mesh_bytes, response)) self._api.requestUpload(request, lambda response: self._onPrintJobCreated(mesh_bytes, response))
## Get remote printers. ## Called when the connection to the cluster changes.
def connect(self) -> None:
super().connect()
## Called when the network data should be updated.
def _update(self) -> None:
super()._update()
if self._last_response_time and time() - self._last_response_time < self.CHECK_CLUSTER_INTERVAL:
return # avoid calling the cloud too often
if self._account.isLoggedIn:
self.setAuthenticationState(AuthState.Authenticated)
self._api.getClusterStatus(self._device_id, self._onStatusCallFinished)
else:
self.setAuthenticationState(AuthState.NotAuthenticated)
## Method called when HTTP request to status endpoint is finished.
# Contains both printers and print jobs statuses in a single response.
def _onStatusCallFinished(self, status: CloudClusterStatus) -> None:
# Update all data from the cluster.
self._updatePrinters(status.printers)
self._updatePrintJobs(status.print_jobs)
## Updates the local list of printers with the list received from the cloud.
# \param jobs: The printers received from the cloud.
def _updatePrinters(self, printers: List[CloudClusterPrinter]) -> None:
previous = {p.key: p for p in self._printers} # type: Dict[str, PrinterOutputModel]
received = {p.uuid: p for p in printers} # type: Dict[str, CloudClusterPrinter]
removed_printers, added_printers, updated_printers = findChanges(previous, received)
for removed_printer in removed_printers:
if self._active_printer == removed_printer:
self.setActivePrinter(None)
self._printers.remove(removed_printer)
for added_printer in added_printers:
self._printers.append(added_printer.createOutputModel(CloudOutputController(self)))
for model, printer in updated_printers:
printer.updateOutputModel(model)
# Always have an active printer
if not self._active_printer:
self.setActivePrinter(self._printers[0])
if removed_printers or added_printers or updated_printers:
self._clusterPrintersChanged.emit()
## Updates the local list of print jobs with the list received from the cloud.
# \param jobs: The print jobs received from the cloud.
def _updatePrintJobs(self, jobs: List[CloudClusterPrintJob]) -> None:
received = {j.uuid: j for j in jobs} # type: Dict[str, CloudClusterPrintJob]
previous = {j.key: j for j in self._print_jobs} # type: Dict[str, UM3PrintJobOutputModel]
removed_jobs, added_jobs, updated_jobs = findChanges(previous, received)
# TODO: we see that not all data in the UI is correctly updated when the queue and active jobs change.
# TODO: we need to fix this here somehow by updating the correct output models.
# TODO: the configuration drop down in the slice window is not populated because we are missing some data.
# TODO: to fix this we need to implement more data as shown in ClusterUM3OutputDevice._createPrintJobModel
for removed_job in removed_jobs:
self._print_jobs.remove(removed_job)
for added_job in added_jobs:
self._addPrintJob(added_job)
for model, job in updated_jobs:
job.updateOutputModel(model)
if job.printer_uuid:
self._updateAssignedPrinter(model, job.printer_uuid)
# We only have to update when jobs are added or removed
# updated jobs push their changes via their output model
if added_jobs or removed_jobs or updated_jobs:
self.printJobsChanged.emit()
## Registers a new print job received via the cloud API.
# \param job: The print job received.
def _addPrintJob(self, job: CloudClusterPrintJob) -> None:
model = job.createOutputModel(CloudOutputController(self))
model.stateChanged.connect(self._onPrintJobStateChanged)
if job.printer_uuid:
self._updateAssignedPrinter(model, job.printer_uuid)
self._print_jobs.append(model)
## Handles the event of a change in a print job state
def _onPrintJobStateChanged(self) -> None:
username = self._account.userName
finished_jobs = [job for job in self._print_jobs if job.state == "wait_cleanup"]
newly_finished_jobs = [job for job in finished_jobs if job not in self._finished_jobs and job.owner == username]
for job in newly_finished_jobs:
if job.assignedPrinter:
job_completed_text = T.JOB_COMPLETED_PRINTER.format(printer_name=job.assignedPrinter.name,
job_name=job.name)
else:
job_completed_text = T.JOB_COMPLETED_NO_PRINTER.format(job_name=job.name)
job_completed_message = Message(text=job_completed_text, title = T.JOB_COMPLETED_TITLE)
job_completed_message.show()
# Ensure UI gets updated
self.printJobsChanged.emit()
## 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)
if not printer:
return Logger.log("w", "Missing printer %s for job %s in %s", model.assignedPrinter, model.key,
[p.key for p in self._printers])
printer.updateActivePrintJob(model)
model.updateAssignedPrinter(printer)
## Uploads the mesh when the print job was registered with the cloud API.
# \param mesh: The bytes to upload.
# \param job_response: The response received from the cloud API.
def _onPrintJobCreated(self, mesh: bytes, job_response: CloudJobResponse) -> None:
self._api.uploadMesh(job_response, mesh, self._onPrintJobUploaded, self._updateUploadProgress,
lambda _: self._onUploadError(T.UPLOAD_ERROR))
## Requests the print to be sent to the printer when we finished uploading the mesh.
# \param job_id: The ID of the job.
def _onPrintJobUploaded(self, job_id: str) -> None:
self._api.requestPrint(self._device_id, job_id, self._onUploadSuccess)
## Updates the progress of the mesh upload.
# \param progress: The amount of percentage points uploaded until now (0-100).
def _updateUploadProgress(self, progress: int) -> None:
if not self._progress_message:
self._progress_message = Message(
text = T.SENDING_DATA_TEXT,
title = T.SENDING_DATA_TITLE,
progress = -1,
lifetime = 0,
dismissable = False,
use_inactivity_timer = False
)
self._progress_message.setProgress(progress)
self._progress_message.show()
## Hides the upload progress bar
def _resetUploadProgress(self) -> None:
if self._progress_message:
self._progress_message.hide()
self._progress_message = None
## Displays the given message if uploading the mesh has failed
# \param message: The message to display.
def _onUploadError(self, message: str = None) -> None:
self._resetUploadProgress()
if message:
message = Message(
text = message,
title = T.ERROR,
lifetime = 10,
dismissable = True
)
message.show()
self._sending_job = False # the upload has finished so we're not sending a job anymore
self.writeError.emit()
## Shows a message when the upload has succeeded
# \param response: The response from the cloud API.
def _onUploadSuccess(self, response: CloudPrintResponse) -> None:
Logger.log("i", "The cluster will be printing this print job with the ID %s", response.cluster_job_id)
self._resetUploadProgress()
message = Message(
text = T.UPLOAD_SUCCESS_TEXT,
title = T.UPLOAD_SUCCESS_TITLE,
lifetime = 5,
dismissable = True,
)
message.show()
self._sending_job = False # the upload has finished so we're not sending a job anymore
self.writeFinished.emit()
## Gets the remote printers.
@pyqtProperty("QVariantList", notify = _clusterPrintersChanged) @pyqtProperty("QVariantList", notify = _clusterPrintersChanged)
def printers(self) -> List[PrinterOutputModel]: def printers(self) -> List[PrinterOutputModel]:
return self._printers return self._printers
@ -209,170 +394,11 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
@pyqtSlot(int, result = str) @pyqtSlot(int, result = str)
def getTimeCompleted(self, time_remaining: int) -> str: def getTimeCompleted(self, time_remaining: int) -> str:
# TODO: this really shouldn't be in this class # TODO: this really shouldn't be in this class
current_time = time() return formatTimeCompleted(time_remaining)
datetime_completed = datetime.fromtimestamp(current_time + time_remaining)
return "{hour:02d}:{minute:02d}".format(hour = datetime_completed.hour, minute = datetime_completed.minute)
@pyqtSlot(int, result = str) @pyqtSlot(int, result = str)
def getDateCompleted(self, time_remaining: int) -> str: def getDateCompleted(self, time_remaining: int) -> str:
# TODO: this really shouldn't be in this class return formatDateCompleted(time_remaining)
current_time = time()
completed = datetime.fromtimestamp(current_time + time_remaining)
today = datetime.fromtimestamp(current_time)
# If finishing date is more than 7 days out, using "Mon Dec 3 at HH:MM" format
if completed.toordinal() > today.toordinal() + 7:
return completed.strftime("%a %b ") + "{day}".format(day = completed.day)
# If finishing date is within the next week, use "Monday at HH:MM" format
elif completed.toordinal() > today.toordinal() + 1:
return completed.strftime("%a")
# If finishing tomorrow, use "tomorrow at HH:MM" format
elif completed.toordinal() > today.toordinal():
return "tomorrow"
# If finishing today, use "today at HH:MM" format
else:
return "today"
## Called when the connection to the cluster changes.
def connect(self) -> None:
super().connect()
## Called when the network data should be updated.
def _update(self) -> None:
super()._update()
if self._last_response_time and time() - self._last_response_time < self.CHECK_CLUSTER_INTERVAL:
return # avoid calling the cloud too often
if self._account.isLoggedIn:
self.setAuthenticationState(AuthState.Authenticated)
self._api.getClusterStatus(self._device_id, self._onStatusCallFinished)
else:
self.setAuthenticationState(AuthState.NotAuthenticated)
## Method called when HTTP request to status endpoint is finished.
# Contains both printers and print jobs statuses in a single response.
def _onStatusCallFinished(self, status: CloudClusterStatus) -> None:
# Update all data from the cluster.
self._updatePrinters(status.printers)
self._updatePrintJobs(status.print_jobs)
def _updatePrinters(self, printers: List[CloudClusterPrinter]) -> None:
previous = {p.key: p for p in self._printers} # type: Dict[str, PrinterOutputModel]
received = {p.uuid: p for p in printers} # type: Dict[str, CloudClusterPrinter]
removed_printers, added_printers, updated_printers = findChanges(previous, received)
for removed_printer in removed_printers:
if self._active_printer == removed_printer:
self.setActivePrinter(None)
self._printers.remove(removed_printer)
for added_printer in added_printers:
self._printers.append(added_printer.createOutputModel(CloudOutputController(self)))
for model, printer in updated_printers:
printer.updateOutputModel(model)
# Always have an active printer
if not self._active_printer:
self.setActivePrinter(self._printers[0])
if removed_printers or added_printers or updated_printers:
self._clusterPrintersChanged.emit()
def _updatePrintJobs(self, jobs: List[CloudClusterPrintJob]) -> None:
received = {j.uuid: j for j in jobs} # type: Dict[str, CloudClusterPrintJob]
previous = {j.key: j for j in self._print_jobs} # type: Dict[str, UM3PrintJobOutputModel]
removed_jobs, added_jobs, updated_jobs = findChanges(previous, received)
# TODO: we see that not all data in the UI is correctly updated when the queue and active jobs change.
# TODO: we need to fix this here somehow by updating the correct output models.
# TODO: also the configuration drop down in the slice window is not populated because we are missing some data.
# TODO: to fix this we need to implement more data as shown in ClusterUM3OutputDevice._createPrintJobModel
for removed_job in removed_jobs:
self._print_jobs.remove(removed_job)
for added_job in added_jobs:
self._addPrintJob(added_job)
for model, job in updated_jobs:
job.updateOutputModel(model)
self._updatePrintJobDetails(model)
# We only have to update when jobs are added or removed
# updated jobs push their changes via their output model
if added_jobs or removed_jobs or updated_jobs:
self.printJobsChanged.emit()
def _addPrintJob(self, job: CloudClusterPrintJob) -> None:
print_job = job.createOutputModel(CloudOutputController(self))
self._updatePrintJobDetails(print_job)
self._print_jobs.append(print_job)
def _updatePrintJobDetails(self, print_job: UM3PrintJobOutputModel):
printer = None
try:
printer = next(p for p in self._printers if print_job.assignedPrinter == p.key)
except StopIteration:
Logger.log("w", "Missing printer %s for job %s in %s", print_job.assignedPrinter, print_job.key,
[p.key for p in self._printers])
if printer:
printer.updateActivePrintJob(print_job)
print_job.updateAssignedPrinter(printer)
def _onPrintJobCreated(self, mesh: bytes, job_response: CloudJobResponse) -> None:
self._api.uploadMesh(job_response, mesh, self._onPrintJobUploaded, self._updateUploadProgress,
lambda _: self._onUploadError(T.UPLOAD_ERROR))
def _onPrintJobUploaded(self, job_id: str) -> None:
self._api.requestPrint(self._device_id, job_id, self._onUploadSuccess)
def _updateUploadProgress(self, progress: int):
if not self._progress_message:
self._progress_message = Message(
text = T.SENDING_DATA_TEXT,
title = T.SENDING_DATA_TITLE,
progress = -1,
lifetime = 0,
dismissable = False,
use_inactivity_timer = False
)
self._progress_message.setProgress(progress)
self._progress_message.show()
def _resetUploadProgress(self):
if self._progress_message:
self._progress_message.hide()
self._progress_message = None
def _onUploadError(self, message: str = None):
self._resetUploadProgress()
if message:
message = Message(
text = message,
title = T.ERROR,
lifetime = 10,
dismissable = True
)
message.show()
self._sending_job = False # the upload has finished so we're not sending a job anymore
self.writeError.emit()
# Shows a message when the upload has succeeded
def _onUploadSuccess(self, response: CloudPrintResponse):
Logger.log("i", "The cluster will be printing this print job with the ID %s", response.cluster_job_id)
self._resetUploadProgress()
message = Message(
text = T.UPLOAD_SUCCESS_TEXT,
title = T.UPLOAD_SUCCESS_TITLE,
lifetime = 5,
dismissable = True,
)
message.show()
self._sending_job = False # the upload has finished so we're not sending a job anymore
self.writeFinished.emit()
## TODO: The following methods are required by the monitor page QML, but are not actually available using cloud. ## TODO: The following methods are required by the monitor page QML, but are not actually available using cloud.
# TODO: We fake the methods here to not break the monitor page. # TODO: We fake the methods here to not break the monitor page.

View file

@ -1,7 +1,8 @@
# 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.
from typing import List, Optional from typing import List
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
from plugins.UM3NetworkPrinting.src.Cloud.CloudOutputController import CloudOutputController from plugins.UM3NetworkPrinting.src.Cloud.CloudOutputController import CloudOutputController
from .CloudClusterPrinterConfiguration import CloudClusterPrinterConfiguration from .CloudClusterPrinterConfiguration import CloudClusterPrinterConfiguration
from .CloudClusterPrintJobConstraint import CloudClusterPrintJobConstraint from .CloudClusterPrintJobConstraint import CloudClusterPrintJobConstraint
@ -31,20 +32,33 @@ class CloudClusterPrintJob(BaseModel):
self.time_total = None # type: str self.time_total = None # type: str
self.uuid = None # type: str self.uuid = None # type: str
super().__init__(**kwargs) super().__init__(**kwargs)
self.printers = [CloudClusterPrinterConfiguration(**c) if isinstance(c, dict) else c self.configuration = [CloudClusterPrinterConfiguration(**c) if isinstance(c, dict) else c
for c in self.configuration] for c in self.configuration]
self.print_jobs = [CloudClusterPrintJobConstraint(**p) if isinstance(p, dict) else p self.constraints = [CloudClusterPrintJobConstraint(**p) if isinstance(p, dict) else p
for p in self.constraints] for p in self.constraints]
## Creates an UM3 print job output model based on this cloud cluster print job. ## Creates an UM3 print job output model based on this cloud cluster print job.
# \param printer: The output model of the printer # \param printer: The output model of the printer
def createOutputModel(self, controller: CloudOutputController) -> UM3PrintJobOutputModel: def createOutputModel(self, controller: CloudOutputController) -> UM3PrintJobOutputModel:
model = UM3PrintJobOutputModel(controller, self.uuid, self.name) model = UM3PrintJobOutputModel(controller, self.uuid, self.name)
self.updateOutputModel(model)
return model return model
## Creates a new configuration model
def _createConfigurationModel(self) -> ConfigurationModel:
extruders = [extruder.createConfigurationModel() for extruder in self.configuration or ()]
configuration = ConfigurationModel()
configuration.setExtruderConfigurations(extruders)
return configuration
## Updates an UM3 print job output model based on this cloud cluster print job. ## Updates an UM3 print job output model based on this cloud cluster print job.
# \param model: The model to update. # \param model: The model to update.
def updateOutputModel(self, model: UM3PrintJobOutputModel) -> None: def updateOutputModel(self, model: UM3PrintJobOutputModel) -> None:
# TODO: Add `compatible_machine_families` to the cloud, than add model.setCompatibleMachineFamilies()
# TODO: Add `impediments_to_printing` to the cloud, see ClusterUM3OutputDevice._updatePrintJob
# TODO: Use model.updateConfigurationChanges, see ClusterUM3OutputDevice#_createConfigurationChanges
model.updateConfiguration(self._createConfigurationModel())
model.updateTimeTotal(self.time_total) model.updateTimeTotal(self.time_total)
model.updateTimeElapsed(self.time_elapsed) model.updateTimeElapsed(self.time_elapsed)
model.updateOwner(self.owner) model.updateOwner(self.owner)

View file

@ -2,6 +2,7 @@
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from typing import List from typing import List
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
from .CloudClusterPrinterConfiguration import CloudClusterPrinterConfiguration from .CloudClusterPrinterConfiguration import CloudClusterPrinterConfiguration
@ -40,5 +41,9 @@ class CloudClusterPrinter(BaseModel):
model.updateType(self.machine_variant) model.updateType(self.machine_variant)
model.updateState(self.status if self.enabled else "disabled") model.updateState(self.status if self.enabled else "disabled")
for configuration, extruder in zip(self.configuration, model.extruders): for configuration, extruder_output, extruder_config in \
configuration.updateOutputModel(extruder) zip(self.configuration, model.extruders, model.printerConfiguration.extruderConfigurations):
configuration.updateOutputModel(extruder_output)
configuration.updateConfigurationModel(extruder_config)
pass

View file

@ -1,5 +1,9 @@
# 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.
from typing import List, Optional
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel
from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel
from .CloudClusterPrinterConfigurationMaterial import CloudClusterPrinterConfigurationMaterial from .CloudClusterPrinterConfigurationMaterial import CloudClusterPrinterConfigurationMaterial
from ...Models import BaseModel from ...Models import BaseModel
@ -8,7 +12,7 @@ from ...Models import BaseModel
## Class representing a cloud cluster printer configuration ## Class representing a cloud cluster printer configuration
class CloudClusterPrinterConfiguration(BaseModel): class CloudClusterPrinterConfiguration(BaseModel):
def __init__(self, **kwargs) -> None: def __init__(self, **kwargs) -> None:
self.extruder_index = None # type: str self.extruder_index = None # type: int
self.material = None # type: CloudClusterPrinterConfigurationMaterial self.material = None # type: CloudClusterPrinterConfigurationMaterial
self.nozzle_diameter = None # type: str self.nozzle_diameter = None # type: str
self.print_core_id = None # type: str self.print_core_id = None # type: str
@ -25,3 +29,16 @@ class CloudClusterPrinterConfiguration(BaseModel):
if model.activeMaterial is None or model.activeMaterial.guid != self.material.guid: if model.activeMaterial is None or model.activeMaterial.guid != self.material.guid:
material = self.material.createOutputModel() material = self.material.createOutputModel()
model.updateActiveMaterial(material) model.updateActiveMaterial(material)
## Creates a configuration model
def createConfigurationModel(self) -> ExtruderConfigurationModel:
model = ExtruderConfigurationModel(position = self.extruder_index)
self.updateConfigurationModel(model)
return model
## Creates a configuration model
def updateConfigurationModel(self, model: ExtruderConfigurationModel) -> ExtruderConfigurationModel:
model.setHotendID(self.print_core_id)
if self.material:
model.setMaterial(self.material.createOutputModel())
return model

View file

@ -1,5 +1,8 @@
from datetime import datetime, timedelta
from typing import TypeVar, Dict, Tuple, List from typing import TypeVar, Dict, Tuple, List
from UM import i18nCatalog
T = TypeVar("T") T = TypeVar("T")
U = TypeVar("U") U = TypeVar("U")
@ -24,3 +27,27 @@ def findChanges(previous: Dict[str, T], received: Dict[str, U]) -> Tuple[List[T]
updated = [(previous[updated_id], received[updated_id]) for updated_id in updated_ids] updated = [(previous[updated_id], received[updated_id]) for updated_id in updated_ids]
return removed, added, updated return removed, added, updated
def formatTimeCompleted(time_remaining: int) -> str:
completed = datetime.now() + timedelta(seconds=time_remaining)
return "{hour:02d}:{minute:02d}".format(hour = completed.hour, minute = completed.minute)
def formatDateCompleted(time_remaining: int) -> str:
remaining = timedelta(seconds=time_remaining)
completed = datetime.now() + remaining
i18n = i18nCatalog("cura")
# If finishing date is more than 7 days out, using "Mon Dec 3 at HH:MM" format
if remaining.days >= 7:
return completed.strftime("%a %b ") + "{day}".format(day = completed.day)
# If finishing date is within the next week, use "Monday at HH:MM" format
elif remaining.days >= 2:
return completed.strftime("%a")
# If finishing tomorrow, use "tomorrow at HH:MM" format
elif remaining.days >= 1:
return i18n.i18nc("@info:status", "tomorrow")
# If finishing today, use "today at HH:MM" format
else:
return i18n.i18nc("@info:status", "today")

View file

@ -25,6 +25,7 @@ from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationM
from cura.PrinterOutput.NetworkedPrinterOutputDevice import AuthState, NetworkedPrinterOutputDevice from cura.PrinterOutput.NetworkedPrinterOutputDevice import AuthState, NetworkedPrinterOutputDevice
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
from plugins.UM3NetworkPrinting.src.Cloud.Utils import formatTimeCompleted, formatDateCompleted
from .ClusterUM3PrinterOutputController import ClusterUM3PrinterOutputController from .ClusterUM3PrinterOutputController import ClusterUM3PrinterOutputController
from .ConfigurationChangeModel import ConfigurationChangeModel from .ConfigurationChangeModel import ConfigurationChangeModel
@ -337,14 +338,12 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
return self._printers return self._printers
@pyqtSlot(int, result = str) @pyqtSlot(int, result = str)
def formatDuration(self, seconds: int) -> str: def getTimeCompleted(self, time_remaining: int) -> str:
return Duration(seconds).getDisplayString(DurationFormat.Format.Short) return formatTimeCompleted(time_remaining)
@pyqtSlot(int, result = str) @pyqtSlot(int, result = str)
def getTimeCompleted(self, time_remaining: int) -> str: def getDateCompleted(self, time_remaining: int) -> str:
current_time = time() return formatDateCompleted(time_remaining)
datetime_completed = datetime.fromtimestamp(current_time + time_remaining)
return "{hour:02d}:{minute:02d}".format(hour=datetime_completed.hour, minute=datetime_completed.minute)
@pyqtSlot(int, result = str) @pyqtSlot(int, result = str)
def getDateCompleted(self, time_remaining: int) -> str: def getDateCompleted(self, time_remaining: int) -> str: