# Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from typing import Any, Dict, Optional, TYPE_CHECKING from PyQt5.QtCore import pyqtSlot, QObject, Qt from UM.Logger import Logger from UM.Qt.ListModel import ListModel from UM.Settings.InstanceContainer import InstanceContainer # To create new profiles. import cura.CuraApplication # Imported this way to prevent circular imports. from cura.Machines.ContainerTree import ContainerTree from cura.Settings.cura_empty_instance_containers import empty_quality_changes_container if TYPE_CHECKING: from UM.Settings.Interfaces import ContainerInterface from cura.Machines.QualityChangesGroup import QualityChangesGroup from cura.Settings.ExtruderStack import ExtruderStack from cura.Settings.GlobalStack import GlobalStack # # This the QML model for the quality management page. # class QualityManagementModel(ListModel): NameRole = Qt.UserRole + 1 IsReadOnlyRole = Qt.UserRole + 2 QualityGroupRole = Qt.UserRole + 3 QualityChangesGroupRole = Qt.UserRole + 4 def __init__(self, parent = None): super().__init__(parent) self.addRoleName(self.NameRole, "name") self.addRoleName(self.IsReadOnlyRole, "is_read_only") self.addRoleName(self.QualityGroupRole, "quality_group") self.addRoleName(self.QualityChangesGroupRole, "quality_changes_group") application = cura.CuraApplication.CuraApplication.getInstance() container_registry = application.getContainerRegistry() self._machine_manager = application.getMachineManager() self._extruder_manager = application.getExtruderManager() self._machine_manager.globalContainerChanged.connect(self._update) container_registry.containerAdded.connect(self._qualityChangesListChanged) container_registry.containerRemoved.connect(self._qualityChangesListChanged) container_registry.containerMetaDataChanged.connect(self._qualityChangesListChanged) self._update() ## Deletes a custom profile. It will be gone forever. # \param quality_changes_group The quality changes group representing the # profile to delete. @pyqtSlot(QObject) def removeQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup") -> None: Logger.log("i", "Removing quality changes group {group_name}".format(group_name = quality_changes_group.name)) removed_quality_changes_ids = set() container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry() for metadata in [quality_changes_group.metadata_for_global] + list(quality_changes_group.metadata_per_extruder.values()): container_id = metadata["id"] container_registry.removeContainer(container_id) removed_quality_changes_ids.add(container_id) # Reset all machines that have activated this custom profile. for global_stack in container_registry.findContainerStacks(type = "machine"): if global_stack.qualityChanges.getId() in removed_quality_changes_ids: global_stack.qualityChanges = empty_quality_changes_container for extruder_stack in container_registry.findContainerStacks(type = "extruder_train"): if extruder_stack.qualityChanges.getId() in removed_quality_changes_ids: extruder_stack.qualityChanges = empty_quality_changes_container ## Rename a custom profile. # # Because the names must be unique, the new name may not actually become # the name that was given. The actual name is returned by this function. # \param quality_changes_group The custom profile that must be renamed. # \param new_name The desired name for the profile. # \return The actual new name of the profile, after making the name # unique. @pyqtSlot(QObject, str, result = str) def renameQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup", new_name: str) -> str: Logger.log("i", "Renaming QualityChangesGroup {old_name} to {new_name}.".format(old_name = quality_changes_group.name, new_name = new_name)) if new_name == quality_changes_group.name: Logger.log("i", "QualityChangesGroup name {name} unchanged.".format(name = quality_changes_group.name)) return new_name application = cura.CuraApplication.CuraApplication.getInstance() new_name = application.getContainerRegistry().uniqueName(new_name) for node in quality_changes_group.getAllNodes(): container = node.container if container: container.setName(new_name) quality_changes_group.name = new_name application.getMachineManager().activeQualityChanged.emit() application.getMachineManager().activeQualityGroupChanged.emit() return new_name ## Duplicates a given quality profile OR quality changes profile. # \param new_name The desired name of the new profile. This will be made # unique, so it might end up with a different name. # \param quality_model_item The item of this model to duplicate, as # dictionary. See the descriptions of the roles of this list model. @pyqtSlot(str, "QVariantMap") def duplicateQualityChanges(self, new_name: str, quality_model_item: Dict[str, Any]) -> None: global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() if not global_stack: Logger.log("i", "No active global stack, cannot duplicate quality (changes) profile.") return container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry() new_name = container_registry.uniqueName(new_name) quality_group = quality_model_item["quality_group"] quality_changes_group = quality_model_item["quality_changes_group"] if quality_changes_group is None: # Create global quality changes only. new_quality_changes = self._createQualityChanges(quality_group.quality_type, new_name, global_stack, extruder_stack = None) container_registry.addContainer(new_quality_changes) else: for metadata in [quality_changes_group.metadata_for_global] + quality_changes_group.metadata_per_extruder.values(): containers = container_registry.findContainers(id = metadata["id"]) if not containers: continue container = containers[0] new_id = container_registry.uniqueName(container.getId()) container_registry.addContainer(container.duplicate(new_id, new_name)) ## Create a quality changes container with the given set-up. # \param quality_type The quality type of the new container. # \param new_name The name of the container. This name must be unique. # \param machine The global stack to create the profile for. # \param extruder_stack The extruder stack to create the profile for. If # not provided, only a global container will be created. def _createQualityChanges(self, quality_type: str, new_name: str, machine: "GlobalStack", extruder_stack: Optional["ExtruderStack"]) -> "InstanceContainer": container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry() base_id = machine.definition.getId() if extruder_stack is None else extruder_stack.getId() new_id = base_id + "_" + new_name new_id = new_id.lower().replace(" ", "_") new_id = container_registry.uniqueName(new_id) # Create a new quality_changes container for the quality. quality_changes = InstanceContainer(new_id) quality_changes.setName(new_name) quality_changes.setMetaDataEntry("type", "quality_changes") quality_changes.setMetaDataEntry("quality_type", quality_type) # If we are creating a container for an extruder, ensure we add that to the container. if extruder_stack is not None: quality_changes.setMetaDataEntry("position", extruder_stack.getMetaDataEntry("position")) # If the machine specifies qualities should be filtered, ensure we match the current criteria. machine_definition_id = ContainerTree.getInstance().machines[machine.definition.getId()].quality_definition quality_changes.setDefinition(machine_definition_id) quality_changes.setMetaDataEntry("setting_version", cura.CuraApplication.CuraApplication.getInstance().SettingVersion) return quality_changes ## Triggered when any container changed. # # This filters the updates to the container manager: When it applies to # the list of quality changes, we need to update our list. def _qualityChangesListChanged(self, container: "ContainerInterface") -> None: if container.getMetaDataEntry("type") == "quality_changes": self._update() def _update(self): Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__)) global_stack = self._machine_manager.activeMachine if not global_stack: self.setItems([]) return container_tree = ContainerTree.getInstance() quality_group_dict = container_tree.getCurrentQualityGroups() quality_changes_group_list = container_tree.getCurrentQualityChangesGroups() available_quality_types = set(quality_type for quality_type, quality_group in quality_group_dict.items() if quality_group.is_available) if not available_quality_types and not quality_changes_group_list: # Nothing to show self.setItems([]) return item_list = [] # Create quality group items for quality_group in quality_group_dict.values(): if not quality_group.is_available: continue item = {"name": quality_group.name, "is_read_only": True, "quality_group": quality_group, "quality_changes_group": None} item_list.append(item) # Sort by quality names item_list = sorted(item_list, key = lambda x: x["name"].upper()) # Create quality_changes group items quality_changes_item_list = [] for quality_changes_group in quality_changes_group_list: quality_group = quality_group_dict.get(quality_changes_group.quality_type) item = {"name": quality_changes_group.name, "is_read_only": False, "quality_group": quality_group, "quality_changes_group": quality_changes_group} quality_changes_item_list.append(item) # Sort quality_changes items by names and append to the item list quality_changes_item_list = sorted(quality_changes_item_list, key = lambda x: x["name"].upper()) item_list += quality_changes_item_list self.setItems(item_list) # TODO: Duplicated code here from InstanceContainersModel. Refactor and remove this later. # ## Gets a list of the possible file filters that the plugins have # registered they can read or write. The convenience meta-filters # "All Supported Types" and "All Files" are added when listing # readers, but not when listing writers. # # \param io_type \type{str} name of the needed IO type # \return A list of strings indicating file name filters for a file # dialog. @pyqtSlot(str, result = "QVariantList") def getFileNameFilters(self, io_type): from UM.i18n import i18nCatalog catalog = i18nCatalog("uranium") #TODO: This function should be in UM.Resources! filters = [] all_types = [] for plugin_id, meta_data in self._getIOPlugins(io_type): for io_plugin in meta_data[io_type]: filters.append(io_plugin["description"] + " (*." + io_plugin["extension"] + ")") all_types.append("*.{0}".format(io_plugin["extension"])) if "_reader" in io_type: # if we're listing readers, add the option to show all supported files as the default option filters.insert(0, catalog.i18nc("@item:inlistbox", "All Supported Types ({0})", " ".join(all_types))) filters.append(catalog.i18nc("@item:inlistbox", "All Files (*)")) # Also allow arbitrary files, if the user so prefers. return filters ## Gets a list of profile reader or writer plugins # \return List of tuples of (plugin_id, meta_data). def _getIOPlugins(self, io_type): from UM.PluginRegistry import PluginRegistry pr = PluginRegistry.getInstance() active_plugin_ids = pr.getActivePlugins() result = [] for plugin_id in active_plugin_ids: meta_data = pr.getMetaData(plugin_id) if io_type in meta_data: result.append( (plugin_id, meta_data) ) return result