ProfilesModel.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. # Copyright (c) 2017 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. from collections import OrderedDict
  4. from PyQt5.QtCore import Qt
  5. from UM.Application import Application
  6. from UM.Settings.ContainerRegistry import ContainerRegistry
  7. from UM.Settings.Models.InstanceContainersModel import InstanceContainersModel
  8. from cura.QualityManager import QualityManager
  9. from cura.Settings.ExtruderManager import ExtruderManager
  10. from typing import List, TYPE_CHECKING
  11. if TYPE_CHECKING:
  12. from cura.Settings.ExtruderStack import ExtruderStack
  13. ## QML Model for listing the current list of valid quality profiles.
  14. #
  15. class ProfilesModel(InstanceContainersModel):
  16. LayerHeightRole = Qt.UserRole + 1001
  17. LayerHeightWithoutUnitRole = Qt.UserRole + 1002
  18. AvailableRole = Qt.UserRole + 1003
  19. def __init__(self, parent = None):
  20. super().__init__(parent)
  21. self.addRoleName(self.LayerHeightRole, "layer_height")
  22. self.addRoleName(self.LayerHeightWithoutUnitRole, "layer_height_without_unit")
  23. self.addRoleName(self.AvailableRole, "available")
  24. Application.getInstance().globalContainerStackChanged.connect(self._update)
  25. Application.getInstance().getMachineManager().activeVariantChanged.connect(self._update)
  26. Application.getInstance().getMachineManager().activeStackChanged.connect(self._update)
  27. Application.getInstance().getMachineManager().activeMaterialChanged.connect(self._update)
  28. self._empty_quality = ContainerRegistry.getInstance().findContainers(id = "empty_quality")[0]
  29. # Factory function, used by QML
  30. @staticmethod
  31. def createProfilesModel(engine, js_engine):
  32. return ProfilesModel.getInstance()
  33. ## Get the singleton instance for this class.
  34. @classmethod
  35. def getInstance(cls) -> "ProfilesModel":
  36. # Note: Explicit use of class name to prevent issues with inheritance.
  37. if not ProfilesModel.__instance:
  38. ProfilesModel.__instance = cls()
  39. return ProfilesModel.__instance
  40. @classmethod
  41. def hasInstance(cls) -> bool:
  42. return ProfilesModel.__instance is not None
  43. __instance = None # type: "ProfilesModel"
  44. ## Fetch the list of containers to display.
  45. #
  46. # See UM.Settings.Models.InstanceContainersModel._fetchInstanceContainers().
  47. def _fetchInstanceContainers(self):
  48. global_container_stack = Application.getInstance().getGlobalContainerStack()
  49. if global_container_stack is None:
  50. return {}, {}
  51. global_stack_definition = global_container_stack.definition
  52. # Get the list of extruders and place the selected extruder at the front of the list.
  53. extruder_stacks = self._getOrderedExtruderStacksList()
  54. materials = [extruder.material for extruder in extruder_stacks]
  55. # Fetch the list of usable qualities across all extruders.
  56. # The actual list of quality profiles come from the first extruder in the extruder list.
  57. result = QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks)
  58. # The usable quality types are set
  59. quality_type_set = set([x.getMetaDataEntry("quality_type") for x in result])
  60. # Fetch all qualities available for this machine and the materials selected in extruders
  61. all_qualities = QualityManager.getInstance().findAllQualitiesForMachineAndMaterials(global_stack_definition, materials)
  62. # If in the all qualities there is some of them that are not available due to incompatibility with materials
  63. # we also add it so that they will appear in the slide quality bar. However in recomputeItems will be marked as
  64. # not available so they will be shown in gray
  65. for quality in all_qualities:
  66. if quality.getMetaDataEntry("quality_type") not in quality_type_set:
  67. result.append(quality)
  68. if len(result) > 1 and self._empty_quality in result:
  69. result.remove(self._empty_quality)
  70. return {item.getId(): item for item in result}, {} #Only return true profiles for now, no metadata. The quality manager is not able to get only metadata yet.
  71. ## Re-computes the items in this model, and adds the layer height role.
  72. def _recomputeItems(self):
  73. # Some globals that we can re-use.
  74. global_container_stack = Application.getInstance().getGlobalContainerStack()
  75. if global_container_stack is None:
  76. return
  77. extruder_stacks = self._getOrderedExtruderStacksList()
  78. container_registry = ContainerRegistry.getInstance()
  79. # Get a list of usable/available qualities for this machine and material
  80. qualities = QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks)
  81. unit = global_container_stack.getBottom().getProperty("layer_height", "unit")
  82. if not unit:
  83. unit = ""
  84. # group all quality items according to quality_types, so we know which profile suits the currently
  85. # active machine and material, and later yield the right ones.
  86. tmp_all_quality_items = OrderedDict()
  87. for item in super()._recomputeItems():
  88. profiles = container_registry.findContainersMetadata(id = item["id"])
  89. if not profiles or "quality_type" not in profiles[0]:
  90. quality_type = ""
  91. else:
  92. quality_type = profiles[0]["quality_type"]
  93. if quality_type not in tmp_all_quality_items:
  94. tmp_all_quality_items[quality_type] = {"suitable_container": None, "all_containers": []}
  95. tmp_all_quality_items[quality_type]["all_containers"].append(item)
  96. if tmp_all_quality_items[quality_type]["suitable_container"] is None:
  97. tmp_all_quality_items[quality_type]["suitable_container"] = item
  98. # reverse the ordering (finest first, coarsest last)
  99. all_quality_items = OrderedDict()
  100. for key in reversed(tmp_all_quality_items.keys()):
  101. all_quality_items[key] = tmp_all_quality_items[key]
  102. # First the suitable containers are set in the model
  103. containers = []
  104. for data_item in all_quality_items.values():
  105. suitable_item = data_item["suitable_container"]
  106. if suitable_item is not None:
  107. containers.append(suitable_item)
  108. # Once the suitable containers are collected, the rest of the containers are appended
  109. for data_item in all_quality_items.values():
  110. for item in data_item["all_containers"]:
  111. if item not in containers:
  112. containers.append(item)
  113. # Now all the containers are set
  114. for item in containers:
  115. profile = container_registry.findContainers(id = item["id"])
  116. # When for some reason there is no profile container in the registry
  117. if not profile:
  118. self._setItemLayerHeight(item, "", "")
  119. item["available"] = False
  120. yield item
  121. continue
  122. profile = profile[0]
  123. # When there is a profile but it's an empty quality should. It's shown in the list (they are "Not Supported" profiles)
  124. if profile.getId() == "empty_quality":
  125. self._setItemLayerHeight(item, "", "")
  126. item["available"] = True
  127. yield item
  128. continue
  129. item["available"] = profile in qualities
  130. # Easy case: This profile defines its own layer height.
  131. if profile.hasProperty("layer_height", "value"):
  132. self._setItemLayerHeight(item, profile.getProperty("layer_height", "value"), unit)
  133. yield item
  134. continue
  135. machine_manager = Application.getInstance().getMachineManager()
  136. # Quality-changes profile that has no value for layer height. Get the corresponding quality profile and ask that profile.
  137. quality_type = profile.getMetaDataEntry("quality_type", None)
  138. if quality_type:
  139. quality_results = machine_manager.determineQualityAndQualityChangesForQualityType(quality_type)
  140. for quality_result in quality_results:
  141. if quality_result["stack"] is global_container_stack:
  142. quality = quality_result["quality"]
  143. break
  144. else:
  145. # No global container stack in the results:
  146. if quality_results:
  147. # Take any of the extruders.
  148. quality = quality_results[0]["quality"]
  149. else:
  150. quality = None
  151. if quality and quality.hasProperty("layer_height", "value"):
  152. self._setItemLayerHeight(item, quality.getProperty("layer_height", "value"), unit)
  153. yield item
  154. continue
  155. # Quality has no value for layer height either. Get the layer height from somewhere lower in the stack.
  156. skip_until_container = global_container_stack.material
  157. if not skip_until_container or skip_until_container == ContainerRegistry.getInstance().getEmptyInstanceContainer(): # No material in stack.
  158. skip_until_container = global_container_stack.variant
  159. if not skip_until_container or skip_until_container == ContainerRegistry.getInstance().getEmptyInstanceContainer(): # No variant in stack.
  160. skip_until_container = global_container_stack.getBottom()
  161. self._setItemLayerHeight(item, global_container_stack.getRawProperty("layer_height", "value", skip_until_container = skip_until_container.getId()), unit) # Fall through to the currently loaded material.
  162. yield item
  163. ## Get a list of extruder stacks with the active extruder at the front of the list.
  164. @staticmethod
  165. def _getOrderedExtruderStacksList() -> List["ExtruderStack"]:
  166. extruder_manager = ExtruderManager.getInstance()
  167. extruder_stacks = extruder_manager.getActiveExtruderStacks()
  168. active_extruder = extruder_manager.getActiveExtruderStack()
  169. if active_extruder in extruder_stacks:
  170. extruder_stacks.remove(active_extruder)
  171. extruder_stacks = [active_extruder] + extruder_stacks
  172. return extruder_stacks
  173. @staticmethod
  174. def _setItemLayerHeight(item, value, unit):
  175. item["layer_height"] = str(value) + unit
  176. item["layer_height_without_unit"] = str(value)