# Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. # This collects a lot of quality and quality changes related code which was split between ContainerManager # and the MachineManager and really needs to usable from both. from typing import Any, Dict, List, Optional, TYPE_CHECKING from UM.Application import Application from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.InstanceContainer import InstanceContainer from cura.Settings.ExtruderManager import ExtruderManager if TYPE_CHECKING: from cura.Settings.GlobalStack import GlobalStack from cura.Settings.ExtruderStack import ExtruderStack from UM.Settings.DefinitionContainer import DefinitionContainerInterface class QualityManager: ## Get the singleton instance for this class. @classmethod def getInstance(cls) -> "QualityManager": # Note: Explicit use of class name to prevent issues with inheritance. if not QualityManager.__instance: QualityManager.__instance = cls() return QualityManager.__instance __instance = None # type: "QualityManager" ## Find a quality by name for a specific machine definition and materials. # # \param quality_name # \param machine_definition (Optional) \type{DefinitionContainerInterface} If nothing is # specified then the currently selected machine definition is used. # \param material_containers_metadata If nothing is specified then the # current set of selected materials is used. # \return the matching quality container \type{InstanceContainer} def findQualityByName(self, quality_name: str, machine_definition: Optional["DefinitionContainerInterface"] = None, material_containers_metadata: Optional[List[Dict[str, Any]]] = None) -> Optional[InstanceContainer]: criteria = {"type": "quality", "name": quality_name} result = self._getFilteredContainersForStack(machine_definition, material_containers_metadata, **criteria) # Fall back to using generic materials and qualities if nothing could be found. if not result and material_containers_metadata and len(material_containers_metadata) == 1: basic_materials = self._getBasicMaterialMetadatas(material_containers_metadata[0]) result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria) return result[0] if result else None ## Find a quality changes container by name. # # \param quality_changes_name \type{str} the name of the quality changes container. # \param machine_definition (Optional) \type{DefinitionContainer} If nothing is # specified then the currently selected machine definition is used.. # \return the matching quality changes containers \type{List[InstanceContainer]} def findQualityChangesByName(self, quality_changes_name: str, machine_definition: Optional["DefinitionContainerInterface"] = None): if not machine_definition: global_stack = Application.getGlobalContainerStack() if not global_stack: return [] #No stack, so no current definition could be found, so there are no quality changes either. machine_definition = global_stack.definition result = self.findAllQualityChangesForMachine(machine_definition) result = [quality_change for quality_change in result if quality_change.getName() == quality_changes_name] return result ## Fetch the list of available quality types for this combination of machine definition and materials. # # \param machine_definition \type{DefinitionContainer} # \param material_containers \type{List[InstanceContainer]} # \return \type{List[str]} def findAllQualityTypesForMachineAndMaterials(self, machine_definition: "DefinitionContainerInterface", material_containers: List[InstanceContainer]) -> List[str]: # Determine the common set of quality types which can be # applied to all of the materials for this machine. quality_type_dict = self.__fetchQualityTypeDictForMaterial(machine_definition, material_containers[0]) common_quality_types = set(quality_type_dict.keys()) for material_container in material_containers[1:]: next_quality_type_dict = self.__fetchQualityTypeDictForMaterial(machine_definition, material_container) common_quality_types.intersection_update(set(next_quality_type_dict.keys())) return list(common_quality_types) def findAllQualitiesForMachineAndMaterials(self, machine_definition: "DefinitionContainerInterface", material_containers: List[InstanceContainer]) -> List[InstanceContainer]: # Determine the common set of quality types which can be # applied to all of the materials for this machine. quality_type_dict = self.__fetchQualityTypeDictForMaterial(machine_definition, material_containers[0]) qualities = set(quality_type_dict.values()) for material_container in material_containers[1:]: next_quality_type_dict = self.__fetchQualityTypeDictForMaterial(machine_definition, material_container) qualities.intersection_update(set(next_quality_type_dict.values())) return list(qualities) ## Fetches a dict of quality types names to quality profiles for a combination of machine and material. # # \param machine_definition \type{DefinitionContainer} the machine definition. # \param material \type{InstanceContainer} the material. # \return \type{Dict[str, InstanceContainer]} the dict of suitable quality type names mapping to qualities. def __fetchQualityTypeDictForMaterial(self, machine_definition: "DefinitionContainerInterface", material: InstanceContainer) -> Dict[str, InstanceContainer]: qualities = self.findAllQualitiesForMachineMaterial(machine_definition, material) quality_type_dict = {} for quality in qualities: quality_type_dict[quality.getMetaDataEntry("quality_type")] = quality return quality_type_dict ## Find a quality container by quality type. # # \param quality_type \type{str} the name of the quality type to search for. # \param machine_definition (Optional) \type{InstanceContainer} If nothing is # specified then the currently selected machine definition is used. # \param material_containers_metadata If nothing is specified then the # current set of selected materials is used. # \return the matching quality container \type{InstanceContainer} def findQualityByQualityType(self, quality_type: str, machine_definition: Optional["DefinitionContainerInterface"] = None, material_containers_metadata: Optional[List[Dict[str, Any]]] = None, **kwargs) -> InstanceContainer: criteria = kwargs criteria["type"] = "quality" if quality_type: criteria["quality_type"] = quality_type result = self._getFilteredContainersForStack(machine_definition, material_containers_metadata, **criteria) # Fall back to using generic materials and qualities if nothing could be found. if not result and material_containers_metadata and len(material_containers_metadata) == 1: basic_materials = self._getBasicMaterialMetadatas(material_containers_metadata[0]) if basic_materials: result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria) return result[0] if result else None ## Find all suitable qualities for a combination of machine and material. # # \param machine_definition \type{DefinitionContainer} the machine definition. # \param material_container \type{InstanceContainer} the material. # \return \type{List[InstanceContainer]} the list of suitable qualities. def findAllQualitiesForMachineMaterial(self, machine_definition: "DefinitionContainerInterface", material_container: InstanceContainer) -> List[InstanceContainer]: criteria = {"type": "quality"} result = self._getFilteredContainersForStack(machine_definition, [material_container.getMetaData()], **criteria) if not result: basic_materials = self._getBasicMaterialMetadatas(material_container.getMetaData()) if basic_materials: result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria) return result ## Find all quality changes for a machine. # # \param machine_definition \type{DefinitionContainer} the machine definition. # \return \type{List[InstanceContainer]} the list of quality changes def findAllQualityChangesForMachine(self, machine_definition: "DefinitionContainerInterface") -> List[InstanceContainer]: if machine_definition.getMetaDataEntry("has_machine_quality"): definition_id = machine_definition.getId() else: definition_id = "fdmprinter" filter_dict = { "type": "quality_changes", "definition": definition_id } quality_changes_list = ContainerRegistry.getInstance().findInstanceContainers(**filter_dict) return quality_changes_list def findAllExtruderDefinitionsForMachine(self, machine_definition: "DefinitionContainerInterface") -> List["DefinitionContainerInterface"]: filter_dict = { "machine": machine_definition.getId() } return ContainerRegistry.getInstance().findDefinitionContainers(**filter_dict) ## Find all quality changes for a given extruder. # # \param extruder_definition The extruder to find the quality changes for. # \return The list of quality changes for the given extruder. def findAllQualityChangesForExtruder(self, extruder_definition: "DefinitionContainerInterface") -> List[InstanceContainer]: filter_dict = {"type": "quality_changes", "extruder": extruder_definition.getId()} return ContainerRegistry.getInstance().findInstanceContainers(**filter_dict) ## Find all usable qualities for a machine and extruders. # # Finds all of the qualities for this combination of machine and extruders. # Only one quality per quality type is returned. i.e. if there are 2 qualities with quality_type=normal # then only one of then is returned (at random). # # \param global_container_stack \type{GlobalStack} the global machine definition # \param extruder_stacks \type{List[ExtruderStack]} the list of extruder stacks # \return \type{List[InstanceContainer]} the list of the matching qualities. The quality profiles # return come from the first extruder in the given list of extruders. def findAllUsableQualitiesForMachineAndExtruders(self, global_container_stack: "GlobalStack", extruder_stacks: List["ExtruderStack"]) -> List[InstanceContainer]: global_machine_definition = global_container_stack.getBottom() machine_manager = Application.getInstance().getMachineManager() active_stack_id = machine_manager.activeStackId materials = [] for stack in extruder_stacks: if stack.getId() == active_stack_id and machine_manager.newMaterial: materials.append(machine_manager.newMaterial) else: materials.append(stack.material) quality_types = self.findAllQualityTypesForMachineAndMaterials(global_machine_definition, materials) # Map the list of quality_types to InstanceContainers qualities = self.findAllQualitiesForMachineMaterial(global_machine_definition, materials[0]) quality_type_dict = {} for quality in qualities: quality_type_dict[quality.getMetaDataEntry("quality_type")] = quality return [quality_type_dict[quality_type] for quality_type in quality_types] ## Fetch more basic versions of a material. # # This tries to find a generic or basic version of the given material. # \param material_container \type{Dict[str, Any]} The metadata of a # material to find the basic versions of. # \return \type{List[Dict[str, Any]]} A list of the metadata of basic # materials, or an empty list if none could be found. def _getBasicMaterialMetadatas(self, material_container: Dict[str, Any]) -> List[Dict[str, Any]]: if "definition" not in material_container: definition_id = "fdmprinter" else: material_container_definition = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = material_container["definition"]) if not material_container_definition: definition_id = "fdmprinter" else: material_container_definition = material_container_definition[0] if "has_machine_quality" not in material_container_definition: definition_id = "fdmprinter" else: definition_id = material_container_definition.get("quality_definition", material_container_definition["id"]) base_material = material_container.get("material") if base_material: # There is a basic material specified criteria = { "type": "material", "name": base_material, "definition": definition_id, "variant": material_container.get("variant") } containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(**criteria) return containers return [] def _getFilteredContainers(self, **kwargs): return self._getFilteredContainersForStack(None, None, **kwargs) def _getFilteredContainersForStack(self, machine_definition: "DefinitionContainerInterface" = None, material_metadata: Optional[List[Dict[str, Any]]] = None, **kwargs): # Fill in any default values. if machine_definition is None: machine_definition = Application.getInstance().getGlobalContainerStack().getBottom() quality_definition_id = machine_definition.getMetaDataEntry("quality_definition") if quality_definition_id is not None: machine_definition = ContainerRegistry.getInstance().findDefinitionContainers(id = quality_definition_id)[0] if not material_metadata: active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() if active_stacks: material_metadata = [stack.material.getMetaData() for stack in active_stacks] criteria = kwargs filter_by_material = False machine_definition = self.getParentMachineDefinition(machine_definition) criteria["definition"] = machine_definition.getId() found_containers_with_machine_definition = ContainerRegistry.getInstance().findInstanceContainersMetadata(**criteria) whole_machine_definition = self.getWholeMachineDefinition(machine_definition) if whole_machine_definition.getMetaDataEntry("has_machine_quality"): definition_id = machine_definition.getMetaDataEntry("quality_definition", whole_machine_definition.getId()) criteria["definition"] = definition_id filter_by_material = whole_machine_definition.getMetaDataEntry("has_materials") # only fall back to "fdmprinter" when there is no container for this machine elif not found_containers_with_machine_definition: criteria["definition"] = "fdmprinter" # Stick the material IDs in a set material_ids = set() for material_instance in material_metadata: if material_instance is not None: # Add the parent material too. for basic_material in self._getBasicMaterialMetadatas(material_instance): material_ids.add(basic_material["id"]) material_ids.add(material_instance["id"]) containers = ContainerRegistry.getInstance().findInstanceContainers(**criteria) result = [] for container in containers: # If the machine specifies we should filter by material, exclude containers that do not match any active material. if filter_by_material and container.getMetaDataEntry("material") not in material_ids and "global_quality" not in kwargs: continue result.append(container) return result ## Get the parent machine definition of a machine definition. # # \param machine_definition \type{DefinitionContainer} This may be a normal machine definition or # an extruder definition. # \return \type{DefinitionContainer} the parent machine definition. If the given machine # definition doesn't have a parent then it is simply returned. def getParentMachineDefinition(self, machine_definition: "DefinitionContainerInterface") -> "DefinitionContainerInterface": container_registry = ContainerRegistry.getInstance() machine_entry = machine_definition.getMetaDataEntry("machine") if machine_entry is None: # We have a normal (whole) machine defintion quality_definition = machine_definition.getMetaDataEntry("quality_definition") if quality_definition is not None: parent_machine_definition = container_registry.findDefinitionContainers(id = quality_definition)[0] return self.getParentMachineDefinition(parent_machine_definition) else: return machine_definition else: # This looks like an extruder. Find the rest of the machine. whole_machine = container_registry.findDefinitionContainers(id = machine_entry)[0] parent_machine = self.getParentMachineDefinition(whole_machine) if whole_machine is parent_machine: # This extruder already belongs to a 'parent' machine def. return machine_definition else: # Look up the corresponding extruder definition in the parent machine definition. extruder_position = machine_definition.getMetaDataEntry("position") parent_extruder_id = parent_machine.getMetaDataEntry("machine_extruder_trains")[extruder_position] return container_registry.findDefinitionContainers(id = parent_extruder_id)[0] ## Get the whole/global machine definition from an extruder definition. # # \param machine_definition \type{DefinitionContainer} This may be a normal machine definition or # an extruder definition. # \return \type{DefinitionContainerInterface} def getWholeMachineDefinition(self, machine_definition: "DefinitionContainerInterface") -> "DefinitionContainerInterface": machine_entry = machine_definition.getMetaDataEntry("machine") if machine_entry is None: # This already is a 'global' machine definition. return machine_definition else: container_registry = ContainerRegistry.getInstance() whole_machine = container_registry.findDefinitionContainers(id = machine_entry)[0] return whole_machine