Merge branch 'feature_intent' of github.com:Ultimaker/Cura into feature_intent_container_tree

This commit is contained in:
Jaime van Kessel 2019-08-27 14:18:41 +02:00
commit d1a8ce54a1
52 changed files with 774 additions and 290 deletions

View file

@ -56,7 +56,7 @@ class CloudApiClient:
## Retrieves all the clusters for the user that is currently logged in.
# \param on_finished: The function to be called after the result is parsed.
def getClusters(self, on_finished: Callable[[List[CloudClusterResponse]], Any]) -> None:
url = "{}/clusters".format(self.CLUSTER_API_ROOT)
url = "{}/clusters?status=active".format(self.CLUSTER_API_ROOT)
reply = self._manager.get(self._createEmptyRequest(url))
self._addCallback(reply, on_finished, CloudClusterResponse)

View file

@ -42,20 +42,18 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
# The interval with which the remote cluster is checked.
# We can do this relatively often as this API call is quite fast.
CHECK_CLUSTER_INTERVAL = 8.0 # seconds
CHECK_CLUSTER_INTERVAL = 10.0 # seconds
# Override the network response timeout in seconds after which we consider the device offline.
# For cloud this needs to be higher because the interval at which we check the status is higher as well.
NETWORK_RESPONSE_CONSIDER_OFFLINE = 15.0 # seconds
# The minimum version of firmware that support print job actions over cloud.
PRINT_JOB_ACTIONS_MIN_VERSION = Version("5.3.0")
# Signal triggered when the print jobs in the queue were changed.
printJobsChanged = pyqtSignal()
# Signal triggered when the selected printer in the UI should be changed.
activePrinterChanged = pyqtSignal()
# Notify can only use signals that are defined by the class that they are in, not inherited ones.
# Therefore we create a private signal used to trigger the printersChanged signal.
_clusterPrintersChanged = pyqtSignal()
_cloudClusterPrintersChanged = pyqtSignal()
## Creates a new cloud output device
# \param api_client: The client that will run the API calls
@ -89,7 +87,7 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
self._setInterfaceElements()
# Trigger the printersChanged signal when the private signal is triggered.
self.printersChanged.connect(self._clusterPrintersChanged)
self.printersChanged.connect(self._cloudClusterPrintersChanged)
# Keep server string of the last generated time to avoid updating models more than once for the same response
self._received_printers = None # type: Optional[List[ClusterPrinterStatus]]
@ -144,8 +142,9 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
## 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
if time() - self._time_of_last_request < self.CHECK_CLUSTER_INTERVAL:
return # avoid calling the cloud too often
self._time_of_last_request = time()
if self._account.isLoggedIn:
self.setAuthenticationState(AuthState.Authenticated)
self._last_request_time = time()
@ -156,9 +155,8 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
## 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._responseReceived()
if status.printers != self._received_printers:
self._received_printers = status.printers
self._updatePrinters(status.printers)
if status.print_jobs != self._received_print_jobs:
@ -232,7 +230,7 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
self.writeError.emit()
## Whether the printer that this output device represents supports print job actions via the cloud.
@pyqtProperty(bool, notify=_clusterPrintersChanged)
@pyqtProperty(bool, notify=_cloudClusterPrintersChanged)
def supportsPrintJobActions(self) -> bool:
if not self._printers:
return False
@ -274,7 +272,7 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
@clusterData.setter
def clusterData(self, value: CloudClusterResponse) -> None:
self._cluster = value
## Gets the URL on which to monitor the cluster via the cloud.
@property
def clusterCloudUrl(self) -> str:

View file

@ -161,15 +161,17 @@ class CloudOutputDeviceManager:
self._connectToOutputDevice(device, active_machine)
elif local_network_key and device.matchesNetworkKey(local_network_key):
# Connect to it if we can match the local network key that was already present.
active_machine.setMetaDataEntry(self.META_CLUSTER_ID, device.key)
self._connectToOutputDevice(device, active_machine)
elif device.key in output_device_manager.getOutputDeviceIds():
# Remove device if it is not meant for the active machine.
output_device_manager.removeOutputDevice(device.key)
## Connects to an output device and makes sure it is registered in the output device manager.
@staticmethod
def _connectToOutputDevice(device: CloudOutputDevice, active_machine: GlobalStack) -> None:
def _connectToOutputDevice(self, device: CloudOutputDevice, machine: GlobalStack) -> None:
machine.setName(device.name)
machine.setMetaDataEntry(self.META_CLUSTER_ID, device.key)
machine.setMetaDataEntry("group_name", device.name)
device.connect()
active_machine.addConfiguredConnectionType(device.connectionType.value)
machine.addConfiguredConnectionType(device.connectionType.value)
CuraApplication.getInstance().getOutputDeviceManager().addOutputDevice(device)

View file

@ -9,7 +9,8 @@ from .ClusterPrinterConfigurationMaterial import ClusterPrinterConfigurationMate
from ..BaseModel import BaseModel
## Class representing a cloud cluster printer configuration
## Class representing a cloud cluster printer configuration
# Also used for representing slots in a Material Station (as from Cura's perspective these are the same).
class ClusterPrintCoreConfiguration(BaseModel):
## Creates a new cloud cluster printer configuration object
@ -18,7 +19,7 @@ class ClusterPrintCoreConfiguration(BaseModel):
# \param nozzle_diameter: The diameter of the print core at this position in millimeters, e.g. '0.4'.
# \param print_core_id: The type of print core inserted at this position, e.g. 'AA 0.4'.
def __init__(self, extruder_index: int,
material: Union[None, Dict[str, Any], ClusterPrinterConfigurationMaterial],
material: Union[None, Dict[str, Any], ClusterPrinterConfigurationMaterial] = None,
print_core_id: Optional[str] = None, **kwargs) -> None:
self.extruder_index = extruder_index
self.material = self.parseModel(ClusterPrinterConfigurationMaterial, material) if material else None

View file

@ -101,6 +101,7 @@ class ClusterPrintJobStatus(BaseModel):
extruders = [extruder.createConfigurationModel() for extruder in self.configuration or ()]
configuration = PrinterConfigurationModel()
configuration.setExtruderConfigurations(extruders)
configuration.setPrinterType(self.machine_variant)
return configuration
## Updates an UM3 print job output model based on this cloud cluster print job.

View file

@ -0,0 +1,23 @@
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Union, Dict, Any, List
from ..BaseModel import BaseModel
from .ClusterPrinterMaterialStationSlot import ClusterPrinterMaterialStationSlot
## Class representing the data of a Material Station in the cluster.
class ClusterPrinterMaterialStation(BaseModel):
## Creates a new Material Station status.
# \param status: The status of the material station.
# \param: supported: Whether the material station is supported on this machine or not.
# \param material_slots: The active slots configurations of this material station.
def __init__(self, status: str, supported: bool = False,
material_slots: Union[None, Dict[str, Any], ClusterPrinterMaterialStationSlot] = None,
**kwargs) -> None:
self.status = status
self.supported = supported
self.material_slots = self.parseModels(ClusterPrinterMaterialStationSlot, material_slots)\
if material_slots else [] # type: List[ClusterPrinterMaterialStationSlot]
super().__init__(**kwargs)

View file

@ -0,0 +1,17 @@
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from .ClusterPrintCoreConfiguration import ClusterPrintCoreConfiguration
## Class representing the data of a single slot in the material station.
class ClusterPrinterMaterialStationSlot(ClusterPrintCoreConfiguration):
## Create a new material station slot object.
# \param slot_index: The index of the slot in the material station (ranging 0 to 5).
# \param compatible: Whether the configuration is compatible with the print core.
# \param material_remaining: How much material is remaining on the spool (between 0 and 1, or -1 for missing data).
def __init__(self, slot_index: int, compatible: bool, material_remaining: float, **kwargs):
self.slot_index = slot_index
self.compatible = compatible
self.material_remaining = material_remaining
super().__init__(**kwargs)

View file

@ -1,14 +1,18 @@
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from itertools import product
from typing import List, Union, Dict, Optional, Any
from PyQt5.QtCore import QUrl
from cura.PrinterOutput.Models.PrinterConfigurationModel import PrinterConfigurationModel
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
from cura.PrinterOutput.Models.PrinterOutputModel import PrinterOutputModel
from .ClusterBuildPlate import ClusterBuildPlate
from .ClusterPrintCoreConfiguration import ClusterPrintCoreConfiguration
from .ClusterPrinterMaterialStation import ClusterPrinterMaterialStation
from .ClusterPrinterMaterialStationSlot import ClusterPrinterMaterialStationSlot
from ..BaseModel import BaseModel
@ -26,17 +30,19 @@ class ClusterPrinterStatus(BaseModel):
# \param uuid: The unique ID of the printer, also known as GUID.
# \param configuration: The active print core configurations of this printer.
# \param reserved_by: A printer can be claimed by a specific print job.
# \param maintenance_required: Indicates if maintenance is necessary
# \param maintenance_required: Indicates if maintenance is necessary.
# \param firmware_update_status: Whether the printer's firmware is up-to-date, value is one of: "up_to_date",
# "pending_update", "update_available", "update_in_progress", "update_failed", "update_impossible"
# \param latest_available_firmware: The version of the latest firmware that is available
# \param build_plate: The build plate that is on the printer
# "pending_update", "update_available", "update_in_progress", "update_failed", "update_impossible".
# \param latest_available_firmware: The version of the latest firmware that is available.
# \param build_plate: The build plate that is on the printer.
# \param material_station: The material station that is on the printer.
def __init__(self, enabled: bool, firmware_version: str, friendly_name: str, ip_address: str, machine_variant: str,
status: str, unique_name: str, uuid: str,
configuration: List[Union[Dict[str, Any], ClusterPrintCoreConfiguration]],
reserved_by: Optional[str] = None, maintenance_required: Optional[bool] = None,
firmware_update_status: Optional[str] = None, latest_available_firmware: Optional[str] = None,
build_plate: Union[Dict[str, Any], ClusterBuildPlate] = None, **kwargs) -> None:
build_plate: Union[Dict[str, Any], ClusterBuildPlate] = None,
material_station: Union[Dict[str, Any], ClusterPrinterMaterialStation] = None, **kwargs) -> None:
self.configuration = self.parseModels(ClusterPrintCoreConfiguration, configuration)
self.enabled = enabled
@ -52,6 +58,8 @@ class ClusterPrinterStatus(BaseModel):
self.firmware_update_status = firmware_update_status
self.latest_available_firmware = latest_available_firmware
self.build_plate = self.parseModel(ClusterBuildPlate, build_plate) if build_plate else None
self.material_station = self.parseModel(ClusterPrinterMaterialStation,
material_station) if material_station else None
super().__init__(**kwargs)
## Creates a new output model.
@ -71,8 +79,53 @@ class ClusterPrinterStatus(BaseModel):
model.updateBuildplate(self.build_plate.type if self.build_plate else "glass")
model.setCameraUrl(QUrl("http://{}:8080/?action=stream".format(self.ip_address)))
if model.printerConfiguration is not None:
for configuration, extruder_output, extruder_config in \
zip(self.configuration, model.extruders, model.printerConfiguration.extruderConfigurations):
configuration.updateOutputModel(extruder_output)
configuration.updateConfigurationModel(extruder_config)
# Set the possible configurations based on whether a Material Station is present or not.
if self.material_station is not None and len(self.material_station.material_slots):
self._updateAvailableConfigurations(model)
if self.configuration is not None:
self._updateActiveConfiguration(model)
def _updateActiveConfiguration(self, model: PrinterOutputModel) -> None:
configurations = zip(self.configuration, model.extruders, model.printerConfiguration.extruderConfigurations)
for configuration, extruder_output, extruder_config in configurations:
configuration.updateOutputModel(extruder_output)
configuration.updateConfigurationModel(extruder_config)
def _updateAvailableConfigurations(self, model: PrinterOutputModel) -> None:
# Generate a list of configurations for the left extruder.
left_configurations = [slot for slot in self.material_station.material_slots if self._isSupportedConfiguration(
slot = slot,
extruder_index = 0
)]
# Generate a list of configurations for the right extruder.
right_configurations = [slot for slot in self.material_station.material_slots if self._isSupportedConfiguration(
slot = slot,
extruder_index = 1
)]
# Create a list of all available combinations between both print cores.
available_configurations = [self._createAvailableConfigurationFromPrinterConfiguration(
left_slot = left_slot,
right_slot = right_slot,
printer_configuration = model.printerConfiguration
) for left_slot, right_slot in product(left_configurations, right_configurations)]
# Let Cura know which available configurations there are.
model.setAvailableConfigurations(available_configurations)
## Check if a configuration is supported in order to make it selectable by the user.
# We filter out any slot that is not supported by the extruder index, print core type or if the material is empty.
@staticmethod
def _isSupportedConfiguration(slot: ClusterPrinterMaterialStationSlot, extruder_index: int) -> bool:
return slot.extruder_index == extruder_index and slot.compatible and slot.material and \
slot.material_remaining != 0
@staticmethod
def _createAvailableConfigurationFromPrinterConfiguration(left_slot: ClusterPrinterMaterialStationSlot,
right_slot: ClusterPrinterMaterialStationSlot,
printer_configuration: PrinterConfigurationModel
) -> PrinterConfigurationModel:
available_configuration = PrinterConfigurationModel()
available_configuration.setExtruderConfigurations([left_slot.createConfigurationModel(),
right_slot.createConfigurationModel()])
available_configuration.setPrinterType(printer_configuration.printerType)
available_configuration.setBuildplateConfiguration(printer_configuration.buildplateConfiguration)
return available_configuration

View file

@ -62,6 +62,11 @@ class ClusterApiClient:
def movePrintJobToTop(self, print_job_uuid: str) -> None:
url = "{}/print_jobs/{}/action/move".format(self.CLUSTER_API_PREFIX, print_job_uuid)
self._manager.post(self._createEmptyRequest(url), json.dumps({"to_position": 0, "list": "queued"}).encode())
## Override print job configuration and force it to be printed.
def forcePrintJob(self, print_job_uuid: str) -> None:
url = "{}/print_jobs/{}".format(self.CLUSTER_API_PREFIX, print_job_uuid)
self._manager.put(self._createEmptyRequest(url), json.dumps({"force": True}).encode())
## Delete a print job from the queue.
def deletePrintJob(self, print_job_uuid: str) -> None:

View file

@ -38,16 +38,13 @@ class LocalClusterOutputDevice(UltimakerNetworkedPrinterOutputDevice):
parent=parent
)
# API client for making requests to the print cluster.
self._cluster_api = ClusterApiClient(address, on_error=lambda error: print(error))
self._cluster_api = None # type: Optional[ClusterApiClient]
# We don't have authentication over local networking, so we're always authenticated.
self.setAuthenticationState(AuthState.Authenticated)
self._setInterfaceElements()
self._active_camera_url = QUrl() # type: QUrl
# Get the printers of this cluster to check if this device is a group host or not.
self._cluster_api.getPrinters(self._updatePrinters)
## Set all the interface elements and texts for this output device.
def _setInterfaceElements(self) -> None:
self.setPriority(3) # Make sure the output device gets selected above local file output
@ -81,26 +78,26 @@ class LocalClusterOutputDevice(UltimakerNetworkedPrinterOutputDevice):
@pyqtSlot(str, name="sendJobToTop")
def sendJobToTop(self, print_job_uuid: str) -> None:
self._cluster_api.movePrintJobToTop(print_job_uuid)
self._getApiClient().movePrintJobToTop(print_job_uuid)
@pyqtSlot(str, name="deleteJobFromQueue")
def deleteJobFromQueue(self, print_job_uuid: str) -> None:
self._cluster_api.deletePrintJob(print_job_uuid)
self._getApiClient().deletePrintJob(print_job_uuid)
@pyqtSlot(str, name="forceSendJob")
def forceSendJob(self, print_job_uuid: str) -> None:
pass # TODO
self._getApiClient().forcePrintJob(print_job_uuid)
## Set the remote print job state.
# \param print_job_uuid: The UUID of the print job to set the state for.
# \param action: The action to undertake ('pause', 'resume', 'abort').
def setJobState(self, print_job_uuid: str, action: str) -> None:
self._cluster_api.setPrintJobState(print_job_uuid, action)
self._getApiClient().setPrintJobState(print_job_uuid, action)
def _update(self) -> None:
super()._update()
self._cluster_api.getPrinters(self._updatePrinters)
self._cluster_api.getPrintJobs(self._updatePrintJobs)
self._getApiClient().getPrinters(self._updatePrinters)
self._getApiClient().getPrintJobs(self._updatePrintJobs)
self._updatePrintJobPreviewImages()
## Sync the material profiles in Cura with the printer.
@ -162,4 +159,10 @@ class LocalClusterOutputDevice(UltimakerNetworkedPrinterOutputDevice):
def _updatePrintJobPreviewImages(self):
for print_job in self._print_jobs:
if print_job.getPreviewImage() is None:
self._cluster_api.getPrintJobPreviewImage(print_job.key, print_job.updatePreviewImageData)
self._getApiClient().getPrintJobPreviewImage(print_job.key, print_job.updatePreviewImageData)
## Get the API client instance.
def _getApiClient(self) -> ClusterApiClient:
if not self._cluster_api:
self._cluster_api = ClusterApiClient(self.address, on_error=lambda error: print(error))
return self._cluster_api

View file

@ -236,7 +236,7 @@ class LocalClusterOutputDeviceManager:
machine.setName(device.name)
machine.setMetaDataEntry(self.META_NETWORK_KEY, device.key)
machine.setMetaDataEntry("group_name", device.name)
device.connect()
machine.addConfiguredConnectionType(device.connectionType.value)
CuraApplication.getInstance().getOutputDeviceManager().addOutputDevice(device)

View file

@ -18,7 +18,7 @@ I18N_CATALOG = i18nCatalog("cura")
## Machine action that allows to connect the active machine to a networked devices.
# TODO: in the future this should be part of the new discovery workflow baked into Cura.
class UltimakerNetworkedPrinterAction(MachineAction):
# Signal emitted when discovered devices have changed.
discoveredDevicesChanged = pyqtSignal()
@ -34,58 +34,54 @@ class UltimakerNetworkedPrinterAction(MachineAction):
## Start listening to network discovery events via the plugin.
@pyqtSlot(name = "startDiscovery")
def startDiscovery(self) -> None:
network_plugin = self._getNetworkPlugin()
network_plugin.discoveredDevicesChanged.connect(self._onDeviceDiscoveryChanged)
self._networkPlugin.discoveredDevicesChanged.connect(self._onDeviceDiscoveryChanged)
self.discoveredDevicesChanged.emit() # trigger at least once to populate the list
## Reset the discovered devices.
@pyqtSlot(name = "reset")
def reset(self) -> None:
self.restartDiscovery()
self.discoveredDevicesChanged.emit() # trigger to reset the list
## Reset the discovered devices.
@pyqtSlot(name = "restartDiscovery")
def restartDiscovery(self) -> None:
network_plugin = self._getNetworkPlugin()
network_plugin.startDiscovery()
self._networkPlugin.startDiscovery()
self.discoveredDevicesChanged.emit() # trigger to reset the list
## Remove a manually added device.
@pyqtSlot(str, str, name = "removeManualDevice")
def removeManualDevice(self, key: str, address: str) -> None:
network_plugin = self._getNetworkPlugin()
network_plugin.removeManualDevice(key, address)
self._networkPlugin.removeManualDevice(key, address)
## Add a new manual device. Can replace an existing one by key.
@pyqtSlot(str, str, name = "setManualDevice")
def setManualDevice(self, key: str, address: str) -> None:
network_plugin = self._getNetworkPlugin()
if key != "":
network_plugin.removeManualDevice(key)
self._networkPlugin.removeManualDevice(key)
if address != "":
network_plugin.addManualDevice(address)
self._networkPlugin.addManualDevice(address)
## Get the devices discovered in the local network sorted by name.
@pyqtProperty("QVariantList", notify = discoveredDevicesChanged)
def foundDevices(self):
network_plugin = self._getNetworkPlugin()
discovered_devices = list(network_plugin.getDiscoveredDevices().values())
discovered_devices = list(self._networkPlugin.getDiscoveredDevices().values())
discovered_devices.sort(key = lambda d: d.name)
return discovered_devices
## Connect a device selected in the list with the active machine.
@pyqtSlot(QObject, name = "associateActiveMachineWithPrinterDevice")
def associateActiveMachineWithPrinterDevice(self, device: LocalClusterOutputDevice) -> None:
network_plugin = self._getNetworkPlugin()
network_plugin.associateActiveMachineWithPrinterDevice(device)
self._networkPlugin.associateActiveMachineWithPrinterDevice(device)
## Callback for when the list of discovered devices in the plugin was changed.
def _onDeviceDiscoveryChanged(self) -> None:
self.discoveredDevicesChanged.emit()
## Get the network manager from the plugin.
def _getNetworkPlugin(self) -> UM3OutputDevicePlugin:
@property
def _networkPlugin(self) -> UM3OutputDevicePlugin:
if not self._network_plugin:
plugin = CuraApplication.getInstance().getOutputDeviceManager().getOutputDevicePlugin("UM3NetworkPrinting")
self._network_plugin = cast(UM3OutputDevicePlugin, plugin)
output_device_manager = CuraApplication.getInstance().getOutputDeviceManager()
network_plugin = output_device_manager.getOutputDevicePlugin("UM3NetworkPrinting")
self._network_plugin = cast(UM3OutputDevicePlugin, network_plugin)
return self._network_plugin

View file

@ -26,7 +26,7 @@ from .Models.Http.ClusterPrintJobStatus import ClusterPrintJobStatus
# Currently used for local networking and cloud printing using Ultimaker Connect.
# This base class primarily contains all the Qt properties and slots needed for the monitor page to work.
class UltimakerNetworkedPrinterOutputDevice(NetworkedPrinterOutputDevice):
META_NETWORK_KEY = "um_network_key"
META_CLUSTER_ID = "um_cloud_cluster_id"
@ -42,21 +42,23 @@ class UltimakerNetworkedPrinterOutputDevice(NetworkedPrinterOutputDevice):
# States indicating if a print job is queued.
QUEUED_PRINT_JOBS_STATES = {"queued", "error"}
# Time in seconds since last network response after which we consider this device offline.
# We set this a bit higher than some of the other intervals to make sure they don't overlap.
NETWORK_RESPONSE_CONSIDER_OFFLINE = 12.0
NETWORK_RESPONSE_CONSIDER_OFFLINE = 10.0 # seconds
def __init__(self, device_id: str, address: str, properties: Dict[bytes, bytes], connection_type: ConnectionType,
parent=None) -> None:
super().__init__(device_id=device_id, address=address, properties=properties, connection_type=connection_type,
parent=parent)
# Trigger the printersChanged signal when the private signal is triggered.
self.printersChanged.connect(self._clusterPrintersChanged)
# Keeps track the last network response to determine if we are still connected.
self._time_of_last_response = time()
self._time_of_last_request = time()
# Set the display name from the properties
self.setName(self.getProperty("name"))
@ -101,15 +103,18 @@ class UltimakerNetworkedPrinterOutputDevice(NetworkedPrinterOutputDevice):
return [print_job for print_job in self._print_jobs if
print_job.assignedPrinter is not None and print_job.state not in self.QUEUED_PRINT_JOBS_STATES]
@pyqtProperty(bool, notify=printJobsChanged)
def receivedPrintJobs(self) -> bool:
return bool(self._print_jobs)
@pyqtProperty(bool, notify=_clusterPrintersChanged)
def receivedData(self) -> bool:
return self._has_received_printers
# Get the amount of printers in the cluster.
@pyqtProperty(int, notify=_clusterPrintersChanged)
def clusterSize(self) -> int:
if not self._has_received_printers:
return 1 # prevent false positives when discovering new devices
discovered_size = self.getProperty("cluster_size")
if discovered_size == "":
return 1 # prevent false positives for new devices
return int(discovered_size)
return len(self._printers)
# Get the amount of printer in the cluster per type.
@ -294,6 +299,8 @@ class UltimakerNetworkedPrinterOutputDevice(NetworkedPrinterOutputDevice):
print_job_data.updateOutputModel(print_job)
if print_job_data.printer_uuid:
self._updateAssignedPrinter(print_job, print_job_data.printer_uuid)
if print_job_data.assigned_to:
self._updateAssignedPrinter(print_job, print_job_data.assigned_to)
new_print_jobs.append(print_job)
# Check which print job need to be removed (de-referenced).
@ -312,6 +319,8 @@ class UltimakerNetworkedPrinterOutputDevice(NetworkedPrinterOutputDevice):
model = remote_job.createOutputModel(ClusterOutputController(self))
if remote_job.printer_uuid:
self._updateAssignedPrinter(model, remote_job.printer_uuid)
if remote_job.assigned_to:
self._updateAssignedPrinter(model, remote_job.assigned_to)
return model
## Updates the printer assignment for the given print job model.