CURA-4400 fixed merge conflicts

This commit is contained in:
Jack Ha 2018-03-05 16:47:40 +01:00
commit bad637eb14
121 changed files with 1157 additions and 936 deletions

View file

@ -1,6 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
#
# A MaterialGroup represents a group of material InstanceContainers that are derived from a single material profile.
# The main InstanceContainer which has the ID of the material profile file name is called the "root_material". For

View file

@ -2,16 +2,24 @@
# Cura is released under the terms of the LGPLv3 or higher.
from collections import defaultdict, OrderedDict
from typing import Optional
import copy
import uuid
from typing import Optional, TYPE_CHECKING
from PyQt5.Qt import QTimer, QObject, pyqtSignal
from PyQt5.Qt import QTimer, QObject, pyqtSignal, pyqtSlot
from UM.Application import Application
from UM.Logger import Logger
from UM.Settings import ContainerRegistry
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.SettingFunction import SettingFunction
from UM.Util import parseBool
from .MaterialNode import MaterialNode
from .MaterialGroup import MaterialGroup
if TYPE_CHECKING:
from cura.Settings.GlobalStack import GlobalStack
#
# MaterialManager maintains a number of maps and trees for material lookup.
@ -29,6 +37,7 @@ class MaterialManager(QObject):
def __init__(self, container_registry, parent = None):
super().__init__(parent)
self._application = Application.getInstance()
self._container_registry = container_registry # type: ContainerRegistry
self._fallback_materials_map = dict() # material_type -> generic material metadata
@ -260,6 +269,20 @@ class MaterialManager(QObject):
return material_id_metadata_dict
#
# 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]:
machine_definition_id = machine.definition.getId()
variant_name = None
if extruder_stack.variant.getId() != "empty_variant":
variant_name = extruder_stack.variant.getName()
diameter = extruder_stack.approximateMaterialDiameter
# Fetch the available materials (ContainerNode) for the current active machine and extruder setup.
return self.getAvailableMaterials(machine_definition_id, variant_name, diameter)
#
# Gets MaterialNode for the given extruder and machine with the given material name.
# Returns None if:
@ -315,7 +338,7 @@ class MaterialManager(QObject):
# This function returns the generic root material ID for the given material type, where material types are "PLA",
# "ABS", etc.
#
def getFallbackMaterialIdByMaterialType(self, material_type: str) -> str:
def getFallbackMaterialIdByMaterialType(self, material_type: str) -> Optional[str]:
# For safety
if material_type not in self._fallback_materials_map:
Logger.log("w", "The material type [%s] does not have a fallback material" % material_type)
@ -325,3 +348,132 @@ class MaterialManager(QObject):
return self.getRootMaterialIDWithoutDiameter(fallback_material["id"])
else:
return None
def getDefaultMaterial(self, global_stack: "GlobalStack", extruder_variant_name: str) -> Optional["MaterialNode"]:
node = None
machine_definition = global_stack.definition
if parseBool(machine_definition.getMetaDataEntry("has_materials", False)):
material_diameter = machine_definition.getProperty("material_diameter", "value")
if isinstance(material_diameter, SettingFunction):
material_diameter = material_diameter(global_stack)
approximate_material_diameter = str(round(material_diameter))
root_material_id = machine_definition.getMetaDataEntry("preferred_material")
root_material_id = self.getRootMaterialIDForDiameter(root_material_id, approximate_material_diameter)
node = self.getMaterialNode(machine_definition.getId(), extruder_variant_name,
material_diameter, root_material_id)
return node
#
# Methods for GUI
#
#
# Sets the new name for the given material.
#
@pyqtSlot("QVariant", str)
def setMaterialName(self, material_node: "MaterialNode", name: str):
root_material_id = material_node.metadata["base_file"]
if self._container_registry.isReadOnly(root_material_id):
Logger.log("w", "Cannot set name of read-only container %s.", root_material_id)
return
material_group = self.getMaterialGroup(root_material_id)
material_group.root_material_node.getContainer().setName(name)
#
# Removes the given material.
#
@pyqtSlot("QVariant")
def removeMaterial(self, material_node: "MaterialNode"):
root_material_id = material_node.metadata["base_file"]
material_group = self.getMaterialGroup(root_material_id)
if not material_group:
Logger.log("d", "Unable to remove the material with id %s, because it doesn't exist.", root_material_id)
return
nodes_to_remove = [material_group.root_material_node] + material_group.derived_material_node_list
for node in nodes_to_remove:
self._container_registry.removeContainer(node.metadata["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, new_base_id = None, new_metadata = None) -> Optional[str]:
root_material_id = material_node.metadata["base_file"]
material_group = self.getMaterialGroup(root_material_id)
if not material_group:
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.getContainer()
# Ensure all settings are saved.
self._application.saveSettings()
# Create a new ID & container to hold the data.
new_containers = []
if new_base_id is None:
new_base_id = self._container_registry.uniqueName(base_container.getId())
new_base_container = copy.deepcopy(base_container)
new_base_container.getMetaData()["id"] = new_base_id
new_base_container.getMetaData()["base_file"] = new_base_id
if new_metadata is not None:
for key, value in new_metadata.items():
new_base_container.getMetaData()[key] = value
new_containers.append(new_base_container)
# Clone all of them.
for node in material_group.derived_material_node_list:
container_to_copy = node.getContainer()
# Create unique IDs for every clone.
new_id = new_base_id
if container_to_copy.getMetaDataEntry("definition") != "fdmprinter":
new_id += "_" + container_to_copy.getMetaDataEntry("definition")
if container_to_copy.getMetaDataEntry("variant_name"):
variant_name = container_to_copy.getMetaDataEntry("variant_name")
new_id += "_" + variant_name.replace(" ", "_")
new_container = copy.deepcopy(container_to_copy)
new_container.getMetaData()["id"] = new_id
new_container.getMetaData()["base_file"] = new_base_id
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:
container_to_add.setDirty(True)
self._container_registry.addContainer(container_to_add)
return new_base_id
#
# Create a new material by cloning Generic PLA for the current material diameter and generate a new GUID.
#
@pyqtSlot(result = str)
def createMaterial(self) -> str:
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
# Ensure all settings are saved.
self._application.saveSettings()
global_stack = self._application.getGlobalContainerStack()
approximate_diameter = str(round(global_stack.getProperty("material_diameter", "value")))
root_material_id = "generic_pla"
root_material_id = self.getRootMaterialIDForDiameter(root_material_id, approximate_diameter)
material_group = self.getMaterialGroup(root_material_id)
# Create a new ID & container to hold the data.
new_id = self._container_registry.uniqueName("custom_material")
new_metadata = {"name": catalog.i18nc("@label", "Custom Material"),
"brand": catalog.i18nc("@label", "Custom"),
"GUID": str(uuid.uuid4()),
}
self.duplicateMaterial(material_group.root_material_node,
new_base_id = new_id,
new_metadata = new_metadata)
return new_id

View file

@ -1,41 +1,17 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
from UM.Logger import Logger
from UM.Qt.ListModel import ListModel
def getAvailableMaterials(extruder_position: Optional[int] = None):
from cura.CuraApplication import CuraApplication
machine_manager = CuraApplication.getInstance().getMachineManager()
extruder_manager = CuraApplication.getInstance().getExtruderManager()
material_manager = CuraApplication.getInstance().getMaterialManager()
active_global_stack = machine_manager.activeMachine
extruder_stack = extruder_manager.getActiveExtruderStack()
if extruder_position is not None:
if active_global_stack is not None:
extruder_stack = active_global_stack.extruders.get(str(extruder_position))
if active_global_stack is None or extruder_stack is None:
Logger.log("d", "Active global stack [%s] or extruder stack [%s] is None, setting material list to empty.",
active_global_stack, extruder_stack)
return
machine_definition_id = active_global_stack.definition.getId()
variant_name = None
if extruder_stack.variant.getId() != "empty_variant":
variant_name = extruder_stack.variant.getName()
diameter = extruder_stack.approximateMaterialDiameter
# Fetch the available materials (ContainerNode) for the current active machine and extruder setup.
result_dict = material_manager.getAvailableMaterials(machine_definition_id, variant_name, diameter)
return result_dict
#
# This is the base model class for GenericMaterialsModel and BrandMaterialsModel
# Those 2 models are used by the material drop down menu to show generic materials and branded materials separately.
# The extruder position defined here is being used to bound a menu to the correct extruder. This is used in the top
# bar menu "Settings" -> "Extruder nr" -> "Material" -> this menu
#
class BaseMaterialsModel(ListModel):
RootMaterialIdRole = Qt.UserRole + 1
IdRole = Qt.UserRole + 2

View file

@ -5,9 +5,12 @@ from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
from UM.Qt.ListModel import ListModel
from .BaseMaterialsModel import BaseMaterialsModel, getAvailableMaterials
from .BaseMaterialsModel import BaseMaterialsModel
#
# This is an intermediate model to group materials with different colours for a same brand and type.
#
class MaterialsModelGroupedByType(ListModel):
NameRole = Qt.UserRole + 1
ColorsRole = Qt.UserRole + 2
@ -19,7 +22,18 @@ class MaterialsModelGroupedByType(ListModel):
self.addRoleName(self.ColorsRole, "colors")
## Brand --> Material Type -> list of materials
#
# This model is used to show branded materials in the material drop down menu.
# The structure of the menu looks like this:
# Brand -> Material Type -> list of materials
#
# To illustrate, a branded material menu may look like this:
# Ultimaker -> PLA -> Yellow PLA
# -> Black PLA
# -> ...
# -> ABS -> White ABS
# ...
#
class BrandMaterialsModel(ListModel):
NameRole = Qt.UserRole + 1
MaterialsRole = Qt.UserRole + 2
@ -36,12 +50,12 @@ class BrandMaterialsModel(ListModel):
from cura.CuraApplication import CuraApplication
self._machine_manager = CuraApplication.getInstance().getMachineManager()
extruder_manager = CuraApplication.getInstance().getExtruderManager()
material_manager = CuraApplication.getInstance().getMaterialManager()
self._extruder_manager = CuraApplication.getInstance().getExtruderManager()
self._material_manager = CuraApplication.getInstance().getMaterialManager()
self._machine_manager.globalContainerChanged.connect(self._update)
extruder_manager.activeExtruderChanged.connect(self._update)
material_manager.materialsUpdated.connect(self._update)
self._extruder_manager.activeExtruderChanged.connect(self._update)
self._material_manager.materialsUpdated.connect(self._update)
self._update()
@ -59,15 +73,21 @@ class BrandMaterialsModel(ListModel):
if global_stack is None:
self.setItems([])
return
extruder_position = str(self._extruder_position)
if extruder_position not in global_stack.extruders:
self.setItems([])
return
extruder_stack = global_stack.extruders[str(self._extruder_position)]
result_dict = getAvailableMaterials(self._extruder_position)
if result_dict is None:
available_material_dict = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack,
extruder_stack)
if available_material_dict is None:
self.setItems([])
return
brand_item_list = []
brand_group_dict = {}
for root_material_id, container_node in result_dict.items():
for root_material_id, container_node in available_material_dict.items():
metadata = container_node.metadata
brand = metadata["brand"]
# Only add results for generic materials
@ -109,4 +129,3 @@ class BrandMaterialsModel(ListModel):
brand_item_list.append(brand_item)
self.setItems(brand_item_list)

View file

@ -1,21 +1,20 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Application import Application
from UM.Logger import Logger
from cura.Machines.Models.QualityProfilesModel import QualityProfilesModel
from cura.Machines.Models.QualityProfilesDropDownMenuModel import QualityProfilesDropDownMenuModel
#
# This model is used for the custom profile items in the profile drop down menu.
#
class CustomQualityProfilesModel(QualityProfilesModel):
class CustomQualityProfilesDropDownMenuModel(QualityProfilesDropDownMenuModel):
def _update(self):
Logger.log("d", "Updating %s ...", self.__class__.__name__)
active_global_stack = Application.getInstance().getMachineManager().activeMachine
active_global_stack = self._machine_manager.activeMachine
if active_global_stack is None:
self.setItems([])
Logger.log("d", "No active GlobalStack, set %s as empty.", self.__class__.__name__)

View file

@ -1,7 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from .BaseMaterialsModel import BaseMaterialsModel, getAvailableMaterials
from .BaseMaterialsModel import BaseMaterialsModel
class GenericMaterialsModel(BaseMaterialsModel):
@ -25,14 +25,20 @@ class GenericMaterialsModel(BaseMaterialsModel):
if global_stack is None:
self.setItems([])
return
extruder_position = str(self._extruder_position)
if extruder_position not in global_stack.extruders:
self.setItems([])
return
extruder_stack = global_stack.extruders[extruder_position]
result_dict = getAvailableMaterials(self._extruder_position)
if result_dict is None:
available_material_dict = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack,
extruder_stack)
if available_material_dict is None:
self.setItems([])
return
item_list = []
for root_material_id, container_node in result_dict.items():
for root_material_id, container_node in available_material_dict.items():
metadata = container_node.metadata
# Only add results for generic materials
if metadata["brand"].lower() != "generic":

View file

@ -5,8 +5,6 @@ from PyQt5.QtCore import Qt, pyqtProperty
from UM.Qt.ListModel import ListModel
from .BaseMaterialsModel import getAvailableMaterials
#
# This model is for the Material management page.
@ -52,12 +50,12 @@ class MaterialManagementModel(ListModel):
from cura.CuraApplication import CuraApplication
self._container_registry = CuraApplication.getInstance().getContainerRegistry()
self._machine_manager = CuraApplication.getInstance().getMachineManager()
extruder_manager = CuraApplication.getInstance().getExtruderManager()
material_manager = CuraApplication.getInstance().getMaterialManager()
self._extruder_manager = CuraApplication.getInstance().getExtruderManager()
self._material_manager = CuraApplication.getInstance().getMaterialManager()
self._machine_manager.globalContainerChanged.connect(self._update)
extruder_manager.activeExtruderChanged.connect(self._update)
material_manager.materialsUpdated.connect(self._update)
self._extruder_manager.activeExtruderChanged.connect(self._update)
self._material_manager.materialsUpdated.connect(self._update)
self._update()
@ -66,14 +64,16 @@ class MaterialManagementModel(ListModel):
if global_stack is None:
self.setItems([])
return
active_extruder_stack = self._machine_manager.activeStack
result_dict = getAvailableMaterials()
if result_dict is None:
available_material_dict = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack,
active_extruder_stack)
if available_material_dict is None:
self.setItems([])
return
material_list = []
for root_material_id, container_node in result_dict.items():
for root_material_id, container_node in available_material_dict.items():
keys_to_fetch = ("name",
"brand",
"material",

View file

@ -1,3 +1,6 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtSignal, pyqtProperty
from UM.Application import Application
@ -5,6 +8,10 @@ from UM.Scene.Selection import Selection
from UM.Qt.ListModel import ListModel
#
# This is the model for multi build plate feature.
# This has nothing to do with the build plate types you can choose on the sidebar for a machine.
#
class MultiBuildPlateModel(ListModel):
maxBuildPlateChanged = pyqtSignal()

View file

@ -6,6 +6,9 @@ from PyQt5.QtCore import Qt, pyqtSlot
from UM.Qt.ListModel import ListModel
#
# This the QML model for the quality management page.
#
class QualityManagementModel(ListModel):
NameRole = Qt.UserRole + 1
IsReadOnlyRole = Qt.UserRole + 2
@ -24,7 +27,7 @@ class QualityManagementModel(ListModel):
self._container_registry = CuraApplication.getInstance().getContainerRegistry()
self._machine_manager = CuraApplication.getInstance().getMachineManager()
self._extruder_manager = CuraApplication.getInstance().getExtruderManager()
self._quality_manager = CuraApplication.getInstance()._quality_manager
self._quality_manager = CuraApplication.getInstance().getQualityManager()
self._machine_manager.globalContainerChanged.connect(self._update)
self._quality_manager.qualitiesUpdated.connect(self._update)
@ -32,12 +35,13 @@ class QualityManagementModel(ListModel):
self._update()
def _update(self):
global_stack = self._machine_manager._global_container_stack
global_stack = self._machine_manager.activeMachine
quality_group_dict = self._quality_manager.getQualityGroups(global_stack)
quality_changes_group_dict = self._quality_manager.getQualityChangesGroups(global_stack)
available_quality_types = set(qt for qt, qg in quality_group_dict.items() if qg.is_available)
available_quality_types = set(quality_type for quality_type, quality_group in quality_group_dict.items()
if quality_group.is_available)
if not available_quality_types and not quality_changes_group_dict:
# Nothing to show
self.setItems([])

View file

@ -6,17 +6,18 @@ from PyQt5.QtCore import Qt
from UM.Application import Application
from UM.Logger import Logger
from UM.Qt.ListModel import ListModel
from cura.Machines.QualityManager import QualityGroup
#
# QML Model for all built-in quality profiles.
# QML Model for all built-in quality profiles. This model is used for the drop-down quality menu.
#
class QualityProfilesModel(ListModel):
class QualityProfilesDropDownMenuModel(ListModel):
NameRole = Qt.UserRole + 1
QualityTypeRole = Qt.UserRole + 2
LayerHeightRole = Qt.UserRole + 3
LayerHeightWithoutUnitRole = Qt.UserRole + 4
LayerHeightUnitRole = Qt.UserRole + 4
AvailableRole = Qt.UserRole + 5
QualityGroupRole = Qt.UserRole + 6
QualityChangesGroupRole = Qt.UserRole + 7
@ -27,17 +28,18 @@ class QualityProfilesModel(ListModel):
self.addRoleName(self.NameRole, "name")
self.addRoleName(self.QualityTypeRole, "quality_type")
self.addRoleName(self.LayerHeightRole, "layer_height")
self.addRoleName(self.LayerHeightWithoutUnitRole, "layer_height_without_unit")
self.addRoleName(self.LayerHeightUnitRole, "layer_height_unit")
self.addRoleName(self.AvailableRole, "available")
self.addRoleName(self.QualityGroupRole, "quality_group")
self.addRoleName(self.QualityChangesGroupRole, "quality_changes_group")
# connect signals
Application.getInstance().globalContainerStackChanged.connect(self._update)
Application.getInstance().getMachineManager().activeQualityGroupChanged.connect(self._update)
Application.getInstance().getMachineManager().extruderChanged.connect(self._update)
self._application = Application.getInstance()
self._machine_manager = self._application.getMachineManager()
self._quality_manager = Application.getInstance().getQualityManager()
self._quality_manager = Application.getInstance()._quality_manager
self._application.globalContainerStackChanged.connect(self._update)
self._machine_manager.activeQualityGroupChanged.connect(self._update)
self._machine_manager.extruderChanged.connect(self._update)
self._quality_manager.qualitiesUpdated.connect(self._update)
self._layer_height_unit = "" # This is cached
@ -47,15 +49,15 @@ class QualityProfilesModel(ListModel):
def _update(self):
Logger.log("d", "Updating quality profile model ...")
machine_manager = Application.getInstance().getMachineManager()
global_stack = machine_manager._global_container_stack
global_stack = self._machine_manager.activeMachine
if global_stack is None:
self.setItems([])
Logger.log("d", "No active GlobalStack, set quality profile model as empty.")
return
# Check for material compatibility
if not machine_manager.activeMaterialsCompatible():
if not self._machine_manager.activeMaterialsCompatible():
Logger.log("d", "No active material compatibility, set quality profile model as empty.")
self.setItems([])
return
@ -69,20 +71,20 @@ class QualityProfilesModel(ListModel):
item = {"name": quality_group.name,
"quality_type": quality_group.quality_type,
"layer_height": layer_height + self._layer_height_unit,
"layer_height_without_unit": layer_height,
"layer_height": layer_height,
"layer_height_unit": self._layer_height_unit,
"available": quality_group.is_available,
"quality_group": quality_group}
item_list.append(item)
# Sort items based on layer_height
item_list = sorted(item_list, key = lambda x: float(x["layer_height_without_unit"]))
item_list = sorted(item_list, key = lambda x: x["layer_height"])
self.setItems(item_list)
def _fetchLayerHeight(self, quality_group: "QualityGroup"):
global_stack = Application.getInstance().getMachineManager()._global_container_stack
global_stack = self._machine_manager.activeMachine
if not self._layer_height_unit:
unit = global_stack.definition.getProperty("layer_height", "unit")
if not unit:
@ -96,10 +98,10 @@ class QualityProfilesModel(ListModel):
layer_height = default_layer_height
if container.hasProperty("layer_height", "value"):
layer_height = str(container.getProperty("layer_height", "value"))
layer_height = container.getProperty("layer_height", "value")
else:
# Look for layer_height in the GlobalStack from material -> definition
container = global_stack.definition
if container.hasProperty("layer_height", "value"):
layer_height = container.getProperty("layer_height", "value")
return str(layer_height)
return float(layer_height)

View file

@ -4,6 +4,7 @@
from PyQt5.QtCore import pyqtProperty, pyqtSignal, Qt
from UM.Application import Application
from UM.Logger import Logger
from UM.Qt.ListModel import ListModel
from UM.Settings.ContainerRegistry import ContainerRegistry
@ -33,9 +34,10 @@ class QualitySettingsModel(ListModel):
self._container_registry = ContainerRegistry.getInstance()
self._application = Application.getInstance()
self._quality_manager = self._application._quality_manager
self._quality_manager = self._application.getQualityManager()
self._extruder_position = ""
self._selected_position = "" # empty string means GlobalStack
# strings such as "0", "1", etc. mean extruder positions
self._selected_quality_item = None # The selected quality in the quality management page
self._i18n_catalog = None
@ -43,18 +45,18 @@ class QualitySettingsModel(ListModel):
self._update()
extruderPositionChanged = pyqtSignal()
selectedPositionChanged = pyqtSignal()
selectedQualityItemChanged = pyqtSignal()
def setExtruderPosition(self, extruder_position):
if extruder_position != self._extruder_position:
self._extruder_position = extruder_position
self.extruderPositionChanged.emit()
def setSelectedPosition(self, selected_position):
if selected_position != self._selected_position:
self._selected_position = selected_position
self.selectedPositionChanged.emit()
self._update()
@pyqtProperty(str, fset = setExtruderPosition, notify = extruderPositionChanged)
def extruderPosition(self):
return self._extruder_position
@pyqtProperty(str, fset = setSelectedPosition, notify = selectedPositionChanged)
def selectedPosition(self):
return self._selected_position
def setSelectedQualityItem(self, selected_quality_item):
if selected_quality_item != self._selected_quality_item:
@ -73,32 +75,38 @@ class QualitySettingsModel(ListModel):
items = []
global_container_stack = Application.getInstance().getGlobalContainerStack()
global_container_stack = self._application.getGlobalContainerStack()
definition_container = global_container_stack.definition
quality_group = self._selected_quality_item["quality_group"]
quality_changes_group = self._selected_quality_item["quality_changes_group"]
if self._extruder_position == "":
if self._selected_position == "":
quality_node = quality_group.node_for_global
else:
quality_node = quality_group.nodes_for_extruders.get(self._extruder_position)
quality_node = quality_group.nodes_for_extruders.get(self._selected_position)
settings_keys = quality_group.getAllKeys()
quality_containers = [quality_node.getContainer()]
# Here, if the user has selected a quality changes, then "quality_changes_group" will not be None, and we fetch
# the settings in that quality_changes_group.
if quality_changes_group is not None:
if self._extruder_position == "":
if self._selected_position == "":
quality_changes_node = quality_changes_group.node_for_global
else:
quality_changes_node = quality_changes_group.nodes_for_extruders.get(self._extruder_position)
quality_changes_node = quality_changes_group.nodes_for_extruders.get(self._selected_position)
if quality_changes_node is not None: # it can be None if number of extruders are changed during runtime
try:
quality_containers.insert(0, quality_changes_node.getContainer())
except:
# FIXME: This is to prevent incomplete update of QualityManager
Logger.logException("d", "Failed to get container for quality changes node %s", quality_changes_node)
return
settings_keys.update(quality_changes_group.getAllKeys())
# We iterate over all definitions instead of settings in a quality/qualtiy_changes group is because in the GUI,
# the settings are grouped together by categories, and we had to go over all the definitions to figure out
# which setting belongs in which category.
current_category = ""
for definition in definition_container.findDefinitions():
if definition.type == "category":
@ -117,7 +125,7 @@ class QualitySettingsModel(ListModel):
profile_value = new_value
# Global tab should use resolve (if there is one)
if self._extruder_position == "":
if self._selected_position == "":
resolve_value = global_container_stack.getProperty(definition.key, "resolve")
if resolve_value is not None and definition.key in settings_keys:
profile_value = resolve_value
@ -125,10 +133,10 @@ class QualitySettingsModel(ListModel):
if profile_value is not None:
break
if not self._extruder_position:
if not self._selected_position:
user_value = global_container_stack.userChanges.getProperty(definition.key, "value")
else:
extruder_stack = global_container_stack.extruders[self._extruder_position]
extruder_stack = global_container_stack.extruders[self._selected_position]
user_value = extruder_stack.userChanges.getProperty(definition.key, "value")
if profile_value is None and user_value is None:

View file

@ -0,0 +1,53 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Application import Application
from .QualityGroup import QualityGroup
class QualityChangesGroup(QualityGroup):
def __init__(self, name: str, quality_type: str, parent = None):
super().__init__(name, quality_type, parent)
self._container_registry = Application.getInstance().getContainerRegistry()
def addNode(self, node: "QualityNode"):
# TODO: in 3.2 and earlier, a quality_changes container may have a field called "extruder" which contains the
# extruder definition ID it belongs to. But, in fact, we only need to know the following things:
# 1. which machine a custom profile is suitable for,
# 2. if this profile is for the GlobalStack,
# 3. if this profile is for an ExtruderStack and which one (the position).
#
# So, it is preferred to have a field like this:
# extruder_position = 1
# instead of this:
# extruder = custom_extruder_1
#
# An upgrade needs to be done if we want to do it this way. Before that, we use the extruder's definition
# to figure out its position.
#
extruder_definition_id = node.metadata.get("extruder")
if extruder_definition_id:
metadata_list = self._container_registry.findDefinitionContainersMetadata(id = extruder_definition_id)
if not metadata_list:
raise RuntimeError("%s cannot get metadata for extruder definition [%s]" %
(self, extruder_definition_id))
extruder_definition_metadata = metadata_list[0]
extruder_position = str(extruder_definition_metadata["position"])
if extruder_position in self.nodes_for_extruders:
raise RuntimeError("%s tries to overwrite the existing nodes_for_extruders position [%s] %s with %s" %
(self, extruder_position, self.node_for_global, node))
self.nodes_for_extruders[extruder_position] = node
else:
# This is a quality_changes for the GlobalStack
if self.node_for_global is not None:
raise RuntimeError("%s tries to overwrite the existing node_for_global %s with %s" %
(self, self.node_for_global, node))
self.node_for_global = node
def __str__(self) -> str:
return "%s[<%s>, available = %s]" % (self.__class__.__name__, self.name, self.is_available)

View file

@ -0,0 +1,50 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional, List
from PyQt5.QtCore import QObject, pyqtSlot
#
# A QualityGroup represents a group of containers that must be applied to each ContainerStack when it's used.
# Some concrete examples are Quality and QualityChanges: when we select quality type "normal", this quality type
# must be applied to all stacks in a machine, although each stack can have different containers. Use an Ultimaker 3
# as an example, suppose we choose quality type "normal", the actual InstanceContainers on each stack may look
# as below:
# GlobalStack ExtruderStack 1 ExtruderStack 2
# quality container: um3_global_normal um3_aa04_pla_normal um3_aa04_abs_normal
#
# This QualityGroup is mainly used in quality and quality_changes to group the containers that can be applied to
# a machine, so when a quality/custom quality is selected, the container can be directly applied to each stack instead
# of looking them up again.
#
class QualityGroup(QObject):
def __init__(self, name: str, quality_type: str, parent = None):
super().__init__(parent)
self.name = name
self.node_for_global = None # type: Optional["QualityGroup"]
self.nodes_for_extruders = dict() # position str -> QualityGroup
self.quality_type = quality_type
self.is_available = False
@pyqtSlot(result = str)
def getName(self) -> str:
return self.name
def getAllKeys(self) -> set:
result = set()
for node in [self.node_for_global] + list(self.nodes_for_extruders.values()):
if node is None:
continue
result.update(node.getContainer().getAllKeys())
return result
def getAllNodes(self) -> List["QualityGroup"]:
result = []
if self.node_for_global is not None:
result.append(self.node_for_global)
for extruder_node in self.nodes_for_extruders.values():
result.append(extruder_node)
return result

View file

@ -1,154 +1,27 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional, List
from typing import TYPE_CHECKING, Optional
from PyQt5.QtCore import QObject, QTimer, pyqtSignal, pyqtSlot
from UM.Application import Application
from UM.Logger import Logger
from UM.Util import parseBool
from UM.Settings.InstanceContainer import InstanceContainer
from cura.Machines.ContainerNode import ContainerNode
from cura.Settings.ExtruderStack import ExtruderStack
from .QualityGroup import QualityGroup
from .QualityNode import QualityNode
if TYPE_CHECKING:
from cura.Settings.GlobalStack import GlobalStack
from .QualityChangesGroup import QualityChangesGroup
#
# Quality lookup tree structure:
#
# <machine_definition_id>------|
# | |
# <variant_name> <root_material_id>
# |
# <root_material_id>
# |
# <quality_type>
# |
# <quality_name>
# + <quality_changes_name>
#
#
# A QualityGroup represents a group of containers that must be applied to each ContainerStack when it's used.
# Some concrete examples are Quality and QualityChanges: when we select quality type "normal", this quality type
# must be applied to all stacks in a machine, although each stack can have different containers. Use an Ultimaker 3
# as an example, suppose we choose quality type "normal", the actual InstanceContainers on each stack may look
# as below:
# GlobalStack ExtruderStack 1 ExtruderStack 2
# quality container: um3_global_normal um3_aa04_pla_normal um3_aa04_abs_normal
#
# This QualityGroup is mainly used in quality and quality_changes to group the containers that can be applied to
# a machine, so when a quality/custom quality is selected, the container can be directly applied to each stack instead
# of looking them up again.
#
class QualityGroup(QObject):
def __init__(self, name: str, quality_type: str, parent = None):
super().__init__(parent)
self.name = name
self.node_for_global = None # type: Optional["QualityGroup"]
self.nodes_for_extruders = dict() # position str -> QualityGroup
self.quality_type = quality_type
self.is_available = False
@pyqtSlot(result = str)
def getName(self) -> str:
return self.name
def getAllKeys(self) -> set:
result = set()
for node in [self.node_for_global] + list(self.nodes_for_extruders.values()):
if node is None:
continue
for key in node.getContainer().getAllKeys():
result.add(key)
return result
def getAllNodes(self) -> List["QualityGroup"]:
result = []
if self.node_for_global is not None:
result.append(self.node_for_global)
for extruder_node in self.nodes_for_extruders.values():
result.append(extruder_node)
return result
class QualityChangesGroup(QualityGroup):
def __init__(self, name: str, quality_type: str, parent = None):
super().__init__(name, quality_type, parent)
def addNode(self, node: "QualityNode"):
# TODO: in 3.2 and earlier, a quality_changes container may have a field called "extruder" which contains the
# extruder definition ID it belongs to. But, in fact, we only need to know the following things:
# 1. which machine a custom profile is suitable for,
# 2. if this profile is for the GlobalStack,
# 3. if this profile is for an ExtruderStack and which one (the position).
#
# So, it is preferred to have a field like this:
# extruder_position = 1
# instead of this:
# extruder = custom_extruder_1
#
# An upgrade needs to be done if we want to do it this way. Before that, we use the extruder's definition
# to figure out its position.
#
extruder_definition_id = node.metadata.get("extruder")
if extruder_definition_id:
container_registry = Application.getInstance().getContainerRegistry()
metadata_list = container_registry.findDefinitionContainersMetadata(id = extruder_definition_id)
if not metadata_list:
raise RuntimeError("%s cannot get metadata for extruder definition [%s]" %
(self, extruder_definition_id))
extruder_definition_metadata = metadata_list[0]
extruder_position = str(extruder_definition_metadata["position"])
if extruder_position in self.nodes_for_extruders:
raise RuntimeError("%s tries to overwrite the existing nodes_for_extruders position [%s] %s with %s" %
(self, extruder_position, self.node_for_global, node))
self.nodes_for_extruders[extruder_position] = node
else:
# This is a quality_changes for the GlobalStack
if self.node_for_global is not None:
raise RuntimeError("%s tries to overwrite the existing node_for_global %s with %s" %
(self, self.node_for_global, node))
self.node_for_global = node
def __str__(self) -> str:
return "%s[<%s>, available = %s]" % (self.__class__.__name__, self.name, self.is_available)
#
# QualityNode is used for BOTH quality and quality_changes containers.
#
class QualityNode(ContainerNode):
def __init__(self, metadata: Optional[dict] = None):
super().__init__(metadata = metadata)
self.quality_type_map = {} # quality_type -> QualityNode for InstanceContainer
def addQualityMetadata(self, quality_type: str, metadata: dict):
if quality_type not in self.quality_type_map:
self.quality_type_map[quality_type] = QualityNode(metadata)
def getQualityNode(self, quality_type: str) -> Optional["QualityNode"]:
return self.quality_type_map.get(quality_type)
def addQualityChangesMetadata(self, quality_type: str, metadata: dict):
if quality_type not in self.quality_type_map:
self.quality_type_map[quality_type] = QualityNode()
quality_type_node = self.quality_type_map[quality_type]
name = metadata["name"]
if name not in quality_type_node.children_map:
quality_type_node.children_map[name] = QualityChangesGroup(name, quality_type)
quality_changes_group = quality_type_node.children_map[name]
quality_changes_group.addNode(QualityNode(metadata))
#
# Similar to MaterialManager, QualityManager maintains a number of maps and trees for material lookup.
# Similar to MaterialManager, QualityManager maintains a number of maps and trees for quality profile lookup.
# The models GUI and QML use are now only dependent on the QualityManager. That means as long as the data in
# QualityManager gets updated correctly, the GUI models should be updated correctly too, and the same goes for GUI.
#
@ -164,7 +37,7 @@ class QualityManager(QObject):
def __init__(self, container_registry, parent = None):
super().__init__(parent)
self._application = Application.getInstance()
self._material_manager = self._application._material_manager
self._material_manager = self._application.getMaterialManager()
self._container_registry = container_registry
self._empty_quality_container = self._application.empty_quality_container
@ -305,7 +178,6 @@ class QualityManager(QObject):
# Returns a dict of "custom profile name" -> QualityChangesGroup
def getQualityChangesGroups(self, machine: "GlobalStack") -> dict:
# Get machine definition ID for quality search
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine)
machine_node = self._machine_quality_type_to_quality_changes_dict.get(machine_definition_id)
@ -327,9 +199,15 @@ class QualityManager(QObject):
return quality_changes_group_dict
#
# Gets all quality groups for the given machine. Both available and none available ones will be included.
# It returns a dictionary with "quality_type"s as keys and "QualityGroup"s as values.
# Whether a QualityGroup is available can be unknown via the field QualityGroup.is_available.
# For more details, see QualityGroup.
#
def getQualityGroups(self, machine: "GlobalStack") -> dict:
# Get machine definition ID for quality search
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine)
# This determines if we should only get the global qualities for the global stack and skip the global qualities for the extruder stacks
has_variant_materials = parseBool(machine.getMetaDataEntry("has_variant_materials", False))
@ -380,6 +258,17 @@ class QualityManager(QObject):
if fallback_root_material_id:
root_material_id_list.append(fallback_root_material_id)
# Here we construct a list of nodes we want to look for qualities with the highest priority first.
# The use case is that, when we look for qualities for a machine, we first want to search in the following
# order:
# 1. machine-variant-and-material-specific qualities if exist
# 2. machine-variant-specific qualities if exist
# 3. machine-material-specific qualities if exist
# 4. machine-specific qualities if exist
# 5. generic qualities if exist
# Each points above can be represented as a node in the lookup tree, so here we simply put those nodes into
# the list with priorities as the order. Later, we just need to loop over each node in this list and fetch
# qualities from there.
nodes_to_check = []
if variant_name:
@ -426,7 +315,6 @@ class QualityManager(QObject):
return quality_group_dict
def getQualityGroupsForMachineDefinition(self, machine: "GlobalStack") -> dict:
# Get machine definition ID for quality search
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine)
# To find the quality container for the GlobalStack, check in the following fall-back manner:
@ -449,6 +337,135 @@ class QualityManager(QObject):
return quality_group_dict
#
# Methods for GUI
#
#
# Remove the given quality changes group.
#
@pyqtSlot(QObject)
def removeQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup"):
Logger.log("i", "Removing quality changes group [%s]", quality_changes_group.name)
for node in quality_changes_group.getAllNodes():
self._container_registry.removeContainer(node.metadata["id"])
#
# Rename a set of quality changes containers. Returns the new name.
#
@pyqtSlot(QObject, str, result = str)
def renameQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup", new_name: str) -> str:
Logger.log("i", "Renaming QualityChangesGroup[%s] to [%s]", quality_changes_group.name, new_name)
if new_name == quality_changes_group.name:
Logger.log("i", "QualityChangesGroup name [%s] unchanged.", quality_changes_group.name)
return new_name
new_name = self._container_registry.uniqueName(new_name)
for node in quality_changes_group.getAllNodes():
node.getContainer().setName(new_name)
quality_changes_group.name = new_name
self._application.getMachineManager().activeQualityChanged.emit()
self._application.getMachineManager().activeQualityGroupChanged.emit()
return new_name
#
# Duplicates the given quality.
#
@pyqtSlot(str, "QVariantMap")
def duplicateQualityChanges(self, quality_changes_name, quality_model_item):
global_stack = self._application.getGlobalContainerStack()
if not global_stack:
Logger.log("i", "No active global stack, cannot duplicate quality changes.")
return
quality_group = quality_model_item["quality_group"]
quality_changes_group = quality_model_item["quality_changes_group"]
if quality_changes_group is None:
# create global quality changes only
new_quality_changes = self._createQualityChanges(quality_group.quality_type, quality_changes_name,
global_stack, extruder_id = None)
self._container_registry.addContainer(new_quality_changes)
else:
new_name = self._container_registry.uniqueName(quality_changes_name)
for node in quality_changes_group.getAllNodes():
container = node.getContainer()
new_id = self._container_registry.uniqueName(container.getId())
self._container_registry.addContainer(container.duplicate(new_id, new_name))
## Create quality changes containers from the user containers in the active stacks.
#
# This will go through the global and extruder stacks and create quality_changes containers from
# the user containers in each stack. These then replace the quality_changes containers in the
# stack and clear the user settings.
@pyqtSlot(str)
def createQualityChanges(self, base_name):
machine_manager = Application.getInstance().getMachineManager()
global_stack = machine_manager.activeMachine
if not global_stack:
return
active_quality_name = machine_manager.activeQualityOrQualityChangesName
if active_quality_name == "":
Logger.log("w", "No quality container found in stack %s, cannot create profile", global_stack.getId())
return
machine_manager.blurSettings.emit()
if base_name is None or base_name == "":
base_name = active_quality_name
unique_name = self._container_registry.uniqueName(base_name)
# Go through the active stacks and create quality_changes containers from the user containers.
stack_list = [global_stack] + list(global_stack.extruders.values())
for stack in stack_list:
user_container = stack.userChanges
quality_container = stack.quality
quality_changes_container = stack.qualityChanges
if not quality_container or not quality_changes_container:
Logger.log("w", "No quality or quality changes container found in stack %s, ignoring it", stack.getId())
continue
extruder_definition_id = None
if isinstance(stack, ExtruderStack):
extruder_definition_id = stack.definition.getId()
quality_type = quality_container.getMetaDataEntry("quality_type")
new_changes = self._createQualityChanges(quality_type, unique_name, global_stack, extruder_definition_id)
from cura.Settings.ContainerManager import ContainerManager
ContainerManager.getInstance()._performMerge(new_changes, quality_changes_container, clear_settings = False)
ContainerManager.getInstance()._performMerge(new_changes, user_container)
self._container_registry.addContainer(new_changes)
#
# Create a quality changes container with the given setup.
#
def _createQualityChanges(self, quality_type: str, new_name: str, machine: "GlobalStack",
extruder_id: Optional[str]) -> "InstanceContainer":
base_id = machine.definition.getId() if extruder_id is None else extruder_id
new_id = base_id + "_" + new_name
new_id = new_id.lower().replace(" ", "_")
new_id = self._container_registry.uniqueName(new_id)
# Create a new quality_changes container for the quality.
quality_changes = InstanceContainer(new_id)
quality_changes.setName(new_name)
quality_changes.addMetaDataEntry("type", "quality_changes")
quality_changes.addMetaDataEntry("quality_type", quality_type)
# If we are creating a container for an extruder, ensure we add that to the container
if extruder_id is not None:
quality_changes.addMetaDataEntry("extruder", extruder_id)
# If the machine specifies qualities should be filtered, ensure we match the current criteria.
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine)
quality_changes.setDefinition(machine_definition_id)
quality_changes.addMetaDataEntry("setting_version", self._application.SettingVersion)
return quality_changes
#
# Gets the machine definition ID that can be used to search for Quality containers that are suitable for the given

View file

@ -0,0 +1,35 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional
from .ContainerNode import ContainerNode
from .QualityChangesGroup import QualityChangesGroup
#
# QualityNode is used for BOTH quality and quality_changes containers.
#
class QualityNode(ContainerNode):
def __init__(self, metadata: Optional[dict] = None):
super().__init__(metadata = metadata)
self.quality_type_map = {} # quality_type -> QualityNode for InstanceContainer
def addQualityMetadata(self, quality_type: str, metadata: dict):
if quality_type not in self.quality_type_map:
self.quality_type_map[quality_type] = QualityNode(metadata)
def getQualityNode(self, quality_type: str) -> Optional["QualityNode"]:
return self.quality_type_map.get(quality_type)
def addQualityChangesMetadata(self, quality_type: str, metadata: dict):
if quality_type not in self.quality_type_map:
self.quality_type_map[quality_type] = QualityNode()
quality_type_node = self.quality_type_map[quality_type]
name = metadata["name"]
if name not in quality_type_node.children_map:
quality_type_node.children_map[name] = QualityChangesGroup(name, quality_type)
quality_changes_group = quality_type_node.children_map[name]
quality_changes_group.addNode(QualityNode(metadata))

View file

@ -1,17 +1,22 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from enum import Enum
from collections import OrderedDict
from typing import Optional
from typing import Optional, TYPE_CHECKING
from UM.Logger import Logger
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Util import parseBool
from cura.Machines.ContainerNode import ContainerNode
from cura.Settings.GlobalStack import GlobalStack
if TYPE_CHECKING:
from UM.Settings.DefinitionContainer import DefinitionContainer
class VariantType:
class VariantType(Enum):
BUILD_PLATE = "buildplate"
NOZZLE = "nozzle"
@ -64,6 +69,7 @@ class VariantManager:
self._machine_to_variant_dict_map[variant_definition][variant_type] = dict()
variant_type = variant_metadata["hardware_type"]
variant_type = VariantType(variant_type)
variant_dict = self._machine_to_variant_dict_map[variant_definition][variant_type]
if variant_name in variant_dict:
# ERROR: duplicated variant name.
@ -77,10 +83,29 @@ class VariantManager:
# Almost the same as getVariantMetadata() except that this returns an InstanceContainer if present.
#
def getVariantNode(self, machine_definition_id: str, variant_name: str,
variant_type: Optional[str] = VariantType.NOZZLE) -> Optional["ContainerNode"]:
variant_type: Optional["VariantType"] = VariantType.NOZZLE) -> Optional["ContainerNode"]:
return self._machine_to_variant_dict_map[machine_definition_id].get(variant_type, {}).get(variant_name)
def getVariantNodes(self, machine: "GlobalStack",
variant_type: Optional[str] = VariantType.NOZZLE) -> dict:
variant_type: Optional["VariantType"] = VariantType.NOZZLE) -> dict:
machine_definition_id = machine.definition.getId()
return self._machine_to_variant_dict_map.get(machine_definition_id, {}).get(variant_type, {})
#
# Gets the default variant for the given machine definition.
#
def getDefaultVariantNode(self, machine_definition: "DefinitionContainer",
variant_type: VariantType) -> Optional["ContainerNode"]:
machine_definition_id = machine_definition.getId()
preferred_variant_name = None
if variant_type == VariantType.BUILD_PLATE:
if parseBool(machine_definition.getMetaDataEntry("has_variant_buildplates", False)):
preferred_variant_name = machine_definition.getMetaDataEntry("preferred_variant_buildplate_name")
else:
if parseBool(machine_definition.getMetaDataEntry("has_variants", False)):
preferred_variant_name = machine_definition.getMetaDataEntry("preferred_variant_name")
node = None
if preferred_variant_name:
node = self.getVariantNode(machine_definition_id, preferred_variant_name, variant_type)
return node