From a303f394c8744eba3afee74c53462511ca5250f3 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 2 Aug 2018 16:09:31 +0200 Subject: [PATCH 01/13] Move VariantType to VariantType.py Less circular dependencies for imports. --- cura/Machines/Models/BuildPlateModel.py | 2 +- cura/Machines/Models/NozzleModel.py | 3 ++- cura/Machines/VariantManager.py | 10 +--------- cura/Machines/VariantType.py | 15 +++++++++++++++ cura/Settings/CuraStackBuilder.py | 2 +- plugins/3MFReader/ThreeMFWorkspaceReader.py | 3 +-- plugins/XmlMaterialProfile/XmlMaterialProfile.py | 4 +++- 7 files changed, 24 insertions(+), 15 deletions(-) create mode 100644 cura/Machines/VariantType.py diff --git a/cura/Machines/Models/BuildPlateModel.py b/cura/Machines/Models/BuildPlateModel.py index e1b4f40d8e..82b9db4d64 100644 --- a/cura/Machines/Models/BuildPlateModel.py +++ b/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): diff --git a/cura/Machines/Models/NozzleModel.py b/cura/Machines/Models/NozzleModel.py index 0879998b7d..9d97106d6b 100644 --- a/cura/Machines/Models/NozzleModel.py +++ b/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([]) diff --git a/cura/Machines/VariantManager.py b/cura/Machines/VariantManager.py index 036c79d6c7..969fed670e 100644 --- a/cura/Machines/VariantManager.py +++ b/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: diff --git a/cura/Machines/VariantType.py b/cura/Machines/VariantType.py new file mode 100644 index 0000000000..82bb86d7d9 --- /dev/null +++ b/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"] diff --git a/cura/Settings/CuraStackBuilder.py b/cura/Settings/CuraStackBuilder.py index 841d45ed31..a794dde651 100644 --- a/cura/Settings/CuraStackBuilder.py +++ b/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 diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index f72029524e..389a7da704 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -24,6 +24,7 @@ from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType from UM.Job import Job from UM.Preferences import Preferences +from cura.Machines.VariantType import VariantType from cura.Settings.CuraStackBuilder import CuraStackBuilder from cura.Settings.ExtruderStack import ExtruderStack from cura.Settings.GlobalStack import GlobalStack @@ -889,7 +890,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader): parser = self._machine_info.variant_info.parser variant_name = parser["general"]["name"] - from cura.Machines.VariantManager import VariantType variant_type = VariantType.BUILD_PLATE node = variant_manager.getVariantNode(global_stack.definition.getId(), variant_name, variant_type) @@ -905,7 +905,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader): parser = extruder_info.variant_info.parser variant_name = parser["general"]["name"] - from cura.Machines.VariantManager import VariantType variant_type = VariantType.NOZZLE node = variant_manager.getVariantNode(global_stack.definition.getId(), variant_name, variant_type) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index eac6646197..a799f948c4 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -19,8 +19,11 @@ from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.ContainerRegistry import ContainerRegistry from UM.ConfigurationErrorMessage import ConfigurationErrorMessage +from cura.Machines.VariantType import VariantType + from .XmlMaterialValidator import XmlMaterialValidator + ## Handles serializing and deserializing material containers from an XML file class XmlMaterialProfile(InstanceContainer): CurrentFdmMaterialVersion = "1.3" @@ -269,7 +272,6 @@ class XmlMaterialProfile(InstanceContainer): buildplate_dict = {} # type: Dict[str, Any] for variant_name, variant_dict in machine_variant_map[definition_id].items(): variant_type = variant_dict["variant_node"].metadata["hardware_type"] - from cura.Machines.VariantManager import VariantType variant_type = VariantType(variant_type) if variant_type == VariantType.NOZZLE: # The hotend identifier is not the containers name, but its "name". From 066a00653ab6ba62fcd5267a352646a7078af142 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 2 Aug 2018 17:15:30 +0200 Subject: [PATCH 02/13] Add one more layer to the decision tree --- cura/Machines/MaterialManager.py | 200 +++++++++++------- cura/Machines/QualityManager.py | 125 ++++++----- cura/Settings/GlobalStack.py | 6 + cura/Settings/MachineManager.py | 22 +- .../XmlMaterialProfile/XmlMaterialProfile.py | 199 ++++++++++++----- resources/definitions/fdmprinter.def.json | 1 + 6 files changed, 368 insertions(+), 185 deletions(-) diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py index 5d691fcef4..86f7dea81f 100644 --- a/cura/Machines/MaterialManager.py +++ b/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 @@ -186,10 +186,11 @@ class MaterialManager(QObject): for root_material_id in data_dict.values(): self._diameter_material_map[root_material_id] = default_root_material_id + variant_manager = self._application.getVariantManager() + # 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": @@ -199,36 +200,62 @@ class MaterialManager(QObject): definition = material_metadata["definition"] approximate_diameter = material_metadata["approximate_diameter"] - if approximate_diameter not in self._diameter_machine_variant_material_map: - self._diameter_machine_variant_material_map[approximate_diameter] = {} + if approximate_diameter not in self._diameter_machine_nozzle_buildplate_material_map: + self._diameter_machine_nozzle_buildplate_material_map[approximate_diameter] = {} - 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() + 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() - 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 = machine_nozzle_buildplate_material_map[definition] + nozzle_name = material_metadata.get("variant_name") + buildplate_name = material_metadata.get("buildplate_name") + if not nozzle_name: + # if there is no nozzle, 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 + # this material is nozzle-specific, so we save it in a nozzle-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() + # Check first if the nozzle exists in the manager + existing_nozzle = variant_manager.getVariantNode(definition, nozzle_name, VariantType.NOZZLE) + if existing_nozzle is not None: + if nozzle_name not in machine_node.children_map: + machine_node.children_map[nozzle_name] = MaterialNode() + + nozzle_node = machine_node.children_map[nozzle_name] + + # Check build plate node + if not buildplate_name: + # if there is no buildplate, this material is for the machine, so put its metadata in the machine node. + if root_material_id in nozzle_node.material_map: # We shouldn't have duplicated nozzle-specific materials for the same machine. + ConfigurationErrorMessage.getInstance().addFaultyContainers(root_material_id) + continue + nozzle_node.material_map[root_material_id] = MaterialNode(material_metadata) + else: + # this material is nozzle-and-buildplate-specific, so we save it in a buildplate-specific node + # under the machine-specific node + + # Check first if the buildplate exists in the manager + existing_buildplate = variant_manager.getVariantNode(definition, buildplate_name, VariantType.BUILD_PLATE) + if existing_buildplate is not None: + if buildplate_name not in nozzle_node.children_map: + nozzle_node.children_map[buildplate_name] = MaterialNode() + + buildplate_node = nozzle_node.children_map[buildplate_name] + if root_material_id in buildplate_node.material_map: # We shouldn't have duplicated nozzle-and-buildplate-specific materials for the same machine. + ConfigurationErrorMessage.getInstance().addFaultyContainers(root_material_id) + continue + buildplate_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 buildplate does not exist.".format(id = material_metadata["id"])) + self._container_registry.addWrongContainerId(material_metadata["id"]) - 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"])) + Logger.log("w", "Not adding {id} to the material manager because the nozzle does not exist.".format(id = material_metadata["id"])) self._container_registry.addWrongContainerId(material_metadata["id"]) self.materialsUpdated.emit() @@ -263,45 +290,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", extruder_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 extruder_nozzle_name is not None and machine_node is not None: + nozzle_node = machine_node.getChildNode(extruder_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 - if material_id not in material_id_metadata_dict: - material_id_metadata_dict[material_id] = node + # 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 return material_id_metadata_dict @@ -310,13 +344,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 +359,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 +405,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 +425,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 +453,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 +472,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 +559,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 diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py index 82a11f9960..273f1ae11f 100644 --- a/cura/Machines/QualityManager.py +++ b/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: - # -> -> - # -> + # -> -> -> + # -> - 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,47 +79,58 @@ 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]) + # Check if nozzle si specified + if nozzle_name is not None: + if nozzle_name not in machine_node.children_map: + machine_node.children_map[nozzle_name] = QualityNode() + nozzle_node = cast(QualityNode, machine_node.children_map[nozzle_name]) + + # Check if buildplate is specified + if buildplate_name is not None: + if buildplate_name not in nozzle_node.children_map: + nozzle_node.children_map[buildplate_name] = QualityNode() + buildplate_node = cast(QualityNode, nozzle_node.children_map[buildplate_name]) + + if root_material_id is None: + buildplate_node.addQualityMetadata(quality_type, metadata) + else: + if root_material_id not in buildplate_node.children_map: + buildplate_node.children_map[root_material_id] = QualityNode() + material_node = cast(QualityNode, buildplate_node.children_map[root_material_id]) + + material_node.addQualityMetadata(quality_type, metadata) - 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]) + if root_material_id is None: + nozzle_node.addQualityMetadata(quality_type, metadata) + else: + if root_material_id not in nozzle_node.children_map: + nozzle_node.children_map[root_material_id] = QualityNode() + material_node = cast(QualityNode, nozzle_node.children_map[root_material_id]) - material_node.addQualityMetadata(quality_type, metadata) + material_node.addQualityMetadata(quality_type, metadata) else: - # If variant_name is not specified, check if material is specified. + # If nozzle 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() @@ -217,8 +228,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,11 +249,13 @@ 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. @@ -258,34 +271,47 @@ class QualityManager(QObject): # Also try to get the fallback material material_type = extruder.material.getMetaDataEntry("material") fallback_root_material_id = self._material_manager.getFallbackMaterialIdByMaterialType(material_type) - if fallback_root_material_id: + if fallback_root_material_id and root_material_id not in root_material_id_list: root_material_id_list.append(fallback_root_material_id) # 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. 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: + if nozzle_name: + # In this case, we have both a specific nozzle and a specific material + nozzle_node = machine_node.getChildNode(nozzle_name) + if nozzle_node and has_material: + # Check build plate if exists + if buildplate_name: + buildplate_node = nozzle_node.getChildNode(buildplate_name) + if buildplate_node and has_material: + for root_material_id in root_material_id_list: + material_node = buildplate_node.getChildNode(root_material_id) + if material_node: + nodes_to_check.append(material_node) + break + nodes_to_check.append(buildplate_node) + + # Then add nozzle specific materials for root_material_id in root_material_id_list: - material_node = variant_node.getChildNode(root_material_id) + material_node = nozzle_node.getChildNode(root_material_id) if material_node: nodes_to_check.append(material_node) break - nodes_to_check.append(variant_node) + nodes_to_check.append(nozzle_node) - # In this case, we only have a specific material but NOT a variant + # In this case, we only have a specific material but NOT a nozzle if has_material: for root_material_id in root_material_id_list: material_node = machine_node.getChildNode(root_material_id) @@ -309,8 +335,9 @@ 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 + #break # Update availabilities for each quality group self._updateQualityGroupsAvailability(machine, quality_group_dict.values()) @@ -323,8 +350,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] diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py index 66f3290b85..35edfe1053 100755 --- a/cura/Settings/GlobalStack.py +++ b/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. diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index ff585deb54..733340e0ce 100755 --- a/cura/Settings/MachineManager.py +++ b/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) @@ -1389,12 +1394,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 diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index a799f948c4..7d9b2aacc3 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -6,19 +6,17 @@ import io import json #To parse the product-to-id mapping file. import os.path #To find the product-to-id mapping. import sys -from typing import Any, Dict, List, Optional, cast +from typing import Any, Dict, List, Optional, Tuple, cast import xml.etree.ElementTree as ET -from typing import Dict -from typing import Iterator from UM.Resources import Resources from UM.Logger import Logger -from cura.CuraApplication import CuraApplication import UM.Dictionary from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.ContainerRegistry import ContainerRegistry from UM.ConfigurationErrorMessage import ConfigurationErrorMessage +from cura.CuraApplication import CuraApplication from cura.Machines.VariantType import VariantType from .XmlMaterialValidator import XmlMaterialValidator @@ -695,74 +693,38 @@ class XmlMaterialProfile(InstanceContainer): if buildplate_id is None: continue - from cura.Machines.VariantManager import VariantType variant_manager = CuraApplication.getInstance().getVariantManager() variant_node = variant_manager.getVariantNode(machine_id, buildplate_id, variant_type = VariantType.BUILD_PLATE) if not variant_node: continue - buildplate_compatibility = machine_compatibility - buildplate_recommended = machine_compatibility - settings = buildplate.iterfind("./um:setting", self.__namespaces) - for entry in settings: - key = entry.get("key") - if key in self.__unmapped_settings: - if key == "hardware compatible": - buildplate_compatibility = self._parseCompatibleValue(entry.text) - elif key == "hardware recommended": - buildplate_recommended = self._parseCompatibleValue(entry.text) - else: - Logger.log("d", "Unsupported material setting %s", key) + _, buildplate_unmapped_settings_dict = self._getSettingsDictForNode(buildplate) + + buildplate_compatibility = buildplate_unmapped_settings_dict.get("hardware compatible", + machine_compatibility) + buildplate_recommended = buildplate_unmapped_settings_dict.get("hardware recommended", + machine_compatibility) buildplate_map["buildplate_compatible"][buildplate_id] = buildplate_compatibility buildplate_map["buildplate_recommended"][buildplate_id] = buildplate_recommended hotends = machine.iterfind("./um:hotend", self.__namespaces) for hotend in hotends: - # The "id" field for hotends in material profiles are actually + # The "id" field for hotends in material profiles is actually name hotend_name = hotend.get("id") if hotend_name is None: continue variant_manager = CuraApplication.getInstance().getVariantManager() - variant_node = variant_manager.getVariantNode(machine_id, hotend_name) + variant_node = variant_manager.getVariantNode(machine_id, hotend_name, VariantType.NOZZLE) if not variant_node: continue - hotend_compatibility = machine_compatibility - hotend_setting_values = {} - settings = hotend.iterfind("./um:setting", self.__namespaces) - for entry in settings: - key = entry.get("key") - if key in self.__material_settings_setting_map: - if key == "processing temperature graph": #This setting has no setting text but subtags. - graph_nodes = entry.iterfind("./um:point", self.__namespaces) - graph_points = [] - for graph_node in graph_nodes: - flow = float(graph_node.get("flow")) - temperature = float(graph_node.get("temperature")) - graph_points.append([flow, temperature]) - hotend_setting_values[self.__material_settings_setting_map[key]] = str(graph_points) - else: - hotend_setting_values[self.__material_settings_setting_map[key]] = entry.text - elif key in self.__unmapped_settings: - if key == "hardware compatible": - hotend_compatibility = self._parseCompatibleValue(entry.text) - else: - Logger.log("d", "Unsupported material setting %s", key) - - # Add namespaced Cura-specific settings - settings = hotend.iterfind("./cura:setting", self.__namespaces) - for entry in settings: - value = entry.text - if value.lower() == "yes": - value = True - elif value.lower() == "no": - value = False - key = entry.get("key") - hotend_setting_values[key] = value + hotend_mapped_settings, hotend_unmapped_settings = self._getSettingsDictForNode(hotend) + hotend_compatibility = hotend_unmapped_settings.get("hardware compatible", machine_compatibility) + # Generate container ID for the hotend-specific material container new_hotend_specific_material_id = self.getId() + "_" + machine_id + "_" + hotend_name.replace(" ", "_") # Same as machine compatibility, keep the derived material containers consistent with the parent material @@ -787,7 +749,7 @@ class XmlMaterialProfile(InstanceContainer): new_hotend_material.getMetaData()["buildplate_recommended"] = buildplate_map["buildplate_recommended"] cached_hotend_setting_properties = cached_machine_setting_properties.copy() - cached_hotend_setting_properties.update(hotend_setting_values) + cached_hotend_setting_properties.update(hotend_mapped_settings) new_hotend_material.setCachedValues(cached_hotend_setting_properties) @@ -796,6 +758,61 @@ class XmlMaterialProfile(InstanceContainer): if is_new_material: containers_to_add.append(new_hotend_material) + # + # Build plates in hotend + # + buildplates = hotend.iterfind("./um:buildplate", self.__namespaces) + for buildplate in buildplates: + # The "id" field for buildplate in material profiles is actually name + buildplate_name = buildplate.get("id") + if buildplate_name is None: + continue + + variant_manager = CuraApplication.getInstance().getVariantManager() + variant_node = variant_manager.getVariantNode(machine_id, buildplate_name, VariantType.BUILD_PLATE) + if not variant_node: + continue + + buildplate_mapped_settings, buildplate_unmapped_settings = self._getSettingsDictForNode(buildplate) + buildplate_compatibility = buildplate_unmapped_settings.get("hardware compatible", + buildplate_map["buildplate_compatible"]) + buildplate_recommended = buildplate_unmapped_settings.get("hardware recommended", + buildplate_map["buildplate_recommended"]) + + # Generate container ID for the hotend-and-buildplate-specific material container + new_hotend_and_buildplate_specific_material_id = new_hotend_specific_material_id + "_" + buildplate_name.replace(" ", "_") + + # Same as machine compatibility, keep the derived material containers consistent with the parent material + if ContainerRegistry.getInstance().isLoaded(new_hotend_and_buildplate_specific_material_id): + new_hotend_and_buildplate_material = ContainerRegistry.getInstance().findContainers(id = new_hotend_and_buildplate_specific_material_id)[0] + is_new_material = False + else: + new_hotend_and_buildplate_material = XmlMaterialProfile(new_hotend_and_buildplate_specific_material_id) + is_new_material = True + + new_hotend_and_buildplate_material.setMetaData(copy.deepcopy(new_hotend_material.getMetaData())) + new_hotend_and_buildplate_material.getMetaData()["id"] = new_hotend_and_buildplate_specific_material_id + new_hotend_and_buildplate_material.getMetaData()["name"] = self.getName() + new_hotend_and_buildplate_material.getMetaData()["variant_name"] = hotend_name + new_hotend_and_buildplate_material.getMetaData()["buildplate_name"] = buildplate_name + new_hotend_and_buildplate_material.setDefinition(machine_id) + # Don't use setMetadata, as that overrides it for all materials with same base file + new_hotend_and_buildplate_material.getMetaData()["compatible"] = buildplate_compatibility + new_hotend_and_buildplate_material.getMetaData()["machine_manufacturer"] = machine_manufacturer + new_hotend_and_buildplate_material.getMetaData()["definition"] = machine_id + new_hotend_and_buildplate_material.getMetaData()["buildplate_compatible"] = buildplate_compatibility + new_hotend_and_buildplate_material.getMetaData()["buildplate_recommended"] = buildplate_recommended + + cached_hotend_and_buildplate_setting_properties = cached_hotend_setting_properties.copy() + cached_hotend_and_buildplate_setting_properties.update(buildplate_mapped_settings) + + new_hotend_and_buildplate_material.setCachedValues(cached_hotend_and_buildplate_setting_properties) + + new_hotend_and_buildplate_material._dirty = False + + if is_new_material: + containers_to_add.append(new_hotend_and_buildplate_material) + # there is only one ID for a machine. Once we have reached here, it means we have already found # a workable ID for that machine, so there is no need to continue break @@ -803,6 +820,54 @@ class XmlMaterialProfile(InstanceContainer): for container_to_add in containers_to_add: ContainerRegistry.getInstance().addContainer(container_to_add) + @classmethod + def _getSettingsDictForNode(cls, node) -> Tuple[dict, dict]: + node_mapped_settings_dict = dict() + node_unmapped_settings_dict = dict() + + # Fetch settings in the "um" namespace + um_settings = node.iterfind("./um:setting", cls.__namespaces) + for um_setting_entry in um_settings: + setting_key = um_setting_entry.get("key") + + # Mapped settings + if setting_key in cls.__material_settings_setting_map: + if setting_key == "processing temperature graph": # This setting has no setting text but subtags. + graph_nodes = um_setting_entry.iterfind("./um:point", cls.__namespaces) + graph_points = [] + for graph_node in graph_nodes: + flow = float(graph_node.get("flow")) + temperature = float(graph_node.get("temperature")) + graph_points.append([flow, temperature]) + node_mapped_settings_dict[cls.__material_settings_setting_map[setting_key]] = str( + graph_points) + else: + node_mapped_settings_dict[cls.__material_settings_setting_map[setting_key]] = um_setting_entry.text + + # Unmapped settings + elif setting_key in cls.__unmapped_settings: + if setting_key in ("hardware compatible", "hardware recommended"): + node_unmapped_settings_dict[setting_key] = cls._parseCompatibleValue(um_setting_entry.text) + + # Unknown settings + else: + Logger.log("w", "Unsupported material setting %s", setting_key) + + # Fetch settings in the "cura" namespace + cura_settings = node.iterfind("./cura:setting", cls.__namespaces) + for cura_setting_entry in cura_settings: + value = cura_setting_entry.text + if value.lower() == "yes": + value = True + elif value.lower() == "no": + value = False + key = cura_setting_entry.get("key") + + # Cura settings are all mapped + node_mapped_settings_dict[key] = value + + return node_mapped_settings_dict, node_unmapped_settings_dict + @classmethod def deserializeMetadata(cls, serialized: str, container_id: str) -> List[Dict[str, Any]]: result_metadata = [] #All the metadata that we found except the base (because the base is returned). @@ -985,6 +1050,36 @@ class XmlMaterialProfile(InstanceContainer): result_metadata.append(new_hotend_material_metadata) + # + # Buildplates in Hotends + # + buildplates = hotend.iterfind("./um:buildplate", cls.__namespaces) + for buildplate in buildplates: + # The "id" field for buildplate in material profiles is actually name + buildplate_name = buildplate.get("id") + if buildplate_name is None: + continue + + buildplate_mapped_settings, buildplate_unmapped_settings = cls._getSettingsDictForNode(buildplate) + buildplate_compatibility = buildplate_unmapped_settings.get("hardware compatible", + buildplate_map["buildplate_compatible"]) + buildplate_recommended = buildplate_unmapped_settings.get("hardware recommended", + buildplate_map["buildplate_recommended"]) + + # Generate container ID for the hotend-and-buildplate-specific material container + new_hotend_and_buildplate_specific_material_id = new_hotend_specific_material_id + "_" + buildplate_name.replace( + " ", "_") + + new_hotend_and_buildplate_material_metadata = {} + new_hotend_and_buildplate_material_metadata.update(new_hotend_material_metadata) + new_hotend_and_buildplate_material_metadata["id"] = new_hotend_and_buildplate_specific_material_id + new_hotend_and_buildplate_material_metadata["buildplate_name"] = buildplate_name + new_hotend_and_buildplate_material_metadata["compatible"] = buildplate_compatibility + new_hotend_and_buildplate_material_metadata["buildplate_compatible"] = buildplate_compatibility + new_hotend_and_buildplate_material_metadata["buildplate_recommended"] = buildplate_recommended + + result_metadata.append(new_hotend_and_buildplate_material_metadata) + # there is only one ID for a machine. Once we have reached here, it means we have already found # a workable ID for that machine, so there is no need to continue break diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index b767aac7b9..6134a43b8e 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -2127,6 +2127,7 @@ "type": "float", "default_value": 60, "value": "default_material_bed_temperature", + "resolve": "max(extruderValues('material_bed_temperature'))", "minimum_value": "-273.15", "minimum_value_warning": "0", "maximum_value_warning": "130", From cc39ca09b8568c79f3c045189dda2a262c851eb1 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 2 Aug 2018 17:16:16 +0200 Subject: [PATCH 03/13] Enable build plate selection menu --- resources/qml/Cura.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index 09c66f98f9..e1bf8c8b77 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -232,7 +232,7 @@ UM.MainWindow } // TODO Temporary hidden, add back again when feature ready -// BuildplateMenu { title: catalog.i18nc("@title:menu", "&Build plate"); visible: Cura.MachineManager.hasVariantBuildplates } + BuildplateMenu { title: catalog.i18nc("@title:menu", "&Build plate"); visible: Cura.MachineManager.hasVariantBuildplates } ProfileMenu { title: catalog.i18nc("@title:menu", "&Profile"); } MenuSeparator { } From 5320d05b2009bdbd73496698d5ffade0dea5b944 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 2 Aug 2018 17:22:31 +0200 Subject: [PATCH 04/13] Add special quality profiles --- ...5_aa0.4_aluminum_ABS_High_Quality.inst.cfg | 34 +++++++++ ..._aa0.4_aluminum_CPEP_High_Quality.inst.cfg | 53 ++++++++++++++ ...5_aa0.4_aluminum_CPE_High_Quality.inst.cfg | 32 +++++++++ ...s5_aa0.4_aluminum_PC_High_Quality.inst.cfg | 71 +++++++++++++++++++ ...s5_aa0.8_aluminum_ABS_Draft_Print.inst.cfg | 27 +++++++ ...s5_aa0.8_aluminum_CPEP_Fast_Print.inst.cfg | 43 +++++++++++ ...s5_aa0.8_aluminum_CPE_Draft_Print.inst.cfg | 29 ++++++++ ...m_s5_aa0.8_aluminum_PC_Fast_Print.inst.cfg | 36 ++++++++++ ..._s5_aa0.8_aluminum_PP_Draft_Print.inst.cfg | 53 ++++++++++++++ 9 files changed, 378 insertions(+) create mode 100644 resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_ABS_High_Quality.inst.cfg create mode 100644 resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_CPEP_High_Quality.inst.cfg create mode 100644 resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_CPE_High_Quality.inst.cfg create mode 100644 resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_PC_High_Quality.inst.cfg create mode 100644 resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_ABS_Draft_Print.inst.cfg create mode 100644 resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_CPEP_Fast_Print.inst.cfg create mode 100644 resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_CPE_Draft_Print.inst.cfg create mode 100644 resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_PC_Fast_Print.inst.cfg create mode 100644 resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_PP_Draft_Print.inst.cfg diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_ABS_High_Quality.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_ABS_High_Quality.inst.cfg new file mode 100644 index 0000000000..9a3eef5762 --- /dev/null +++ b/resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_ABS_High_Quality.inst.cfg @@ -0,0 +1,34 @@ +[general] +version = 4 +name = Extra Fine +definition = ultimaker_s5 + +[metadata] +setting_version = 5 +type = quality +quality_type = high +weight = 1 +material = generic_abs +variant = AA 0.4 +buildplate = Aluminum + +[values] +cool_min_speed = 12 +layer_height_0 = 0.17 +machine_nozzle_cool_down_speed = 0.8 +machine_nozzle_heat_up_speed = 1.5 +material_bed_temperature = 90 +material_bed_temperature_layer_0 = 100 +material_print_temperature = =default_material_print_temperature + 5 +material_initial_print_temperature = =material_print_temperature - 15 +material_final_print_temperature = =material_print_temperature - 20 +prime_blob_enable = False +prime_tower_enable = False +speed_print = 50 +speed_layer_0 = =math.ceil(speed_print * 20 / 50) +speed_topbottom = =math.ceil(speed_print * 30 / 50) +speed_wall = =math.ceil(speed_print * 30 / 50) + +infill_line_width = =round(line_width * 0.4 / 0.35, 2) +speed_infill = =math.ceil(speed_print * 40 / 50) + diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_CPEP_High_Quality.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_CPEP_High_Quality.inst.cfg new file mode 100644 index 0000000000..8b0b08f731 --- /dev/null +++ b/resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_CPEP_High_Quality.inst.cfg @@ -0,0 +1,53 @@ +[general] +version = 4 +name = Extra Fine +definition = ultimaker_s5 + +[metadata] +setting_version = 5 +type = quality +quality_type = high +weight = 1 +material = generic_cpe_plus +variant = AA 0.4 +buildplate = Aluminum + +[values] +acceleration_enabled = True +acceleration_print = 4000 +cool_fan_speed_max = 50 +cool_min_speed = 5 +infill_line_width = =round(line_width * 0.35 / 0.35, 2) +infill_overlap = 0 +infill_wipe_dist = 0 +jerk_enabled = True +jerk_print = 25 +layer_height_0 = 0.17 +machine_min_cool_heat_time_window = 15 +machine_nozzle_cool_down_speed = 0.85 +machine_nozzle_heat_up_speed = 1.5 +material_bed_temperature = 105 +material_bed_temperature_layer_0 = 115 +material_final_print_temperature = =material_print_temperature - 10 +material_initial_print_temperature = =material_print_temperature - 5 +material_print_temperature = =default_material_print_temperature + 2 +material_print_temperature_layer_0 = =material_print_temperature +multiple_mesh_overlap = 0 +prime_blob_enable = False +prime_tower_enable = True +prime_tower_wipe_enabled = True +retraction_combing = off +retraction_extrusion_window = 1 +retraction_hop = 0.2 +retraction_hop_enabled = False +retraction_hop_only_when_collides = True +skin_overlap = 20 +speed_layer_0 = =math.ceil(speed_print * 20 / 40) +speed_print = 40 +speed_topbottom = =math.ceil(speed_print * 30 / 35) + +speed_wall = =math.ceil(speed_print * 35 / 40) +speed_wall_0 = =math.ceil(speed_wall * 30 / 35) +support_bottom_distance = =support_z_distance +support_z_distance = =layer_height +wall_0_inset = 0 diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_CPE_High_Quality.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_CPE_High_Quality.inst.cfg new file mode 100644 index 0000000000..6299071194 --- /dev/null +++ b/resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_CPE_High_Quality.inst.cfg @@ -0,0 +1,32 @@ +[general] +version = 4 +name = Extra Fine +definition = ultimaker_s5 + +[metadata] +setting_version = 5 +type = quality +quality_type = high +weight = 1 +material = generic_cpe +variant = AA 0.4 +buildplate = Aluminum + +[values] +cool_min_speed = 12 +layer_height_0 = 0.17 +machine_nozzle_cool_down_speed = 0.85 +machine_nozzle_heat_up_speed = 1.5 +material_bed_temperature = 80 +material_bed_temperature_layer_0 = 90 +material_print_temperature = =default_material_print_temperature - 5 +material_initial_print_temperature = =material_print_temperature - 5 +material_final_print_temperature = =material_print_temperature - 10 +prime_blob_enable = False +speed_print = 50 +speed_layer_0 = =math.ceil(speed_print * 20 / 50) +speed_topbottom = =math.ceil(speed_print * 30 / 50) +speed_wall = =math.ceil(speed_print * 30 / 50) + +infill_pattern = zigzag +speed_infill = =math.ceil(speed_print * 40 / 50) \ No newline at end of file diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_PC_High_Quality.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_PC_High_Quality.inst.cfg new file mode 100644 index 0000000000..2afaf21de1 --- /dev/null +++ b/resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_PC_High_Quality.inst.cfg @@ -0,0 +1,71 @@ +[general] +version = 4 +name = Extra Fine +definition = ultimaker_s5 + +[metadata] +setting_version = 5 +type = quality +quality_type = high +weight = 1 +material = generic_pc +variant = AA 0.4 +buildplate = Aluminum + +[values] +acceleration_enabled = True +acceleration_print = 4000 +adhesion_type = brim +brim_width = 10 +cool_fan_full_at_height = =layer_height_0 + layer_height +cool_fan_speed_max = 50 +cool_min_layer_time_fan_speed_max = 5 +cool_min_speed = 8 +infill_line_width = =round(line_width * 0.4 / 0.35, 2) +infill_overlap = 0 +infill_overlap_mm = 0.05 +infill_pattern = triangles +infill_wipe_dist = 0.1 +jerk_enabled = True +jerk_print = 25 +layer_height_0 = 0.17 +machine_min_cool_heat_time_window = 15 +machine_nozzle_cool_down_speed = 0.85 +machine_nozzle_heat_up_speed = 1.5 +material_bed_temperature = 115 +material_bed_temperature_layer_0 = 125 +material_final_print_temperature = =material_print_temperature - 10 +material_initial_print_temperature = =material_print_temperature - 5 +material_print_temperature = =default_material_print_temperature - 10 +material_standby_temperature = 100 +multiple_mesh_overlap = 0 +ooze_shield_angle = 40 +prime_blob_enable = False +prime_tower_enable = True +prime_tower_wipe_enabled = True +raft_airgap = 0.25 +raft_interface_thickness = =max(layer_height * 1.5, 0.225) +retraction_count_max = 80 +retraction_extrusion_window = 1 +retraction_hop = 2 +retraction_hop_only_when_collides = True +retraction_min_travel = 0.8 +retraction_prime_speed = 15 +skin_overlap = 30 +speed_layer_0 = =math.ceil(speed_print * 25 / 50) +speed_print = 50 +speed_topbottom = =math.ceil(speed_print * 25 / 50) + +speed_wall = =math.ceil(speed_print * 40 / 50) +speed_wall_0 = =math.ceil(speed_wall * 25 / 40) +support_bottom_distance = =support_z_distance +support_interface_density = 87.5 +support_interface_pattern = lines +switch_extruder_prime_speed = 15 +switch_extruder_retraction_amount = 20 +switch_extruder_retraction_speeds = 35 +wall_0_inset = 0 +wall_line_width_x = =round(line_width * 0.4 / 0.35, 2) +wall_thickness = 1.2 + + diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_ABS_Draft_Print.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_ABS_Draft_Print.inst.cfg new file mode 100644 index 0000000000..3d984e1dff --- /dev/null +++ b/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_ABS_Draft_Print.inst.cfg @@ -0,0 +1,27 @@ +[general] +version = 4 +name = Fast +definition = ultimaker_s5 + +[metadata] +setting_version = 5 +type = quality +quality_type = draft +weight = -2 +material = generic_abs +variant = AA 0.8 +buildplate = Aluminum + +[values] +layer_height_0 = 0.3 +line_width = =machine_nozzle_size * 0.875 +material_bed_temperature = 90 +material_bed_temperature_layer_0 = 100 +material_print_temperature = =default_material_print_temperature + 20 +material_standby_temperature = 100 +prime_blob_enable = False +speed_print = 50 +speed_topbottom = =math.ceil(speed_print * 30 / 50) +speed_wall = =math.ceil(speed_print * 40 / 50) +speed_wall_0 = =math.ceil(speed_wall * 30 / 40) +retract_at_layer_change = False diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_CPEP_Fast_Print.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_CPEP_Fast_Print.inst.cfg new file mode 100644 index 0000000000..6fd60c197a --- /dev/null +++ b/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_CPEP_Fast_Print.inst.cfg @@ -0,0 +1,43 @@ +[general] +version = 4 +name = Fast - Experimental +definition = ultimaker_s5 + +[metadata] +setting_version = 5 +type = quality +quality_type = draft +weight = -2 +material = generic_cpe_plus +variant = AA 0.8 +buildplate = Aluminum + +[values] +brim_width = 14 +cool_fan_full_at_height = =layer_height_0 + 14 * layer_height +infill_before_walls = True +layer_height_0 = 0.3 +line_width = =machine_nozzle_size * 0.9375 +machine_nozzle_cool_down_speed = 0.9 +machine_nozzle_heat_up_speed = 1.4 +material_bed_temperature = 105 +material_bed_temperature_layer_0 = 115 +material_print_temperature = =default_material_print_temperature - 10 +material_print_temperature_layer_0 = =material_print_temperature +material_standby_temperature = 100 +prime_blob_enable = False +prime_tower_enable = True +retraction_combing = off +retraction_hop = 0.1 +retraction_hop_enabled = False +skin_overlap = 0 +speed_layer_0 = =math.ceil(speed_print * 15 / 50) +speed_print = 50 +speed_slowdown_layers = 15 +speed_topbottom = =math.ceil(speed_print * 35 / 50) +speed_wall = =math.ceil(speed_print * 40 / 50) +speed_wall_0 = =math.ceil(speed_wall * 35 / 40) +support_bottom_distance = =support_z_distance +support_line_width = =round(line_width * 0.6 / 0.7, 2) +support_z_distance = =layer_height +top_bottom_thickness = 1.2 diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_CPE_Draft_Print.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_CPE_Draft_Print.inst.cfg new file mode 100644 index 0000000000..37aa25f9d8 --- /dev/null +++ b/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_CPE_Draft_Print.inst.cfg @@ -0,0 +1,29 @@ +[general] +version = 4 +name = Fast +definition = ultimaker_s5 + +[metadata] +setting_version = 5 +type = quality +quality_type = draft +weight = -2 +material = generic_cpe +variant = AA 0.8 +buildplate = Aluminum + +[values] +brim_width = 15 +layer_height_0 = 0.3 +line_width = =machine_nozzle_size * 0.875 +material_bed_temperature = 80 +material_bed_temperature_layer_0 = 90 +material_print_temperature = =default_material_print_temperature + 15 +material_standby_temperature = 100 +prime_blob_enable = False +prime_tower_enable = True +speed_print = 40 +speed_topbottom = =math.ceil(speed_print * 25 / 40) +speed_wall = =math.ceil(speed_print * 30 / 40) + +jerk_travel = 50 \ No newline at end of file diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_PC_Fast_Print.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_PC_Fast_Print.inst.cfg new file mode 100644 index 0000000000..9ebf2ea151 --- /dev/null +++ b/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_PC_Fast_Print.inst.cfg @@ -0,0 +1,36 @@ +[general] +version = 4 +name = Fast - Experimental +definition = ultimaker_s5 + +[metadata] +setting_version = 5 +type = quality +quality_type = draft +weight = 0 +material = generic_pc +variant = AA 0.8 +buildplate = Aluminum + +[values] +brim_width = 10 +cool_fan_full_at_height = =layer_height_0 + 14 * layer_height +infill_before_walls = True +layer_height_0 = 0.3 +line_width = =machine_nozzle_size * 0.875 +material_bed_temperature = 115 +material_bed_temperature_layer_0 = 125 +material_print_temperature = =default_material_print_temperature - 5 +material_print_temperature_layer_0 = =material_print_temperature +material_standby_temperature = 100 +prime_blob_enable = False +raft_airgap = 0.5 +raft_margin = 15 +skin_overlap = 0 +speed_layer_0 = =math.ceil(speed_print * 15 / 50) +speed_print = 50 +speed_slowdown_layers = 15 +speed_topbottom = =math.ceil(speed_print * 25 / 50) +speed_wall = =math.ceil(speed_print * 40 / 50) +speed_wall_0 = =math.ceil(speed_wall * 30 / 40) +support_line_width = =round(line_width * 0.6 / 0.7, 2) diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_PP_Draft_Print.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_PP_Draft_Print.inst.cfg new file mode 100644 index 0000000000..94bede16bd --- /dev/null +++ b/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_PP_Draft_Print.inst.cfg @@ -0,0 +1,53 @@ +[general] +version = 4 +name = Fast +definition = ultimaker_s5 + +[metadata] +setting_version = 5 +type = quality +quality_type = draft +weight = -2 +material = generic_pp +variant = AA 0.8 +buildplate = Aluminum + +[values] +brim_width = 25 +cool_min_layer_time_fan_speed_max = 6 +cool_min_speed = 17 +top_skin_expand_distance = =line_width * 2 +infill_before_walls = True +infill_line_width = =round(line_width * 0.7 / 0.8, 2) +infill_pattern = tetrahedral +jerk_prime_tower = =math.ceil(jerk_print * 25 / 25) +jerk_support = =math.ceil(jerk_print * 25 / 25) +jerk_wall_0 = =math.ceil(jerk_wall * 15 / 25) +material_bed_temperature_layer_0 = =material_bed_temperature + 5 +material_print_temperature = =default_material_print_temperature - 2 +material_print_temperature_layer_0 = =default_material_print_temperature + 2 +material_standby_temperature = 100 +multiple_mesh_overlap = 0.2 +prime_tower_enable = True +prime_tower_flow = 100 +prime_tower_min_volume = 10 +retract_at_layer_change = False +retraction_count_max = 12 +retraction_extra_prime_amount = 0.5 +retraction_hop = 0.5 +retraction_min_travel = 1.5 +retraction_prime_speed = 15 +skin_line_width = =round(line_width * 0.78 / 0.8, 2) + +speed_wall_x = =math.ceil(speed_wall * 30 / 30) +support_bottom_distance = =support_z_distance +support_line_width = =round(line_width * 0.7 / 0.8, 2) +support_offset = =line_width +switch_extruder_prime_speed = 15 +switch_extruder_retraction_amount = 20 +switch_extruder_retraction_speeds = 45 +top_bottom_thickness = 1.6 +travel_compensate_overlapping_walls_0_enabled = False +wall_0_wipe_dist = =line_width * 2 +wall_line_width_x = =round(line_width * 0.8 / 0.8, 2) +wall_thickness = 1.6 From a7448e4970e9f1e72a82035dc12f2814e4437801 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 3 Aug 2018 09:59:23 +0200 Subject: [PATCH 05/13] Refactor material lookup tree creation --- cura/Machines/MaterialManager.py | 137 ++++++++++++++++--------------- 1 file changed, 69 insertions(+), 68 deletions(-) diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py index 86f7dea81f..3c9a7bfd59 100644 --- a/cura/Machines/MaterialManager.py +++ b/cura/Machines/MaterialManager.py @@ -192,74 +192,75 @@ class MaterialManager(QObject): # "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 - - 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() - - machine_node = machine_nozzle_buildplate_material_map[definition] - nozzle_name = material_metadata.get("variant_name") - buildplate_name = material_metadata.get("buildplate_name") - if not nozzle_name: - # if there is no nozzle, 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 nozzle-specific, so we save it in a nozzle-specific node under the - # machine-specific node - - # Check first if the nozzle exists in the manager - existing_nozzle = variant_manager.getVariantNode(definition, nozzle_name, VariantType.NOZZLE) - if existing_nozzle is not None: - if nozzle_name not in machine_node.children_map: - machine_node.children_map[nozzle_name] = MaterialNode() - - nozzle_node = machine_node.children_map[nozzle_name] - - # Check build plate node - if not buildplate_name: - # if there is no buildplate, this material is for the machine, so put its metadata in the machine node. - if root_material_id in nozzle_node.material_map: # We shouldn't have duplicated nozzle-specific materials for the same machine. - ConfigurationErrorMessage.getInstance().addFaultyContainers(root_material_id) - continue - nozzle_node.material_map[root_material_id] = MaterialNode(material_metadata) - else: - # this material is nozzle-and-buildplate-specific, so we save it in a buildplate-specific node - # under the machine-specific node - - # Check first if the buildplate exists in the manager - existing_buildplate = variant_manager.getVariantNode(definition, buildplate_name, VariantType.BUILD_PLATE) - if existing_buildplate is not None: - if buildplate_name not in nozzle_node.children_map: - nozzle_node.children_map[buildplate_name] = MaterialNode() - - buildplate_node = nozzle_node.children_map[buildplate_name] - if root_material_id in buildplate_node.material_map: # We shouldn't have duplicated nozzle-and-buildplate-specific materials for the same machine. - ConfigurationErrorMessage.getInstance().addFaultyContainers(root_material_id) - continue - buildplate_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 buildplate does not exist.".format(id = material_metadata["id"])) - self._container_registry.addWrongContainerId(material_metadata["id"]) - - 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 nozzle does not exist.".format(id = material_metadata["id"])) - self._container_registry.addWrongContainerId(material_metadata["id"]) + self.__addMaterialMetadataIntoLookupTree(material_metadata) self.materialsUpdated.emit() + def __addMaterialMetadataIntoLookupTree(self, material_metadata: dict) -> None: + material_id = material_metadata["id"] + + # We don't store empty material in the lookup tables + if material_id == "empty_material": + return + + 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 + + # 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 ...") self.initialize() @@ -290,7 +291,7 @@ 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_nozzle_name: Optional[str], + 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)) @@ -306,8 +307,8 @@ class MaterialManager(QObject): default_machine_node = machine_nozzle_buildplate_material_map.get(self._default_machine_definition_id) nozzle_node = None buildplate_node = None - if extruder_nozzle_name is not None and machine_node is not None: - nozzle_node = machine_node.getChildNode(extruder_nozzle_name) + 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) From e2a97e3a012287ad8b50affc7692149e10b8669c Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 3 Aug 2018 10:28:21 +0200 Subject: [PATCH 06/13] Refactor quality lookup table creation --- cura/Machines/QualityManager.py | 50 +++++++++------------------------ 1 file changed, 13 insertions(+), 37 deletions(-) diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py index 273f1ae11f..263b3a9620 100644 --- a/cura/Machines/QualityManager.py +++ b/cura/Machines/QualityManager.py @@ -98,45 +98,22 @@ class QualityManager(QObject): machine_node.addQualityMetadata(quality_type, metadata) continue - # Check if nozzle si specified - if nozzle_name is not None: - if nozzle_name not in machine_node.children_map: - machine_node.children_map[nozzle_name] = QualityNode() - nozzle_node = cast(QualityNode, machine_node.children_map[nozzle_name]) + current_node = machine_node + intermediate_node_info_list = [nozzle_name, buildplate_name, root_material_id] + current_intermediate_node_info_idx = 0 - # Check if buildplate is specified - if buildplate_name is not None: - if buildplate_name not in nozzle_node.children_map: - nozzle_node.children_map[buildplate_name] = QualityNode() - buildplate_node = cast(QualityNode, nozzle_node.children_map[buildplate_name]) + 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]) - if root_material_id is None: - buildplate_node.addQualityMetadata(quality_type, metadata) - else: - if root_material_id not in buildplate_node.children_map: - buildplate_node.children_map[root_material_id] = QualityNode() - material_node = cast(QualityNode, buildplate_node.children_map[root_material_id]) + current_intermediate_node_info_idx += 1 - material_node.addQualityMetadata(quality_type, metadata) - - else: - if root_material_id is None: - nozzle_node.addQualityMetadata(quality_type, metadata) - else: - if root_material_id not in nozzle_node.children_map: - nozzle_node.children_map[root_material_id] = QualityNode() - material_node = cast(QualityNode, nozzle_node.children_map[root_material_id]) - - material_node.addQualityMetadata(quality_type, metadata) - - else: - # If nozzle 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.addQualityMetadata(quality_type, metadata) # Initialize the lookup tree for quality_changes profiles with following structure: # -> -> @@ -337,7 +314,6 @@ class QualityManager(QObject): quality_group = quality_group_dict[quality_type] if position not in quality_group.nodes_for_extruders: quality_group.nodes_for_extruders[position] = quality_node - #break # Update availabilities for each quality group self._updateQualityGroupsAvailability(machine, quality_group_dict.values()) From 206a32ae5011f7c703a34c542a3eb84ddae7c067 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 3 Aug 2018 10:41:36 +0200 Subject: [PATCH 07/13] Refactor quality lookup --- cura/Machines/QualityManager.py | 45 ++++++++++++++------------------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py index 263b3a9620..c0954c005c 100644 --- a/cura/Machines/QualityManager.py +++ b/cura/Machines/QualityManager.py @@ -263,38 +263,31 @@ class QualityManager(QObject): # 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] + current_node_info_idx = 0 nodes_to_check = [] - if nozzle_name: - # In this case, we have both a specific nozzle and a specific material - nozzle_node = machine_node.getChildNode(nozzle_name) - if nozzle_node and has_material: - # Check build plate if exists - if buildplate_name: - buildplate_node = nozzle_node.getChildNode(buildplate_name) - if buildplate_node and has_material: - for root_material_id in root_material_id_list: - material_node = buildplate_node.getChildNode(root_material_id) - if material_node: - nodes_to_check.append(material_node) - break - nodes_to_check.append(buildplate_node) + # 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 current_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) - # Then add nozzle specific materials - for root_material_id in root_material_id_list: - material_node = nozzle_node.getChildNode(root_material_id) + 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(nozzle_node) - # In this case, we only have a specific material but NOT a nozzle - 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: From 5eb9ae9177cf8f114efd6eb7b8773922a85dcbc1 Mon Sep 17 00:00:00 2001 From: Aleksei S Date: Mon, 6 Aug 2018 14:32:59 +0200 Subject: [PATCH 08/13] removed unnecessary variable --- cura/Machines/MaterialManager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py index 3c9a7bfd59..7c5c901b32 100644 --- a/cura/Machines/MaterialManager.py +++ b/cura/Machines/MaterialManager.py @@ -186,7 +186,6 @@ class MaterialManager(QObject): for root_material_id in data_dict.values(): self._diameter_material_map[root_material_id] = default_root_material_id - variant_manager = self._application.getVariantManager() # Map #4 # "machine" -> "nozzle name" -> "buildplate name" -> "root material ID" -> specific material InstanceContainer From 57f04d8940876b6a4b46a4569dc501272c2c90b3 Mon Sep 17 00:00:00 2001 From: Aleksei S Date: Thu, 9 Aug 2018 15:39:10 +0200 Subject: [PATCH 09/13] Load 3mf projects CURA-5570 --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 389a7da704..e33b88375e 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -928,12 +928,16 @@ class ThreeMFWorkspaceReader(WorkspaceReader): root_material_id = extruder_info.root_material_id root_material_id = self._old_new_materials.get(root_material_id, root_material_id) + build_plate_id = global_stack.variant.getId() + # get material diameter of this extruder machine_material_diameter = extruder_stack.materialDiameter material_node = material_manager.getMaterialNode(global_stack.definition.getId(), extruder_stack.variant.getName(), + build_plate_id, machine_material_diameter, root_material_id) + if material_node is not None and material_node.getContainer() is not None: extruder_stack.material = material_node.getContainer() From 033a00ec20bc952b981af97384700240617db7e7 Mon Sep 17 00:00:00 2001 From: Aleksei S Date: Thu, 9 Aug 2018 16:06:09 +0200 Subject: [PATCH 10/13] fixed addNodesToCheck function, wrong max index value CURA-5570 --- cura/Machines/QualityManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py index c0954c005c..5496ca9f87 100644 --- a/cura/Machines/QualityManager.py +++ b/cura/Machines/QualityManager.py @@ -271,7 +271,7 @@ class QualityManager(QObject): # 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 current_node_info_idx < len(node_info_list): + 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) From 825a3cd08fc5b796cadf6bea10442f73492d545d Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Mon, 13 Aug 2018 09:55:57 +0200 Subject: [PATCH 11/13] CURA-5570 Fix the configuration sync feature. It crashed Cura because it was using an outdated signature of the method getMaterialNodeByType. --- cura/Machines/MaterialManager.py | 1 - cura/Machines/QualityManager.py | 1 - cura/Settings/MachineManager.py | 7 ++++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py index 7c5c901b32..d5a7d5d089 100644 --- a/cura/Machines/MaterialManager.py +++ b/cura/Machines/MaterialManager.py @@ -186,7 +186,6 @@ class MaterialManager(QObject): for root_material_id in data_dict.values(): self._diameter_material_map[root_material_id] = default_root_material_id - # Map #4 # "machine" -> "nozzle name" -> "buildplate name" -> "root material ID" -> specific material InstanceContainer self._diameter_machine_nozzle_buildplate_material_map = dict() diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py index 5496ca9f87..8b4a396164 100644 --- a/cura/Machines/QualityManager.py +++ b/cura/Machines/QualityManager.py @@ -264,7 +264,6 @@ class QualityManager(QObject): # 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] - current_node_info_idx = 0 nodes_to_check = [] # This function tries to recursively find the deepest (the most specific) branch and add those nodes to diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 733340e0ce..2f81f78c95 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -1331,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: From 98a0bb50025ba71739834a82ebebf48340ea39df Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Mon, 13 Aug 2018 11:15:32 +0200 Subject: [PATCH 12/13] CURA-5570 Remove a check that caused empty list of profiles. --- cura/Machines/QualityManager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py index 8b4a396164..df3bec0827 100644 --- a/cura/Machines/QualityManager.py +++ b/cura/Machines/QualityManager.py @@ -238,6 +238,7 @@ class QualityManager(QObject): # 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") @@ -248,7 +249,7 @@ class QualityManager(QObject): # Also try to get the fallback material material_type = extruder.material.getMetaDataEntry("material") fallback_root_material_id = self._material_manager.getFallbackMaterialIdByMaterialType(material_type) - if fallback_root_material_id and root_material_id not in root_material_id_list: + if fallback_root_material_id: root_material_id_list.append(fallback_root_material_id) # Here we construct a list of nodes we want to look for qualities with the highest priority first. From b1c073aab63ba5869b0cc9d6efad05ef09d15dcd Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Mon, 13 Aug 2018 11:50:50 +0200 Subject: [PATCH 13/13] CURA-5570 Expose the CuraSDKVersion to qml and show the buildplate selector only when "dev" version is selected. --- cura/CuraApplication.py | 4 +++- resources/qml/Cura.qml | 4 ++-- resources/qml/SidebarHeader.qml | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 6164fc8756..a2c4f6ae4c 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -117,11 +117,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): @@ -910,6 +911,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") diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index e1bf8c8b77..60f6e77ea9 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -231,8 +231,8 @@ UM.MainWindow onObjectRemoved: settingsMenu.removeItem(object) } - // TODO Temporary hidden, add back again when feature ready - BuildplateMenu { title: catalog.i18nc("@title:menu", "&Build plate"); visible: Cura.MachineManager.hasVariantBuildplates } + // TODO Only show in dev mode. Remove check when feature ready + BuildplateMenu { title: catalog.i18nc("@title:menu", "&Build plate"); visible: CuraSDKVersion == "dev" ? Cura.MachineManager.hasVariantBuildplates : false } ProfileMenu { title: catalog.i18nc("@title:menu", "&Profile"); } MenuSeparator { } diff --git a/resources/qml/SidebarHeader.qml b/resources/qml/SidebarHeader.qml index 4e8911b3c1..4bc0e8b2ef 100644 --- a/resources/qml/SidebarHeader.qml +++ b/resources/qml/SidebarHeader.qml @@ -476,8 +476,8 @@ Column { id: buildplateRow height: UM.Theme.getSize("sidebar_setup").height - // TODO Temporary hidden, add back again when feature ready - visible: false //Cura.MachineManager.hasVariantBuildplates && !sidebar.hideSettings + // TODO Only show in dev mode. Remove check when feature ready + visible: CuraSDKVersion == "dev" ? Cura.MachineManager.hasVariantBuildplates && !sidebar.hideSettings : false anchors {