IntentManager.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. # Copyright (c) 2019 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
  4. from typing import Any, Dict, List, Optional, Set, Tuple, TYPE_CHECKING
  5. from UM.Logger import Logger
  6. from UM.Settings.InstanceContainer import InstanceContainer
  7. import cura.CuraApplication
  8. from cura.Machines.ContainerTree import ContainerTree
  9. from cura.Settings.cura_empty_instance_containers import empty_intent_container
  10. if TYPE_CHECKING:
  11. from UM.Settings.InstanceContainer import InstanceContainer
  12. ## Front-end for querying which intents are available for a certain
  13. # configuration.
  14. class IntentManager(QObject):
  15. __instance = None
  16. ## This class is a singleton.
  17. @classmethod
  18. def getInstance(cls):
  19. if not cls.__instance:
  20. cls.__instance = IntentManager()
  21. return cls.__instance
  22. intentCategoryChanged = pyqtSignal() #Triggered when we switch categories.
  23. ## Gets the metadata dictionaries of all intent profiles for a given
  24. # configuration.
  25. #
  26. # \param definition_id ID of the printer.
  27. # \param nozzle_name Name of the nozzle.
  28. # \param material_base_file The base_file of the material.
  29. # \return A list of metadata dictionaries matching the search criteria, or
  30. # an empty list if nothing was found.
  31. def intentMetadatas(self, definition_id: str, nozzle_name: str, material_base_file: str) -> List[Dict[str, Any]]:
  32. intent_metadatas = [] # type: List[Dict[str, Any]]
  33. try:
  34. materials = ContainerTree.getInstance().machines[definition_id].variants[nozzle_name].materials
  35. except KeyError:
  36. Logger.log("w", "Unable to find the machine %s or the variant %s", definition_id, nozzle_name)
  37. materials = {}
  38. if material_base_file not in materials:
  39. return intent_metadatas
  40. material_node = materials[material_base_file]
  41. for quality_node in material_node.qualities.values():
  42. for intent_node in quality_node.intents.values():
  43. intent_metadatas.append(intent_node.getMetadata())
  44. return intent_metadatas
  45. ## Collects and returns all intent categories available for the given
  46. # parameters. Note that the 'default' category is always available.
  47. #
  48. # \param definition_id ID of the printer.
  49. # \param nozzle_name Name of the nozzle.
  50. # \param material_id ID of the material.
  51. # \return A set of intent category names.
  52. def intentCategories(self, definition_id: str, nozzle_id: str, material_id: str) -> List[str]:
  53. categories = set()
  54. for intent in self.intentMetadatas(definition_id, nozzle_id, material_id):
  55. categories.add(intent["intent_category"])
  56. categories.add("default") #The "empty" intent is not an actual profile specific to the configuration but we do want it to appear in the categories list.
  57. return list(categories)
  58. ## List of intents to be displayed in the interface.
  59. #
  60. # For the interface this will have to be broken up into the different
  61. # intent categories. That is up to the model there.
  62. #
  63. # \return A list of tuples of intent_category and quality_type. The actual
  64. # instance may vary per extruder.
  65. def getCurrentAvailableIntents(self) -> List[Tuple[str, str]]:
  66. application = cura.CuraApplication.CuraApplication.getInstance()
  67. global_stack = application.getGlobalContainerStack()
  68. if global_stack is None:
  69. return [("default", "normal")]
  70. # TODO: We now do this (return a default) if the global stack is missing, but not in the code below,
  71. # even though there should always be defaults. The problem then is what to do with the quality_types.
  72. # Currently _also_ inconsistent with 'currentAvailableIntentCategories', which _does_ return default.
  73. quality_groups = ContainerTree.getInstance().getCurrentQualityGroups()
  74. available_quality_types = {quality_group.quality_type for quality_group in quality_groups.values() if quality_group.node_for_global is not None}
  75. final_intent_ids = set() # type: Set[str]
  76. current_definition_id = global_stack.definition.getId()
  77. for extruder_stack in global_stack.extruderList:
  78. if not extruder_stack.isEnabled:
  79. continue
  80. nozzle_name = extruder_stack.variant.getMetaDataEntry("name")
  81. material_id = extruder_stack.material.getMetaDataEntry("base_file")
  82. final_intent_ids |= {metadata["id"] for metadata in self.intentMetadatas(current_definition_id, nozzle_name, material_id) if metadata.get("quality_type") in available_quality_types}
  83. result = set() # type: Set[Tuple[str, str]]
  84. for intent_id in final_intent_ids:
  85. intent_metadata = application.getContainerRegistry().findContainersMetadata(id = intent_id)[0]
  86. result.add((intent_metadata["intent_category"], intent_metadata["quality_type"]))
  87. return list(result)
  88. ## List of intent categories available in either of the extruders.
  89. #
  90. # This is purposefully inconsistent with the way that the quality types
  91. # are listed. The quality types will show all quality types available in
  92. # the printer using any configuration. This will only list the intent
  93. # categories that are available using the current configuration (but the
  94. # union over the extruders).
  95. # \return List of all categories in the current configurations of all
  96. # extruders.
  97. def currentAvailableIntentCategories(self) -> List[str]:
  98. global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
  99. if global_stack is None:
  100. return ["default"]
  101. current_definition_id = global_stack.definition.getId()
  102. final_intent_categories = set() # type: Set[str]
  103. for extruder_stack in global_stack.extruderList:
  104. if not extruder_stack.isEnabled:
  105. continue
  106. nozzle_name = extruder_stack.variant.getMetaDataEntry("name")
  107. material_id = extruder_stack.material.getMetaDataEntry("base_file")
  108. final_intent_categories.update(self.intentCategories(current_definition_id, nozzle_name, material_id))
  109. return list(final_intent_categories)
  110. ## The intent that gets selected by default when no intent is available for
  111. # the configuration, an extruder can't match the intent that the user
  112. # selects, or just when creating a new printer.
  113. def getDefaultIntent(self) -> "InstanceContainer":
  114. return empty_intent_container
  115. @pyqtProperty(str, notify = intentCategoryChanged)
  116. def currentIntentCategory(self) -> str:
  117. application = cura.CuraApplication.CuraApplication.getInstance()
  118. active_extruder_stack = application.getMachineManager().activeStack
  119. if active_extruder_stack is None:
  120. return ""
  121. return active_extruder_stack.intent.getMetaDataEntry("intent_category", "")
  122. ## Apply intent on the stacks.
  123. @pyqtSlot(str, str)
  124. def selectIntent(self, intent_category: str, quality_type: str) -> None:
  125. Logger.log("i", "Attempting to set intent_category to [%s] and quality type to [%s]", intent_category, quality_type)
  126. old_intent_category = self.currentIntentCategory
  127. application = cura.CuraApplication.CuraApplication.getInstance()
  128. global_stack = application.getGlobalContainerStack()
  129. if global_stack is None:
  130. return
  131. current_definition_id = global_stack.definition.getId()
  132. machine_node = ContainerTree.getInstance().machines[current_definition_id]
  133. for extruder_stack in global_stack.extruderList:
  134. nozzle_name = extruder_stack.variant.getMetaDataEntry("name")
  135. material_id = extruder_stack.material.getMetaDataEntry("base_file")
  136. material_node = machine_node.variants[nozzle_name].materials[material_id]
  137. # Since we want to switch to a certain quality type, check the tree if we have one.
  138. quality_node = None
  139. for q_node in material_node.qualities.values():
  140. if q_node.quality_type == quality_type:
  141. quality_node = q_node
  142. if quality_node is None:
  143. Logger.log("w", "Unable to find quality_type [%s] for extruder [%s]", quality_type, extruder_stack.getId())
  144. continue
  145. # Check that quality node if we can find a matching intent.
  146. intent_id = None
  147. for id, intent_node in quality_node.intents.items():
  148. if intent_node.intent_category == intent_category:
  149. intent_id = id
  150. intent = application.getContainerRegistry().findContainers(id = intent_id)
  151. if intent:
  152. extruder_stack.intent = intent[0]
  153. else:
  154. extruder_stack.intent = self.getDefaultIntent()
  155. application.getMachineManager().setQualityGroupByQualityType(quality_type)
  156. if old_intent_category != intent_category:
  157. self.intentCategoryChanged.emit()