diff --git a/cura/Machines/ContainerTree.py b/cura/Machines/ContainerTree.py index 2f641973ac..ca24950c78 100644 --- a/cura/Machines/ContainerTree.py +++ b/cura/Machines/ContainerTree.py @@ -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.") diff --git a/cura/Machines/MachineNode.py b/cura/Machines/MachineNode.py index b474ae94bd..f0a001b2d6 100644 --- a/cura/Machines/MachineNode.py +++ b/cura/Machines/MachineNode.py @@ -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) \ No newline at end of file + self.global_qualities[global_quality["quality_type"]] = QualityNode(global_quality["id"], parent = self) \ No newline at end of file diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py index bbe305a01b..4790314b25 100644 --- a/cura/Machines/MaterialManager.py +++ b/cura/Machines/MaterialManager.py @@ -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) diff --git a/cura/Machines/MaterialNode.py b/cura/Machines/MaterialNode.py index 5fa28de77f..9421448287 100644 --- a/cura/Machines/MaterialNode.py +++ b/cura/Machines/MaterialNode.py @@ -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) \ No newline at end of file + 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. \ No newline at end of file diff --git a/cura/Machines/Models/BaseMaterialsModel.py b/cura/Machines/Models/BaseMaterialsModel.py index cd8fc70dbd..188ce1791b 100644 --- a/cura/Machines/Models/BaseMaterialsModel.py +++ b/cura/Machines/Models/BaseMaterialsModel.py @@ -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"], diff --git a/cura/Machines/Models/FavoriteMaterialsModel.py b/cura/Machines/Models/FavoriteMaterialsModel.py index dda06a953a..bab0809ee0 100644 --- a/cura/Machines/Models/FavoriteMaterialsModel.py +++ b/cura/Machines/Models/FavoriteMaterialsModel.py @@ -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()) diff --git a/cura/Machines/Models/GenericMaterialsModel.py b/cura/Machines/Models/GenericMaterialsModel.py index 8a03dcfdeb..78d3641652 100644 --- a/cura/Machines/Models/GenericMaterialsModel.py +++ b/cura/Machines/Models/GenericMaterialsModel.py @@ -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()) diff --git a/cura/Machines/Models/IntentModel.py b/cura/Machines/Models/IntentModel.py index 275087689b..5a44883b76 100644 --- a/cura/Machines/Models/IntentModel.py +++ b/cura/Machines/Models/IntentModel.py @@ -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: diff --git a/cura/Machines/Models/MaterialBrandsModel.py b/cura/Machines/Models/MaterialBrandsModel.py index c4721db5f7..85167632af 100644 --- a/cura/Machines/Models/MaterialBrandsModel.py +++ b/cura/Machines/Models/MaterialBrandsModel.py @@ -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 # diff --git a/cura/Machines/Models/QualityManagementModel.py b/cura/Machines/Models/QualityManagementModel.py index 315ab010bb..2a661ec49e 100644 --- a/cura/Machines/Models/QualityManagementModel.py +++ b/cura/Machines/Models/QualityManagementModel.py @@ -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() diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py index 07ef857af4..57bcaa9c70 100644 --- a/cura/Machines/QualityManager.py +++ b/cura/Machines/QualityManager.py @@ -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. # diff --git a/cura/Machines/QualityNode.py b/cura/Machines/QualityNode.py index 8e58700589..09785056f1 100644 --- a/cura/Machines/QualityNode.py +++ b/cura/Machines/QualityNode.py @@ -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) \ No newline at end of file + # Otherwise, there are no intents for global profiles. \ No newline at end of file diff --git a/cura/Machines/VariantManager.py b/cura/Machines/VariantManager.py index c80545145f..9c074dcf17 100644 --- a/cura/Machines/VariantManager.py +++ b/cura/Machines/VariantManager.py @@ -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] diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index 1438faee96..4edddb9d83 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -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. diff --git a/cura/Settings/CuraStackBuilder.py b/cura/Settings/CuraStackBuilder.py index e5bce516bf..d0424983cd 100644 --- a/cura/Settings/CuraStackBuilder.py +++ b/cura/Settings/CuraStackBuilder.py @@ -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 diff --git a/cura/Settings/IntentManager.py b/cura/Settings/IntentManager.py index 5a3c1a737f..2cfbf11ae2 100644 --- a/cura/Settings/IntentManager.py +++ b/cura/Settings/IntentManager.py @@ -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] diff --git a/plugins/UM3NetworkPrinting/src/Models/Http/ClusterPrinterConfigurationMaterial.py b/plugins/UM3NetworkPrinting/src/Models/Http/ClusterPrinterConfigurationMaterial.py index aea244b31d..8edb9fb808 100644 --- a/plugins/UM3NetworkPrinting/src/Models/Http/ClusterPrinterConfigurationMaterial.py +++ b/plugins/UM3NetworkPrinting/src/Models/Http/ClusterPrinterConfigurationMaterial.py @@ -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"]) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 2ab9e701eb..157d871d54 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -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 diff --git a/resources/qml/Preferences/Materials/MaterialsPage.qml b/resources/qml/Preferences/Materials/MaterialsPage.qml index a0ce3c4b49..481a256501 100644 --- a/resources/qml/Preferences/Materials/MaterialsPage.qml +++ b/resources/qml/Preferences/Materials/MaterialsPage.qml @@ -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); } } diff --git a/tests/Machines/TestMachineNode.py b/tests/Machines/TestMachineNode.py index db3946fc3a..fe0330609a 100644 --- a/tests/Machines/TestMachineNode.py +++ b/tests/Machines/TestMachineNode.py @@ -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 \ No newline at end of file + assert len(machine_node.variants) == 2 # And ensure that *only* those two got added. \ No newline at end of file diff --git a/tests/Machines/TestMaterialNode.py b/tests/Machines/TestMaterialNode.py index 099be2b891..f9ea0e3622 100644 --- a/tests/Machines/TestMaterialNode.py +++ b/tests/Machines/TestMaterialNode.py @@ -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 \ No newline at end of file + assert "quality_3" in node.qualities \ No newline at end of file diff --git a/tests/Machines/TestQualityNode.py b/tests/Machines/TestQualityNode.py index cad30d642c..64685689c2 100644 --- a/tests/Machines/TestQualityNode.py +++ b/tests/Machines/TestQualityNode.py @@ -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 \ No newline at end of file