1# Copyright (c) 2019 Ultimaker B.V. 2# Cura is released under the terms of the LGPLv3 or higher. 3from itertools import product 4from typing import List, Union, Dict, Optional, Any 5 6from PyQt5.QtCore import QUrl 7 8from cura.PrinterOutput.Models.PrinterConfigurationModel import PrinterConfigurationModel 9from cura.PrinterOutput.PrinterOutputController import PrinterOutputController 10from cura.PrinterOutput.Models.PrinterOutputModel import PrinterOutputModel 11 12from .ClusterBuildPlate import ClusterBuildPlate 13from .ClusterPrintCoreConfiguration import ClusterPrintCoreConfiguration 14from .ClusterPrinterMaterialStation import ClusterPrinterMaterialStation 15from .ClusterPrinterMaterialStationSlot import ClusterPrinterMaterialStationSlot 16from .ClusterPrinterConfigurationMaterial import ClusterPrinterConfigurationMaterial 17from ..BaseModel import BaseModel 18 19 20class ClusterPrinterStatus(BaseModel): 21 """Class representing a cluster printer""" 22 23 24 def __init__(self, enabled: bool, firmware_version: str, friendly_name: str, ip_address: str, machine_variant: str, 25 status: str, unique_name: str, uuid: str, 26 configuration: List[Union[Dict[str, Any], ClusterPrintCoreConfiguration]], 27 reserved_by: Optional[str] = None, maintenance_required: Optional[bool] = None, 28 firmware_update_status: Optional[str] = None, latest_available_firmware: Optional[str] = None, 29 build_plate: Union[Dict[str, Any], ClusterBuildPlate] = None, 30 material_station: Union[Dict[str, Any], ClusterPrinterMaterialStation] = None, **kwargs) -> None: 31 """Creates a new cluster printer status 32 33 :param enabled: A printer can be disabled if it should not receive new jobs. By default every printer is enabled. 34 :param firmware_version: Firmware version installed on the printer. Can differ for each printer in a cluster. 35 :param friendly_name: Human readable name of the printer. Can be used for identification purposes. 36 :param ip_address: The IP address of the printer in the local network. 37 :param machine_variant: The type of printer. Can be 'Ultimaker 3' or 'Ultimaker 3ext'. 38 :param status: The status of the printer. 39 :param unique_name: The unique name of the printer in the network. 40 :param uuid: The unique ID of the printer, also known as GUID. 41 :param configuration: The active print core configurations of this printer. 42 :param reserved_by: A printer can be claimed by a specific print job. 43 :param maintenance_required: Indicates if maintenance is necessary. 44 :param firmware_update_status: Whether the printer's firmware is up-to-date, value is one of: "up_to_date", 45 "pending_update", "update_available", "update_in_progress", "update_failed", "update_impossible". 46 :param latest_available_firmware: The version of the latest firmware that is available. 47 :param build_plate: The build plate that is on the printer. 48 :param material_station: The material station that is on the printer. 49 """ 50 51 self.configuration = self.parseModels(ClusterPrintCoreConfiguration, configuration) 52 self.enabled = enabled 53 self.firmware_version = firmware_version 54 self.friendly_name = friendly_name 55 self.ip_address = ip_address 56 self.machine_variant = machine_variant 57 self.status = status 58 self.unique_name = unique_name 59 self.uuid = uuid 60 self.reserved_by = reserved_by 61 self.maintenance_required = maintenance_required 62 self.firmware_update_status = firmware_update_status 63 self.latest_available_firmware = latest_available_firmware 64 self.build_plate = self.parseModel(ClusterBuildPlate, build_plate) if build_plate else None 65 self.material_station = self.parseModel(ClusterPrinterMaterialStation, 66 material_station) if material_station else None 67 super().__init__(**kwargs) 68 69 def createOutputModel(self, controller: PrinterOutputController) -> PrinterOutputModel: 70 """Creates a new output model. 71 72 :param controller: - The controller of the model. 73 """ 74 model = PrinterOutputModel(controller, len(self.configuration), firmware_version = self.firmware_version) 75 self.updateOutputModel(model) 76 return model 77 78 def updateOutputModel(self, model: PrinterOutputModel) -> None: 79 """Updates the given output model. 80 81 :param model: - The output model to update. 82 """ 83 84 model.updateKey(self.uuid) 85 model.updateName(self.friendly_name) 86 model.updateUniqueName(self.unique_name) 87 model.updateType(self.machine_variant) 88 model.updateState(self.status if self.enabled else "disabled") 89 model.updateBuildplate(self.build_plate.type if self.build_plate else "glass") 90 model.setCameraUrl(QUrl("http://{}:8080/?action=stream".format(self.ip_address))) 91 92 if not model.printerConfiguration: 93 # Prevent accessing printer configuration when not available. 94 # This sometimes happens when a printer was just added to a group and Cura is connected to that group. 95 return 96 97 # Set the possible configurations based on whether a Material Station is present or not. 98 if self.material_station and self.material_station.material_slots: 99 self._updateAvailableConfigurations(model) 100 if self.configuration: 101 self._updateActiveConfiguration(model) 102 103 def _updateActiveConfiguration(self, model: PrinterOutputModel) -> None: 104 configurations = zip(self.configuration, model.extruders, model.printerConfiguration.extruderConfigurations) 105 for configuration, extruder_output, extruder_config in configurations: 106 configuration.updateOutputModel(extruder_output) 107 configuration.updateConfigurationModel(extruder_config) 108 109 def _updateAvailableConfigurations(self, model: PrinterOutputModel) -> None: 110 available_configurations = [self._createAvailableConfigurationFromPrinterConfiguration( 111 left_slot = left_slot, 112 right_slot = right_slot, 113 printer_configuration = model.printerConfiguration 114 ) for left_slot, right_slot in product(self._getSlotsForExtruder(0), self._getSlotsForExtruder(1))] 115 model.setAvailableConfigurations(available_configurations) 116 117 def _getSlotsForExtruder(self, extruder_index: int) -> List[ClusterPrinterMaterialStationSlot]: 118 """Create a list of Material Station slots for the given extruder index. 119 120 Returns a list with a single empty material slot if none are found to ensure we don't miss configurations. 121 """ 122 123 if not self.material_station: # typing guard 124 return [] 125 slots = [slot for slot in self.material_station.material_slots if self._isSupportedConfiguration( 126 slot = slot, 127 extruder_index = extruder_index 128 )] 129 return slots or [self._createEmptyMaterialSlot(extruder_index)] 130 131 @staticmethod 132 def _isSupportedConfiguration(slot: ClusterPrinterMaterialStationSlot, extruder_index: int) -> bool: 133 """Check if a configuration is supported in order to make it selectable by the user. 134 135 We filter out any slot that is not supported by the extruder index, print core type or if the material is empty. 136 """ 137 138 return slot.extruder_index == extruder_index and slot.compatible and not slot.material_empty 139 140 @staticmethod 141 def _createEmptyMaterialSlot(extruder_index: int) -> ClusterPrinterMaterialStationSlot: 142 """Create an empty material slot with a fake empty material.""" 143 144 empty_material = ClusterPrinterConfigurationMaterial(guid = "", material = "empty", brand = "", color = "") 145 return ClusterPrinterMaterialStationSlot(slot_index = 0, extruder_index = extruder_index, 146 compatible = True, material_remaining = 0, material = empty_material) 147 148 @staticmethod 149 def _createAvailableConfigurationFromPrinterConfiguration(left_slot: ClusterPrinterMaterialStationSlot, 150 right_slot: ClusterPrinterMaterialStationSlot, 151 printer_configuration: PrinterConfigurationModel 152 ) -> PrinterConfigurationModel: 153 available_configuration = PrinterConfigurationModel() 154 available_configuration.setExtruderConfigurations([left_slot.createConfigurationModel(), 155 right_slot.createConfigurationModel()]) 156 available_configuration.setPrinterType(printer_configuration.printerType) 157 available_configuration.setBuildplateConfiguration(printer_configuration.buildplateConfiguration) 158 return available_configuration 159