Browse Source

Merge pull request #4184 from Ultimaker/CURA-5570_beyond_comprehension

CURA-5570 More decision points in material and quality lookup!
Diego Prado Gesto 6 years ago
parent
commit
152005ab14

+ 3 - 1
cura/CuraApplication.py

@@ -118,11 +118,12 @@ if TYPE_CHECKING:
 numpy.seterr(all = "ignore")
 
 try:
-    from cura.CuraVersion import CuraVersion, CuraBuildType, CuraDebugMode
+    from cura.CuraVersion import CuraVersion, CuraBuildType, CuraDebugMode, CuraSDKVersion
 except ImportError:
     CuraVersion = "master"  # [CodeStyle: Reflecting imported value]
     CuraBuildType = ""
     CuraDebugMode = False
+    CuraSDKVerion = ""
 
 
 class CuraApplication(QtApplication):
@@ -913,6 +914,7 @@ class CuraApplication(QtApplication):
         engine.rootContext().setContextProperty("CuraApplication", self)
         engine.rootContext().setContextProperty("PrintInformation", self._print_information)
         engine.rootContext().setContextProperty("CuraActions", self._cura_actions)
+        engine.rootContext().setContextProperty("CuraSDKVersion", CuraSDKVersion)
 
         qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type")
 

+ 136 - 93
cura/Machines/MaterialManager.py

@@ -4,8 +4,7 @@
 from collections import defaultdict, OrderedDict
 import copy
 import uuid
-from typing import Dict, cast
-from typing import Optional, TYPE_CHECKING
+from typing import Dict, Optional, TYPE_CHECKING
 
 from PyQt5.Qt import QTimer, QObject, pyqtSignal, pyqtSlot
 
@@ -18,6 +17,7 @@ from UM.Util import parseBool
 
 from .MaterialNode import MaterialNode
 from .MaterialGroup import MaterialGroup
+from .VariantType import VariantType
 
 if TYPE_CHECKING:
     from UM.Settings.DefinitionContainer import DefinitionContainer
@@ -47,7 +47,7 @@ class MaterialManager(QObject):
 
         self._fallback_materials_map = dict()  # material_type -> generic material metadata
         self._material_group_map = dict()  # root_material_id -> MaterialGroup
-        self._diameter_machine_variant_material_map = dict()  # approximate diameter str -> dict(machine_definition_id -> MaterialNode)
+        self._diameter_machine_nozzle_buildplate_material_map = dict()  # approximate diameter str -> dict(machine_definition_id -> MaterialNode)
 
         # We're using these two maps to convert between the specific diameter material id and the generic material id
         # because the generic material ids are used in qualities and definitions, while the specific diameter material is meant
@@ -187,51 +187,77 @@ class MaterialManager(QObject):
                 self._diameter_material_map[root_material_id] = default_root_material_id
 
         # Map #4
-        #    "machine" -> "variant_name" -> "root material ID" -> specific material InstanceContainer
-        # Construct the "machine" -> "variant" -> "root material ID" -> specific material InstanceContainer
-        self._diameter_machine_variant_material_map = dict()
+        # "machine" -> "nozzle name" -> "buildplate name" -> "root material ID" -> specific material InstanceContainer
+        self._diameter_machine_nozzle_buildplate_material_map = dict()
         for material_metadata in material_metadatas.values():
-            # We don't store empty material in the lookup tables
-            if material_metadata["id"] == "empty_material":
-                continue
+            self.__addMaterialMetadataIntoLookupTree(material_metadata)
 
-            root_material_id = material_metadata["base_file"]
-            definition = material_metadata["definition"]
-            approximate_diameter = material_metadata["approximate_diameter"]
+        self.materialsUpdated.emit()
 
-            if approximate_diameter not in self._diameter_machine_variant_material_map:
-                self._diameter_machine_variant_material_map[approximate_diameter] = {}
+    def __addMaterialMetadataIntoLookupTree(self, material_metadata: dict) -> None:
+        material_id = material_metadata["id"]
 
-            machine_variant_material_map = self._diameter_machine_variant_material_map[approximate_diameter]
-            if definition not in machine_variant_material_map:
-                machine_variant_material_map[definition] = MaterialNode()
+        # We don't store empty material in the lookup tables
+        if material_id == "empty_material":
+            return
 
-            machine_node = machine_variant_material_map[definition]
-            variant_name = material_metadata.get("variant_name")
-            if not variant_name:
-                # if there is no variant, this material is for the machine, so put its metadata in the machine node.
-                machine_node.material_map[root_material_id] = MaterialNode(material_metadata)
-            else:
-                # this material is variant-specific, so we save it in a variant-specific node under the
-                # machine-specific node
-
-                # Check first if the variant exist in the manager
-                existing_variant = self._application.getVariantManager().getVariantNode(definition, variant_name)
-                if existing_variant is not None:
-                    if variant_name not in machine_node.children_map:
-                        machine_node.children_map[variant_name] = MaterialNode()
-
-                    variant_node = machine_node.children_map[variant_name]
-                    if root_material_id in variant_node.material_map:  # We shouldn't have duplicated variant-specific materials for the same machine.
-                        ConfigurationErrorMessage.getInstance().addFaultyContainers(root_material_id)
-                        continue
-                    variant_node.material_map[root_material_id] = MaterialNode(material_metadata)
-                else:
-                    # Add this container id to the wrong containers list in the registry
-                    Logger.log("w", "Not adding {id} to the material manager because the variant does not exist.".format(id = material_metadata["id"]))
-                    self._container_registry.addWrongContainerId(material_metadata["id"])
+        root_material_id = material_metadata["base_file"]
+        definition = material_metadata["definition"]
+        approximate_diameter = material_metadata["approximate_diameter"]
+
+        if approximate_diameter not in self._diameter_machine_nozzle_buildplate_material_map:
+            self._diameter_machine_nozzle_buildplate_material_map[approximate_diameter] = {}
+
+        machine_nozzle_buildplate_material_map = self._diameter_machine_nozzle_buildplate_material_map[
+            approximate_diameter]
+        if definition not in machine_nozzle_buildplate_material_map:
+            machine_nozzle_buildplate_material_map[definition] = MaterialNode()
+
+        # This is a list of information regarding the intermediate nodes:
+        #    nozzle -> buildplate
+        nozzle_name = material_metadata.get("variant_name")
+        buildplate_name = material_metadata.get("buildplate_name")
+        intermediate_node_info_list = [(nozzle_name, VariantType.NOZZLE),
+                                       (buildplate_name, VariantType.BUILD_PLATE),
+                                       ]
+
+        variant_manager = self._application.getVariantManager()
+
+        machine_node = machine_nozzle_buildplate_material_map[definition]
+        current_node = machine_node
+        current_intermediate_node_info_idx = 0
+        error_message = None  # type: Optional[str]
+        while current_intermediate_node_info_idx < len(intermediate_node_info_list):
+            variant_name, variant_type = intermediate_node_info_list[current_intermediate_node_info_idx]
+            if variant_name is not None:
+                # The new material has a specific variant, so it needs to be added to that specific branch in the tree.
+                variant = variant_manager.getVariantNode(definition, variant_name, variant_type)
+                if variant is None:
+                    error_message = "Material {id} contains a variant {name} that does not exist.".format(
+                        id = material_metadata["id"], name = variant_name)
+                    break
 
-        self.materialsUpdated.emit()
+                # Update the current node to advance to a more specific branch
+                if variant_name not in current_node.children_map:
+                    current_node.children_map[variant_name] = MaterialNode()
+                current_node = current_node.children_map[variant_name]
+
+            current_intermediate_node_info_idx += 1
+
+        if error_message is not None:
+            Logger.log("e", "%s It will not be added into the material lookup tree.", error_message)
+            self._container_registry.addWrongContainerId(material_metadata["id"])
+            return
+
+        # Add the material to the current tree node, which is the deepest (the most specific) branch we can find.
+        # Sanity check: Make sure that there is no duplicated materials.
+        if root_material_id in current_node.material_map:
+            Logger.log("e", "Duplicated material [%s] with root ID [%s]. It has already been added.",
+                       material_id, root_material_id)
+            ConfigurationErrorMessage.getInstance().addFaultyContainers(root_material_id)
+            return
+
+        current_node.material_map[root_material_id] = MaterialNode(material_metadata)
 
     def _updateMaps(self):
         Logger.log("i", "Updating material lookup data ...")
@@ -263,45 +289,52 @@ class MaterialManager(QObject):
     #
     # Return a dict with all root material IDs (k) and ContainerNodes (v) that's suitable for the given setup.
     #
-    def getAvailableMaterials(self, machine_definition: "DefinitionContainer", extruder_variant_name: Optional[str],
-                              diameter: float) -> Dict[str, MaterialNode]:
+    def getAvailableMaterials(self, machine_definition: "DefinitionContainer", nozzle_name: Optional[str],
+                              buildplate_name: Optional[str], diameter: float) -> Dict[str, MaterialNode]:
         # round the diameter to get the approximate diameter
         rounded_diameter = str(round(diameter))
-        if rounded_diameter not in self._diameter_machine_variant_material_map:
+        if rounded_diameter not in self._diameter_machine_nozzle_buildplate_material_map:
             Logger.log("i", "Cannot find materials with diameter [%s] (rounded to [%s])", diameter, rounded_diameter)
             return dict()
 
         machine_definition_id = machine_definition.getId()
 
-        # If there are variant materials, get the variant material
-        machine_variant_material_map = self._diameter_machine_variant_material_map[rounded_diameter]
-        machine_node = machine_variant_material_map.get(machine_definition_id)
-        default_machine_node = machine_variant_material_map.get(self._default_machine_definition_id)
-        variant_node = None
-        if extruder_variant_name is not None and machine_node is not None:
-            variant_node = machine_node.getChildNode(extruder_variant_name)
+        # If there are nozzle-and-or-buildplate materials, get the nozzle-and-or-buildplate material
+        machine_nozzle_buildplate_material_map = self._diameter_machine_nozzle_buildplate_material_map[rounded_diameter]
+        machine_node = machine_nozzle_buildplate_material_map.get(machine_definition_id)
+        default_machine_node = machine_nozzle_buildplate_material_map.get(self._default_machine_definition_id)
+        nozzle_node = None
+        buildplate_node = None
+        if nozzle_name is not None and machine_node is not None:
+            nozzle_node = machine_node.getChildNode(nozzle_name)
+            # Get buildplate node if possible
+            if nozzle_node is not None and buildplate_name is not None:
+                buildplate_node = nozzle_node.getChildNode(buildplate_name)
 
-        nodes_to_check = [variant_node, machine_node, default_machine_node]
+        nodes_to_check = [buildplate_node, nozzle_node, machine_node, default_machine_node]
 
         # Fallback mechanism of finding materials:
-        #  1. variant-specific material
-        #  2. machine-specific material
-        #  3. generic material (for fdmprinter)
+        #  1. buildplate-specific material
+        #  2. nozzle-specific material
+        #  3. machine-specific material
+        #  4. generic material (for fdmprinter)
         machine_exclude_materials = machine_definition.getMetaDataEntry("exclude_materials", [])
 
-        material_id_metadata_dict = dict() # type: Dict[str, MaterialNode]
-        for node in nodes_to_check:
-            if node is not None:
-                # Only exclude the materials that are explicitly specified in the "exclude_materials" field.
-                # Do not exclude other materials that are of the same type.
-                for material_id, node in node.material_map.items():
-                    if material_id in machine_exclude_materials:
-                        Logger.log("d", "Exclude material [%s] for machine [%s]",
-                                   material_id, machine_definition.getId())
-                        continue
+        material_id_metadata_dict = dict()  # type: Dict[str, MaterialNode]
+        for current_node in nodes_to_check:
+            if current_node is None:
+                continue
+
+            # Only exclude the materials that are explicitly specified in the "exclude_materials" field.
+            # Do not exclude other materials that are of the same type.
+            for material_id, node in current_node.material_map.items():
+                if material_id in machine_exclude_materials:
+                    Logger.log("d", "Exclude material [%s] for machine [%s]",
+                               material_id, machine_definition.getId())
+                    continue
 
-                    if material_id not in material_id_metadata_dict:
-                        material_id_metadata_dict[material_id] = node
+                if material_id not in material_id_metadata_dict:
+                    material_id_metadata_dict[material_id] = node
 
         return material_id_metadata_dict
 
@@ -310,13 +343,14 @@ class MaterialManager(QObject):
     #
     def getAvailableMaterialsForMachineExtruder(self, machine: "GlobalStack",
                                                 extruder_stack: "ExtruderStack") -> Optional[dict]:
-        variant_name = None
+        buildplate_name = machine.getBuildplateName()
+        nozzle_name = None
         if extruder_stack.variant.getId() != "empty_variant":
-            variant_name = extruder_stack.variant.getName()
+            nozzle_name = extruder_stack.variant.getName()
         diameter = extruder_stack.approximateMaterialDiameter
 
         # Fetch the available materials (ContainerNode) for the current active machine and extruder setup.
-        return self.getAvailableMaterials(machine.definition, variant_name, diameter)
+        return self.getAvailableMaterials(machine.definition, nozzle_name, buildplate_name, diameter)
 
     #
     # Gets MaterialNode for the given extruder and machine with the given material name.
@@ -324,32 +358,36 @@ class MaterialManager(QObject):
     #  1. the given machine doesn't have materials;
     #  2. cannot find any material InstanceContainers with the given settings.
     #
-    def getMaterialNode(self, machine_definition_id: str, extruder_variant_name: Optional[str],
-                        diameter: float, root_material_id: str) -> Optional["InstanceContainer"]:
+    def getMaterialNode(self, machine_definition_id: str, nozzle_name: Optional[str],
+                        buildplate_name: Optional[str], diameter: float, root_material_id: str) -> Optional["InstanceContainer"]:
         # round the diameter to get the approximate diameter
         rounded_diameter = str(round(diameter))
-        if rounded_diameter not in self._diameter_machine_variant_material_map:
+        if rounded_diameter not in self._diameter_machine_nozzle_buildplate_material_map:
             Logger.log("i", "Cannot find materials with diameter [%s] (rounded to [%s]) for root material id [%s]",
                        diameter, rounded_diameter, root_material_id)
             return None
 
-        # If there are variant materials, get the variant material
-        machine_variant_material_map = self._diameter_machine_variant_material_map[rounded_diameter]
-        machine_node = machine_variant_material_map.get(machine_definition_id)
-        variant_node = None
+        # If there are nozzle materials, get the nozzle-specific material
+        machine_nozzle_buildplate_material_map = self._diameter_machine_nozzle_buildplate_material_map[rounded_diameter]
+        machine_node = machine_nozzle_buildplate_material_map.get(machine_definition_id)
+        nozzle_node = None
+        buildplate_node = None
 
         # Fallback for "fdmprinter" if the machine-specific materials cannot be found
         if machine_node is None:
-            machine_node = machine_variant_material_map.get(self._default_machine_definition_id)
-        if machine_node is not None and extruder_variant_name is not None:
-            variant_node = machine_node.getChildNode(extruder_variant_name)
+            machine_node = machine_nozzle_buildplate_material_map.get(self._default_machine_definition_id)
+        if machine_node is not None and nozzle_name is not None:
+            nozzle_node = machine_node.getChildNode(nozzle_name)
+        if nozzle_node is not None and buildplate_name is not None:
+            buildplate_node = nozzle_node.getChildNode(buildplate_name)
 
         # Fallback mechanism of finding materials:
-        #  1. variant-specific material
-        #  2. machine-specific material
-        #  3. generic material (for fdmprinter)
-        nodes_to_check = [variant_node, machine_node,
-                          machine_variant_material_map.get(self._default_machine_definition_id)]
+        #  1. buildplate-specific material
+        #  2. nozzle-specific material
+        #  3. machine-specific material
+        #  4. generic material (for fdmprinter)
+        nodes_to_check = [buildplate_node, nozzle_node, machine_node,
+                          machine_nozzle_buildplate_material_map.get(self._default_machine_definition_id)]
 
         material_node = None
         for node in nodes_to_check:
@@ -366,7 +404,8 @@ class MaterialManager(QObject):
     #  1. the given machine doesn't have materials;
     #  2. cannot find any material InstanceContainers with the given settings.
     #
-    def getMaterialNodeByType(self, global_stack: "GlobalStack", position: str, extruder_variant_name: str, material_guid: str) -> Optional["MaterialNode"]:
+    def getMaterialNodeByType(self, global_stack: "GlobalStack", position: str, nozzle_name: str,
+                              buildplate_name: Optional[str], material_guid: str) -> Optional["MaterialNode"]:
         node = None
         machine_definition = global_stack.definition
         extruder_definition = global_stack.extruders[position].definition
@@ -385,7 +424,7 @@ class MaterialManager(QObject):
                 Logger.log("i", "Cannot find materials with guid [%s] ", material_guid)
                 return None
 
-            node = self.getMaterialNode(machine_definition.getId(), extruder_variant_name,
+            node = self.getMaterialNode(machine_definition.getId(), nozzle_name, buildplate_name,
                                         material_diameter, root_material_id)
         return node
 
@@ -413,13 +452,17 @@ class MaterialManager(QObject):
         else:
             return None
 
-    ##  Get default material for given global stack, extruder position and extruder variant name
+    ##  Get default material for given global stack, extruder position and extruder nozzle name
     #   you can provide the extruder_definition and then the position is ignored (useful when building up global stack in CuraStackBuilder)
-    def getDefaultMaterial(self, global_stack: "GlobalStack", position: str, extruder_variant_name: Optional[str], extruder_definition: Optional["DefinitionContainer"] = None) -> Optional["MaterialNode"]:
+    def getDefaultMaterial(self, global_stack: "GlobalStack", position: str, nozzle_name: Optional[str],
+                           extruder_definition: Optional["DefinitionContainer"] = None) -> Optional["MaterialNode"]:
         node = None
+
+        buildplate_name = global_stack.getBuildplateName()
         machine_definition = global_stack.definition
         if extruder_definition is None:
             extruder_definition = global_stack.extruders[position].definition
+
         if extruder_definition and parseBool(global_stack.getMetaDataEntry("has_materials", False)):
             # At this point the extruder_definition is not None
             material_diameter = extruder_definition.getProperty("material_diameter", "value")
@@ -428,7 +471,7 @@ class MaterialManager(QObject):
             approximate_material_diameter = str(round(material_diameter))
             root_material_id = machine_definition.getMetaDataEntry("preferred_material")
             root_material_id = self.getRootMaterialIDForDiameter(root_material_id, approximate_material_diameter)
-            node = self.getMaterialNode(machine_definition.getId(), extruder_variant_name,
+            node = self.getMaterialNode(machine_definition.getId(), nozzle_name, buildplate_name,
                                         material_diameter, root_material_id)
         return node
 
@@ -515,8 +558,8 @@ class MaterialManager(QObject):
             if container_to_copy.getMetaDataEntry("definition") != "fdmprinter":
                 new_id += "_" + container_to_copy.getMetaDataEntry("definition")
                 if container_to_copy.getMetaDataEntry("variant_name"):
-                    variant_name = container_to_copy.getMetaDataEntry("variant_name")
-                    new_id += "_" + variant_name.replace(" ", "_")
+                    nozzle_name = container_to_copy.getMetaDataEntry("variant_name")
+                    new_id += "_" + nozzle_name.replace(" ", "_")
 
             new_container = copy.deepcopy(container_to_copy)
             new_container.getMetaData()["id"] = new_id

+ 1 - 1
cura/Machines/Models/BuildPlateModel.py

@@ -8,7 +8,7 @@ from UM.Logger import Logger
 from UM.Qt.ListModel import ListModel
 from UM.Util import parseBool
 
-from cura.Machines.VariantManager import VariantType
+from cura.Machines.VariantType import VariantType
 
 
 class BuildPlateModel(ListModel):

+ 2 - 1
cura/Machines/Models/NozzleModel.py

@@ -8,6 +8,8 @@ from UM.Logger import Logger
 from UM.Qt.ListModel import ListModel
 from UM.Util import parseBool
 
+from cura.Machines.VariantType import VariantType
+
 
 class NozzleModel(ListModel):
     IdRole = Qt.UserRole + 1
@@ -43,7 +45,6 @@ class NozzleModel(ListModel):
             self.setItems([])
             return
 
-        from cura.Machines.VariantManager import VariantType
         variant_node_dict = self._variant_manager.getVariantNodes(global_stack, VariantType.NOZZLE)
         if not variant_node_dict:
             self.setItems([])

+ 64 - 68
cura/Machines/QualityManager.py

@@ -45,7 +45,7 @@ class QualityManager(QObject):
         self._empty_quality_container = self._application.empty_quality_container
         self._empty_quality_changes_container = self._application.empty_quality_changes_container
 
-        self._machine_variant_material_quality_type_to_quality_dict = {}  # for quality lookup
+        self._machine_nozzle_buildplate_material_quality_type_to_quality_dict = {}  # for quality lookup
         self._machine_quality_type_to_quality_changes_dict = {}  # for quality_changes lookup
 
         self._default_machine_definition_id = "fdmprinter"
@@ -64,10 +64,10 @@ class QualityManager(QObject):
 
     def initialize(self):
         # Initialize the lookup tree for quality profiles with following structure:
-        # <machine> -> <variant> -> <material>
-        #           -> <material>
+        # <machine> -> <nozzle> -> <buildplate> -> <material>
+        # <machine> -> <material>
 
-        self._machine_variant_material_quality_type_to_quality_dict = {}  # for quality lookup
+        self._machine_nozzle_buildplate_material_quality_type_to_quality_dict = {}  # for quality lookup
         self._machine_quality_type_to_quality_changes_dict = {}  # for quality_changes lookup
 
         quality_metadata_list = self._container_registry.findContainersMetadata(type = "quality")
@@ -79,53 +79,41 @@ class QualityManager(QObject):
             quality_type = metadata["quality_type"]
 
             root_material_id = metadata.get("material")
-            variant_name = metadata.get("variant")
+            nozzle_name = metadata.get("variant")
+            buildplate_name = metadata.get("buildplate")
             is_global_quality = metadata.get("global_quality", False)
-            is_global_quality = is_global_quality or (root_material_id is None and variant_name is None)
+            is_global_quality = is_global_quality or (root_material_id is None and nozzle_name is None and buildplate_name is None)
 
             # Sanity check: material+variant and is_global_quality cannot be present at the same time
-            if is_global_quality and (root_material_id or variant_name):
+            if is_global_quality and (root_material_id or nozzle_name):
                 ConfigurationErrorMessage.getInstance().addFaultyContainers(metadata["id"])
                 continue
 
-            if definition_id not in self._machine_variant_material_quality_type_to_quality_dict:
-                self._machine_variant_material_quality_type_to_quality_dict[definition_id] = QualityNode()
-            machine_node = cast(QualityNode, self._machine_variant_material_quality_type_to_quality_dict[definition_id])
+            if definition_id not in self._machine_nozzle_buildplate_material_quality_type_to_quality_dict:
+                self._machine_nozzle_buildplate_material_quality_type_to_quality_dict[definition_id] = QualityNode()
+            machine_node = cast(QualityNode, self._machine_nozzle_buildplate_material_quality_type_to_quality_dict[definition_id])
 
             if is_global_quality:
                 # For global qualities, save data in the machine node
                 machine_node.addQualityMetadata(quality_type, metadata)
                 continue
 
-            if variant_name is not None:
-                # If variant_name is specified in the quality/quality_changes profile, check if material is specified,
-                # too.
-                if variant_name not in machine_node.children_map:
-                    machine_node.children_map[variant_name] = QualityNode()
-                variant_node = cast(QualityNode, machine_node.children_map[variant_name])
-
-                if root_material_id is None:
-                    # If only variant_name is specified but material is not, add the quality/quality_changes metadata
-                    # into the current variant node.
-                    variant_node.addQualityMetadata(quality_type, metadata)
-                else:
-                    # If only variant_name and material are both specified, go one level deeper: create a material node
-                    # under the current variant node, and then add the quality/quality_changes metadata into the
-                    # material node.
-                    if root_material_id not in variant_node.children_map:
-                        variant_node.children_map[root_material_id] = QualityNode()
-                    material_node = cast(QualityNode, variant_node.children_map[root_material_id])
-
-                    material_node.addQualityMetadata(quality_type, metadata)
-
-            else:
-                # If variant_name is not specified, check if material is specified.
-                if root_material_id is not None:
-                    if root_material_id not in machine_node.children_map:
-                        machine_node.children_map[root_material_id] = QualityNode()
-                    material_node = cast(QualityNode, machine_node.children_map[root_material_id])
-
-                    material_node.addQualityMetadata(quality_type, metadata)
+            current_node = machine_node
+            intermediate_node_info_list = [nozzle_name, buildplate_name, root_material_id]
+            current_intermediate_node_info_idx = 0
+
+            while current_intermediate_node_info_idx < len(intermediate_node_info_list):
+                node_name = intermediate_node_info_list[current_intermediate_node_info_idx]
+                if node_name is not None:
+                    # There is specific information, update the current node to go deeper so we can add this quality
+                    # at the most specific branch in the lookup tree.
+                    if node_name not in current_node.children_map:
+                        current_node.children_map[node_name] = QualityNode()
+                    current_node = cast(QualityNode, current_node.children_map[node_name])
+
+                current_intermediate_node_info_idx += 1
+
+            current_node.addQualityMetadata(quality_type, metadata)
 
         # Initialize the lookup tree for quality_changes profiles with following structure:
         # <machine> -> <quality_type> -> <name>
@@ -217,8 +205,8 @@ class QualityManager(QObject):
         # To find the quality container for the GlobalStack, check in the following fall-back manner:
         #   (1) the machine-specific node
         #   (2) the generic node
-        machine_node = self._machine_variant_material_quality_type_to_quality_dict.get(machine_definition_id)
-        default_machine_node = self._machine_variant_material_quality_type_to_quality_dict.get(self._default_machine_definition_id)
+        machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get(machine_definition_id)
+        default_machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get(self._default_machine_definition_id)
         nodes_to_check = [machine_node, default_machine_node]
 
         # Iterate over all quality_types in the machine node
@@ -238,16 +226,19 @@ class QualityManager(QObject):
                     quality_group_dict[quality_type] = quality_group
                 break
 
+        buildplate_name = machine.getBuildplateName()
+
         # Iterate over all extruders to find quality containers for each extruder
         for position, extruder in machine.extruders.items():
-            variant_name = None
+            nozzle_name = None
             if extruder.variant.getId() != "empty_variant":
-                variant_name = extruder.variant.getName()
+                nozzle_name = extruder.variant.getName()
 
             # This is a list of root material IDs to use for searching for suitable quality profiles.
             # The root material IDs in this list are in prioritized order.
             root_material_id_list = []
             has_material = False  # flag indicating whether this extruder has a material assigned
+            root_material_id = None
             if extruder.material.getId() != "empty_material":
                 has_material = True
                 root_material_id = extruder.material.getMetaDataEntry("base_file")
@@ -264,34 +255,39 @@ class QualityManager(QObject):
             # Here we construct a list of nodes we want to look for qualities with the highest priority first.
             # The use case is that, when we look for qualities for a machine, we first want to search in the following
             # order:
-            #   1. machine-variant-and-material-specific qualities if exist
-            #   2. machine-variant-specific qualities if exist
-            #   3. machine-material-specific qualities if exist
-            #   4. machine-specific qualities if exist
-            #   5. generic qualities if exist
+            #   1. machine-nozzle-buildplate-and-material-specific qualities if exist
+            #   2. machine-nozzle-and-material-specific qualities if exist
+            #   3. machine-nozzle-specific qualities if exist
+            #   4. machine-material-specific qualities if exist
+            #   5. machine-specific qualities if exist
+            #   6. generic qualities if exist
             # Each points above can be represented as a node in the lookup tree, so here we simply put those nodes into
             # the list with priorities as the order. Later, we just need to loop over each node in this list and fetch
             # qualities from there.
+            node_info_list_0 = [nozzle_name, buildplate_name, root_material_id]
             nodes_to_check = []
 
-            if variant_name:
-                # In this case, we have both a specific variant and a specific material
-                variant_node = machine_node.getChildNode(variant_name)
-                if variant_node and has_material:
-                    for root_material_id in root_material_id_list:
-                        material_node = variant_node.getChildNode(root_material_id)
+            # This function tries to recursively find the deepest (the most specific) branch and add those nodes to
+            # the search list in the order described above. So, by iterating over that search node list, we first look
+            # in the more specific branches and then the less specific (generic) ones.
+            def addNodesToCheck(node, nodes_to_check_list, node_info_list, node_info_idx):
+                if node_info_idx < len(node_info_list):
+                    node_name = node_info_list[node_info_idx]
+                    if node_name is not None:
+                        current_node = node.getChildNode(node_name)
+                        if current_node is not None and has_material:
+                            addNodesToCheck(current_node, nodes_to_check_list, node_info_list, node_info_idx + 1)
+
+                if has_material:
+                    for rmid in root_material_id_list:
+                        material_node = node.getChildNode(rmid)
                         if material_node:
-                            nodes_to_check.append(material_node)
+                            nodes_to_check_list.append(material_node)
                             break
-                nodes_to_check.append(variant_node)
-
-            # In this case, we only have a specific material but NOT a variant
-            if has_material:
-                for root_material_id in root_material_id_list:
-                    material_node = machine_node.getChildNode(root_material_id)
-                    if material_node:
-                        nodes_to_check.append(material_node)
-                        break
+
+                nodes_to_check_list.append(node)
+
+            addNodesToCheck(machine_node, nodes_to_check, node_info_list_0, 0)
 
             nodes_to_check += [machine_node, default_machine_node]
             for node in nodes_to_check:
@@ -309,8 +305,8 @@ class QualityManager(QObject):
                             quality_group_dict[quality_type] = quality_group
 
                         quality_group = quality_group_dict[quality_type]
-                        quality_group.nodes_for_extruders[position] = quality_node
-                    break
+                        if position not in quality_group.nodes_for_extruders:
+                            quality_group.nodes_for_extruders[position] = quality_node
 
         # Update availabilities for each quality group
         self._updateQualityGroupsAvailability(machine, quality_group_dict.values())
@@ -323,8 +319,8 @@ class QualityManager(QObject):
         # To find the quality container for the GlobalStack, check in the following fall-back manner:
         #   (1) the machine-specific node
         #   (2) the generic node
-        machine_node = self._machine_variant_material_quality_type_to_quality_dict.get(machine_definition_id)
-        default_machine_node = self._machine_variant_material_quality_type_to_quality_dict.get(
+        machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get(machine_definition_id)
+        default_machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get(
             self._default_machine_definition_id)
         nodes_to_check = [machine_node, default_machine_node]
 

+ 1 - 9
cura/Machines/VariantManager.py

@@ -1,7 +1,6 @@
 # Copyright (c) 2018 Ultimaker B.V.
 # Cura is released under the terms of the LGPLv3 or higher.
 
-from enum import Enum
 from collections import OrderedDict
 from typing import Optional, TYPE_CHECKING
 
@@ -11,20 +10,13 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
 from UM.Util import parseBool
 
 from cura.Machines.ContainerNode import ContainerNode
+from cura.Machines.VariantType import VariantType, ALL_VARIANT_TYPES
 from cura.Settings.GlobalStack import GlobalStack
 
 if TYPE_CHECKING:
     from UM.Settings.DefinitionContainer import DefinitionContainer
 
 
-class VariantType(Enum):
-    BUILD_PLATE = "buildplate"
-    NOZZLE = "nozzle"
-
-
-ALL_VARIANT_TYPES = (VariantType.BUILD_PLATE, VariantType.NOZZLE)
-
-
 #
 # VariantManager is THE place to look for a specific variant. It maintains two variant lookup tables with the following
 # structure:

+ 15 - 0
cura/Machines/VariantType.py

@@ -0,0 +1,15 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from enum import Enum
+
+
+class VariantType(Enum):
+    BUILD_PLATE = "buildplate"
+    NOZZLE = "nozzle"
+
+
+ALL_VARIANT_TYPES = (VariantType.BUILD_PLATE, VariantType.NOZZLE)
+
+
+__all__ = ["VariantType", "ALL_VARIANT_TYPES"]

+ 1 - 1
cura/Settings/CuraStackBuilder.py

@@ -8,7 +8,7 @@ from UM.Logger import Logger
 from UM.Settings.Interfaces import DefinitionContainerInterface
 from UM.Settings.InstanceContainer import InstanceContainer
 
-from cura.Machines.VariantManager import VariantType
+from cura.Machines.VariantType import VariantType
 from .GlobalStack import GlobalStack
 from .ExtruderStack import ExtruderStack
 

+ 6 - 0
cura/Settings/GlobalStack.py

@@ -55,6 +55,12 @@ class GlobalStack(CuraContainerStack):
             return "machine_stack"
         return configuration_type
 
+    def getBuildplateName(self) -> Optional[str]:
+        name = None
+        if self.variant.getId() != "empty_variant":
+            name = self.variant.getName()
+        return name
+
     ##  Add an extruder to the list of extruders of this stack.
     #
     #   \param extruder The extruder to add.

+ 22 - 7
cura/Settings/MachineManager.py

@@ -1256,13 +1256,17 @@ class MachineManager(QObject):
         else:
             position_list = [position]
 
+        buildplate_name = None
+        if self._global_container_stack.variant.getId() != "empty_variant":
+            buildplate_name = self._global_container_stack.variant.getName()
+
         for position_item in position_list:
             extruder = self._global_container_stack.extruders[position_item]
 
             current_material_base_name = extruder.material.getMetaDataEntry("base_file")
-            current_variant_name = None
+            current_nozzle_name = None
             if extruder.variant.getId() != self._empty_variant_container.getId():
-                current_variant_name = extruder.variant.getMetaDataEntry("name")
+                current_nozzle_name = extruder.variant.getMetaDataEntry("name")
 
             from UM.Settings.Interfaces import PropertyEvaluationContext
             from cura.Settings.CuraContainerStack import _ContainerIndexes
@@ -1271,7 +1275,8 @@ class MachineManager(QObject):
             material_diameter = extruder.getProperty("material_diameter", "value", context)
             candidate_materials = self._material_manager.getAvailableMaterials(
                 self._global_container_stack.definition,
-                current_variant_name,
+                current_nozzle_name,
+                buildplate_name,
                 material_diameter)
 
             if not candidate_materials:
@@ -1284,7 +1289,7 @@ class MachineManager(QObject):
                 continue
 
             # The current material is not available, find the preferred one
-            material_node = self._material_manager.getDefaultMaterial(self._global_container_stack, position_item, current_variant_name)
+            material_node = self._material_manager.getDefaultMaterial(self._global_container_stack, position_item, current_nozzle_name)
             if material_node is not None:
                 self._setMaterial(position_item, material_node)
 
@@ -1326,7 +1331,12 @@ class MachineManager(QObject):
             for extruder_configuration in configuration.extruderConfigurations:
                 position = str(extruder_configuration.position)
                 variant_container_node = self._variant_manager.getVariantNode(self._global_container_stack.definition.getId(), extruder_configuration.hotendID)
-                material_container_node = self._material_manager.getMaterialNodeByType(self._global_container_stack, position, extruder_configuration.hotendID, extruder_configuration.material.guid)
+                material_container_node = self._material_manager.getMaterialNodeByType(self._global_container_stack,
+                                                                                       position,
+                                                                                       extruder_configuration.hotendID,
+                                                                                       configuration.buildplateConfiguration,
+                                                                                       extruder_configuration.material.guid)
+
                 if variant_container_node:
                     self._setVariantNode(position, variant_container_node)
                 else:
@@ -1389,12 +1399,17 @@ class MachineManager(QObject):
     def setMaterialById(self, position: str, root_material_id: str) -> None:
         if self._global_container_stack is None:
             return
+        buildplate_name = None
+        if self._global_container_stack.variant.getId() != "empty_variant":
+            buildplate_name = self._global_container_stack.variant.getName()
+
         machine_definition_id = self._global_container_stack.definition.id
         position = str(position)
         extruder_stack = self._global_container_stack.extruders[position]
-        variant_name = extruder_stack.variant.getName()
+        nozzle_name = extruder_stack.variant.getName()
         material_diameter = extruder_stack.approximateMaterialDiameter
-        material_node = self._material_manager.getMaterialNode(machine_definition_id, variant_name, material_diameter, root_material_id)
+        material_node = self._material_manager.getMaterialNode(machine_definition_id, nozzle_name, buildplate_name,
+                                                               material_diameter, root_material_id)
         self.setMaterial(position, material_node)
 
     ##  global_stack: if you want to provide your own global_stack instead of the current active one

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