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()