QualityManagementModel.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. # Copyright (c) 2019 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. from typing import Any, Dict, Optional, TYPE_CHECKING
  4. from PyQt5.QtCore import pyqtSlot, QObject, Qt
  5. from UM.Logger import Logger
  6. from UM.Qt.ListModel import ListModel
  7. from UM.Settings.InstanceContainer import InstanceContainer # To create new profiles.
  8. import cura.CuraApplication # Imported this way to prevent circular imports.
  9. from cura.Machines.ContainerTree import ContainerTree
  10. from cura.Settings.cura_empty_instance_containers import empty_quality_changes_container
  11. if TYPE_CHECKING:
  12. from UM.Settings.Interfaces import ContainerInterface
  13. from cura.Machines.QualityChangesGroup import QualityChangesGroup
  14. from cura.Settings.ExtruderStack import ExtruderStack
  15. from cura.Settings.GlobalStack import GlobalStack
  16. #
  17. # This the QML model for the quality management page.
  18. #
  19. class QualityManagementModel(ListModel):
  20. NameRole = Qt.UserRole + 1
  21. IsReadOnlyRole = Qt.UserRole + 2
  22. QualityGroupRole = Qt.UserRole + 3
  23. QualityChangesGroupRole = Qt.UserRole + 4
  24. def __init__(self, parent = None):
  25. super().__init__(parent)
  26. self.addRoleName(self.NameRole, "name")
  27. self.addRoleName(self.IsReadOnlyRole, "is_read_only")
  28. self.addRoleName(self.QualityGroupRole, "quality_group")
  29. self.addRoleName(self.QualityChangesGroupRole, "quality_changes_group")
  30. application = cura.CuraApplication.CuraApplication.getInstance()
  31. container_registry = application.getContainerRegistry()
  32. self._machine_manager = application.getMachineManager()
  33. self._extruder_manager = application.getExtruderManager()
  34. self._machine_manager.globalContainerChanged.connect(self._update)
  35. container_registry.containerAdded.connect(self._qualityChangesListChanged)
  36. container_registry.containerRemoved.connect(self._qualityChangesListChanged)
  37. container_registry.containerMetaDataChanged.connect(self._qualityChangesListChanged)
  38. self._update()
  39. ## Deletes a custom profile. It will be gone forever.
  40. # \param quality_changes_group The quality changes group representing the
  41. # profile to delete.
  42. @pyqtSlot(QObject)
  43. def removeQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup") -> None:
  44. Logger.log("i", "Removing quality changes group {group_name}".format(group_name = quality_changes_group.name))
  45. removed_quality_changes_ids = set()
  46. container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry()
  47. for metadata in [quality_changes_group.metadata_for_global] + list(quality_changes_group.metadata_per_extruder.values()):
  48. container_id = metadata["id"]
  49. container_registry.removeContainer(container_id)
  50. removed_quality_changes_ids.add(container_id)
  51. # Reset all machines that have activated this custom profile.
  52. for global_stack in container_registry.findContainerStacks(type = "machine"):
  53. if global_stack.qualityChanges.getId() in removed_quality_changes_ids:
  54. global_stack.qualityChanges = empty_quality_changes_container
  55. for extruder_stack in container_registry.findContainerStacks(type = "extruder_train"):
  56. if extruder_stack.qualityChanges.getId() in removed_quality_changes_ids:
  57. extruder_stack.qualityChanges = empty_quality_changes_container
  58. ## Rename a custom profile.
  59. #
  60. # Because the names must be unique, the new name may not actually become
  61. # the name that was given. The actual name is returned by this function.
  62. # \param quality_changes_group The custom profile that must be renamed.
  63. # \param new_name The desired name for the profile.
  64. # \return The actual new name of the profile, after making the name
  65. # unique.
  66. @pyqtSlot(QObject, str, result = str)
  67. def renameQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup", new_name: str) -> str:
  68. Logger.log("i", "Renaming QualityChangesGroup {old_name} to {new_name}.".format(old_name = quality_changes_group.name, new_name = new_name))
  69. if new_name == quality_changes_group.name:
  70. Logger.log("i", "QualityChangesGroup name {name} unchanged.".format(name = quality_changes_group.name))
  71. return new_name
  72. application = cura.CuraApplication.CuraApplication.getInstance()
  73. new_name = application.getContainerRegistry().uniqueName(new_name)
  74. for node in quality_changes_group.getAllNodes():
  75. container = node.container
  76. if container:
  77. container.setName(new_name)
  78. quality_changes_group.name = new_name
  79. application.getMachineManager().activeQualityChanged.emit()
  80. application.getMachineManager().activeQualityGroupChanged.emit()
  81. return new_name
  82. ## Duplicates a given quality profile OR quality changes profile.
  83. # \param new_name The desired name of the new profile. This will be made
  84. # unique, so it might end up with a different name.
  85. # \param quality_model_item The item of this model to duplicate, as
  86. # dictionary. See the descriptions of the roles of this list model.
  87. @pyqtSlot(str, "QVariantMap")
  88. def duplicateQualityChanges(self, new_name: str, quality_model_item: Dict[str, Any]) -> None:
  89. global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
  90. if not global_stack:
  91. Logger.log("i", "No active global stack, cannot duplicate quality (changes) profile.")
  92. return
  93. container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry()
  94. new_name = container_registry.uniqueName(new_name)
  95. quality_group = quality_model_item["quality_group"]
  96. quality_changes_group = quality_model_item["quality_changes_group"]
  97. if quality_changes_group is None:
  98. # Create global quality changes only.
  99. new_quality_changes = self._createQualityChanges(quality_group.quality_type, new_name, global_stack, extruder_stack = None)
  100. container_registry.addContainer(new_quality_changes)
  101. else:
  102. for metadata in [quality_changes_group.metadata_for_global] + quality_changes_group.metadata_per_extruder.values():
  103. containers = container_registry.findContainers(id = metadata["id"])
  104. if not containers:
  105. continue
  106. container = containers[0]
  107. new_id = container_registry.uniqueName(container.getId())
  108. container_registry.addContainer(container.duplicate(new_id, new_name))
  109. ## Create a quality changes container with the given set-up.
  110. # \param quality_type The quality type of the new container.
  111. # \param new_name The name of the container. This name must be unique.
  112. # \param machine The global stack to create the profile for.
  113. # \param extruder_stack The extruder stack to create the profile for. If
  114. # not provided, only a global container will be created.
  115. def _createQualityChanges(self, quality_type: str, new_name: str, machine: "GlobalStack", extruder_stack: Optional["ExtruderStack"]) -> "InstanceContainer":
  116. container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry()
  117. base_id = machine.definition.getId() if extruder_stack is None else extruder_stack.getId()
  118. new_id = base_id + "_" + new_name
  119. new_id = new_id.lower().replace(" ", "_")
  120. new_id = container_registry.uniqueName(new_id)
  121. # Create a new quality_changes container for the quality.
  122. quality_changes = InstanceContainer(new_id)
  123. quality_changes.setName(new_name)
  124. quality_changes.setMetaDataEntry("type", "quality_changes")
  125. quality_changes.setMetaDataEntry("quality_type", quality_type)
  126. # If we are creating a container for an extruder, ensure we add that to the container.
  127. if extruder_stack is not None:
  128. quality_changes.setMetaDataEntry("position", extruder_stack.getMetaDataEntry("position"))
  129. # If the machine specifies qualities should be filtered, ensure we match the current criteria.
  130. machine_definition_id = ContainerTree.getInstance().machines[machine.definition.getId()].quality_definition
  131. quality_changes.setDefinition(machine_definition_id)
  132. quality_changes.setMetaDataEntry("setting_version", cura.CuraApplication.CuraApplication.getInstance().SettingVersion)
  133. return quality_changes
  134. ## Triggered when any container changed.
  135. #
  136. # This filters the updates to the container manager: When it applies to
  137. # the list of quality changes, we need to update our list.
  138. def _qualityChangesListChanged(self, container: "ContainerInterface") -> None:
  139. if container.getMetaDataEntry("type") == "quality_changes":
  140. self._update()
  141. def _update(self):
  142. Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
  143. global_stack = self._machine_manager.activeMachine
  144. if not global_stack:
  145. self.setItems([])
  146. return
  147. container_tree = ContainerTree.getInstance()
  148. quality_group_dict = container_tree.getCurrentQualityGroups()
  149. quality_changes_group_list = container_tree.getCurrentQualityChangesGroups()
  150. available_quality_types = set(quality_type for quality_type, quality_group in quality_group_dict.items()
  151. if quality_group.is_available)
  152. if not available_quality_types and not quality_changes_group_list:
  153. # Nothing to show
  154. self.setItems([])
  155. return
  156. item_list = []
  157. # Create quality group items
  158. for quality_group in quality_group_dict.values():
  159. if not quality_group.is_available:
  160. continue
  161. item = {"name": quality_group.name,
  162. "is_read_only": True,
  163. "quality_group": quality_group,
  164. "quality_changes_group": None}
  165. item_list.append(item)
  166. # Sort by quality names
  167. item_list = sorted(item_list, key = lambda x: x["name"].upper())
  168. # Create quality_changes group items
  169. quality_changes_item_list = []
  170. for quality_changes_group in quality_changes_group_list:
  171. quality_group = quality_group_dict.get(quality_changes_group.quality_type)
  172. item = {"name": quality_changes_group.name,
  173. "is_read_only": False,
  174. "quality_group": quality_group,
  175. "quality_changes_group": quality_changes_group}
  176. quality_changes_item_list.append(item)
  177. # Sort quality_changes items by names and append to the item list
  178. quality_changes_item_list = sorted(quality_changes_item_list, key = lambda x: x["name"].upper())
  179. item_list += quality_changes_item_list
  180. self.setItems(item_list)
  181. # TODO: Duplicated code here from InstanceContainersModel. Refactor and remove this later.
  182. #
  183. ## Gets a list of the possible file filters that the plugins have
  184. # registered they can read or write. The convenience meta-filters
  185. # "All Supported Types" and "All Files" are added when listing
  186. # readers, but not when listing writers.
  187. #
  188. # \param io_type \type{str} name of the needed IO type
  189. # \return A list of strings indicating file name filters for a file
  190. # dialog.
  191. @pyqtSlot(str, result = "QVariantList")
  192. def getFileNameFilters(self, io_type):
  193. from UM.i18n import i18nCatalog
  194. catalog = i18nCatalog("uranium")
  195. #TODO: This function should be in UM.Resources!
  196. filters = []
  197. all_types = []
  198. for plugin_id, meta_data in self._getIOPlugins(io_type):
  199. for io_plugin in meta_data[io_type]:
  200. filters.append(io_plugin["description"] + " (*." + io_plugin["extension"] + ")")
  201. all_types.append("*.{0}".format(io_plugin["extension"]))
  202. if "_reader" in io_type:
  203. # if we're listing readers, add the option to show all supported files as the default option
  204. filters.insert(0, catalog.i18nc("@item:inlistbox", "All Supported Types ({0})", " ".join(all_types)))
  205. filters.append(catalog.i18nc("@item:inlistbox", "All Files (*)")) # Also allow arbitrary files, if the user so prefers.
  206. return filters
  207. ## Gets a list of profile reader or writer plugins
  208. # \return List of tuples of (plugin_id, meta_data).
  209. def _getIOPlugins(self, io_type):
  210. from UM.PluginRegistry import PluginRegistry
  211. pr = PluginRegistry.getInstance()
  212. active_plugin_ids = pr.getActivePlugins()
  213. result = []
  214. for plugin_id in active_plugin_ids:
  215. meta_data = pr.getMetaData(plugin_id)
  216. if io_type in meta_data:
  217. result.append( (plugin_id, meta_data) )
  218. return result