This commit is contained in:
ChrisTerBeke 2019-07-30 13:09:29 +02:00
parent 9e4c71cce3
commit bfca117bff
6 changed files with 119 additions and 110 deletions

View file

@ -9,6 +9,7 @@ from PyQt5.QtGui import QDesktopServices
from UM import i18nCatalog
from UM.Backend.Backend import BackendState
from UM.FileHandler.FileHandler import FileHandler
from UM.FileHandler.WriteFileJob import WriteFileJob
from UM.Logger import Logger
from UM.Message import Message
from UM.Scene.SceneNode import SceneNode
@ -16,6 +17,7 @@ from UM.Version import Version
from cura.CuraApplication import CuraApplication
from cura.PrinterOutput.NetworkedPrinterOutputDevice import AuthState
from cura.PrinterOutput.PrinterOutputDevice import ConnectionType
from plugins.UM3NetworkPrinting.src.ExportFileJob import ExportFileJob
from .CloudApiClient import CloudApiClient
from ..UltimakerNetworkedPrinterOutputDevice import UltimakerNetworkedPrinterOutputDevice
@ -93,6 +95,7 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
self._received_print_jobs = None # type: Optional[List[ClusterPrintJobStatus]]
# Reference to the uploaded print job / mesh
# We do this to prevent re-uploading the same file multiple times.
self._tool_path = None # type: Optional[bytes]
self._uploaded_print_job = None # type: Optional[CloudPrintJobResponse]
@ -129,7 +132,7 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
return True
return False
## 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) -> None:
self.setPriority(2) # Make sure we end up below the local networking and above 'save to file'.
self.setName(self._id)
@ -137,6 +140,32 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
self.setDescription(I18N_CATALOG.i18nc("@properties:tooltip", "Print via Cloud"))
self.setConnectionText(I18N_CATALOG.i18nc("@info:status", "Connected via Cloud"))
## Called when the network data should be updated.
def _update(self) -> None:
super()._update()
if self._last_request_time and time() - self._last_request_time < self.CHECK_CLUSTER_INTERVAL:
return # Avoid calling the cloud too often
Logger.log("d", "Updating: %s - %s >= %s", time(), self._last_request_time, self.CHECK_CLUSTER_INTERVAL)
if self._account.isLoggedIn:
self.setAuthenticationState(AuthState.Authenticated)
self._last_request_time = time()
self._api.getClusterStatus(self.key, 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._last_response_time = time()
if self._received_printers != status.printers:
self._received_printers = status.printers
self._updatePrinters(status.printers)
if status.print_jobs != self._received_print_jobs:
self._received_print_jobs = status.print_jobs
self._updatePrintJobs(status.print_jobs)
## Called when Cura requests an output device to receive a (G-code) file.
def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False,
file_handler: Optional[FileHandler] = None, filter_by_machine: bool = False, **kwargs) -> None:
@ -159,54 +188,29 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
# Indicate we have started sending a job.
self.writeStarted.emit(self)
mesh_format = MeshFormatHandler(file_handler, self.firmwareVersion)
if not mesh_format.is_valid:
Logger.log("e", "Missing file or mesh writer!")
return self._onUploadError(I18N_CATALOG.i18nc("@info:status", "Could not export print job."))
# Export the scene to the correct file type.
job = ExportFileJob(file_handler=file_handler, nodes=nodes, firmware_version=self.firmwareVersion)
job.finished.connect(self._onPrintJobCreated)
job.start()
# TODO: use stream just like the network output device
mesh = mesh_format.getBytes(nodes)
self._tool_path = mesh
## Handler for when the print job was created locally.
# It can now be sent over the cloud.
def _onPrintJobCreated(self, job: WriteFileJob) -> None:
self._progress.show()
self._tool_path = job.getOutput()
request = CloudPrintJobUploadRequest(
job_name=file_name or mesh_format.file_extension,
file_size=len(mesh),
content_type=mesh_format.mime_type,
job_name=job.getFileName(),
file_size=len(self._tool_path),
content_type=job.getMimeType(),
)
self._api.requestUpload(request, self._onPrintJobCreated)
## Called when the network data should be updated.
def _update(self) -> None:
super()._update()
if self._last_request_time and time() - self._last_request_time < self.CHECK_CLUSTER_INTERVAL:
return # Avoid calling the cloud too often
Logger.log("d", "Updating: %s - %s >= %s", time(), self._last_request_time, self.CHECK_CLUSTER_INTERVAL)
if self._account.isLoggedIn:
self.setAuthenticationState(AuthState.Authenticated)
self._last_request_time = time()
self._api.getClusterStatus(self.key, 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._last_response_time = time()
if self._received_printers != status.printers:
self._received_printers = status.printers
self._updatePrinters(status.printers)
if status.print_jobs != self._received_print_jobs:
self._received_print_jobs = status.print_jobs
self._updatePrintJobs(status.print_jobs)
self._api.requestUpload(request, self._uploadPrintJob)
## Uploads the mesh when the print job was registered with the cloud API.
# \param job_response: The response received from the cloud API.
def _onPrintJobCreated(self, job_response: CloudPrintJobResponse) -> None:
def _uploadPrintJob(self, job_response: CloudPrintJobResponse) -> None:
self._progress.show()
self._uploaded_print_job = job_response
tool_path = cast(bytes, self._tool_path)
self._api.uploadToolPath(job_response, tool_path, self._onPrintJobUploaded, self._progress.update,
self._uploaded_print_job = job_response # store the last uploaded job to prevent re-upload of the same file
self._api.uploadToolPath(job_response, self._tool_path, self._onPrintJobUploaded, self._progress.update,
self._onUploadError)
## Requests the print to be sent to the printer when we finished uploading the mesh.

View file

@ -40,6 +40,7 @@ class CloudOutputDeviceManager:
self._remote_clusters = {} # type: Dict[str, CloudOutputDevice]
self._account = CuraApplication.getInstance().getCuraAPI().account # type: Account
self._api = CloudApiClient(self._account, self._onApiError)
self._account.loginStateChanged.connect(self._onLoginStateChanged)
# Create a timer to update the remote cluster list
self._update_timer = QTimer()
@ -53,17 +54,22 @@ class CloudOutputDeviceManager:
def start(self):
if self._running:
return
self._account.loginStateChanged.connect(self._onLoginStateChanged)
if not self._account.isLoggedIn:
return
self._running = True
if not self._update_timer.isActive():
self._update_timer.start()
self._update_timer.timeout.connect(self._getRemoteClusters)
self._onLoginStateChanged(is_logged_in=self._account.isLoggedIn)
## Stops running the cloud output device manager.
def stop(self):
if not self._running:
return
self._account.loginStateChanged.disconnect(self._onLoginStateChanged)
self._running = False
if self._update_timer.isActive():
self._update_timer.stop()
self._onGetRemoteClustersFinished([]) # Make sure we remove all cloud output devices.
self._update_timer.timeout.disconnect(self._getRemoteClusters)
self._onLoginStateChanged(is_logged_in=False)
## Force refreshing connections.
def refreshConnections(self) -> None:
@ -72,17 +78,13 @@ class CloudOutputDeviceManager:
## Called when the uses logs in or out
def _onLoginStateChanged(self, is_logged_in: bool) -> None:
if is_logged_in:
if not self._update_timer.isActive():
self._update_timer.start()
self._getRemoteClusters()
self.start()
else:
if self._update_timer.isActive():
self._update_timer.stop()
# Notify that all clusters have disappeared
self._onGetRemoteClustersFinished([])
self.stop()
## Gets all remote clusters from the API.
def _getRemoteClusters(self) -> None:
print("getRemoteClusters")
self._api.getClusters(self._onGetRemoteClustersFinished)
## Callback for when the request for getting the clusters is finished.
@ -113,6 +115,8 @@ class CloudOutputDeviceManager:
keys = self._remote_clusters.keys()
removed_devices = [cluster for cluster in self._remote_clusters.values() if cluster.key not in keys]
for device in removed_devices:
device.disconnect()
device.close()
CuraApplication.getInstance().getOutputDeviceManager().removeOutputDevice(device.key)
discovery.removeDiscoveredPrinter(device.key)
@ -127,11 +131,13 @@ class CloudOutputDeviceManager:
return
# The newly added machine is automatically activated.
CuraApplication.getInstance().getMachineManager().addMachine(device.printerType,
device.clusterData.friendly_name)
machine_manager = CuraApplication.getInstance().getMachineManager()
machine_manager.addMachine(device.printerType, device.clusterData.friendly_name)
active_machine = CuraApplication.getInstance().getGlobalContainerStack()
if active_machine:
self._connectToOutputDevice(device, active_machine)
if not active_machine:
return
active_machine.setMetaDataEntry(self.META_CLUSTER_ID, device.key)
self._connectToOutputDevice(device, active_machine)
## Callback for when the active machine was changed by the user or a new remote cluster was found.
def _connectToActiveMachine(self) -> None:
@ -150,28 +156,25 @@ class CloudOutputDeviceManager:
device = self._remote_clusters[stored_cluster_id]
self._connectToOutputDevice(device, active_machine)
Logger.log("d", "Device connected by metadata cluster ID %s", stored_cluster_id)
else:
self._connectByNetworkKey(active_machine)
# else:
# self._connectByNetworkKey(active_machine)
## Tries to match the local network key to the cloud cluster host name.
def _connectByNetworkKey(self, active_machine: GlobalStack) -> None:
# Check if the active printer has a local network connection and match this key to the remote cluster.
local_network_key = active_machine.getMetaDataEntry("um_network_key")
if not local_network_key:
return
device = next((c for c in self._remote_clusters.values() if c.matchesNetworkKey(local_network_key)), None)
if not device:
return
Logger.log("i", "Found cluster %s with network key %s", device, local_network_key)
active_machine.setMetaDataEntry(self.META_CLUSTER_ID, device.key)
self._connectToOutputDevice(device, active_machine)
## Connects to an output device and makes sure it is registered in the output device manager.
def _connectToOutputDevice(self, device: CloudOutputDevice, active_machine: GlobalStack) -> None:
@staticmethod
def _connectToOutputDevice(device: CloudOutputDevice, active_machine: GlobalStack) -> None:
device.connect()
active_machine.setMetaDataEntry(self.META_CLUSTER_ID, device.key)
active_machine.addConfiguredConnectionType(device.connectionType.value)
CuraApplication.getInstance().getOutputDeviceManager().addOutputDevice(device)