QualityManager.py 16 KB

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