Rework container tree structure

This sets up a few new classes, subclasses of ContainerNode.

This is intended to simplify the current structure in the QualityManager.

Contributes to issue CURA-6600.
This commit is contained in:
Ghostkeeper 2019-08-05 17:39:19 +02:00
parent 037f1967c8
commit 9fda7bd0b9
No known key found for this signature in database
GPG key ID: 86BEF881AE2CF276
6 changed files with 136 additions and 63 deletions

View file

@ -1,64 +1,71 @@
# Copyright (c) 2018 Ultimaker B.V.
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional, Any, Dict, Union, TYPE_CHECKING
from collections import OrderedDict
from typing import Any, Dict, Optional
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Logger import Logger
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Decorators import deprecated
##
# A metadata / container combination. Use getContainer() to get the container corresponding to the metadata.
#
# ContainerNode is a multi-purpose class. It has two main purposes:
# 1. It encapsulates an InstanceContainer. It contains that InstanceContainer's
# - metadata (Always)
# - container (lazy-loaded when needed)
# 2. It also serves as a node in a hierarchical InstanceContainer lookup table/tree.
# This is used in Variant, Material, and Quality Managers.
## A node in the container tree. It represents one container.
#
# The container it represents is referenced by its container_id. During normal
# use of the tree, this container is not constructed. Only when parts of the
# tree need to get loaded in the container stack should it get constructed.
class ContainerNode:
__slots__ = ("_metadata", "_container", "children_map")
## Creates a new node for the container tree.
# \param container_id The ID of the container that this node should
# represent.
# \param parent The parent container node, if any.
def __init__(self, container_id: str, parent: Optional["ContainerNode"]) -> None:
self.container_id = container_id
self.parent = parent
self._container = None # type: Optional[InstanceContainer]
self.children_map = {} # type: Dict[str, ContainerNode] # Mapping from container ID to container node.
def __init__(self, metadata: Optional[Dict[str, Any]] = None) -> None:
self._metadata = metadata
self._container = None # type: Optional[InstanceContainer]
self.children_map = OrderedDict() # type: ignore # This is because it's children are supposed to override it.
## Get an entry value from the metadata
## Get an entry from the metadata of the container that this node contains.
# \param entry The metadata entry key to return.
# \param default If the metadata is not present or the container is not
# found, the value of this default is returned.
# \return The value of the metadata entry, or the default if it was not
# present.
@deprecated("Get the metadata from the container with the ID of this node yourself.", "4.3")
def getMetaDataEntry(self, entry: str, default: Any = None) -> Any:
if self._metadata is None:
container_metadata = ContainerRegistry.getInstance().findContainersMetadata(id = self.container_id)
if len(container_metadata) == 0:
return default
return self._metadata.get(entry, default)
return container_metadata[0].get(entry, default)
def getMetadata(self) -> Dict[str, Any]:
if self._metadata is None:
return {}
return self._metadata
## Get the child with the specified container ID.
# \param child_id The container ID to get from among the children.
# \return The child node, or ``None`` if no child is present with the
# specified ID.
@deprecated("Iterate over the children instead of requesting them one by one.", "4.3")
def getChildNode(self, child_id: str) -> Optional["ContainerNode"]:
return self.children_map.get(child_id)
def getChildNode(self, child_key: str) -> Optional["ContainerNode"]:
return self.children_map.get(child_key)
@deprecated("Use `.container` instead.", "4.3")
def getContainer(self) -> Optional[InstanceContainer]:
return self.container
def getContainer(self) -> Optional["InstanceContainer"]:
if self._metadata is None:
Logger.log("e", "Cannot get container for a ContainerNode without metadata.")
return None
if self._container is None:
container_id = self._metadata["id"]
from UM.Settings.ContainerRegistry import ContainerRegistry
container_list = ContainerRegistry.getInstance().findInstanceContainers(id = container_id)
if not container_list:
Logger.log("e", "Failed to lazy-load container [{container_id}]. Cannot find it.".format(container_id = container_id))
## The container that this node's container ID refers to.
#
# This can be used to finally instantiate the container in order to put it
# in the container stack.
# \return A container.
@property
def container(self) -> Optional[InstanceContainer]:
if not self._container:
container_list = ContainerRegistry.getInstance().findInstanceContainers(id = self.container_id)
if len(container_list) == 0:
Logger.log("e", "Failed to lazy-load container [{container_id}]. Cannot find it.".format(container_id = self.container_id))
error_message = ConfigurationErrorMessage.getInstance()
error_message.addFaultyContainers(container_id)
error_message.addFaultyContainers(self.container_id)
return None
self._container = container_list[0]
return self._container
def __str__(self) -> str:
return "%s[%s]" % (self.__class__.__name__, self.getMetaDataEntry("id"))
return "%s[%s]" % (self.__class__.__name__, self.container_id)

View file

@ -0,0 +1,15 @@
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from cura.Machines.MachineNode import MachineNode
from typing import Dict
## This class contains a look-up tree for which containers are available at
# which stages of configuration.
#
# The tree starts at the machine definitions. For every distinct definition
# there will be one machine node here.
class ContainerTree:
def __init__(self) -> None:
self.machines = {} # type: Dict[str, MachineNode] # Mapping from definition ID to machine nodes.

View file

@ -0,0 +1,19 @@
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import TYPE_CHECKING
from cura.Machines.ContainerNode import ContainerNode
from cura.Machines.MaterialNode import MaterialNode
from cura.Machines.QualityNode import QualityNode
if TYPE_CHECKING:
from typing import Dict
## This class represents an intent category in the container tree.
#
# This class has no more subnodes.
class IntentNode(ContainerNode):
def __init__(self, container_id: str, parent: QualityNode) -> None:
super().__init__(container_id, parent)
self.variants = {} # type: Dict[str, MaterialNode] # mapping variant IDs to their nodes.

View file

@ -0,0 +1,19 @@
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import TYPE_CHECKING
from cura.Machines.ContainerNode import ContainerNode
from cura.Machines.ContainerTree import ContainerTree
from cura.Machines.VariantNode import VariantNode
if TYPE_CHECKING:
from typing import Dict
## This class represents a machine in the container tree.
#
# The subnodes of these nodes are variants.
class MachineNode(ContainerNode):
def __init__(self, container_id: str) -> None:
super().__init__(container_id, None)
self.variants = {} # type: Dict[str, VariantNode] # mapping variant IDs to their nodes.

View file

@ -1,25 +1,19 @@
# Copyright (c) 2018 Ultimaker B.V.
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional, Dict, Any
from collections import OrderedDict
from .ContainerNode import ContainerNode
from typing import TYPE_CHECKING
from cura.Machines.ContainerNode import ContainerNode
from cura.Machines.QualityNode import QualityNode
from cura.Machines.VariantNode import VariantNode
if TYPE_CHECKING:
from typing import Dict
## Represents a material in the container tree.
#
# A MaterialNode is a node in the material lookup tree/map/table. It contains 2 (extra) fields:
# - material_map: a one-to-one map of "material_root_id" to material_node.
# - children_map: the key-value map for child nodes of this node. This is used in a lookup tree.
#
#
# Its subcontainers are quality profiles.
class MaterialNode(ContainerNode):
__slots__ = ("material_map", "children_map")
def __init__(self, metadata: Optional[Dict[str, Any]] = None) -> None:
super().__init__(metadata = metadata)
self.material_map = {} # type: Dict[str, MaterialNode] # material_root_id -> material_node
# We overide this as we want to indicate that MaterialNodes can only contain other material nodes.
self.children_map = OrderedDict() # type: OrderedDict[str, "MaterialNode"]
def getChildNode(self, child_key: str) -> Optional["MaterialNode"]:
return self.children_map.get(child_key)
def __init__(self, container_id, parent: VariantNode) -> None:
super().__init__(container_id, parent)
self.qualities = {} # type: Dict[str, QualityNode] # Mapping container IDs to quality profiles.

View file

@ -0,0 +1,19 @@
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import TYPE_CHECKING
from cura.Machines.ContainerNode import ContainerNode
from cura.Machines.MachineNode import MachineNode
from cura.Machines.MaterialNode import MaterialNode
if TYPE_CHECKING:
from typing import Dict
## This class represents an extruder variant in the container tree.
#
# The subnodes of these nodes are materials.
class VariantNode(ContainerNode):
def __init__(self, container_id: str, parent: MachineNode) -> None:
super().__init__(container_id, parent)
self.variants = {} # type: Dict[str, MaterialNode] # mapping variant IDs to their nodes.