From b919be48bc241a485d008a1c4093646eb4459da1 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Fri, 1 Nov 2019 10:50:02 +0100 Subject: [PATCH 01/19] Introduce new API call for fetching package updates --- plugins/Toolbox/src/Toolbox.py | 41 +++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 08b6c80b1d..c63d36d8c4 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -48,7 +48,7 @@ class Toolbox(QObject, Extension): self._download_progress = 0 # type: float self._is_downloading = False # type: bool self._network_manager = None # type: Optional[QNetworkAccessManager] - self._request_headers = [] # type: List[Tuple[bytes, bytes]] + self._request_headers = [] # type: List[Tuple[bytes, bytes]] self._updateRequestHeader() self._request_urls = {} # type: Dict[str, QUrl] @@ -59,13 +59,15 @@ class Toolbox(QObject, Extension): # The responses as given by the server parsed to a list. self._server_response_data = { "authors": [], - "packages": [] + "packages": [], + "updates": [], } # type: Dict[str, List[Any]] # Models: self._models = { "authors": AuthorsModel(self), "packages": PackagesModel(self), + "updates": PackagesModel(self), } # type: Dict[str, Union[AuthorsModel, PackagesModel]] self._plugins_showcase_model = PackagesModel(self) @@ -186,18 +188,22 @@ class Toolbox(QObject, Extension): cloud_api_version = self._cloud_api_version, sdk_version = self._sdk_version ) + + installed_package_ids_with_versions = [":".join(items) for items in + self._package_manager.getAllInstalledPackageIdsAndVersions()] + installed_packages_query = ",".join(installed_package_ids_with_versions) self._request_urls = { "authors": QUrl("{base_url}/authors".format(base_url = self._api_url)), - "packages": QUrl("{base_url}/packages".format(base_url = self._api_url)) + "packages": QUrl("{base_url}/packages".format(base_url = self._api_url)), + "updates": QUrl("{base_url}/packages/package-updates?installed_packages={query}".format( + base_url = self._api_url, query = installed_packages_query)) } - # Request the latest and greatest! - self._fetchPackageData() + # On boot we check which packages have updates. + if len(installed_package_ids_with_versions) > 0: + self._fetchPackageUpdates() - def _fetchPackageData(self): - # Create the network manager: - # This was formerly its own function but really had no reason to be as - # it was never called more than once ever. + def _prepareNetworkManager(self): if self._network_manager is not None: self._network_manager.finished.disconnect(self._onRequestFinished) self._network_manager.networkAccessibleChanged.disconnect(self._onNetworkAccessibleChanged) @@ -205,10 +211,15 @@ class Toolbox(QObject, Extension): self._network_manager.finished.connect(self._onRequestFinished) self._network_manager.networkAccessibleChanged.connect(self._onNetworkAccessibleChanged) + def _fetchPackageUpdates(self): + self._prepareNetworkManager() + self._makeRequestByType("updates") + + def _fetchPackageData(self): + self._prepareNetworkManager() # Make remote requests: self._makeRequestByType("packages") self._makeRequestByType("authors") - # Gather installed packages: self._updateInstalledModels() @@ -234,7 +245,7 @@ class Toolbox(QObject, Extension): if not plugin_path: return None path = os.path.join(plugin_path, "resources", "qml", qml_name) - + dialog = self._application.createQmlComponent(path, {"toolbox": self}) if not dialog: raise Exception("Failed to create Marketplace dialog") @@ -618,7 +629,7 @@ class Toolbox(QObject, Extension): if not self._models[response_type]: Logger.log("e", "Could not find the %s model.", response_type) break - + self._server_response_data[response_type] = json_data["data"] self._models[response_type].setMetadata(self._server_response_data[response_type]) @@ -630,6 +641,10 @@ class Toolbox(QObject, Extension): elif response_type == "authors": self._models[response_type].setFilter({"package_types": "material"}) self._models[response_type].setFilter({"tags": "generic"}) + elif response_type == "updates": + # Tell the package manager that there's a new set of updates available. + packages = set([pkg["package_id"] for pkg in self._server_response_data[response_type]]) + self._package_manager.setPackagesWithUpdate(packages) self.metadataChanged.emit() @@ -660,7 +675,7 @@ class Toolbox(QObject, Extension): self.setIsDownloading(False) self._download_reply = cast(QNetworkReply, self._download_reply) self._download_reply.downloadProgress.disconnect(self._onDownloadProgress) - + # Check if the download was sucessfull if self._download_reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200: try: From b1316b8a053906949a444ca8c7c685324ef711fa Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Mon, 4 Nov 2019 12:56:41 +0100 Subject: [PATCH 02/19] Fix installed_packages query params --- plugins/Toolbox/src/Toolbox.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index c63d36d8c4..be62873a2f 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -189,9 +189,11 @@ class Toolbox(QObject, Extension): sdk_version = self._sdk_version ) + # We need to construct a query like installed_packages=ID:VERSION&installed_packages=ID:VERSION, etc. installed_package_ids_with_versions = [":".join(items) for items in self._package_manager.getAllInstalledPackageIdsAndVersions()] - installed_packages_query = ",".join(installed_package_ids_with_versions) + installed_packages_query = "&installed_packages=".join(installed_package_ids_with_versions) + self._request_urls = { "authors": QUrl("{base_url}/authors".format(base_url = self._api_url)), "packages": QUrl("{base_url}/packages".format(base_url = self._api_url)), From 870db0641bdb89c3efd06f58c761454d1c31419d Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 5 Nov 2019 13:11:49 +0100 Subject: [PATCH 03/19] Ensure that machines with 1.75mm filament select the right preferred material CURA-6950 --- cura/Machines/VariantNode.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/cura/Machines/VariantNode.py b/cura/Machines/VariantNode.py index 8275c70e62..b2115ca099 100644 --- a/cura/Machines/VariantNode.py +++ b/cura/Machines/VariantNode.py @@ -85,11 +85,20 @@ class VariantNode(ContainerNode): for base_material, material_node in self.materials.items(): if self.machine.preferred_material == base_material and approximate_diameter == int(material_node.getMetaDataEntry("approximate_diameter")): return material_node - # First fallback: Choose any material with matching diameter. + + # First fallback: Check if we should be checking for the 175 variant. + if approximate_diameter == 2: + preferred_material = self.machine.preferred_material + "_175" + for base_material, material_node in self.materials.items(): + if preferred_material == base_material and approximate_diameter == int(material_node.getMetaDataEntry("approximate_diameter")): + return material_node + + # Second fallback: Choose any material with matching diameter. for material_node in self.materials.values(): if material_node.getMetaDataEntry("approximate_diameter") and approximate_diameter == int(material_node.getMetaDataEntry("approximate_diameter")): Logger.log("w", "Could not find preferred material %s, falling back to whatever works", self.machine.preferred_material) return material_node + fallback = next(iter(self.materials.values())) # Should only happen with empty material node. Logger.log("w", "Could not find preferred material {preferred_material} with diameter {diameter} for variant {variant_id}, falling back to {fallback}.".format( preferred_material = self.machine.preferred_material, From 674d8d387b0769bbd698e69c6115286bedf4f31e Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 5 Nov 2019 14:52:12 +0100 Subject: [PATCH 04/19] Move translations for intent to it's own file CURA-6956 --- cura/Machines/Models/IntentCategoryModel.py | 28 +++------------------ cura/Machines/Models/IntentTranslations.py | 20 +++++++++++++++ 2 files changed, 24 insertions(+), 24 deletions(-) create mode 100644 cura/Machines/Models/IntentTranslations.py diff --git a/cura/Machines/Models/IntentCategoryModel.py b/cura/Machines/Models/IntentCategoryModel.py index cb81aec3c7..a09d6ce3c4 100644 --- a/cura/Machines/Models/IntentCategoryModel.py +++ b/cura/Machines/Models/IntentCategoryModel.py @@ -2,8 +2,8 @@ #Cura is released under the terms of the LGPLv3 or higher. from PyQt5.QtCore import Qt, QTimer -import collections from typing import TYPE_CHECKING, Optional, Dict +from cura.Machines.Models.IntentTranslations import intent_translations from cura.Machines.Models.IntentModel import IntentModel from cura.Settings.IntentManager import IntentManager @@ -29,26 +29,6 @@ class IntentCategoryModel(ListModel): modelUpdated = pyqtSignal() - # Translations to user-visible string. Ordered by weight. - # TODO: Create a solution for this name and weight to be used dynamically. - _translations = collections.OrderedDict() # type: "collections.OrderedDict[str,Dict[str,Optional[str]]]" - _translations["default"] = { - "name": catalog.i18nc("@label", "Default") - } - _translations["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.") - } - _translations["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.") - } - _translations["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.") - } - - ## Creates a new model for a certain intent category. # \param The category to list the intent profiles for. def __init__(self, intent_category: str) -> None: @@ -99,15 +79,15 @@ class IntentCategoryModel(ListModel): "name": IntentCategoryModel.translation(category, "name", catalog.i18nc("@label", "Unknown")), "description": IntentCategoryModel.translation(category, "description", None), "intent_category": category, - "weight": list(self._translations.keys()).index(category), + "weight": list(intent_translations).index(category), "qualities": qualities }) result.sort(key = lambda k: k["weight"]) self.setItems(result) - ## Get a display value for a category. See IntenCategoryModel._translations + ## Get a display value for a category. ## for categories and keys @staticmethod def translation(category: str, key: str, default: Optional[str] = None): - display_strings = IntentCategoryModel._translations.get(category, {}) + display_strings = intent_translations.get(category, {}) return display_strings.get(key, default) diff --git a/cura/Machines/Models/IntentTranslations.py b/cura/Machines/Models/IntentTranslations.py new file mode 100644 index 0000000000..fd6a2db9ee --- /dev/null +++ b/cura/Machines/Models/IntentTranslations.py @@ -0,0 +1,20 @@ +import collections +from UM.i18n import i18nCatalog +catalog = i18nCatalog("cura") + +intent_translations = collections.OrderedDict() # type: "collections.OrderedDict[str, Dict[str, Optional[str]]]" +intent_translations["default"] = { + "name": catalog.i18nc("@label", "Default") +} +intent_translations["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.") +} +intent_translations["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.") +} +intent_translations["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.") +} \ No newline at end of file From ff8d8735de168a7ba61c1d8188f5ae353c4f786d Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 5 Nov 2019 14:57:44 +0100 Subject: [PATCH 05/19] Make the intents in QualityManagementModel also translated CURA-6956 --- cura/Machines/Models/QualityManagementModel.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cura/Machines/Models/QualityManagementModel.py b/cura/Machines/Models/QualityManagementModel.py index d8b0785778..6a73a1104f 100644 --- a/cura/Machines/Models/QualityManagementModel.py +++ b/cura/Machines/Models/QualityManagementModel.py @@ -14,6 +14,7 @@ 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 cura.Machines.Models.MachineModelUtils import fetchLayerHeight +from cura.Machines.Models.IntentTranslations import intent_translations from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") @@ -336,7 +337,7 @@ class QualityManagementModel(ListModel): "quality_type": quality_type, "quality_changes_group": None, "intent_category": intent_category, - "section_name": catalog.i18nc("@label", intent_category.capitalize()), + "section_name": catalog.i18nc("@label", intent_translations.get(intent_category, {}).get("name", catalog.i18nc("@label", "Unknown"))), }) # Sort by quality_type for each intent category result = sorted(result, key = lambda x: (x["intent_category"], x["quality_type"])) From 04304c1515c7d0e56af37deb0683c903ac048fc9 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 5 Nov 2019 14:59:44 +0100 Subject: [PATCH 06/19] Correctly sort the intents in QualityManagementModel CURA-6956 --- cura/Machines/Models/QualityManagementModel.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cura/Machines/Models/QualityManagementModel.py b/cura/Machines/Models/QualityManagementModel.py index 6a73a1104f..74dc8649d0 100644 --- a/cura/Machines/Models/QualityManagementModel.py +++ b/cura/Machines/Models/QualityManagementModel.py @@ -340,7 +340,8 @@ class QualityManagementModel(ListModel): "section_name": catalog.i18nc("@label", intent_translations.get(intent_category, {}).get("name", catalog.i18nc("@label", "Unknown"))), }) # Sort by quality_type for each intent category - result = sorted(result, key = lambda x: (x["intent_category"], x["quality_type"])) + + result = sorted(result, key = lambda x: (list(intent_translations).index(x["intent_category"]), x["quality_type"])) item_list += result # Create quality_changes group items From da92c36afbd4af1e56b421adbaf562acd0e3ec31 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 6 Nov 2019 17:22:58 +0100 Subject: [PATCH 07/19] Don't show tooltip immediately. --- .../Recommended/RecommendedQualityProfileSelector.qml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml b/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml index 5e58faec78..7c527f9448 100644 --- a/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml +++ b/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml @@ -160,14 +160,20 @@ Item enabled: model.description !== undefined acceptedButtons: Qt.NoButton // react to hover only, don't steal clicks - onEntered: + Timer { - base.showTooltip( + 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() } From 19d334f35c7232e87f77e7de55126959bf5753d8 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 7 Nov 2019 14:31:38 +0100 Subject: [PATCH 08/19] Fix typing imports. --- cura/Machines/Models/IntentTranslations.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cura/Machines/Models/IntentTranslations.py b/cura/Machines/Models/IntentTranslations.py index fd6a2db9ee..f78e8df128 100644 --- a/cura/Machines/Models/IntentTranslations.py +++ b/cura/Machines/Models/IntentTranslations.py @@ -1,5 +1,6 @@ import collections from UM.i18n import i18nCatalog +from typing import Dict, Optional catalog = i18nCatalog("cura") intent_translations = collections.OrderedDict() # type: "collections.OrderedDict[str, Dict[str, Optional[str]]]" From 0b450ef9f468899c8b36d18e02d8edcc67879757 Mon Sep 17 00:00:00 2001 From: Nino van Hooff Date: Thu, 7 Nov 2019 15:28:09 +0100 Subject: [PATCH 09/19] Fix linked materials not being found due to key casing CURA-6917 --- cura/Settings/ContainerManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index 4a4a7b64dd..f2917ca21f 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -339,7 +339,7 @@ class ContainerManager(QObject): # \return A list of names of materials with the same GUID. @pyqtSlot("QVariant", bool, result = "QStringList") def getLinkedMaterials(self, material_node: "MaterialNode", exclude_self: bool = False) -> List[str]: - same_guid = ContainerRegistry.getInstance().findInstanceContainersMetadata(guid = material_node.guid) + same_guid = ContainerRegistry.getInstance().findInstanceContainersMetadata(GUID = material_node.guid) if exclude_self: return [metadata["name"] for metadata in same_guid if metadata["base_file"] != material_node.base_file] else: From 6e79f489e2087c32c84d82390ad514340165ff96 Mon Sep 17 00:00:00 2001 From: Nino van Hooff Date: Thu, 7 Nov 2019 15:28:53 +0100 Subject: [PATCH 10/19] When multiple linked materials are found, only display the first CURA-6917 --- resources/qml/Preferences/Materials/MaterialsView.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/Preferences/Materials/MaterialsView.qml b/resources/qml/Preferences/Materials/MaterialsView.qml index f781497081..8ada441f1e 100644 --- a/resources/qml/Preferences/Materials/MaterialsView.qml +++ b/resources/qml/Preferences/Materials/MaterialsView.qml @@ -46,7 +46,7 @@ TabView { return "" } - return linkedMaterials.join(", "); + return linkedMaterials[0]; } function getApproximateDiameter(diameter) From 3636eed96441e3c7e57bb1c519b7efb8e4c1bc37 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 7 Nov 2019 17:19:18 +0100 Subject: [PATCH 11/19] Instead of selecting first, return only uniques. part of CURA-6917 --- cura/Settings/ContainerManager.py | 4 ++-- resources/qml/Preferences/Materials/MaterialsView.qml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index f2917ca21f..ff129d35e2 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -341,9 +341,9 @@ class ContainerManager(QObject): def getLinkedMaterials(self, material_node: "MaterialNode", exclude_self: bool = False) -> List[str]: same_guid = ContainerRegistry.getInstance().findInstanceContainersMetadata(GUID = material_node.guid) if exclude_self: - return [metadata["name"] for metadata in same_guid if metadata["base_file"] != material_node.base_file] + return list({meta["name"] for meta in same_guid if meta["base_file"] != material_node.base_file}) else: - return [metadata["name"] for metadata in same_guid] + return list({meta["name"] for meta in same_guid}) ## Unlink a material from all other materials by creating a new GUID # \param material_id \type{str} the id of the material to create a new GUID for. diff --git a/resources/qml/Preferences/Materials/MaterialsView.qml b/resources/qml/Preferences/Materials/MaterialsView.qml index 8ada441f1e..57253b9dff 100644 --- a/resources/qml/Preferences/Materials/MaterialsView.qml +++ b/resources/qml/Preferences/Materials/MaterialsView.qml @@ -46,7 +46,7 @@ TabView { return "" } - return linkedMaterials[0]; + return linkedMaterials; } function getApproximateDiameter(diameter) From d93f6f25d20b231fe6c75e3b8d40d158d5c35ff5 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 11 Nov 2019 10:47:53 +0100 Subject: [PATCH 12/19] Prevent "sticky" tooltips When quickly moving the mouse in and out, the timer would still be running, so the tooltip would become active once the mouse moved out of the area. --- .../Recommended/RecommendedQualityProfileSelector.qml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml b/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml index 7c527f9448..337aff573f 100644 --- a/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml +++ b/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml @@ -174,7 +174,11 @@ Item } onEntered: intentTooltipTimer.start() - onExited: base.hideTooltip() + onExited: + { + base.hideTooltip() + intentTooltipTimer.stop() + } } NoIntentIcon // This icon has hover priority over intentDescriptionHoverArea, so draw it above it. From e2414ed5c94ce8776f6b3b1a771da50c888b24f4 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 11 Nov 2019 15:58:39 +0100 Subject: [PATCH 13/19] Also log the exception when the connection failed to connect --- cura/OAuth2/AuthorizationHelpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/OAuth2/AuthorizationHelpers.py b/cura/OAuth2/AuthorizationHelpers.py index 08309fa30e..9fc01ba50b 100644 --- a/cura/OAuth2/AuthorizationHelpers.py +++ b/cura/OAuth2/AuthorizationHelpers.py @@ -99,7 +99,7 @@ class AuthorizationHelpers: }) except requests.exceptions.ConnectionError: # Connection was suddenly dropped. Nothing we can do about that. - Logger.log("w", "Something failed while attempting to parse the JWT token") + Logger.logException("w", "Something failed while attempting to parse the JWT token") return None if token_request.status_code not in (200, 201): Logger.log("w", "Could not retrieve token data from auth server: %s", token_request.text) From 17b8e9930c32aebedab50dc697d43242b32a916f Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 12 Nov 2019 13:28:12 +0100 Subject: [PATCH 14/19] Add handling for case where variant could not be found CURA-6975 --- cura/Machines/Models/BaseMaterialsModel.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cura/Machines/Models/BaseMaterialsModel.py b/cura/Machines/Models/BaseMaterialsModel.py index 73d9d48b22..db660704b5 100644 --- a/cura/Machines/Models/BaseMaterialsModel.py +++ b/cura/Machines/Models/BaseMaterialsModel.py @@ -6,6 +6,7 @@ from typing import Dict, Set from PyQt5.QtCore import Qt, QTimer, pyqtSignal, pyqtProperty from UM.Qt.ListModel import ListModel +from UM.Logger import Logger import cura.CuraApplication # Imported like this to prevent a circular reference. from cura.Machines.ContainerTree import ContainerTree @@ -153,7 +154,12 @@ class BaseMaterialsModel(ListModel): if not extruder_stack: return nozzle_name = extruder_stack.variant.getName() - materials = ContainerTree.getInstance().machines[global_stack.definition.getId()].variants[nozzle_name].materials + machine_node = ContainerTree.getInstance().machines[global_stack.definition.getId()] + if nozzle_name not in machine_node.variants: + Logger.log("w", "Unable to find variant %s in container tree", nozzle_name) + self._available_materials = {} + return + materials = machine_node.variants[nozzle_name].materials approximate_material_diameter = extruder_stack.getApproximateMaterialDiameter() self._available_materials = {key: material for key, material in materials.items() if float(material.getMetaDataEntry("approximate_diameter", -1)) == approximate_material_diameter} From 8228c6a7433ce7c3302fb89b637efcfd2b5e7614 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 12 Nov 2019 14:22:18 +0100 Subject: [PATCH 15/19] Make sure position is not None CURA-6974 --- cura/Machines/MachineNode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/Machines/MachineNode.py b/cura/Machines/MachineNode.py index 72652f2987..8d69ffdc8d 100644 --- a/cura/Machines/MachineNode.py +++ b/cura/Machines/MachineNode.py @@ -140,7 +140,7 @@ class MachineNode(ContainerNode): elif groups_by_name[name].intent_category == "default": # Intent category should be stored as "default" if everything is default or as the intent if any of the extruder have an actual intent. groups_by_name[name].intent_category = quality_changes.get("intent_category", "default") - if "position" in quality_changes: # An extruder profile. + if quality_changes.get("position") is not None: # An extruder profile. groups_by_name[name].metadata_per_extruder[int(quality_changes["position"])] = quality_changes else: # Global profile. groups_by_name[name].metadata_for_global = quality_changes From 77c194464e5edd70aedd255163513970dfcf8032 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 12 Nov 2019 14:33:54 +0100 Subject: [PATCH 16/19] Fix tests --- tests/Machines/TestVariantNode.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/Machines/TestVariantNode.py b/tests/Machines/TestVariantNode.py index bc860058a1..6e3a469877 100644 --- a/tests/Machines/TestVariantNode.py +++ b/tests/Machines/TestVariantNode.py @@ -139,6 +139,7 @@ def test_preferredMaterialExactMatch(empty_variant_node): "some_different_material": MagicMock(getMetaDataEntry = lambda x: 3), "preferred_material_base_file": MagicMock(getMetaDataEntry = lambda x: 3) # Exact match. } + empty_variant_node.machine.preferred_material = "preferred_material_base_file" assert empty_variant_node.preferredMaterial(approximate_diameter = 3) == empty_variant_node.materials["preferred_material_base_file"], "It should match exactly on this one since it's the preferred material." @@ -149,6 +150,7 @@ def test_preferredMaterialSubmaterial(empty_variant_node): "some_different_material": MagicMock(getMetaDataEntry = lambda x: 3), "preferred_material_base_file_aa04": MagicMock(getMetaDataEntry = lambda x: 3) # This is a submaterial of the preferred material. } + empty_variant_node.machine.preferred_material = "preferred_material_base_file_aa04" assert empty_variant_node.preferredMaterial(approximate_diameter = 3) == empty_variant_node.materials["preferred_material_base_file_aa04"], "It should match on the submaterial just as well." @@ -160,6 +162,7 @@ def test_preferredMaterialDiameter(empty_variant_node): "preferred_material_wrong_diameter": MagicMock(getMetaDataEntry = lambda x: 2), # Approximate diameter is 2 instead of 3. "preferred_material_correct_diameter": MagicMock(getMetaDataEntry = lambda x: 3) # Correct approximate diameter. } + empty_variant_node.machine.preferred_material = "preferred_material_correct_diameter" assert empty_variant_node.preferredMaterial(approximate_diameter = 3) == empty_variant_node.materials["preferred_material_correct_diameter"], "It should match only on the material with correct diameter." From fe27193c98d3b845e72cd58ed0535ca11d286dfb Mon Sep 17 00:00:00 2001 From: Nino van Hooff Date: Tue, 12 Nov 2019 15:39:44 +0100 Subject: [PATCH 17/19] Add dict key check for a crash found in IntentManager CURA-6976 --- cura/Settings/IntentManager.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cura/Settings/IntentManager.py b/cura/Settings/IntentManager.py index c1d59fb84a..879db4f4de 100644 --- a/cura/Settings/IntentManager.py +++ b/cura/Settings/IntentManager.py @@ -36,8 +36,12 @@ class IntentManager(QObject): # \return A list of metadata dictionaries matching the search criteria, or # an empty list if nothing was found. def intentMetadatas(self, definition_id: str, nozzle_name: str, material_base_file: str) -> List[Dict[str, Any]]: - material_node = ContainerTree.getInstance().machines[definition_id].variants[nozzle_name].materials[material_base_file] intent_metadatas = [] + materials = ContainerTree.getInstance().machines[definition_id].variants[nozzle_name].materials + if material_base_file not in materials: + return intent_metadatas + + material_node = materials[material_base_file] for quality_node in material_node.qualities.values(): for intent_node in quality_node.intents.values(): intent_metadatas.append(intent_node.getMetadata()) From c08e7df277514fce8ed0a63ec280756bc1071a9a Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 13 Nov 2019 11:10:03 +0100 Subject: [PATCH 18/19] Fix typing CURA-6976 --- cura/Settings/IntentManager.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cura/Settings/IntentManager.py b/cura/Settings/IntentManager.py index 879db4f4de..c53022fb6c 100644 --- a/cura/Settings/IntentManager.py +++ b/cura/Settings/IntentManager.py @@ -3,11 +3,13 @@ from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot from typing import Any, Dict, List, Optional, Set, Tuple, TYPE_CHECKING -import cura.CuraApplication + from UM.Logger import Logger +from UM.Settings.InstanceContainer import InstanceContainer + +import cura.CuraApplication from cura.Machines.ContainerTree import ContainerTree from cura.Settings.cura_empty_instance_containers import empty_intent_container -from UM.Settings.InstanceContainer import InstanceContainer if TYPE_CHECKING: from UM.Settings.InstanceContainer import InstanceContainer @@ -36,7 +38,7 @@ class IntentManager(QObject): # \return A list of metadata dictionaries matching the search criteria, or # an empty list if nothing was found. def intentMetadatas(self, definition_id: str, nozzle_name: str, material_base_file: str) -> List[Dict[str, Any]]: - intent_metadatas = [] + intent_metadatas = [] # type: List[Dict[str, Any]] materials = ContainerTree.getInstance().machines[definition_id].variants[nozzle_name].materials if material_base_file not in materials: return intent_metadatas From 377a21071b170c605c7677b8af11219b43a574da Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 18 Nov 2019 11:33:36 +0100 Subject: [PATCH 19/19] Remove color from discard dialog text, since it uses system style Originally a lot of text would not be readable when in dark theme because the text would be light, but the background would always be white. --- resources/qml/Dialogs/DiscardOrKeepProfileChangesDialog.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/qml/Dialogs/DiscardOrKeepProfileChangesDialog.qml b/resources/qml/Dialogs/DiscardOrKeepProfileChangesDialog.qml index afa9fda0bd..316878a2bd 100644 --- a/resources/qml/Dialogs/DiscardOrKeepProfileChangesDialog.qml +++ b/resources/qml/Dialogs/DiscardOrKeepProfileChangesDialog.qml @@ -100,7 +100,6 @@ UM.Dialog { text: styleData.value font: UM.Theme.getFont("system") - color: UM.Theme.getColor("setting_control_disabled_text") } }