Browse Source

Merge branch 'master' of github.com:Ultimaker/Cura into network_rewrite

Jaime van Kessel 7 years ago
parent
commit
24bd32477a

+ 1 - 0
.gitignore

@@ -46,6 +46,7 @@ plugins/cura-siemensnx-plugin
 plugins/CuraVariSlicePlugin
 plugins/CuraLiveScriptingPlugin
 plugins/CuraPrintProfileCreator
+plugins/OctoPrintPlugin
 
 #Build stuff
 CMakeCache.txt

+ 1 - 1
README.md

@@ -3,7 +3,7 @@ Cura
 
 This is the new, shiny frontend for Cura. [daid/Cura](https://github.com/daid/Cura.git) is the old legacy Cura that everyone knows and loves/hates.
 
-We re-worked the whole GUI code at Ultimaker, because the old code started to become a unmaintainable.
+We re-worked the whole GUI code at Ultimaker, because the old code started to become unmaintainable.
 
 
 Logging Issues

+ 0 - 5
cura/ConvexHullDecorator.py

@@ -56,11 +56,6 @@ class ConvexHullDecorator(SceneNodeDecorator):
         if self._node is None:
             return None
 
-        if getattr(self._node, "_non_printing_mesh", False):
-            # infill_mesh, cutting_mesh and anti_overhang_mesh do not need a convex hull
-            # node._non_printing_mesh is set in SettingOverrideDecorator
-            return None
-
         hull = self._compute2DConvexHull()
 
         if self._global_stack and self._node:

+ 3 - 3
cura/CrashHandler.py

@@ -59,7 +59,7 @@ class CrashHandler:
         self.data = dict()
         self.data["time_stamp"] = time.time()
 
-        Logger.log("c", "An uncaught exception has occurred!")
+        Logger.log("c", "An uncaught error has occurred!")
         for line in traceback.format_exception(exception_type, value, tb):
             for part in line.rstrip("\n").split("\n"):
                 Logger.log("c", part)
@@ -90,7 +90,7 @@ class CrashHandler:
 
     def _messageWidget(self):
         label = QLabel()
-        label.setText(catalog.i18nc("@label crash message", """<p><b>A fatal exception has occurred. Please send us this Crash Report to fix the problem</p></b>
+        label.setText(catalog.i18nc("@label crash message", """<p><b>A fatal error has occurred. Please send us this Crash Report to fix the problem</p></b>
             <p>Please use the "Send report" button to post a bug report automatically to our servers</p>
         """))
 
@@ -143,7 +143,7 @@ class CrashHandler:
 
     def _exceptionInfoWidget(self):
         group = QGroupBox()
-        group.setTitle(catalog.i18nc("@title:groupbox", "Exception traceback"))
+        group.setTitle(catalog.i18nc("@title:groupbox", "Error traceback"))
         layout = QVBoxLayout()
 
         text_area = QTextEdit()

+ 17 - 74
cura/CuraApplication.py

@@ -172,13 +172,14 @@ class CuraApplication(QtApplication):
         Resources.addStorageType(self.ResourceTypes.MachineStack, "machine_instances")
         Resources.addStorageType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes")
 
-        ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer)
-        ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.VariantInstanceContainer)
-        ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.MaterialInstanceContainer)
-        ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.UserInstanceContainer)
-        ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.ExtruderStack)
-        ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.MachineStack)
-        ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.DefinitionChangesContainer)
+        ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality")
+        ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality_changes")
+        ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.VariantInstanceContainer, "variant")
+        ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.MaterialInstanceContainer, "material")
+        ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.UserInstanceContainer, "user")
+        ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.ExtruderStack, "extruder_train")
+        ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.MachineStack, "machine")
+        ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes")
 
         ##  Initialise the version upgrade manager with Cura's storage paths.
         #   Needs to be here to prevent circular dependencies.
@@ -266,17 +267,17 @@ class CuraApplication(QtApplication):
         empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
 
         empty_variant_container = copy.deepcopy(empty_container)
-        empty_variant_container._id = "empty_variant"
+        empty_variant_container.setMetaDataEntry("id", "empty_variant")
         empty_variant_container.addMetaDataEntry("type", "variant")
         ContainerRegistry.getInstance().addContainer(empty_variant_container)
 
         empty_material_container = copy.deepcopy(empty_container)
-        empty_material_container._id = "empty_material"
+        empty_material_container.setMetaDataEntry("id", "empty_material")
         empty_material_container.addMetaDataEntry("type", "material")
         ContainerRegistry.getInstance().addContainer(empty_material_container)
 
         empty_quality_container = copy.deepcopy(empty_container)
-        empty_quality_container._id = "empty_quality"
+        empty_quality_container.setMetaDataEntry("id", "empty_quality")
         empty_quality_container.setName("Not Supported")
         empty_quality_container.addMetaDataEntry("quality_type", "not_supported")
         empty_quality_container.addMetaDataEntry("type", "quality")
@@ -284,12 +285,12 @@ class CuraApplication(QtApplication):
         ContainerRegistry.getInstance().addContainer(empty_quality_container)
 
         empty_quality_changes_container = copy.deepcopy(empty_container)
-        empty_quality_changes_container._id = "empty_quality_changes"
+        empty_quality_changes_container.setMetaDataEntry("id", "empty_quality_changes")
         empty_quality_changes_container.addMetaDataEntry("type", "quality_changes")
         ContainerRegistry.getInstance().addContainer(empty_quality_changes_container)
 
         with ContainerRegistry.getInstance().lockFile():
-            ContainerRegistry.getInstance().load()
+            ContainerRegistry.getInstance().loadAllMetadata()
 
         # set the setting version for Preferences
         preferences = Preferences.getInstance()
@@ -312,6 +313,7 @@ class CuraApplication(QtApplication):
         preferences.addPreference("cura/material_settings", "{}")
 
         preferences.addPreference("view/invert_zoom", False)
+        preferences.addPreference("cura/sidebar_collapse", False)
 
         self._need_to_show_user_agreement = not Preferences.getInstance().getValue("general/accepted_user_agreement")
 
@@ -469,69 +471,10 @@ class CuraApplication(QtApplication):
         if not self._started: # Do not do saving during application start
             return
 
-        # Lock file for "more" atomically loading and saving to/from config dir.
-        with ContainerRegistry.getInstance().lockFile():
-            for instance in ContainerRegistry.getInstance().findInstanceContainers():
-                if not instance.isDirty():
-                    continue
-
-                try:
-                    data = instance.serialize()
-                except NotImplementedError:
-                    continue
-                except Exception:
-                    Logger.logException("e", "An exception occurred when serializing container %s", instance.getId())
-                    continue
-
-                mime_type = ContainerRegistry.getMimeTypeForContainer(type(instance))
-                file_name = urllib.parse.quote_plus(instance.getId()) + "." + mime_type.preferredSuffix
-                instance_type = instance.getMetaDataEntry("type")
-                path = None
-                if instance_type == "material":
-                    path = Resources.getStoragePath(self.ResourceTypes.MaterialInstanceContainer, file_name)
-                elif instance_type == "quality" or instance_type == "quality_changes":
-                    path = Resources.getStoragePath(self.ResourceTypes.QualityInstanceContainer, file_name)
-                elif instance_type == "user":
-                    path = Resources.getStoragePath(self.ResourceTypes.UserInstanceContainer, file_name)
-                elif instance_type == "variant":
-                    path = Resources.getStoragePath(self.ResourceTypes.VariantInstanceContainer, file_name)
-                elif instance_type == "definition_changes":
-                    path = Resources.getStoragePath(self.ResourceTypes.DefinitionChangesContainer, file_name)
-
-                if path:
-                    instance.setPath(path)
-                    with SaveFile(path, "wt") as f:
-                        f.write(data)
-
-            for stack in ContainerRegistry.getInstance().findContainerStacks():
-                self.saveStack(stack)
+        ContainerRegistry.getInstance().saveDirtyContainers()
 
     def saveStack(self, stack):
-        if not stack.isDirty():
-            return
-        try:
-            data = stack.serialize()
-        except NotImplementedError:
-            return
-        except Exception:
-            Logger.logException("e", "An exception occurred when serializing container %s", stack.getId())
-            return
-
-        mime_type = ContainerRegistry.getMimeTypeForContainer(type(stack))
-        file_name = urllib.parse.quote_plus(stack.getId()) + "." + mime_type.preferredSuffix
-
-        path = None
-        if isinstance(stack, GlobalStack):
-            path = Resources.getStoragePath(self.ResourceTypes.MachineStack, file_name)
-        elif isinstance(stack, ExtruderStack):
-            path = Resources.getStoragePath(self.ResourceTypes.ExtruderStack, file_name)
-        else:
-            path = Resources.getStoragePath(Resources.ContainerStacks, file_name)
-
-        stack.setPath(path)
-        with SaveFile(path, "wt") as f:
-            f.write(data)
-
+        ContainerRegistry.getInstance().saveContainer(stack)
 
     @pyqtSlot(str, result = QUrl)
     def getDefaultPath(self, key):
@@ -733,7 +676,7 @@ class CuraApplication(QtApplication):
 
             self.exec_()
 
-    def getMachineManager(self, *args):
+    def getMachineManager(self, *args) -> MachineManager:
         if self._machine_manager is None:
             self._machine_manager = MachineManager.createMachineManager()
         return self._machine_manager

+ 1 - 1
cura/PrintInformation.py

@@ -238,7 +238,7 @@ class PrintInformation(QObject):
                 pass
 
         active_material_id = Application.getInstance().getMachineManager().activeMaterialId
-        active_material_containers = ContainerRegistry.getInstance().findInstanceContainers(id=active_material_id)
+        active_material_containers = ContainerRegistry.getInstance().findInstanceContainers(id = active_material_id)
 
         if active_material_containers:
             self._active_material_container = active_material_containers[0]

+ 54 - 47
cura/QualityManager.py

@@ -1,13 +1,12 @@
-# Copyright (c) 2016 Ultimaker B.V.
+# Copyright (c) 2017 Ultimaker B.V.
 # Cura is released under the terms of the LGPLv3 or higher.
 
 # This collects a lot of quality and quality changes related code which was split between ContainerManager
 # and the MachineManager and really needs to usable from both.
-from typing import List, Optional, Dict, TYPE_CHECKING
+from typing import Any, Dict, List, Optional, TYPE_CHECKING
 
 from UM.Application import Application
 from UM.Settings.ContainerRegistry import ContainerRegistry
-from UM.Settings.DefinitionContainer import DefinitionContainer
 from UM.Settings.InstanceContainer import InstanceContainer
 from cura.Settings.ExtruderManager import ExtruderManager
 
@@ -33,16 +32,16 @@ class QualityManager:
     #   \param quality_name
     #   \param machine_definition (Optional) \type{DefinitionContainerInterface} If nothing is
     #                               specified then the currently selected machine definition is used.
-    #   \param material_containers (Optional) \type{List[InstanceContainer]} If nothing is specified then
-    #                               the current set of selected materials is used.
+    #   \param material_containers_metadata If nothing is specified then the
+    #   current set of selected materials is used.
     #   \return the matching quality container \type{InstanceContainer}
-    def findQualityByName(self, quality_name: str, machine_definition: Optional["DefinitionContainerInterface"] = None, material_containers: List[InstanceContainer] = None) -> Optional[InstanceContainer]:
+    def findQualityByName(self, quality_name: str, machine_definition: Optional["DefinitionContainerInterface"] = None, material_containers_metadata: Optional[List[Dict[str, Any]]] = None) -> Optional[InstanceContainer]:
         criteria = {"type": "quality", "name": quality_name}
-        result = self._getFilteredContainersForStack(machine_definition, material_containers, **criteria)
+        result = self._getFilteredContainersForStack(machine_definition, material_containers_metadata, **criteria)
 
         # Fall back to using generic materials and qualities if nothing could be found.
-        if not result and material_containers and len(material_containers) == 1:
-            basic_materials = self._getBasicMaterials(material_containers[0])
+        if not result and material_containers_metadata and len(material_containers_metadata) == 1:
+            basic_materials = self._getBasicMaterialMetadatas(material_containers_metadata[0])
             result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria)
 
         return result[0] if result else None
@@ -108,18 +107,18 @@ class QualityManager:
     #   \param quality_type \type{str} the name of the quality type to search for.
     #   \param machine_definition (Optional) \type{InstanceContainer} If nothing is
     #                               specified then the currently selected machine definition is used.
-    #   \param material_containers (Optional) \type{List[InstanceContainer]} If nothing is specified then
-    #                               the current set of selected materials is used.
+    #   \param material_containers_metadata If nothing is specified then the
+    #   current set of selected materials is used.
     #   \return the matching quality container \type{InstanceContainer}
-    def findQualityByQualityType(self, quality_type: str, machine_definition: Optional["DefinitionContainerInterface"] = None, material_containers: List[InstanceContainer] = None, **kwargs) -> InstanceContainer:
+    def findQualityByQualityType(self, quality_type: str, machine_definition: Optional["DefinitionContainerInterface"] = None, material_containers_metadata: Optional[List[Dict[str, Any]]] = None, **kwargs) -> InstanceContainer:
         criteria = kwargs
         criteria["type"] = "quality"
         if quality_type:
             criteria["quality_type"] = quality_type
-        result = self._getFilteredContainersForStack(machine_definition, material_containers, **criteria)
+        result = self._getFilteredContainersForStack(machine_definition, material_containers_metadata, **criteria)
         # Fall back to using generic materials and qualities if nothing could be found.
-        if not result and material_containers and len(material_containers) == 1:
-            basic_materials = self._getBasicMaterials(material_containers[0])
+        if not result and material_containers_metadata and len(material_containers_metadata) == 1:
+            basic_materials = self._getBasicMaterialMetadatas(material_containers_metadata[0])
             if basic_materials:
                 result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria)
         return result[0] if result else None
@@ -131,9 +130,9 @@ class QualityManager:
     #   \return \type{List[InstanceContainer]} the list of suitable qualities.
     def findAllQualitiesForMachineMaterial(self, machine_definition: "DefinitionContainerInterface", material_container: InstanceContainer) -> List[InstanceContainer]:
         criteria = {"type": "quality"}
-        result = self._getFilteredContainersForStack(machine_definition, [material_container], **criteria)
+        result = self._getFilteredContainersForStack(machine_definition, [material_container.getMetaData()], **criteria)
         if not result:
-            basic_materials = self._getBasicMaterials(material_container)
+            basic_materials = self._getBasicMaterialMetadatas(material_container.getMetaData())
             if basic_materials:
                 result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria)
 
@@ -202,22 +201,34 @@ class QualityManager:
     ##  Fetch more basic versions of a material.
     #
     #   This tries to find a generic or basic version of the given material.
-    #   \param material_container \type{InstanceContainer} the material
-    #   \return \type{List[InstanceContainer]} a list of the basic materials or an empty list if one could not be found.
-    def _getBasicMaterials(self, material_container: InstanceContainer):
-        base_material = material_container.getMetaDataEntry("material")
-        material_container_definition = material_container.getDefinition()
-        if material_container_definition and material_container_definition.getMetaDataEntry("has_machine_quality"):
-            definition_id = material_container.getDefinition().getMetaDataEntry("quality_definition", material_container.getDefinition().getId())
-        else:
+    #   \param material_container \type{Dict[str, Any]} The metadata of a
+    #   material to find the basic versions of.
+    #   \return \type{List[Dict[str, Any]]} A list of the metadata of basic
+    #   materials, or an empty list if none could be found.
+    def _getBasicMaterialMetadatas(self, material_container: Dict[str, Any]) -> List[Dict[str, Any]]:
+        if "definition" not in material_container:
             definition_id = "fdmprinter"
+        else:
+            material_container_definition = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = material_container["definition"])
+            if not material_container_definition:
+                definition_id = "fdmprinter"
+            else:
+                material_container_definition = material_container_definition[0]
+                if "has_machine_quality" not in material_container_definition:
+                    definition_id = "fdmprinter"
+                else:
+                    definition_id = material_container_definition.get("quality_definition", material_container_definition["id"])
+
+        base_material = material_container.get("material")
         if base_material:
             # There is a basic material specified
-            criteria = { "type": "material", "name": base_material, "definition": definition_id }
-            containers = ContainerRegistry.getInstance().findInstanceContainers(**criteria)
-            containers = [basic_material for basic_material in containers if
-                               basic_material.getMetaDataEntry("variant") == material_container.getMetaDataEntry(
-                                   "variant")]
+            criteria = {
+                "type": "material",
+                "name": base_material,
+                "definition": definition_id,
+                "variant": material_container.get("variant")
+            }
+            containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(**criteria)
             return containers
 
         return []
@@ -225,29 +236,25 @@ class QualityManager:
     def _getFilteredContainers(self, **kwargs):
         return self._getFilteredContainersForStack(None, None, **kwargs)
 
-    def _getFilteredContainersForStack(self, machine_definition: "DefinitionContainerInterface" = None, material_containers: List[InstanceContainer] = None, **kwargs):
+    def _getFilteredContainersForStack(self, machine_definition: "DefinitionContainerInterface" = None, material_metadata: Optional[List[Dict[str, Any]]] = None, **kwargs):
         # Fill in any default values.
         if machine_definition is None:
             machine_definition = Application.getInstance().getGlobalContainerStack().getBottom()
             quality_definition_id = machine_definition.getMetaDataEntry("quality_definition")
             if quality_definition_id is not None:
-                machine_definition = ContainerRegistry.getInstance().findDefinitionContainers(id=quality_definition_id)[0]
-
-        # for convenience
-        if material_containers is None:
-            material_containers = []
+                machine_definition = ContainerRegistry.getInstance().findDefinitionContainers(id = quality_definition_id)[0]
 
-        if not material_containers:
+        if not material_metadata:
             active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
             if active_stacks:
-                material_containers = [stack.material for stack in active_stacks]
+                material_metadata = [stack.material.getMetaData() for stack in active_stacks]
 
         criteria = kwargs
         filter_by_material = False
 
         machine_definition = self.getParentMachineDefinition(machine_definition)
         criteria["definition"] = machine_definition.getId()
-        found_containers_with_machine_definition = ContainerRegistry.getInstance().findInstanceContainers(**criteria)
+        found_containers_with_machine_definition = ContainerRegistry.getInstance().findInstanceContainersMetadata(**criteria)
         whole_machine_definition = self.getWholeMachineDefinition(machine_definition)
         if whole_machine_definition.getMetaDataEntry("has_machine_quality"):
             definition_id = machine_definition.getMetaDataEntry("quality_definition", whole_machine_definition.getId())
@@ -261,12 +268,12 @@ class QualityManager:
         # Stick the material IDs in a set
         material_ids = set()
 
-        for material_instance in material_containers:
+        for material_instance in material_metadata:
             if material_instance is not None:
                 # Add the parent material too.
-                for basic_material in self._getBasicMaterials(material_instance):
-                    material_ids.add(basic_material.getId())
-                material_ids.add(material_instance.getId())
+                for basic_material in self._getBasicMaterialMetadatas(material_instance):
+                    material_ids.add(basic_material["id"])
+                material_ids.add(material_instance["id"])
         containers = ContainerRegistry.getInstance().findInstanceContainers(**criteria)
 
         result = []
@@ -292,13 +299,13 @@ class QualityManager:
             # We have a normal (whole) machine defintion
             quality_definition = machine_definition.getMetaDataEntry("quality_definition")
             if quality_definition is not None:
-                parent_machine_definition = container_registry.findDefinitionContainers(id=quality_definition)[0]
+                parent_machine_definition = container_registry.findDefinitionContainers(id = quality_definition)[0]
                 return self.getParentMachineDefinition(parent_machine_definition)
             else:
                 return machine_definition
         else:
             # This looks like an extruder. Find the rest of the machine.
-            whole_machine = container_registry.findDefinitionContainers(id=machine_entry)[0]
+            whole_machine = container_registry.findDefinitionContainers(id = machine_entry)[0]
             parent_machine = self.getParentMachineDefinition(whole_machine)
             if whole_machine is parent_machine:
                 # This extruder already belongs to a 'parent' machine def.
@@ -307,7 +314,7 @@ class QualityManager:
                 # Look up the corresponding extruder definition in the parent machine definition.
                 extruder_position = machine_definition.getMetaDataEntry("position")
                 parent_extruder_id = parent_machine.getMetaDataEntry("machine_extruder_trains")[extruder_position]
-                return container_registry.findDefinitionContainers(id=parent_extruder_id)[0]
+                return container_registry.findDefinitionContainers(id = parent_extruder_id)[0]
 
     ##  Get the whole/global machine definition from an extruder definition.
     #
@@ -321,5 +328,5 @@ class QualityManager:
             return machine_definition
         else:
             container_registry = ContainerRegistry.getInstance()
-            whole_machine = container_registry.findDefinitionContainers(id=machine_entry)[0]
+            whole_machine = container_registry.findDefinitionContainers(id = machine_entry)[0]
             return whole_machine

+ 122 - 101
cura/Settings/ContainerManager.py

@@ -1,10 +1,11 @@
 # Copyright (c) 2017 Ultimaker B.V.
 # Cura is released under the terms of the LGPLv3 or higher.
 
+import copy
 import os.path
 import urllib
 import uuid
-from typing import Dict, Union
+from typing import Any, Dict, List, Union
 
 from PyQt5.QtCore import QObject, QUrl, QVariant
 from UM.FlameProfiler import pyqtSlot
@@ -55,7 +56,8 @@ class ContainerManager(QObject):
     #   \return The ID of the new container, or an empty string if duplication failed.
     @pyqtSlot(str, result = str)
     def duplicateContainer(self, container_id):
-        containers = self._container_registry.findContainers(None, id = container_id)
+        #TODO: It should be able to duplicate a container of which only the metadata is known.
+        containers = self._container_registry.findContainers(id = container_id)
         if not containers:
             Logger.log("w", "Could duplicate container %s because it was not found.", container_id)
             return ""
@@ -98,14 +100,14 @@ class ContainerManager(QObject):
     #   \return True if successful, False if not.
     @pyqtSlot(str, str, str, result = bool)
     def renameContainer(self, container_id, new_id, new_name):
-        containers = self._container_registry.findContainers(None, id = container_id)
+        containers = self._container_registry.findContainers(id = container_id)
         if not containers:
             Logger.log("w", "Could rename container %s because it was not found.", container_id)
             return False
 
         container = containers[0]
         # First, remove the container from the registry. This will clean up any files related to the container.
-        self._container_registry.removeContainer(container)
+        self._container_registry.removeContainer(container_id)
 
         # Ensure we have a unique name for the container
         new_name = self._container_registry.uniqueName(new_name)
@@ -126,9 +128,9 @@ class ContainerManager(QObject):
     #   \return True if the container was successfully removed, False if not.
     @pyqtSlot(str, result = bool)
     def removeContainer(self, container_id):
-        containers = self._container_registry.findContainers(None, id = container_id)
+        containers = self._container_registry.findContainers(id = container_id)
         if not containers:
-            Logger.log("w", "Could remove container %s because it was not found.", container_id)
+            Logger.log("w", "Could not remove container %s because it was not found.", container_id)
             return False
 
         self._container_registry.removeContainer(containers[0].getId())
@@ -146,14 +148,14 @@ class ContainerManager(QObject):
     #   \return True if successfully merged, False if not.
     @pyqtSlot(str, result = bool)
     def mergeContainers(self, merge_into_id, merge_id):
-        containers = self._container_registry.findContainers(None, id = merge_into_id)
+        containers = self._container_registry.findContainers(id = merge_into_id)
         if not containers:
             Logger.log("w", "Could merge into container %s because it was not found.", merge_into_id)
             return False
 
         merge_into = containers[0]
 
-        containers = self._container_registry.findContainers(None, id = merge_id)
+        containers = self._container_registry.findContainers(id = merge_id)
         if not containers:
             Logger.log("w", "Could not merge container %s because it was not found", merge_id)
             return False
@@ -175,13 +177,13 @@ class ContainerManager(QObject):
     #   \return True if successful, False if not.
     @pyqtSlot(str, result = bool)
     def clearContainer(self, container_id):
-        containers = self._container_registry.findContainers(None, id = container_id)
-        if not containers:
-            Logger.log("w", "Could clear container %s because it was not found.", container_id)
+        if self._container_registry.isReadOnly(container_id):
+            Logger.log("w", "Cannot clear read-only container %s", container_id)
             return False
 
-        if containers[0].isReadOnly():
-            Logger.log("w", "Cannot clear read-only container %s", container_id)
+        containers = self._container_registry.findContainers(id = container_id)
+        if not containers:
+            Logger.log("w", "Could clear container %s because it was not found.", container_id)
             return False
 
         containers[0].clear()
@@ -190,16 +192,12 @@ class ContainerManager(QObject):
 
     @pyqtSlot(str, str, result=str)
     def getContainerMetaDataEntry(self, container_id, entry_name):
-        containers = self._container_registry.findContainers(None, id=container_id)
-        if not containers:
+        metadatas = self._container_registry.findContainersMetadata(id = container_id)
+        if not metadatas:
             Logger.log("w", "Could not get metadata of container %s because it was not found.", container_id)
             return ""
 
-        result = containers[0].getMetaDataEntry(entry_name)
-        if result is not None:
-            return str(result)
-        else:
-            return ""
+        return str(metadatas[0].get(entry_name, ""))
 
     ##  Set a metadata entry of the specified container.
     #
@@ -215,17 +213,17 @@ class ContainerManager(QObject):
     #   \return True if successful, False if not.
     @pyqtSlot(str, str, str, result = bool)
     def setContainerMetaDataEntry(self, container_id, entry_name, entry_value):
-        containers = self._container_registry.findContainers(None, id = container_id)
+        if self._container_registry.isReadOnly(container_id):
+            Logger.log("w", "Cannot set metadata of read-only container %s.", container_id)
+            return False
+
+        containers = self._container_registry.findContainers(id = container_id) #We need the complete container, since we need to know whether the container is read-only or not.
         if not containers:
             Logger.log("w", "Could not set metadata of container %s because it was not found.", container_id)
             return False
 
         container = containers[0]
 
-        if container.isReadOnly():
-            Logger.log("w", "Cannot set metadata of read-only container %s.", container_id)
-            return False
-
         entries = entry_name.split("/")
         entry_name = entries.pop()
 
@@ -265,17 +263,17 @@ class ContainerManager(QObject):
     #   \return True if successful, False if not.
     @pyqtSlot(str, str, str, str, result = bool)
     def setContainerProperty(self, container_id, setting_key, property_name, property_value):
-        containers = self._container_registry.findContainers(None, id = container_id)
+        if self._container_registry.isReadOnly(container_id):
+            Logger.log("w", "Cannot set properties of read-only container %s.", container_id)
+            return False
+
+        containers = self._container_registry.findContainers(id = container_id)
         if not containers:
             Logger.log("w", "Could not set properties of container %s because it was not found.", container_id)
             return False
 
         container = containers[0]
 
-        if container.isReadOnly():
-            Logger.log("w", "Cannot set properties of read-only container %s.", container_id)
-            return False
-
         container.setProperty(setting_key, property_name, property_value)
 
         basefile = container.getMetaDataEntry("base_file", container_id)
@@ -311,35 +309,30 @@ class ContainerManager(QObject):
     ##  Set the name of the specified container.
     @pyqtSlot(str, str, result = bool)
     def setContainerName(self, container_id, new_name):
-        containers = self._container_registry.findContainers(None, id = container_id)
-        if not containers:
-            Logger.log("w", "Could not set name of container %s because it was not found.", container_id)
+        if self._container_registry.isReadOnly(container_id):
+            Logger.log("w", "Cannot set name of read-only container %s.", container_id)
             return False
 
-        container = containers[0]
-
-        if container.isReadOnly():
-            Logger.log("w", "Cannot set name of read-only container %s.", container_id)
+        containers = self._container_registry.findContainers(id = container_id) #We need to get the full container, not just metadata, since we need to know whether it's read-only.
+        if not containers:
+            Logger.log("w", "Could not set name of container %s because it was not found.", container_id)
             return False
 
-        container.setName(new_name)
+        containers[0].setName(new_name)
 
         return True
 
     ##  Find instance containers matching certain criteria.
     #
-    #   This effectively forwards to ContainerRegistry::findInstanceContainers.
+    #   This effectively forwards to
+    #   ContainerRegistry::findInstanceContainersMetadata.
     #
     #   \param criteria A dict of key - value pairs to search for.
     #
     #   \return A list of container IDs that match the given criteria.
     @pyqtSlot("QVariantMap", result = "QVariantList")
     def findInstanceContainers(self, criteria):
-        result = []
-        for entry in self._container_registry.findInstanceContainers(**criteria):
-            result.append(entry.getId())
-
-        return result
+        return [entry["id"] for entry in self._container_registry.findInstanceContainersMetadata(**criteria)]
 
     @pyqtSlot(str, result = bool)
     def isContainerUsed(self, container_id):
@@ -347,15 +340,17 @@ class ContainerManager(QObject):
         # check if this is a material container. If so, check if any material with the same base is being used by any
         # stacks.
         container_ids_to_check = [container_id]
-        container_results = self._container_registry.findInstanceContainers(id = container_id, type = "material")
+        container_results = self._container_registry.findInstanceContainersMetadata(id = container_id, type = "material")
         if container_results:
             this_container = container_results[0]
-            material_base_file = this_container.getMetaDataEntry("base_file", this_container.getId())
+            material_base_file = this_container["id"]
+            if "base_file" in this_container:
+                material_base_file = this_container["base_file"]
             # check all material container IDs with the same base
-            material_containers = self._container_registry.findInstanceContainers(base_file = material_base_file,
+            material_containers = self._container_registry.findInstanceContainersMetadata(base_file = material_base_file,
                                                                                   type = "material")
             if material_containers:
-                container_ids_to_check = [container.getId() for container in material_containers]
+                container_ids_to_check = [container["id"] for container in material_containers]
 
         all_stacks = self._container_registry.findContainerStacks()
         for stack in all_stacks:
@@ -423,7 +418,7 @@ class ContainerManager(QObject):
         else:
             mime_type = self._container_name_filters[file_type]["mime"]
 
-        containers = self._container_registry.findContainers(None, id = container_id)
+        containers = self._container_registry.findContainers(id = container_id)
         if not containers:
             return { "status": "error", "message": "Container not found"}
         container = containers[0]
@@ -627,9 +622,9 @@ class ContainerManager(QObject):
 
         elif activate_quality:
             definition_id = "fdmprinter" if not self._machine_manager.filterQualityByMachine else self._machine_manager.activeDefinitionId
-            containers = self._container_registry.findInstanceContainers(type = "quality", definition = definition_id, quality_type = activate_quality_type)
+            containers = self._container_registry.findInstanceContainersMetadata(type = "quality", definition = definition_id, quality_type = activate_quality_type)
             if containers:
-                self._machine_manager.setActiveQuality(containers[0].getId())
+                self._machine_manager.setActiveQuality(containers[0]["id"])
                 self._machine_manager.activeQualityChanged.emit()
 
         return containers_found
@@ -664,11 +659,13 @@ class ContainerManager(QObject):
 
         container_registry = self._container_registry
 
-        containers_to_rename = self._container_registry.findInstanceContainers(type = "quality_changes", name = quality_name)
+        containers_to_rename = self._container_registry.findInstanceContainersMetadata(type = "quality_changes", name = quality_name)
 
         for container in containers_to_rename:
-            stack_id = container.getMetaDataEntry("extruder", global_stack.getId())
-            container_registry.renameContainer(container.getId(), new_name, self._createUniqueId(stack_id, new_name))
+            stack_id = global_stack.getId()
+            if "extruder" in container:
+                stack_id = container["extruder"]
+            container_registry.renameContainer(container["id"], new_name, self._createUniqueId(stack_id, new_name))
 
         if not containers_to_rename:
             Logger.log("e", "Unable to rename %s, because we could not find the profile", quality_name)
@@ -693,27 +690,29 @@ class ContainerManager(QObject):
         machine_definition = global_stack.getBottom()
 
         active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
-        material_containers = [stack.material for stack in active_stacks]
+        if active_stacks is None:
+            return ""
+        material_metadatas = [stack.material.getMetaData() for stack in active_stacks]
 
         result = self._duplicateQualityOrQualityChangesForMachineType(quality_name, base_name,
                     QualityManager.getInstance().getParentMachineDefinition(machine_definition),
-                    material_containers)
+                    material_metadatas)
         return result[0].getName() if result else ""
 
     ##  Duplicate a quality or quality changes profile specific to a machine type
     #
-    #   \param quality_name \type{str} the name of the quality or quality changes container to duplicate.
-    #   \param base_name \type{str} the desired name for the new container.
-    #   \param machine_definition \type{DefinitionContainer}
-    #   \param material_instances \type{List[InstanceContainer]}
-    #   \return \type{str} the name of the newly created container.
-    def _duplicateQualityOrQualityChangesForMachineType(self, quality_name, base_name, machine_definition, material_instances):
+    #   \param quality_name The name of the quality or quality changes container to duplicate.
+    #   \param base_name The desired name for the new container.
+    #   \param machine_definition The machine with the specific machine type.
+    #   \param material_metadatas Metadata of materials
+    #   \return List of duplicated quality profiles.
+    def _duplicateQualityOrQualityChangesForMachineType(self, quality_name: str, base_name: str, machine_definition: DefinitionContainer, material_metadatas: List[Dict[str, Any]]) -> List[InstanceContainer]:
         Logger.log("d", "Attempting to duplicate the quality %s", quality_name)
 
         if base_name is None:
             base_name = quality_name
         # Try to find a Quality with the name.
-        container = QualityManager.getInstance().findQualityByName(quality_name, machine_definition, material_instances)
+        container = QualityManager.getInstance().findQualityByName(quality_name, machine_definition, material_metadatas)
         if container:
             Logger.log("d", "We found a quality to duplicate.")
             return self._duplicateQualityForMachineType(container, base_name, machine_definition)
@@ -722,7 +721,7 @@ class ContainerManager(QObject):
         return self._duplicateQualityChangesForMachineType(quality_name, base_name, machine_definition)
 
     # Duplicate a quality profile
-    def _duplicateQualityForMachineType(self, quality_container, base_name, machine_definition):
+    def _duplicateQualityForMachineType(self, quality_container, base_name, machine_definition) -> List[InstanceContainer]:
         if base_name is None:
             base_name = quality_container.getName()
         new_name = self._container_registry.uniqueName(base_name)
@@ -746,7 +745,7 @@ class ContainerManager(QObject):
         return new_change_instances
 
     #  Duplicate a quality changes container
-    def _duplicateQualityChangesForMachineType(self, quality_changes_name, base_name, machine_definition):
+    def _duplicateQualityChangesForMachineType(self, quality_changes_name, base_name, machine_definition) -> List[InstanceContainer]:
         new_change_instances = []
         for container in QualityManager.getInstance().findQualityChangesByName(quality_changes_name,
                                                               machine_definition):
@@ -765,27 +764,57 @@ class ContainerManager(QObject):
     #   \return \type{str} the id of the newly created container.
     @pyqtSlot(str, result = str)
     def duplicateMaterial(self, material_id: str) -> str:
-        containers = self._container_registry.findInstanceContainers(id=material_id)
-        if not containers:
+        original = self._container_registry.findContainersMetadata(id = material_id)
+        if not original:
             Logger.log("d", "Unable to duplicate the material with id %s, because it doesn't exist.", material_id)
             return ""
+        original = original[0]
+
+        base_container_id = original.get("base_file")
+        base_container = self._container_registry.findContainers(id = base_container_id)
+        if not base_container:
+            Logger.log("d", "Unable to duplicate the material with id {material_id}, because base_file {base_container_id} doesn't exist.".format(material_id = material_id, base_container_id = base_container_id))
+            return ""
+        base_container = base_container[0]
+
+        #We'll copy all containers with the same base.
+        #This way the correct variant and machine still gets assigned when loading the copy of the material.
+        containers_to_copy = self._container_registry.findInstanceContainers(base_file = base_container_id)
 
         # Ensure all settings are saved.
         Application.getInstance().saveSettings()
 
         # Create a new ID & container to hold the data.
-        new_id = self._container_registry.uniqueName(material_id)
-        container_type = type(containers[0])  # Could be either a XMLMaterialProfile or a InstanceContainer
-        duplicated_container = container_type(new_id)
-
-        # Instead of duplicating we load the data from the basefile again.
-        # This ensures that the inheritance goes well and all "cut up" subclasses of the xmlMaterial profile
-        # are also correctly created.
-        with open(containers[0].getPath(), encoding="utf-8") as f:
-            duplicated_container.deserialize(f.read())
-        duplicated_container.setDirty(True)
-        self._container_registry.addContainer(duplicated_container)
-        return self._getMaterialContainerIdForActiveMachine(new_id)
+        new_containers = []
+        new_base_id = self._container_registry.uniqueName(base_container.getId())
+        new_base_container = copy.deepcopy(base_container)
+        new_base_container.getMetaData()["id"] = new_base_id
+        new_base_container.getMetaData()["base_file"] = new_base_id
+        new_containers.append(new_base_container)
+
+        #Clone all of them.
+        clone_of_original = None #Keeping track of which one is the clone of the original material, since we need to return that.
+        for container_to_copy in containers_to_copy:
+            #Create unique IDs for every clone.
+            current_id = container_to_copy.getId()
+            new_id = new_base_id
+            if container_to_copy.getMetaDataEntry("definition") != "fdmprinter":
+                new_id += "_" + container_to_copy.getMetaDataEntry("definition")
+                if container_to_copy.getMetaDataEntry("variant"):
+                    variant = self._container_registry.findContainers(id = container_to_copy.getMetaDataEntry("variant"))[0]
+                    new_id += "_" + variant.getName().replace(" ", "_")
+            if current_id == material_id:
+                clone_of_original = new_id
+
+            new_container = copy.deepcopy(container_to_copy)
+            new_container.getMetaData()["id"] = new_id
+            new_container.getMetaData()["base_file"] = new_base_id
+            new_containers.append(new_container)
+
+        for container_to_add in new_containers:
+            container_to_add.setDirty(True)
+            ContainerRegistry.getInstance().addContainer(container_to_add)
+        return self._getMaterialContainerIdForActiveMachine(clone_of_original)
 
     ##  Create a new material by cloning Generic PLA for the current material diameter and setting the GUID to something unqiue
     #
@@ -800,12 +829,12 @@ class ContainerManager(QObject):
             return ""
 
         approximate_diameter = str(round(global_stack.getProperty("material_diameter", "value")))
-        containers = self._container_registry.findInstanceContainers(id = "generic_pla*", approximate_diameter = approximate_diameter)
+        containers = self._container_registry.findInstanceContainersMetadata(id = "generic_pla*", approximate_diameter = approximate_diameter)
         if not containers:
             Logger.log("d", "Unable to create a new material by cloning Generic PLA, because it cannot be found for the material diameter for this machine.")
             return ""
 
-        base_file = containers[0].getMetaDataEntry("base_file")
+        base_file = containers[0].get("base_file")
         containers = self._container_registry.findInstanceContainers(id = base_file)
         if not containers:
             Logger.log("d", "Unable to create a new material by cloning Generic PLA, because the base file for Generic PLA for this machine can not be found.")
@@ -846,14 +875,14 @@ class ContainerManager(QObject):
         has_variants = parseBool(global_stack.getMetaDataEntry("has_variants", default = False))
         if has_machine_materials or has_variant_materials:
             if has_variants:
-                materials = self._container_registry.findInstanceContainers(type = "material", base_file = base_file, definition = global_stack.getBottom().getId(), variant = self._machine_manager.activeVariantId)
+                materials = self._container_registry.findInstanceContainersMetadata(type = "material", base_file = base_file, definition = global_stack.getBottom().getId(), variant = self._machine_manager.activeVariantId)
             else:
-                materials = self._container_registry.findInstanceContainers(type = "material", base_file = base_file, definition = global_stack.getBottom().getId())
+                materials = self._container_registry.findInstanceContainersMetadata(type = "material", base_file = base_file, definition = global_stack.getBottom().getId())
 
             if materials:
-                return materials[0].getId()
+                return materials[0]["id"]
 
-            Logger.log("w", "Unable to find a suitable container based on %s for the current machine .", base_file)
+            Logger.log("w", "Unable to find a suitable container based on %s for the current machine.", base_file)
             return "" # do not activate a new material if a container can not be found
 
         return base_file
@@ -864,25 +893,25 @@ class ContainerManager(QObject):
     #   \return \type{list} a list of names of materials with the same GUID
     @pyqtSlot(str, result = "QStringList")
     def getLinkedMaterials(self, material_id: str):
-        containers = self._container_registry.findInstanceContainers(id=material_id)
+        containers = self._container_registry.findInstanceContainersMetadata(id = material_id)
         if not containers:
             Logger.log("d", "Unable to find materials linked to material with id %s, because it doesn't exist.", material_id)
             return []
 
         material_container = containers[0]
-        material_base_file = material_container.getMetaDataEntry("base_file", "")
-        material_guid = material_container.getMetaDataEntry("GUID", "")
+        material_base_file = material_container.get("base_file", "")
+        material_guid = material_container.get("GUID", "")
         if not material_guid:
             Logger.log("d", "Unable to find materials linked to material with id %s, because it doesn't have a GUID.", material_id)
             return []
 
-        containers = self._container_registry.findInstanceContainers(type = "material", GUID = material_guid)
+        containers = self._container_registry.findInstanceContainersMetadata(type = "material", GUID = material_guid)
         linked_material_names = []
         for container in containers:
-            if container.getId() in [material_id, material_base_file] or container.getMetaDataEntry("base_file") != container.getId():
+            if container["id"] in [material_id, material_base_file] or container.get("base_file") != container["id"]:
                 continue
 
-            linked_material_names.append(container.getName())
+            linked_material_names.append(container["name"])
         return linked_material_names
 
     ##  Unlink a material from all other materials by creating a new GUID
@@ -968,14 +997,6 @@ class ContainerManager(QObject):
             name_filter = "{0} ({1})".format(mime_type.comment, suffix_list)
             self._container_name_filters[name_filter] = entry
 
-    ##  Get containers filtered by machine type and material if required.
-    #
-    #   \param kwargs Initial search criteria that the containers need to match.
-    #
-    #   \return A list of containers matching the search criteria.
-    def _getFilteredContainers(self, **kwargs):
-        return QualityManager.getInstance()._getFilteredContainers(**kwargs)
-
     ##  Creates a unique ID for a container by prefixing the name with the stack ID.
     #
     #   This method creates a unique ID for a container by prefixing it with a specified stack ID.
@@ -1015,9 +1036,9 @@ class ContainerManager(QObject):
 
         # If the machine specifies qualities should be filtered, ensure we match the current criteria.
         if not machine_definition.getMetaDataEntry("has_machine_quality"):
-            quality_changes.setDefinition(self._container_registry.findContainers(id = "fdmprinter")[0])
+            quality_changes.setDefinition("fdmprinter")
         else:
-            quality_changes.setDefinition(QualityManager.getInstance().getParentMachineDefinition(machine_definition))
+            quality_changes.setDefinition(QualityManager.getInstance().getParentMachineDefinition(machine_definition).getId())
 
         from cura.CuraApplication import CuraApplication
         quality_changes.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)

+ 10 - 12
cura/Settings/CuraContainerRegistry.py

@@ -44,7 +44,6 @@ class CuraContainerRegistry(ContainerRegistry):
     #   Global stack based on metadata information.
     @override(ContainerRegistry)
     def addContainer(self, container):
-
         # Note: Intentional check with type() because we want to ignore subclasses
         if type(container) == ContainerStack:
             container = self._convertContainerStack(container)
@@ -89,8 +88,8 @@ class CuraContainerRegistry(ContainerRegistry):
     def _containerExists(self, container_type, container_name):
         container_class = ContainerStack if container_type == "machine" else InstanceContainer
 
-        return self.findContainers(container_class, id = container_name, type = container_type, ignore_case = True) or \
-                self.findContainers(container_class, name = container_name, type = container_type)
+        return self.findContainersMetadata(id = container_name, type = container_type, ignore_case = True) or \
+                self.findContainersMetadata(container_type = container_class, name = container_name, type = container_type)
 
     ##  Exports an profile to a file
     #
@@ -119,7 +118,7 @@ class CuraContainerRegistry(ContainerRegistry):
         found_containers = []
         extruder_positions = []
         for instance_id in instance_ids:
-            containers = ContainerRegistry.getInstance().findInstanceContainers(id=instance_id)
+            containers = ContainerRegistry.getInstance().findInstanceContainers(id = instance_id)
             if containers:
                 found_containers.append(containers[0])
 
@@ -129,9 +128,9 @@ class CuraContainerRegistry(ContainerRegistry):
                     # Global stack
                     extruder_positions.append(-1)
                 else:
-                    extruder_containers = ContainerRegistry.getInstance().findDefinitionContainers(id=extruder_id)
+                    extruder_containers = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = extruder_id)
                     if extruder_containers:
-                        extruder_positions.append(int(extruder_containers[0].getMetaDataEntry("position", 0)))
+                        extruder_positions.append(int(extruder_containers[0].get("position", 0)))
                     else:
                         extruder_positions.append(0)
         # Ensure the profiles are always exported in order (global, extruder 0, extruder 1, ...)
@@ -274,7 +273,6 @@ class CuraContainerRegistry(ContainerRegistry):
     #
     #   \return None if configuring was successful or an error message if an error occurred.
     def _configureProfile(self, profile: InstanceContainer, id_seed: str, new_name: str) -> Optional[str]:
-        profile.setReadOnly(False)
         profile.setDirty(True)  # Ensure the profiles are correctly saved
 
         new_id = self.createUniqueName("quality_changes", "", id_seed, catalog.i18nc("@label", "Custom profile"))
@@ -292,7 +290,7 @@ class CuraContainerRegistry(ContainerRegistry):
 
         quality_type_criteria = {"quality_type": quality_type}
         if self._machineHasOwnQualities():
-            profile.setDefinition(self._activeQualityDefinition())
+            profile.setDefinition(self._activeQualityDefinition().getId())
             if self._machineHasOwnMaterials():
                 active_material_id = self._activeMaterialId()
                 if active_material_id and active_material_id != "empty":  # only update if there is an active material
@@ -302,7 +300,7 @@ class CuraContainerRegistry(ContainerRegistry):
             quality_type_criteria["definition"] = profile.getDefinition().getId()
 
         else:
-            profile.setDefinition(ContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")[0])
+            profile.setDefinition(fdmprinter)
             quality_type_criteria["definition"] = "fdmprinter"
 
         machine_definition = Application.getInstance().getGlobalContainerStack().getBottom()
@@ -349,7 +347,7 @@ class CuraContainerRegistry(ContainerRegistry):
         global_container_stack = Application.getInstance().getGlobalContainerStack()
         if global_container_stack:
             definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(global_container_stack.getBottom())
-            definition = self.findDefinitionContainers(id=definition_id)[0]
+            definition = self.findDefinitionContainers(id = definition_id)[0]
 
             if definition:
                 return definition
@@ -534,13 +532,13 @@ class CuraContainerRegistry(ContainerRegistry):
     # set after upgrading, because the proper global stack was not yet loaded. This method
     # makes sure those extruders also get the right stack set.
     def _connectUpgradedExtruderStacksToMachines(self):
-        extruder_stacks = self.findContainers(ExtruderStack.ExtruderStack)
+        extruder_stacks = self.findContainers(container_type = ExtruderStack.ExtruderStack)
         for extruder_stack in extruder_stacks:
             if extruder_stack.getNextStack():
                 # Has the right next stack, so ignore it.
                 continue
 
-            machines = ContainerRegistry.getInstance().findContainerStacks(id=extruder_stack.getMetaDataEntry("machine", ""))
+            machines = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack.getMetaDataEntry("machine", ""))
             if machines:
                 extruder_stack.setNextStack(machines[0])
             else:

+ 16 - 10
cura/Settings/CuraContainerStack.py

@@ -14,7 +14,7 @@ from UM.Settings.ContainerStack import ContainerStack, InvalidContainerStackErro
 from UM.Settings.InstanceContainer import InstanceContainer
 from UM.Settings.DefinitionContainer import DefinitionContainer
 from UM.Settings.ContainerRegistry import ContainerRegistry
-from UM.Settings.Interfaces import ContainerInterface
+from UM.Settings.Interfaces import ContainerInterface, DefinitionContainerInterface
 
 from . import Exceptions
 
@@ -246,7 +246,7 @@ class CuraContainerStack(ContainerStack):
     ##  Set the definition container.
     #
     #   \param new_quality_changes The new definition container. It is expected to have a "type" metadata entry with the value "quality_changes".
-    def setDefinition(self, new_definition: DefinitionContainer) -> None:
+    def setDefinition(self, new_definition: DefinitionContainerInterface) -> None:
         self.replaceContainer(_ContainerIndexes.Definition, new_definition)
 
     ##  Set the definition container by an ID.
@@ -487,11 +487,17 @@ class CuraContainerStack(ContainerStack):
             search_criteria.pop("name", None)
             materials = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
 
-        if materials:
-            return materials[0]
+        if not materials:
+            Logger.log("w", "Could not find a valid material for stack {stack}", stack = self.id)
+            return None
+
+        for material in materials:
+            # Prefer a read-only material
+            if ContainerRegistry.getInstance().isReadOnly(material.getId()):
+                return material
+
+        return materials[0]
 
-        Logger.log("w", "Could not find a valid material for stack {stack}", stack = self.id)
-        return None
 
     ##  Find the quality that should be used as "default" quality.
     #
@@ -502,7 +508,7 @@ class CuraContainerStack(ContainerStack):
     def findDefaultQuality(self) -> Optional[ContainerInterface]:
         definition = self._getMachineDefinition()
         registry = ContainerRegistry.getInstance()
-        material_container = self.material if self.material != self._empty_instance_container else None
+        material_container = self.material if self.material.getId() not in (self._empty_material.getId(), self._empty_instance_container.getId()) else None
 
         search_criteria = {"type": "quality"}
 
@@ -546,7 +552,7 @@ class CuraContainerStack(ContainerStack):
             material_search_criteria = {"type": "material", "material": material_container.getMetaDataEntry("material"), "color_name": "Generic"}
             if definition.getMetaDataEntry("has_machine_quality"):
                 if self.material != self._empty_instance_container:
-                    material_search_criteria["definition"] = material_container.getDefinition().id
+                    material_search_criteria["definition"] = material_container.getMetaDataEntry("definition")
 
                     if definition.getMetaDataEntry("has_variants"):
                         material_search_criteria["variant"] = material_container.getMetaDataEntry("variant")
@@ -557,10 +563,10 @@ class CuraContainerStack(ContainerStack):
                         material_search_criteria["variant"] = self.variant.id
             else:
                 material_search_criteria["definition"] = "fdmprinter"
-            material_containers = registry.findInstanceContainers(**material_search_criteria)
+            material_containers = registry.findInstanceContainersMetadata(**material_search_criteria)
             # Try all materials to see if there is a quality profile available.
             for material_container in material_containers:
-                search_criteria["material"] = material_container.getId()
+                search_criteria["material"] = material_container["id"]
 
                 containers = registry.findInstanceContainers(**search_criteria)
                 if containers:

Some files were not shown because too many files changed in this diff