QualityManager.py 19 KB

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