diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 113e7b3ff4..1073288b7a 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 Ultimaker B.V. +# Copyright (c) 2023 UltiMaker # Cura is released under the terms of the LGPLv3 or higher. import enum import os @@ -147,6 +147,7 @@ class CuraApplication(QtApplication): DefinitionChangesContainer = Resources.UserType + 10 SettingVisibilityPreset = Resources.UserType + 11 IntentInstanceContainer = Resources.UserType + 12 + ImageFiles = Resources.UserType + 13 pyqtEnum(ResourceTypes) @@ -425,6 +426,7 @@ class CuraApplication(QtApplication): Resources.addStorageType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes") Resources.addStorageType(self.ResourceTypes.SettingVisibilityPreset, "setting_visibility") Resources.addStorageType(self.ResourceTypes.IntentInstanceContainer, "intent") + Resources.addStorageType(self.ResourceTypes.ImageFiles, "images") self._container_registry.addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality") self._container_registry.addResourceType(self.ResourceTypes.QualityChangesInstanceContainer, "quality_changes") @@ -435,6 +437,7 @@ class CuraApplication(QtApplication): self._container_registry.addResourceType(self.ResourceTypes.MachineStack, "machine") self._container_registry.addResourceType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes") self._container_registry.addResourceType(self.ResourceTypes.IntentInstanceContainer, "intent") + self._container_registry.addResourceType(self.ResourceTypes.ImageFiles, "images") Resources.addType(self.ResourceTypes.QmlFiles, "qml") Resources.addType(self.ResourceTypes.Firmware, "firmware") diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index a8f5b24a7b..e6879c3294 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018 Ultimaker B.V. +# Copyright (c) 2023 UltiMaker # Cura is released under the terms of the LGPLv3 or higher. import glob import os @@ -55,7 +55,9 @@ class CuraPackageManager(PackageManager): def initialize(self) -> None: self._installation_dirs_dict["materials"] = Resources.getStoragePath(CuraApplication.ResourceTypes.MaterialInstanceContainer) self._installation_dirs_dict["qualities"] = Resources.getStoragePath(CuraApplication.ResourceTypes.QualityInstanceContainer) - self._installation_dirs_dict["variants"] = Resources.getStoragePath(CuraApplication.ResourceTypes.VariantInstanceContainer) + self._installation_dirs_dict["variants"] = Resources.getStoragePath( + CuraApplication.ResourceTypes.VariantInstanceContainer) + self._installation_dirs_dict["images"] = Resources.getStoragePath(CuraApplication.ResourceTypes.ImageFiles) # Due to a bug in Cura 5.1.0 we needed to change the directory structure of the curapackage on the server side (See SD-3871). # Although the material intent profiles will be installed in the `intent` folder, the curapackage from the server side will diff --git a/cura/Machines/Models/IntentSelectionModel.py b/cura/Machines/Models/IntentSelectionModel.py index 1eee3c0d02..1443d1427b 100644 --- a/cura/Machines/Models/IntentSelectionModel.py +++ b/cura/Machines/Models/IntentSelectionModel.py @@ -1,29 +1,32 @@ -# Copyright (c) 2022 Ultimaker B.V. +# Copyright (c) 2023 UltiMaker # Cura is released under the terms of the LGPLv3 or higher. import collections -from typing import OrderedDict, Optional +from typing import Optional -from PyQt6.QtCore import Qt, QTimer, QObject +from PyQt6.QtCore import Qt, QTimer, QObject, QUrl import cura from UM import i18nCatalog from UM.Logger import Logger from UM.Qt.ListModel import ListModel +from UM.Resources import Resources from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.Interfaces import ContainerInterface + +from cura.Machines.Models.IntentCategoryModel import IntentCategoryModel 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 + CustomIconRole = Qt.ItemDataRole.UserRole + 6 def __init__(self, parent: Optional[QObject] = None) -> None: super().__init__(parent) @@ -33,6 +36,7 @@ class IntentSelectionModel(ListModel): self.addRoleName(self.WeightRole, "weight") self.addRoleName(self.DescriptionRole, "description") self.addRoleName(self.IconRole, "icon") + self.addRoleName(self.CustomIconRole, "custom_icon") application = cura.CuraApplication.CuraApplication.getInstance() @@ -53,36 +57,8 @@ class IntentSelectionModel(ListModel): 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" - } - default_profile_information["annealing"] = { - "name": catalog.i18nc("@label", "Annealing"), - "description": catalog.i18nc("@text", - "The annealing profile requires post-processing in an oven after the print is finished. This profile retains the dimensional accuracy of the printed part after annealing and improves strength, stiffness, and thermal resistance."), - "icon": "Anneal" - } - return default_profile_information + _default_intent_categories = ["default", "visual", "engineering", "quick", "annealing"] + _icons = {"default": "GearCheck", "visual": "Visual", "engineering": "Nut", "quick": "SpeedOMeter", "annealing": "Anneal"} def _onContainerChange(self, container: ContainerInterface) -> None: """Updates the list of intents if an intent profile was added or removed.""" @@ -108,25 +84,42 @@ class IntentSelectionModel(ListModel): 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, {}) + for category in available_categories: - try: - weight = list(default_profile_info.keys()).index(category) - except ValueError: - weight = len(available_categories) + i + if category in self._default_intent_categories: + result.append({ + "name": IntentCategoryModel.translation(category, "name", category.title()), + "description": IntentCategoryModel.translation(category, "description", None), + "icon": self._icons[category], + "custom_icon": None, + "intent_category": category, + "weight": self._default_intent_categories.index(category), + }) + else: + # There can be multiple intents with the same category, use one of these + # intent-metadata's for the icon/description defintions for the intent + intent_metadata = cura.CuraApplication.CuraApplication \ + .getInstance() \ + .getContainerRegistry() \ + .findContainersMetadata(type="intent", definition=global_stack.definition.getId(), + intent_category=category)[0] - 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, - }) + icon = intent_metadata.get("icon", None) + if icon is not None: + icon = QUrl.fromLocalFile( + Resources.getPath(cura.CuraApplication.CuraApplication.ResourceTypes.ImageFiles, icon)) + + result.append({ + "name": intent_metadata.get("name", category.title()), + "description": intent_metadata.get("description", None), + "custom_icon": icon, + "icon": None, + "intent_category": category, + "weight": 5, + }) result.sort(key=lambda k: k["weight"]) diff --git a/cura/Settings/DatabaseHandlers/IntentDatabaseHandler.py b/cura/Settings/DatabaseHandlers/IntentDatabaseHandler.py index cd6c662591..ba74a33cb7 100644 --- a/cura/Settings/DatabaseHandlers/IntentDatabaseHandler.py +++ b/cura/Settings/DatabaseHandlers/IntentDatabaseHandler.py @@ -1,4 +1,4 @@ -# Copyright (c) 2021 Ultimaker B.V. +# Copyright (c) 2023 UltiMaker # Cura is released under the terms of the LGPLv3 or higher. from UM.Settings.SQLQueryFactory import SQLQueryFactory @@ -10,8 +10,8 @@ class IntentDatabaseHandler(DatabaseMetadataContainerController): """The Database handler for Intent containers""" def __init__(self) -> None: - super().__init__(SQLQueryFactory(table = "intent", - fields = { + super().__init__(SQLQueryFactory(table="intent", + fields={ "id": "text", "name": "text", "quality_type": "text", @@ -20,6 +20,8 @@ class IntentDatabaseHandler(DatabaseMetadataContainerController): "definition": "text", "material": "text", "version": "text", - "setting_version": "text" + "setting_version": "text", + "icon": "text", + "description": "text", })) self._container_type = InstanceContainer diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 8de3fbf7d4..706aa52905 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -1665,15 +1665,16 @@ { "label": "Small Top/Bottom Width", "description": "Small top/bottom regions are filled with walls instead of the default top/bottom pattern. This helps to avoids jerky motions.", - "value": "skin_line_width * 2", - "default_value": 1, + "value": "0", + "default_value": 0, "minimum_value": "0", "maximum_value_warning": "skin_line_width * 10", "type": "float", - "enabled": "(top_layers > 0 or bottom_layers > 0) and top_bottom_pattern != 'concentric'", + "enabled": false, "limit_to_extruder": "top_bottom_extruder_nr", "settable_per_mesh": true, - "unit": "mm" + "unit": "mm", + "comment": "Disabled for 5.4.x, as we're worried about micro-segments in the infill. Also disabled in the engine, so forcing this > 0 will not do anything at the moment." }, "skin_no_small_gaps_heuristic": { diff --git a/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml b/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml index b9fbf04f9c..f72a5b7a5a 100644 --- a/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml +++ b/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml @@ -1,4 +1,4 @@ -// Copyright (c) 2022 Ultimaker B.V. +// Copyright (c) 2023 UltiMaker // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.10 @@ -32,6 +32,7 @@ Item { profileName: model.name icon: model.icon + custom_icon: model.custom_icon tooltipText: model.description ? model.description : "" selected: Cura.MachineManager.activeIntentCategory == model.intent_category diff --git a/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelectorButton.qml b/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelectorButton.qml index 4e912edfe0..37478781ca 100644 --- a/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelectorButton.qml +++ b/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelectorButton.qml @@ -1,4 +1,4 @@ -// Copyright (c) 2022 Ultimaker B.V. +// Copyright (c) 2023 UltiMaker // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.10 @@ -19,6 +19,7 @@ Rectangle property bool selected: false property string profileName: "" property string icon: "" + property string custom_icon: "" property alias tooltipText: tooltip.text signal clicked() @@ -55,22 +56,32 @@ Rectangle 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 != "" + visible: icon !== "" source: UM.Theme.getIcon(icon) color: UM.Theme.getColor("icon") } + UM.ColorImage + { + anchors.fill: parent + anchors.centerIn: parent + visible: custom_icon !== "" + source: custom_icon + color: UM.Theme.getColor("icon") + } + Rectangle { id: circle anchors.fill: parent radius: width anchors.verticalCenter: parent.verticalCenter - visible: icon == "" + visible: icon === "" && custom_icon === "" border.width: UM.Theme.getSize("thick_lining").width border.color: UM.Theme.getColor("text") diff --git a/resources/themes/cura-light/icons/default/Anneal.svg b/resources/themes/cura-light/icons/default/Anneal.svg index 613d2de2b6..63bf674cfb 100644 --- a/resources/themes/cura-light/icons/default/Anneal.svg +++ b/resources/themes/cura-light/icons/default/Anneal.svg @@ -1,6 +1,4 @@ - - - - +