1#Copyright (c) 2019 Ultimaker B.V.
2#Cura is released under the terms of the LGPLv3 or higher.
3
4import collections
5from PyQt5.QtCore import Qt, QTimer
6from typing import TYPE_CHECKING, Optional, Dict
7
8from cura.Machines.Models.IntentModel import IntentModel
9from cura.Settings.IntentManager import IntentManager
10from UM.Qt.ListModel import ListModel
11from UM.Settings.ContainerRegistry import ContainerRegistry #To update the list if anything changes.
12from PyQt5.QtCore import pyqtSignal
13import cura.CuraApplication
14if TYPE_CHECKING:
15    from UM.Settings.ContainerRegistry import ContainerInterface
16
17from UM.i18n import i18nCatalog
18catalog = i18nCatalog("cura")
19
20
21class IntentCategoryModel(ListModel):
22    """Lists the intent categories that are available for the current printer configuration. """
23
24    NameRole = Qt.UserRole + 1
25    IntentCategoryRole = Qt.UserRole + 2
26    WeightRole = Qt.UserRole + 3
27    QualitiesRole = Qt.UserRole + 4
28    DescriptionRole = Qt.UserRole + 5
29
30    modelUpdated = pyqtSignal()
31
32    _translations = collections.OrderedDict()  # type: "collections.OrderedDict[str,Dict[str,Optional[str]]]"
33
34    @classmethod
35    def _get_translations(cls):
36        """Translations to user-visible string. Ordered by weight.
37
38        TODO: Create a solution for this name and weight to be used dynamically.
39        """
40        if len(cls._translations) == 0:
41            cls._translations["default"] = {
42                "name": catalog.i18nc("@label", "Default")
43            }
44            cls._translations["visual"] = {
45                "name": catalog.i18nc("@label", "Visual"),
46                "description": catalog.i18nc("@text", "The visual profile is designed to print visual prototypes and models with the intent of high visual and surface quality.")
47            }
48            cls._translations["engineering"] = {
49                "name": catalog.i18nc("@label", "Engineering"),
50                "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.")
51            }
52            cls._translations["quick"] = {
53                "name": catalog.i18nc("@label", "Draft"),
54                "description": catalog.i18nc("@text", "The draft profile is designed to print initial prototypes and concept validation with the intent of significant print time reduction.")
55            }
56        return cls._translations
57
58    def __init__(self, intent_category: str) -> None:
59        """Creates a new model for a certain intent category.
60
61        :param intent_category: category to list the intent profiles for.
62        """
63
64        super().__init__()
65        self._intent_category = intent_category
66
67        self.addRoleName(self.NameRole, "name")
68        self.addRoleName(self.IntentCategoryRole, "intent_category")
69        self.addRoleName(self.WeightRole, "weight")
70        self.addRoleName(self.QualitiesRole, "qualities")
71        self.addRoleName(self.DescriptionRole, "description")
72
73        application = cura.CuraApplication.CuraApplication.getInstance()
74
75        ContainerRegistry.getInstance().containerAdded.connect(self._onContainerChange)
76        ContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChange)
77        machine_manager = cura.CuraApplication.CuraApplication.getInstance().getMachineManager()
78        machine_manager.activeMaterialChanged.connect(self.update)
79        machine_manager.activeVariantChanged.connect(self.update)
80        machine_manager.extruderChanged.connect(self.update)
81
82        extruder_manager = application.getExtruderManager()
83        extruder_manager.extrudersChanged.connect(self.update)
84
85        self._update_timer = QTimer()
86        self._update_timer.setInterval(500)
87        self._update_timer.setSingleShot(True)
88        self._update_timer.timeout.connect(self._update)
89
90        self.update()
91
92    def _onContainerChange(self, container: "ContainerInterface") -> None:
93        """Updates the list of intents if an intent profile was added or removed."""
94
95        if container.getMetaDataEntry("type") == "intent":
96            self.update()
97
98    def update(self):
99        self._update_timer.start()
100
101    def _update(self) -> None:
102        """Updates the list of intents."""
103
104        available_categories = IntentManager.getInstance().currentAvailableIntentCategories()
105        result = []
106        for category in available_categories:
107            qualities = IntentModel()
108            qualities.setIntentCategory(category)
109            result.append({
110                "name": IntentCategoryModel.translation(category, "name", catalog.i18nc("@label", "Unknown")),
111                "description": IntentCategoryModel.translation(category, "description", None),
112                "intent_category": category,
113                "weight": list(IntentCategoryModel._get_translations().keys()).index(category),
114                "qualities": qualities
115            })
116        result.sort(key = lambda k: k["weight"])
117        self.setItems(result)
118
119    @staticmethod
120    def translation(category: str, key: str, default: Optional[str] = None):
121        """Get a display value for a category.for categories and keys"""
122
123        display_strings = IntentCategoryModel._get_translations().get(category, {})
124        return display_strings.get(key, default)
125