mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-08-09 23:05:01 -06:00
Merge branch 'feature_intent_container_tree' of https://github.com/Ultimaker/Cura into feature_intent_container_tree
This commit is contained in:
commit
5039d8db05
22 changed files with 258 additions and 398 deletions
|
@ -5,10 +5,15 @@ from UM.Logger import Logger
|
|||
from UM.Settings.ContainerRegistry import ContainerRegistry # To listen to containers being added.
|
||||
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||
from UM.Settings.Interfaces import ContainerInterface
|
||||
import cura.CuraApplication # Imported like this to prevent circular dependencies.
|
||||
from cura.Machines.MachineNode import MachineNode
|
||||
|
||||
from typing import Dict
|
||||
from typing import Dict, List, TYPE_CHECKING
|
||||
import time
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.Machines.QualityGroup import QualityGroup
|
||||
|
||||
## This class contains a look-up tree for which containers are available at
|
||||
# which stages of configuration.
|
||||
#
|
||||
|
@ -29,6 +34,21 @@ class ContainerTree:
|
|||
container_registry.containerAdded.connect(self._machineAdded)
|
||||
self._loadAll()
|
||||
|
||||
## Get the quality groups available for the currently activated printer.
|
||||
#
|
||||
# This contains all quality groups, enabled or disabled. To check whether
|
||||
# the quality group can be activated, test for the
|
||||
# ``QualityGroup.is_available`` property.
|
||||
# \return For every quality type, one quality group.
|
||||
def getCurrentQualityGroups(self) -> Dict[str, "QualityGroup"]:
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if global_stack is None:
|
||||
return {}
|
||||
variant_names = [extruder.variant.getName() for extruder in global_stack.extruders.values()]
|
||||
material_bases = [extruder.material.getMetaDataEntry("base_file") for extruder in global_stack.extruders.values()]
|
||||
extruder_enabled = [extruder.isEnabled for extruder in global_stack.extruders.values()]
|
||||
return self.machines[global_stack.definition.getId()].getQualityGroups(variant_names, material_bases, extruder_enabled)
|
||||
|
||||
## Builds the initial container tree.
|
||||
def _loadAll(self):
|
||||
Logger.log("i", "Building container tree.")
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Dict, List
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.Util import parseBool
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry # To find all the variants for this machine.
|
||||
from UM.Settings.Interfaces import ContainerInterface
|
||||
from cura.Machines.ContainerNode import ContainerNode
|
||||
from cura.Machines.QualityGroup import QualityGroup # To construct groups of quality profiles that belong together.
|
||||
from cura.Machines.QualityNode import QualityNode
|
||||
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.
|
||||
|
@ -36,10 +33,57 @@ class MachineNode(ContainerNode):
|
|||
self.quality_definition = my_metadata.get("quality_definition", container_id)
|
||||
self.exclude_materials = my_metadata.get("exclude_materials", [])
|
||||
self.preferred_variant_name = my_metadata.get("preferred_variant_name", "")
|
||||
self.preferred_quality_type = my_metadata.get("preferred_quality_type", "")
|
||||
|
||||
container_registry.containerAdded.connect(self._variantAdded)
|
||||
self._loadAll()
|
||||
|
||||
## Get the available quality groups for this machine.
|
||||
#
|
||||
# This returns all quality groups, regardless of whether they are
|
||||
# available to the combination of extruders or not. On the resulting
|
||||
# quality groups, the is_available property is set to indicate whether the
|
||||
# quality group can be selected according to the combination of extruders
|
||||
# in the parameters.
|
||||
# \param variant_names The names of the variants loaded in each extruder.
|
||||
# \param material_bases The base file names of the materials loaded in
|
||||
# each extruder.
|
||||
# \param extruder_enabled Whether or not the extruders are enabled. This
|
||||
# allows the function to set the is_available properly.
|
||||
# \return For each available quality type, a QualityGroup instance.
|
||||
def getQualityGroups(self, variant_names: List[str], material_bases: List[str], extruder_enabled: List[bool]) -> Dict[str, QualityGroup]:
|
||||
if len(variant_names) != len(material_bases) or len(variant_names) != len(extruder_enabled):
|
||||
Logger.log("e", "The number of extruders in the list of variants (" + str(len(variant_names)) + ") is not equal to the number of extruders in the list of materials (" + str(len(material_bases)) + ") or the list of enabled extruders (" + str(len(extruder_enabled)) + ").")
|
||||
return {}
|
||||
# For each extruder, find which quality profiles are available. Later we'll intersect the quality types.
|
||||
qualities_per_type_per_extruder = [{} for _ in range(len(variant_names))] # type: List[Dict[str, QualityNode]]
|
||||
for extruder_nr, variant_name in enumerate(variant_names):
|
||||
if not extruder_enabled[extruder_nr]:
|
||||
continue # No qualities are available in this extruder. It'll get skipped when calculating the available quality types.
|
||||
material_base = material_bases[extruder_nr]
|
||||
if variant_name not in self.variants or material_base not in self.variants[variant_name].materials:
|
||||
# The printer has no variant/material-specific quality profiles. Use the global quality profiles.
|
||||
qualities_per_type_per_extruder[extruder_nr] = self.global_qualities
|
||||
else:
|
||||
# Use the actually specialised quality profiles.
|
||||
qualities_per_type_per_extruder[extruder_nr] = self.variants[variant_name].materials[material_base].qualities
|
||||
|
||||
# Create the quality group for each available type.
|
||||
quality_groups = {}
|
||||
for quality_type, global_quality_node in self.global_qualities.items():
|
||||
quality_groups[quality_type] = QualityGroup(name = global_quality_node.getMetaDataEntry("name", "Unnamed profile"), quality_type = quality_type)
|
||||
quality_groups[quality_type].node_for_global = global_quality_node
|
||||
for extruder, qualities_per_type in qualities_per_type_per_extruder:
|
||||
quality_groups[quality_type].nodes_for_extruders[extruder] = qualities_per_type[quality_type]
|
||||
|
||||
available_quality_types = set(quality_groups.keys())
|
||||
for extruder_nr, qualities_per_type in enumerate(qualities_per_type_per_extruder):
|
||||
if not extruder_enabled[extruder_nr]:
|
||||
continue
|
||||
available_quality_types.intersection_update(qualities_per_type.keys())
|
||||
for quality_type in available_quality_types:
|
||||
quality_groups[quality_type].is_available = True
|
||||
return quality_groups
|
||||
|
||||
## (Re)loads all variants under this printer.
|
||||
def _loadAll(self):
|
||||
# Find all the variants for this definition ID.
|
||||
|
@ -55,19 +99,4 @@ class MachineNode(ContainerNode):
|
|||
if len(global_qualities) == 0: # This printer doesn't override the global qualities.
|
||||
global_qualities = container_registry.findInstanceContainersMetadata(type = "quality", definition = "fdmprinter", global_quality = True) # Otherwise pick the global global qualities.
|
||||
for global_quality in global_qualities:
|
||||
self.global_qualities[global_quality["quality_type"]] = QualityNode(global_quality["id"], parent = self)
|
||||
|
||||
## When a variant gets added to the set of profiles, we need to update our
|
||||
# tree here.
|
||||
def _variantAdded(self, container: ContainerInterface):
|
||||
if container.getMetaDataEntry("type") != "variant":
|
||||
return # Not interested.
|
||||
name = container.getMetaDataEntry("name")
|
||||
if name in self.variants:
|
||||
return # Already have this one.
|
||||
if container.getMetaDataEntry("hardware_type") != "nozzle":
|
||||
return # Only want nozzles in my tree.
|
||||
if container.getMetaDataEntry("definition") != self.container_id:
|
||||
return # Not a nozzle that fits in my machine.
|
||||
|
||||
self.variants[name] = VariantNode(container.getId(), machine = self)
|
||||
self.global_qualities[global_quality["quality_type"]] = QualityNode(global_quality["id"], parent = self)
|
|
@ -59,15 +59,6 @@ class MaterialManager(QObject):
|
|||
# Root_material_id -> MaterialGroup
|
||||
self._material_group_map = dict() # type: Dict[str, MaterialGroup]
|
||||
|
||||
# Approximate diameter str
|
||||
self._diameter_machine_nozzle_buildplate_material_map = dict() # type: Dict[str, Dict[str, 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
|
||||
# i.e. generic_pla -> generic_pla_175
|
||||
# root_material_id -> approximate diameter str -> root_material_id for that diameter
|
||||
self._material_diameter_map = defaultdict(dict) # type: Dict[str, Dict[str, str]]
|
||||
|
||||
# Material id including diameter (generic_pla_175) -> material root id (generic_pla)
|
||||
self._diameter_material_map = dict() # type: Dict[str, str]
|
||||
|
||||
|
@ -75,11 +66,6 @@ class MaterialManager(QObject):
|
|||
# GUID -> a list of material_groups
|
||||
self._guid_material_groups_map = defaultdict(list) # type: Dict[str, List[MaterialGroup]]
|
||||
|
||||
# The machine definition ID for the non-machine-specific materials.
|
||||
# This is used as the last fallback option if the given machine-specific material(s) cannot be found.
|
||||
self._default_machine_definition_id = "fdmprinter"
|
||||
self._default_approximate_diameter_for_quality_search = "3"
|
||||
|
||||
self._favorites = set(cura.CuraApplication.CuraApplication.getInstance().getPreferences().getValue("cura/favorite_materials").split(";"))
|
||||
self.materialsUpdated.emit()
|
||||
|
||||
|
@ -87,7 +73,15 @@ class MaterialManager(QObject):
|
|||
return self._material_group_map.get(root_material_id)
|
||||
|
||||
def getRootMaterialIDForDiameter(self, root_material_id: str, approximate_diameter: str) -> str:
|
||||
return self._material_diameter_map.get(root_material_id, {}).get(approximate_diameter, root_material_id)
|
||||
original_material = CuraContainerRegistry.getInstance().findInstanceContainersMetadata(id=root_material_id)[0]
|
||||
if original_material["approximate_diameter"] == approximate_diameter:
|
||||
return root_material_id
|
||||
|
||||
matching_materials = CuraContainerRegistry.getInstance().findInstanceContainersMetadata(type = "material", brand = original_material["brand"], definition = original_material["definition"], material = original_material["material"], color_name = original_material["color_name"])
|
||||
for material in matching_materials:
|
||||
if material["approximate_diameter"] == approximate_diameter:
|
||||
return material["id"]
|
||||
return root_material_id
|
||||
|
||||
def getRootMaterialIDWithoutDiameter(self, root_material_id: str) -> str:
|
||||
return self._diameter_material_map.get(root_material_id, "")
|
||||
|
@ -109,13 +103,16 @@ class MaterialManager(QObject):
|
|||
# A convenience function to get available materials for the given machine with the extruder position.
|
||||
#
|
||||
def getAvailableMaterialsForMachineExtruder(self, machine: "GlobalStack",
|
||||
extruder_stack: "ExtruderStack") -> Optional[Dict[str, MaterialNode]]:
|
||||
extruder_stack: "ExtruderStack") -> Dict[str, MaterialNode]:
|
||||
nozzle_name = None
|
||||
if extruder_stack.variant.getId() != "empty_variant":
|
||||
nozzle_name = extruder_stack.variant.getName()
|
||||
|
||||
# Fetch the available materials (ContainerNode) for the current active machine and extruder setup.
|
||||
return self.getAvailableMaterials(machine.definition.getId(), nozzle_name)
|
||||
materials =self.getAvailableMaterials(machine.definition.getId(), nozzle_name)
|
||||
compatible_material_diameter = str(round(extruder_stack.getCompatibleMaterialDiameter()))
|
||||
|
||||
return {key: material for key, material in materials.items() if material.getMetaDataEntry("approximate_diameter") == compatible_material_diameter}
|
||||
|
||||
#
|
||||
# Gets MaterialNode for the given extruder and machine with the given material name.
|
||||
|
@ -152,27 +149,10 @@ class MaterialManager(QObject):
|
|||
#
|
||||
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
|
||||
if parseBool(machine_definition.getMetaDataEntry("has_materials", False)):
|
||||
material_diameter = extruder_definition.getProperty("material_diameter", "value")
|
||||
if isinstance(material_diameter, SettingFunction):
|
||||
material_diameter = material_diameter(global_stack)
|
||||
variant_name = global_stack.extruders[position].variant.getName()
|
||||
|
||||
# Look at the guid to material dictionary
|
||||
root_material_id = None
|
||||
for material_group in self._guid_material_groups_map[material_guid]:
|
||||
root_material_id = cast(str, material_group.root_material_node.getMetaDataEntry("id", ""))
|
||||
break
|
||||
|
||||
if not root_material_id:
|
||||
Logger.log("i", "Cannot find materials with guid [%s] ", material_guid)
|
||||
return None
|
||||
|
||||
node = self.getMaterialNode(machine_definition.getId(), nozzle_name, buildplate_name,
|
||||
material_diameter, root_material_id)
|
||||
return node
|
||||
return self.getMaterialNode(machine_definition.getId(), variant_name, buildplate_name, 3, material_guid)
|
||||
|
||||
# There are 2 ways to get fallback materials;
|
||||
# - A fallback by type (@sa getFallbackMaterialIdByMaterialType), which adds the generic version of this material
|
||||
|
@ -254,41 +234,21 @@ class MaterialManager(QObject):
|
|||
return node
|
||||
|
||||
def removeMaterialByRootId(self, root_material_id: str):
|
||||
material_group = self.getMaterialGroup(root_material_id)
|
||||
if not material_group:
|
||||
Logger.log("i", "Unable to remove the material with id %s, because it doesn't exist.", root_material_id)
|
||||
return
|
||||
|
||||
container_registry = CuraContainerRegistry.getInstance()
|
||||
nodes_to_remove = [material_group.root_material_node] + material_group.derived_material_node_list
|
||||
# Sort all nodes with respect to the container ID lengths in the ascending order so the base material container
|
||||
# will be the first one to be removed. We need to do this to ensure that all containers get loaded & deleted.
|
||||
nodes_to_remove = sorted(nodes_to_remove, key = lambda x: len(x.getMetaDataEntry("id", "")))
|
||||
# Try to load all containers first. If there is any faulty ones, they will be put into the faulty container
|
||||
# list, so removeContainer() can ignore those ones.
|
||||
for node in nodes_to_remove:
|
||||
container_id = node.getMetaDataEntry("id", "")
|
||||
results = container_registry.findContainers(id = container_id)
|
||||
if not results:
|
||||
container_registry.addWrongContainerId(container_id)
|
||||
for node in nodes_to_remove:
|
||||
container_registry.removeContainer(node.getMetaDataEntry("id", ""))
|
||||
results = container_registry.findContainers(id=root_material_id)
|
||||
if not results:
|
||||
container_registry.addWrongContainerId(root_material_id)
|
||||
|
||||
for result in results:
|
||||
container_registry.removeContainer(result.getMetaDataEntry("id", ""))
|
||||
|
||||
#
|
||||
# Methods for GUI
|
||||
#
|
||||
@pyqtSlot("QVariant", result=bool)
|
||||
def canMaterialBeRemoved(self, material_node: "MaterialNode"):
|
||||
# Check if the material is active in any extruder train. In that case, the material shouldn't be removed!
|
||||
# In the future we might enable this again, but right now, it's causing a ton of issues if we do (since it
|
||||
# corrupts the configuration)
|
||||
root_material_id = material_node.getMetaDataEntry("base_file")
|
||||
material_group = self.getMaterialGroup(root_material_id)
|
||||
if not material_group:
|
||||
return False
|
||||
|
||||
nodes_to_remove = [material_group.root_material_node] + material_group.derived_material_node_list
|
||||
ids_to_remove = [node.getMetaDataEntry("id", "") for node in nodes_to_remove]
|
||||
ids_to_remove = [metadata.get("id", "") for metadata in CuraContainerRegistry.getInstance().findInstanceContainersMetadata(base_file=root_material_id)]
|
||||
|
||||
for extruder_stack in CuraContainerRegistry.getInstance().findContainerStacks(type = "extruder_train"):
|
||||
if extruder_stack.material.getId() in ids_to_remove:
|
||||
|
@ -303,38 +263,24 @@ class MaterialManager(QObject):
|
|||
if CuraContainerRegistry.getInstance().isReadOnly(root_material_id):
|
||||
Logger.log("w", "Cannot set name of read-only container %s.", root_material_id)
|
||||
return
|
||||
containers = CuraContainerRegistry.getInstance().findInstanceContainers(id = root_material_id)
|
||||
containers[0].setName(name)
|
||||
|
||||
material_group = self.getMaterialGroup(root_material_id)
|
||||
if material_group:
|
||||
container = material_group.root_material_node.container
|
||||
if container:
|
||||
container.setName(name)
|
||||
|
||||
#
|
||||
# Removes the given material.
|
||||
#
|
||||
@pyqtSlot("QVariant")
|
||||
def removeMaterial(self, material_node: "MaterialNode") -> None:
|
||||
root_material_id = material_node.getMetaDataEntry("base_file")
|
||||
if root_material_id is not None:
|
||||
self.removeMaterialByRootId(root_material_id)
|
||||
|
||||
#
|
||||
# Creates a duplicate of a material, which has the same GUID and base_file metadata.
|
||||
# Returns the root material ID of the duplicated material if successful.
|
||||
#
|
||||
@pyqtSlot("QVariant", result = str)
|
||||
def duplicateMaterial(self, material_node: MaterialNode, new_base_id: Optional[str] = None, new_metadata: Dict[str, Any] = None) -> Optional[str]:
|
||||
root_material_id = cast(str, material_node.getMetaDataEntry("base_file", ""))
|
||||
def duplicateMaterialByRootId(self, root_material_id, new_base_id: Optional[str] = None, new_metadata: Dict[str, Any] = None) -> Optional[str]:
|
||||
container_registry = CuraContainerRegistry.getInstance()
|
||||
results = container_registry.findContainers(id=root_material_id)
|
||||
|
||||
material_group = self.getMaterialGroup(root_material_id)
|
||||
if not material_group:
|
||||
if not results:
|
||||
Logger.log("i", "Unable to duplicate the material with id %s, because it doesn't exist.", root_material_id)
|
||||
return None
|
||||
|
||||
base_container = material_group.root_material_node.container
|
||||
if not base_container:
|
||||
return None
|
||||
base_container = results[0]
|
||||
|
||||
# Ensure all settings are saved.
|
||||
cura.CuraApplication.CuraApplication.getInstance().saveSettings()
|
||||
|
@ -353,11 +299,9 @@ class MaterialManager(QObject):
|
|||
new_containers.append(new_base_container)
|
||||
|
||||
# Clone all of them.
|
||||
for node in material_group.derived_material_node_list:
|
||||
container_to_copy = node.container
|
||||
if not container_to_copy:
|
||||
continue
|
||||
# Create unique IDs for every clone.
|
||||
for container_to_copy in container_registry.findContainers(base_file=root_material_id):
|
||||
if container_to_copy.getId() == root_material_id:
|
||||
continue # We already have that one, skip it
|
||||
new_id = new_base_id
|
||||
if container_to_copy.getMetaDataEntry("definition") != "fdmprinter":
|
||||
new_id += "_" + container_to_copy.getMetaDataEntry("definition")
|
||||
|
@ -371,7 +315,6 @@ class MaterialManager(QObject):
|
|||
if new_metadata is not None:
|
||||
for key, value in new_metadata.items():
|
||||
new_container.getMetaData()[key] = value
|
||||
|
||||
new_containers.append(new_container)
|
||||
|
||||
for container_to_add in new_containers:
|
||||
|
@ -383,8 +326,15 @@ class MaterialManager(QObject):
|
|||
self.addFavorite(new_base_id)
|
||||
|
||||
return new_base_id
|
||||
|
||||
#
|
||||
# Creates a duplicate of a material, which has the same GUID and base_file metadata.
|
||||
# Returns the root material ID of the duplicated material if successful.
|
||||
#
|
||||
@pyqtSlot("QVariant", result = str)
|
||||
def duplicateMaterial(self, material_node: MaterialNode, new_base_id: Optional[str] = None, new_metadata: Dict[str, Any] = None) -> Optional[str]:
|
||||
root_material_id = cast(str, material_node.getMetaDataEntry("base_file", ""))
|
||||
return self.duplicateMaterialByRootId(root_material_id, new_base_id, new_metadata)
|
||||
|
||||
# Create a new material by cloning Generic PLA for the current material diameter and generate a new GUID.
|
||||
# Returns the ID of the newly created material.
|
||||
@pyqtSlot(result = str)
|
||||
|
@ -403,11 +353,6 @@ class MaterialManager(QObject):
|
|||
|
||||
approximate_diameter = str(extruder_stack.approximateMaterialDiameter)
|
||||
root_material_id = self.getRootMaterialIDForDiameter(root_material_id, approximate_diameter)
|
||||
material_group = self.getMaterialGroup(root_material_id)
|
||||
|
||||
if not material_group: # This should never happen
|
||||
Logger.log("w", "Cannot get the material group of %s.", root_material_id)
|
||||
return ""
|
||||
|
||||
# Create a new ID & container to hold the data.
|
||||
new_id = CuraContainerRegistry.getInstance().uniqueName("custom_material")
|
||||
|
@ -416,9 +361,7 @@ class MaterialManager(QObject):
|
|||
"GUID": str(uuid.uuid4()),
|
||||
}
|
||||
|
||||
self.duplicateMaterial(material_group.root_material_node,
|
||||
new_base_id = new_id,
|
||||
new_metadata = new_metadata)
|
||||
self.duplicateMaterialByRootId(root_material_id, new_base_id = new_id, new_metadata = new_metadata)
|
||||
return new_id
|
||||
|
||||
@pyqtSlot(str)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Any, TYPE_CHECKING
|
||||
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Settings.Interfaces import ContainerInterface
|
||||
|
@ -23,8 +23,11 @@ class MaterialNode(ContainerNode):
|
|||
container_registry = ContainerRegistry.getInstance()
|
||||
my_metadata = container_registry.findContainersMetadata(id = container_id)[0]
|
||||
self.base_file = my_metadata["base_file"]
|
||||
container_registry.containerAdded.connect(self._qualityAdded)
|
||||
self.material_type = my_metadata["material"]
|
||||
self.guid = my_metadata["GUID"]
|
||||
self._loadAll()
|
||||
container_registry.containerRemoved.connect(self._onRemoved)
|
||||
container_registry.containerMetaDataChanged.connect(self._onMetadataChanged)
|
||||
|
||||
def _loadAll(self) -> None:
|
||||
container_registry = ContainerRegistry.getInstance()
|
||||
|
@ -33,14 +36,13 @@ class MaterialNode(ContainerNode):
|
|||
qualities = container_registry.findInstanceContainersMetadata(type = "quality", definition = "fdmprinter")
|
||||
else:
|
||||
# Need to find the qualities that specify a material profile with the same material type.
|
||||
my_metadata = container_registry.findInstanceContainersMetadata(id = self.container_id)[0]
|
||||
my_material_type = my_metadata.get("material")
|
||||
my_material_type = self.material_type
|
||||
qualities = []
|
||||
qualities_any_material = container_registry.findInstanceContainersMetadata(type = "quality", definition = self.variant.machine.quality_definition, variant = self.variant.variant_name)
|
||||
for material_metadata in container_registry.findInstanceContainersMetadata(type = "material", material = my_material_type):
|
||||
qualities.extend((quality for quality in qualities_any_material if quality["material"] == material_metadata["id"]))
|
||||
if not qualities: # No quality profiles found. Go by GUID then.
|
||||
my_guid = my_metadata.get("material")
|
||||
my_guid = self.guid
|
||||
for material_metadata in container_registry.findInstanceContainersMetadata(type = "material", guid = my_guid):
|
||||
qualities.extend((quality for quality in qualities_any_material if quality["material"] == material_metadata["id"]))
|
||||
|
||||
|
@ -49,37 +51,37 @@ class MaterialNode(ContainerNode):
|
|||
if quality_id not in self.qualities:
|
||||
self.qualities[quality_id] = QualityNode(quality_id, parent = self)
|
||||
|
||||
def _qualityAdded(self, container: ContainerInterface) -> None:
|
||||
if container.getMetaDataEntry("type") != "quality":
|
||||
return # Not interested.
|
||||
if not self.variant.machine.has_machine_quality:
|
||||
if container.getMetaDataEntry("definition") != "fdmprinter":
|
||||
return # Only want global qualities.
|
||||
else:
|
||||
if container.getMetaDataEntry("definition") != self.variant.machine.quality_definition:
|
||||
return # Doesn't match the machine.
|
||||
if container.getMetaDataEntry("variant") != self.variant.variant_name:
|
||||
return # Doesn't match the variant.
|
||||
# Detect if we're falling back to matching via GUID.
|
||||
# If so, we might need to erase the current list and put just this one in (i.e. no longer use the fallback).
|
||||
container_registry = ContainerRegistry.getInstance()
|
||||
my_metadata = container_registry.findInstanceContainersMetadata(id = self.container_id)[0]
|
||||
my_material_type = my_metadata.get("material")
|
||||
allowed_material_ids = {metadata["id"] for metadata in container_registry.findInstanceContainersMetadata(type = "material", material = my_material_type)}
|
||||
# Select any quality profile; if the material is not matching by material type, we've been falling back to GUID all along.
|
||||
is_fallback_guid = len(self.qualities) == 0 or next(iter(self.qualities.values())).getMetaDataEntry("material") not in allowed_material_ids
|
||||
## Triggered when any container is removed, but only handles it when the
|
||||
# container is removed that this node represents.
|
||||
# \param container The container that was allegedly removed.
|
||||
def _onRemoved(self, container: ContainerInterface) -> None:
|
||||
if container.getId() == self.container_id:
|
||||
# Remove myself from my parent.
|
||||
if self.base_file in self.variant.materials:
|
||||
del self.variant.materials[self.base_file]
|
||||
|
||||
if is_fallback_guid and container.getMetaDataEntry("material") in allowed_material_ids: # So far we needed the fallback, but no longer!
|
||||
self.qualities.clear() # It'll get filled with the new quality profile then.
|
||||
else:
|
||||
if not is_fallback_guid:
|
||||
if container.getMetaDataEntry("material") not in allowed_material_ids:
|
||||
return # Doesn't match the material type.
|
||||
else:
|
||||
my_material_guid = my_metadata.get("GUID")
|
||||
allowed_material_ids = {metadata["id"] for metadata in container_registry.findInstanceContainersMetadata(type = "material", guid = my_material_guid)}
|
||||
if container.getMetaDataEntry("material") not in allowed_material_ids:
|
||||
return # Doesn't match the material GUID.
|
||||
## Triggered when any metadata changed in any container, but only handles
|
||||
# it when the metadata of this node is changed.
|
||||
# \param container The container whose metadata changed.
|
||||
# \param kwargs Key-word arguments provided when changing the metadata.
|
||||
# These are ignored. As far as I know they are never provided to this
|
||||
# call.
|
||||
def _onMetadataChanged(self, container: ContainerInterface, **kwargs: Any) -> None:
|
||||
if container.getId() != self.container_id:
|
||||
return
|
||||
|
||||
quality_id = container.getId()
|
||||
self.qualities[quality_id] = QualityNode(quality_id, parent = self)
|
||||
new_metadata = container.getMetaData()
|
||||
old_base_file = self.base_file
|
||||
if new_metadata["base_file"] != old_base_file:
|
||||
self.base_file = new_metadata["base_file"]
|
||||
if old_base_file in self.variant.materials: # Move in parent node.
|
||||
del self.variant.materials[old_base_file]
|
||||
self.variant.materials[self.base_file] = self
|
||||
|
||||
old_material_type = self.material_type
|
||||
self.material_type = new_metadata["material"]
|
||||
old_guid = self.guid
|
||||
self.guid = new_metadata["GUID"]
|
||||
if self.base_file != old_base_file or self.material_type != old_material_type or self.guid != old_guid: # List of quality profiles could've changed.
|
||||
self.qualities = {}
|
||||
self._loadAll() # Re-load the quality profiles for this node.
|
|
@ -34,9 +34,6 @@ class BaseMaterialsModel(ListModel):
|
|||
|
||||
# Update this model when switching machines
|
||||
self._machine_manager.activeStackChanged.connect(self._update)
|
||||
|
||||
# Update this model when list of materials changes
|
||||
self._material_manager.materialsUpdated.connect(self._update)
|
||||
|
||||
self.addRoleName(Qt.UserRole + 1, "root_material_id")
|
||||
self.addRoleName(Qt.UserRole + 2, "id")
|
||||
|
@ -109,26 +106,24 @@ class BaseMaterialsModel(ListModel):
|
|||
# so it's placed here for easy access.
|
||||
def _canUpdate(self):
|
||||
global_stack = self._machine_manager.activeMachine
|
||||
|
||||
if global_stack is None or not self._enabled:
|
||||
return False
|
||||
|
||||
extruder_position = str(self._extruder_position)
|
||||
|
||||
if extruder_position not in global_stack.extruders:
|
||||
return False
|
||||
|
||||
extruder_stack = global_stack.extruders[extruder_position]
|
||||
self._available_materials = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack, extruder_stack)
|
||||
if self._available_materials is None:
|
||||
return False
|
||||
|
||||
self._available_materials = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack, extruder_stack)
|
||||
return True
|
||||
|
||||
## This is another convenience function which is shared by all material
|
||||
# models so it's put here to avoid having so much duplicated code.
|
||||
def _createMaterialItem(self, root_material_id, container_node):
|
||||
metadata = CuraContainerRegistry.getInstance().findContainersMetadata(id = container_node.container_id)[0]
|
||||
metadata_list = CuraContainerRegistry.getInstance().findContainersMetadata(id = container_node.container_id)
|
||||
if not metadata_list:
|
||||
return None
|
||||
metadata = metadata_list[0]
|
||||
item = {
|
||||
"root_material_id": root_material_id,
|
||||
"id": metadata["id"],
|
||||
|
|
|
@ -2,11 +2,20 @@
|
|||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
|
||||
import cura.CuraApplication # To listen to changes to the preferences.
|
||||
|
||||
## Model that shows the list of favorite materials.
|
||||
class FavoriteMaterialsModel(BaseMaterialsModel):
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
cura.CuraApplication.CuraApplication.getInstance().getPreferences().preferenceChanged.connect(self._onFavoritesChanged)
|
||||
self._update()
|
||||
|
||||
## Triggered when any preference changes, but only handles it when the list
|
||||
# of favourites is changed.
|
||||
def _onFavoritesChanged(self, preference_key: str) -> None:
|
||||
if preference_key != "cura/favorite_materials":
|
||||
return
|
||||
self._update()
|
||||
|
||||
def _update(self):
|
||||
|
@ -28,7 +37,8 @@ class FavoriteMaterialsModel(BaseMaterialsModel):
|
|||
continue
|
||||
|
||||
item = self._createMaterialItem(root_material_id, container_node)
|
||||
item_list.append(item)
|
||||
if item:
|
||||
item_list.append(item)
|
||||
|
||||
# Sort the item list alphabetically by name
|
||||
item_list = sorted(item_list, key = lambda d: d["brand"].upper())
|
||||
|
|
|
@ -24,11 +24,12 @@ class GenericMaterialsModel(BaseMaterialsModel):
|
|||
continue
|
||||
|
||||
# Only add results for generic materials
|
||||
if container_node.getMetaDataEntry("brand").lower() != "generic":
|
||||
if container_node.getMetaDataEntry("brand", "unknown").lower() != "generic":
|
||||
continue
|
||||
|
||||
item = self._createMaterialItem(root_material_id, container_node)
|
||||
item_list.append(item)
|
||||
if item:
|
||||
item_list.append(item)
|
||||
|
||||
# Sort the item list alphabetically by name
|
||||
item_list = sorted(item_list, key = lambda d: d["name"].upper())
|
||||
|
|
|
@ -8,6 +8,7 @@ from PyQt5.QtCore import Qt, QObject, pyqtProperty, pyqtSignal
|
|||
from UM.Qt.ListModel import ListModel
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
from cura.Settings.IntentManager import IntentManager
|
||||
import cura.CuraApplication
|
||||
|
||||
|
@ -47,13 +48,11 @@ class IntentModel(ListModel):
|
|||
|
||||
def _update(self) -> None:
|
||||
new_items = [] # type: List[Dict[str, Any]]
|
||||
application = cura.CuraApplication.CuraApplication.getInstance()
|
||||
quality_manager = application.getQualityManager()
|
||||
global_stack = application.getGlobalContainerStack()
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if not global_stack:
|
||||
self.setItems(new_items)
|
||||
return
|
||||
quality_groups = quality_manager.getQualityGroups(global_stack)
|
||||
quality_groups = ContainerTree.getInstance().getCurrentQualityGroups()
|
||||
|
||||
for intent_category, quality_type in IntentManager.getInstance().getCurrentAvailableIntents():
|
||||
if intent_category == self._intent_category:
|
||||
|
|
|
@ -55,7 +55,8 @@ class MaterialBrandsModel(BaseMaterialsModel):
|
|||
|
||||
# Now handle the individual materials
|
||||
item = self._createMaterialItem(root_material_id, container_node)
|
||||
brand_group_dict[brand][material_type].append(item)
|
||||
if item:
|
||||
brand_group_dict[brand][material_type].append(item)
|
||||
|
||||
# Part 2: Organize the tree into models
|
||||
#
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSlot
|
||||
|
||||
from UM.Qt.ListModel import ListModel
|
||||
from UM.Logger import Logger
|
||||
from UM.Qt.ListModel import ListModel
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
|
||||
#
|
||||
# This the QML model for the quality management page.
|
||||
|
@ -42,7 +43,7 @@ class QualityManagementModel(ListModel):
|
|||
self.setItems([])
|
||||
return
|
||||
|
||||
quality_group_dict = self._quality_manager.getQualityGroups(global_stack)
|
||||
quality_group_dict = ContainerTree.getInstance().getCurrentQualityGroups()
|
||||
quality_changes_group_dict = self._quality_manager.getQualityChangesGroups(global_stack)
|
||||
|
||||
available_quality_types = set(quality_type for quality_type, quality_group in quality_group_dict.items()
|
||||
|
|
|
@ -105,37 +105,12 @@ class QualityManager(QObject):
|
|||
# \return A dictionary with quality types as keys and the quality groups
|
||||
# for those types as values.
|
||||
def getQualityGroups(self, global_stack: "GlobalStack") -> Dict[str, QualityGroup]:
|
||||
# Gather up the variant names and material base files for each extruder.
|
||||
variant_names = [extruder.variant.getName() for extruder in global_stack.extruders.values()]
|
||||
material_bases = [extruder.material.getMetaDataEntry("base_file") for extruder in global_stack.extruders.values()]
|
||||
extruder_enabled = [extruder.isEnabled for extruder in global_stack.extruders.values()]
|
||||
definition_id = global_stack.definition.getId()
|
||||
machine_node = ContainerTree.getInstance().machines[definition_id]
|
||||
|
||||
# For each extruder, find which quality profiles are available. Later we'll intersect the quality types.
|
||||
qualities_per_type_per_extruder = {} # type: Dict[str, Dict[str, QualityNode]]
|
||||
for extruder_nr, extruder in global_stack.extruders.items():
|
||||
if not extruder.isEnabled:
|
||||
continue # No qualities available in this extruder. It'll get skipped when intersecting the quality types.
|
||||
nozzle_name = extruder.variant.getName()
|
||||
material_base = extruder.material.getMetaDataEntry("base_file")
|
||||
if nozzle_name not in machine_node.variants or material_base not in machine_node.variants[nozzle_name].materials:
|
||||
# The printer has no variant/material-specific quality profiles. Use the global quality profiles.
|
||||
qualities_per_type_per_extruder[extruder_nr] = machine_node.global_qualities
|
||||
else:
|
||||
# Use the actually specialised quality profiles.
|
||||
qualities_per_type_per_extruder[extruder_nr] = machine_node.variants[nozzle_name].materials[material_base].qualities
|
||||
|
||||
# Create the quality group for each available type.
|
||||
quality_groups = {}
|
||||
for quality_type, global_quality_node in machine_node.global_qualities.items():
|
||||
quality_groups[quality_type] = QualityGroup(name = global_quality_node.getMetaDataEntry("name", "Unnamed profile"), quality_type = quality_type)
|
||||
quality_groups[quality_type].node_for_global = global_quality_node
|
||||
for extruder, qualities_per_type in qualities_per_type_per_extruder:
|
||||
quality_groups[quality_type].nodes_for_extruders[extruder] = qualities_per_type[quality_type]
|
||||
|
||||
available_quality_types = set(quality_groups.keys())
|
||||
for qualities_per_type in qualities_per_type_per_extruder.values():
|
||||
available_quality_types.intersection_update(qualities_per_type.keys())
|
||||
for quality_type in available_quality_types:
|
||||
quality_groups[quality_type].is_available = True
|
||||
return quality_groups
|
||||
return ContainerTree.getInstance().machines[definition_id].getQualityGroups(variant_names, material_bases, extruder_enabled)
|
||||
|
||||
def getQualityGroupsForMachineDefinition(self, machine: "GlobalStack") -> Dict[str, QualityGroup]:
|
||||
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
|
||||
|
@ -160,11 +135,22 @@ class QualityManager(QObject):
|
|||
|
||||
return quality_group_dict
|
||||
|
||||
## Get the quality group for the preferred quality type for a certain
|
||||
# global stack.
|
||||
#
|
||||
# If the preferred quality type is not available, ``None`` will be
|
||||
# returned.
|
||||
# \param machine The global stack of the machine to get the preferred
|
||||
# quality group for.
|
||||
# \return The preferred quality group, or ``None`` if that is not
|
||||
# available.
|
||||
def getDefaultQualityType(self, machine: "GlobalStack") -> Optional[QualityGroup]:
|
||||
preferred_quality_type = machine.definition.getMetaDataEntry("preferred_quality_type")
|
||||
quality_group_dict = self.getQualityGroups(machine)
|
||||
quality_group = quality_group_dict.get(preferred_quality_type)
|
||||
return quality_group
|
||||
machine_node = ContainerTree.getInstance().machines[machine.definition.getId()]
|
||||
quality_groups = self.getQualityGroups(machine)
|
||||
result = quality_groups.get(machine_node.preferred_quality_type)
|
||||
if result is not None and result.is_available:
|
||||
return result
|
||||
return None # If preferred quality type is not available, leave it up for the caller.
|
||||
|
||||
|
||||
#
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
from typing import Union, TYPE_CHECKING
|
||||
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Settings.Interfaces import ContainerInterface
|
||||
from cura.Machines.ContainerNode import ContainerNode
|
||||
from cura.Machines.IntentNode import IntentNode
|
||||
|
||||
|
@ -21,7 +20,6 @@ class QualityNode(ContainerNode):
|
|||
super().__init__(container_id)
|
||||
self.parent = parent
|
||||
self.intents = {} # type: Dict[str, IntentNode]
|
||||
ContainerRegistry.getInstance().containerAdded.connect(self._intentAdded)
|
||||
self._loadAll()
|
||||
|
||||
def _loadAll(self) -> None:
|
||||
|
@ -31,21 +29,4 @@ class QualityNode(ContainerNode):
|
|||
if not isinstance(self.parent, MachineNode): # Not a global profile.
|
||||
for intent in container_registry.findInstanceContainersMetadata(type = "intent", definition = self.parent.variant.machine.quality_definition, variant = self.parent.variant.variant_name, material = self.parent.base_file):
|
||||
self.intents[intent["id"]] = IntentNode(intent["id"], quality = self)
|
||||
# Otherwise, there are no intents for global profiles.
|
||||
|
||||
def _intentAdded(self, container: ContainerInterface) -> None:
|
||||
from cura.Machines.MachineNode import MachineNode # Imported here to prevent circular imports.
|
||||
if container.getMetaDataEntry("type") != "intent":
|
||||
return # Not interested if it's not an intent.
|
||||
if isinstance(self.parent, MachineNode):
|
||||
return # Global profiles don't have intents.
|
||||
if container.getMetaDataEntry("definition") != self.parent.variant.machine.quality_definition:
|
||||
return # Incorrect printer.
|
||||
if container.getMetaDataEntry("variant") != self.parent.variant.variant_name:
|
||||
return # Incorrect variant.
|
||||
if container.getMetaDataEntry("material") != self.parent.base_file:
|
||||
return # Incorrect material.
|
||||
container_id = container.getId()
|
||||
if container_id in self.intents:
|
||||
return # Already have this.
|
||||
self.intents[container_id] = IntentNode(container_id, quality = self)
|
||||
# Otherwise, there are no intents for global profiles.
|
|
@ -115,7 +115,7 @@ class VariantManager:
|
|||
variant_type: Optional["VariantType"] = None) -> Optional["ContainerNode"]:
|
||||
if variant_type is None:
|
||||
variant_node = None
|
||||
variant_type_dict = self._machine_to_variant_dict_map[machine_definition_id]
|
||||
variant_type_dict = self._machine_to_variant_dict_map.get("machine_definition_id", {})
|
||||
for variant_dict in variant_type_dict.values():
|
||||
if variant_name in variant_dict:
|
||||
variant_node = variant_dict[variant_name]
|
||||
|
|
|
@ -9,7 +9,6 @@ from typing import Dict, Union, Any, TYPE_CHECKING, List
|
|||
from PyQt5.QtCore import QObject, QUrl
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.FlameProfiler import pyqtSlot
|
||||
from UM.Logger import Logger
|
||||
|
@ -17,18 +16,19 @@ from UM.MimeTypeDatabase import MimeTypeDatabase, MimeTypeNotFoundError
|
|||
from UM.Platform import Platform
|
||||
from UM.SaveFile import SaveFile
|
||||
from UM.Settings.ContainerFormatError import ContainerFormatError
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Settings.ContainerStack import ContainerStack
|
||||
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
import cura.CuraApplication
|
||||
|
||||
from cura.Machines.MaterialManager import MaterialManager
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Machines.ContainerNode import ContainerNode
|
||||
from cura.Machines.MaterialNode import MaterialNode
|
||||
from cura.Machines.QualityChangesGroup import QualityChangesGroup
|
||||
from cura.Machines.MaterialManager import MaterialManager
|
||||
|
||||
from cura.Machines.QualityManager import QualityManager
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
@ -323,22 +323,18 @@ class ContainerManager(QObject):
|
|||
|
||||
## Get a list of materials that have the same GUID as the reference material
|
||||
#
|
||||
# \param material_id \type{str} the id of the material for which to get the linked materials.
|
||||
# \return \type{list} a list of names of materials with the same GUID
|
||||
# \param material_node The node representing the material for which to get
|
||||
# the same GUID.
|
||||
# \param exclude_self Whether to include the name of the material you
|
||||
# provided.
|
||||
# \return A list of names of materials with the same GUID.
|
||||
@pyqtSlot("QVariant", bool, result = "QStringList")
|
||||
def getLinkedMaterials(self, material_node: "MaterialNode", exclude_self: bool = False):
|
||||
guid = material_node.getMetaDataEntry("GUID", "")
|
||||
|
||||
self_root_material_id = material_node.getMetaDataEntry("base_file")
|
||||
material_group_list = MaterialManager.getInstance().getMaterialGroupListByGUID(guid)
|
||||
|
||||
linked_material_names = []
|
||||
if material_group_list:
|
||||
for material_group in material_group_list:
|
||||
if exclude_self and material_group.name == self_root_material_id:
|
||||
continue
|
||||
linked_material_names.append(material_group.root_material_node.getMetaDataEntry("name", ""))
|
||||
return linked_material_names
|
||||
def getLinkedMaterials(self, material_node: "MaterialNode", exclude_self: bool = False) -> List[str]:
|
||||
same_guid = ContainerRegistry.getInstance().findInstanceContainersMetadata(guid = material_node.guid)
|
||||
if exclude_self:
|
||||
return [metadata["name"] for metadata in same_guid if metadata["base_file"] != material_node.base_file]
|
||||
else:
|
||||
return [metadata["name"] for metadata in same_guid]
|
||||
|
||||
## Unlink a material from all other materials by creating a new GUID
|
||||
# \param material_id \type{str} the id of the material to create a new GUID for.
|
||||
|
|
|
@ -28,7 +28,6 @@ class CuraStackBuilder:
|
|||
def createMachine(cls, name: str, definition_id: str) -> Optional[GlobalStack]:
|
||||
from cura.CuraApplication import CuraApplication
|
||||
application = CuraApplication.getInstance()
|
||||
quality_manager = application.getQualityManager()
|
||||
registry = application.getContainerRegistry()
|
||||
|
||||
definitions = registry.findDefinitionContainers(id = definition_id)
|
||||
|
@ -64,7 +63,7 @@ class CuraStackBuilder:
|
|||
registry.addContainer(new_extruder)
|
||||
|
||||
preferred_quality_type = machine_definition.getMetaDataEntry("preferred_quality_type")
|
||||
quality_group_dict = quality_manager.getQualityGroups(new_global_stack)
|
||||
quality_group_dict = ContainerTree.getInstance().getCurrentQualityGroups()
|
||||
if not quality_group_dict:
|
||||
# There is no available quality group, set all quality containers to empty.
|
||||
new_global_stack.quality = application.empty_quality_container
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple, TYPE_CHECKING
|
||||
import cura.CuraApplication
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
from cura.Settings.cura_empty_instance_containers import empty_intent_container
|
||||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
|
||||
|
@ -75,7 +76,8 @@ class IntentManager(QObject):
|
|||
# TODO: We now do this (return a default) if the global stack is missing, but not in the code below,
|
||||
# even though there should always be defaults. The problem then is what to do with the quality_types.
|
||||
# Currently _also_ inconsistent with 'currentAvailableIntentCategories', which _does_ return default.
|
||||
quality_groups = application.getQualityManager().getQualityGroups(global_stack)
|
||||
quality_groups = ContainerTree.getInstance().getCurrentQualityGroups()
|
||||
# TODO: These quality nodes in that tree already contain the intent nodes. We can optimise this.
|
||||
available_quality_types = {quality_group.quality_type for quality_group in quality_groups.values() if quality_group.node_for_global is not None}
|
||||
|
||||
final_intent_ids = set() # type: Set[str]
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from cura.PrinterOutput.Models.MaterialOutputModel import MaterialOutputModel
|
||||
|
||||
from ..BaseModel import BaseModel
|
||||
|
@ -24,32 +25,27 @@ class ClusterPrinterConfigurationMaterial(BaseModel):
|
|||
self.material = material
|
||||
super().__init__(**kwargs)
|
||||
|
||||
## Creates a material output model based on this cloud printer material.
|
||||
## Creates a material output model based on this cloud printer material.
|
||||
#
|
||||
# A material is chosen that matches the current GUID. If multiple such
|
||||
# materials are available, read-only materials are preferred and the
|
||||
# material with the earliest alphabetical name will be selected.
|
||||
# \return A material output model that matches the current GUID.
|
||||
def createOutputModel(self) -> MaterialOutputModel:
|
||||
material_manager = CuraApplication.getInstance().getMaterialManager()
|
||||
material_group_list = material_manager.getMaterialGroupListByGUID(self.guid) or []
|
||||
|
||||
# Sort the material groups by "is_read_only = True" first, and then the name alphabetically.
|
||||
read_only_material_group_list = list(filter(lambda x: x.is_read_only, material_group_list))
|
||||
non_read_only_material_group_list = list(filter(lambda x: not x.is_read_only, material_group_list))
|
||||
material_group = None
|
||||
if read_only_material_group_list:
|
||||
read_only_material_group_list = sorted(read_only_material_group_list, key = lambda x: x.name)
|
||||
material_group = read_only_material_group_list[0]
|
||||
elif non_read_only_material_group_list:
|
||||
non_read_only_material_group_list = sorted(non_read_only_material_group_list, key = lambda x: x.name)
|
||||
material_group = non_read_only_material_group_list[0]
|
||||
|
||||
if material_group:
|
||||
container = material_group.root_material_node.container
|
||||
color = container.getMetaDataEntry("color_code")
|
||||
brand = container.getMetaDataEntry("brand")
|
||||
material_type = container.getMetaDataEntry("material")
|
||||
name = container.getName()
|
||||
container_registry = ContainerRegistry.getInstance()
|
||||
same_guid = container_registry.findInstanceContainersMetadata(GUID = self.guid)
|
||||
if same_guid:
|
||||
read_only = sorted(filter(lambda metadata: container_registry.isReadOnly(metadata["id"]), same_guid), key = lambda metadata: metadata["name"])
|
||||
if read_only:
|
||||
material_metadata = read_only[0]
|
||||
else:
|
||||
material_metadata = min(same_guid, key = lambda metadata: metadata["name"])
|
||||
else:
|
||||
color = self.color
|
||||
brand = self.brand
|
||||
material_type = self.material
|
||||
name = "Empty" if self.material == "empty" else "Unknown"
|
||||
material_metadata = {
|
||||
"color_code": self.color,
|
||||
"brand": self.brand,
|
||||
"material": self.material,
|
||||
"name": "Empty" if self.material == "empty" else "Unknown"
|
||||
}
|
||||
|
||||
return MaterialOutputModel(guid=self.guid, type=material_type, brand=brand, color=color, name=name)
|
||||
return MaterialOutputModel(guid = self.guid, type = material_metadata["material"], brand = material_metadata["brand"], color = material_metadata["color_code"], name = material_metadata["name"])
|
||||
|
|
|
@ -244,7 +244,10 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
|
||||
variant_name = container.getMetaDataEntry("variant_name")
|
||||
if variant_name:
|
||||
variant_dict = {"variant_node": variant_manager.getVariantNode(definition_id, variant_name),
|
||||
variant_node = variant_manager.getVariantNode(definition_id, variant_name)
|
||||
if variant_node is None:
|
||||
continue
|
||||
variant_dict = {"variant_node":variant_node ,
|
||||
"material_container": container}
|
||||
machine_variant_map[definition_id][variant_name] = variant_dict
|
||||
continue
|
||||
|
|
|
@ -17,6 +17,8 @@ Item
|
|||
property var resetEnabled: false
|
||||
property var currentItem: null
|
||||
|
||||
property var materialManager: CuraApplication.getMaterialManager()
|
||||
|
||||
property var hasCurrentItem: base.currentItem != null
|
||||
property var isCurrentItemActivated:
|
||||
{
|
||||
|
@ -119,7 +121,7 @@ Item
|
|||
onClicked:
|
||||
{
|
||||
forceActiveFocus();
|
||||
base.newRootMaterialIdToSwitchTo = CuraApplication.getMaterialManager().createMaterial();
|
||||
base.newRootMaterialIdToSwitchTo = base.materialManager.createMaterial();
|
||||
base.toActivateNewMaterial = true;
|
||||
}
|
||||
}
|
||||
|
@ -134,7 +136,7 @@ Item
|
|||
onClicked:
|
||||
{
|
||||
forceActiveFocus();
|
||||
base.newRootMaterialIdToSwitchTo = CuraApplication.getMaterialManager().duplicateMaterial(base.currentItem.container_node);
|
||||
base.newRootMaterialIdToSwitchTo = base.materialManager.duplicateMaterial(base.currentItem.container_node);
|
||||
base.toActivateNewMaterial = true;
|
||||
}
|
||||
}
|
||||
|
@ -145,7 +147,8 @@ Item
|
|||
id: removeMenuButton
|
||||
text: catalog.i18nc("@action:button", "Remove")
|
||||
iconName: "list-remove"
|
||||
enabled: base.hasCurrentItem && !base.currentItem.is_read_only && !base.isCurrentItemActivated && CuraApplication.getMaterialManager().canMaterialBeRemoved(base.currentItem.container_node)
|
||||
enabled: base.hasCurrentItem && !base.currentItem.is_read_only && !base.isCurrentItemActivated && base.materialManager.canMaterialBeRemoved(base.currentItem.container_node)
|
||||
|
||||
onClicked:
|
||||
{
|
||||
forceActiveFocus();
|
||||
|
@ -294,7 +297,7 @@ Item
|
|||
{
|
||||
// Set the active material as the fallback. It will be selected when the current material is deleted
|
||||
base.newRootMaterialIdToSwitchTo = base.active_root_material_id
|
||||
CuraApplication.getMaterialManager().removeMaterial(base.currentItem.container_node);
|
||||
base.materialManager.removeMaterial(base.currentItem.container_node);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,14 +4,6 @@ import pytest
|
|||
from UM.Settings.Interfaces import ContainerInterface
|
||||
from cura.Machines.MachineNode import MachineNode
|
||||
|
||||
|
||||
machine_node_variant_added_test_data = [({"type": "Not a variant!"}, ["Variant One", "Variant Two"]), # Wrong type
|
||||
({"type": "variant", "name": "Variant One"}, ["Variant One", "Variant Two"]), # Name already added
|
||||
({"type": "variant", "name": "Variant Three", "hardware_type": "Not a nozzle"}, ["Variant One", "Variant Two"]), # Wrong hardware type
|
||||
({"type": "variant", "name": "Variant Three", "hardware_type": "nozzle", "definition": "machine_3"}, ["Variant One", "Variant Two"]), # Wrong definition ID
|
||||
({"type": "variant", "name": "Variant Three", "hardware_type": "nozzle", "definition": "machine_1"}, ["Variant One", "Variant Two", "Variant Three"])] # Yay! It's finally added
|
||||
|
||||
|
||||
metadata_dict = {}
|
||||
|
||||
|
||||
|
@ -44,18 +36,4 @@ def test_machineNodeInit(container_registry):
|
|||
# As variants get stored by name, we want to check if those get added.
|
||||
assert "Variant One" in machine_node.variants
|
||||
assert "Variant Two" in machine_node.variants
|
||||
assert len(machine_node.variants) == 2 # And ensure that *only* those two got added.
|
||||
|
||||
|
||||
@pytest.mark.parametrize("metadata,variant_result_list", machine_node_variant_added_test_data)
|
||||
def test_machineNodeVariantAdded(container_registry, metadata, variant_result_list):
|
||||
machine_node = createMachineNode("machine_1", container_registry)
|
||||
|
||||
with patch("cura.Machines.MachineNode.VariantNode"): # We're not testing the variant node here, so patch it out.
|
||||
with patch.dict(metadata_dict, metadata):
|
||||
mocked_container = createMockedInstanceContainer()
|
||||
machine_node._variantAdded(mocked_container)
|
||||
|
||||
assert len(variant_result_list) == len(machine_node.variants)
|
||||
for name in variant_result_list:
|
||||
assert name in machine_node.variants
|
||||
assert len(machine_node.variants) == 2 # And ensure that *only* those two got added.
|
|
@ -6,21 +6,6 @@ from cura.Machines.MaterialNode import MaterialNode
|
|||
instance_container_metadata_dict = {"fdmprinter": {"no_variant": [{"id": "quality_1", "material": "material_1"}]},
|
||||
"machine_1": {"variant_1": {"material_1": [{"id": "quality_2", "material": "material_1"}, {"id": "quality_3","material": "material_1"}]}}}
|
||||
|
||||
|
||||
quality_metadata_machine_quality_test_data = [({"type": "Not a quality"}, ["quality_2", "quality_3"]), # Wrong type
|
||||
({"type": "quality", "definition": "machine_2"}, ["quality_2", "quality_3"]), # Wrong defintion
|
||||
({"type": "quality", "definition": "machine_1", "variant": "variant_2"}, ["quality_2", "quality_3"]), # Wrong variant
|
||||
({"type": "quality", "definition": "machine_1", "variant": "variant_1", "material": "material_2"}, ["quality_2", "quality_3"]), # wrong material
|
||||
|
||||
]
|
||||
|
||||
quality_metadata_no_machine_quality =[({"type": "Not a quality"}, ["quality_1"]), # Wrong type
|
||||
({"type": "quality", "definition": "machine_1"}, ["quality_1"]), # Wrong defintion (it needs fdmprinter)
|
||||
({"type": "quality", "definition": "fdmprinter", "variant": "variant_2"}, ["quality_1", "quality_4"]), # Wrong variant, but should be added (as we ignore the variant)
|
||||
({"type": "quality", "definition": "fdmprinter", "variant": "variant_1", "material": "material_2"}, ["quality_1", "quality_4"]), # wrong material, but should be added (as we ignore the material)
|
||||
({"type": "quality", "definition": "fdmprinter", "variant": "variant_1", "material": "material_1"}, ["quality_1", "quality_4"]),
|
||||
]
|
||||
|
||||
metadata_dict = {}
|
||||
|
||||
|
||||
|
@ -86,45 +71,4 @@ def test_materialNodeInit_MachineQuality(container_registry):
|
|||
|
||||
assert len(node.qualities) == 2
|
||||
assert "quality_2" in node.qualities
|
||||
assert "quality_3" in node.qualities
|
||||
|
||||
|
||||
@pytest.mark.parametrize("metadata,qualities_result_list", quality_metadata_machine_quality_test_data)
|
||||
def test_qualityAdded_hasMachineQuality(container_registry, metadata, qualities_result_list):
|
||||
variant_node = MagicMock()
|
||||
variant_node.variant_name = "variant_1"
|
||||
variant_node.machine.has_machine_quality = True
|
||||
variant_node.machine.quality_definition = "machine_1"
|
||||
|
||||
container = createMockedInstanceContainer("quality_4")
|
||||
|
||||
with patch("cura.Machines.MaterialNode.QualityNode"):
|
||||
with patch("UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value=container_registry)):
|
||||
node = MaterialNode("material_1", variant_node)
|
||||
|
||||
with patch.dict(metadata_dict, metadata):
|
||||
node._qualityAdded(container)
|
||||
|
||||
assert len(qualities_result_list) == len(node.qualities)
|
||||
for name in qualities_result_list:
|
||||
assert name in node.qualities
|
||||
|
||||
|
||||
@pytest.mark.parametrize("metadata,qualities_result_list", quality_metadata_no_machine_quality)
|
||||
def test_qualityAdded_noMachineQuality(container_registry, metadata, qualities_result_list):
|
||||
variant_node = MagicMock()
|
||||
variant_node.variant_name = "variant_1"
|
||||
variant_node.machine.has_machine_quality = False
|
||||
|
||||
container = createMockedInstanceContainer("quality_4")
|
||||
|
||||
with patch("cura.Machines.MaterialNode.QualityNode"):
|
||||
with patch("UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value=container_registry)):
|
||||
node = MaterialNode("material_1", variant_node)
|
||||
|
||||
with patch.dict(metadata_dict, metadata):
|
||||
node._qualityAdded(container)
|
||||
|
||||
assert len(qualities_result_list) == len(node.qualities)
|
||||
for name in qualities_result_list:
|
||||
assert name in node.qualities
|
||||
assert "quality_3" in node.qualities
|
|
@ -1,21 +1,12 @@
|
|||
from unittest.mock import patch, MagicMock
|
||||
import pytest
|
||||
|
||||
from cura.Machines.MaterialNode import MaterialNode
|
||||
from cura.Machines.QualityNode import QualityNode
|
||||
|
||||
|
||||
instance_container_metadata_dict = {"fdmprinter": {"variant_1": {"material_1": [{"id": "intent_1"}, {"id": "intent_2"}]}},
|
||||
"machine_1": {"variant_2": {"material_2": [{"id": "intent_3"}, {"id": "intent_4"}]}}}
|
||||
|
||||
|
||||
intent_metadata_intent_added_data = [({"type": "Not an intent"}, ["intent_3", "intent_4"]), # Wrong type
|
||||
({"type": "intent", "definition": "machine_9000"}, ["intent_3", "intent_4"]), # wrong definition
|
||||
({"type": "intent", "definition": "machine_1", "variant": "variant_299101"}, ["intent_3", "intent_4"]), # wrong variant
|
||||
({"type": "intent", "definition": "machine_1", "variant": "variant_2", "material": "super cool material!"}, ["intent_3", "intent_4"]), # Wrong material
|
||||
({"type": "intent", "definition": "machine_1", "variant": "variant_2", "material": "material_2"}, ["intent_3", "intent_4", "intent_9001"]), # Yay, all good.
|
||||
]
|
||||
|
||||
metadata_dict = {}
|
||||
|
||||
|
||||
|
@ -55,24 +46,4 @@ def test_qualityNode_machine_1(container_registry):
|
|||
|
||||
assert len(node.intents) == 2
|
||||
assert "intent_3" in node.intents
|
||||
assert "intent_4" in node.intents
|
||||
|
||||
@pytest.mark.parametrize("metadata,intent_result_list", intent_metadata_intent_added_data)
|
||||
def test_intentNodeAdded(container_registry, metadata, intent_result_list):
|
||||
material_node = MagicMock()
|
||||
material_node.variant.machine.quality_definition = "machine_1"
|
||||
material_node.variant.variant_name = "variant_2"
|
||||
material_node.base_file = "material_2"
|
||||
|
||||
intent_container = createMockedInstanceContainer("intent_9001")
|
||||
|
||||
with patch("cura.Machines.QualityNode.IntentNode"):
|
||||
with patch("UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value=container_registry)):
|
||||
node = QualityNode("quality_1", material_node)
|
||||
with patch.dict(metadata_dict, metadata):
|
||||
node._intentAdded(intent_container)
|
||||
|
||||
assert len(intent_result_list) == len(node.intents)
|
||||
for identifier in intent_result_list:
|
||||
assert identifier in node.intents
|
||||
|
||||
assert "intent_4" in node.intents
|
Loading…
Add table
Add a link
Reference in a new issue