QualityManager.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. # Copyright (c) 2016 Ultimaker B.V.
  2. # Cura is released under the terms of the AGPLv3 or higher.
  3. # This collects a lot of quality and quality changes related code which was split between ContainerManager
  4. # and the MachineManager and really needs to usable from both.
  5. from typing import List, Optional, Dict, TYPE_CHECKING
  6. from UM.Application import Application
  7. from UM.Settings.ContainerRegistry import ContainerRegistry
  8. from UM.Settings.DefinitionContainer import DefinitionContainer
  9. from UM.Settings.InstanceContainer import InstanceContainer
  10. from cura.Settings.ExtruderManager import ExtruderManager
  11. if TYPE_CHECKING:
  12. from cura.Settings.GlobalStack import GlobalStack
  13. from cura.Settings.ExtruderStack import ExtruderStack
  14. from UM.Settings.DefinitionContainer import DefinitionContainerInterface
  15. class QualityManager:
  16. ## Get the singleton instance for this class.
  17. @classmethod
  18. def getInstance(cls) -> "QualityManager":
  19. # Note: Explicit use of class name to prevent issues with inheritance.
  20. if not QualityManager.__instance:
  21. QualityManager.__instance = cls()
  22. return QualityManager.__instance
  23. __instance = None # type: "QualityManager"
  24. ## Find a quality by name for a specific machine definition and materials.
  25. #
  26. # \param quality_name
  27. # \param machine_definition (Optional) \type{DefinitionContainerInterface} If nothing is
  28. # specified then the currently selected machine definition is used.
  29. # \param material_containers (Optional) \type{List[InstanceContainer]} If nothing is specified then
  30. # the current set of selected materials is used.
  31. # \return the matching quality container \type{InstanceContainer}
  32. def findQualityByName(self, quality_name: str, machine_definition: Optional["DefinitionContainerInterface"] = None, material_containers: List[InstanceContainer] = None) -> Optional[InstanceContainer]:
  33. criteria = {"type": "quality", "name": quality_name}
  34. result = self._getFilteredContainersForStack(machine_definition, material_containers, **criteria)
  35. # Fall back to using generic materials and qualities if nothing could be found.
  36. if not result and material_containers and len(material_containers) == 1:
  37. basic_materials = self._getBasicMaterials(material_containers[0])
  38. result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria)
  39. return result[0] if result else None
  40. ## Find a quality changes container by name.
  41. #
  42. # \param quality_changes_name \type{str} the name of the quality changes container.
  43. # \param machine_definition (Optional) \type{DefinitionContainer} If nothing is
  44. # specified then the currently selected machine definition is used..
  45. # \return the matching quality changes containers \type{List[InstanceContainer]}
  46. def findQualityChangesByName(self, quality_changes_name: str, machine_definition: Optional["DefinitionContainerInterface"] = None):
  47. criteria = {"type": "quality_changes", "name": quality_changes_name}
  48. result = self._getFilteredContainersForStack(machine_definition, [], **criteria)
  49. return result
  50. ## Fetch the list of available quality types for this combination of machine definition and materials.
  51. #
  52. # \param machine_definition \type{DefinitionContainer}
  53. # \param material_containers \type{List[InstanceContainer]}
  54. # \return \type{List[str]}
  55. def findAllQualityTypesForMachineAndMaterials(self, machine_definition: "DefinitionContainerInterface", material_containers: List[InstanceContainer]) -> List[str]:
  56. # Determine the common set of quality types which can be
  57. # applied to all of the materials for this machine.
  58. quality_type_dict = self.__fetchQualityTypeDictForMaterial(machine_definition, material_containers[0])
  59. common_quality_types = set(quality_type_dict.keys())
  60. for material_container in material_containers[1:]:
  61. next_quality_type_dict = self.__fetchQualityTypeDictForMaterial(machine_definition, material_container)
  62. common_quality_types.intersection_update(set(next_quality_type_dict.keys()))
  63. return list(common_quality_types)
  64. ## Fetches a dict of quality types names to quality profiles for a combination of machine and material.
  65. #
  66. # \param machine_definition \type{DefinitionContainer} the machine definition.
  67. # \param material \type{InstanceContainer} the material.
  68. # \return \type{Dict[str, InstanceContainer]} the dict of suitable quality type names mapping to qualities.
  69. def __fetchQualityTypeDictForMaterial(self, machine_definition: "DefinitionContainerInterface", material: InstanceContainer) -> Dict[str, InstanceContainer]:
  70. qualities = self.findAllQualitiesForMachineMaterial(machine_definition, material)
  71. quality_type_dict = {}
  72. for quality in qualities:
  73. quality_type_dict[quality.getMetaDataEntry("quality_type")] = quality
  74. return quality_type_dict
  75. ## Find a quality container by quality type.
  76. #
  77. # \param quality_type \type{str} the name of the quality type to search for.
  78. # \param machine_definition (Optional) \type{InstanceContainer} If nothing is
  79. # specified then the currently selected machine definition is used.
  80. # \param material_containers (Optional) \type{List[InstanceContainer]} If nothing is specified then
  81. # the current set of selected materials is used.
  82. # \return the matching quality container \type{InstanceContainer}
  83. def findQualityByQualityType(self, quality_type: str, machine_definition: Optional["DefinitionContainerInterface"] = None, material_containers: List[InstanceContainer] = None, **kwargs) -> InstanceContainer:
  84. criteria = kwargs
  85. criteria["type"] = "quality"
  86. if quality_type:
  87. criteria["quality_type"] = quality_type
  88. result = self._getFilteredContainersForStack(machine_definition, material_containers, **criteria)
  89. # Fall back to using generic materials and qualities if nothing could be found.
  90. if not result and material_containers and len(material_containers) == 1:
  91. basic_materials = self._getBasicMaterials(material_containers[0])
  92. result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria)
  93. return result[0] if result else None
  94. ## Find all suitable qualities for a combination of machine and material.
  95. #
  96. # \param machine_definition \type{DefinitionContainer} the machine definition.
  97. # \param material_container \type{InstanceContainer} the material.
  98. # \return \type{List[InstanceContainer]} the list of suitable qualities.
  99. def findAllQualitiesForMachineMaterial(self, machine_definition: "DefinitionContainerInterface", material_container: InstanceContainer) -> List[InstanceContainer]:
  100. criteria = {"type": "quality" }
  101. result = self._getFilteredContainersForStack(machine_definition, [material_container], **criteria)
  102. if not result:
  103. basic_materials = self._getBasicMaterials(material_container)
  104. result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria)
  105. return result
  106. ## Find all quality changes for a machine.
  107. #
  108. # \param machine_definition \type{DefinitionContainer} the machine definition.
  109. # \return \type{List[InstanceContainer]} the list of quality changes
  110. def findAllQualityChangesForMachine(self, machine_definition: "DefinitionContainerInterface") -> List[InstanceContainer]:
  111. if machine_definition.getMetaDataEntry("has_machine_quality"):
  112. definition_id = machine_definition.getId()
  113. else:
  114. definition_id = "fdmprinter"
  115. filter_dict = { "type": "quality_changes", "extruder": None, "definition": definition_id }
  116. quality_changes_list = ContainerRegistry.getInstance().findInstanceContainers(**filter_dict)
  117. return quality_changes_list
  118. ## Find all usable qualities for a machine and extruders.
  119. #
  120. # Finds all of the qualities for this combination of machine and extruders.
  121. # Only one quality per quality type is returned. i.e. if there are 2 qualities with quality_type=normal
  122. # then only one of then is returned (at random).
  123. #
  124. # \param global_container_stack \type{GlobalStack} the global machine definition
  125. # \param extruder_stacks \type{List[ExtruderStack]} the list of extruder stacks
  126. # \return \type{List[InstanceContainer]} the list of the matching qualities. The quality profiles
  127. # return come from the first extruder in the given list of extruders.
  128. def findAllUsableQualitiesForMachineAndExtruders(self, global_container_stack: "GlobalStack", extruder_stacks: List["ExtruderStack"]) -> List[InstanceContainer]:
  129. global_machine_definition = global_container_stack.getBottom()
  130. if extruder_stacks:
  131. # Multi-extruder machine detected.
  132. materials = [stack.material for stack in extruder_stacks]
  133. else:
  134. # Machine with one extruder.
  135. materials = [global_container_stack.material]
  136. quality_types = self.findAllQualityTypesForMachineAndMaterials(global_machine_definition, materials)
  137. # Map the list of quality_types to InstanceContainers
  138. qualities = self.findAllQualitiesForMachineMaterial(global_machine_definition, materials[0])
  139. quality_type_dict = {}
  140. for quality in qualities:
  141. quality_type_dict[quality.getMetaDataEntry("quality_type")] = quality
  142. return [quality_type_dict[quality_type] for quality_type in quality_types]
  143. ## Fetch more basic versions of a material.
  144. #
  145. # This tries to find a generic or basic version of the given material.
  146. # \param material_container \type{InstanceContainer} the material
  147. # \return \type{List[InstanceContainer]} a list of the basic materials or an empty list if one could not be found.
  148. def _getBasicMaterials(self, material_container: InstanceContainer):
  149. base_material = material_container.getMetaDataEntry("material")
  150. material_container_definition = material_container.getDefinition()
  151. if material_container_definition and material_container_definition.getMetaDataEntry("has_machine_quality"):
  152. definition_id = material_container.getDefinition().getMetaDataEntry("quality_definition", material_container.getDefinition().getId())
  153. else:
  154. definition_id = "fdmprinter"
  155. if base_material:
  156. # There is a basic material specified
  157. criteria = { "type": "material", "name": base_material, "definition": definition_id }
  158. containers = ContainerRegistry.getInstance().findInstanceContainers(**criteria)
  159. containers = [basic_material for basic_material in containers if
  160. basic_material.getMetaDataEntry("variant") == material_container.getMetaDataEntry(
  161. "variant")]
  162. return containers
  163. return []
  164. def _getFilteredContainers(self, **kwargs):
  165. return self._getFilteredContainersForStack(None, None, **kwargs)
  166. def _getFilteredContainersForStack(self, machine_definition: "DefinitionContainerInterface" = None, material_containers: List[InstanceContainer] = None, **kwargs):
  167. # Fill in any default values.
  168. if machine_definition is None:
  169. machine_definition = Application.getInstance().getGlobalContainerStack().getBottom()
  170. quality_definition_id = machine_definition.getMetaDataEntry("quality_definition")
  171. if quality_definition_id is not None:
  172. machine_definition = ContainerRegistry.getInstance().findDefinitionContainers(id=quality_definition_id)[0]
  173. if material_containers is None:
  174. active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
  175. if active_stacks:
  176. material_containers = [stack.material for stack in active_stacks]
  177. criteria = kwargs
  178. filter_by_material = False
  179. machine_definition = self.getParentMachineDefinition(machine_definition)
  180. whole_machine_definition = self.getWholeMachineDefinition(machine_definition)
  181. if whole_machine_definition.getMetaDataEntry("has_machine_quality"):
  182. definition_id = machine_definition.getMetaDataEntry("quality_definition", whole_machine_definition.getId())
  183. criteria["definition"] = definition_id
  184. filter_by_material = whole_machine_definition.getMetaDataEntry("has_materials")
  185. else:
  186. criteria["definition"] = "fdmprinter"
  187. material_ids = set()
  188. # Stick the material IDs in a set
  189. if material_containers is None or len(material_containers) == 0:
  190. filter_by_material = False
  191. else:
  192. for material_instance in material_containers:
  193. if material_instance is not None:
  194. # Add the parent material too.
  195. for basic_material in self._getBasicMaterials(material_instance):
  196. material_ids.add(basic_material.getId())
  197. material_ids.add(material_instance.getId())
  198. containers = ContainerRegistry.getInstance().findInstanceContainers(**criteria)
  199. result = []
  200. for container in containers:
  201. # If the machine specifies we should filter by material, exclude containers that do not match any active material.
  202. if filter_by_material and container.getMetaDataEntry("material") not in material_ids and not "global_quality" in kwargs:
  203. continue
  204. result.append(container)
  205. return result
  206. ## Get the parent machine definition of a machine definition.
  207. #
  208. # \param machine_definition \type{DefinitionContainer} This may be a normal machine definition or
  209. # an extruder definition.
  210. # \return \type{DefinitionContainer} the parent machine definition. If the given machine
  211. # definition doesn't have a parent then it is simply returned.
  212. def getParentMachineDefinition(self, machine_definition: "DefinitionContainerInterface") -> "DefinitionContainerInterface":
  213. container_registry = ContainerRegistry.getInstance()
  214. machine_entry = machine_definition.getMetaDataEntry("machine")
  215. if machine_entry is None:
  216. # We have a normal (whole) machine defintion
  217. quality_definition = machine_definition.getMetaDataEntry("quality_definition")
  218. if quality_definition is not None:
  219. parent_machine_definition = container_registry.findDefinitionContainers(id=quality_definition)[0]
  220. return self.getParentMachineDefinition(parent_machine_definition)
  221. else:
  222. return machine_definition
  223. else:
  224. # This looks like an extruder. Find the rest of the machine.
  225. whole_machine = container_registry.findDefinitionContainers(id=machine_entry)[0]
  226. parent_machine = self.getParentMachineDefinition(whole_machine)
  227. if whole_machine is parent_machine:
  228. # This extruder already belongs to a 'parent' machine def.
  229. return machine_definition
  230. else:
  231. # Look up the corresponding extruder definition in the parent machine definition.
  232. extruder_position = machine_definition.getMetaDataEntry("position")
  233. parent_extruder_id = parent_machine.getMetaDataEntry("machine_extruder_trains")[extruder_position]
  234. return container_registry.findDefinitionContainers(id=parent_extruder_id)[0]
  235. ## Get the whole/global machine definition from an extruder definition.
  236. #
  237. # \param machine_definition \type{DefinitionContainer} This may be a normal machine definition or
  238. # an extruder definition.
  239. # \return \type{DefinitionContainerInterface}
  240. def getWholeMachineDefinition(self, machine_definition: "DefinitionContainerInterface") -> "DefinitionContainerInterface":
  241. machine_entry = machine_definition.getMetaDataEntry("machine")
  242. if machine_entry is None:
  243. # This already is a 'global' machine definition.
  244. return machine_definition
  245. else:
  246. container_registry = ContainerRegistry.getInstance()
  247. whole_machine = container_registry.findDefinitionContainers(id=machine_entry)[0]
  248. return whole_machine