IntentManager.py 8.6 KB

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