ExtrudersModel.py 9.9 KB

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