QualityManager.py 15 KB

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