From fbf4d42f0654419bd5123b8c0e289bed5830e272 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 27 Aug 2019 15:22:26 +0200 Subject: [PATCH 01/50] Active quality group can be None. part of CURA-6600 --- cura/Settings/MachineManager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index e0293c200f..3447581bca 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -598,6 +598,8 @@ class MachineManager(QObject): global_container_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() if not global_container_stack: return False + if not self.activeQualityGroup: + return False return self.activeQualityGroup.is_available @pyqtProperty(bool, notify = activeQualityGroupChanged) From d5a8b2640f86673a740e85c468c2e5c334b8f2ea Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 27 Aug 2019 15:46:47 +0200 Subject: [PATCH 02/50] Fix 'getQualityChangesGroups' now gives a list issues. part of CURA-6600 --- cura/Machines/Models/QualityManagementModel.py | 6 +++--- cura/Machines/QualityManager.py | 5 +++-- plugins/3MFReader/ThreeMFWorkspaceReader.py | 6 +++--- tests/TestQualityManager.py | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/cura/Machines/Models/QualityManagementModel.py b/cura/Machines/Models/QualityManagementModel.py index 2a661ec49e..aa2cb3ba5f 100644 --- a/cura/Machines/Models/QualityManagementModel.py +++ b/cura/Machines/Models/QualityManagementModel.py @@ -44,11 +44,11 @@ class QualityManagementModel(ListModel): return quality_group_dict = ContainerTree.getInstance().getCurrentQualityGroups() - quality_changes_group_dict = self._quality_manager.getQualityChangesGroups(global_stack) + quality_changes_group_list = self._quality_manager.getQualityChangesGroups(global_stack) available_quality_types = set(quality_type for quality_type, quality_group in quality_group_dict.items() if quality_group.is_available) - if not available_quality_types and not quality_changes_group_dict: + if not available_quality_types and not quality_changes_group_list: # Nothing to show self.setItems([]) return @@ -69,7 +69,7 @@ class QualityManagementModel(ListModel): # Create quality_changes group items quality_changes_item_list = [] - for quality_changes_group in quality_changes_group_dict.values(): + for quality_changes_group in quality_changes_group_list: quality_group = quality_group_dict.get(quality_changes_group.quality_type) item = {"name": quality_changes_group.name, "is_read_only": False, diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py index 3bb77504c8..c03c580197 100644 --- a/cura/Machines/QualityManager.py +++ b/cura/Machines/QualityManager.py @@ -1,7 +1,7 @@ # Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import TYPE_CHECKING, Optional, Dict +from typing import TYPE_CHECKING, Optional, Dict, List from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot @@ -14,6 +14,7 @@ import cura.CuraApplication from cura.Settings.ExtruderStack import ExtruderStack from cura.Machines.ContainerTree import ContainerTree # The implementation that replaces this manager, to keep the deprecated interface working. +from .QualityChangesGroup import QualityChangesGroup from .QualityGroup import QualityGroup from .QualityNode import QualityNode @@ -75,7 +76,7 @@ class QualityManager(QObject): return # Returns a dict of "custom profile name" -> QualityChangesGroup - def getQualityChangesGroups(self, machine: "GlobalStack") -> dict: + def getQualityChangesGroups(self, machine: "GlobalStack") -> List[QualityChangesGroup]: variant_names = [extruder.variant.getName() for extruder in machine.extruders.values()] material_bases = [extruder.material.getMetaDataEntry("base_file") for extruder in machine.extruders.values()] extruder_enabled = [extruder.isEnabled for extruder in machine.extruders.values()] diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index b9b69d1ae0..d5fa1cb272 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -1003,11 +1003,11 @@ class ThreeMFWorkspaceReader(WorkspaceReader): global_stack.setMetaDataEntry(key, value) if self._quality_changes_to_apply: - quality_changes_group_dict = quality_manager.getQualityChangesGroups(global_stack) - if self._quality_changes_to_apply not in quality_changes_group_dict: + quality_changes_group_list = quality_manager.getQualityChangesGroups(global_stack) + quality_changes_group = next((qcg for qcg in quality_changes_group_list if qcg.name == self._quality_changes_to_apply), None) + if not quality_changes_group: Logger.log("e", "Could not find quality_changes [%s]", self._quality_changes_to_apply) return - quality_changes_group = quality_changes_group_dict[self._quality_changes_to_apply] machine_manager.setQualityChangesGroup(quality_changes_group, no_dialog = True) else: self._quality_type_to_apply = self._quality_type_to_apply.lower() diff --git a/tests/TestQualityManager.py b/tests/TestQualityManager.py index ef3e9fa6bc..4a0f29c9ee 100644 --- a/tests/TestQualityManager.py +++ b/tests/TestQualityManager.py @@ -61,7 +61,7 @@ def test_getQualityChangesGroup(quality_mocked_application): manager = QualityManager(quality_mocked_application) manager.initialize() - assert "herp" in manager.getQualityChangesGroups(mocked_stack) + assert "herp" in [qcg.name for qcg in manager.getQualityChangesGroups(mocked_stack)] @pytest.mark.skip("Doesn't work on remote") From 5b8ed91b04d88667e9d32604200752cb5bcbefdc Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Aug 2019 13:50:48 +0200 Subject: [PATCH 03/50] Create new MaterialManagementModel and move canMaterialBeRemoved Just like the QualityManagementModel, this class is intended to be used as proxy for the material management page in the preferences. I'm intending to move all relevant pyqtSlots from the material manager into this one. The advantage of this switch is that the material manager had no well-bounded scope and so tended to become a big mess of all sorts of functions. This one has a clear scope: serve as a proxy for the buttons you can press in the preferences screen for materials. Contributes to issue CURA-6600. --- cura/CuraApplication.py | 2 ++ cura/Machines/MaterialManager.py | 10 +++--- .../Models/MaterialManagementModel.py | 33 +++++++++++++++++++ .../Preferences/Materials/MaterialsPage.qml | 9 +++-- 4 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 cura/Machines/Models/MaterialManagementModel.py diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index c9d2163609..e378224a04 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -85,6 +85,7 @@ from cura.Machines.Models.FirstStartMachineActionsModel import FirstStartMachine from cura.Machines.Models.GenericMaterialsModel import GenericMaterialsModel from cura.Machines.Models.GlobalStacksModel import GlobalStacksModel from cura.Machines.Models.MaterialBrandsModel import MaterialBrandsModel +from cura.Machines.Models.MaterialManagementModel import MaterialManagementModel from cura.Machines.Models.MultiBuildPlateModel import MultiBuildPlateModel from cura.Machines.Models.NozzleModel import NozzleModel from cura.Machines.Models.QualityManagementModel import QualityManagementModel @@ -1054,6 +1055,7 @@ class CuraApplication(QtApplication): qmlRegisterType(GenericMaterialsModel, "Cura", 1, 0, "GenericMaterialsModel") qmlRegisterType(MaterialBrandsModel, "Cura", 1, 0, "MaterialBrandsModel") qmlRegisterType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel") + qmlRegisterType(MaterialManagementModel, "Cura", 1, 5, "MaterialManagementModel") qmlRegisterType(DiscoveredPrintersModel, "Cura", 1, 0, "DiscoveredPrintersModel") diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py index b19c8b7926..24995e417b 100644 --- a/cura/Machines/MaterialManager.py +++ b/cura/Machines/MaterialManager.py @@ -1,5 +1,6 @@ # Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. + from collections import defaultdict import copy import uuid @@ -7,19 +8,16 @@ from typing import Dict, Optional, TYPE_CHECKING, Any, List, cast from PyQt5.Qt import QTimer, QObject, pyqtSignal, pyqtSlot -from UM.ConfigurationErrorMessage import ConfigurationErrorMessage from UM.Decorators import deprecated from UM.Logger import Logger from UM.Settings.ContainerRegistry import ContainerRegistry -from UM.Settings.SettingFunction import SettingFunction from UM.Util import parseBool -import cura.CuraApplication #Imported like this to prevent circular imports. +import cura.CuraApplication # Imported like this to prevent circular imports. from cura.Machines.ContainerTree import ContainerTree from cura.Settings.CuraContainerRegistry import CuraContainerRegistry from .MaterialNode import MaterialNode from .MaterialGroup import MaterialGroup -from .VariantType import VariantType if TYPE_CHECKING: from UM.Settings.DefinitionContainer import DefinitionContainer @@ -260,8 +258,8 @@ class MaterialManager(QObject): # Check if the material is active in any extruder train. In that case, the material shouldn't be removed! # In the future we might enable this again, but right now, it's causing a ton of issues if we do (since it # corrupts the configuration) - root_material_id = material_node.container.getMetaDataEntry("base_file") - ids_to_remove = [metadata.get("id", "") for metadata in CuraContainerRegistry.getInstance().findInstanceContainersMetadata(base_file=root_material_id)] + root_material_id = material_node.base_file + ids_to_remove = {metadata.get("id", "") for metadata in CuraContainerRegistry.getInstance().findInstanceContainersMetadata(base_file = root_material_id)} for extruder_stack in CuraContainerRegistry.getInstance().findContainerStacks(type = "extruder_train"): if extruder_stack.material.getId() in ids_to_remove: diff --git a/cura/Machines/Models/MaterialManagementModel.py b/cura/Machines/Models/MaterialManagementModel.py new file mode 100644 index 0000000000..9cc7cb07c9 --- /dev/null +++ b/cura/Machines/Models/MaterialManagementModel.py @@ -0,0 +1,33 @@ +# Copyright (c) 2019 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from PyQt5.QtCore import QObject, pyqtSlot # To allow the preference page proxy to be used from the actual preferences page. +from typing import TYPE_CHECKING + +from cura.Settings.CuraContainerRegistry import CuraContainerRegistry # To find the sets of materials belonging to each other, and currently loaded extruder stacks. + +if TYPE_CHECKING: + from cura.Machines.MaterialNode import MaterialNode + +## Proxy class to the materials page in the preferences. +# +# This class handles the actions in that page, such as creating new materials, +# renaming them, etc. +class MaterialManagementModel(QObject): + ## Can a certain material be deleted, or is it still in use in one of the + # container stacks anywhere? + # + # We forbid the user from deleting a material if it's in use in any stack. + # Deleting it while it's in use can lead to corrupted stacks. In the + # future we might enable this functionality again (deleting the material + # from those stacks) but for now it is easier to prevent the user from + # doing this. + # \return Whether or not the material can be removed. + @pyqtSlot("QVariant", result = bool) + def canMaterialBeRemoved(self, material_node: "MaterialNode"): + container_registry = CuraContainerRegistry.getInstance() + ids_to_remove = {metadata.get("id", "") for metadata in container_registry.findInstanceContainersMetadata(base_file = material_node.base_file)} + for extruder_stack in container_registry.findContainerStacks(type = "extruder_train"): + if extruder_stack.material.getId() in ids_to_remove: + return False + return True \ No newline at end of file diff --git a/resources/qml/Preferences/Materials/MaterialsPage.qml b/resources/qml/Preferences/Materials/MaterialsPage.qml index 481a256501..80d746351c 100644 --- a/resources/qml/Preferences/Materials/MaterialsPage.qml +++ b/resources/qml/Preferences/Materials/MaterialsPage.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.5 as Cura Item { @@ -42,6 +42,11 @@ Item name: "cura" } + Cura.MaterialManagementModel + { + id: materialManagement + } + function resetExpandedActiveMaterial() { materialListView.expandActiveMaterial(active_root_material_id) @@ -147,7 +152,7 @@ Item id: removeMenuButton text: catalog.i18nc("@action:button", "Remove") iconName: "list-remove" - enabled: base.hasCurrentItem && !base.currentItem.is_read_only && !base.isCurrentItemActivated && base.materialManager.canMaterialBeRemoved(base.currentItem.container_node) + enabled: base.hasCurrentItem && !base.currentItem.is_read_only && !base.isCurrentItemActivated && materialManagement.canMaterialBeRemoved(base.currentItem.container_node) onClicked: { From 99ccddefa4824d010214b3693a717183f9a534d6 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Aug 2019 14:26:06 +0200 Subject: [PATCH 04/50] Move setMaterialName to MaterialManagementModel No longer use the material manager which is deprecated. Contributes to issue CURA-6600. --- cura/Machines/Models/MaterialManagementModel.py | 17 ++++++++++++++++- .../qml/Preferences/Materials/MaterialsView.qml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/cura/Machines/Models/MaterialManagementModel.py b/cura/Machines/Models/MaterialManagementModel.py index 9cc7cb07c9..1b7774c4d3 100644 --- a/cura/Machines/Models/MaterialManagementModel.py +++ b/cura/Machines/Models/MaterialManagementModel.py @@ -4,6 +4,8 @@ from PyQt5.QtCore import QObject, pyqtSlot # To allow the preference page proxy to be used from the actual preferences page. from typing import TYPE_CHECKING +from UM.Logger import Logger + from cura.Settings.CuraContainerRegistry import CuraContainerRegistry # To find the sets of materials belonging to each other, and currently loaded extruder stacks. if TYPE_CHECKING: @@ -22,6 +24,7 @@ class MaterialManagementModel(QObject): # future we might enable this functionality again (deleting the material # from those stacks) but for now it is easier to prevent the user from # doing this. + # \param material_node The ContainerTree node of the material to check. # \return Whether or not the material can be removed. @pyqtSlot("QVariant", result = bool) def canMaterialBeRemoved(self, material_node: "MaterialNode"): @@ -30,4 +33,16 @@ class MaterialManagementModel(QObject): for extruder_stack in container_registry.findContainerStacks(type = "extruder_train"): if extruder_stack.material.getId() in ids_to_remove: return False - return True \ No newline at end of file + return True + + ## Change the user-visible name of a material. + # \param material_node The ContainerTree node of the material to rename. + # \param name The new name for the material. + @pyqtSlot("QVariant", str) + def setMaterialName(self, material_node: "MaterialNode", name: str) -> None: + container_registry = CuraContainerRegistry.getInstance() + root_material_id = material_node.base_file + if container_registry.isReadOnly(root_material_id): + Logger.log("w", "Cannot set name of read-only container %s.", root_material_id) + return + return container_registry.findContainers(id = root_material_id)[0].setName(name) \ No newline at end of file diff --git a/resources/qml/Preferences/Materials/MaterialsView.qml b/resources/qml/Preferences/Materials/MaterialsView.qml index 30b2474e09..8302f5f65a 100644 --- a/resources/qml/Preferences/Materials/MaterialsView.qml +++ b/resources/qml/Preferences/Materials/MaterialsView.qml @@ -565,7 +565,7 @@ TabView } // update the values - CuraApplication.getMaterialManager().setMaterialName(base.currentMaterialNode, new_name) + base.materialManagement.setMaterialName(base.currentMaterialNode, new_name) properties.name = new_name } From 3dc7c7b61c4c354ceb7fe16b84cb0bbacf434372 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Aug 2019 14:53:50 +0200 Subject: [PATCH 05/50] Move removeMaterial to MaterialManagementModel Moving away from the MaterialManager. Contributes to issue CURA-6600. --- cura/Machines/MaterialManager.py | 2 +- .../Models/MaterialManagementModel.py | 19 ++++++++++++++++++- .../Preferences/Materials/MaterialsPage.qml | 2 +- .../Preferences/Materials/MaterialsView.qml | 2 +- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py index 24995e417b..5d5938d95d 100644 --- a/cura/Machines/MaterialManager.py +++ b/cura/Machines/MaterialManager.py @@ -246,7 +246,7 @@ class MaterialManager(QObject): def removeMaterialByRootId(self, root_material_id: str): container_registry = CuraContainerRegistry.getInstance() - results = container_registry.findContainers(id=root_material_id) + results = container_registry.findContainers(id = root_material_id) if not results: container_registry.addWrongContainerId(root_material_id) diff --git a/cura/Machines/Models/MaterialManagementModel.py b/cura/Machines/Models/MaterialManagementModel.py index 1b7774c4d3..2c8617073d 100644 --- a/cura/Machines/Models/MaterialManagementModel.py +++ b/cura/Machines/Models/MaterialManagementModel.py @@ -45,4 +45,21 @@ class MaterialManagementModel(QObject): if container_registry.isReadOnly(root_material_id): Logger.log("w", "Cannot set name of read-only container %s.", root_material_id) return - return container_registry.findContainers(id = root_material_id)[0].setName(name) \ No newline at end of file + return container_registry.findContainers(id = root_material_id)[0].setName(name) + + ## Deletes a material from Cura. + # + # This function does not do any safety checking any more. Please call this + # function only if: + # - The material is not read-only. + # - The material is not used in any stacks. + # If the material was not lazy-loaded yet, this will fully load the + # container. When removing this material node, all other materials with + # the same base fill will also be removed. + # \param material_node The material to remove. + @pyqtSlot("QVariant") + def removeMaterial(self, material_node: "MaterialNode") -> None: + container_registry = CuraContainerRegistry.getInstance() + materials_this_base_file = container_registry.findContainersMetadata(base_file = material_node.base_file) + for material_metadata in materials_this_base_file: + container_registry.removeContainer(material_metadata["id"]) \ No newline at end of file diff --git a/resources/qml/Preferences/Materials/MaterialsPage.qml b/resources/qml/Preferences/Materials/MaterialsPage.qml index 80d746351c..3453c866e3 100644 --- a/resources/qml/Preferences/Materials/MaterialsPage.qml +++ b/resources/qml/Preferences/Materials/MaterialsPage.qml @@ -302,7 +302,7 @@ Item { // Set the active material as the fallback. It will be selected when the current material is deleted base.newRootMaterialIdToSwitchTo = base.active_root_material_id - base.materialManager.removeMaterial(base.currentItem.container_node); + base.materialManagement.removeMaterial(base.currentItem.container_node); } } diff --git a/resources/qml/Preferences/Materials/MaterialsView.qml b/resources/qml/Preferences/Materials/MaterialsView.qml index 8302f5f65a..83c7de9aee 100644 --- a/resources/qml/Preferences/Materials/MaterialsView.qml +++ b/resources/qml/Preferences/Materials/MaterialsView.qml @@ -565,7 +565,7 @@ TabView } // update the values - base.materialManagement.setMaterialName(base.currentMaterialNode, new_name) + materialManagement.setMaterialName(base.currentMaterialNode, new_name) properties.name = new_name } From b60b13e5bfaf43dec47d94e58da13554721dd765 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Aug 2019 15:06:44 +0200 Subject: [PATCH 06/50] Use container tree to check for variants when serialising Not the variant manager, because it's deprecated. Contributes to issue CURA-6600. --- .../XmlMaterialProfile/XmlMaterialProfile.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 157d871d54..4c1920a97e 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -226,7 +226,7 @@ class XmlMaterialProfile(InstanceContainer): machine_container_map = {} # type: Dict[str, InstanceContainer] machine_variant_map = {} # type: Dict[str, Dict[str, Any]] - variant_manager = CuraApplication.getInstance().getVariantManager() + container_tree = ContainerTree.getInstance() root_material_id = self.getMetaDataEntry("base_file") # if basefile is self.getId, this is a basefile. all_containers = registry.findInstanceContainers(base_file = root_material_id) @@ -243,16 +243,15 @@ class XmlMaterialProfile(InstanceContainer): machine_variant_map[definition_id] = {} variant_name = container.getMetaDataEntry("variant_name") - if variant_name: - variant_node = variant_manager.getVariantNode(definition_id, variant_name) - if variant_node is None: - continue - variant_dict = {"variant_node":variant_node , - "material_container": container} - machine_variant_map[definition_id][variant_name] = variant_dict - continue + if not variant_name: + machine_container_map[definition_id] = container - machine_container_map[definition_id] = container + if variant_name not in container_tree.machines[definition_id].variants: + continue + variant_node = container_tree.machines[definition_id].variants[variant_name] + variant_dict = {"variant_node": variant_node, + "material_container": container} + machine_variant_map[definition_id][variant_name] = variant_dict # Map machine human-readable names to IDs product_id_map = self.getProductIdMap() From 8346e465f65c3cef248ab22b4aa643f3cb1898e3 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Aug 2019 15:22:28 +0200 Subject: [PATCH 07/50] Turn MaterialManagementModel into a singleton Just like MaterialManager used to be. There can be only one instance of the page then. This prevents a crash when Qt deletes the QObject because it's no longer used in the page when you close the preferences screen. But when you open it again it doesn't construct a new one. Now there is always one instance so that's not a problem any more. Also it allows other pages to access this item. Contributes to issue CURA-6600. --- cura/CuraApplication.py | 7 ++++++- resources/qml/Preferences/Materials/MaterialsPage.qml | 10 +++------- resources/qml/Preferences/Materials/MaterialsView.qml | 3 ++- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index e378224a04..c452c56cf5 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -222,6 +222,7 @@ class CuraApplication(QtApplication): self._machine_error_checker = None self._machine_settings_manager = MachineSettingsManager(self, parent = self) + self._material_management_model = MaterialManagementModel() self._discovered_printer_model = DiscoveredPrintersModel(self, parent = self) self._first_start_machine_actions_model = FirstStartMachineActionsModel(self, parent = self) @@ -976,6 +977,10 @@ class CuraApplication(QtApplication): def getMachineActionManager(self, *args): return self._machine_action_manager + @pyqtSlot(result = QObject) + def getMaterialManagementModel(self): + return self._material_management_model + def getSimpleModeSettingsManager(self, *args): if self._simple_mode_settings_manager is None: self._simple_mode_settings_manager = SimpleModeSettingsManager() @@ -1055,7 +1060,7 @@ class CuraApplication(QtApplication): qmlRegisterType(GenericMaterialsModel, "Cura", 1, 0, "GenericMaterialsModel") qmlRegisterType(MaterialBrandsModel, "Cura", 1, 0, "MaterialBrandsModel") qmlRegisterType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel") - qmlRegisterType(MaterialManagementModel, "Cura", 1, 5, "MaterialManagementModel") + qmlRegisterSingletonType(MaterialManagementModel, "Cura", 1, 5, "MaterialManagementModel", self.getMaterialManagementModel) qmlRegisterType(DiscoveredPrintersModel, "Cura", 1, 0, "DiscoveredPrintersModel") diff --git a/resources/qml/Preferences/Materials/MaterialsPage.qml b/resources/qml/Preferences/Materials/MaterialsPage.qml index 3453c866e3..4a6ff75f0d 100644 --- a/resources/qml/Preferences/Materials/MaterialsPage.qml +++ b/resources/qml/Preferences/Materials/MaterialsPage.qml @@ -18,6 +18,7 @@ Item property var currentItem: null property var materialManager: CuraApplication.getMaterialManager() + property var materialManagementModel: CuraApplication.getMaterialManagementModel() property var hasCurrentItem: base.currentItem != null property var isCurrentItemActivated: @@ -42,11 +43,6 @@ Item name: "cura" } - Cura.MaterialManagementModel - { - id: materialManagement - } - function resetExpandedActiveMaterial() { materialListView.expandActiveMaterial(active_root_material_id) @@ -152,7 +148,7 @@ Item id: removeMenuButton text: catalog.i18nc("@action:button", "Remove") iconName: "list-remove" - enabled: base.hasCurrentItem && !base.currentItem.is_read_only && !base.isCurrentItemActivated && materialManagement.canMaterialBeRemoved(base.currentItem.container_node) + enabled: base.hasCurrentItem && !base.currentItem.is_read_only && !base.isCurrentItemActivated && base.materialManagementModel.canMaterialBeRemoved(base.currentItem.container_node) onClicked: { @@ -302,7 +298,7 @@ Item { // Set the active material as the fallback. It will be selected when the current material is deleted base.newRootMaterialIdToSwitchTo = base.active_root_material_id - base.materialManagement.removeMaterial(base.currentItem.container_node); + base.materialManagementModel.removeMaterial(base.currentItem.container_node); } } diff --git a/resources/qml/Preferences/Materials/MaterialsView.qml b/resources/qml/Preferences/Materials/MaterialsView.qml index 83c7de9aee..0f5eba2f2f 100644 --- a/resources/qml/Preferences/Materials/MaterialsView.qml +++ b/resources/qml/Preferences/Materials/MaterialsView.qml @@ -23,6 +23,7 @@ TabView property real secondColumnWidth: (width * 0.40) | 0 property string containerId: "" property var materialPreferenceValues: UM.Preferences.getValue("cura/material_settings") ? JSON.parse(UM.Preferences.getValue("cura/material_settings")) : {} + property var materialManagementModel: CuraApplication.getMaterialManagementModel() property double spoolLength: calculateSpoolLength() property real costPerMeter: calculateCostPerMeter() @@ -565,7 +566,7 @@ TabView } // update the values - materialManagement.setMaterialName(base.currentMaterialNode, new_name) + base.materialManagementModel.setMaterialName(base.currentMaterialNode, new_name) properties.name = new_name } From 63ae6ee9ec874f734ad28975fe2866b0df8d03b9 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Aug 2019 15:33:39 +0200 Subject: [PATCH 08/50] Fix updating materials models when materials change before first printer switch Otherwise the _extruder_stack field would not yet be set. Contributes to issue CURA-6600. --- cura/Machines/Models/BaseMaterialsModel.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cura/Machines/Models/BaseMaterialsModel.py b/cura/Machines/Models/BaseMaterialsModel.py index 3ab11b7e9d..81be676a33 100644 --- a/cura/Machines/Models/BaseMaterialsModel.py +++ b/cura/Machines/Models/BaseMaterialsModel.py @@ -31,8 +31,13 @@ class BaseMaterialsModel(ListModel): self._container_registry = self._application.getInstance().getContainerRegistry() self._machine_manager = self._application.getMachineManager() + self._extruder_position = 0 + self._extruder_stack = None + self._enabled = True + # Update the stack and the model data when the machine changes self._machine_manager.globalContainerChanged.connect(self._updateExtruderStack) + self._updateExtruderStack() # Update this model when switching machines, when adding materials or changing their metadata. self._machine_manager.activeStackChanged.connect(self._update) @@ -55,12 +60,8 @@ class BaseMaterialsModel(ListModel): self.addRoleName(Qt.UserRole + 15, "container_node") self.addRoleName(Qt.UserRole + 16, "is_favorite") - self._extruder_position = 0 - self._extruder_stack = None - self._available_materials = None # type: Optional[Dict[str, MaterialNode]] self._favorite_ids = set() # type: Set[str] - self._enabled = True def _updateExtruderStack(self): global_stack = self._machine_manager.activeMachine From dabd905853b178856dd2fc8320b4a4633b7fa2f3 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Aug 2019 15:47:23 +0200 Subject: [PATCH 09/50] Fix serialising materials with submaterials in not loaded container trees Material profiles need to serialise subprofiles that belong to different printers as well. Some of these materials may not be loaded in the ContainerTree structure. To prevent having to load that as well, we're just not going to use the container tree any more. It turns out that the only reason it was using the container tree was to get the hardware_type metadata from the node in the tree. So just get that from the container itself and we're fine. Contributes to issue CURA-6600. --- plugins/XmlMaterialProfile/XmlMaterialProfile.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 4c1920a97e..ac53564eaf 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -223,8 +223,8 @@ class XmlMaterialProfile(InstanceContainer): for instance in self.findInstances(): self._addSettingElement(builder, instance) - machine_container_map = {} # type: Dict[str, InstanceContainer] - machine_variant_map = {} # type: Dict[str, Dict[str, Any]] + machine_container_map = {} # type: Dict[str, InstanceContainer] + machine_variant_map = {} # type: Dict[str, Dict[str, Any]] container_tree = ContainerTree.getInstance() @@ -246,10 +246,7 @@ class XmlMaterialProfile(InstanceContainer): if not variant_name: machine_container_map[definition_id] = container - if variant_name not in container_tree.machines[definition_id].variants: - continue - variant_node = container_tree.machines[definition_id].variants[variant_name] - variant_dict = {"variant_node": variant_node, + variant_dict = {"variant_type": container.getMetaDataEntry("hardware_type", str(VariantType.NOZZLE)), "material_container": container} machine_variant_map[definition_id][variant_name] = variant_dict @@ -284,8 +281,7 @@ class XmlMaterialProfile(InstanceContainer): # Find all hotend sub-profiles corresponding to this material and machine and add them to this profile. buildplate_dict = {} # type: Dict[str, Any] for variant_name, variant_dict in machine_variant_map[definition_id].items(): - variant_type = variant_dict["variant_node"].getMetaDataEntry("hardware_type", str(VariantType.NOZZLE)) - variant_type = VariantType(variant_type) + variant_type = VariantType(variant_dict["variant_type"]) if variant_type == VariantType.NOZZLE: # The hotend identifier is not the containers name, but its "name". builder.start("hotend", {"id": variant_name}) From 20be7fd8a15e4454b604cc1b188887ac6e9628e5 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Aug 2019 16:04:53 +0200 Subject: [PATCH 10/50] Don't add to variant-specific mapping if it's not variant-specific Otherwise we'll encounter that the variant name is None when serialising that subprofile. Contributes to issue CURA-6600. --- plugins/XmlMaterialProfile/XmlMaterialProfile.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index ac53564eaf..cbdec39054 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -245,6 +245,7 @@ class XmlMaterialProfile(InstanceContainer): variant_name = container.getMetaDataEntry("variant_name") if not variant_name: machine_container_map[definition_id] = container + continue variant_dict = {"variant_type": container.getMetaDataEntry("hardware_type", str(VariantType.NOZZLE)), "material_container": container} @@ -344,7 +345,7 @@ class XmlMaterialProfile(InstanceContainer): stream = io.BytesIO() tree = ET.ElementTree(root) # this makes sure that the XML header states encoding="utf-8" - tree.write(stream, encoding = "utf-8", xml_declaration=True) + tree.write(stream, encoding = "utf-8", xml_declaration = True) return stream.getvalue().decode("utf-8") From c71660cc114abf9a9f547d1ae11e0d0383ba40ab Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 27 Aug 2019 16:21:30 +0200 Subject: [PATCH 11/50] Fix occasional crash when entering profiles section. Make QualityManagementModel into a singleton. part of CURA-6600 --- cura/CuraApplication.py | 11 +++++++++-- resources/qml/Preferences/ProfilesPage.qml | 23 ++++++++++------------ 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index c452c56cf5..a70ca56da7 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -221,8 +221,9 @@ class CuraApplication(QtApplication): self._cura_scene_controller = None self._machine_error_checker = None - self._machine_settings_manager = MachineSettingsManager(self, parent = self) + self._machine_settings_manager = MachineSettingsManager(self, parent=self) self._material_management_model = MaterialManagementModel() + self._quality_management_model = None self._discovered_printer_model = DiscoveredPrintersModel(self, parent = self) self._first_start_machine_actions_model = FirstStartMachineActionsModel(self, parent = self) @@ -981,6 +982,12 @@ class CuraApplication(QtApplication): def getMaterialManagementModel(self): return self._material_management_model + @pyqtSlot(result=QObject) + def getQualityManagementModel(self): + if not self._quality_management_model: + self._quality_management_model = QualityManagementModel() + return self._quality_management_model + def getSimpleModeSettingsManager(self, *args): if self._simple_mode_settings_manager is None: self._simple_mode_settings_manager = SimpleModeSettingsManager() @@ -1059,7 +1066,7 @@ class CuraApplication(QtApplication): qmlRegisterType(FavoriteMaterialsModel, "Cura", 1, 0, "FavoriteMaterialsModel") qmlRegisterType(GenericMaterialsModel, "Cura", 1, 0, "GenericMaterialsModel") qmlRegisterType(MaterialBrandsModel, "Cura", 1, 0, "MaterialBrandsModel") - qmlRegisterType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel") + qmlRegisterSingletonType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel", self.getQualityManagementModel) qmlRegisterSingletonType(MaterialManagementModel, "Cura", 1, 5, "MaterialManagementModel", self.getMaterialManagementModel) qmlRegisterType(DiscoveredPrintersModel, "Cura", 1, 0, "DiscoveredPrintersModel") diff --git a/resources/qml/Preferences/ProfilesPage.qml b/resources/qml/Preferences/ProfilesPage.qml index 0bac5aefca..da41a0f23c 100644 --- a/resources/qml/Preferences/ProfilesPage.qml +++ b/resources/qml/Preferences/ProfilesPage.qml @@ -17,13 +17,10 @@ Item property QtObject qualityManager: CuraApplication.getQualityManager() property var resetEnabled: false // Keep PreferencesDialog happy property var extrudersModel: CuraApplication.getExtrudersModel() + property var qualityManagementModel: CuraApplication.getQualityManagementModel() UM.I18nCatalog { id: catalog; name: "cura"; } - Cura.QualityManagementModel { - id: qualitiesModel - } - Label { id: titleLabel anchors { @@ -40,7 +37,7 @@ Item property var currentItem: { var current_index = qualityListView.currentIndex; - return (current_index == -1) ? null : qualitiesModel.getItem(current_index); + return (current_index == -1) ? null : base.qualityManagementModel.getItem(current_index); } property var currentItemName: hasCurrentItem ? base.currentItem.name : "" @@ -195,7 +192,7 @@ Item // This connection makes sure that we will switch to the correct quality after the model gets updated Connections { - target: qualitiesModel + target: base.qualityManagementModel onItemsChanged: { var toSelectItemName = base.currentItem == null ? "" : base.currentItem.name; @@ -208,9 +205,9 @@ Item if (toSelectItemName != "") { // Select the required quality name if given - for (var idx = 0; idx < qualitiesModel.count; ++idx) + for (var idx = 0; idx < base.qualityManagementModel.count; ++idx) { - var item = qualitiesModel.getItem(idx); + var item = base.qualityManagementModel.getItem(idx); if (item.name == toSelectItemName) { // Switch to the newly created profile if needed @@ -282,7 +279,7 @@ Item id: importDialog title: catalog.i18nc("@title:window", "Import Profile") selectExisting: true - nameFilters: qualitiesModel.getFileNameFilters("profile_reader") + nameFilters: base.qualityManagementModel.getFileNameFilters("profile_reader") folder: CuraApplication.getDefaultPath("dialog_profile_path") onAccepted: { @@ -308,7 +305,7 @@ Item id: exportDialog title: catalog.i18nc("@title:window", "Export Profile") selectExisting: false - nameFilters: qualitiesModel.getFileNameFilters("profile_writer") + nameFilters: base.qualityManagementModel.getFileNameFilters("profile_writer") folder: CuraApplication.getDefaultPath("dialog_profile_path") onAccepted: { @@ -390,16 +387,16 @@ Item { id: qualityListView - model: qualitiesModel + model: base.qualityManagementModel Component.onCompleted: { var selectedItemName = Cura.MachineManager.activeQualityOrQualityChangesName; // Select the required quality name if given - for (var idx = 0; idx < qualitiesModel.count; idx++) + for (var idx = 0; idx < base.qualityManagementModel.count; idx++) { - var item = qualitiesModel.getItem(idx); + var item = base.qualityManagementModel.getItem(idx); if (item.name == selectedItemName) { currentIndex = idx; From 924d4cc13b5745492a4145311afedb135566628f Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Aug 2019 16:07:37 +0200 Subject: [PATCH 12/50] Remove unused container_tree variable Contributes to issue CURA-6600. --- plugins/XmlMaterialProfile/XmlMaterialProfile.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index cbdec39054..71a7f8e593 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -226,8 +226,6 @@ class XmlMaterialProfile(InstanceContainer): machine_container_map = {} # type: Dict[str, InstanceContainer] machine_variant_map = {} # type: Dict[str, Dict[str, Any]] - container_tree = ContainerTree.getInstance() - root_material_id = self.getMetaDataEntry("base_file") # if basefile is self.getId, this is a basefile. all_containers = registry.findInstanceContainers(base_file = root_material_id) From 1874a6453da603100633a7aa624c527fc398fdd9 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Aug 2019 16:40:11 +0200 Subject: [PATCH 13/50] Move duplicateMaterial into MaterialManagementModel To move away from the deprecated MaterialManager class. Contributes to issue CURA-6600. --- cura/Machines/MaterialManager.py | 4 +- .../Models/MaterialManagementModel.py | 68 ++++++++++++++++++- .../Preferences/Materials/MaterialsPage.qml | 2 +- 3 files changed, 69 insertions(+), 5 deletions(-) diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py index 5d5938d95d..b055cf7ab4 100644 --- a/cura/Machines/MaterialManager.py +++ b/cura/Machines/MaterialManager.py @@ -298,7 +298,6 @@ class MaterialManager(QObject): # Create a new ID & container to hold the data. new_containers = [] - container_registry = CuraContainerRegistry.getInstance() if new_base_id is None: new_base_id = container_registry.uniqueName(base_container.getId()) new_base_container = copy.deepcopy(base_container) @@ -334,9 +333,10 @@ class MaterialManager(QObject): # if the duplicated material was favorite then the new material should also be added to favorite. if root_material_id in self.getFavorites(): - self.addFavorite(new_base_id) + cura.CuraApplication.CuraApplication.getInstance().getMaterialManagementModel().addFavorite(new_base_id) return new_base_id + # # Creates a duplicate of a material, which has the same GUID and base_file metadata. # Returns the root material ID of the duplicated material if successful. diff --git a/cura/Machines/Models/MaterialManagementModel.py b/cura/Machines/Models/MaterialManagementModel.py index 2c8617073d..55ded501c2 100644 --- a/cura/Machines/Models/MaterialManagementModel.py +++ b/cura/Machines/Models/MaterialManagementModel.py @@ -1,11 +1,13 @@ # Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +import copy # To duplicate materials. from PyQt5.QtCore import QObject, pyqtSlot # To allow the preference page proxy to be used from the actual preferences page. -from typing import TYPE_CHECKING +from typing import Any, Dict, Optional, TYPE_CHECKING from UM.Logger import Logger +import cura.CuraApplication # Imported like this to prevent circular imports. from cura.Settings.CuraContainerRegistry import CuraContainerRegistry # To find the sets of materials belonging to each other, and currently loaded extruder stacks. if TYPE_CHECKING: @@ -62,4 +64,66 @@ class MaterialManagementModel(QObject): container_registry = CuraContainerRegistry.getInstance() materials_this_base_file = container_registry.findContainersMetadata(base_file = material_node.base_file) for material_metadata in materials_this_base_file: - container_registry.removeContainer(material_metadata["id"]) \ No newline at end of file + container_registry.removeContainer(material_metadata["id"]) + + ## Creates a duplicate of a material with the same GUID and base_file + # metadata. + # \param material_node The node representing the material to duplicate. + # \param new_base_id A new material ID for the base material. The IDs of + # the submaterials will be based off this one. If not provided, a material + # ID will be generated automatically. + # \param new_metadata Metadata for the new material. If not provided, this + # will be duplicated from the original material. + # \return The root material ID of the duplicate material. + @pyqtSlot("QVariant", result = str) + def duplicateMaterial(self, material_node: "MaterialNode", new_base_id: Optional[str] = None, new_metadata: Dict[str, Any] = None) -> Optional[str]: + container_registry = CuraContainerRegistry.getInstance() + + root_materials = container_registry.findContainers(id = material_node.base_file) + if not root_materials: + Logger.log("i", "Unable to duplicate the root material with ID {root_id}, because it doesn't exist.".format(root_id = material_node.base_file)) + return None + root_material = root_materials[0] + + # Ensure that all settings are saved. + cura.CuraApplication.CuraApplication.getInstance().saveSettings() + + # Create a new ID and container to hold the data. + if new_base_id is None: + new_base_id = container_registry.uniqueName(root_material.getId()) + new_root_material = copy.deepcopy(root_material) + new_root_material.getMetaData()["id"] = new_base_id + new_root_material.getMetaData()["base_file"] = new_base_id + if new_metadata is not None: + new_root_material.getMetaData().update(new_metadata) + new_containers = [new_root_material] + + # Clone all submaterials. + for container_to_copy in container_registry.findInstanceContainers(base_file = material_node.base_file): + if container_to_copy.getId() == material_node.base_file: + continue # We already have that one. Skip it. + new_id = new_base_id + definition = container_to_copy.getMetaDataEntry("definition") + if definition != "fdmprinter": + new_id += "_" + definition + variant_name = container_to_copy.getMetaDataEntry("variant_name") + if variant_name: + new_id += "_" + variant_name.replace(" ", "_") + + new_container = copy.deepcopy(container_to_copy) + new_container.getMetaData()["id"] = new_id + new_container.getMetaData()["base_file"] = new_base_id + if new_metadata is not None: + new_container.getMetaData().update(new_metadata) + new_containers.append(new_container) + + for container_to_add in new_containers: + container_to_add.setDirty(True) + container_registry.addContainer(container_to_add) + + # If the duplicated material was favorite then the new material should also be added to the favorites. + # TODO: Move favourites to here. + #if material_node.base_file in self.getFavorites(): + # self.addFavorite(new_base_id) + + return new_base_id \ No newline at end of file diff --git a/resources/qml/Preferences/Materials/MaterialsPage.qml b/resources/qml/Preferences/Materials/MaterialsPage.qml index 4a6ff75f0d..1568e8707f 100644 --- a/resources/qml/Preferences/Materials/MaterialsPage.qml +++ b/resources/qml/Preferences/Materials/MaterialsPage.qml @@ -137,7 +137,7 @@ Item onClicked: { forceActiveFocus(); - base.newRootMaterialIdToSwitchTo = base.materialManager.duplicateMaterial(base.currentItem.container_node); + base.newRootMaterialIdToSwitchTo = base.materialManagementModel.duplicateMaterial(base.currentItem.container_node); base.toActivateNewMaterial = true; } } From 957894b614e22cc3a96baf6a4c300051258cb726 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Aug 2019 16:45:50 +0200 Subject: [PATCH 14/50] Fix duplicating a favourite material The duplicate must also be favourite. Contributes to issue CURA-6600. --- cura/Machines/Models/MaterialManagementModel.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cura/Machines/Models/MaterialManagementModel.py b/cura/Machines/Models/MaterialManagementModel.py index 55ded501c2..301dc12025 100644 --- a/cura/Machines/Models/MaterialManagementModel.py +++ b/cura/Machines/Models/MaterialManagementModel.py @@ -86,7 +86,8 @@ class MaterialManagementModel(QObject): root_material = root_materials[0] # Ensure that all settings are saved. - cura.CuraApplication.CuraApplication.getInstance().saveSettings() + application = cura.CuraApplication.CuraApplication.getInstance() + application.saveSettings() # Create a new ID and container to hold the data. if new_base_id is None: @@ -122,8 +123,9 @@ class MaterialManagementModel(QObject): container_registry.addContainer(container_to_add) # If the duplicated material was favorite then the new material should also be added to the favorites. - # TODO: Move favourites to here. - #if material_node.base_file in self.getFavorites(): - # self.addFavorite(new_base_id) + favorites_set = set(application.getPreferences().getValue("cura/favorite_materials").split(";")) + if material_node.base_file in favorites_set: + favorites_set.add(new_base_id) + application.getPreferences().setValue("cura/favorite_materials", ";".join(favorites_set)) return new_base_id \ No newline at end of file From cb344f9dec5362c334c033be45d4388ef819d60a Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Aug 2019 17:05:46 +0200 Subject: [PATCH 15/50] Fix serialising materials without nozzle profile again Oops. This is simpler anyway. Contributes to issue CURA-6600. --- plugins/XmlMaterialProfile/XmlMaterialProfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 71a7f8e593..5ec14fe60c 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -245,7 +245,7 @@ class XmlMaterialProfile(InstanceContainer): machine_container_map[definition_id] = container continue - variant_dict = {"variant_type": container.getMetaDataEntry("hardware_type", str(VariantType.NOZZLE)), + variant_dict = {"variant_type": container.getMetaDataEntry("hardware_type", "nozzle"), "material_container": container} machine_variant_map[definition_id][variant_name] = variant_dict From 39523667984e46c2f214c0791bda4f19e0faa105 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Aug 2019 17:08:56 +0200 Subject: [PATCH 16/50] Move createMaterial to MaterialManagementModel and simplify it a bit We can reuse our duplicateMaterial function again but in a simpler way. Also finding the preferred material is simpler with our container tree. However there seems to be a problem with finding the preferred material; it's not finding generic_pla for UM3 and AA0.4 anyway, and then falls back on a random material. This needs to be fixed in the variant node class. Contributes to issue CURA-6600. --- .../Models/MaterialManagementModel.py | 37 ++++++++++++++++++- .../Preferences/Materials/MaterialsPage.qml | 2 +- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/cura/Machines/Models/MaterialManagementModel.py b/cura/Machines/Models/MaterialManagementModel.py index 301dc12025..6ff836cef2 100644 --- a/cura/Machines/Models/MaterialManagementModel.py +++ b/cura/Machines/Models/MaterialManagementModel.py @@ -4,15 +4,20 @@ import copy # To duplicate materials. from PyQt5.QtCore import QObject, pyqtSlot # To allow the preference page proxy to be used from the actual preferences page. from typing import Any, Dict, Optional, TYPE_CHECKING +import uuid # To generate new GUIDs for new materials. +from UM.i18n import i18nCatalog from UM.Logger import Logger import cura.CuraApplication # Imported like this to prevent circular imports. +from cura.Machines.ContainerTree import ContainerTree from cura.Settings.CuraContainerRegistry import CuraContainerRegistry # To find the sets of materials belonging to each other, and currently loaded extruder stacks. if TYPE_CHECKING: from cura.Machines.MaterialNode import MaterialNode +catalog = i18nCatalog("cura") + ## Proxy class to the materials page in the preferences. # # This class handles the actions in that page, such as creating new materials, @@ -128,4 +133,34 @@ class MaterialManagementModel(QObject): favorites_set.add(new_base_id) application.getPreferences().setValue("cura/favorite_materials", ";".join(favorites_set)) - return new_base_id \ No newline at end of file + return new_base_id + + ## Create a new material by cloning the preferred material for the current + # material diameter and generate a new GUID. + # + # The material type is explicitly left to be the one from the preferred + # material, since this allows the user to still have SOME profiles to work + # with. + # \return The ID of the newly created material. + @pyqtSlot(result = str) + def createMaterial(self) -> str: + # Ensure all settings are saved. + application = cura.CuraApplication.CuraApplication.getInstance() + application.saveSettings() + + # Find the preferred material. + extruder_stack = application.getMachineManager().activeStack + active_variant_name = extruder_stack.variant.getName() + approximate_diameter = str(extruder_stack.approximateMaterialDiameter) + machine_node = ContainerTree.getInstance().machines[application.getGlobalContainerStack().definition.getId()] + preferred_material_node = machine_node.variants[active_variant_name].preferredMaterial(approximate_diameter) + + # Create a new ID & new metadata for the new material. + new_id = CuraContainerRegistry.getInstance().uniqueName("custom_material") + new_metadata = {"name": catalog.i18nc("@label", "Custom Material"), + "brand": catalog.i18nc("@label", "Custom"), + "GUID": str(uuid.uuid4()), + } + + self.duplicateMaterial(preferred_material_node, new_base_id = new_id, new_metadata = new_metadata) + return new_id \ No newline at end of file diff --git a/resources/qml/Preferences/Materials/MaterialsPage.qml b/resources/qml/Preferences/Materials/MaterialsPage.qml index 1568e8707f..6e637a8d1f 100644 --- a/resources/qml/Preferences/Materials/MaterialsPage.qml +++ b/resources/qml/Preferences/Materials/MaterialsPage.qml @@ -122,7 +122,7 @@ Item onClicked: { forceActiveFocus(); - base.newRootMaterialIdToSwitchTo = base.materialManager.createMaterial(); + base.newRootMaterialIdToSwitchTo = base.materialManagementModel.createMaterial(); base.toActivateNewMaterial = true; } } From 9297890d78ec78d522f88e2b65480870a1e6576f Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Aug 2019 17:21:05 +0200 Subject: [PATCH 17/50] Fix typing of approximate diameter and add typing to function for it Seems I forgot to add typing and that's biting my bum right now. Contributes to issue CURA-6600. --- cura/Machines/Models/MaterialManagementModel.py | 2 +- cura/Machines/VariantNode.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cura/Machines/Models/MaterialManagementModel.py b/cura/Machines/Models/MaterialManagementModel.py index 6ff836cef2..90e63e6240 100644 --- a/cura/Machines/Models/MaterialManagementModel.py +++ b/cura/Machines/Models/MaterialManagementModel.py @@ -151,7 +151,7 @@ class MaterialManagementModel(QObject): # Find the preferred material. extruder_stack = application.getMachineManager().activeStack active_variant_name = extruder_stack.variant.getName() - approximate_diameter = str(extruder_stack.approximateMaterialDiameter) + approximate_diameter = int(extruder_stack.approximateMaterialDiameter) machine_node = ContainerTree.getInstance().machines[application.getGlobalContainerStack().definition.getId()] preferred_material_node = machine_node.variants[active_variant_name].preferredMaterial(approximate_diameter) diff --git a/cura/Machines/VariantNode.py b/cura/Machines/VariantNode.py index f5a1e3006e..abcd588c4a 100644 --- a/cura/Machines/VariantNode.py +++ b/cura/Machines/VariantNode.py @@ -79,7 +79,7 @@ class VariantNode(ContainerNode): # material. # \return The node for the preferred material, or any arbitrary material # if there is no match. - def preferredMaterial(self, approximate_diameter) -> MaterialNode: + def preferredMaterial(self, approximate_diameter: int) -> MaterialNode: for base_material, material_node in self.materials.items(): if self.machine.preferred_material in base_material and approximate_diameter == int(material_node.getMetaDataEntry("approximate_diameter")): return material_node From 745390e51fe2fc761ba11aa8b79aa5ab7dc4388a Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 27 Aug 2019 17:57:11 +0200 Subject: [PATCH 18/50] Fix typing. part CURA-6600 --- cura/Machines/MachineNode.py | 7 +++++-- cura/Machines/MaterialManager.py | 15 +++++++++++++-- cura/Machines/Models/BaseMaterialsModel.py | 5 ++++- cura/Machines/QualityChangesGroup.py | 2 +- cura/Machines/QualityGroup.py | 7 +++++++ cura/Machines/QualityManager.py | 10 ++++------ cura/Settings/ContainerManager.py | 6 ++++++ cura/Settings/CuraContainerRegistry.py | 4 ++-- cura/Settings/MachineManager.py | 8 ++++++-- tests/TestQualityManager.py | 7 ------- 10 files changed, 48 insertions(+), 23 deletions(-) diff --git a/cura/Machines/MachineNode.py b/cura/Machines/MachineNode.py index efc5b0f128..ac293a1503 100644 --- a/cura/Machines/MachineNode.py +++ b/cura/Machines/MachineNode.py @@ -77,6 +77,9 @@ class MachineNode(ContainerNode): # Create the quality group for each available type. quality_groups = {} for quality_type, global_quality_node in self.global_qualities.items(): + if not global_quality_node.container: + Logger.log("w", "Node {0} doesn't have a container.".format(global_quality_node.container_id)) + continue quality_groups[quality_type] = QualityGroup(name = global_quality_node.container.getMetaDataEntry("name", "Unnamed profile"), quality_type = quality_type) quality_groups[quality_type].node_for_global = global_quality_node for extruder, qualities_per_type in enumerate(qualities_per_type_per_extruder): @@ -116,7 +119,7 @@ class MachineNode(ContainerNode): def getQualityChangesGroups(self, variant_names: List[str], material_bases: List[str], extruder_enabled: List[bool]) -> List[QualityChangesGroup]: machine_quality_changes = ContainerRegistry.getInstance().findContainersMetadata(type = "quality_changes", definition = self.quality_definition) # All quality changes for each extruder. - groups_by_name = {} # Group quality changes profiles by their display name. The display name must be unique for quality changes. This finds profiles that belong together in a group. + groups_by_name = {} #type: Dict[str, QualityChangesGroup] # Group quality changes profiles by their display name. The display name must be unique for quality changes. This finds profiles that belong together in a group. for quality_changes in machine_quality_changes: name = quality_changes["name"] if name not in groups_by_name: @@ -143,7 +146,7 @@ class MachineNode(ContainerNode): # quality is taken. # If there are no global qualities, an empty quality is returned. def preferredGlobalQuality(self) -> QualityNode: - return self.global_qualities.get(self.preferred_quality_type, next(iter(self.global_qualities))) + return self.global_qualities.get(self.preferred_quality_type, next(iter(self.global_qualities.values()))) ## (Re)loads all variants under this printer. def _loadAll(self): diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py index 5d5938d95d..e298ef7389 100644 --- a/cura/Machines/MaterialManager.py +++ b/cura/Machines/MaterialManager.py @@ -132,7 +132,7 @@ class MaterialManager(QObject): # Fetch the available materials (ContainerNode) for the current active machine and extruder setup. materials = self.getAvailableMaterials(machine.definition.getId(), nozzle_name) compatible_material_diameter = str(round(extruder_stack.getCompatibleMaterialDiameter())) - result = {key: material for key, material in materials.items() if material.container.getMetaDataEntry("approximate_diameter") == compatible_material_diameter} + result = {key: material for key, material in materials.items() if material.container and material.container.getMetaDataEntry("approximate_diameter") == compatible_material_diameter} return result # @@ -268,6 +268,8 @@ class MaterialManager(QObject): @pyqtSlot("QVariant", str) def setMaterialName(self, material_node: "MaterialNode", name: str) -> None: + if material_node.container is None: + return root_material_id = material_node.container.getMetaDataEntry("base_file") if root_material_id is None: return @@ -279,6 +281,8 @@ class MaterialManager(QObject): @pyqtSlot("QVariant") def removeMaterial(self, material_node: "MaterialNode") -> None: + if material_node.container is None: + return root_material_id = material_node.container.getMetaDataEntry("base_file") if root_material_id is not None: self.removeMaterialByRootId(root_material_id) @@ -343,6 +347,9 @@ class MaterialManager(QObject): # @pyqtSlot("QVariant", result = str) def duplicateMaterial(self, material_node: MaterialNode, new_base_id: Optional[str] = None, new_metadata: Dict[str, Any] = None) -> Optional[str]: + if material_node.container is None: + Logger.log("e", "Material node {0} doesn't have container.".format(material_node.container_id)) + return "ERROR" root_material_id = cast(str, material_node.container.getMetaDataEntry("base_file", "")) return self.duplicateMaterialByRootId(root_material_id, new_base_id, new_metadata) @@ -359,7 +366,11 @@ class MaterialManager(QObject): machine_manager = application.getMachineManager() extruder_stack = machine_manager.activeStack - machine_definition = application.getGlobalContainerStack().definition + global_stack = application.getGlobalContainerStack() + if global_stack is None: + Logger.log("e", "Global stack not present!") + return "ERROR" + machine_definition = global_stack.definition root_material_id = machine_definition.getMetaDataEntry("preferred_material", default = "generic_pla") approximate_diameter = str(extruder_stack.approximateMaterialDiameter) diff --git a/cura/Machines/Models/BaseMaterialsModel.py b/cura/Machines/Models/BaseMaterialsModel.py index 81be676a33..0a5e58d52f 100644 --- a/cura/Machines/Models/BaseMaterialsModel.py +++ b/cura/Machines/Models/BaseMaterialsModel.py @@ -106,7 +106,10 @@ class BaseMaterialsModel(ListModel): def _materialsListChanged(self, material: MaterialNode) -> None: if material.variant.container_id != self._extruder_stack.variant.getId(): return - if material.variant.machine.container_id != cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack().definition.getId(): + global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() + if not global_stack: + return + if material.variant.machine.container_id != global_stack.definition.getId(): return self._update() diff --git a/cura/Machines/QualityChangesGroup.py b/cura/Machines/QualityChangesGroup.py index f798a18e29..65ebb71f4c 100644 --- a/cura/Machines/QualityChangesGroup.py +++ b/cura/Machines/QualityChangesGroup.py @@ -16,7 +16,7 @@ class QualityChangesGroup(QObject): self.quality_type = quality_type self.intent_category = intent_category self.is_available = False - self.metadata_for_global = None # type: Optional[str] + self.metadata_for_global = {} # type: Dict[str, Any] self.metadata_per_extruder = {} # type: Dict[int, Dict[str, Any]] def __str__(self) -> str: diff --git a/cura/Machines/QualityGroup.py b/cura/Machines/QualityGroup.py index a4324e069d..951d3717de 100644 --- a/cura/Machines/QualityGroup.py +++ b/cura/Machines/QualityGroup.py @@ -5,6 +5,7 @@ from typing import Dict, Optional, List, Set from PyQt5.QtCore import QObject, pyqtSlot +from UM.Logger import Logger from UM.Util import parseBool from cura.Machines.ContainerNode import ContainerNode @@ -60,6 +61,9 @@ class QualityGroup(QObject): self.node_for_global = node # Update is_experimental flag + if not node.container: + Logger.log("w", "Node {0} doesn't have a container.".format(node.container_id)) + return is_experimental = parseBool(node.container.getMetaDataEntry("is_experimental", False)) self.is_experimental |= is_experimental @@ -67,5 +71,8 @@ class QualityGroup(QObject): self.nodes_for_extruders[position] = node # Update is_experimental flag + if not node.container: + Logger.log("w", "Node {0} doesn't have a container.".format(node.container_id)) + return is_experimental = parseBool(node.container.getMetaDataEntry("is_experimental", False)) self.is_experimental |= is_experimental diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py index c03c580197..75c8e89e11 100644 --- a/cura/Machines/QualityManager.py +++ b/cura/Machines/QualityManager.py @@ -112,12 +112,10 @@ class QualityManager(QObject): # Iterate over all quality_types in the machine node quality_group_dict = dict() for node in nodes_to_check: - if node and node.quality_type_map: - for quality_type, quality_node in node.quality_type_map.items(): - quality_group = QualityGroup(quality_node.getMetaDataEntry("name", ""), quality_type) - quality_group.setGlobalNode(quality_node) - quality_group_dict[quality_type] = quality_group - break + if node and node.quality_type: + quality_group = QualityGroup(node.getMetaDataEntry("name", ""), node.quality_type) + quality_group.setGlobalNode(node) + quality_group_dict[node.quality_type] = quality_group return quality_group_dict diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index a4c75353f5..d1c25530e3 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -83,6 +83,9 @@ class ContainerManager(QObject): # Update: In order for QML to use objects and sub objects, those (sub) objects must all be QObject. Is that what we want? @pyqtSlot("QVariant", str, str) def setContainerMetaDataEntry(self, container_node: "ContainerNode", entry_name: str, entry_value: str) -> bool: + if container_node.container is None: + Logger.log("w", "Container node {0} doesn't have a container.".format(container_node.container_id)) + return False root_material_id = container_node.container.getMetaDataEntry("base_file", "") if cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry().isReadOnly(root_material_id): Logger.log("w", "Cannot set metadata of read-only container %s.", root_material_id) @@ -341,6 +344,9 @@ class ContainerManager(QObject): @pyqtSlot("QVariant") def unlinkMaterial(self, material_node: "MaterialNode") -> None: # Get the material group + if material_node.container is None: + Logger.log("w", "Material node {0} doesn't have a container.".format(material_node.container_id)) + return material_group = MaterialManager.getInstance().getMaterialGroup(material_node.container.getMetaDataEntry("base_file", "")) if material_group is None: diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index 314adeeb54..c288f3c0ce 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -28,6 +28,7 @@ from . import GlobalStack import cura.CuraApplication from cura.Settings.cura_empty_instance_containers import empty_quality_container +from cura.Machines.ContainerTree import ContainerTree from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch from cura.ReaderWriters.ProfileReader import NoProfileException, ProfileReader @@ -386,8 +387,7 @@ class CuraContainerRegistry(ContainerRegistry): # Check to make sure the imported profile actually makes sense in context of the current configuration. # This prevents issues where importing a "draft" profile for a machine without "draft" qualities would report as # successfully imported but then fail to show up. - quality_manager = cura.CuraApplication.CuraApplication.getInstance()._quality_manager - quality_group_dict = quality_manager.getQualityGroupsForMachineDefinition(global_stack) + quality_group_dict = ContainerTree.getInstance().machines[definition_id] # "not_supported" profiles can be imported. if quality_type != empty_quality_container.getMetaDataEntry("quality_type") and quality_type not in quality_group_dict: return catalog.i18nc("@info:status", "Could not find a quality type {0} for the current configuration.", quality_type) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 3447581bca..5ad1c2dc43 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -24,6 +24,7 @@ from UM.Signal import postponeSignals, CompressTechnique import cura.CuraApplication # Imported like this to prevent circular references. +from cura.Machines.ContainerNode import ContainerNode from cura.Machines.ContainerTree import ContainerTree from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch, QualityManager from cura.Machines.MaterialManager import MaterialManager @@ -1185,7 +1186,7 @@ class MachineManager(QObject): def _setMaterial(self, position: str, material_node: Optional["MaterialNode"] = None) -> None: if self._global_container_stack is None: return - if material_node: + if material_node and material_node.container: material_container = material_node.container self._global_container_stack.extruders[position].material = material_container root_material_id = material_container.getMetaDataEntry("base_file", None) @@ -1239,6 +1240,9 @@ class MachineManager(QObject): # The current quality type is not available so we use the preferred quality type if it's available, # otherwise use one of the available quality types. quality_type = sorted(list(available_quality_types))[0] + if self._global_container_stack is None: + Logger.log("e", "Global stack not present!") + return preferred_quality_type = self._global_container_stack.getMetaDataEntry("preferred_quality_type") if preferred_quality_type in available_quality_types: quality_type = preferred_quality_type @@ -1549,7 +1553,7 @@ class MachineManager(QObject): @pyqtProperty(bool, notify = activeQualityGroupChanged) def hasNotSupportedQuality(self) -> bool: global_container_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() - return global_container_stack and global_container_stack.quality == empty_quality_container and global_container_stack.qualityChanges == empty_quality_changes_container + return (not global_container_stack is None) and global_container_stack.quality == empty_quality_container and global_container_stack.qualityChanges == empty_quality_changes_container def _updateUponMaterialMetadataChange(self) -> None: if self._global_container_stack is None: diff --git a/tests/TestQualityManager.py b/tests/TestQualityManager.py index 4a0f29c9ee..52c3da4fbb 100644 --- a/tests/TestQualityManager.py +++ b/tests/TestQualityManager.py @@ -50,13 +50,6 @@ def test_getQualityGroups(quality_mocked_application): assert "normal" in manager.getQualityGroups(mocked_stack) -def test_getQualityGroupsForMachineDefinition(quality_mocked_application): - manager = QualityManager(quality_mocked_application) - manager.initialize() - - assert "normal" in manager.getQualityGroupsForMachineDefinition(mocked_stack) - - def test_getQualityChangesGroup(quality_mocked_application): manager = QualityManager(quality_mocked_application) manager.initialize() From 21412986d3cbb73cfc3c5108ac93fdd226eec2a3 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Aug 2019 17:28:26 +0200 Subject: [PATCH 19/50] Remove material manager from MaterialsPage It is no longer used now that everything relevant has been moved to a separate class for this page specifically. Contributes to issue CURA-6600. --- resources/qml/Preferences/Materials/MaterialsPage.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/qml/Preferences/Materials/MaterialsPage.qml b/resources/qml/Preferences/Materials/MaterialsPage.qml index 6e637a8d1f..a8de240924 100644 --- a/resources/qml/Preferences/Materials/MaterialsPage.qml +++ b/resources/qml/Preferences/Materials/MaterialsPage.qml @@ -17,7 +17,6 @@ Item property var resetEnabled: false property var currentItem: null - property var materialManager: CuraApplication.getMaterialManager() property var materialManagementModel: CuraApplication.getMaterialManagementModel() property var hasCurrentItem: base.currentItem != null From f5ca29c7aa6bb6af51b6c0342474c098e446e93c Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Aug 2019 17:36:27 +0200 Subject: [PATCH 20/50] Emit materialsChanged from the variant when a material gets added This allows the material models to update themselves. Contributes to issue CURA-6600. --- cura/Machines/Models/BaseMaterialsModel.py | 3 +-- cura/Machines/VariantNode.py | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cura/Machines/Models/BaseMaterialsModel.py b/cura/Machines/Models/BaseMaterialsModel.py index 0a5e58d52f..d62b848343 100644 --- a/cura/Machines/Models/BaseMaterialsModel.py +++ b/cura/Machines/Models/BaseMaterialsModel.py @@ -96,7 +96,7 @@ class BaseMaterialsModel(ListModel): self._update() self.enabledChanged.emit() - @pyqtProperty(bool, fset=setEnabled, notify=enabledChanged) + @pyqtProperty(bool, fset = setEnabled, notify = enabledChanged) def enabled(self): return self._enabled @@ -169,4 +169,3 @@ class BaseMaterialsModel(ListModel): "is_favorite": root_material_id in self._favorite_ids } return item - diff --git a/cura/Machines/VariantNode.py b/cura/Machines/VariantNode.py index abcd588c4a..7262ccfb58 100644 --- a/cura/Machines/VariantNode.py +++ b/cura/Machines/VariantNode.py @@ -127,4 +127,5 @@ class VariantNode(ContainerNode): if "empty_material" in self.materials: del self.materials["empty_material"] self.materials[base_file] = MaterialNode(container.getId(), variant = self) - self.materials[base_file].materialChanged.connect(self.materialsChanged) \ No newline at end of file + self.materials[base_file].materialChanged.connect(self.materialsChanged) + self.materialsChanged.emit(self.materials[base_file]) \ No newline at end of file From 0398c404fb9228da3dc7436f82497b10d78aa04c Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Aug 2019 18:01:19 +0200 Subject: [PATCH 21/50] Update tree when material gets deleted Contributes to issue CURA-6600. --- cura/Machines/VariantNode.py | 42 ++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/cura/Machines/VariantNode.py b/cura/Machines/VariantNode.py index 7262ccfb58..00be1e3807 100644 --- a/cura/Machines/VariantNode.py +++ b/cura/Machines/VariantNode.py @@ -33,10 +33,11 @@ class VariantNode(ContainerNode): container_registry = ContainerRegistry.getInstance() self.variant_name = container_registry.findContainersMetadata(id = container_id)[0]["name"] # Store our own name so that we can filter more easily. container_registry.containerAdded.connect(self._materialAdded) + container_registry.containerRemoved.connect(self._materialRemoved) self._loadAll() ## (Re)loads all materials under this variant. - def _loadAll(self): + def _loadAll(self) -> None: container_registry = ContainerRegistry.getInstance() if not self.machine.has_materials: @@ -55,10 +56,8 @@ class VariantNode(ContainerNode): materials_per_base_file.update({material["base_file"]: material for material in variant_specific_materials}) # Variant-specific profiles override all of those. materials = materials_per_base_file.values() - filtered_materials = [] - for material in materials: - if material["id"] not in self.machine.exclude_materials: - filtered_materials.append(material) + # Filter materials based on the exclude_materials property. + filtered_materials = [material for material in materials if material["id"] not in self.machine.exclude_materials] for material in filtered_materials: base_file = material["base_file"] @@ -94,7 +93,7 @@ class VariantNode(ContainerNode): ## When a material gets added to the set of profiles, we need to update our # tree here. - def _materialAdded(self, container: ContainerInterface): + def _materialAdded(self, container: ContainerInterface) -> None: if container.getMetaDataEntry("type") != "material": return # Not interested. if not self.machine.has_materials: @@ -128,4 +127,33 @@ class VariantNode(ContainerNode): del self.materials["empty_material"] self.materials[base_file] = MaterialNode(container.getId(), variant = self) self.materials[base_file].materialChanged.connect(self.materialsChanged) - self.materialsChanged.emit(self.materials[base_file]) \ No newline at end of file + self.materialsChanged.emit(self.materials[base_file]) + + def _materialRemoved(self, container: ContainerInterface) -> None: + if container.getMetaDataEntry("type") != "material": + return # Only interested in materials. + base_file = container.getMetaDataEntry("base_file") + if base_file not in self.materials: + return # We don't track this material anyway. No need to remove it. + + original_node = self.materials[base_file] + del self.materials[base_file] + self.materialsChanged.emit(original_node) + + # Now a different material from the same base file may have been hidden because it was not as specific as the one we deleted. + # Search for any submaterials from that base file that are still left. + materials_same_base_file = ContainerRegistry.getInstance().findContainersMetadata(base_file = base_file) + if materials_same_base_file: + most_specific_submaterial = materials_same_base_file[0] + for submaterial in materials_same_base_file: + if submaterial["definition"] == self.machine.container_id: + if most_specific_submaterial["definition"] == "fdmprinter": + most_specific_submaterial = submaterial + if most_specific_submaterial.get("variant", "empty") == "empty" and submaterial.get("variant", "empty") == self.variant_name: + most_specific_submaterial = submaterial + self.materials[base_file] = MaterialNode(most_specific_submaterial["id"], variant = self) + self.materialsChanged.emit(self.materials[base_file]) + + if not self.materials: # The last available material just got deleted and there is nothing with the same base file to replace it. + self.materials["empty_material"] = MaterialNode("empty_material", variant = self) + self.materialsChanged.emit(self.materials["empty_material"]) \ No newline at end of file From 944d1090cf0e16034ad47394f214380d2a7d8eaa Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Aug 2019 08:42:39 +0200 Subject: [PATCH 22/50] Use ContainerTree to get current quality groups and MachineManager to update This removes all dependencies from the quality manager. Contributes to issue CURA-6600. --- cura/Machines/Models/QualityManagementModel.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cura/Machines/Models/QualityManagementModel.py b/cura/Machines/Models/QualityManagementModel.py index aa2cb3ba5f..dbe2a94033 100644 --- a/cura/Machines/Models/QualityManagementModel.py +++ b/cura/Machines/Models/QualityManagementModel.py @@ -28,10 +28,9 @@ class QualityManagementModel(ListModel): self._container_registry = CuraApplication.getInstance().getContainerRegistry() self._machine_manager = CuraApplication.getInstance().getMachineManager() self._extruder_manager = CuraApplication.getInstance().getExtruderManager() - self._quality_manager = CuraApplication.getInstance().getQualityManager() self._machine_manager.globalContainerChanged.connect(self._update) - self._quality_manager.qualitiesUpdated.connect(self._update) + self._machine_manager.activeQualityGroupChanged.connect(self._onChange) self._update() @@ -43,8 +42,9 @@ class QualityManagementModel(ListModel): self.setItems([]) return - quality_group_dict = ContainerTree.getInstance().getCurrentQualityGroups() - quality_changes_group_list = self._quality_manager.getQualityChangesGroups(global_stack) + container_tree = ContainerTree.getInstance() + quality_group_dict = container_tree.getCurrentQualityGroups() + quality_changes_group_list = container_tree.getCurrentQualityChangesGroups() available_quality_types = set(quality_type for quality_type, quality_group in quality_group_dict.items() if quality_group.is_available) From 972531b0a6db361779a1708c4df56db63635a4ab Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Aug 2019 08:49:28 +0200 Subject: [PATCH 23/50] No longer update upon switching active profile It's not necessary since our model doesn't depend on that. Contributes to issue CURA-6600. --- cura/Machines/Models/QualityManagementModel.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cura/Machines/Models/QualityManagementModel.py b/cura/Machines/Models/QualityManagementModel.py index dbe2a94033..792159b31d 100644 --- a/cura/Machines/Models/QualityManagementModel.py +++ b/cura/Machines/Models/QualityManagementModel.py @@ -5,6 +5,7 @@ from PyQt5.QtCore import Qt, pyqtSlot from UM.Logger import Logger from UM.Qt.ListModel import ListModel +import cura.CuraApplication # Imported this way to prevent circular imports. from cura.Machines.ContainerTree import ContainerTree # @@ -24,13 +25,12 @@ class QualityManagementModel(ListModel): self.addRoleName(self.QualityGroupRole, "quality_group") self.addRoleName(self.QualityChangesGroupRole, "quality_changes_group") - from cura.CuraApplication import CuraApplication - self._container_registry = CuraApplication.getInstance().getContainerRegistry() - self._machine_manager = CuraApplication.getInstance().getMachineManager() - self._extruder_manager = CuraApplication.getInstance().getExtruderManager() + application = cura.CuraApplication.CuraApplication.getInstance() + self._container_registry = application.getContainerRegistry() + self._machine_manager = application.getMachineManager() + self._extruder_manager = application.getExtruderManager() self._machine_manager.globalContainerChanged.connect(self._update) - self._machine_manager.activeQualityGroupChanged.connect(self._onChange) self._update() From b2cee850c971c84896cf48c4ce99ea83f20222a8 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Aug 2019 08:58:03 +0200 Subject: [PATCH 24/50] Use container tree to find current available quality groups Contributes to issue CURA-6600. --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index d5fa1cb272..873d5e8fae 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -23,6 +23,7 @@ from UM.Settings.ContainerRegistry import ContainerRegistry from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType from UM.Job import Job from UM.Preferences import Preferences +from cura.Machines.ContainerTree import ContainerTree from cura.Machines.VariantType import VariantType from cura.Settings.CuraStackBuilder import CuraStackBuilder @@ -992,8 +993,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): def _updateActiveMachine(self, global_stack): # Actually change the active machine. machine_manager = Application.getInstance().getMachineManager() - material_manager = Application.getInstance().getMaterialManager() - quality_manager = Application.getInstance().getQualityManager() + container_tree = ContainerTree.getInstance() machine_manager.setActiveMachine(global_stack.getId()) @@ -1003,7 +1003,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): global_stack.setMetaDataEntry(key, value) if self._quality_changes_to_apply: - quality_changes_group_list = quality_manager.getQualityChangesGroups(global_stack) + quality_changes_group_list = container_tree.getCurrentQualityChangesGroups() quality_changes_group = next((qcg for qcg in quality_changes_group_list if qcg.name == self._quality_changes_to_apply), None) if not quality_changes_group: Logger.log("e", "Could not find quality_changes [%s]", self._quality_changes_to_apply) @@ -1011,13 +1011,12 @@ class ThreeMFWorkspaceReader(WorkspaceReader): machine_manager.setQualityChangesGroup(quality_changes_group, no_dialog = True) else: self._quality_type_to_apply = self._quality_type_to_apply.lower() - quality_group_dict = quality_manager.getQualityGroups(global_stack) + quality_group_dict = container_tree.getCurrentQualityGroups() if self._quality_type_to_apply in quality_group_dict: quality_group = quality_group_dict[self._quality_type_to_apply] else: Logger.log("i", "Could not find quality type [%s], switch to default", self._quality_type_to_apply) preferred_quality_type = global_stack.getMetaDataEntry("preferred_quality_type") - quality_group_dict = quality_manager.getQualityGroups(global_stack) quality_group = quality_group_dict.get(preferred_quality_type) if quality_group is None: Logger.log("e", "Could not get preferred quality type [%s]", preferred_quality_type) From 4fd886f2e8dd3e1de605101050653e3250705075 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Aug 2019 09:02:26 +0200 Subject: [PATCH 25/50] Use container tree to get current quality groups Contributes to issue CURA-6600. --- cura/Settings/MachineManager.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 5ad1c2dc43..406ae0bdba 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -1135,9 +1135,8 @@ class MachineManager(QObject): # A custom quality can be created based on "not supported". # In that case, do not set quality containers to empty. quality_group = None - if quality_type != "not_supported": - quality_group_dict = QualityManager.getInstance().getQualityGroups(self._global_container_stack) - quality_group = quality_group_dict.get(quality_type) + if quality_type != "not_supported": # Find the quality group that the quality changes was based on. + quality_group = ContainerTree.getInstance().getCurrentQualityGroups().get(quality_type) if quality_group is None: self._fixQualityChangesGroupToNotSupported(quality_changes_group) From 83c8b814d9a7e3922aee133854a880f6c9de5181 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Aug 2019 10:33:33 +0200 Subject: [PATCH 26/50] Apply intent category when changing to quality changes group This essentially makes the quality changes depend on the intent that was active when it was created. Contributes to issue CURA-6600. --- cura/Machines/IntentNode.py | 5 ++++- cura/Settings/MachineManager.py | 29 ++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/cura/Machines/IntentNode.py b/cura/Machines/IntentNode.py index 232498536c..8ec8dd6d6b 100644 --- a/cura/Machines/IntentNode.py +++ b/cura/Machines/IntentNode.py @@ -3,6 +3,8 @@ from typing import TYPE_CHECKING +from UM.Settings.ContainerRegistry import ContainerRegistry + from cura.Machines.ContainerNode import ContainerNode if TYPE_CHECKING: @@ -14,4 +16,5 @@ if TYPE_CHECKING: class IntentNode(ContainerNode): def __init__(self, container_id: str, quality: "QualityNode") -> None: super().__init__(container_id) - self.quality = quality \ No newline at end of file + self.quality = quality + self.intent_category = ContainerRegistry.getInstance().findContainersMetadata(id = container_id)[0].get("intent_category", "default") \ No newline at end of file diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 406ae0bdba..57ec0003cd 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -38,7 +38,7 @@ from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderStack import ExtruderStack from cura.Settings.cura_empty_instance_containers import (empty_definition_changes_container, empty_variant_container, empty_material_container, empty_quality_container, - empty_quality_changes_container) + empty_quality_changes_container, empty_intent_container) from .CuraStackBuilder import CuraStackBuilder @@ -1127,6 +1127,7 @@ class MachineManager(QObject): if container: container.setMetaDataEntry("quality_type", "not_supported") quality_changes_group.quality_type = "not_supported" + quality_changes_group.intent_category = "default" def _setQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup") -> None: if self._global_container_stack is None: @@ -1166,6 +1167,8 @@ class MachineManager(QObject): extruder.quality = quality_container extruder.qualityChanges = quality_changes_container + self.setIntentByCategory(quality_changes_group.intent_category) + self.activeQualityGroupChanged.emit() self.activeQualityChangesGroupChanged.emit() @@ -1498,6 +1501,30 @@ class MachineManager(QObject): if not no_dialog and self.hasUserSettings and self._application.getPreferences().getValue("cura/active_mode") == 1: self._application.discardOrKeepProfileChanges() + ## Change the intent category of the current printer. + # + # All extruders can change their profiles. If an intent profile is + # available with the desired intent category, that one will get chosen. + # 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. + def setIntentByCategory(self, intent_category: str) -> None: + global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() + container_tree = ContainerTree.getInstance() + for extruder in global_stack.extruderList: + definition_id = global_stack.definition.getId() + variant_name = extruder.variant.getName() + material_base_file = extruder.material.getMetaDataEntry("base_file") + quality_id = extruder.quality.getId() + quality_node = container_tree.machines[definition_id].variants[variant_name].materials[material_base_file].qualities[quality_id] + + for intent_node in quality_node.intents.values(): + if intent_node.intent_category == intent_category: # Found an intent with the correct category. + extruder.intent = intent_node.container + break + else: # No intent had the correct category. + extruder.intent = empty_intent_container + @pyqtProperty(QObject, fset = setQualityGroup, notify = activeQualityGroupChanged) def activeQualityGroup(self) -> Optional["QualityGroup"]: global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() From 64a8aff6277cfacaac96d06872a924c00e0c6016 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Aug 2019 10:35:56 +0200 Subject: [PATCH 27/50] Remove usage of deprecated getContainer() function This was the last place where it was used in our code base. Contributes to issue CURA-6600. --- cura/Machines/Models/QualitySettingsModel.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cura/Machines/Models/QualitySettingsModel.py b/cura/Machines/Models/QualitySettingsModel.py index 796d11fb87..f0f7a55228 100644 --- a/cura/Machines/Models/QualitySettingsModel.py +++ b/cura/Machines/Models/QualitySettingsModel.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018 Ultimaker B.V. +# Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from PyQt5.QtCore import pyqtProperty, pyqtSignal, Qt @@ -93,8 +93,8 @@ class QualitySettingsModel(ListModel): quality_node = quality_group.nodes_for_extruders.get(str(self._selected_position)) settings_keys = quality_group.getAllKeys() quality_containers = [] - if quality_node is not None and quality_node.getContainer() is not None: - quality_containers.append(quality_node.getContainer()) + if quality_node is not None and quality_node.container is not None: + quality_containers.append(quality_node.container) # Here, if the user has selected a quality changes, then "quality_changes_group" will not be None, and we fetch # the settings in that quality_changes_group. From c9191beb6158fc5a341661b1ddced8705548e99c Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Aug 2019 11:11:13 +0200 Subject: [PATCH 28/50] Fix updating intents list when printer changes Contributes to issue CURA-6600. --- cura/Machines/Models/IntentModel.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/cura/Machines/Models/IntentModel.py b/cura/Machines/Models/IntentModel.py index 5a44883b76..c61d0bfcca 100644 --- a/cura/Machines/Models/IntentModel.py +++ b/cura/Machines/Models/IntentModel.py @@ -6,7 +6,6 @@ from typing import Optional, List, Dict, Any from PyQt5.QtCore import Qt, QObject, pyqtProperty, pyqtSignal from UM.Qt.ListModel import ListModel -from UM.Settings.ContainerRegistry import ContainerRegistry from cura.Machines.ContainerTree import ContainerTree from cura.Settings.IntentManager import IntentManager @@ -25,9 +24,9 @@ class IntentModel(ListModel): self._intent_category = "engineering" - ContainerRegistry.getInstance().containerAdded.connect(self._onChanged) - ContainerRegistry.getInstance().containerRemoved.connect(self._onChanged) - + machine_manager = cura.CuraApplication.CuraApplication.getInstance().getMachineManager() + machine_manager.globalContainerChanged.connect(self._update) + machine_manager.activeStackChanged.connect(self._update) self._update() intentCategoryChanged = pyqtSignal() @@ -42,10 +41,6 @@ class IntentModel(ListModel): def intentCategory(self) -> str: return self._intent_category - def _onChanged(self, container): - if container.getMetaDataEntry("type") == "intent": - self._update() - def _update(self) -> None: new_items = [] # type: List[Dict[str, Any]] global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() From 5a7054ecc330d4019715205bfee5d91354d2e42e Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Aug 2019 13:16:39 +0200 Subject: [PATCH 29/50] Remove references to quality manager The last two remaining here. Contributes to issue CURA-6600. --- cura/Settings/MachineManager.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 57ec0003cd..6adcd8cb3a 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -26,7 +26,6 @@ import cura.CuraApplication # Imported like this to prevent circular references from cura.Machines.ContainerNode import ContainerNode from cura.Machines.ContainerTree import ContainerTree -from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch, QualityManager from cura.Machines.MaterialManager import MaterialManager from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice, ConnectionType @@ -696,9 +695,10 @@ class MachineManager(QObject): # \returns DefinitionID (string) if found, empty string otherwise @pyqtProperty(str, notify = globalContainerChanged) def activeQualityDefinitionId(self) -> str: - if self._global_container_stack: - return getMachineDefinitionIDForQualitySearch(self._global_container_stack.definition) - return "" + global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() + if not global_stack: + return "" + return ContainerTree.getInstance().machines[global_stack.definition.getId()].quality_definition ## Gets how the active definition calls variants # Caveat: per-definition-variant-title is currently not translated (though the fallback is) @@ -1468,9 +1468,7 @@ class MachineManager(QObject): if self._global_container_stack is None: return # Get all the quality groups for this global stack and filter out by quality_type - quality_group_dict = self._application.getQualityManager().getQualityGroups(self._global_container_stack) - quality_group = quality_group_dict[quality_type] - self.setQualityGroup(quality_group) + self.setQualityGroup(ContainerTree.getInstance().getCurrentQualityGroups()[quality_type]) ## Optionally provide global_stack if you want to use your own # The active global_stack is treated differently. From 6dd03336269e53821ac6317071f342f534369904 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Aug 2019 13:21:23 +0200 Subject: [PATCH 30/50] Use container tree instead of getMachineDefinitionIdForQualitySearch It's in the metadata there, so use it. Contributes to issue CURA-6600. --- cura/Settings/CuraContainerRegistry.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index c288f3c0ce..918e6a5257 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -381,7 +381,8 @@ class CuraContainerRegistry(ContainerRegistry): global_stack = Application.getInstance().getGlobalContainerStack() if global_stack is None: return None - definition_id = getMachineDefinitionIDForQualitySearch(global_stack.definition) + machine_node = ContainerTree.getInstance().machines[global_stack.definition.getId()] + definition_id = machine_node.quality_definition profile.setDefinition(definition_id) # Check to make sure the imported profile actually makes sense in context of the current configuration. From 61e13087cd7a7503db4daf104ae2e7bec023cf14 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Aug 2019 13:26:18 +0200 Subject: [PATCH 31/50] Fix testing for available quality types Contributes to issue CURA-6600. --- cura/Settings/CuraContainerRegistry.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index 918e6a5257..4cf311f5ca 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -381,14 +381,13 @@ class CuraContainerRegistry(ContainerRegistry): global_stack = Application.getInstance().getGlobalContainerStack() if global_stack is None: return None - machine_node = ContainerTree.getInstance().machines[global_stack.definition.getId()] - definition_id = machine_node.quality_definition + definition_id = ContainerTree.getInstance().machines[global_stack.definition.getId()].quality_definition profile.setDefinition(definition_id) # Check to make sure the imported profile actually makes sense in context of the current configuration. # This prevents issues where importing a "draft" profile for a machine without "draft" qualities would report as # successfully imported but then fail to show up. - quality_group_dict = ContainerTree.getInstance().machines[definition_id] + quality_group_dict = ContainerTree.getInstance().getCurrentQualityGroups() # "not_supported" profiles can be imported. if quality_type != empty_quality_container.getMetaDataEntry("quality_type") and quality_type not in quality_group_dict: return catalog.i18nc("@info:status", "Could not find a quality type {0} for the current configuration.", quality_type) From a05f077df8a9b5303eaf6e38d4525fe1d09a312d Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Aug 2019 13:35:49 +0200 Subject: [PATCH 32/50] Use fdmprinter for machines that don't have printer-specific qualities This encodes the behaviour of QualityManager.getMachineDefinitionIDForQualitySearch. Contributes to issue CURA-6600. --- 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 ac293a1503..e7930b6a1f 100644 --- a/cura/Machines/MachineNode.py +++ b/cura/Machines/MachineNode.py @@ -36,7 +36,7 @@ class MachineNode(ContainerNode): self.has_variants = parseBool(my_metadata.get("has_variants", "false")) self.has_machine_materials = parseBool(my_metadata.get("has_machine_materials", "false")) self.has_machine_quality = parseBool(my_metadata.get("has_machine_quality", "false")) - self.quality_definition = my_metadata.get("quality_definition", container_id) + self.quality_definition = my_metadata.get("quality_definition", container_id) if self.has_machine_quality else "fdmprinter" self.exclude_materials = my_metadata.get("exclude_materials", []) self.preferred_variant_name = my_metadata.get("preferred_variant_name", "") self.preferred_material = my_metadata.get("preferred_material", "") From 24fd67c36097429e1e2d4ae6fc20f193619b2f5d Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Aug 2019 13:53:21 +0200 Subject: [PATCH 33/50] Also try looking for material-specific profiles, not just by type Type is only a fallback after the exact ID match. This way we can also have profiles specific to Ultimaker PLA Red and such in the future. Contributes to issue CURA-6600. --- cura/Machines/MaterialNode.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/cura/Machines/MaterialNode.py b/cura/Machines/MaterialNode.py index a5a3bd8e72..988adeb830 100644 --- a/cura/Machines/MaterialNode.py +++ b/cura/Machines/MaterialNode.py @@ -62,15 +62,16 @@ class MaterialNode(ContainerNode): qualities = container_registry.findInstanceContainersMetadata(type = "quality", definition = "fdmprinter") else: # Need to find the qualities that specify a material profile with the same material type. - my_material_type = self.material_type - qualities = [] - qualities_any_material = container_registry.findInstanceContainersMetadata(type = "quality", definition = self.variant.machine.quality_definition, variant = self.variant.variant_name) - for material_metadata in container_registry.findInstanceContainersMetadata(type = "material", material = my_material_type): - qualities.extend((quality for quality in qualities_any_material if quality["material"] == material_metadata["id"])) - if not qualities: # No quality profiles found. Go by GUID then. - my_guid = self.guid - for material_metadata in container_registry.findInstanceContainersMetadata(type = "material", guid = my_guid): + qualities = container_registry.findInstanceContainersMetadata(type = "quality", definition = self.variant.machine.quality_definition, variant = self.variant.variant_name, material = self.container_id) # First try by exact material ID. + if not qualities: + my_material_type = self.material_type + qualities_any_material = container_registry.findInstanceContainersMetadata(type = "quality", definition = self.variant.machine.quality_definition, variant = self.variant.variant_name) + for material_metadata in container_registry.findInstanceContainersMetadata(type = "material", material = my_material_type): qualities.extend((quality for quality in qualities_any_material if quality["material"] == material_metadata["id"])) + if not qualities: # No quality profiles found. Go by GUID then. + my_guid = self.guid + for material_metadata in container_registry.findInstanceContainersMetadata(type = "material", guid = my_guid): + qualities.extend((quality for quality in qualities_any_material if quality["material"] == material_metadata["id"])) for quality in qualities: quality_id = quality["id"] From ae77f9124c7e417e79812d4c42956e224f17a430 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Aug 2019 13:55:11 +0200 Subject: [PATCH 34/50] Use container tree to determine quality_definition of profile and printer To see if they match. Contributes to issue CURA-6600. --- cura/Settings/CuraContainerRegistry.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index 4cf311f5ca..c551431882 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -178,6 +178,7 @@ class CuraContainerRegistry(ContainerRegistry): global_stack = Application.getInstance().getGlobalContainerStack() if not global_stack: return {"status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags !", "Can't import profile from {0} before a printer is added.", file_name)} + container_tree = ContainerTree.getInstance() machine_extruders = [] for position in sorted(global_stack.extruders): @@ -227,7 +228,7 @@ class CuraContainerRegistry(ContainerRegistry): # Make sure we have a profile_definition in the file: if profile_definition is None: break - machine_definitions = self.findDefinitionContainers(id = profile_definition) + machine_definitions = self.findContainers(id = profile_definition) if not machine_definitions: Logger.log("e", "Incorrect profile [%s]. Unknown machine type [%s]", file_name, profile_definition) return {"status": "error", @@ -237,8 +238,8 @@ class CuraContainerRegistry(ContainerRegistry): # Get the expected machine definition. # i.e.: We expect gcode for a UM2 Extended to be defined as normal UM2 gcode... - profile_definition = getMachineDefinitionIDForQualitySearch(machine_definition) - expected_machine_definition = getMachineDefinitionIDForQualitySearch(global_stack.definition) + profile_definition = container_tree.machines[machine_definition.getId()].quality_definition + expected_machine_definition = container_tree.machines[global_stack.definition.getId()].quality_definition # And check if the profile_definition matches either one (showing error if not): if profile_definition != expected_machine_definition: From 7c90b5dd306629d3a3e99f38cfd9dc0a2760643f Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Aug 2019 13:56:37 +0200 Subject: [PATCH 35/50] Use shortcut to get current quality changes groups Contributes to issue CURA-6600. --- .../Models/CustomQualityProfilesDropDownMenuModel.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/cura/Machines/Models/CustomQualityProfilesDropDownMenuModel.py b/cura/Machines/Models/CustomQualityProfilesDropDownMenuModel.py index 11ea391aaa..3ade02120d 100644 --- a/cura/Machines/Models/CustomQualityProfilesDropDownMenuModel.py +++ b/cura/Machines/Models/CustomQualityProfilesDropDownMenuModel.py @@ -19,11 +19,7 @@ class CustomQualityProfilesDropDownMenuModel(QualityProfilesDropDownMenuModel): Logger.log("d", "No active GlobalStack, set %s as empty.", self.__class__.__name__) return - variant_names = [extruder.variant.getName() for extruder in active_global_stack.extruders.values()] - material_bases = [extruder.material.getMetaDataEntry("base_file") for extruder in active_global_stack.extruders.values()] - extruder_enabled = [extruder.isEnabled for extruder in active_global_stack.extruders.values()] - machine_node = ContainerTree.getInstance().machines[active_global_stack.definition.getId()] - quality_changes_list = machine_node.getQualityChangesGroups(variant_names, material_bases, extruder_enabled) + quality_changes_list = ContainerTree.getInstance().getCurrentQualityChangesGroups() item_list = [] for quality_changes_group in sorted(quality_changes_list, key = lambda qgc: qgc.name.lower()): From 619adcb5b3e15ff176d7fa3222c48d1bf868a866 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Aug 2019 14:07:49 +0200 Subject: [PATCH 36/50] Fix handling quality_changes_group by metadata We only need to access the metadata so this is fine. Contributes to issue CURA-6600. --- cura/Settings/MachineManager.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 6adcd8cb3a..3386c4d396 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -1121,11 +1121,9 @@ class MachineManager(QObject): self.activeQualityChangesGroupChanged.emit() def _fixQualityChangesGroupToNotSupported(self, quality_changes_group: "QualityChangesGroup") -> None: - nodes = [quality_changes_group.node_for_global] + list(quality_changes_group.nodes_for_extruders.values()) - containers = [n.container for n in nodes if n is not None] - for container in containers: - if container: - container.setMetaDataEntry("quality_type", "not_supported") + metadatas = [quality_changes_group.metadata_for_global] + list(quality_changes_group.metadata_per_extruder.values()) + for metadata in metadatas: + metadata["quality_type"] = "not_supported" # This actually changes the metadata of the container since they are stored by reference! quality_changes_group.quality_type = "not_supported" quality_changes_group.intent_category = "default" From 35907e52285c9f46e3c030a516c1e7ccb86d3d7e Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Aug 2019 14:17:17 +0200 Subject: [PATCH 37/50] Use Pythonic way of creating list of N elements Contributes to issue CURA-6600. --- 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 e7930b6a1f..3b35a3db02 100644 --- a/cura/Machines/MachineNode.py +++ b/cura/Machines/MachineNode.py @@ -62,7 +62,7 @@ class MachineNode(ContainerNode): Logger.log("e", "The number of extruders in the list of variants (" + str(len(variant_names)) + ") is not equal to the number of extruders in the list of materials (" + str(len(material_bases)) + ") or the list of enabled extruders (" + str(len(extruder_enabled)) + ").") return {} # For each extruder, find which quality profiles are available. Later we'll intersect the quality types. - qualities_per_type_per_extruder = [{} for _ in range(len(variant_names))] # type: List[Dict[str, QualityNode]] + qualities_per_type_per_extruder = [{}] * len(variant_names) # type: List[Dict[str, QualityNode]] for extruder_nr, variant_name in enumerate(variant_names): if not extruder_enabled[extruder_nr]: continue # No qualities are available in this extruder. It'll get skipped when calculating the available quality types. From f89f47f8d53b02115fd706b53fbca6dd07b4ff84 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Aug 2019 14:31:07 +0200 Subject: [PATCH 38/50] Get quality definition from ContainerTree rather than QualityManager Contributes to issue CURA-6600. --- plugins/3MFReader/ThreeMFReader.py | 4 ++-- plugins/3MFReader/ThreeMFWorkspaceReader.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/3MFReader/ThreeMFReader.py b/plugins/3MFReader/ThreeMFReader.py index b81d0858a4..20eb9b29dc 100755 --- a/plugins/3MFReader/ThreeMFReader.py +++ b/plugins/3MFReader/ThreeMFReader.py @@ -19,12 +19,12 @@ from UM.Scene.SceneNode import SceneNode #For typing. from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType from cura.CuraApplication import CuraApplication +from cura.Machines.ContainerTree import ContainerTree from cura.Settings.ExtruderManager import ExtruderManager from cura.Scene.CuraSceneNode import CuraSceneNode from cura.Scene.BuildPlateDecorator import BuildPlateDecorator from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator from cura.Scene.ZOffsetDecorator import ZOffsetDecorator -from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch try: @@ -131,7 +131,7 @@ class ThreeMFReader(MeshReader): um_node.callDecoration("setActiveExtruder", default_stack.getId()) # Get the definition & set it - definition_id = getMachineDefinitionIDForQualitySearch(global_container_stack.definition) + definition_id = ContainerTree.getInstance().machines[global_container_stack.definition.getId()].quality_definition um_node.callDecoration("getStack").getTop().setDefinition(definition_id) setting_container = um_node.callDecoration("getStack").getTop() diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 873d5e8fae..d7cc2f0b70 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -747,8 +747,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): self._machine_info.quality_changes_info.name) # Get the correct extruder definition IDs for quality changes - from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch - machine_definition_id_for_quality = getMachineDefinitionIDForQualitySearch(global_stack.definition) + machine_definition_id_for_quality = ContainerTree.getInstance().machines[global_stack.definition.getId()].quality_definition machine_definition_for_quality = self._container_registry.findDefinitionContainers(id = machine_definition_id_for_quality)[0] quality_changes_info = self._machine_info.quality_changes_info From b9370f864e7d06c4e53d6f756bf75f5b9a3b163b Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Aug 2019 14:34:00 +0200 Subject: [PATCH 39/50] Get quality definition from container tree Contributes to issue CURA-6600. --- cura/Settings/CuraContainerRegistry.py | 1 - plugins/GCodeWriter/GCodeWriter.py | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index c551431882..80698e982a 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -29,7 +29,6 @@ from . import GlobalStack import cura.CuraApplication from cura.Settings.cura_empty_instance_containers import empty_quality_container from cura.Machines.ContainerTree import ContainerTree -from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch from cura.ReaderWriters.ProfileReader import NoProfileException, ProfileReader from UM.i18n import i18nCatalog diff --git a/plugins/GCodeWriter/GCodeWriter.py b/plugins/GCodeWriter/GCodeWriter.py index 3e5bf59e73..2c6603cb3f 100644 --- a/plugins/GCodeWriter/GCodeWriter.py +++ b/plugins/GCodeWriter/GCodeWriter.py @@ -9,8 +9,7 @@ from UM.Mesh.MeshWriter import MeshWriter from UM.Logger import Logger from UM.Application import Application from UM.Settings.InstanceContainer import InstanceContainer - -from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch +from cura.Machines.ContainerTree import ContainerTree from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") @@ -139,7 +138,7 @@ class GCodeWriter(MeshWriter): flat_global_container.setMetaDataEntry("quality_type", stack.quality.getMetaDataEntry("quality_type", "normal")) # Get the machine definition ID for quality profiles - machine_definition_id_for_quality = getMachineDefinitionIDForQualitySearch(stack.definition) + machine_definition_id_for_quality = ContainerTree.getInstance().machines[stack.definition.getId()].quality_definition flat_global_container.setMetaDataEntry("definition", machine_definition_id_for_quality) serialized = flat_global_container.serialize() From ba608c5987c1ac574b0c867f1d4a3d8c3f0e75f4 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Aug 2019 14:57:02 +0200 Subject: [PATCH 40/50] Use container tree to reset quality after deleting packages Contributes to issue CURA-6600. --- plugins/GCodeWriter/GCodeWriter.py | 2 +- plugins/Toolbox/src/Toolbox.py | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/plugins/GCodeWriter/GCodeWriter.py b/plugins/GCodeWriter/GCodeWriter.py index 2c6603cb3f..9f9d6ebb79 100644 --- a/plugins/GCodeWriter/GCodeWriter.py +++ b/plugins/GCodeWriter/GCodeWriter.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017 Ultimaker B.V. +# Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. import re # For escaping characters in the settings. diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 4dabba87a0..5f73d563c7 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018 Ultimaker B.V. +# Copyright (c) 2019 Ultimaker B.V. # Toolbox is released under the terms of the LGPLv3 or higher. import json @@ -19,6 +19,7 @@ from UM.Version import Version from cura import ApplicationMetadata from cura import UltimakerCloudAuthentication from cura.CuraApplication import CuraApplication +from cura.Machines.ContainerTree import ContainerTree from .AuthorsModel import AuthorsModel from .PackagesModel import PackagesModel @@ -360,14 +361,19 @@ class Toolbox(QObject, Extension): def resetMaterialsQualitiesAndUninstall(self) -> None: application = CuraApplication.getInstance() material_manager = application.getMaterialManager() - quality_manager = application.getQualityManager() machine_manager = application.getMachineManager() + container_tree = ContainerTree.getInstance() for global_stack, extruder_nr, container_id in self._package_used_materials: default_material_node = material_manager.getDefaultMaterial(global_stack, extruder_nr, global_stack.extruders[extruder_nr].variant.getName()) machine_manager.setMaterial(extruder_nr, default_material_node, global_stack = global_stack) for global_stack, extruder_nr, container_id in self._package_used_qualities: - default_quality_group = quality_manager.getDefaultQualityType(global_stack) + variant_names = [extruder.variant.getName() for extruder in global_stack.extruders.values()] + material_bases = [extruder.material.getMetaDataEntry("base_file") for extruder in global_stack.extruders.values()] + extruder_enabled = [extruder.isEnabled for extruder in global_stack.extruders.values()] + definition_id = global_stack.definition.getId() + machine_node = container_tree.machines[definition_id] + default_quality_group = machine_node.getQualityGroups(variant_names, material_bases, extruder_enabled)[machine_node.preferred_quality_type] machine_manager.setQualityGroup(default_quality_group, global_stack = global_stack) if self._package_id_to_uninstall is not None: From b3fd310d37ffe02341f27bbe3c4701eade9e9365 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Aug 2019 15:21:33 +0200 Subject: [PATCH 41/50] Move removeQualityChangesGroup to QualityManagementModel This is an operation specific to the quality management page, so it should be located there. Contributes to issue CURA-6600. --- .../Machines/Models/QualityManagementModel.py | 29 ++++++++++++++++++- cura/Machines/QualityManager.py | 15 +++++----- resources/qml/Preferences/ProfilesPage.qml | 2 +- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/cura/Machines/Models/QualityManagementModel.py b/cura/Machines/Models/QualityManagementModel.py index 792159b31d..eaaf1eb058 100644 --- a/cura/Machines/Models/QualityManagementModel.py +++ b/cura/Machines/Models/QualityManagementModel.py @@ -1,12 +1,18 @@ # Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from PyQt5.QtCore import Qt, pyqtSlot +from typing import TYPE_CHECKING +from PyQt5.QtCore import pyqtSlot, QObject, Qt from UM.Logger import Logger from UM.Qt.ListModel import ListModel + import cura.CuraApplication # Imported this way to prevent circular imports. from cura.Machines.ContainerTree import ContainerTree +from cura.Settings.cura_empty_instance_containers import empty_quality_changes_container + +if TYPE_CHECKING: + from cura.Machines.QualityChangesGroup import QualityChangesGroup # # This the QML model for the quality management page. @@ -34,6 +40,27 @@ class QualityManagementModel(ListModel): self._update() + ## Deletes a custom profile. It will be gone forever. + # \param quality_changes_group The quality changes group representing the + # profile to delete. + @pyqtSlot(QObject) + def removeQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup") -> None: + Logger.log("i", "Removing quality changes group {group_name}".format(group_name = quality_changes_group.name)) + removed_quality_changes_ids = set() + container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry() + for metadata in [quality_changes_group.metadata_for_global] + list(quality_changes_group.metadata_per_extruder.values()): + container_id = metadata["id"] + container_registry.removeContainer(container_id) + removed_quality_changes_ids.add(container_id) + + # Reset all machines that have activated this custom profile. + for global_stack in container_registry.findContainerStacks(type = "machine"): + if global_stack.qualityChanges.getId() in removed_quality_changes_ids: + global_stack.qualityChanges = empty_quality_changes_container + for extruder_stack in container_registry.findContainerStacks(type = "extruder_train"): + if extruder_stack.qualityChanges.getId() in removed_quality_changes_ids: + extruder_stack.qualityChanges = empty_quality_changes_container + def _update(self): Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__)) diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py index 75c8e89e11..94dfdd78e9 100644 --- a/cura/Machines/QualityManager.py +++ b/cura/Machines/QualityManager.py @@ -146,18 +146,19 @@ class QualityManager(QObject): # @pyqtSlot(QObject) def removeQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup") -> None: - Logger.log("i", "Removing quality changes group [%s]", quality_changes_group.name) + Logger.log("i", "Removing quality changes group {group_name}".format(group_name = quality_changes_group.name)) removed_quality_changes_ids = set() - for node in quality_changes_group.getAllNodes(): - container_id = node.container_id - self._container_registry.removeContainer(container_id) + container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry() + for metadata in [quality_changes_group.metadata_for_global] + list(quality_changes_group.metadata_per_extruder.values()): + container_id = metadata["id"] + container_registry.removeContainer(container_id) removed_quality_changes_ids.add(container_id) - # Reset all machines that have activated this quality changes to empty. - for global_stack in self._container_registry.findContainerStacks(type = "machine"): + # Reset all machines that have activated this custom profile. + for global_stack in container_registry.findContainerStacks(type = "machine"): if global_stack.qualityChanges.getId() in removed_quality_changes_ids: global_stack.qualityChanges = self._empty_quality_changes_container - for extruder_stack in self._container_registry.findContainerStacks(type = "extruder_train"): + for extruder_stack in container_registry.findContainerStacks(type = "extruder_train"): if extruder_stack.qualityChanges.getId() in removed_quality_changes_ids: extruder_stack.qualityChanges = self._empty_quality_changes_container diff --git a/resources/qml/Preferences/ProfilesPage.qml b/resources/qml/Preferences/ProfilesPage.qml index da41a0f23c..6907795e1a 100644 --- a/resources/qml/Preferences/ProfilesPage.qml +++ b/resources/qml/Preferences/ProfilesPage.qml @@ -254,7 +254,7 @@ Item onYes: { - base.qualityManager.removeQualityChangesGroup(base.currentItem.quality_changes_group); + base.qualityManagementModel.removeQualityChangesGroup(base.currentItem.quality_changes_group); // reset current item to the first if available qualityListView.currentIndex = -1; // Reset selection. } From be49956de95ab0267ced5d29f73138cdeed931f6 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Aug 2019 15:29:03 +0200 Subject: [PATCH 42/50] Refer to quality management model to remove quality changes This function is deprecated now. Contributes to issue CURA-6600. --- cura/Machines/QualityManager.py | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py index 94dfdd78e9..b95c65d5b5 100644 --- a/cura/Machines/QualityManager.py +++ b/cura/Machines/QualityManager.py @@ -141,26 +141,12 @@ class QualityManager(QObject): # Methods for GUI # - # - # Remove the given quality changes group. - # + ## Deletes a custom profile. It will be gone forever. + # \param quality_changes_group The quality changes group representing the + # profile to delete. @pyqtSlot(QObject) def removeQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup") -> None: - Logger.log("i", "Removing quality changes group {group_name}".format(group_name = quality_changes_group.name)) - removed_quality_changes_ids = set() - container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry() - for metadata in [quality_changes_group.metadata_for_global] + list(quality_changes_group.metadata_per_extruder.values()): - container_id = metadata["id"] - container_registry.removeContainer(container_id) - removed_quality_changes_ids.add(container_id) - - # Reset all machines that have activated this custom profile. - for global_stack in container_registry.findContainerStacks(type = "machine"): - if global_stack.qualityChanges.getId() in removed_quality_changes_ids: - global_stack.qualityChanges = self._empty_quality_changes_container - for extruder_stack in container_registry.findContainerStacks(type = "extruder_train"): - if extruder_stack.qualityChanges.getId() in removed_quality_changes_ids: - extruder_stack.qualityChanges = self._empty_quality_changes_container + return cura.CuraApplication.CuraApplication.getInstance().getQualityManagementModel().removeQualityChangesGroup(quality_changes_group) # # Rename a set of quality changes containers. Returns the new name. From 5fadc7019dea1df7cb03b772b346fcea2f57e971 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Aug 2019 16:31:50 +0200 Subject: [PATCH 43/50] Move renameQualityChangesGroup to QualityManagementModel Contributes to issue CURA-6600. --- .../Machines/Models/QualityManagementModel.py | 29 +++++++++++++++++++ cura/Machines/QualityManager.py | 28 +++++------------- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/cura/Machines/Models/QualityManagementModel.py b/cura/Machines/Models/QualityManagementModel.py index eaaf1eb058..4807471615 100644 --- a/cura/Machines/Models/QualityManagementModel.py +++ b/cura/Machines/Models/QualityManagementModel.py @@ -61,6 +61,35 @@ class QualityManagementModel(ListModel): if extruder_stack.qualityChanges.getId() in removed_quality_changes_ids: extruder_stack.qualityChanges = empty_quality_changes_container + ## Rename a custom profile. + # + # Because the names must be unique, the new name may not actually become + # the name that was given. The actual name is returned by this function. + # \param quality_changes_group The custom profile that must be renamed. + # \param new_name The desired name for the profile. + # \return The actual new name of the profile, after making the name + # unique. + @pyqtSlot(QObject, str, result = str) + def renameQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup", new_name: str) -> str: + Logger.log("i", "Renaming QualityChangesGroup {old_name} to {new_name}.".format(old_name = quality_changes_group.name, new_name = new_name)) + if new_name == quality_changes_group.name: + Logger.log("i", "QualityChangesGroup name {name} unchanged.".format(name = quality_changes_group.name)) + return new_name + + application = cura.CuraApplication.CuraApplication.getInstance() + new_name = application.getContainerRegistry().uniqueName(new_name) + for node in quality_changes_group.getAllNodes(): + container = node.container + if container: + container.setName(new_name) + + quality_changes_group.name = new_name + + application.getMachineManager().activeQualityChanged.emit() + application.getMachineManager().activeQualityGroupChanged.emit() + + return new_name + def _update(self): Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__)) diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py index b95c65d5b5..804f47ad76 100644 --- a/cura/Machines/QualityManager.py +++ b/cura/Machines/QualityManager.py @@ -148,29 +148,17 @@ class QualityManager(QObject): def removeQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup") -> None: return cura.CuraApplication.CuraApplication.getInstance().getQualityManagementModel().removeQualityChangesGroup(quality_changes_group) + ## Rename a custom profile. # - # Rename a set of quality changes containers. Returns the new name. - # + # Because the names must be unique, the new name may not actually become + # the name that was given. The actual name is returned by this function. + # \param quality_changes_group The custom profile that must be renamed. + # \param new_name The desired name for the profile. + # \return The actual new name of the profile, after making the name + # unique. @pyqtSlot(QObject, str, result = str) def renameQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup", new_name: str) -> str: - Logger.log("i", "Renaming QualityChangesGroup[%s] to [%s]", quality_changes_group.name, new_name) - if new_name == quality_changes_group.name: - Logger.log("i", "QualityChangesGroup name [%s] unchanged.", quality_changes_group.name) - return new_name - - new_name = self._container_registry.uniqueName(new_name) - for node in quality_changes_group.getAllNodes(): - container = node.container - if container: - container.setName(new_name) - - quality_changes_group.name = new_name - - application = cura.CuraApplication.CuraApplication.getInstance() - application.getMachineManager().activeQualityChanged.emit() - application.getMachineManager().activeQualityGroupChanged.emit() - - return new_name + return cura.CuraApplication.CuraApplication.getInstance().getQualityManagementModel().removeQualityChangesGroup(quality_changes_group) # # Duplicates the given quality. From 5d8fff69e422435f6e2b504a4b12cd081c57981a Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Aug 2019 16:32:20 +0200 Subject: [PATCH 44/50] Fix referencing nodes for quality changes Contributes to issue CURA-6600. --- cura/Machines/Models/QualitySettingsModel.py | 30 +++++++++++++------- cura/Settings/MachineManager.py | 13 +++++++-- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/cura/Machines/Models/QualitySettingsModel.py b/cura/Machines/Models/QualitySettingsModel.py index f0f7a55228..969fc95c13 100644 --- a/cura/Machines/Models/QualitySettingsModel.py +++ b/cura/Machines/Models/QualitySettingsModel.py @@ -3,7 +3,7 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, Qt -from UM.Application import Application +import cura.CuraApplication from UM.Logger import Logger from UM.Qt.ListModel import ListModel from UM.Settings.ContainerRegistry import ContainerRegistry @@ -35,15 +35,13 @@ class QualitySettingsModel(ListModel): self.addRoleName(self.CategoryRole, "category") self._container_registry = ContainerRegistry.getInstance() - self._application = Application.getInstance() - self._quality_manager = self._application.getQualityManager() + self._application = cura.CuraApplication.CuraApplication.getInstance() + self._application.getMachineManager().activeStackChanged.connect(self._update) self._selected_position = self.GLOBAL_STACK_POSITION #Must be either GLOBAL_STACK_POSITION or an extruder position (0, 1, etc.) self._selected_quality_item = None # The selected quality in the quality management page self._i18n_catalog = None - self._quality_manager.qualitiesUpdated.connect(self._update) - self._update() selectedPositionChanged = pyqtSignal() @@ -99,15 +97,25 @@ class QualitySettingsModel(ListModel): # Here, if the user has selected a quality changes, then "quality_changes_group" will not be None, and we fetch # the settings in that quality_changes_group. if quality_changes_group is not None: + container_registry = ContainerRegistry.getInstance() + global_containers = container_registry.findContainers(id = quality_changes_group.metadata_for_global["id"]) + global_container = None if len(global_containers) == 0 else global_containers[0] + extruders_containers = {pos: container_registry.findContainers(id = quality_changes_group.metadata_per_extruder[pos]["id"]) for pos in quality_changes_group.metadata_per_extruder} + extruders_container = {pos: None if not containers else containers[0] for pos, containers in extruders_containers.items()} if self._selected_position == self.GLOBAL_STACK_POSITION: - quality_changes_node = quality_changes_group.node_for_global + quality_changes_metadata = global_container.getMetaData() else: - quality_changes_node = quality_changes_group.nodes_for_extruders.get(str(self._selected_position)) - if quality_changes_node is not None and quality_changes_node.container is not None: # it can be None if number of extruders are changed during runtime - quality_containers.insert(0, quality_changes_node.container) - settings_keys.update(quality_changes_group.getAllKeys()) + quality_changes_metadata = extruders_container.get(str(self._selected_position)) + if quality_changes_metadata is not None: # It can be None if number of extruders are changed during runtime. + container = container_registry.findContainers(id = quality_changes_metadata["id"]) + if container: + quality_containers.insert(0, container[0]) - # We iterate over all definitions instead of settings in a quality/qualtiy_changes group is because in the GUI, + settings_keys.update(global_container.getAllKeys()) + for container in extruders_container.values(): + settings_keys.update(container.getAllKeys()) + + # We iterate over all definitions instead of settings in a quality/quality_changes group is because in the GUI, # the settings are grouped together by categories, and we had to go over all the definitions to figure out # which setting belongs in which category. current_category = "" diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 3386c4d396..dbd9cf2479 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -1139,8 +1139,13 @@ class MachineManager(QObject): if quality_group is None: self._fixQualityChangesGroupToNotSupported(quality_changes_group) + container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry() quality_changes_container = empty_quality_changes_container quality_container = empty_quality_container # type: Optional[InstanceContainer] + if quality_changes_group.metadata_for_global: + global_containers = container_registry.findContainers(id = quality_changes_group.metadata_for_global["id"]) + if global_containers: + quality_changes_container = global_containers[0] if quality_changes_group.node_for_global and quality_changes_group.node_for_global.container: quality_changes_container = cast(InstanceContainer, quality_changes_group.node_for_global.container) if quality_group is not None and quality_group.node_for_global and quality_group.node_for_global.container: @@ -1150,15 +1155,17 @@ class MachineManager(QObject): self._global_container_stack.qualityChanges = quality_changes_container for position, extruder in self._global_container_stack.extruders.items(): - quality_changes_node = quality_changes_group.nodes_for_extruders.get(position) quality_node = None if quality_group is not None: quality_node = quality_group.nodes_for_extruders.get(position) quality_changes_container = empty_quality_changes_container quality_container = empty_quality_container - if quality_changes_node and quality_changes_node.container: - quality_changes_container = cast(InstanceContainer, quality_changes_node.container) + quality_changes_metadata = quality_changes_group.metadata_for_extruders.get(position) + if quality_changes_metadata: + containers = container_registry.findContainers(id = quality_changes_metadata["id"]) + if containers: + quality_changes_container = cast(InstanceContainer, containers[0]) if quality_node and quality_node.container: quality_container = quality_node.container From 2647b7e994938e8a91879e3d32687332a82f5c69 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Aug 2019 16:44:10 +0200 Subject: [PATCH 45/50] Fix latent querying for node_for_global on quality changes There are no quality changes nodes any more so this all has to happen through metadata. Contributes to issue CURA-6600. --- cura/Settings/MachineManager.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index dbd9cf2479..c1f3ea6cb5 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -1146,8 +1146,10 @@ class MachineManager(QObject): global_containers = container_registry.findContainers(id = quality_changes_group.metadata_for_global["id"]) if global_containers: quality_changes_container = global_containers[0] - if quality_changes_group.node_for_global and quality_changes_group.node_for_global.container: - quality_changes_container = cast(InstanceContainer, quality_changes_group.node_for_global.container) + if quality_changes_group.metadata_for_global: + containers = container_registry.findContainers(id = quality_changes_group.metadata_for_global["id"]) + if containers: + quality_changes_container = cast(InstanceContainer, containers[0]) if quality_group is not None and quality_group.node_for_global and quality_group.node_for_global.container: quality_container = quality_group.node_for_global.container From a7e5830762f9e8abd06b01b915088b7b4184ebc7 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Aug 2019 16:50:14 +0200 Subject: [PATCH 46/50] Fix case if containers are None This happens when the number of containers was resized. Contributes to issue CURA-6600. --- cura/Machines/Models/QualitySettingsModel.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cura/Machines/Models/QualitySettingsModel.py b/cura/Machines/Models/QualitySettingsModel.py index 969fc95c13..6835ffb68f 100644 --- a/cura/Machines/Models/QualitySettingsModel.py +++ b/cura/Machines/Models/QualitySettingsModel.py @@ -102,7 +102,7 @@ class QualitySettingsModel(ListModel): global_container = None if len(global_containers) == 0 else global_containers[0] extruders_containers = {pos: container_registry.findContainers(id = quality_changes_group.metadata_per_extruder[pos]["id"]) for pos in quality_changes_group.metadata_per_extruder} extruders_container = {pos: None if not containers else containers[0] for pos, containers in extruders_containers.items()} - if self._selected_position == self.GLOBAL_STACK_POSITION: + if self._selected_position == self.GLOBAL_STACK_POSITION and global_container: quality_changes_metadata = global_container.getMetaData() else: quality_changes_metadata = extruders_container.get(str(self._selected_position)) @@ -111,9 +111,11 @@ class QualitySettingsModel(ListModel): if container: quality_containers.insert(0, container[0]) - settings_keys.update(global_container.getAllKeys()) + if global_container: + settings_keys.update(global_container.getAllKeys()) for container in extruders_container.values(): - settings_keys.update(container.getAllKeys()) + if container: + settings_keys.update(container.getAllKeys()) # We iterate over all definitions instead of settings in a quality/quality_changes group is because in the GUI, # the settings are grouped together by categories, and we had to go over all the definitions to figure out From b046ff6683ab58fdd7f77e46c6a30675c3bac88a Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Aug 2019 16:54:41 +0200 Subject: [PATCH 47/50] Fix updating quality management page profile list When a custom profile gets added, deleted or renamed we need to update our model. Contributes to issue CURA-6600. --- cura/Machines/Models/QualityManagementModel.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/cura/Machines/Models/QualityManagementModel.py b/cura/Machines/Models/QualityManagementModel.py index 4807471615..fef7ded30c 100644 --- a/cura/Machines/Models/QualityManagementModel.py +++ b/cura/Machines/Models/QualityManagementModel.py @@ -12,6 +12,7 @@ from cura.Machines.ContainerTree import ContainerTree from cura.Settings.cura_empty_instance_containers import empty_quality_changes_container if TYPE_CHECKING: + from UM.Settings.Interfaces import ContainerInterface from cura.Machines.QualityChangesGroup import QualityChangesGroup # @@ -37,6 +38,9 @@ class QualityManagementModel(ListModel): self._extruder_manager = application.getExtruderManager() self._machine_manager.globalContainerChanged.connect(self._update) + self._container_registry.containerAdded.connect(self._qualityChangesListChanged) + self._container_registry.containerRemoved.connect(self._qualityChangesListChanged) + self._container_registry.containerMetaDataChanged.connect(self._qualityChangesListChanged) self._update() @@ -90,6 +94,14 @@ class QualityManagementModel(ListModel): return new_name + ## Triggered when any container changed. + # + # This filters the updates to the container manager: When it applies to + # the list of quality changes, we need to update our list. + def _qualityChangesListChanged(self, container: "ContainerInterface") -> None: + if container.getMetaDataEntry("type") == "quality_changes": + self._update() + def _update(self): Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__)) From 9614cef13557dd9f7c7cf76b1f1fcf032a4768b1 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 29 Aug 2019 09:02:18 +0200 Subject: [PATCH 48/50] Move duplicateQualityChanges to QualityManagementModel This is specific to the quality management page, so moving it here prevents QualityManager from becoming a big ball of spaghetti again. Contributes to issue CURA-6600. --- .../Machines/Models/QualityManagementModel.py | 73 +++++++++++++++++-- cura/Machines/QualityManager.py | 36 +++------ 2 files changed, 77 insertions(+), 32 deletions(-) diff --git a/cura/Machines/Models/QualityManagementModel.py b/cura/Machines/Models/QualityManagementModel.py index fef7ded30c..dcd7a770ee 100644 --- a/cura/Machines/Models/QualityManagementModel.py +++ b/cura/Machines/Models/QualityManagementModel.py @@ -1,11 +1,12 @@ # Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import TYPE_CHECKING +from typing import Any, Dict, Optional, TYPE_CHECKING from PyQt5.QtCore import pyqtSlot, QObject, Qt from UM.Logger import Logger from UM.Qt.ListModel import ListModel +from UM.Settings.InstanceContainer import InstanceContainer # To create new profiles. import cura.CuraApplication # Imported this way to prevent circular imports. from cura.Machines.ContainerTree import ContainerTree @@ -14,6 +15,8 @@ from cura.Settings.cura_empty_instance_containers import empty_quality_changes_c if TYPE_CHECKING: from UM.Settings.Interfaces import ContainerInterface from cura.Machines.QualityChangesGroup import QualityChangesGroup + from cura.Settings.ExtruderStack import ExtruderStack + from cura.Settings.GlobalStack import GlobalStack # # This the QML model for the quality management page. @@ -33,14 +36,14 @@ class QualityManagementModel(ListModel): self.addRoleName(self.QualityChangesGroupRole, "quality_changes_group") application = cura.CuraApplication.CuraApplication.getInstance() - self._container_registry = application.getContainerRegistry() + container_registry = application.getContainerRegistry() self._machine_manager = application.getMachineManager() self._extruder_manager = application.getExtruderManager() self._machine_manager.globalContainerChanged.connect(self._update) - self._container_registry.containerAdded.connect(self._qualityChangesListChanged) - self._container_registry.containerRemoved.connect(self._qualityChangesListChanged) - self._container_registry.containerMetaDataChanged.connect(self._qualityChangesListChanged) + container_registry.containerAdded.connect(self._qualityChangesListChanged) + container_registry.containerRemoved.connect(self._qualityChangesListChanged) + container_registry.containerMetaDataChanged.connect(self._qualityChangesListChanged) self._update() @@ -94,6 +97,66 @@ class QualityManagementModel(ListModel): return new_name + ## Duplicates a given quality profile OR quality changes profile. + # \param new_name The desired name of the new profile. This will be made + # unique, so it might end up with a different name. + # \param quality_model_item The item of this model to duplicate, as + # dictionary. See the descriptions of the roles of this list model. + @pyqtSlot(str, "QVariantMap") + def duplicateQualityChanges(self, new_name: str, quality_model_item: Dict[str, Any]) -> None: + global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() + if not global_stack: + Logger.log("i", "No active global stack, cannot duplicate quality (changes) profile.") + return + + container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry() + new_name = container_registry.uniqueName(new_name) + + 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, new_name, global_stack, extruder_stack = None) + container_registry.addContainer(new_quality_changes) + else: + for metadata in [quality_changes_group.metadata_for_global] + quality_changes_group.metadata_per_extruder.values(): + containers = container_registry.findContainers(id = metadata["id"]) + if not containers: + continue + container = containers[0] + new_id = container_registry.uniqueName(container.getId()) + container_registry.addContainer(container.duplicate(new_id, new_name)) + + ## Create a quality changes container with the given set-up. + # \param quality_type The quality type of the new container. + # \param new_name The name of the container. This name must be unique. + # \param machine The global stack to create the profile for. + # \param extruder_stack The extruder stack to create the profile for. If + # not provided, only a global container will be created. + def _createQualityChanges(self, quality_type: str, new_name: str, machine: "GlobalStack", extruder_stack: Optional["ExtruderStack"]) -> "InstanceContainer": + container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry() + base_id = machine.definition.getId() if extruder_stack is None else extruder_stack.getId() + new_id = base_id + "_" + new_name + new_id = new_id.lower().replace(" ", "_") + new_id = container_registry.uniqueName(new_id) + + # Create a new quality_changes container for the quality. + quality_changes = InstanceContainer(new_id) + quality_changes.setName(new_name) + quality_changes.setMetaDataEntry("type", "quality_changes") + quality_changes.setMetaDataEntry("quality_type", quality_type) + + # If we are creating a container for an extruder, ensure we add that to the container. + if extruder_stack is not None: + quality_changes.setMetaDataEntry("position", extruder_stack.getMetaDataEntry("position")) + + # If the machine specifies qualities should be filtered, ensure we match the current criteria. + machine_definition_id = ContainerTree.getInstance().machines[machine.definition.getId()].quality_definition + quality_changes.setDefinition(machine_definition_id) + + quality_changes.setMetaDataEntry("setting_version", cura.CuraApplication.CuraApplication.getInstance().SettingVersion) + return quality_changes + ## Triggered when any container changed. # # This filters the updates to the container manager: When it applies to diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py index 804f47ad76..7d0ad1e24e 100644 --- a/cura/Machines/QualityManager.py +++ b/cura/Machines/QualityManager.py @@ -1,7 +1,7 @@ # Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import TYPE_CHECKING, Optional, Dict, List +from typing import Any, Dict, List, Optional, TYPE_CHECKING from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot @@ -158,34 +158,16 @@ class QualityManager(QObject): # unique. @pyqtSlot(QObject, str, result = str) def renameQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup", new_name: str) -> str: - return cura.CuraApplication.CuraApplication.getInstance().getQualityManagementModel().removeQualityChangesGroup(quality_changes_group) + return cura.CuraApplication.CuraApplication.getInstance().getQualityManagementModel().removeQualityChangesGroup(quality_changes_group, new_name) - # - # Duplicates the given quality. - # + ## Duplicates a given quality profile OR quality changes profile. + # \param new_name The desired name of the new profile. This will be made + # unique, so it might end up with a different name. + # \param quality_model_item The item of this model to duplicate, as + # dictionary. See the descriptions of the roles of this list model. @pyqtSlot(str, "QVariantMap") - def duplicateQualityChanges(self, quality_changes_name: str, quality_model_item) -> None: - global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() - if not global_stack: - Logger.log("i", "No active global stack, cannot duplicate quality changes.") - return - - 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_name = self._container_registry.uniqueName(quality_changes_name) - new_quality_changes = self._createQualityChanges(quality_group.quality_type, new_name, - global_stack, None) - self._container_registry.addContainer(new_quality_changes) - else: - new_name = self._container_registry.uniqueName(quality_changes_name) - for node in quality_changes_group.getAllNodes(): - container = node.container - if not container: - continue - new_id = self._container_registry.uniqueName(container.getId()) - self._container_registry.addContainer(container.duplicate(new_id, new_name)) + def duplicateQualityChanges(self, quality_changes_name: str, quality_model_item: Dict[str, Any]) -> None: + return cura.CuraApplication.CuraApplication.getInstance().getQualityManagementModel().duplicateQualityChanges(quality_changes_name, quality_model_item) ## Create quality changes containers from the user containers in the active stacks. # From 2676c7fa2ffc4ba5cd4736ea88ce3992afe945af Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 29 Aug 2019 09:03:04 +0200 Subject: [PATCH 49/50] Fix getting container metadata when switching to quality changes profile Contributes to issue CURA-6600. --- cura/Settings/MachineManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index c1f3ea6cb5..89838e6def 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -1163,7 +1163,7 @@ class MachineManager(QObject): quality_changes_container = empty_quality_changes_container quality_container = empty_quality_container - quality_changes_metadata = quality_changes_group.metadata_for_extruders.get(position) + quality_changes_metadata = quality_changes_group.metadata_per_extruder.get(position) if quality_changes_metadata: containers = container_registry.findContainers(id = quality_changes_metadata["id"]) if containers: From d548404dfd4ae235b90861f4a65b4251e6c7ce4f Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 29 Aug 2019 16:23:10 +0200 Subject: [PATCH 50/50] Fix typing issues --- cura/CuraApplication.py | 4 ++-- cura/Machines/Models/MaterialManagementModel.py | 5 ++++- cura/Machines/VariantNode.py | 3 ++- cura/Settings/MachineManager.py | 2 ++ 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index a70ca56da7..b704fb35b2 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -921,12 +921,12 @@ class CuraApplication(QtApplication): # Can't deprecate this function since the deprecation marker collides with pyqtSlot! @pyqtSlot(result = QObject) - def getMaterialManager(self, *args) -> "MaterialManager": + def getMaterialManager(self, *args) -> cura.Machines.MaterialManager.MaterialManager: return cura.Machines.MaterialManager.MaterialManager.getInstance() # Can't deprecate this function since the deprecation marker collides with pyqtSlot! @pyqtSlot(result = QObject) - def getQualityManager(self, *args) -> "QualityManager": + def getQualityManager(self, *args) -> cura.Machines.QualityManager.QualityManager: return cura.Machines.QualityManager.QualityManager.getInstance() def getIntentManager(self, *args) -> IntentManager: diff --git a/cura/Machines/Models/MaterialManagementModel.py b/cura/Machines/Models/MaterialManagementModel.py index 90e63e6240..f9af587293 100644 --- a/cura/Machines/Models/MaterialManagementModel.py +++ b/cura/Machines/Models/MaterialManagementModel.py @@ -152,7 +152,10 @@ class MaterialManagementModel(QObject): extruder_stack = application.getMachineManager().activeStack active_variant_name = extruder_stack.variant.getName() approximate_diameter = int(extruder_stack.approximateMaterialDiameter) - machine_node = ContainerTree.getInstance().machines[application.getGlobalContainerStack().definition.getId()] + global_container_stack = application.getGlobalContainerStack() + if not global_container_stack: + return "" + machine_node = ContainerTree.getInstance().machines[global_container_stack.definition.getId()] preferred_material_node = machine_node.variants[active_variant_name].preferredMaterial(approximate_diameter) # Create a new ID & new metadata for the new material. diff --git a/cura/Machines/VariantNode.py b/cura/Machines/VariantNode.py index 00be1e3807..2680c6d28b 100644 --- a/cura/Machines/VariantNode.py +++ b/cura/Machines/VariantNode.py @@ -14,6 +14,7 @@ if TYPE_CHECKING: from typing import Dict from cura.Machines.MachineNode import MachineNode + ## This class represents an extruder variant in the container tree. # # The subnodes of these nodes are materials. @@ -54,7 +55,7 @@ class VariantNode(ContainerNode): materials_per_base_file = {material["base_file"]: material for material in all_materials} materials_per_base_file.update({material["base_file"]: material for material in printer_specific_materials}) # Printer-specific profiles override global ones. materials_per_base_file.update({material["base_file"]: material for material in variant_specific_materials}) # Variant-specific profiles override all of those. - materials = materials_per_base_file.values() + materials = list(materials_per_base_file.values()) # Filter materials based on the exclude_materials property. filtered_materials = [material for material in materials if material["id"] not in self.machine.exclude_materials] diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 89838e6def..a7447aaa1e 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -1515,6 +1515,8 @@ class MachineManager(QObject): # \param intent_category The intent category to change to. def setIntentByCategory(self, intent_category: str) -> None: global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() + if global_stack is None: + return container_tree = ContainerTree.getInstance() for extruder in global_stack.extruderList: definition_id = global_stack.definition.getId()