Add one more layer to the decision tree

This commit is contained in:
Lipu Fei 2018-08-02 17:15:30 +02:00
parent a303f394c8
commit 066a00653a
6 changed files with 368 additions and 185 deletions

View file

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