BaseMaterialsModel.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. # Copyright (c) 2019 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. from typing import Dict, Set
  4. from PyQt5.QtCore import Qt, QTimer, pyqtSignal, pyqtProperty
  5. from UM.Qt.ListModel import ListModel
  6. from UM.Logger import Logger
  7. import cura.CuraApplication # Imported like this to prevent a circular reference.
  8. from cura.Machines.ContainerTree import ContainerTree
  9. from cura.Machines.MaterialNode import MaterialNode
  10. from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
  11. ## This is the base model class for GenericMaterialsModel and MaterialBrandsModel.
  12. # Those 2 models are used by the material drop down menu to show generic materials and branded materials separately.
  13. # The extruder position defined here is being used to bound a menu to the correct extruder. This is used in the top
  14. # bar menu "Settings" -> "Extruder nr" -> "Material" -> this menu
  15. class BaseMaterialsModel(ListModel):
  16. extruderPositionChanged = pyqtSignal()
  17. enabledChanged = pyqtSignal()
  18. def __init__(self, parent = None):
  19. super().__init__(parent)
  20. from cura.CuraApplication import CuraApplication
  21. self._application = CuraApplication.getInstance()
  22. self._available_materials = {} # type: Dict[str, MaterialNode]
  23. self._favorite_ids = set() # type: Set[str]
  24. # Make these managers available to all material models
  25. self._container_registry = self._application.getInstance().getContainerRegistry()
  26. self._machine_manager = self._application.getMachineManager()
  27. self._extruder_position = 0
  28. self._extruder_stack = None
  29. self._enabled = True
  30. # CURA-6904
  31. # Updating the material model requires information from material nodes and containers. We use a timer here to
  32. # make sure that an update function call will not be directly invoked by an event. Because the triggered event
  33. # can be caused in the middle of a XMLMaterial loading, and the material container we try to find may not be
  34. # in the system yet. This will cause an infinite recursion of (1) trying to load a material, (2) trying to
  35. # update the material model, (3) cannot find the material container, load it, (4) repeat #1.
  36. self._update_timer = QTimer(self)
  37. self._update_timer.setInterval(100)
  38. self._update_timer.setSingleShot(True)
  39. self._update_timer.timeout.connect(self._update)
  40. # Update the stack and the model data when the machine changes
  41. self._machine_manager.globalContainerChanged.connect(self._updateExtruderStack)
  42. self._updateExtruderStack()
  43. # Update this model when switching machines or tabs, when adding materials or changing their metadata.
  44. self._machine_manager.activeStackChanged.connect(self._onChanged)
  45. ContainerTree.getInstance().materialsChanged.connect(self._materialsListChanged)
  46. self._application.getMaterialManagementModel().favoritesChanged.connect(self._onChanged)
  47. self.addRoleName(Qt.UserRole + 1, "root_material_id")
  48. self.addRoleName(Qt.UserRole + 2, "id")
  49. self.addRoleName(Qt.UserRole + 3, "GUID")
  50. self.addRoleName(Qt.UserRole + 4, "name")
  51. self.addRoleName(Qt.UserRole + 5, "brand")
  52. self.addRoleName(Qt.UserRole + 6, "description")
  53. self.addRoleName(Qt.UserRole + 7, "material")
  54. self.addRoleName(Qt.UserRole + 8, "color_name")
  55. self.addRoleName(Qt.UserRole + 9, "color_code")
  56. self.addRoleName(Qt.UserRole + 10, "density")
  57. self.addRoleName(Qt.UserRole + 11, "diameter")
  58. self.addRoleName(Qt.UserRole + 12, "approximate_diameter")
  59. self.addRoleName(Qt.UserRole + 13, "adhesion_info")
  60. self.addRoleName(Qt.UserRole + 14, "is_read_only")
  61. self.addRoleName(Qt.UserRole + 15, "container_node")
  62. self.addRoleName(Qt.UserRole + 16, "is_favorite")
  63. def _onChanged(self) -> None:
  64. self._update_timer.start()
  65. def _updateExtruderStack(self):
  66. global_stack = self._machine_manager.activeMachine
  67. if global_stack is None:
  68. return
  69. if self._extruder_stack is not None:
  70. self._extruder_stack.pyqtContainersChanged.disconnect(self._onChanged)
  71. self._extruder_stack.approximateMaterialDiameterChanged.disconnect(self._onChanged)
  72. try:
  73. self._extruder_stack = global_stack.extruderList[self._extruder_position]
  74. except IndexError:
  75. self._extruder_stack = None
  76. if self._extruder_stack is not None:
  77. self._extruder_stack.pyqtContainersChanged.connect(self._onChanged)
  78. self._extruder_stack.approximateMaterialDiameterChanged.connect(self._onChanged)
  79. # Force update the model when the extruder stack changes
  80. self._onChanged()
  81. def setExtruderPosition(self, position: int):
  82. if self._extruder_stack is None or self._extruder_position != position:
  83. self._extruder_position = position
  84. self._updateExtruderStack()
  85. self.extruderPositionChanged.emit()
  86. @pyqtProperty(int, fset = setExtruderPosition, notify = extruderPositionChanged)
  87. def extruderPosition(self) -> int:
  88. return self._extruder_position
  89. def setEnabled(self, enabled):
  90. if self._enabled != enabled:
  91. self._enabled = enabled
  92. if self._enabled:
  93. # ensure the data is there again.
  94. self._onChanged()
  95. self.enabledChanged.emit()
  96. @pyqtProperty(bool, fset = setEnabled, notify = enabledChanged)
  97. def enabled(self):
  98. return self._enabled
  99. ## Triggered when a list of materials changed somewhere in the container
  100. # tree. This change may trigger an _update() call when the materials
  101. # changed for the configuration that this model is looking for.
  102. def _materialsListChanged(self, material: MaterialNode) -> None:
  103. if self._extruder_stack is None:
  104. return
  105. if material.variant.container_id != self._extruder_stack.variant.getId():
  106. return
  107. global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
  108. if not global_stack:
  109. return
  110. if material.variant.machine.container_id != global_stack.definition.getId():
  111. return
  112. self._onChanged()
  113. ## Triggered when the list of favorite materials is changed.
  114. def _favoritesChanged(self, material_base_file: str) -> None:
  115. if material_base_file in self._available_materials:
  116. self._onChanged()
  117. ## This is an abstract method that needs to be implemented by the specific
  118. # models themselves.
  119. def _update(self):
  120. self._favorite_ids = set(cura.CuraApplication.CuraApplication.getInstance().getPreferences().getValue("cura/favorite_materials").split(";"))
  121. # Update the available materials (ContainerNode) for the current active machine and extruder setup.
  122. global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
  123. if not global_stack.hasMaterials:
  124. return # There are no materials for this machine, so nothing to do.
  125. extruder_stack = global_stack.extruders.get(str(self._extruder_position))
  126. if not extruder_stack:
  127. return
  128. nozzle_name = extruder_stack.variant.getName()
  129. machine_node = ContainerTree.getInstance().machines[global_stack.definition.getId()]
  130. if nozzle_name not in machine_node.variants:
  131. Logger.log("w", "Unable to find variant %s in container tree", nozzle_name)
  132. self._available_materials = {}
  133. return
  134. materials = machine_node.variants[nozzle_name].materials
  135. approximate_material_diameter = extruder_stack.getApproximateMaterialDiameter()
  136. self._available_materials = {key: material for key, material in materials.items() if float(material.getMetaDataEntry("approximate_diameter", -1)) == approximate_material_diameter}
  137. ## This method is used by all material models in the beginning of the
  138. # _update() method in order to prevent errors. It's the same in all models
  139. # so it's placed here for easy access.
  140. def _canUpdate(self):
  141. global_stack = self._machine_manager.activeMachine
  142. if global_stack is None or not self._enabled:
  143. return False
  144. extruder_position = str(self._extruder_position)
  145. if extruder_position not in global_stack.extruders:
  146. return False
  147. return True
  148. ## This is another convenience function which is shared by all material
  149. # models so it's put here to avoid having so much duplicated code.
  150. def _createMaterialItem(self, root_material_id, container_node):
  151. metadata_list = CuraContainerRegistry.getInstance().findContainersMetadata(id = container_node.container_id)
  152. if not metadata_list:
  153. return None
  154. metadata = metadata_list[0]
  155. item = {
  156. "root_material_id": root_material_id,
  157. "id": metadata["id"],
  158. "container_id": metadata["id"], # TODO: Remove duplicate in material manager qml
  159. "GUID": metadata["GUID"],
  160. "name": metadata["name"],
  161. "brand": metadata["brand"],
  162. "description": metadata["description"],
  163. "material": metadata["material"],
  164. "color_name": metadata["color_name"],
  165. "color_code": metadata.get("color_code", ""),
  166. "density": metadata.get("properties", {}).get("density", ""),
  167. "diameter": metadata.get("properties", {}).get("diameter", ""),
  168. "approximate_diameter": metadata["approximate_diameter"],
  169. "adhesion_info": metadata["adhesion_info"],
  170. "is_read_only": self._container_registry.isReadOnly(metadata["id"]),
  171. "container_node": container_node,
  172. "is_favorite": root_material_id in self._favorite_ids
  173. }
  174. return item