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

CURA-5570 More decision points in material and quality lookup!
This commit is contained in:
Diego Prado Gesto 2018-08-13 12:06:29 +02:00 committed by GitHub
commit 152005ab14
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 790 additions and 241 deletions

View File

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

View File

@ -4,8 +4,7 @@
from collections import defaultdict, OrderedDict from collections import defaultdict, OrderedDict
import copy import copy
import uuid import uuid
from typing import Dict, cast from typing import Dict, Optional, TYPE_CHECKING
from typing import Optional, TYPE_CHECKING
from PyQt5.Qt import QTimer, QObject, pyqtSignal, pyqtSlot from PyQt5.Qt import QTimer, QObject, pyqtSignal, pyqtSlot
@ -18,6 +17,7 @@ from UM.Util import parseBool
from .MaterialNode import MaterialNode from .MaterialNode import MaterialNode
from .MaterialGroup import MaterialGroup from .MaterialGroup import MaterialGroup
from .VariantType import VariantType
if TYPE_CHECKING: if TYPE_CHECKING:
from UM.Settings.DefinitionContainer import DefinitionContainer from UM.Settings.DefinitionContainer import DefinitionContainer
@ -47,7 +47,7 @@ class MaterialManager(QObject):
self._fallback_materials_map = dict() # material_type -> generic material metadata self._fallback_materials_map = dict() # material_type -> generic material metadata
self._material_group_map = dict() # root_material_id -> MaterialGroup 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 # 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 # because the generic material ids are used in qualities and definitions, while the specific diameter material is meant
@ -187,52 +187,78 @@ class MaterialManager(QObject):
self._diameter_material_map[root_material_id] = default_root_material_id self._diameter_material_map[root_material_id] = default_root_material_id
# Map #4 # Map #4
# "machine" -> "variant_name" -> "root material ID" -> specific material InstanceContainer # "machine" -> "nozzle name" -> "buildplate name" -> "root material ID" -> specific material InstanceContainer
# Construct the "machine" -> "variant" -> "root material ID" -> specific material InstanceContainer self._diameter_machine_nozzle_buildplate_material_map = dict()
self._diameter_machine_variant_material_map = dict()
for material_metadata in material_metadatas.values(): for material_metadata in material_metadatas.values():
# We don't store empty material in the lookup tables self.__addMaterialMetadataIntoLookupTree(material_metadata)
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_variant_material_map:
self._diameter_machine_variant_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_node = machine_variant_material_map[definition]
variant_name = material_metadata.get("variant_name")
if not variant_name:
# if there is no variant, this material is for the machine, so put its metadata in the machine node.
machine_node.material_map[root_material_id] = MaterialNode(material_metadata)
else:
# this material is variant-specific, so we save it in a variant-specific node under the
# machine-specific node
# Check first if the variant exist in the manager
existing_variant = self._application.getVariantManager().getVariantNode(definition, variant_name)
if existing_variant is not None:
if variant_name not in machine_node.children_map:
machine_node.children_map[variant_name] = MaterialNode()
variant_node = machine_node.children_map[variant_name]
if root_material_id in variant_node.material_map: # We shouldn't have duplicated variant-specific materials for the same machine.
ConfigurationErrorMessage.getInstance().addFaultyContainers(root_material_id)
continue
variant_node.material_map[root_material_id] = MaterialNode(material_metadata)
else:
# Add this container id to the wrong containers list in the registry
Logger.log("w", "Not adding {id} to the material manager because the variant does not exist.".format(id = material_metadata["id"]))
self._container_registry.addWrongContainerId(material_metadata["id"])
self.materialsUpdated.emit() 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): def _updateMaps(self):
Logger.log("i", "Updating material lookup data ...") Logger.log("i", "Updating material lookup data ...")
self.initialize() self.initialize()
@ -263,45 +289,52 @@ class MaterialManager(QObject):
# #
# Return a dict with all root material IDs (k) and ContainerNodes (v) that's suitable for the given setup. # 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], def getAvailableMaterials(self, machine_definition: "DefinitionContainer", nozzle_name: Optional[str],
diameter: float) -> Dict[str, MaterialNode]: buildplate_name: Optional[str], diameter: float) -> Dict[str, MaterialNode]:
# round the diameter to get the approximate diameter # round the diameter to get the approximate diameter
rounded_diameter = str(round(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) Logger.log("i", "Cannot find materials with diameter [%s] (rounded to [%s])", diameter, rounded_diameter)
return dict() return dict()
machine_definition_id = machine_definition.getId() machine_definition_id = machine_definition.getId()
# If there are variant materials, get the variant material # If there are nozzle-and-or-buildplate materials, get the nozzle-and-or-buildplate material
machine_variant_material_map = self._diameter_machine_variant_material_map[rounded_diameter] machine_nozzle_buildplate_material_map = self._diameter_machine_nozzle_buildplate_material_map[rounded_diameter]
machine_node = machine_variant_material_map.get(machine_definition_id) machine_node = machine_nozzle_buildplate_material_map.get(machine_definition_id)
default_machine_node = machine_variant_material_map.get(self._default_machine_definition_id) default_machine_node = machine_nozzle_buildplate_material_map.get(self._default_machine_definition_id)
variant_node = None nozzle_node = None
if extruder_variant_name is not None and machine_node is not None: buildplate_node = None
variant_node = machine_node.getChildNode(extruder_variant_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)
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: # Fallback mechanism of finding materials:
# 1. variant-specific material # 1. buildplate-specific material
# 2. machine-specific material # 2. nozzle-specific material
# 3. generic material (for fdmprinter) # 3. machine-specific material
# 4. generic material (for fdmprinter)
machine_exclude_materials = machine_definition.getMetaDataEntry("exclude_materials", []) machine_exclude_materials = machine_definition.getMetaDataEntry("exclude_materials", [])
material_id_metadata_dict = dict() # type: Dict[str, MaterialNode] material_id_metadata_dict = dict() # type: Dict[str, MaterialNode]
for node in nodes_to_check: for current_node in nodes_to_check:
if node is not None: if current_node is None:
# Only exclude the materials that are explicitly specified in the "exclude_materials" field. continue
# 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
if material_id not in material_id_metadata_dict: # Only exclude the materials that are explicitly specified in the "exclude_materials" field.
material_id_metadata_dict[material_id] = node # 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 return material_id_metadata_dict
@ -310,13 +343,14 @@ class MaterialManager(QObject):
# #
def getAvailableMaterialsForMachineExtruder(self, machine: "GlobalStack", def getAvailableMaterialsForMachineExtruder(self, machine: "GlobalStack",
extruder_stack: "ExtruderStack") -> Optional[dict]: extruder_stack: "ExtruderStack") -> Optional[dict]:
variant_name = None buildplate_name = machine.getBuildplateName()
nozzle_name = None
if extruder_stack.variant.getId() != "empty_variant": if extruder_stack.variant.getId() != "empty_variant":
variant_name = extruder_stack.variant.getName() nozzle_name = extruder_stack.variant.getName()
diameter = extruder_stack.approximateMaterialDiameter diameter = extruder_stack.approximateMaterialDiameter
# Fetch the available materials (ContainerNode) for the current active machine and extruder setup. # 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. # Gets MaterialNode for the given extruder and machine with the given material name.
@ -324,32 +358,36 @@ class MaterialManager(QObject):
# 1. the given machine doesn't have materials; # 1. the given machine doesn't have materials;
# 2. cannot find any material InstanceContainers with the given settings. # 2. cannot find any material InstanceContainers with the given settings.
# #
def getMaterialNode(self, machine_definition_id: str, extruder_variant_name: Optional[str], def getMaterialNode(self, machine_definition_id: str, nozzle_name: Optional[str],
diameter: float, root_material_id: str) -> Optional["InstanceContainer"]: buildplate_name: Optional[str], diameter: float, root_material_id: str) -> Optional["InstanceContainer"]:
# round the diameter to get the approximate diameter # round the diameter to get the approximate diameter
rounded_diameter = str(round(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]", Logger.log("i", "Cannot find materials with diameter [%s] (rounded to [%s]) for root material id [%s]",
diameter, rounded_diameter, root_material_id) diameter, rounded_diameter, root_material_id)
return None return None
# If there are variant materials, get the variant material # If there are nozzle materials, get the nozzle-specific material
machine_variant_material_map = self._diameter_machine_variant_material_map[rounded_diameter] machine_nozzle_buildplate_material_map = self._diameter_machine_nozzle_buildplate_material_map[rounded_diameter]
machine_node = machine_variant_material_map.get(machine_definition_id) machine_node = machine_nozzle_buildplate_material_map.get(machine_definition_id)
variant_node = None nozzle_node = None
buildplate_node = None
# Fallback for "fdmprinter" if the machine-specific materials cannot be found # Fallback for "fdmprinter" if the machine-specific materials cannot be found
if machine_node is None: if machine_node is None:
machine_node = machine_variant_material_map.get(self._default_machine_definition_id) machine_node = machine_nozzle_buildplate_material_map.get(self._default_machine_definition_id)
if machine_node is not None and extruder_variant_name is not None: if machine_node is not None and nozzle_name is not None:
variant_node = machine_node.getChildNode(extruder_variant_name) 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: # Fallback mechanism of finding materials:
# 1. variant-specific material # 1. buildplate-specific material
# 2. machine-specific material # 2. nozzle-specific material
# 3. generic material (for fdmprinter) # 3. machine-specific material
nodes_to_check = [variant_node, machine_node, # 4. generic material (for fdmprinter)
machine_variant_material_map.get(self._default_machine_definition_id)] nodes_to_check = [buildplate_node, nozzle_node, machine_node,
machine_nozzle_buildplate_material_map.get(self._default_machine_definition_id)]
material_node = None material_node = None
for node in nodes_to_check: for node in nodes_to_check:
@ -366,7 +404,8 @@ class MaterialManager(QObject):
# 1. the given machine doesn't have materials; # 1. the given machine doesn't have materials;
# 2. cannot find any material InstanceContainers with the given settings. # 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 node = None
machine_definition = global_stack.definition machine_definition = global_stack.definition
extruder_definition = global_stack.extruders[position].definition extruder_definition = global_stack.extruders[position].definition
@ -385,7 +424,7 @@ class MaterialManager(QObject):
Logger.log("i", "Cannot find materials with guid [%s] ", material_guid) Logger.log("i", "Cannot find materials with guid [%s] ", material_guid)
return None 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) material_diameter, root_material_id)
return node return node
@ -413,13 +452,17 @@ class MaterialManager(QObject):
else: else:
return None 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) # 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 node = None
buildplate_name = global_stack.getBuildplateName()
machine_definition = global_stack.definition machine_definition = global_stack.definition
if extruder_definition is None: if extruder_definition is None:
extruder_definition = global_stack.extruders[position].definition extruder_definition = global_stack.extruders[position].definition
if extruder_definition and parseBool(global_stack.getMetaDataEntry("has_materials", False)): if extruder_definition and parseBool(global_stack.getMetaDataEntry("has_materials", False)):
# At this point the extruder_definition is not None # At this point the extruder_definition is not None
material_diameter = extruder_definition.getProperty("material_diameter", "value") material_diameter = extruder_definition.getProperty("material_diameter", "value")
@ -428,7 +471,7 @@ class MaterialManager(QObject):
approximate_material_diameter = str(round(material_diameter)) approximate_material_diameter = str(round(material_diameter))
root_material_id = machine_definition.getMetaDataEntry("preferred_material") root_material_id = machine_definition.getMetaDataEntry("preferred_material")
root_material_id = self.getRootMaterialIDForDiameter(root_material_id, approximate_material_diameter) 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) material_diameter, root_material_id)
return node return node
@ -515,8 +558,8 @@ class MaterialManager(QObject):
if container_to_copy.getMetaDataEntry("definition") != "fdmprinter": if container_to_copy.getMetaDataEntry("definition") != "fdmprinter":
new_id += "_" + container_to_copy.getMetaDataEntry("definition") new_id += "_" + container_to_copy.getMetaDataEntry("definition")
if container_to_copy.getMetaDataEntry("variant_name"): if container_to_copy.getMetaDataEntry("variant_name"):
variant_name = container_to_copy.getMetaDataEntry("variant_name") nozzle_name = container_to_copy.getMetaDataEntry("variant_name")
new_id += "_" + variant_name.replace(" ", "_") new_id += "_" + nozzle_name.replace(" ", "_")
new_container = copy.deepcopy(container_to_copy) new_container = copy.deepcopy(container_to_copy)
new_container.getMetaData()["id"] = new_id new_container.getMetaData()["id"] = new_id

View File

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

View File

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

View File

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

View File

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

View File

@ -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"]

View File

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

View File

@ -55,6 +55,12 @@ class GlobalStack(CuraContainerStack):
return "machine_stack" return "machine_stack"
return configuration_type 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. ## Add an extruder to the list of extruders of this stack.
# #
# \param extruder The extruder to add. # \param extruder The extruder to add.

View File

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

View File

@ -24,6 +24,7 @@ from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
from UM.Job import Job from UM.Job import Job
from UM.Preferences import Preferences from UM.Preferences import Preferences
from cura.Machines.VariantType import VariantType
from cura.Settings.CuraStackBuilder import CuraStackBuilder from cura.Settings.CuraStackBuilder import CuraStackBuilder
from cura.Settings.ExtruderStack import ExtruderStack from cura.Settings.ExtruderStack import ExtruderStack
from cura.Settings.GlobalStack import GlobalStack from cura.Settings.GlobalStack import GlobalStack
@ -889,7 +890,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
parser = self._machine_info.variant_info.parser parser = self._machine_info.variant_info.parser
variant_name = parser["general"]["name"] variant_name = parser["general"]["name"]
from cura.Machines.VariantManager import VariantType
variant_type = VariantType.BUILD_PLATE variant_type = VariantType.BUILD_PLATE
node = variant_manager.getVariantNode(global_stack.definition.getId(), variant_name, variant_type) 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 parser = extruder_info.variant_info.parser
variant_name = parser["general"]["name"] variant_name = parser["general"]["name"]
from cura.Machines.VariantManager import VariantType
variant_type = VariantType.NOZZLE variant_type = VariantType.NOZZLE
node = variant_manager.getVariantNode(global_stack.definition.getId(), variant_name, variant_type) node = variant_manager.getVariantNode(global_stack.definition.getId(), variant_name, variant_type)
@ -929,12 +928,16 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
root_material_id = extruder_info.root_material_id root_material_id = extruder_info.root_material_id
root_material_id = self._old_new_materials.get(root_material_id, 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 # get material diameter of this extruder
machine_material_diameter = extruder_stack.materialDiameter machine_material_diameter = extruder_stack.materialDiameter
material_node = material_manager.getMaterialNode(global_stack.definition.getId(), material_node = material_manager.getMaterialNode(global_stack.definition.getId(),
extruder_stack.variant.getName(), extruder_stack.variant.getName(),
build_plate_id,
machine_material_diameter, machine_material_diameter,
root_material_id) root_material_id)
if material_node is not None and material_node.getContainer() is not None: if material_node is not None and material_node.getContainer() is not None:
extruder_stack.material = material_node.getContainer() extruder_stack.material = material_node.getContainer()

View File

@ -6,21 +6,22 @@ import io
import json #To parse the product-to-id mapping file. import json #To parse the product-to-id mapping file.
import os.path #To find the product-to-id mapping. import os.path #To find the product-to-id mapping.
import sys 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 import xml.etree.ElementTree as ET
from typing import Dict
from typing import Iterator
from UM.Resources import Resources from UM.Resources import Resources
from UM.Logger import Logger from UM.Logger import Logger
from cura.CuraApplication import CuraApplication
import UM.Dictionary import UM.Dictionary
from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
from cura.CuraApplication import CuraApplication
from cura.Machines.VariantType import VariantType
from .XmlMaterialValidator import XmlMaterialValidator from .XmlMaterialValidator import XmlMaterialValidator
## Handles serializing and deserializing material containers from an XML file ## Handles serializing and deserializing material containers from an XML file
class XmlMaterialProfile(InstanceContainer): class XmlMaterialProfile(InstanceContainer):
CurrentFdmMaterialVersion = "1.3" CurrentFdmMaterialVersion = "1.3"
@ -269,7 +270,6 @@ class XmlMaterialProfile(InstanceContainer):
buildplate_dict = {} # type: Dict[str, Any] buildplate_dict = {} # type: Dict[str, Any]
for variant_name, variant_dict in machine_variant_map[definition_id].items(): for variant_name, variant_dict in machine_variant_map[definition_id].items():
variant_type = variant_dict["variant_node"].metadata["hardware_type"] variant_type = variant_dict["variant_node"].metadata["hardware_type"]
from cura.Machines.VariantManager import VariantType
variant_type = VariantType(variant_type) variant_type = VariantType(variant_type)
if variant_type == VariantType.NOZZLE: if variant_type == VariantType.NOZZLE:
# The hotend identifier is not the containers name, but its "name". # The hotend identifier is not the containers name, but its "name".
@ -693,74 +693,38 @@ class XmlMaterialProfile(InstanceContainer):
if buildplate_id is None: if buildplate_id is None:
continue continue
from cura.Machines.VariantManager import VariantType
variant_manager = CuraApplication.getInstance().getVariantManager() variant_manager = CuraApplication.getInstance().getVariantManager()
variant_node = variant_manager.getVariantNode(machine_id, buildplate_id, variant_node = variant_manager.getVariantNode(machine_id, buildplate_id,
variant_type = VariantType.BUILD_PLATE) variant_type = VariantType.BUILD_PLATE)
if not variant_node: if not variant_node:
continue continue
buildplate_compatibility = machine_compatibility _, buildplate_unmapped_settings_dict = self._getSettingsDictForNode(buildplate)
buildplate_recommended = machine_compatibility
settings = buildplate.iterfind("./um:setting", self.__namespaces) buildplate_compatibility = buildplate_unmapped_settings_dict.get("hardware compatible",
for entry in settings: machine_compatibility)
key = entry.get("key") buildplate_recommended = buildplate_unmapped_settings_dict.get("hardware recommended",
if key in self.__unmapped_settings: machine_compatibility)
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_map["buildplate_compatible"][buildplate_id] = buildplate_compatibility buildplate_map["buildplate_compatible"][buildplate_id] = buildplate_compatibility
buildplate_map["buildplate_recommended"][buildplate_id] = buildplate_recommended buildplate_map["buildplate_recommended"][buildplate_id] = buildplate_recommended
hotends = machine.iterfind("./um:hotend", self.__namespaces) hotends = machine.iterfind("./um:hotend", self.__namespaces)
for hotend in hotends: 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") hotend_name = hotend.get("id")
if hotend_name is None: if hotend_name is None:
continue continue
variant_manager = CuraApplication.getInstance().getVariantManager() 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: if not variant_node:
continue continue
hotend_compatibility = machine_compatibility hotend_mapped_settings, hotend_unmapped_settings = self._getSettingsDictForNode(hotend)
hotend_setting_values = {} hotend_compatibility = hotend_unmapped_settings.get("hardware compatible", machine_compatibility)
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
# Generate container ID for the hotend-specific material container
new_hotend_specific_material_id = self.getId() + "_" + machine_id + "_" + hotend_name.replace(" ", "_") 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 # Same as machine compatibility, keep the derived material containers consistent with the parent material
@ -785,7 +749,7 @@ class XmlMaterialProfile(InstanceContainer):
new_hotend_material.getMetaData()["buildplate_recommended"] = buildplate_map["buildplate_recommended"] new_hotend_material.getMetaData()["buildplate_recommended"] = buildplate_map["buildplate_recommended"]
cached_hotend_setting_properties = cached_machine_setting_properties.copy() 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) new_hotend_material.setCachedValues(cached_hotend_setting_properties)
@ -794,6 +758,61 @@ class XmlMaterialProfile(InstanceContainer):
if is_new_material: if is_new_material:
containers_to_add.append(new_hotend_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 # 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 # a workable ID for that machine, so there is no need to continue
break break
@ -801,6 +820,54 @@ class XmlMaterialProfile(InstanceContainer):
for container_to_add in containers_to_add: for container_to_add in containers_to_add:
ContainerRegistry.getInstance().addContainer(container_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 @classmethod
def deserializeMetadata(cls, serialized: str, container_id: str) -> List[Dict[str, Any]]: 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). result_metadata = [] #All the metadata that we found except the base (because the base is returned).
@ -983,6 +1050,36 @@ class XmlMaterialProfile(InstanceContainer):
result_metadata.append(new_hotend_material_metadata) 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 # 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 # a workable ID for that machine, so there is no need to continue
break break

View File

@ -2127,6 +2127,7 @@
"type": "float", "type": "float",
"default_value": 60, "default_value": 60,
"value": "default_material_bed_temperature", "value": "default_material_bed_temperature",
"resolve": "max(extruderValues('material_bed_temperature'))",
"minimum_value": "-273.15", "minimum_value": "-273.15",
"minimum_value_warning": "0", "minimum_value_warning": "0",
"maximum_value_warning": "130", "maximum_value_warning": "130",

View File

@ -231,8 +231,8 @@ UM.MainWindow
onObjectRemoved: settingsMenu.removeItem(object) onObjectRemoved: settingsMenu.removeItem(object)
} }
// TODO Temporary hidden, add back again when feature ready // TODO Only show in dev mode. Remove check when feature ready
// BuildplateMenu { title: catalog.i18nc("@title:menu", "&Build plate"); visible: Cura.MachineManager.hasVariantBuildplates } BuildplateMenu { title: catalog.i18nc("@title:menu", "&Build plate"); visible: CuraSDKVersion == "dev" ? Cura.MachineManager.hasVariantBuildplates : false }
ProfileMenu { title: catalog.i18nc("@title:menu", "&Profile"); } ProfileMenu { title: catalog.i18nc("@title:menu", "&Profile"); }
MenuSeparator { } MenuSeparator { }

View File

@ -476,8 +476,8 @@ Column
{ {
id: buildplateRow id: buildplateRow
height: UM.Theme.getSize("sidebar_setup").height height: UM.Theme.getSize("sidebar_setup").height
// TODO Temporary hidden, add back again when feature ready // TODO Only show in dev mode. Remove check when feature ready
visible: false //Cura.MachineManager.hasVariantBuildplates && !sidebar.hideSettings visible: CuraSDKVersion == "dev" ? Cura.MachineManager.hasVariantBuildplates && !sidebar.hideSettings : false
anchors anchors
{ {

View File

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

View File

@ -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

View File

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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

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

View File

@ -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