QualityManager.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. # Copyright (c) 2018 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. from typing import TYPE_CHECKING, Optional, cast, Dict, List, Set
  4. from PyQt5.QtCore import QObject, QTimer, pyqtSignal, pyqtSlot
  5. from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
  6. from UM.Logger import Logger
  7. from UM.Util import parseBool
  8. from UM.Settings.InstanceContainer import InstanceContainer
  9. from cura.Settings.ExtruderStack import ExtruderStack
  10. from cura.Machines.ContainerTree import ContainerTree
  11. from .QualityGroup import QualityGroup
  12. from .QualityNode import QualityNode
  13. if TYPE_CHECKING:
  14. from UM.Settings.Interfaces import DefinitionContainerInterface
  15. from cura.Settings.GlobalStack import GlobalStack
  16. from .QualityChangesGroup import QualityChangesGroup
  17. from cura.CuraApplication import CuraApplication
  18. #
  19. # Similar to MaterialManager, QualityManager maintains a number of maps and trees for quality profile lookup.
  20. # The models GUI and QML use are now only dependent on the QualityManager. That means as long as the data in
  21. # QualityManager gets updated correctly, the GUI models should be updated correctly too, and the same goes for GUI.
  22. #
  23. # For now, updating the lookup maps and trees here is very simple: we discard the old data completely and recreate them
  24. # again. This means the update is exactly the same as initialization. There are performance concerns about this approach
  25. # but so far the creation of the tables and maps is very fast and there is no noticeable slowness, we keep it like this
  26. # because it's simple.
  27. #
  28. class QualityManager(QObject):
  29. qualitiesUpdated = pyqtSignal()
  30. def __init__(self, application: "CuraApplication", parent = None) -> None:
  31. super().__init__(parent)
  32. self._application = application
  33. self._material_manager = self._application.getMaterialManager()
  34. self._container_registry = self._application.getContainerRegistry()
  35. self._empty_quality_container = self._application.empty_quality_container
  36. self._empty_quality_changes_container = self._application.empty_quality_changes_container
  37. # For quality lookup
  38. self._machine_nozzle_buildplate_material_quality_type_to_quality_dict = {} # type: Dict[str, QualityNode]
  39. # For quality_changes lookup
  40. self._machine_quality_type_to_quality_changes_dict = {} # type: Dict[str, QualityNode]
  41. self._default_machine_definition_id = "fdmprinter"
  42. self._container_registry.containerMetaDataChanged.connect(self._onContainerMetadataChanged)
  43. self._container_registry.containerAdded.connect(self._onContainerMetadataChanged)
  44. self._container_registry.containerRemoved.connect(self._onContainerMetadataChanged)
  45. # When a custom quality gets added/imported, there can be more than one InstanceContainers. In those cases,
  46. # we don't want to react on every container/metadata changed signal. The timer here is to buffer it a bit so
  47. # we don't react too many time.
  48. self._update_timer = QTimer(self)
  49. self._update_timer.setInterval(300)
  50. self._update_timer.setSingleShot(True)
  51. self._update_timer.timeout.connect(self._updateMaps)
  52. def initialize(self) -> None:
  53. container_tree = ContainerTree()
  54. for machine_id, machine in container_tree.machines.items():
  55. print("--", machine_id)
  56. for variant_name, variant in machine.variants.items():
  57. print("-- --", variant_name)
  58. for material_base_file, material in variant.materials.items():
  59. print("-- -- --", material_base_file)
  60. for quality_id, quality in material.qualities.items():
  61. print("-- -- -- --", quality_id)
  62. for intent_id in quality.intents:
  63. print("-- -- -- -- --", intent_id)
  64. # Initialize the lookup tree for quality profiles with following structure:
  65. # <machine> -> <nozzle> -> <buildplate> -> <material>
  66. # <machine> -> <material>
  67. self._machine_nozzle_buildplate_material_quality_type_to_quality_dict = {} # for quality lookup
  68. self._machine_quality_type_to_quality_changes_dict = {} # for quality_changes lookup
  69. quality_metadata_list = self._container_registry.findContainersMetadata(type = "quality")
  70. for metadata in quality_metadata_list:
  71. if metadata["id"] == "empty_quality":
  72. continue
  73. definition_id = metadata["definition"]
  74. quality_type = metadata["quality_type"]
  75. root_material_id = metadata.get("material")
  76. nozzle_name = metadata.get("variant")
  77. buildplate_name = metadata.get("buildplate")
  78. is_global_quality = metadata.get("global_quality", False)
  79. is_global_quality = is_global_quality or (root_material_id is None and nozzle_name is None and buildplate_name is None)
  80. # Sanity check: material+variant and is_global_quality cannot be present at the same time
  81. if is_global_quality and (root_material_id or nozzle_name):
  82. ConfigurationErrorMessage.getInstance().addFaultyContainers(metadata["id"])
  83. continue
  84. if definition_id not in self._machine_nozzle_buildplate_material_quality_type_to_quality_dict:
  85. self._machine_nozzle_buildplate_material_quality_type_to_quality_dict[definition_id] = QualityNode()
  86. machine_node = cast(QualityNode, self._machine_nozzle_buildplate_material_quality_type_to_quality_dict[definition_id])
  87. if is_global_quality:
  88. # For global qualities, save data in the machine node
  89. machine_node.addQualityMetadata(quality_type, metadata)
  90. continue
  91. current_node = machine_node
  92. intermediate_node_info_list = [nozzle_name, buildplate_name, root_material_id]
  93. current_intermediate_node_info_idx = 0
  94. while current_intermediate_node_info_idx < len(intermediate_node_info_list):
  95. node_name = intermediate_node_info_list[current_intermediate_node_info_idx]
  96. if node_name is not None:
  97. # There is specific information, update the current node to go deeper so we can add this quality
  98. # at the most specific branch in the lookup tree.
  99. if node_name not in current_node.children_map:
  100. current_node.children_map[node_name] = QualityNode()
  101. current_node = cast(QualityNode, current_node.children_map[node_name])
  102. current_intermediate_node_info_idx += 1
  103. current_node.addQualityMetadata(quality_type, metadata)
  104. # Initialize the lookup tree for quality_changes profiles with following structure:
  105. # <machine> -> <quality_type> -> <name>
  106. quality_changes_metadata_list = self._container_registry.findContainersMetadata(type = "quality_changes")
  107. for metadata in quality_changes_metadata_list:
  108. if metadata["id"] == "empty_quality_changes":
  109. continue
  110. machine_definition_id = metadata["definition"]
  111. quality_type = metadata["quality_type"]
  112. if machine_definition_id not in self._machine_quality_type_to_quality_changes_dict:
  113. self._machine_quality_type_to_quality_changes_dict[machine_definition_id] = QualityNode()
  114. machine_node = self._machine_quality_type_to_quality_changes_dict[machine_definition_id]
  115. machine_node.addQualityChangesMetadata(quality_type, metadata)
  116. Logger.log("d", "Lookup tables updated.")
  117. self.qualitiesUpdated.emit()
  118. def _updateMaps(self) -> None:
  119. self.initialize()
  120. def _onContainerMetadataChanged(self, container: InstanceContainer) -> None:
  121. self._onContainerChanged(container)
  122. def _onContainerChanged(self, container: InstanceContainer) -> None:
  123. container_type = container.getMetaDataEntry("type")
  124. if container_type not in ("quality", "quality_changes"):
  125. return
  126. # update the cache table
  127. self._update_timer.start()
  128. # Updates the given quality groups' availabilities according to which extruders are being used/ enabled.
  129. def _updateQualityGroupsAvailability(self, machine: "GlobalStack", quality_group_list) -> None:
  130. used_extruders = set()
  131. for i in range(machine.getProperty("machine_extruder_count", "value")):
  132. if str(i) in machine.extruders and machine.extruders[str(i)].isEnabled:
  133. used_extruders.add(str(i))
  134. # Update the "is_available" flag for each quality group.
  135. for quality_group in quality_group_list:
  136. is_available = True
  137. if quality_group.node_for_global is None:
  138. is_available = False
  139. if is_available:
  140. for position in used_extruders:
  141. if position not in quality_group.nodes_for_extruders:
  142. is_available = False
  143. break
  144. quality_group.is_available = is_available
  145. # Returns a dict of "custom profile name" -> QualityChangesGroup
  146. def getQualityChangesGroups(self, machine: "GlobalStack") -> dict:
  147. machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
  148. machine_node = self._machine_quality_type_to_quality_changes_dict.get(machine_definition_id)
  149. if not machine_node:
  150. Logger.log("i", "Cannot find node for machine def [%s] in QualityChanges lookup table", machine_definition_id)
  151. return dict()
  152. # Update availability for each QualityChangesGroup:
  153. # A custom profile is always available as long as the quality_type it's based on is available
  154. quality_group_dict = self.getQualityGroups(machine)
  155. available_quality_type_list = [qt for qt, qg in quality_group_dict.items() if qg.is_available]
  156. # Iterate over all quality_types in the machine node
  157. quality_changes_group_dict = dict()
  158. for quality_type, quality_changes_node in machine_node.quality_type_map.items():
  159. for quality_changes_name, quality_changes_group in quality_changes_node.children_map.items():
  160. quality_changes_group_dict[quality_changes_name] = quality_changes_group
  161. quality_changes_group.is_available = quality_type in available_quality_type_list
  162. return quality_changes_group_dict
  163. #
  164. # Gets all quality groups for the given machine. Both available and unavailable ones will be included.
  165. # It returns a dictionary with "quality_type"s as keys and "QualityGroup"s as values.
  166. # Whether a QualityGroup is available can be known via the field QualityGroup.is_available.
  167. # For more details, see QualityGroup.
  168. #
  169. def getQualityGroups(self, machine: "GlobalStack") -> Dict[str, QualityGroup]:
  170. machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
  171. # To find the quality container for the GlobalStack, check in the following fall-back manner:
  172. # (1) the machine-specific node
  173. # (2) the generic node
  174. machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get(machine_definition_id)
  175. # Check if this machine has specific quality profiles for its extruders, if so, when looking up extruder
  176. # qualities, we should not fall back to use the global qualities.
  177. has_extruder_specific_qualities = False
  178. if machine_node:
  179. if machine_node.children_map:
  180. has_extruder_specific_qualities = True
  181. default_machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get(self._default_machine_definition_id)
  182. nodes_to_check = [] # type: List[QualityNode]
  183. if machine_node is not None:
  184. nodes_to_check.append(machine_node)
  185. if default_machine_node is not None:
  186. nodes_to_check.append(default_machine_node)
  187. # Iterate over all quality_types in the machine node
  188. quality_group_dict = {}
  189. for node in nodes_to_check:
  190. if node and node.quality_type_map:
  191. quality_node = list(node.quality_type_map.values())[0]
  192. is_global_quality = parseBool(quality_node.getMetaDataEntry("global_quality", False))
  193. if not is_global_quality:
  194. continue
  195. for quality_type, quality_node in node.quality_type_map.items():
  196. quality_group = QualityGroup(quality_node.getMetaDataEntry("name", ""), quality_type)
  197. quality_group.setGlobalNode(quality_node)
  198. quality_group_dict[quality_type] = quality_group
  199. break
  200. buildplate_name = machine.getBuildplateName()
  201. # Iterate over all extruders to find quality containers for each extruder
  202. for position, extruder in machine.extruders.items():
  203. nozzle_name = None
  204. if extruder.variant.getId() != "empty_variant":
  205. nozzle_name = extruder.variant.getName()
  206. # This is a list of root material IDs to use for searching for suitable quality profiles.
  207. # The root material IDs in this list are in prioritized order.
  208. root_material_id_list = []
  209. has_material = False # flag indicating whether this extruder has a material assigned
  210. root_material_id = None
  211. if extruder.material.getId() != "empty_material":
  212. has_material = True
  213. root_material_id = extruder.material.getMetaDataEntry("base_file")
  214. # Convert possible generic_pla_175 -> generic_pla
  215. root_material_id = self._material_manager.getRootMaterialIDWithoutDiameter(root_material_id)
  216. root_material_id_list.append(root_material_id)
  217. # Also try to get the fallback materials
  218. fallback_ids = self._material_manager.getFallBackMaterialIdsByMaterial(extruder.material)
  219. if fallback_ids:
  220. root_material_id_list.extend(fallback_ids)
  221. # Weed out duplicates while preserving the order.
  222. seen = set() # type: Set[str]
  223. root_material_id_list = [x for x in root_material_id_list if x not in seen and not seen.add(x)] # type: ignore
  224. # Here we construct a list of nodes we want to look for qualities with the highest priority first.
  225. # The use case is that, when we look for qualities for a machine, we first want to search in the following
  226. # order:
  227. # 1. machine-nozzle-buildplate-and-material-specific qualities if exist
  228. # 2. machine-nozzle-and-material-specific qualities if exist
  229. # 3. machine-nozzle-specific qualities if exist
  230. # 4. machine-material-specific qualities if exist
  231. # 5. machine-specific global qualities if exist, otherwise generic global qualities
  232. # NOTE: We DO NOT fail back to generic global qualities if machine-specific global qualities exist.
  233. # This is because when a machine defines its own global qualities such as Normal, Fine, etc.,
  234. # it is intended to maintain those specific qualities ONLY. If we still fail back to the generic
  235. # global qualities, there can be unimplemented quality types e.g. "coarse", and this is not
  236. # correct.
  237. # Each points above can be represented as a node in the lookup tree, so here we simply put those nodes into
  238. # the list with priorities as the order. Later, we just need to loop over each node in this list and fetch
  239. # qualities from there.
  240. node_info_list_0 = [nozzle_name, buildplate_name, root_material_id] # type: List[Optional[str]]
  241. nodes_to_check = []
  242. # This function tries to recursively find the deepest (the most specific) branch and add those nodes to
  243. # the search list in the order described above. So, by iterating over that search node list, we first look
  244. # in the more specific branches and then the less specific (generic) ones.
  245. def addNodesToCheck(node: Optional[QualityNode], nodes_to_check_list: List[QualityNode], node_info_list, node_info_idx: int) -> None:
  246. if node is None:
  247. return
  248. if node_info_idx < len(node_info_list):
  249. node_name = node_info_list[node_info_idx]
  250. if node_name is not None:
  251. current_node = node.getChildNode(node_name)
  252. if current_node is not None and has_material:
  253. addNodesToCheck(current_node, nodes_to_check_list, node_info_list, node_info_idx + 1)
  254. if has_material:
  255. for rmid in root_material_id_list:
  256. material_node = node.getChildNode(rmid)
  257. if material_node:
  258. nodes_to_check_list.append(material_node)
  259. break
  260. nodes_to_check_list.append(node)
  261. addNodesToCheck(machine_node, nodes_to_check, node_info_list_0, 0)
  262. # The last fall back will be the global qualities (either from the machine-specific node or the generic
  263. # node), but we only use one. For details see the overview comments above.
  264. if machine_node is not None and machine_node.quality_type_map:
  265. nodes_to_check += [machine_node]
  266. elif default_machine_node is not None:
  267. nodes_to_check += [default_machine_node]
  268. for node_idx, node in enumerate(nodes_to_check):
  269. if node and node.quality_type_map:
  270. if has_extruder_specific_qualities:
  271. # Only include variant qualities; skip non global qualities
  272. quality_node = list(node.quality_type_map.values())[0]
  273. is_global_quality = parseBool(quality_node.getMetaDataEntry("global_quality", False))
  274. if is_global_quality:
  275. continue
  276. for quality_type, quality_node in node.quality_type_map.items():
  277. if quality_type not in quality_group_dict:
  278. quality_group = QualityGroup(quality_node.getMetaDataEntry("name", ""), quality_type)
  279. quality_group_dict[quality_type] = quality_group
  280. quality_group = quality_group_dict[quality_type]
  281. if position not in quality_group.nodes_for_extruders:
  282. quality_group.setExtruderNode(position, quality_node)
  283. # If the machine has its own specific qualities, for extruders, it should skip the global qualities
  284. # and use the material/variant specific qualities.
  285. if has_extruder_specific_qualities:
  286. if node_idx == len(nodes_to_check) - 1:
  287. break
  288. # Update availabilities for each quality group
  289. self._updateQualityGroupsAvailability(machine, quality_group_dict.values())
  290. return quality_group_dict
  291. def getQualityGroupsForMachineDefinition(self, machine: "GlobalStack") -> Dict[str, QualityGroup]:
  292. machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
  293. # To find the quality container for the GlobalStack, check in the following fall-back manner:
  294. # (1) the machine-specific node
  295. # (2) the generic node
  296. machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get(machine_definition_id)
  297. default_machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get(
  298. self._default_machine_definition_id)
  299. nodes_to_check = [machine_node, default_machine_node]
  300. # Iterate over all quality_types in the machine node
  301. quality_group_dict = dict()
  302. for node in nodes_to_check:
  303. if node and node.quality_type_map:
  304. for quality_type, quality_node in node.quality_type_map.items():
  305. quality_group = QualityGroup(quality_node.getMetaDataEntry("name", ""), quality_type)
  306. quality_group.setGlobalNode(quality_node)
  307. quality_group_dict[quality_type] = quality_group
  308. break
  309. return quality_group_dict
  310. def getDefaultQualityType(self, machine: "GlobalStack") -> Optional[QualityGroup]:
  311. preferred_quality_type = machine.definition.getMetaDataEntry("preferred_quality_type")
  312. quality_group_dict = self.getQualityGroups(machine)
  313. quality_group = quality_group_dict.get(preferred_quality_type)
  314. return quality_group
  315. #
  316. # Methods for GUI
  317. #
  318. #
  319. # Remove the given quality changes group.
  320. #
  321. @pyqtSlot(QObject)
  322. def removeQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup") -> None:
  323. Logger.log("i", "Removing quality changes group [%s]", quality_changes_group.name)
  324. removed_quality_changes_ids = set()
  325. for node in quality_changes_group.getAllNodes():
  326. container_id = node.getMetaDataEntry("id")
  327. self._container_registry.removeContainer(container_id)
  328. removed_quality_changes_ids.add(container_id)
  329. # Reset all machines that have activated this quality changes to empty.
  330. for global_stack in self._container_registry.findContainerStacks(type = "machine"):
  331. if global_stack.qualityChanges.getId() in removed_quality_changes_ids:
  332. global_stack.qualityChanges = self._empty_quality_changes_container
  333. for extruder_stack in self._container_registry.findContainerStacks(type = "extruder_train"):
  334. if extruder_stack.qualityChanges.getId() in removed_quality_changes_ids:
  335. extruder_stack.qualityChanges = self._empty_quality_changes_container
  336. #
  337. # Rename a set of quality changes containers. Returns the new name.
  338. #
  339. @pyqtSlot(QObject, str, result = str)
  340. def renameQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup", new_name: str) -> str:
  341. Logger.log("i", "Renaming QualityChangesGroup[%s] to [%s]", quality_changes_group.name, new_name)
  342. if new_name == quality_changes_group.name:
  343. Logger.log("i", "QualityChangesGroup name [%s] unchanged.", quality_changes_group.name)
  344. return new_name
  345. new_name = self._container_registry.uniqueName(new_name)
  346. for node in quality_changes_group.getAllNodes():
  347. container = node.getContainer()
  348. if container:
  349. container.setName(new_name)
  350. quality_changes_group.name = new_name
  351. self._application.getMachineManager().activeQualityChanged.emit()
  352. self._application.getMachineManager().activeQualityGroupChanged.emit()
  353. return new_name
  354. #
  355. # Duplicates the given quality.
  356. #
  357. @pyqtSlot(str, "QVariantMap")
  358. def duplicateQualityChanges(self, quality_changes_name: str, quality_model_item) -> None:
  359. global_stack = self._application.getGlobalContainerStack()
  360. if not global_stack:
  361. Logger.log("i", "No active global stack, cannot duplicate quality changes.")
  362. return
  363. quality_group = quality_model_item["quality_group"]
  364. quality_changes_group = quality_model_item["quality_changes_group"]
  365. if quality_changes_group is None:
  366. # create global quality changes only
  367. new_name = self._container_registry.uniqueName(quality_changes_name)
  368. new_quality_changes = self._createQualityChanges(quality_group.quality_type, new_name,
  369. global_stack, None)
  370. self._container_registry.addContainer(new_quality_changes)
  371. else:
  372. new_name = self._container_registry.uniqueName(quality_changes_name)
  373. for node in quality_changes_group.getAllNodes():
  374. container = node.getContainer()
  375. if not container:
  376. continue
  377. new_id = self._container_registry.uniqueName(container.getId())
  378. self._container_registry.addContainer(container.duplicate(new_id, new_name))
  379. ## Create quality changes containers from the user containers in the active stacks.
  380. #
  381. # This will go through the global and extruder stacks and create quality_changes containers from
  382. # the user containers in each stack. These then replace the quality_changes containers in the
  383. # stack and clear the user settings.
  384. @pyqtSlot(str)
  385. def createQualityChanges(self, base_name: str) -> None:
  386. machine_manager = self._application.getMachineManager()
  387. global_stack = machine_manager.activeMachine
  388. if not global_stack:
  389. return
  390. active_quality_name = machine_manager.activeQualityOrQualityChangesName
  391. if active_quality_name == "":
  392. Logger.log("w", "No quality container found in stack %s, cannot create profile", global_stack.getId())
  393. return
  394. machine_manager.blurSettings.emit()
  395. if base_name is None or base_name == "":
  396. base_name = active_quality_name
  397. unique_name = self._container_registry.uniqueName(base_name)
  398. # Go through the active stacks and create quality_changes containers from the user containers.
  399. stack_list = [global_stack] + list(global_stack.extruders.values())
  400. for stack in stack_list:
  401. user_container = stack.userChanges
  402. quality_container = stack.quality
  403. quality_changes_container = stack.qualityChanges
  404. if not quality_container or not quality_changes_container:
  405. Logger.log("w", "No quality or quality changes container found in stack %s, ignoring it", stack.getId())
  406. continue
  407. quality_type = quality_container.getMetaDataEntry("quality_type")
  408. extruder_stack = None
  409. if isinstance(stack, ExtruderStack):
  410. extruder_stack = stack
  411. new_changes = self._createQualityChanges(quality_type, unique_name, global_stack, extruder_stack)
  412. from cura.Settings.ContainerManager import ContainerManager
  413. ContainerManager.getInstance()._performMerge(new_changes, quality_changes_container, clear_settings = False)
  414. ContainerManager.getInstance()._performMerge(new_changes, user_container)
  415. self._container_registry.addContainer(new_changes)
  416. #
  417. # Create a quality changes container with the given setup.
  418. #
  419. def _createQualityChanges(self, quality_type: str, new_name: str, machine: "GlobalStack",
  420. extruder_stack: Optional["ExtruderStack"]) -> "InstanceContainer":
  421. base_id = machine.definition.getId() if extruder_stack is None else extruder_stack.getId()
  422. new_id = base_id + "_" + new_name
  423. new_id = new_id.lower().replace(" ", "_")
  424. new_id = self._container_registry.uniqueName(new_id)
  425. # Create a new quality_changes container for the quality.
  426. quality_changes = InstanceContainer(new_id)
  427. quality_changes.setName(new_name)
  428. quality_changes.setMetaDataEntry("type", "quality_changes")
  429. quality_changes.setMetaDataEntry("quality_type", quality_type)
  430. # If we are creating a container for an extruder, ensure we add that to the container
  431. if extruder_stack is not None:
  432. quality_changes.setMetaDataEntry("position", extruder_stack.getMetaDataEntry("position"))
  433. # If the machine specifies qualities should be filtered, ensure we match the current criteria.
  434. machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
  435. quality_changes.setDefinition(machine_definition_id)
  436. quality_changes.setMetaDataEntry("setting_version", self._application.SettingVersion)
  437. return quality_changes
  438. #
  439. # Gets the machine definition ID that can be used to search for Quality containers that are suitable for the given
  440. # machine. The rule is as follows:
  441. # 1. By default, the machine definition ID for quality container search will be "fdmprinter", which is the generic
  442. # machine.
  443. # 2. If a machine has its own machine quality (with "has_machine_quality = True"), we should use the given machine's
  444. # own machine definition ID for quality search.
  445. # Example: for an Ultimaker 3, the definition ID should be "ultimaker3".
  446. # 3. When condition (2) is met, AND the machine has "quality_definition" defined in its definition file, then the
  447. # definition ID specified in "quality_definition" should be used.
  448. # Example: for an Ultimaker 3 Extended, it has "quality_definition = ultimaker3". This means Ultimaker 3 Extended
  449. # shares the same set of qualities profiles as Ultimaker 3.
  450. #
  451. def getMachineDefinitionIDForQualitySearch(machine_definition: "DefinitionContainerInterface",
  452. default_definition_id: str = "fdmprinter") -> str:
  453. machine_definition_id = default_definition_id
  454. if parseBool(machine_definition.getMetaDataEntry("has_machine_quality", False)):
  455. # Only use the machine's own quality definition ID if this machine has machine quality.
  456. machine_definition_id = machine_definition.getMetaDataEntry("quality_definition")
  457. if machine_definition_id is None:
  458. machine_definition_id = machine_definition.getId()
  459. return machine_definition_id