1# Copyright (c) 2018 Ultimaker B.V. 2# Cura is released under the terms of the LGPLv3 or higher. 3 4from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty, QTimer 5from typing import Iterable, TYPE_CHECKING 6 7from UM.i18n import i18nCatalog 8from UM.Qt.ListModel import ListModel 9from UM.Application import Application 10import UM.FlameProfiler 11 12if TYPE_CHECKING: 13 from cura.Settings.ExtruderStack import ExtruderStack # To listen to changes on the extruders. 14 15catalog = i18nCatalog("cura") 16 17 18class ExtrudersModel(ListModel): 19 """Model that holds extruders. 20 21 This model is designed for use by any list of extruders, but specifically intended for drop-down lists of the 22 current machine's extruders in place of settings. 23 """ 24 25 # The ID of the container stack for the extruder. 26 IdRole = Qt.UserRole + 1 27 28 NameRole = Qt.UserRole + 2 29 """Human-readable name of the extruder.""" 30 31 ColorRole = Qt.UserRole + 3 32 """Colour of the material loaded in the extruder.""" 33 34 IndexRole = Qt.UserRole + 4 35 """Index of the extruder, which is also the value of the setting itself. 36 37 An index of 0 indicates the first extruder, an index of 1 the second one, and so on. This is the value that will 38 be saved in instance containers. """ 39 40 # The ID of the definition of the extruder. 41 DefinitionRole = Qt.UserRole + 5 42 43 # The material of the extruder. 44 MaterialRole = Qt.UserRole + 6 45 46 # The variant of the extruder. 47 VariantRole = Qt.UserRole + 7 48 StackRole = Qt.UserRole + 8 49 50 MaterialBrandRole = Qt.UserRole + 9 51 ColorNameRole = Qt.UserRole + 10 52 53 EnabledRole = Qt.UserRole + 11 54 """Is the extruder enabled?""" 55 56 defaultColors = ["#ffc924", "#86ec21", "#22eeee", "#245bff", "#9124ff", "#ff24c8"] 57 """List of colours to display if there is no material or the material has no known colour. """ 58 59 def __init__(self, parent = None): 60 """Initialises the extruders model, defining the roles and listening for changes in the data. 61 62 :param parent: Parent QtObject of this list. 63 """ 64 65 super().__init__(parent) 66 67 self.addRoleName(self.IdRole, "id") 68 self.addRoleName(self.NameRole, "name") 69 self.addRoleName(self.EnabledRole, "enabled") 70 self.addRoleName(self.ColorRole, "color") 71 self.addRoleName(self.IndexRole, "index") 72 self.addRoleName(self.DefinitionRole, "definition") 73 self.addRoleName(self.MaterialRole, "material") 74 self.addRoleName(self.VariantRole, "variant") 75 self.addRoleName(self.StackRole, "stack") 76 self.addRoleName(self.MaterialBrandRole, "material_brand") 77 self.addRoleName(self.ColorNameRole, "color_name") 78 self._update_extruder_timer = QTimer() 79 self._update_extruder_timer.setInterval(100) 80 self._update_extruder_timer.setSingleShot(True) 81 self._update_extruder_timer.timeout.connect(self.__updateExtruders) 82 83 self._active_machine_extruders = [] # type: Iterable[ExtruderStack] 84 self._add_optional_extruder = False 85 86 # Listen to changes 87 Application.getInstance().globalContainerStackChanged.connect(self._extrudersChanged) # When the machine is swapped we must update the active machine extruders 88 Application.getInstance().getExtruderManager().extrudersChanged.connect(self._extrudersChanged) # When the extruders change we must link to the stack-changed signal of the new extruder 89 Application.getInstance().getContainerRegistry().containerMetaDataChanged.connect(self._onExtruderStackContainersChanged) # When meta data from a material container changes we must update 90 self._extrudersChanged() # Also calls _updateExtruders 91 92 addOptionalExtruderChanged = pyqtSignal() 93 94 def setAddOptionalExtruder(self, add_optional_extruder): 95 if add_optional_extruder != self._add_optional_extruder: 96 self._add_optional_extruder = add_optional_extruder 97 self.addOptionalExtruderChanged.emit() 98 self._updateExtruders() 99 100 @pyqtProperty(bool, fset = setAddOptionalExtruder, notify = addOptionalExtruderChanged) 101 def addOptionalExtruder(self): 102 return self._add_optional_extruder 103 104 def _extrudersChanged(self, machine_id = None): 105 """Links to the stack-changed signal of the new extruders when an extruder is swapped out or added in the 106 current machine. 107 108 :param machine_id: The machine for which the extruders changed. This is filled by the 109 ExtruderManager.extrudersChanged signal when coming from that signal. Application.globalContainerStackChanged 110 doesn't fill this signal; it's assumed to be the current printer in that case. 111 """ 112 113 machine_manager = Application.getInstance().getMachineManager() 114 if machine_id is not None: 115 if machine_manager.activeMachine is None: 116 # No machine, don't need to update the current machine's extruders 117 return 118 if machine_id != machine_manager.activeMachine.getId(): 119 # Not the current machine 120 return 121 122 # Unlink from old extruders 123 for extruder in self._active_machine_extruders: 124 extruder.containersChanged.disconnect(self._onExtruderStackContainersChanged) 125 extruder.enabledChanged.disconnect(self._updateExtruders) 126 127 # Link to new extruders 128 self._active_machine_extruders = [] 129 extruder_manager = Application.getInstance().getExtruderManager() 130 for extruder in extruder_manager.getActiveExtruderStacks(): 131 if extruder is None: #This extruder wasn't loaded yet. This happens asynchronously while this model is constructed from QML. 132 continue 133 extruder.containersChanged.connect(self._onExtruderStackContainersChanged) 134 extruder.enabledChanged.connect(self._updateExtruders) 135 self._active_machine_extruders.append(extruder) 136 137 self._updateExtruders() # Since the new extruders may have different properties, update our own model. 138 139 def _onExtruderStackContainersChanged(self, container): 140 # Update when there is an empty container or material or variant change 141 if container.getMetaDataEntry("type") in ["material", "variant", None]: 142 # The ExtrudersModel needs to be updated when the material-name or -color changes, because the user identifies extruders by material-name 143 self._updateExtruders() 144 145 modelChanged = pyqtSignal() 146 147 def _updateExtruders(self): 148 self._update_extruder_timer.start() 149 150 @UM.FlameProfiler.profile 151 def __updateExtruders(self): 152 """Update the list of extruders. 153 154 This should be called whenever the list of extruders changes. 155 """ 156 157 extruders_changed = False 158 159 if self.count != 0: 160 extruders_changed = True 161 162 items = [] 163 164 global_container_stack = Application.getInstance().getGlobalContainerStack() 165 if global_container_stack: 166 167 # get machine extruder count for verification 168 machine_extruder_count = global_container_stack.getProperty("machine_extruder_count", "value") 169 170 for extruder in Application.getInstance().getExtruderManager().getActiveExtruderStacks(): 171 position = extruder.getMetaDataEntry("position", default = "0") 172 try: 173 position = int(position) 174 except ValueError: 175 # Not a proper int. 176 position = -1 177 if position >= machine_extruder_count: 178 continue 179 180 default_color = self.defaultColors[position] if 0 <= position < len(self.defaultColors) else self.defaultColors[0] 181 color = extruder.material.getMetaDataEntry("color_code", default = default_color) if extruder.material else default_color 182 material_brand = extruder.material.getMetaDataEntry("brand", default = "generic") 183 color_name = extruder.material.getMetaDataEntry("color_name") 184 # construct an item with only the relevant information 185 item = { 186 "id": extruder.getId(), 187 "name": extruder.getName(), 188 "enabled": extruder.isEnabled, 189 "color": color, 190 "index": position, 191 "definition": extruder.getBottom().getId(), 192 "material": extruder.material.getName() if extruder.material else "", 193 "variant": extruder.variant.getName() if extruder.variant else "", # e.g. print core 194 "stack": extruder, 195 "material_brand": material_brand, 196 "color_name": color_name 197 } 198 199 items.append(item) 200 extruders_changed = True 201 202 if extruders_changed: 203 # sort by extruder index 204 items.sort(key = lambda i: i["index"]) 205 206 # We need optional extruder to be last, so add it after we do sorting. 207 # This way we can simply interpret the -1 of the index as the last item (which it now always is) 208 if self._add_optional_extruder: 209 item = { 210 "id": "", 211 "name": catalog.i18nc("@menuitem", "Not overridden"), 212 "enabled": True, 213 "color": "#ffffff", 214 "index": -1, 215 "definition": "", 216 "material": "", 217 "variant": "", 218 "stack": None, 219 "material_brand": "", 220 "color_name": "", 221 } 222 items.append(item) 223 if self._items != items: 224 self.setItems(items) 225 self.modelChanged.emit() 226