mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-12 17:27:51 -06:00
Add minimal support for discovering cloud printers outside of LAN
This commit is contained in:
parent
b5d4ef61f5
commit
3cbd8a94a9
3 changed files with 79 additions and 20 deletions
|
@ -58,6 +58,14 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
||||||
# Therefore we create a private signal used to trigger the printersChanged signal.
|
# Therefore we create a private signal used to trigger the printersChanged signal.
|
||||||
_clusterPrintersChanged = pyqtSignal()
|
_clusterPrintersChanged = pyqtSignal()
|
||||||
|
|
||||||
|
# Map of Cura Connect machine_variant field to Cura machine types.
|
||||||
|
# Needed for printer discovery stack creation.
|
||||||
|
_host_machine_variant_to_machine_type_map = {
|
||||||
|
"Ultimaker 3": "ultimaker3",
|
||||||
|
"Ultimaker 3 Extended": "ultimaker3_extended",
|
||||||
|
"Ultimaker S5": "ultimaker_s5"
|
||||||
|
}
|
||||||
|
|
||||||
## Creates a new cloud output device
|
## Creates a new cloud output device
|
||||||
# \param api_client: The client that will run the API calls
|
# \param api_client: The client that will run the API calls
|
||||||
# \param cluster: The device response received from the cloud API.
|
# \param cluster: The device response received from the cloud API.
|
||||||
|
@ -68,10 +76,10 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
||||||
# Because the cloud connection does not off all of these, we manually construct this version here.
|
# Because the cloud connection does not off all of these, we manually construct this version here.
|
||||||
# An example of why this is needed is the selection of the compatible file type when exporting the tool path.
|
# An example of why this is needed is the selection of the compatible file type when exporting the tool path.
|
||||||
properties = {
|
properties = {
|
||||||
b"address": b"",
|
b"address": cluster.host_internal_ip.encode() if cluster.host_internal_ip else b"",
|
||||||
b"name": cluster.host_name.encode() if cluster.host_name else b"",
|
b"name": cluster.friendly_name.encode() if cluster.friendly_name else b"",
|
||||||
b"firmware_version": cluster.host_version.encode() if cluster.host_version else b"",
|
b"firmware_version": cluster.host_version.encode() if cluster.host_version else b"",
|
||||||
b"printer_type": b""
|
b"cluster_size": 1 # cloud devices are always clusters of at least one
|
||||||
}
|
}
|
||||||
|
|
||||||
super().__init__(device_id = cluster.cluster_id, address = "",
|
super().__init__(device_id = cluster.cluster_id, address = "",
|
||||||
|
@ -95,6 +103,7 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
||||||
|
|
||||||
# We keep track of which printer is visible in the monitor page.
|
# We keep track of which printer is visible in the monitor page.
|
||||||
self._active_printer = None # type: Optional[PrinterOutputModel]
|
self._active_printer = None # type: Optional[PrinterOutputModel]
|
||||||
|
self._host_machine_type = ""
|
||||||
|
|
||||||
# Properties to populate later on with received cloud data.
|
# Properties to populate later on with received cloud data.
|
||||||
self._print_jobs = [] # type: List[UM3PrintJobOutputModel]
|
self._print_jobs = [] # type: List[UM3PrintJobOutputModel]
|
||||||
|
@ -234,6 +243,9 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
||||||
def _updatePrinters(self, printers: List[CloudClusterPrinterStatus]) -> None:
|
def _updatePrinters(self, printers: List[CloudClusterPrinterStatus]) -> None:
|
||||||
previous = {p.key: p for p in self._printers} # type: Dict[str, PrinterOutputModel]
|
previous = {p.key: p for p in self._printers} # type: Dict[str, PrinterOutputModel]
|
||||||
received = {p.uuid: p for p in printers} # type: Dict[str, CloudClusterPrinterStatus]
|
received = {p.uuid: p for p in printers} # type: Dict[str, CloudClusterPrinterStatus]
|
||||||
|
|
||||||
|
# We need the machine type of the host (1st list entry) to allow discovery to work.
|
||||||
|
self._host_machine_type = printers[0].machine_variant
|
||||||
|
|
||||||
removed_printers, added_printers, updated_printers = findChanges(previous, received)
|
removed_printers, added_printers, updated_printers = findChanges(previous, received)
|
||||||
|
|
||||||
|
@ -358,6 +370,19 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
||||||
).show()
|
).show()
|
||||||
self.writeFinished.emit()
|
self.writeFinished.emit()
|
||||||
|
|
||||||
|
## Gets the printer type of the cluster host. Falls back to the printer type in the device properties.
|
||||||
|
@pyqtProperty(str, notify=_clusterPrintersChanged)
|
||||||
|
def printerType(self) -> str:
|
||||||
|
if self._printers and self._host_machine_type in self._host_machine_variant_to_machine_type_map:
|
||||||
|
return self._host_machine_variant_to_machine_type_map[self._host_machine_type]
|
||||||
|
return super().printerType
|
||||||
|
|
||||||
|
## Gets the number of printers in the cluster.
|
||||||
|
# We use a minimum of 1 because cloud devices are always a cluster and printer discovery needs it.
|
||||||
|
@pyqtProperty(int, notify = _clusterPrintersChanged)
|
||||||
|
def clusterSize(self) -> int:
|
||||||
|
return max(1, len(self._printers))
|
||||||
|
|
||||||
## Gets the remote printers.
|
## Gets the remote printers.
|
||||||
@pyqtProperty("QVariantList", notify=_clusterPrintersChanged)
|
@pyqtProperty("QVariantList", notify=_clusterPrintersChanged)
|
||||||
def printers(self) -> List[PrinterOutputModel]:
|
def printers(self) -> List[PrinterOutputModel]:
|
||||||
|
@ -375,10 +400,6 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
||||||
self._active_printer = printer
|
self._active_printer = printer
|
||||||
self.activePrinterChanged.emit()
|
self.activePrinterChanged.emit()
|
||||||
|
|
||||||
@pyqtProperty(int, notify = _clusterPrintersChanged)
|
|
||||||
def clusterSize(self) -> int:
|
|
||||||
return len(self._printers)
|
|
||||||
|
|
||||||
## Get remote print jobs.
|
## Get remote print jobs.
|
||||||
@pyqtProperty("QVariantList", notify = printJobsChanged)
|
@pyqtProperty("QVariantList", notify = printJobsChanged)
|
||||||
def printJobs(self) -> List[UM3PrintJobOutputModel]:
|
def printJobs(self) -> List[UM3PrintJobOutputModel]:
|
||||||
|
|
|
@ -7,7 +7,7 @@ from PyQt5.QtCore import QTimer
|
||||||
from UM import i18nCatalog
|
from UM import i18nCatalog
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Message import Message
|
from UM.Message import Message
|
||||||
from UM.Signal import Signal, signalemitter
|
from UM.Signal import Signal
|
||||||
from cura.API import Account
|
from cura.API import Account
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
from cura.Settings.GlobalStack import GlobalStack
|
from cura.Settings.GlobalStack import GlobalStack
|
||||||
|
@ -81,25 +81,61 @@ class CloudOutputDeviceManager:
|
||||||
Logger.log("d", "Removed: %s, added: %s, updates: %s", len(removed_devices), len(added_clusters), len(updates))
|
Logger.log("d", "Removed: %s, added: %s, updates: %s", len(removed_devices), len(added_clusters), len(updates))
|
||||||
|
|
||||||
# Remove output devices that are gone
|
# Remove output devices that are gone
|
||||||
for removed_cluster in removed_devices:
|
for device in removed_devices:
|
||||||
if removed_cluster.isConnected():
|
if device.isConnected():
|
||||||
removed_cluster.disconnect()
|
device.disconnect()
|
||||||
removed_cluster.close()
|
device.close()
|
||||||
self._output_device_manager.removeOutputDevice(removed_cluster.key)
|
self._output_device_manager.removeOutputDevice(device.key)
|
||||||
self.removedCloudCluster.emit(removed_cluster)
|
self._application.getDiscoveredPrintersModel().removeDiscoveredPrinter(device.key)
|
||||||
del self._remote_clusters[removed_cluster.key]
|
self.removedCloudCluster.emit(device)
|
||||||
|
del self._remote_clusters[device.key]
|
||||||
|
|
||||||
# Add an output device for each new remote cluster.
|
# Add an output device for each new remote cluster.
|
||||||
# We only add when is_online as we don't want the option in the drop down if the cluster is not online.
|
# We only add when is_online as we don't want the option in the drop down if the cluster is not online.
|
||||||
for added_cluster in added_clusters:
|
for cluster in added_clusters:
|
||||||
device = CloudOutputDevice(self._api, added_cluster)
|
device = CloudOutputDevice(self._api, cluster)
|
||||||
self._remote_clusters[added_cluster.cluster_id] = device
|
self._remote_clusters[cluster.cluster_id] = device
|
||||||
self.addedCloudCluster.emit(added_cluster)
|
self._application.getDiscoveredPrintersModel().addDiscoveredPrinter(
|
||||||
|
cluster.cluster_id,
|
||||||
|
device.key,
|
||||||
|
cluster.friendly_name,
|
||||||
|
self._createMachineFromDiscoveredPrinter,
|
||||||
|
device.printerType,
|
||||||
|
device
|
||||||
|
)
|
||||||
|
self.addedCloudCluster.emit(cluster)
|
||||||
|
|
||||||
|
# Update the output devices
|
||||||
for device, cluster in updates:
|
for device, cluster in updates:
|
||||||
device.clusterData = cluster
|
device.clusterData = cluster
|
||||||
|
self._application.getDiscoveredPrintersModel().updateDiscoveredPrinter(
|
||||||
|
cluster.cluster_id,
|
||||||
|
cluster.friendly_name,
|
||||||
|
device.printerType,
|
||||||
|
)
|
||||||
|
|
||||||
self._connectToActiveMachine()
|
self._connectToActiveMachine()
|
||||||
|
|
||||||
|
def _createMachineFromDiscoveredPrinter(self, key: str) -> None:
|
||||||
|
device = self._remote_clusters[key] # type: CloudOutputDevice
|
||||||
|
if not device:
|
||||||
|
Logger.log("e", "Could not find discovered device with key [%s]", key)
|
||||||
|
return
|
||||||
|
|
||||||
|
group_name = device.clusterData.friendly_name
|
||||||
|
machine_type_id = device.printerType
|
||||||
|
|
||||||
|
Logger.log("i", "Creating machine from cloud device with key = [%s], group name = [%s], printer type = [%s]",
|
||||||
|
key, group_name, machine_type_id)
|
||||||
|
|
||||||
|
# The newly added machine is automatically activated.
|
||||||
|
self._application.getMachineManager().addMachine(machine_type_id, group_name)
|
||||||
|
active_machine = CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
|
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.
|
## Callback for when the active machine was changed by the user or a new remote cluster was found.
|
||||||
def _connectToActiveMachine(self) -> None:
|
def _connectToActiveMachine(self) -> None:
|
||||||
|
|
|
@ -16,7 +16,8 @@ class CloudClusterResponse(BaseCloudModel):
|
||||||
# \param status: The status of the cluster authentication (active or inactive).
|
# \param status: The status of the cluster authentication (active or inactive).
|
||||||
# \param host_version: The firmware version of the cluster host. This is where the Stardust client is running on.
|
# \param host_version: The firmware version of the cluster host. This is where the Stardust client is running on.
|
||||||
def __init__(self, cluster_id: str, host_guid: str, host_name: str, is_online: bool, status: str,
|
def __init__(self, cluster_id: str, host_guid: str, host_name: str, is_online: bool, status: str,
|
||||||
host_internal_ip: Optional[str] = None, host_version: Optional[str] = None, **kwargs) -> None:
|
host_internal_ip: Optional[str] = None, host_version: Optional[str] = None,
|
||||||
|
friendly_name: Optional[str] = None, **kwargs) -> None:
|
||||||
self.cluster_id = cluster_id
|
self.cluster_id = cluster_id
|
||||||
self.host_guid = host_guid
|
self.host_guid = host_guid
|
||||||
self.host_name = host_name
|
self.host_name = host_name
|
||||||
|
@ -24,6 +25,7 @@ class CloudClusterResponse(BaseCloudModel):
|
||||||
self.is_online = is_online
|
self.is_online = is_online
|
||||||
self.host_version = host_version
|
self.host_version = host_version
|
||||||
self.host_internal_ip = host_internal_ip
|
self.host_internal_ip = host_internal_ip
|
||||||
|
self.friendly_name = friendly_name
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
# Validates the model, raising an exception if the model is invalid.
|
# Validates the model, raising an exception if the model is invalid.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue