mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-08-10 07:15:03 -06:00
CURA-4400 fixed merge conflicts
This commit is contained in:
commit
bad637eb14
121 changed files with 1157 additions and 936 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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__)
|
|
@ -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":
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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()
|
|
@ -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([])
|
||||
|
|
|
@ -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)
|
|
@ -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:
|
||||
|
|
53
cura/Machines/QualityChangesGroup.py
Normal file
53
cura/Machines/QualityChangesGroup.py
Normal 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)
|
50
cura/Machines/QualityGroup.py
Normal file
50
cura/Machines/QualityGroup.py
Normal 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
|
|
@ -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
|
||||
|
|
35
cura/Machines/QualityNode.py
Normal file
35
cura/Machines/QualityNode.py
Normal 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))
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue