Cura/cura/QualityManager.py
Simon Edwards f659d01da1 Make the labels in the quality menus match those from the selected extruder.
CURA-2271 Warn for unsupported material/printcore combinations
2016-10-13 10:55:32 +02:00

271 lines
15 KiB
Python

# Copyright (c) 2016 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
import UM.Application
import cura.Settings.ExtruderManager
import UM.Settings.ContainerRegistry
# 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.
class QualityManager:
## Get the singleton instance for this class.
@classmethod
def getInstance(cls):
# Note: Explicit use of class name to prevent issues with inheritance.
if QualityManager.__instance is None:
QualityManager.__instance = cls()
return QualityManager.__instance
__instance = None
## Find a quality by name for a specific machine definition and materials.
#
# \param quality_name
# \param machine_definition (Optional) \type{ContainerInstance} If nothing is
# specified then the currently selected machine definition is used.
# \param material_containers (Optional) \type{List[ContainerInstance]} If nothing is specified then
# the current set of selected materials is used.
# \return the matching quality containers \type{List[ContainerInstance]}
def findQualityByName(self, quality_name, machine_definition=None, material_containers=None):
criteria = {"type": "quality", "name": quality_name}
return self._getFilteredContainersForStack(machine_definition, material_containers, **criteria)
## 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{ContainerInstance} If nothing is
# specified then the currently selected machine definition is used.
# \param material_containers (Optional) \type{List[ContainerInstance]} If nothing is specified then
# the current set of selected materials is used.
# \return the matching quality changes containers \type{List[ContainerInstance]}
def findQualityChangesByName(self, quality_changes_name, machine_definition=None):
criteria = {"type": "quality_changes", "name": quality_changes_name}
result = self._getFilteredContainersForStack(machine_definition, [], **criteria)
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, material_containers):
# 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)
## 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{ContainerInstance} the material.
# \return \type{Dict[str, ContainerInstance]} the dict of suitable quality type names mapping to qualities.
def __fetchQualityTypeDictForMaterial(self, machine_definition, material):
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{ContainerInstance} If nothing is
# specified then the currently selected machine definition is used.
# \param material_containers (Optional) \type{List[ContainerInstance]} If nothing is specified then
# the current set of selected materials is used.
# \return the matching quality container \type{ContainerInstance}
def findQualityByQualityType(self, quality_type, machine_definition=None, material_containers=None):
criteria = {"type": "quality"}
if quality_type:
criteria["quality_type"] = quality_type
result = self._getFilteredContainersForStack(machine_definition, material_containers, **criteria)
# Fall back to using generic materials and qualities if nothing could be found.
if not result and material_containers and len(material_containers) == 1:
basic_materials = self._getBasicMaterials(material_containers[0])
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{ContainerInstance} the material.
# \return \type{List[ContainerInstance]} the list of suitable qualities.
def findAllQualitiesForMachineMaterial(self, machine_definition, material_container):
criteria = {"type": "quality" }
result = self._getFilteredContainersForStack(machine_definition, [material_container], **criteria)
if not result:
basic_materials = self._getBasicMaterials(material_container)
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):
if machine_definition.getMetaDataEntry("has_machine_quality"):
definition_id = machine_definition.getId()
else:
definition_id = "fdmprinter"
filter_dict = { "type": "quality_changes", "extruder": None, "definition": definition_id }
quality_changes_list = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**filter_dict)
return quality_changes_list
## 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{ContainerStack} the global machine definition
# \param extruder_stacks \type{List[ContainerStack]} 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, extruder_stacks):
global_machine_definition = global_container_stack.getBottom()
if extruder_stacks:
# Multi-extruder machine detected.
materials = [stack.findContainer(type="material") for stack in extruder_stacks]
else:
# Machine with one extruder.
materials = [global_container_stack.findContainer(type="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{InstanceContainer} the material
# \return \type{List[InstanceContainer]} a list of the basic materials or an empty list if one could not be found.
def _getBasicMaterials(self, material_container):
base_material = material_container.getMetaDataEntry("material")
if material_container.getDefinition().getMetaDataEntry("has_machine_quality"):
definition_id = material_container.getDefinition().getMetaDataEntry("quality_definition", material_container.getDefinition().getId())
else:
definition_id = "fdmprinter"
if base_material:
# There is a basic material specified
criteria = { "type": "material", "name": base_material, "definition": definition_id }
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**criteria)
containers = [basic_material for basic_material in containers if
basic_material.getMetaDataEntry("variant") == material_container.getMetaDataEntry(
"variant")]
return containers
return []
def _getFilteredContainers(self, **kwargs):
return self._getFilteredContainersForStack(None, None, **kwargs)
def _getFilteredContainersForStack(self, machine_definition=None, material_containers=None, **kwargs):
# Fill in any default values.
if machine_definition is None:
machine_definition = UM.Application.getInstance().getGlobalContainerStack().getBottom()
quality_definition_id = machine_definition.getMetaDataEntry("quality_definition")
if quality_definition_id is not None:
machine_definition = UM.Settings.ContainerRegistry.getInstance().findDefinitionContainers(id=quality_definition_id)[0]
if material_containers is None:
active_stacks = cura.Settings.ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
material_containers = [stack.findContainer(type="material") for stack in active_stacks]
criteria = kwargs
filter_by_material = False
machine_definition = self.getParentMachineDefinition(machine_definition)
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")
else:
criteria["definition"] = "fdmprinter"
# Stick the material IDs in a set
if material_containers is None or len(material_containers) == 0:
filter_by_material = False
else:
material_ids = set()
for material_instance in material_containers:
if material_instance is not None:
material_ids.add(material_instance.getId())
containers = UM.Settings.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:
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):
container_registry = UM.Settings.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{DefinitionContainer}
def getWholeMachineDefinition(self, machine_definition):
machine_entry = machine_definition.getMetaDataEntry("machine")
if machine_entry is None:
# This already is a 'global' machine definition.
return machine_definition
else:
container_registry = UM.Settings.ContainerRegistry.getInstance()
whole_machine = container_registry.findDefinitionContainers(id=machine_entry)[0]
return whole_machine