diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 3a3ac17cdf..e1805584b0 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -115,6 +115,8 @@ from . import CuraActions from . import PlatformPhysics from . import PrintJobPreviewImageProvider from .AutoSave import AutoSave +from .Machines.Models.ActiveIntentQualitiesModel import ActiveIntentQualitiesModel +from .Machines.Models.IntentSelectionModel import IntentSelectionModel from .SingleInstance import SingleInstance if TYPE_CHECKING: @@ -1192,6 +1194,8 @@ class CuraApplication(QtApplication): qmlRegisterType(NozzleModel, "Cura", 1, 0, "NozzleModel") qmlRegisterType(IntentModel, "Cura", 1, 6, "IntentModel") qmlRegisterType(IntentCategoryModel, "Cura", 1, 6, "IntentCategoryModel") + qmlRegisterType(IntentSelectionModel, "Cura", 1, 7, "IntentSelectionModel") + qmlRegisterType(ActiveIntentQualitiesModel, "Cura", 1, 7, "ActiveIntentQualitiesModel") self.processEvents() qmlRegisterType(MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler") diff --git a/cura/Machines/Models/ActiveIntentQualitiesModel.py b/cura/Machines/Models/ActiveIntentQualitiesModel.py new file mode 100644 index 0000000000..c20ccde383 --- /dev/null +++ b/cura/Machines/Models/ActiveIntentQualitiesModel.py @@ -0,0 +1,128 @@ +# Copyright (c) 2022 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. +from typing import Optional, Set, Dict, List, Any + +from PyQt6.QtCore import Qt, QObject, QTimer + +import cura.CuraApplication +from UM.Logger import Logger +from UM.Qt.ListModel import ListModel +from cura.Machines.ContainerTree import ContainerTree +from cura.Machines.Models.MachineModelUtils import fetchLayerHeight +from cura.Machines.MaterialNode import MaterialNode +from cura.Machines.QualityGroup import QualityGroup +from cura.Settings.IntentManager import IntentManager + + +class ActiveIntentQualitiesModel(ListModel): + NameRole = Qt.ItemDataRole.UserRole + 1 + DisplayTextRole = Qt.ItemDataRole.UserRole + 2 + QualityTypeRole = Qt.ItemDataRole.UserRole + 3 + LayerHeightRole = Qt.ItemDataRole.UserRole + 4 + IntentCategeoryRole = Qt.ItemDataRole.UserRole + 5 + + def __init__(self, parent: Optional[QObject] = None) -> None: + super().__init__(parent) + + self.addRoleName(self.NameRole, "name") + self.addRoleName(self.QualityTypeRole, "quality_type") + self.addRoleName(self.LayerHeightRole, "layer_height") + self.addRoleName(self.DisplayTextRole, "display_text") + self.addRoleName(self.IntentCategeoryRole, "intent_category") + + self._intent_category = "" + + IntentManager.intentCategoryChangedSignal.connect(self._update) + machine_manager = cura.CuraApplication.CuraApplication.getInstance().getMachineManager() + machine_manager.activeQualityGroupChanged.connect(self._update) + + self._update_timer = QTimer() + self._update_timer.setInterval(100) + self._update_timer.setSingleShot(True) + self._update_timer.timeout.connect(self._update) + + self._update() + + def _updateDelayed(self): + self._update_timer.start() + + def _onChanged(self, container: ContainerStack) -> None: + if container.getMetaDataEntry("type") == "intent": + self._updateDelayed() + + def _update(self): + active_extruder_stack = cura.CuraApplication.CuraApplication.getInstance().getMachineManager().activeStack + if active_extruder_stack: + self._intent_category = active_extruder_stack.intent.getMetaDataEntry("intent_category", "") + + new_items: List[Dict[str, Any]] = [] + global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() + if not global_stack: + self.setItems(new_items) + return + quality_groups = ContainerTree.getInstance().getCurrentQualityGroups() + + material_nodes = self._getActiveMaterials() + + added_quality_type_set: Set[str] = set() + for material_node in material_nodes: + intents = self._getIntentsForMaterial(material_node, quality_groups) + for intent in intents: + if intent["quality_type"] not in added_quality_type_set: + new_items.append(intent) + added_quality_type_set.add(intent["quality_type"]) + + new_items = sorted(new_items, key=lambda x: x["layer_height"]) + self.setItems(new_items) + + def _getActiveMaterials(self) -> Set["MaterialNode"]: + """Get the active materials for all extruders. No duplicates will be returned""" + + global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() + if global_stack is None: + return set() + + container_tree = ContainerTree.getInstance() + machine_node = container_tree.machines[global_stack.definition.getId()] + nodes: Set[MaterialNode] = set() + + for extruder in global_stack.extruderList: + active_variant_name = extruder.variant.getMetaDataEntry("name") + if active_variant_name not in machine_node.variants: + Logger.log("w", "Could not find the variant %s", active_variant_name) + continue + active_variant_node = machine_node.variants[active_variant_name] + active_material_node = active_variant_node.materials.get(extruder.material.getMetaDataEntry("base_file")) + if active_material_node is None: + Logger.log("w", "Could not find the material %s", extruder.material.getMetaDataEntry("base_file")) + continue + nodes.add(active_material_node) + + return nodes + + def _getIntentsForMaterial(self, active_material_node: "MaterialNode", quality_groups: Dict[str, "QualityGroup"]) -> List[Dict[str, Any]]: + extruder_intents: List[Dict[str, Any]] = [] + + for quality_id, quality_node in active_material_node.qualities.items(): + if quality_node.quality_type not in quality_groups: # Don't add the empty quality type (or anything else that would crash, defensively). + continue + quality_group = quality_groups[quality_node.quality_type] + + if not quality_group.is_available: + continue + + layer_height = fetchLayerHeight(quality_group) + + for intent_id, intent_node in quality_node.intents.items(): + if intent_node.intent_category != self._intent_category: + continue + + extruder_intents.append({"name": quality_group.name, + "display_text": f"{quality_group.name} - {layer_height}mm", + "quality_type": quality_group.quality_type, + "layer_height": layer_height, + "intent_category": self._intent_category + }) + return extruder_intents + + diff --git a/cura/Machines/Models/IntentSelectionModel.py b/cura/Machines/Models/IntentSelectionModel.py new file mode 100644 index 0000000000..ec6957832c --- /dev/null +++ b/cura/Machines/Models/IntentSelectionModel.py @@ -0,0 +1,129 @@ +# Copyright (c) 2022 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +import collections +from typing import OrderedDict, Optional + +from PyQt6.QtCore import Qt, QTimer, QObject + +import cura +from UM import i18nCatalog +from UM.Logger import Logger +from UM.Qt.ListModel import ListModel +from UM.Settings.ContainerRegistry import ContainerRegistry +from UM.Settings.Interfaces import ContainerInterface +from cura.Settings.IntentManager import IntentManager + +catalog = i18nCatalog("cura") + + +class IntentSelectionModel(ListModel): + + NameRole = Qt.ItemDataRole.UserRole + 1 + IntentCategoryRole = Qt.ItemDataRole.UserRole + 2 + WeightRole = Qt.ItemDataRole.UserRole + 3 + DescriptionRole = Qt.ItemDataRole.UserRole + 4 + IconRole = Qt.ItemDataRole.UserRole + 5 + + def __init__(self, parent: Optional[QObject] = None) -> None: + super().__init__(parent) + + self.addRoleName(self.NameRole, "name") + self.addRoleName(self.IntentCategoryRole, "intent_category") + self.addRoleName(self.WeightRole, "weight") + self.addRoleName(self.DescriptionRole, "description") + self.addRoleName(self.IconRole, "icon") + + application = cura.CuraApplication.CuraApplication.getInstance() + + ContainerRegistry.getInstance().containerAdded.connect(self._onContainerChange) + ContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChange) + machine_manager = cura.CuraApplication.CuraApplication.getInstance().getMachineManager() + machine_manager.activeMaterialChanged.connect(self._update) + machine_manager.activeVariantChanged.connect(self._update) + machine_manager.extruderChanged.connect(self._update) + + extruder_manager = application.getExtruderManager() + extruder_manager.extrudersChanged.connect(self._update) + + self._update_timer: QTimer = QTimer() + self._update_timer.setInterval(100) + self._update_timer.setSingleShot(True) + self._update_timer.timeout.connect(self._update) + + self._onChange() + + @staticmethod + def _getDefaultProfileInformation() -> OrderedDict[str, dict]: + """ Default information user-visible string. Ordered by weight. """ + default_profile_information = collections.OrderedDict() + default_profile_information["default"] = { + "name": catalog.i18nc("@label", "Default"), + "icon": "GearCheck" + } + default_profile_information["visual"] = { + "name": catalog.i18nc("@label", "Visual"), + "description": catalog.i18nc("@text", "The visual profile is designed to print visual prototypes and models with the intent of high visual and surface quality."), + "icon" : "Visual" + } + default_profile_information["engineering"] = { + "name": catalog.i18nc("@label", "Engineering"), + "description": catalog.i18nc("@text", "The engineering profile is designed to print functional prototypes and end-use parts with the intent of better accuracy and for closer tolerances."), + "icon": "Nut" + } + default_profile_information["quick"] = { + "name": catalog.i18nc("@label", "Draft"), + "description": catalog.i18nc("@text", "The draft profile is designed to print initial prototypes and concept validation with the intent of significant print time reduction."), + "icon": "SpeedOMeter" + } + return default_profile_information + + def _onContainerChange(self, container: ContainerInterface) -> None: + """Updates the list of intents if an intent profile was added or removed.""" + + if container.getMetaDataEntry("type") == "intent": + self._update() + + def _onChange(self) -> None: + self._update_timer.start() + + def _update(self) -> None: + Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__)) + + global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() + if global_stack is None: + self.setItems([]) + Logger.log("d", "No active GlobalStack, set quality profile model as empty.") + return + + # Check for material compatibility + if not cura.CuraApplication.CuraApplication.getInstance().getMachineManager().activeMaterialsCompatible(): + Logger.log("d", "No active material compatibility, set quality profile model as empty.") + self.setItems([]) + return + + default_profile_info = self._getDefaultProfileInformation() + + available_categories = IntentManager.getInstance().currentAvailableIntentCategories() + result = [] + for i, category in enumerate(available_categories): + profile_info = default_profile_info.get(category, {}) + + try: + weight = list(default_profile_info.keys()).index(category) + except ValueError: + weight = len(available_categories) + i + + result.append({ + "name": profile_info.get("name", category.title()), + "description": profile_info.get("description", None), + "icon" : profile_info.get("icon", ""), + "intent_category": category, + "weight": weight, + }) + + result.sort(key=lambda k: k["weight"]) + + self.setItems(result) + + diff --git a/cura/Settings/IntentManager.py b/cura/Settings/IntentManager.py index 2dfec02201..7d3e2659bf 100644 --- a/cura/Settings/IntentManager.py +++ b/cura/Settings/IntentManager.py @@ -1,4 +1,4 @@ -# Copyright (c) 2019 Ultimaker B.V. +# Copyright (c) 2022 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from PyQt6.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot @@ -8,6 +8,7 @@ from UM.Logger import Logger from UM.Settings.InstanceContainer import InstanceContainer import cura.CuraApplication +from UM.Signal import Signal from cura.Machines.ContainerTree import ContainerTree from cura.Settings.cura_empty_instance_containers import empty_intent_container @@ -29,6 +30,7 @@ class IntentManager(QObject): return cls.__instance intentCategoryChanged = pyqtSignal() #Triggered when we switch categories. + intentCategoryChangedSignal = Signal() def intentMetadatas(self, definition_id: str, nozzle_name: str, material_base_file: str) -> List[Dict[str, Any]]: """Gets the metadata dictionaries of all intent profiles for a given @@ -189,3 +191,4 @@ class IntentManager(QObject): application.getMachineManager().setQualityGroupByQualityType(quality_type) if old_intent_category != intent_category: self.intentCategoryChanged.emit() + self.intentCategoryChangedSignal.emit() diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 9b98179bff..64d34d6c3e 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -1778,3 +1778,31 @@ class MachineManager(QObject): abbr_machine += stripped_word return abbr_machine + + @pyqtSlot(str, str, result = bool) + def intentCategoryHasQuality(self, intent_category: str, quality_type: str) -> bool: + """ Checks if there are any quality groups for active extruders that have an intent category """ + quality_groups = ContainerTree.getInstance().getCurrentQualityGroups() + + if quality_type in quality_groups: + quality_group = quality_groups[quality_type] + for node in quality_group.nodes_for_extruders.values(): + if any(intent.intent_category == intent_category for intent in node.intents.values()): + return True + + return False + + @pyqtSlot(str, result = str) + def getDefaultQualityTypeForIntent(self, intent_category) -> str: + """ If there is an intent category for the default machine quality return it, otherwise return the first quality for this intent category """ + machine = ContainerTree.getInstance().machines.get(self._global_container_stack.definition.getId()) + + if self.intentCategoryHasQuality(intent_category, machine.preferred_quality_type): + return machine.preferred_quality_type + + for quality_type, quality_group in ContainerTree.getInstance().getCurrentQualityGroups().items(): + for node in quality_group.nodes_for_extruders.values(): + if any(intent.intent_category == intent_category for intent in node.intents.values()): + return quality_type + + return "" diff --git a/resources/qml/IconWithText.qml b/resources/qml/IconWithText.qml index cc0834d7ea..24f211ae17 100644 --- a/resources/qml/IconWithText.qml +++ b/resources/qml/IconWithText.qml @@ -22,6 +22,7 @@ Item property alias elide: label.elide property real margin: UM.Theme.getSize("narrow_margin").width property alias wrapMode: label.wrapMode + property real spacing: UM.Theme.getSize("narrow_margin").width // These properties can be used in combination with layouts. readonly property real contentWidth: icon.width + margin + label.contentWidth @@ -61,6 +62,7 @@ Item top: parent.top bottom: parent.bottom rightMargin: 0 + leftMargin: spacing margins: margin } } diff --git a/resources/qml/PrintSetupSelector/Recommended/ProfileWarningReset.qml b/resources/qml/PrintSetupSelector/Recommended/ProfileWarningReset.qml new file mode 100644 index 0000000000..c9abcab1c8 --- /dev/null +++ b/resources/qml/PrintSetupSelector/Recommended/ProfileWarningReset.qml @@ -0,0 +1,108 @@ +import QtQuick 2.10 + +import UM 1.6 as UM +import Cura 1.6 as Cura + +Rectangle +{ + height: visible ? UM.Theme.getSize("action_button_icon").height : 0 + visible: Cura.SimpleModeSettingsManager.isProfileCustomized || Cura.MachineManager.hasCustomQuality + anchors.topMargin: UM.Theme.getSize("default_margin") + anchors.bottomMargin: UM.Theme.getSize("default_margin") + + + Rectangle + { + id: warningIcon + color: UM.Theme.getColor("um_yellow_5") + height: UM.Theme.getSize("action_button_icon").height + width: height + radius: width + anchors + { + left: parent.left + verticalCenter: parent.verticalCenter + } + UM.ColorImage + { + height: UM.Theme.getSize("action_button_icon").height + width: height + source: UM.Theme.getIcon("Warning", "low") + } + } + + UM.Label + { + id: warning + anchors + { + left: warningIcon.right + verticalCenter: parent.verticalCenter + leftMargin: UM.Theme.getSize("thin_margin").width + } + + text: "" + + states: [ + State + { + name: "settings changed and custom quality" + when: Cura.SimpleModeSettingsManager.isProfileCustomized && Cura.MachineManager.hasCustomQuality + PropertyChanges + { + target: warning + text: { + var profile_name = Cura.MachineManager.activeQualityChangesGroup.name + return "%1 %2".arg(profile_name).arg(catalog.i18nc("@info", "custom profile is active and you overwrote some settings.")) + } + } + + }, + State + { + name: "custom quality" + when: Cura.MachineManager.hasCustomQuality + PropertyChanges + { + target: warning + text: { + var profile_name = Cura.MachineManager.activeQualityChangesGroup.name + return "%1 %2".arg(profile_name).arg(catalog.i18nc("@info", "custom profile is overriding some settings.")) + } + } + }, + State + { + name: "settings changed" + when: Cura.SimpleModeSettingsManager.isProfileCustomized + PropertyChanges + { + target: warning + text: catalog.i18nc("@info", "Some settings were changed.") + } + } + ] + + } + + UM.SimpleButton + { + id: resetToDefaultQualityButton + height: UM.Theme.getSize("action_button_icon").height + width: height + iconSource: UM.Theme.getIcon("ArrowReset") + anchors + { + right: parent.right + verticalCenter: parent.verticalCenter + } + + color: UM.Theme.getColor("accent_1") + + onClicked: + { + Cura.MachineManager.resetToUseDefaultQuality() + } + } + +} \ No newline at end of file diff --git a/resources/qml/PrintSetupSelector/Recommended/RecommendedPrintSetup.qml b/resources/qml/PrintSetupSelector/Recommended/RecommendedPrintSetup.qml index de8cce6e94..aac8dcecfb 100644 --- a/resources/qml/PrintSetupSelector/Recommended/RecommendedPrintSetup.qml +++ b/resources/qml/PrintSetupSelector/Recommended/RecommendedPrintSetup.qml @@ -1,10 +1,11 @@ -// Copyright (c) 2018 Ultimaker B.V. -// Cura is released under the terms of the LGPLv3 or higher. +//Copyright (c) 2022 Ultimaker B.V. +//Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.10 +import QtQuick.Layouts 1.1 -import UM 1.2 as UM -import Cura 1.0 as Cura +import UM 1.6 as UM +import Cura 1.6 as Cura Item { @@ -13,11 +14,11 @@ Item height: childrenRect.height + 2 * padding property bool settingsEnabled: Cura.ExtruderManager.activeExtruderStackId || extrudersEnabledCount.properties.value == 1 - property real padding: UM.Theme.getSize("thick_margin").width + property real padding: UM.Theme.getSize("default_margin").width - Column + ColumnLayout { - spacing: UM.Theme.getSize("wide_margin").height + spacing: UM.Theme.getSize("default_margin").height anchors { @@ -30,11 +31,53 @@ Item // TODO property real firstColumnWidth: Math.round(width / 3) + UM.Label + { + text: catalog.i18nc("@label", "Profiles") + font: UM.Theme.getFont("medium") + } + RecommendedQualityProfileSelector { width: parent.width - // TODO Create a reusable component with these properties to not define them separately for each component - labelColumnWidth: parent.firstColumnWidth + visible: recommendedResolutionSelector.visible + } + + RecommendedResolutionSelector + { + id: recommendedResolutionSelector + Layout.fillWidth: true + width: parent.width + } + + UnsupportedProfileIndication + { + width: parent.width + visible: !recommendedResolutionSelector.visible + } + + + ProfileWarningReset + { + width: parent.width + Layout.fillWidth: true + } + + //Line between the sections. + Rectangle + { + width: parent.width + height: UM.Theme.getSize("default_lining").height + Layout.topMargin: UM.Theme.getSize("narrow_margin").height + Layout.bottomMargin: UM.Theme.getSize("narrow_margin").height + Layout.fillWidth: true + color: UM.Theme.getColor("lining") + } + + UM.Label + { + text: catalog.i18nc("@label", "Print settings") + font: UM.Theme.getFont("medium") } RecommendedInfillDensitySelector @@ -42,6 +85,9 @@ Item width: parent.width // TODO Create a reusable component with these properties to not define them separately for each component labelColumnWidth: parent.firstColumnWidth + Layout.fillWidth: true + Layout.leftMargin: UM.Theme.getSize("default_margin").width + Layout.rightMargin: UM.Theme.getSize("default_margin").width } RecommendedSupportSelector @@ -49,6 +95,7 @@ Item width: parent.width // TODO Create a reusable component with these properties to not define them separately for each component labelColumnWidth: parent.firstColumnWidth + Layout.leftMargin: UM.Theme.getSize("default_margin").width } RecommendedAdhesionSelector @@ -56,6 +103,7 @@ Item width: parent.width // TODO Create a reusable component with these properties to not define them separately for each component labelColumnWidth: parent.firstColumnWidth + Layout.leftMargin: UM.Theme.getSize("default_margin").width } } diff --git a/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml b/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml index 3d0077abb0..6bcca955d1 100644 --- a/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml +++ b/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml @@ -3,180 +3,46 @@ import QtQuick 2.10 import QtQuick.Controls 2.3 +import QtQuick.Layouts 2.10 import UM 1.5 as UM -import Cura 1.6 as Cura +import Cura 1.7 as Cura import ".." Item { id: qualityRow height: childrenRect.height + visible: intentSelectionRepeater.count > 1 //Only show selector if there's more options than just "default". - property real labelColumnWidth: Math.round(width / 3) - property real settingsColumnWidth: width - labelColumnWidth - - // Here are the elements that are shown in the left column - - Column + RowLayout { - anchors - { - left: parent.left - right: parent.right - } - - spacing: UM.Theme.getSize("default_margin").height - - ButtonGroup - { - id: activeProfileButtonGroup - exclusive: true - onClicked: Cura.IntentManager.selectIntent(button.modelData.intent_category, button.modelData.quality_type) - } - - Item - { - height: childrenRect.height - anchors - { - left: parent.left - right: parent.right - } - Cura.IconWithText - { - id: profileLabel - source: UM.Theme.getIcon("PrintQuality") - text: catalog.i18nc("@label", "Profiles") - font: UM.Theme.getFont("medium") - width: labelColumnWidth - iconSize: UM.Theme.getSize("medium_button_icon").width - } - UM.SimpleButton - { - id: resetToDefaultQualityButton - - visible: Cura.SimpleModeSettingsManager.isProfileCustomized || Cura.MachineManager.hasCustomQuality - height: visible ? UM.Theme.getSize("print_setup_icon").height : 0 - width: height - anchors - { - right: profileLabel.right - rightMargin: UM.Theme.getSize("default_margin").width - leftMargin: UM.Theme.getSize("default_margin").width - verticalCenter: parent.verticalCenter - } - - color: hovered ? UM.Theme.getColor("setting_control_button_hover") : UM.Theme.getColor("setting_control_button") - iconSource: UM.Theme.getIcon("ArrowReset") - - onClicked: - { - // if the current profile is user-created, switch to a built-in quality - Cura.MachineManager.resetToUseDefaultQuality() - } - onEntered: - { - var tooltipContent = catalog.i18nc("@tooltip","You have modified some profile settings. If you want to change these go to custom mode.") - base.showTooltip(qualityRow, Qt.point(-UM.Theme.getSize("thick_margin").width, 0), tooltipContent) - } - onExited: base.hideTooltip() - } - - Cura.LabelBar - { - id: labelbar - anchors - { - left: profileLabel.right - right: parent.right - verticalCenter: profileLabel.verticalCenter - } - - model: Cura.QualityProfilesDropDownMenuModel - modelKey: "layer_height" - } - } - + id: intentRow + width: parent.width Repeater { - model: Cura.IntentCategoryModel {} - Item + id: intentSelectionRepeater + model: Cura.IntentSelectionModel {} + + RecommendedQualityProfileSelectorButton { - anchors - { - left: parent.left - right: parent.right - } - height: intentCategoryLabel.height + profileName: model.name + icon: model.icon - UM.Label - { - id: intentCategoryLabel - text: model.name - width: labelColumnWidth - UM.Theme.getSize("section_icon").width - anchors.left: parent.left - anchors.leftMargin: UM.Theme.getSize("section_icon").width + UM.Theme.getSize("narrow_margin").width - font: UM.Theme.getFont("medium") - elide: Text.ElideRight - } - Cura.RadioCheckbar - { - anchors + selected: Cura.MachineManager.activeIntentCategory == model.intent_category + + onClicked: { + var qualityType + if (Cura.MachineManager.intentCategoryHasQuality(model.intent_category, Cura.MachineManager.activeQualityType)) { - left: intentCategoryLabel.right - right: parent.right - } - dataModel: model["qualities"] - buttonGroup: activeProfileButtonGroup - - function checkedFunction(modelItem) - { - if(Cura.MachineManager.hasCustomQuality) - { - // When user created profile is active, no quality tickbox should be active. - return false - } - - if(modelItem === null) - { - return false - } - return Cura.MachineManager.activeQualityType == modelItem.quality_type && Cura.MachineManager.activeIntentCategory == modelItem.intent_category - } - - isCheckedFunction: checkedFunction - } - - MouseArea // Intent description tooltip hover area - { - id: intentDescriptionHoverArea - anchors.fill: parent - hoverEnabled: true - enabled: model.description !== undefined - acceptedButtons: Qt.NoButton // react to hover only, don't steal clicks - - Timer - { - id: intentTooltipTimer - interval: 500 - running: false - repeat: false - onTriggered: base.showTooltip( - intentCategoryLabel, - Qt.point(-(intentCategoryLabel.x - qualityRow.x) - UM.Theme.getSize("thick_margin").width, 0), - model.description - ) - } - - onEntered: intentTooltipTimer.start() - onExited: - { - base.hideTooltip() - intentTooltipTimer.stop() + qualityType = Cura.MachineManager.activeQualityType + } else { + qualityType = Cura.MachineManager.getDefaultQualityTypeForIntent(model.intent_category) + print(Cura.MachineManager.getDefaultQualityTypeForIntent(model.intent_category)) } + Cura.IntentManager.selectIntent(model.intent_category, qualityType) } } } diff --git a/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelectorButton.qml b/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelectorButton.qml new file mode 100644 index 0000000000..6804e7e5ba --- /dev/null +++ b/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelectorButton.qml @@ -0,0 +1,93 @@ +// Copyright (c) 2022 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.10 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 2.10 + +import UM 1.5 as UM +import Cura 1.7 as Cura + + +Rectangle +{ + id: base + height: 60 + Layout.fillWidth: true + color: mouseArea.containsMouse || selected ? UM.Theme.getColor("background_3") : UM.Theme.getColor("background_1") + + property bool selected: false + property string profileName: "" + property string icon: "" + + signal clicked() + + MouseArea + { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + onClicked: base.clicked() + } + + Item + { + width: intentIcon.width + anchors + { + top: parent.top + bottom: qualityLabel.top + horizontalCenter: parent.horizontalCenter + topMargin: UM.Theme.getSize("narrow_margin").height + } + + Item + { + id: intentIcon + width: UM.Theme.getSize("recommended_button_icon").width + height: UM.Theme.getSize("recommended_button_icon").height + UM.ColorImage + { + anchors.fill: parent + anchors.centerIn: parent + visible: icon != "" + source: UM.Theme.getIcon(icon) + color: UM.Theme.getColor("icon") + } + + Rectangle + { + id: circle + anchors.fill: parent + radius: width + anchors.verticalCenter: parent.verticalCenter + visible: icon == "" + border.width: UM.Theme.getSize("thick_lining").width + border.color: UM.Theme.getColor("text") + + UM.Label + { + id: initialLabel + anchors.centerIn: parent + text: profileName.charAt(0).toUpperCase() + font: UM.Theme.getFont("small_bold") + horizontalAlignment: Text.AlignHCenter + } + } + + + } + } + + UM.Label + { + id: qualityLabel + text: profileName + anchors + { + bottom: parent.bottom + horizontalCenter: parent.horizontalCenter + bottomMargin: UM.Theme.getSize("narrow_margin").height + } + } +} \ No newline at end of file diff --git a/resources/qml/PrintSetupSelector/Recommended/RecommendedResolutionSelector.qml b/resources/qml/PrintSetupSelector/Recommended/RecommendedResolutionSelector.qml new file mode 100644 index 0000000000..6217cfe1cc --- /dev/null +++ b/resources/qml/PrintSetupSelector/Recommended/RecommendedResolutionSelector.qml @@ -0,0 +1,84 @@ +// Copyright (c) 2022 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.10 + +import UM 1.6 as UM +import Cura 1.7 as Cura + +Item +{ + id: recommendedResolutionSelector + height: childrenRect.height + + property real labelColumnWidth: Math.round(width / 3) + property string _previousResolution: "" //Internal variable to detect changes. + Component.onCompleted: _previousResolution = Cura.MachineManager.activeQualityType; + + visible: visibilityPreset.count > 0 //Only show if there are quality types to select from. + + Cura.IconWithText + { + id: resolutionTitle + anchors.top: parent.top + anchors.left: parent.left + source: UM.Theme.getIcon("PrintQuality") + text: catalog.i18nc("@label", "Resolution") + width: labelColumnWidth + height: parent.height + spacing: UM.Theme.getSize("thick_margin").width + iconSize: UM.Theme.getSize("medium_button_icon").width + } + + Cura.ComboBox + { + id: visibilityPreset + implicitHeight: UM.Theme.getSize("combobox").height + implicitWidth: UM.Theme.getSize("combobox").width + anchors + { + top: parent.top + right: parent.right + } + + textRole: "display_text" + textFormat: Text.StyledText + + model: Cura.ActiveIntentQualitiesModel{} + + currentIndex: + { + var current_quality_type = Cura.MachineManager.activeQualityType + + var index = 0 + for (var i = 0; i < model.count; i++) + { + if (model.getItem(i).quality_type == current_quality_type) + { + index = i + break + } + } + return index + } + + onActivated: + { + var selected_item = model.getItem(currentIndex) + Cura.IntentManager.selectIntent(selected_item.intent_category, selected_item.quality_type) + } + + Connections + { + target: Cura.IntentManager + function onIntentCategoryChanged() + { + if(recommendedResolutionSelector._previousResolution !== Cura.MachineManager.activeQualityType) + { + visibilityPreset.pulse(); + } + recommendedResolutionSelector._previousResolution = Cura.MachineManager.activeQualityType; + } + } + } +} \ No newline at end of file diff --git a/resources/qml/PrintSetupSelector/Recommended/UnsupportedProfileIndication.qml b/resources/qml/PrintSetupSelector/Recommended/UnsupportedProfileIndication.qml new file mode 100644 index 0000000000..ff12fa9307 --- /dev/null +++ b/resources/qml/PrintSetupSelector/Recommended/UnsupportedProfileIndication.qml @@ -0,0 +1,54 @@ +//Copyright (c) 2022 Ultimaker B.V. +//Cura is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.15 + +import Cura 1.6 as Cura +import UM 1.6 as UM + +//Message showing the user that the configuration they have selected has no profiles. +Column +{ + spacing: UM.Theme.getSize("default_margin").height + + Row + { + width: parent.width + + spacing: UM.Theme.getSize("thin_margin").width + + UM.StatusIcon + { + width: UM.Theme.getSize("notification_icon").width + status: UM.StatusIcon.Status.ERROR + } + + UM.Label + { + width: parent.width + + font: UM.Theme.getFont("default_bold") + text: catalog.i18nc("@error", "Configuration not supported") + } + } + + UM.Label + { + width: parent.width + + text: catalog.i18nc("@message:text %1 is the name the printer uses for 'nozzle'.", "No profiles are available for the selected material/%1 configuration. Please change your configuration." + ).arg(Cura.MachineManager.activeDefinitionVariantsName) + } + + Cura.TertiaryButton + { + anchors.right: parent.right + + text: catalog.i18nc("@button:label", "Learn more") + textFont: UM.Theme.getFont("default") + iconSource: UM.Theme.getIcon("LinkExternal") + isIconOnRightSide: true + + onClicked: Qt.openUrlExternally("https://support.ultimaker.com/hc/en-us/articles/360012909099") + } +} \ No newline at end of file diff --git a/resources/qml/Widgets/ComboBox.qml b/resources/qml/Widgets/ComboBox.qml index 988b7c3782..77e6c489e9 100644 --- a/resources/qml/Widgets/ComboBox.qml +++ b/resources/qml/Widgets/ComboBox.qml @@ -17,6 +17,8 @@ ComboBox property var defaultTextOnEmptyModel: catalog.i18nc("@label", "No items to select from") // Text displayed in the combobox when the model is empty property var defaultTextOnEmptyIndex: "" // Text displayed in the combobox when the model has items but no item is selected + property alias textFormat: contentLabel.textFormat + enabled: delegateModel.count > 0 onVisibleChanged: { popup.close() } @@ -52,7 +54,34 @@ ComboBox } ] - background: UM.UnderlineBackground{} + background: UM.UnderlineBackground + { + //Rectangle for highlighting when this combobox needs to pulse. + Rectangle + { + anchors.fill: parent + opacity: 0 + color: UM.Theme.getColor("warning") + + SequentialAnimation on opacity + { + id: pulseAnimation + running: false + loops: 1 + alwaysRunToEnd: true + PropertyAnimation + { + to: 1 + duration: 300 + } + PropertyAnimation + { + to: 0 + duration : 2000 + } + } + } + } indicator: UM.ColorImage { @@ -146,7 +175,7 @@ ComboBox anchors.rightMargin: UM.Theme.getSize("setting_unit_margin").width text: delegateItem.text - textFormat: Text.PlainText + textFormat: control.textFormat color: UM.Theme.getColor("setting_control_text") elide: Text.ElideRight wrapMode: Text.NoWrap @@ -162,4 +191,9 @@ ComboBox text: delegateLabel.truncated ? delegateItem.text : "" } } + + function pulse() + { + pulseAnimation.restart(); + } } diff --git a/resources/themes/cura-light/icons/default/GearCheck.svg b/resources/themes/cura-light/icons/default/GearCheck.svg new file mode 100644 index 0000000000..1ec89a5a8d --- /dev/null +++ b/resources/themes/cura-light/icons/default/GearCheck.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/themes/cura-light/icons/default/Nut.svg b/resources/themes/cura-light/icons/default/Nut.svg new file mode 100644 index 0000000000..77df86d31b --- /dev/null +++ b/resources/themes/cura-light/icons/default/Nut.svg @@ -0,0 +1,8 @@ + + + + + + + diff --git a/resources/themes/cura-light/icons/default/Visual.svg b/resources/themes/cura-light/icons/default/Visual.svg new file mode 100644 index 0000000000..8d75b7feb4 --- /dev/null +++ b/resources/themes/cura-light/icons/default/Visual.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index b3ee4c912b..83eb13f0cb 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -133,6 +133,11 @@ "weight": 400, "family": "Noto Sans" }, + "small_bold": { + "size": 0.9, + "weight": 700, + "family": "Noto Sans" + }, "small_ja_JP": { "size": 0.9, "weight": 400, @@ -637,6 +642,10 @@ "marketplace_large_icon": [4.0, 4.0], - "preferences_page_list_item": [8.0, 2.0] + "preferences_page_list_item": [8.0, 2.0], + + "recommended_button_icon": [1.7, 1.7], + + "reset_profile_icon": [1, 1] } }