Просмотр исходного кода

Merge branch 'master' into feature_firmware_updater

fieldOfView 6 лет назад
Родитель
Сommit
c1d7ca7bff

+ 1 - 0
cura/API/Backups.py

@@ -13,6 +13,7 @@ from cura.Backups.BackupsManager import BackupsManager
 #       api = CuraAPI()
 #       api.backups.createBackup()
 #       api.backups.restoreBackup(my_zip_file, {"cura_release": "3.1"})``
+
 class Backups:
     manager = BackupsManager()  # Re-used instance of the backups manager.
 

+ 33 - 0
cura/API/Interface/Settings.py

@@ -0,0 +1,33 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from cura.CuraApplication import CuraApplication
+
+##  The Interface.Settings API provides a version-proof bridge between Cura's
+#   (currently) sidebar UI and plug-ins that hook into it.
+#
+#   Usage:
+#       ``from cura.API import CuraAPI
+#       api = CuraAPI()
+#       api.interface.settings.getContextMenuItems()
+#       data = {
+#           "name": "My Plugin Action",
+#           "iconName": "my-plugin-icon",
+#           "actions": my_menu_actions,
+#           "menu_item": MyPluginAction(self)
+#       }
+#       api.interface.settings.addContextMenuItem(data)``
+
+class Settings:
+    # Re-used instance of Cura:
+    application = CuraApplication.getInstance()  # type: CuraApplication
+
+    ##  Add items to the sidebar context menu.
+    #   \param menu_item dict containing the menu item to add.
+    def addContextMenuItem(self, menu_item: dict) -> None:
+        self.application.addSidebarCustomMenuItem(menu_item)
+
+    ##  Get all custom items currently added to the sidebar context menu.
+    #   \return List containing all custom context menu items.
+    def getContextMenuItems(self) -> list:
+        return self.application.getSidebarCustomMenuItems()

+ 24 - 0
cura/API/Interface/__init__.py

@@ -0,0 +1,24 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from UM.PluginRegistry import PluginRegistry
+from cura.API.Interface.Settings import Settings
+
+##  The Interface class serves as a common root for the specific API
+#   methods for each interface element.
+#
+#   Usage:
+#       ``from cura.API import CuraAPI
+#       api = CuraAPI()
+#       api.interface.settings.addContextMenuItem()
+#       api.interface.viewport.addOverlay() # Not implemented, just a hypothetical
+#       api.interface.toolbar.getToolButtonCount() # Not implemented, just a hypothetical
+#       # etc.``
+
+class Interface:
+
+    # For now we use the same API version to be consistent.
+    VERSION = PluginRegistry.APIVersion
+
+    # API methods specific to the settings portion of the UI
+    settings = Settings()

+ 6 - 1
cura/API/__init__.py

@@ -2,6 +2,7 @@
 # Cura is released under the terms of the LGPLv3 or higher.
 from UM.PluginRegistry import PluginRegistry
 from cura.API.Backups import Backups
+from cura.API.Interface import Interface
 
 ##  The official Cura API that plug-ins can use to interact with Cura.
 #
@@ -9,10 +10,14 @@ from cura.API.Backups import Backups
 #   this API provides a version-safe interface with proper deprecation warnings
 #   etc. Usage of any other methods than the ones provided in this API can cause
 #   plug-ins to be unstable.
+
 class CuraAPI:
 
     # For now we use the same API version to be consistent.
     VERSION = PluginRegistry.APIVersion
 
-    # Backups API.
+    # Backups API
     backups = Backups()
+
+    # Interface API
+    interface = Interface()

+ 15 - 0
cura/BuildVolume.py

@@ -3,6 +3,7 @@
 
 from cura.Scene.CuraSceneNode import CuraSceneNode
 from cura.Settings.ExtruderManager import ExtruderManager
+from UM.Application import Application #To modify the maximum zoom level.
 from UM.i18n import i18nCatalog
 from UM.Scene.Platform import Platform
 from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
@@ -170,6 +171,12 @@ class BuildVolume(SceneNode):
         if shape:
             self._shape = shape
 
+    ##  Get the length of the 3D diagonal through the build volume.
+    #
+    #   This gives a sense of the scale of the build volume in general.
+    def getDiagonalSize(self) -> float:
+        return math.sqrt(self._width * self._width + self._height * self._height + self._depth * self._depth)
+
     def getDisallowedAreas(self) -> List[Polygon]:
         return self._disallowed_areas
 
@@ -235,6 +242,8 @@ class BuildVolume(SceneNode):
 
                 # Mark the node as outside build volume if the set extruder is disabled
                 extruder_position = node.callDecoration("getActiveExtruderPosition")
+                if extruder_position not in self._global_container_stack.extruders:
+                    continue
                 if not self._global_container_stack.extruders[extruder_position].isEnabled:
                     node.setOutsideBuildArea(True)
                     continue
@@ -552,6 +561,12 @@ class BuildVolume(SceneNode):
             if self._engine_ready:
                 self.rebuild()
 
+            camera = Application.getInstance().getController().getCameraTool()
+            if camera:
+                diagonal = self.getDiagonalSize()
+                if diagonal > 1:
+                    camera.setZoomRange(min = 0.1, max = diagonal * 5) #You can zoom out up to 5 times the diagonal. This gives some space around the volume.
+
     def _onEngineCreated(self):
         self._engine_ready = True
         self.rebuild()

+ 8 - 3
cura/CameraImageProvider.py

@@ -4,15 +4,20 @@ from PyQt5.QtCore import QSize
 
 from UM.Application import Application
 
+
 class CameraImageProvider(QQuickImageProvider):
     def __init__(self):
-        QQuickImageProvider.__init__(self, QQuickImageProvider.Image)
+        super().__init__(QQuickImageProvider.Image)
 
     ##  Request a new image.
     def requestImage(self, id, size):
         for output_device in Application.getInstance().getOutputDeviceManager().getOutputDevices():
             try:
-                return output_device.activePrinter.camera.getImage(), QSize(15, 15)
+                image = output_device.activePrinter.camera.getImage()
+                if image.isNull():
+                    image = QImage()
+
+                return image, QSize(15, 15)
             except AttributeError:
                 pass
-        return QImage(), QSize(15, 15)
+        return QImage(), QSize(15, 15)

+ 2 - 1
cura/CuraActions.py

@@ -50,7 +50,8 @@ class CuraActions(QObject):
         scene = cura.CuraApplication.CuraApplication.getInstance().getController().getScene()
         camera = scene.getActiveCamera()
         if camera:
-            camera.setPosition(Vector(-80, 250, 700))
+            diagonal_size = cura.CuraApplication.CuraApplication.getInstance().getBuildVolume().getDiagonalSize()
+            camera.setPosition(Vector(-80, 250, 700) * diagonal_size / 375)
             camera.setPerspective(True)
             camera.lookAt(Vector(0, 0, 0))
 

+ 40 - 41
cura/CuraApplication.py

@@ -1,11 +1,10 @@
 # Copyright (c) 2018 Ultimaker B.V.
 # Cura is released under the terms of the LGPLv3 or higher.
 
-import copy
 import os
 import sys
 import time
-from typing import cast, TYPE_CHECKING, Optional
+from typing import cast, TYPE_CHECKING
 
 import numpy
 
@@ -104,6 +103,8 @@ from cura.Settings.UserChangesModel import UserChangesModel
 from cura.Settings.ExtrudersModel import ExtrudersModel
 from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler
 from cura.Settings.ContainerManager import ContainerManager
+from cura.Settings.SidebarCustomMenuItemsModel import SidebarCustomMenuItemsModel
+import cura.Settings.cura_empty_instance_containers
 
 from cura.ObjectsModel import ObjectsModel
 
@@ -117,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
+    CuraSDKVersion = ""
 
 
 class CuraApplication(QtApplication):
@@ -226,6 +228,10 @@ class CuraApplication(QtApplication):
 
         self._need_to_show_user_agreement = True
 
+        self._sidebar_custom_menu_items = []  # type: list # Keeps list of custom menu items for the side bar
+
+        self._plugins_loaded = False
+
         # Backups
         self._auto_save = None
         self._save_data_enabled = True
@@ -362,42 +368,23 @@ class CuraApplication(QtApplication):
         # Add empty variant, material and quality containers.
         # Since they are empty, they should never be serialized and instead just programmatically created.
         # We need them to simplify the switching between materials.
-        empty_container = self._container_registry.getEmptyInstanceContainer()
-        self.empty_container = empty_container
-
-        empty_definition_changes_container = copy.deepcopy(empty_container)
-        empty_definition_changes_container.setMetaDataEntry("id", "empty_definition_changes")
-        empty_definition_changes_container.setMetaDataEntry("type", "definition_changes")
-        self._container_registry.addContainer(empty_definition_changes_container)
-        self.empty_definition_changes_container = empty_definition_changes_container
-
-        empty_variant_container = copy.deepcopy(empty_container)
-        empty_variant_container.setMetaDataEntry("id", "empty_variant")
-        empty_variant_container.setMetaDataEntry("type", "variant")
-        self._container_registry.addContainer(empty_variant_container)
-        self.empty_variant_container = empty_variant_container
-
-        empty_material_container = copy.deepcopy(empty_container)
-        empty_material_container.setMetaDataEntry("id", "empty_material")
-        empty_material_container.setMetaDataEntry("type", "material")
-        self._container_registry.addContainer(empty_material_container)
-        self.empty_material_container = empty_material_container
-
-        empty_quality_container = copy.deepcopy(empty_container)
-        empty_quality_container.setMetaDataEntry("id", "empty_quality")
-        empty_quality_container.setName("Not Supported")
-        empty_quality_container.setMetaDataEntry("quality_type", "not_supported")
-        empty_quality_container.setMetaDataEntry("type", "quality")
-        empty_quality_container.setMetaDataEntry("supported", False)
-        self._container_registry.addContainer(empty_quality_container)
-        self.empty_quality_container = empty_quality_container
-
-        empty_quality_changes_container = copy.deepcopy(empty_container)
-        empty_quality_changes_container.setMetaDataEntry("id", "empty_quality_changes")
-        empty_quality_changes_container.setMetaDataEntry("type", "quality_changes")
-        empty_quality_changes_container.setMetaDataEntry("quality_type", "not_supported")
-        self._container_registry.addContainer(empty_quality_changes_container)
-        self.empty_quality_changes_container = empty_quality_changes_container
+        self.empty_container = cura.Settings.cura_empty_instance_containers.empty_container
+
+        self._container_registry.addContainer(
+            cura.Settings.cura_empty_instance_containers.empty_definition_changes_container)
+        self.empty_definition_changes_container = cura.Settings.cura_empty_instance_containers.empty_definition_changes_container
+
+        self._container_registry.addContainer(cura.Settings.cura_empty_instance_containers.empty_variant_container)
+        self.empty_variant_container = cura.Settings.cura_empty_instance_containers.empty_variant_container
+
+        self._container_registry.addContainer(cura.Settings.cura_empty_instance_containers.empty_material_container)
+        self.empty_material_container = cura.Settings.cura_empty_instance_containers.empty_material_container
+
+        self._container_registry.addContainer(cura.Settings.cura_empty_instance_containers.empty_quality_container)
+        self.empty_quality_container = cura.Settings.cura_empty_instance_containers.empty_quality_container
+
+        self._container_registry.addContainer(cura.Settings.cura_empty_instance_containers.empty_quality_changes_container)
+        self.empty_quality_changes_container = cura.Settings.cura_empty_instance_containers.empty_quality_changes_container
 
     # Initializes the version upgrade manager with by providing the paths for each resource type and the latest
     # versions.
@@ -625,7 +612,7 @@ class CuraApplication(QtApplication):
     # Cura has multiple locations where instance containers need to be saved, so we need to handle this differently.
     def saveSettings(self):
         if not self.started or not self._save_data_enabled:
-            # Do not do saving during application start or when data should not be safed on quit.
+            # Do not do saving during application start or when data should not be saved on quit.
             return
         ContainerRegistry.getInstance().saveDirtyContainers()
         self.savePreferences()
@@ -774,7 +761,10 @@ class CuraApplication(QtApplication):
         # Initialize camera
         root = controller.getScene().getRoot()
         camera = Camera("3d", root)
-        camera.setPosition(Vector(-80, 250, 700))
+        diagonal = self.getBuildVolume().getDiagonalSize()
+        if diagonal < 1: #No printer added yet. Set a default camera distance for normal-sized printers.
+            diagonal = 375
+        camera.setPosition(Vector(-80, 250, 700) * diagonal / 375)
         camera.setPerspective(True)
         camera.lookAt(Vector(0, 0, 0))
         controller.getScene().setActiveCamera("3d")
@@ -908,6 +898,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")
 
@@ -942,6 +933,7 @@ class CuraApplication(QtApplication):
         qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator")
         qmlRegisterType(UserChangesModel, "Cura", 1, 0, "UserChangesModel")
         qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.getInstance)
+        qmlRegisterType(SidebarCustomMenuItemsModel, "Cura", 1, 0, "SidebarCustomMenuItemsModel")
 
         # As of Qt5.7, it is necessary to get rid of any ".." in the path for the singleton to work.
         actions_url = QUrl.fromLocalFile(os.path.abspath(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml")))
@@ -1729,3 +1721,10 @@ class CuraApplication(QtApplication):
     @pyqtSlot()
     def showMoreInformationDialogForAnonymousDataCollection(self):
         cast(SliceInfo, self._plugin_registry.getPluginObject("SliceInfoPlugin")).showMoreInfoDialog()
+
+    def addSidebarCustomMenuItem(self, menu_item: dict) -> None:
+        self._sidebar_custom_menu_items.append(menu_item)
+
+    def getSidebarCustomMenuItems(self) -> list:
+        return self._sidebar_custom_menu_items
+

+ 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):

Некоторые файлы не были показаны из-за большого количества измененных файлов