diff --git a/cura/Machines/Models/IntentModel.py b/cura/Machines/Models/IntentModel.py index 4e96d8e152..f5560bc94e 100644 --- a/cura/Machines/Models/IntentModel.py +++ b/cura/Machines/Models/IntentModel.py @@ -65,30 +65,28 @@ class IntentModel(ListModel): material_nodes = self._getActiveMaterials() - layer_heights_added = [] # type: List[float] - + added_quality_type_set = set() # type: Set[str] for material_node in material_nodes: intents = self._getIntentsForMaterial(material_node, quality_groups) for intent in intents: - if intent["layer_height"] not in layer_heights_added: + if intent["quality_type"] not in added_quality_type_set: new_items.append(intent) - layer_heights_added.append(intent["layer_height"]) + added_quality_type_set.add(intent["quality_type"]) # Now that we added all intents that we found something for, ensure that we set add ticks (and layer_heights) # for all groups that we don't have anything for (and set it to not available) - for quality_tuple, quality_group in quality_groups.items(): + for quality_type, quality_group in quality_groups.items(): # Add the intents that are of the correct category - if quality_tuple[0] != self._intent_category: + if quality_type not in added_quality_type_set: layer_height = fetchLayerHeight(quality_group) - if layer_height not in layer_heights_added: - new_items.append({"name": "Unavailable", - "quality_type": "", - "layer_height": layer_height, - "intent_category": self._intent_category, - "available": False}) - layer_heights_added.append(layer_height) + new_items.append({"name": "Unavailable", + "quality_type": quality_type, + "layer_height": layer_height, + "intent_category": self._intent_category, + "available": False}) + added_quality_type_set.add(quality_type) - new_items = sorted(new_items, key=lambda x: x["layer_height"]) + new_items = sorted(new_items, key = lambda x: x["layer_height"]) self.setItems(new_items) ## Get the active materials for all extruders. No duplicates will be returned @@ -122,11 +120,11 @@ class IntentModel(ListModel): if intent_node.intent_category != self._intent_category: continue extruder_intents.append({"name": quality_group.name, - "quality_type": quality_group.quality_type, - "layer_height": layer_height, - "available": quality_group.is_available, - "intent_category": self._intent_category - }) + "quality_type": quality_group.quality_type, + "layer_height": layer_height, + "available": quality_group.is_available, + "intent_category": self._intent_category + }) return extruder_intents def __repr__(self): diff --git a/cura/Machines/Models/QualityManagementModel.py b/cura/Machines/Models/QualityManagementModel.py index dcaa43283c..adaa4309b7 100644 --- a/cura/Machines/Models/QualityManagementModel.py +++ b/cura/Machines/Models/QualityManagementModel.py @@ -12,6 +12,10 @@ import cura.CuraApplication # Imported this way to prevent circular imports. from cura.Settings.ContainerManager import ContainerManager from cura.Machines.ContainerTree import ContainerTree from cura.Settings.cura_empty_instance_containers import empty_quality_changes_container +from cura.Settings.IntentManager import IntentManager + +from UM.i18n import i18nCatalog +catalog = i18nCatalog("cura") if TYPE_CHECKING: from UM.Settings.Interfaces import ContainerInterface @@ -19,6 +23,7 @@ if TYPE_CHECKING: from cura.Settings.ExtruderStack import ExtruderStack from cura.Settings.GlobalStack import GlobalStack + # # This the QML model for the quality management page. # @@ -26,15 +31,21 @@ class QualityManagementModel(ListModel): NameRole = Qt.UserRole + 1 IsReadOnlyRole = Qt.UserRole + 2 QualityGroupRole = Qt.UserRole + 3 - QualityChangesGroupRole = Qt.UserRole + 4 + QualityTypeRole = Qt.UserRole + 4 + QualityChangesGroupRole = Qt.UserRole + 5 + IntentCategoryRole = Qt.UserRole + 6 + SectionNameRole = Qt.UserRole + 7 - def __init__(self, parent = None): + def __init__(self, parent: Optional["QObject"] = None) -> 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.QualityTypeRole, "quality_type") self.addRoleName(self.QualityChangesGroupRole, "quality_changes_group") + self.addRoleName(self.IntentCategoryRole, "intent_category") + self.addRoleName(self.SectionNameRole, "section_name") application = cura.CuraApplication.CuraApplication.getInstance() container_registry = application.getContainerRegistry() @@ -127,11 +138,13 @@ class QualityManagementModel(ListModel): container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry() new_name = container_registry.uniqueName(new_name) + intent_category = quality_model_item["intent_category"] 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, None, new_name, global_stack, extruder_stack = None) + new_quality_changes = self._createQualityChanges(quality_group.quality_type, intent_category, new_name, + global_stack, extruder_stack = None) container_registry.addContainer(new_quality_changes) else: for metadata in [quality_changes_group.metadata_for_global] + list(quality_changes_group.metadata_per_extruder.values()): @@ -233,6 +246,30 @@ class QualityManagementModel(ListModel): if container.getMetaDataEntry("type") == "quality_changes": self._update() + @pyqtSlot("QVariantMap", result = str) + def getQualityItemDisplayName(self, quality_model_item: Dict[str, Any]) -> str: + quality_group = quality_model_item["quality_group"] + is_read_only = quality_model_item["is_read_only"] + intent_category = quality_model_item["intent_category"] + + quality_level_name = "Not Supported" + if quality_group is not None: + quality_level_name = quality_group.name + + display_name = quality_level_name + + if intent_category != "default": + intent_display_name = catalog.i18nc("@label", intent_category.capitalize()) + display_name = "{intent_name} - {the_rest}".format(intent_name = intent_display_name, + the_rest = display_name) + + # A custom quality + if not is_read_only: + display_name = "{custom_profile_name} - {the_rest}".format(custom_profile_name = quality_model_item["name"], + the_rest = display_name) + + return display_name + def _update(self): Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__)) @@ -253,7 +290,7 @@ class QualityManagementModel(ListModel): return item_list = [] - # Create quality group items + # Create quality group items (intent category = "default") for quality_group in quality_group_dict.values(): if not quality_group.is_available: continue @@ -261,11 +298,33 @@ class QualityManagementModel(ListModel): item = {"name": quality_group.name, "is_read_only": True, "quality_group": quality_group, - "quality_changes_group": None} + "quality_type": quality_group.quality_type, + "quality_changes_group": None, + "intent_category": "default", + "section_name": catalog.i18nc("@label", "Default"), + } item_list.append(item) # Sort by quality names item_list = sorted(item_list, key = lambda x: x["name"].upper()) + # Create intent items (non-default) + available_intent_list = IntentManager.getInstance().getCurrentAvailableIntents() + available_intent_list = [i for i in available_intent_list if i[0] != "default"] + result = [] + for intent_category, quality_type in available_intent_list: + result.append({ + "name": quality_group_dict[quality_type].name, # Use the quality name as the display name + "is_read_only": True, + "quality_group": quality_group_dict[quality_type], + "quality_type": quality_type, + "quality_changes_group": None, + "intent_category": intent_category, + "section_name": catalog.i18nc("@label", intent_category.capitalize()), + }) + # Sort by quality_type for each intent category + result = sorted(result, key = lambda x: (x["intent_category"], x["quality_type"])) + item_list += result + # Create quality_changes group items quality_changes_item_list = [] for quality_changes_group in quality_changes_group_list: @@ -273,7 +332,11 @@ class QualityManagementModel(ListModel): item = {"name": quality_changes_group.name, "is_read_only": False, "quality_group": quality_group, - "quality_changes_group": quality_changes_group} + "quality_type": quality_group.quality_type, + "quality_changes_group": quality_changes_group, + "intent_category": quality_changes_group.intent_category, + "section_name": catalog.i18nc("@label", "Custom profiles"), + } quality_changes_item_list.append(item) # Sort quality_changes items by names and append to the item list diff --git a/cura/Settings/IntentManager.py b/cura/Settings/IntentManager.py index 973381504d..9f9d174ffa 100644 --- a/cura/Settings/IntentManager.py +++ b/cura/Settings/IntentManager.py @@ -80,7 +80,7 @@ class IntentManager(QObject): for extruder_stack in global_stack.extruderList: nozzle_name = extruder_stack.variant.getMetaDataEntry("name") material_id = extruder_stack.material.getMetaDataEntry("base_file") - final_intent_ids |= {metadata["id"] for metadata in self.intentMetadatas(current_definition_id, nozzle_name, material_id) if metadata["quality_type"] in available_quality_types} + 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} result = set() # type: Set[Tuple[str, str]] for intent_id in final_intent_ids: @@ -162,4 +162,4 @@ class IntentManager(QObject): extruder_stack.intent = self.getDefaultIntent() application.getMachineManager().setQualityGroupByQualityType(quality_type) if old_intent_category != intent_category: - self.intentCategoryChanged.emit() \ No newline at end of file + self.intentCategoryChanged.emit() diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index c18b76deba..57da690d6d 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -637,6 +637,7 @@ class MachineManager(QObject): category = extruder.intent.getMetaDataEntry("intent_category", "default") if category != "default" and category != intent_category: intent_category = category + return intent_category # Provies a list of extruder positions that have a different intent from the active one. @@ -1626,6 +1627,7 @@ class MachineManager(QObject): # Otherwise the intent profile will be left to the empty profile, which # represents the "default" intent category. # \param intent_category The intent category to change to. + @pyqtSlot(str) def setIntentByCategory(self, intent_category: str) -> None: global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() if global_stack is None: diff --git a/resources/qml/Preferences/ProfilesPage.qml b/resources/qml/Preferences/ProfilesPage.qml index d620c1a37e..51fe7ac81f 100644 --- a/resources/qml/Preferences/ProfilesPage.qml +++ b/resources/qml/Preferences/ProfilesPage.qml @@ -7,7 +7,7 @@ import QtQuick.Layouts 1.3 import QtQuick.Dialogs 1.2 import UM 1.2 as UM -import Cura 1.0 as Cura +import Cura 1.6 as Cura Item @@ -43,6 +43,7 @@ Item } property var currentItemName: hasCurrentItem ? base.currentItem.name : "" + property var currentItemDisplayName: hasCurrentItem ? base.qualityManagementModel.getQualityItemDisplayName(base.currentItem) : "" property var isCurrentItemActivated: { @@ -50,7 +51,14 @@ Item { return false; } - return base.currentItem.name == Cura.MachineManager.activeQualityOrQualityChangesName; + if (base.currentItem.is_read_only) + { + return (base.currentItem.name == Cura.MachineManager.activeQualityOrQualityChangesName) && (base.currentItem.intent_category == Cura.MachineManager.activeIntentCategory); + } + else + { + return base.currentItem.name == Cura.MachineManager.activeQualityOrQualityChangesName; + } } property var canCreateProfile: @@ -80,7 +88,7 @@ Item { if (base.currentItem.is_read_only) { - Cura.MachineManager.setQualityGroup(base.currentItem.quality_group); + Cura.IntentManager.selectIntent(base.currentItem.intent_category, base.currentItem.quality_type); } else { @@ -434,7 +442,7 @@ Item } } - section.property: "is_read_only" + section.property: "section_name" section.delegate: Rectangle { height: childrenRect.height @@ -443,7 +451,7 @@ Item { anchors.left: parent.left anchors.leftMargin: UM.Theme.getSize("default_lining").width - text: section == "true" ? catalog.i18nc("@label", "Default profiles") : catalog.i18nc("@label", "Custom profiles") + text: section font.bold: true } } @@ -467,7 +475,19 @@ Item width: Math.floor((parent.width * 0.8)) text: model.name elide: Text.ElideRight - font.italic: model.name == Cura.MachineManager.activeQualityOrQualityChangesName + font.italic: + { + if (model.is_read_only) + { + // For built-in qualities, it needs to match both the intent category and the quality name + return model.name == Cura.MachineManager.activeQualityOrQualityChangesName && model.intent_category == Cura.MachineManager.activeIntentCategory + } + else + { + // For custom qualities, it only needs to match the name + return model.name == Cura.MachineManager.activeQualityOrQualityChangesName + } + } color: parent.isCurrentItem ? palette.highlightedText : palette.text } @@ -511,7 +531,7 @@ Item Label { - text: base.currentItemName + text: base.currentItemDisplayName font: UM.Theme.getFont("large_bold") } } @@ -519,7 +539,7 @@ Item Flow { id: currentSettingsActions - visible: base.hasCurrentItem && base.currentItem.name == Cura.MachineManager.activeQualityOrQualityChangesName + visible: base.hasCurrentItem && base.currentItem.name == Cura.MachineManager.activeQualityOrQualityChangesName && base.currentItem.intent_category == Cura.MachineManager.activeIntentCategory anchors.left: parent.left anchors.right: parent.right anchors.top: profileName.bottom @@ -567,7 +587,6 @@ Item } } - TabView { anchors.left: parent.left diff --git a/tests/TestQualityManager.py b/tests/TestQualityManager.py index 52c3da4fbb..fb96f4476b 100644 --- a/tests/TestQualityManager.py +++ b/tests/TestQualityManager.py @@ -118,10 +118,12 @@ def test_duplicateQualityChanges(quality_mocked_application): quality_group.getAllNodes = MagicMock(return_value = mocked_quality) quality_changes_group = MagicMock() mocked_quality_changes = MagicMock() - quality_changes_group.getAllNodes = MagicMock(return_value=[mocked_quality_changes]) + quality_changes_group.getAllNodes = MagicMock(return_value = [mocked_quality_changes]) mocked_quality_changes.duplicate = MagicMock(return_value = mocked_quality_changes) manager._container_registry.addContainer = MagicMock() # Side effect we want to check. - manager.duplicateQualityChanges("zomg", {"quality_group": quality_group, "quality_changes_group": quality_changes_group}) - assert manager._container_registry.addContainer.called_once_with(mocked_quality_changes) \ No newline at end of file + manager.duplicateQualityChanges("zomg", {"intent_category": "default", + "quality_group": quality_group, + "quality_changes_group": quality_changes_group}) + assert manager._container_registry.addContainer.called_once_with(mocked_quality_changes)