IntentManager.py 8.7 KB

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