mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-13 09:47:50 -06:00
Merge branch 'feature_intent'
Here we go! Contributes to issue CURA-6864.
This commit is contained in:
commit
cf14fa6820
154 changed files with 5293 additions and 4600 deletions
|
@ -15,7 +15,7 @@ from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qm
|
|||
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Application import Application
|
||||
from UM.Decorators import override
|
||||
from UM.Decorators import override, deprecated
|
||||
from UM.FlameProfiler import pyqtSlot
|
||||
from UM.Logger import Logger
|
||||
from UM.Message import Message
|
||||
|
@ -23,7 +23,6 @@ from UM.Platform import Platform
|
|||
from UM.PluginError import PluginNotFoundError
|
||||
from UM.Resources import Resources
|
||||
from UM.Preferences import Preferences
|
||||
from UM.Qt.Bindings import MainWindow
|
||||
from UM.Qt.QtApplication import QtApplication # The class we're inheriting from.
|
||||
import UM.Util
|
||||
from UM.View.SelectionPass import SelectionPass # For typing.
|
||||
|
@ -47,7 +46,6 @@ from UM.Scene.Selection import Selection
|
|||
from UM.Scene.ToolHandle import ToolHandle
|
||||
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Settings.ContainerStack import ContainerStack
|
||||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
from UM.Settings.SettingDefinition import SettingDefinition, DefinitionPropertyType
|
||||
from UM.Settings.SettingFunction import SettingFunction
|
||||
|
@ -73,7 +71,10 @@ from cura.Scene.GCodeListDecorator import GCodeListDecorator
|
|||
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
|
||||
from cura.Scene import ZOffsetDecorator
|
||||
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
from cura.Machines.MachineErrorChecker import MachineErrorChecker
|
||||
import cura.Machines.MaterialManager #Imported like this to prevent circular imports.
|
||||
import cura.Machines.QualityManager #Imported like this to prevent circular imports.
|
||||
from cura.Machines.VariantManager import VariantManager
|
||||
|
||||
from cura.Machines.Models.BuildPlateModel import BuildPlateModel
|
||||
|
@ -85,6 +86,7 @@ from cura.Machines.Models.FirstStartMachineActionsModel import FirstStartMachine
|
|||
from cura.Machines.Models.GenericMaterialsModel import GenericMaterialsModel
|
||||
from cura.Machines.Models.GlobalStacksModel import GlobalStacksModel
|
||||
from cura.Machines.Models.MaterialBrandsModel import MaterialBrandsModel
|
||||
from cura.Machines.Models.MaterialManagementModel import MaterialManagementModel
|
||||
from cura.Machines.Models.MultiBuildPlateModel import MultiBuildPlateModel
|
||||
from cura.Machines.Models.NozzleModel import NozzleModel
|
||||
from cura.Machines.Models.QualityManagementModel import QualityManagementModel
|
||||
|
@ -92,6 +94,8 @@ from cura.Machines.Models.QualityProfilesDropDownMenuModel import QualityProfile
|
|||
from cura.Machines.Models.QualitySettingsModel import QualitySettingsModel
|
||||
from cura.Machines.Models.SettingVisibilityPresetsModel import SettingVisibilityPresetsModel
|
||||
from cura.Machines.Models.UserChangesModel import UserChangesModel
|
||||
from cura.Machines.Models.IntentModel import IntentModel
|
||||
from cura.Machines.Models.IntentCategoryModel import IntentCategoryModel
|
||||
|
||||
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice
|
||||
from cura.PrinterOutput.NetworkMJPGImage import NetworkMJPGImage
|
||||
|
@ -101,8 +105,10 @@ from cura.Settings.ContainerManager import ContainerManager
|
|||
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
|
||||
from cura.Settings.CuraFormulaFunctions import CuraFormulaFunctions
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
from cura.Settings.ExtruderStack import ExtruderStack
|
||||
from cura.Settings.MachineManager import MachineManager
|
||||
from cura.Settings.MachineNameValidator import MachineNameValidator
|
||||
from cura.Settings.IntentManager import IntentManager
|
||||
from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler
|
||||
from cura.Settings.SettingInheritanceManager import SettingInheritanceManager
|
||||
from cura.Settings.SidebarCustomMenuItemsModel import SidebarCustomMenuItemsModel
|
||||
|
@ -130,13 +136,10 @@ from . import CuraActions
|
|||
from . import PrintJobPreviewImageProvider
|
||||
|
||||
from cura import ApplicationMetadata, UltimakerCloudAuthentication
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.Machines.MaterialManager import MaterialManager
|
||||
from cura.Machines.QualityManager import QualityManager
|
||||
from UM.Settings.EmptyInstanceContainer import EmptyInstanceContainer
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
|
||||
|
||||
numpy.seterr(all = "ignore")
|
||||
|
||||
|
@ -161,6 +164,7 @@ class CuraApplication(QtApplication):
|
|||
ExtruderStack = Resources.UserType + 9
|
||||
DefinitionChangesContainer = Resources.UserType + 10
|
||||
SettingVisibilityPreset = Resources.UserType + 11
|
||||
IntentInstanceContainer = Resources.UserType + 12
|
||||
|
||||
Q_ENUMS(ResourceTypes)
|
||||
|
||||
|
@ -197,13 +201,12 @@ class CuraApplication(QtApplication):
|
|||
self.empty_container = None # type: EmptyInstanceContainer
|
||||
self.empty_definition_changes_container = None # type: EmptyInstanceContainer
|
||||
self.empty_variant_container = None # type: EmptyInstanceContainer
|
||||
self.empty_intent_container = None # type: EmptyInstanceContainer
|
||||
self.empty_material_container = None # type: EmptyInstanceContainer
|
||||
self.empty_quality_container = None # type: EmptyInstanceContainer
|
||||
self.empty_quality_changes_container = None # type: EmptyInstanceContainer
|
||||
|
||||
self._variant_manager = None
|
||||
self._material_manager = None
|
||||
self._quality_manager = None
|
||||
self._machine_manager = None
|
||||
self._extruder_manager = None
|
||||
self._container_manager = None
|
||||
|
@ -220,6 +223,8 @@ class CuraApplication(QtApplication):
|
|||
self._machine_error_checker = None
|
||||
|
||||
self._machine_settings_manager = MachineSettingsManager(self, parent = self)
|
||||
self._material_management_model = None
|
||||
self._quality_management_model = None
|
||||
|
||||
self._discovered_printer_model = DiscoveredPrintersModel(self, parent = self)
|
||||
self._first_start_machine_actions_model = FirstStartMachineActionsModel(self, parent = self)
|
||||
|
@ -346,7 +351,7 @@ class CuraApplication(QtApplication):
|
|||
# Adds expected directory names and search paths for Resources.
|
||||
def __addExpectedResourceDirsAndSearchPaths(self):
|
||||
# this list of dir names will be used by UM to detect an old cura directory
|
||||
for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "quality_changes", "user", "variants"]:
|
||||
for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "quality_changes", "user", "variants", "intent"]:
|
||||
Resources.addExpectedDirNameInData(dir_name)
|
||||
|
||||
Resources.addSearchPath(os.path.join(self._app_install_dir, "share", "cura", "resources"))
|
||||
|
@ -404,6 +409,7 @@ class CuraApplication(QtApplication):
|
|||
Resources.addStorageType(self.ResourceTypes.MachineStack, "machine_instances")
|
||||
Resources.addStorageType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes")
|
||||
Resources.addStorageType(self.ResourceTypes.SettingVisibilityPreset, "setting_visibility")
|
||||
Resources.addStorageType(self.ResourceTypes.IntentInstanceContainer, "intent")
|
||||
|
||||
self._container_registry.addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality")
|
||||
self._container_registry.addResourceType(self.ResourceTypes.QualityChangesInstanceContainer, "quality_changes")
|
||||
|
@ -413,6 +419,7 @@ class CuraApplication(QtApplication):
|
|||
self._container_registry.addResourceType(self.ResourceTypes.ExtruderStack, "extruder_train")
|
||||
self._container_registry.addResourceType(self.ResourceTypes.MachineStack, "machine")
|
||||
self._container_registry.addResourceType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes")
|
||||
self._container_registry.addResourceType(self.ResourceTypes.IntentInstanceContainer, "intent")
|
||||
|
||||
Resources.addType(self.ResourceTypes.QmlFiles, "qml")
|
||||
Resources.addType(self.ResourceTypes.Firmware, "firmware")
|
||||
|
@ -431,6 +438,9 @@ class CuraApplication(QtApplication):
|
|||
self._container_registry.addContainer(cura.Settings.cura_empty_instance_containers.empty_variant_container)
|
||||
self.empty_variant_container = cura.Settings.cura_empty_instance_containers.empty_variant_container
|
||||
|
||||
self._container_registry.addContainer(cura.Settings.cura_empty_instance_containers.empty_intent_container)
|
||||
self.empty_intent_container = cura.Settings.cura_empty_instance_containers.empty_intent_container
|
||||
|
||||
self._container_registry.addContainer(cura.Settings.cura_empty_instance_containers.empty_material_container)
|
||||
self.empty_material_container = cura.Settings.cura_empty_instance_containers.empty_material_container
|
||||
|
||||
|
@ -445,14 +455,15 @@ class CuraApplication(QtApplication):
|
|||
def __setLatestResouceVersionsForVersionUpgrade(self):
|
||||
self._version_upgrade_manager.setCurrentVersions(
|
||||
{
|
||||
("quality", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.QualityInstanceContainer, "application/x-uranium-instancecontainer"),
|
||||
("quality_changes", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.QualityChangesInstanceContainer, "application/x-uranium-instancecontainer"),
|
||||
("machine_stack", ContainerStack.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.MachineStack, "application/x-cura-globalstack"),
|
||||
("extruder_train", ContainerStack.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.ExtruderStack, "application/x-cura-extruderstack"),
|
||||
("preferences", Preferences.Version * 1000000 + self.SettingVersion): (Resources.Preferences, "application/x-uranium-preferences"),
|
||||
("user", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.UserInstanceContainer, "application/x-uranium-instancecontainer"),
|
||||
("definition_changes", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.DefinitionChangesContainer, "application/x-uranium-instancecontainer"),
|
||||
("variant", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.VariantInstanceContainer, "application/x-uranium-instancecontainer"),
|
||||
("quality", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.QualityInstanceContainer, "application/x-uranium-instancecontainer"),
|
||||
("quality_changes", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.QualityChangesInstanceContainer, "application/x-uranium-instancecontainer"),
|
||||
("intent", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.IntentInstanceContainer, "application/x-uranium-instancecontainer"),
|
||||
("machine_stack", GlobalStack.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.MachineStack, "application/x-cura-globalstack"),
|
||||
("extruder_train", ExtruderStack.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.ExtruderStack, "application/x-cura-extruderstack"),
|
||||
("preferences", Preferences.Version * 1000000 + self.SettingVersion): (Resources.Preferences, "application/x-uranium-preferences"),
|
||||
("user", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.UserInstanceContainer, "application/x-uranium-instancecontainer"),
|
||||
("definition_changes", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.DefinitionChangesContainer, "application/x-uranium-instancecontainer"),
|
||||
("variant", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.VariantInstanceContainer, "application/x-uranium-instancecontainer"),
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -504,6 +515,8 @@ class CuraApplication(QtApplication):
|
|||
|
||||
self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Loading machines..."))
|
||||
|
||||
self._container_registry.allMetadataLoaded.connect(ContainerRegistry.getInstance)
|
||||
|
||||
with self._container_registry.lockFile():
|
||||
self._container_registry.loadAllMetadata()
|
||||
|
||||
|
@ -732,21 +745,6 @@ class CuraApplication(QtApplication):
|
|||
|
||||
def run(self):
|
||||
super().run()
|
||||
container_registry = self._container_registry
|
||||
|
||||
Logger.log("i", "Initializing variant manager")
|
||||
self._variant_manager = VariantManager(container_registry)
|
||||
self._variant_manager.initialize()
|
||||
|
||||
Logger.log("i", "Initializing material manager")
|
||||
from cura.Machines.MaterialManager import MaterialManager
|
||||
self._material_manager = MaterialManager(container_registry, parent = self)
|
||||
self._material_manager.initialize()
|
||||
|
||||
Logger.log("i", "Initializing quality manager")
|
||||
from cura.Machines.QualityManager import QualityManager
|
||||
self._quality_manager = QualityManager(self, parent = self)
|
||||
self._quality_manager.initialize()
|
||||
|
||||
Logger.log("i", "Initializing machine manager")
|
||||
self._machine_manager = MachineManager(self, parent = self)
|
||||
|
@ -926,16 +924,22 @@ class CuraApplication(QtApplication):
|
|||
self._extruder_manager = ExtruderManager()
|
||||
return self._extruder_manager
|
||||
|
||||
@deprecated("Use the ContainerTree structure instead.", since = "4.3")
|
||||
def getVariantManager(self, *args) -> VariantManager:
|
||||
return self._variant_manager
|
||||
return VariantManager.getInstance()
|
||||
|
||||
# Can't deprecate this function since the deprecation marker collides with pyqtSlot!
|
||||
@pyqtSlot(result = QObject)
|
||||
def getMaterialManager(self, *args) -> "MaterialManager":
|
||||
return self._material_manager
|
||||
def getMaterialManager(self, *args) -> cura.Machines.MaterialManager.MaterialManager:
|
||||
return cura.Machines.MaterialManager.MaterialManager.getInstance()
|
||||
|
||||
# Can't deprecate this function since the deprecation marker collides with pyqtSlot!
|
||||
@pyqtSlot(result = QObject)
|
||||
def getQualityManager(self, *args) -> "QualityManager":
|
||||
return self._quality_manager
|
||||
def getQualityManager(self, *args) -> cura.Machines.QualityManager.QualityManager:
|
||||
return cura.Machines.QualityManager.QualityManager.getInstance()
|
||||
|
||||
def getIntentManager(self, *args) -> IntentManager:
|
||||
return IntentManager.getInstance()
|
||||
|
||||
def getObjectsModel(self, *args):
|
||||
if self._object_manager is None:
|
||||
|
@ -983,6 +987,18 @@ class CuraApplication(QtApplication):
|
|||
def getMachineActionManager(self, *args):
|
||||
return self._machine_action_manager
|
||||
|
||||
@pyqtSlot(result = QObject)
|
||||
def getMaterialManagementModel(self) -> MaterialManagementModel:
|
||||
if not self._material_management_model:
|
||||
self._material_management_model = MaterialManagementModel(parent = self)
|
||||
return self._material_management_model
|
||||
|
||||
@pyqtSlot(result = QObject)
|
||||
def getQualityManagementModel(self) -> QualityManagementModel:
|
||||
if not self._quality_management_model:
|
||||
self._quality_management_model = QualityManagementModel(parent = self)
|
||||
return self._quality_management_model
|
||||
|
||||
def getSimpleModeSettingsManager(self, *args):
|
||||
if self._simple_mode_settings_manager is None:
|
||||
self._simple_mode_settings_manager = SimpleModeSettingsManager()
|
||||
|
@ -1036,6 +1052,7 @@ class CuraApplication(QtApplication):
|
|||
qmlRegisterSingletonType(CuraSceneController, "Cura", 1, 0, "SceneController", self.getCuraSceneController)
|
||||
qmlRegisterSingletonType(ExtruderManager, "Cura", 1, 0, "ExtruderManager", self.getExtruderManager)
|
||||
qmlRegisterSingletonType(MachineManager, "Cura", 1, 0, "MachineManager", self.getMachineManager)
|
||||
qmlRegisterSingletonType(IntentManager, "Cura", 1, 6, "IntentManager", self.getIntentManager)
|
||||
qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager", self.getSettingInheritanceManager)
|
||||
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager)
|
||||
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
|
||||
|
@ -1060,7 +1077,8 @@ class CuraApplication(QtApplication):
|
|||
qmlRegisterType(FavoriteMaterialsModel, "Cura", 1, 0, "FavoriteMaterialsModel")
|
||||
qmlRegisterType(GenericMaterialsModel, "Cura", 1, 0, "GenericMaterialsModel")
|
||||
qmlRegisterType(MaterialBrandsModel, "Cura", 1, 0, "MaterialBrandsModel")
|
||||
qmlRegisterType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel")
|
||||
qmlRegisterSingletonType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel", self.getQualityManagementModel)
|
||||
qmlRegisterSingletonType(MaterialManagementModel, "Cura", 1, 5, "MaterialManagementModel", self.getMaterialManagementModel)
|
||||
|
||||
qmlRegisterType(DiscoveredPrintersModel, "Cura", 1, 0, "DiscoveredPrintersModel")
|
||||
|
||||
|
@ -1069,6 +1087,8 @@ class CuraApplication(QtApplication):
|
|||
qmlRegisterSingletonType(CustomQualityProfilesDropDownMenuModel, "Cura", 1, 0,
|
||||
"CustomQualityProfilesDropDownMenuModel", self.getCustomQualityProfilesDropDownMenuModel)
|
||||
qmlRegisterType(NozzleModel, "Cura", 1, 0, "NozzleModel")
|
||||
qmlRegisterType(IntentModel, "Cura", 1, 6, "IntentModel")
|
||||
qmlRegisterType(IntentCategoryModel, "Cura", 1, 6, "IntentCategoryModel")
|
||||
|
||||
qmlRegisterType(MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler")
|
||||
qmlRegisterType(SettingVisibilityPresetsModel, "Cura", 1, 0, "SettingVisibilityPresetsModel")
|
||||
|
|
|
@ -1,64 +1,76 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Optional, Any, Dict, Union, TYPE_CHECKING
|
||||
|
||||
from collections import OrderedDict
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Logger import Logger
|
||||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
from UM.Decorators import deprecated
|
||||
|
||||
|
||||
##
|
||||
# A metadata / container combination. Use getContainer() to get the container corresponding to the metadata.
|
||||
#
|
||||
# ContainerNode is a multi-purpose class. It has two main purposes:
|
||||
# 1. It encapsulates an InstanceContainer. It contains that InstanceContainer's
|
||||
# - metadata (Always)
|
||||
# - container (lazy-loaded when needed)
|
||||
# 2. It also serves as a node in a hierarchical InstanceContainer lookup table/tree.
|
||||
# This is used in Variant, Material, and Quality Managers.
|
||||
## A node in the container tree. It represents one container.
|
||||
#
|
||||
# The container it represents is referenced by its container_id. During normal
|
||||
# use of the tree, this container is not constructed. Only when parts of the
|
||||
# tree need to get loaded in the container stack should it get constructed.
|
||||
class ContainerNode:
|
||||
__slots__ = ("_metadata", "_container", "children_map")
|
||||
## Creates a new node for the container tree.
|
||||
# \param container_id The ID of the container that this node should
|
||||
# represent.
|
||||
def __init__(self, container_id: str) -> None:
|
||||
self.container_id = container_id
|
||||
self._container = None # type: Optional[InstanceContainer]
|
||||
self.children_map = {} # type: Dict[str, ContainerNode] # Mapping from container ID to container node.
|
||||
|
||||
def __init__(self, metadata: Optional[Dict[str, Any]] = None) -> None:
|
||||
self._metadata = metadata
|
||||
self._container = None # type: Optional[InstanceContainer]
|
||||
self.children_map = OrderedDict() # type: ignore # This is because it's children are supposed to override it.
|
||||
## Gets the metadata of the container that this node represents.
|
||||
# Getting the metadata from the container directly is about 10x as fast.
|
||||
# \return The metadata of the container in this node.
|
||||
def getMetadata(self):
|
||||
return ContainerRegistry.getInstance().findContainersMetadata(id = self.container_id)[0]
|
||||
|
||||
## Get an entry value from the metadata
|
||||
## Get an entry from the metadata of the container that this node contains.
|
||||
#
|
||||
# This is just a convenience function.
|
||||
# \param entry The metadata entry key to return.
|
||||
# \param default If the metadata is not present or the container is not
|
||||
# found, the value of this default is returned.
|
||||
# \return The value of the metadata entry, or the default if it was not
|
||||
# present.
|
||||
def getMetaDataEntry(self, entry: str, default: Any = None) -> Any:
|
||||
if self._metadata is None:
|
||||
container_metadata = ContainerRegistry.getInstance().findContainersMetadata(id = self.container_id)
|
||||
if len(container_metadata) == 0:
|
||||
return default
|
||||
return self._metadata.get(entry, default)
|
||||
return container_metadata[0].get(entry, default)
|
||||
|
||||
def getMetadata(self) -> Dict[str, Any]:
|
||||
if self._metadata is None:
|
||||
return {}
|
||||
return self._metadata
|
||||
## Get the child with the specified container ID.
|
||||
# \param child_id The container ID to get from among the children.
|
||||
# \return The child node, or ``None`` if no child is present with the
|
||||
# specified ID.
|
||||
@deprecated("Iterate over the children instead of requesting them one by one.", "4.3")
|
||||
def getChildNode(self, child_id: str) -> Optional["ContainerNode"]:
|
||||
return self.children_map.get(child_id)
|
||||
|
||||
def getChildNode(self, child_key: str) -> Optional["ContainerNode"]:
|
||||
return self.children_map.get(child_key)
|
||||
@deprecated("Use `.container` instead.", "4.3")
|
||||
def getContainer(self) -> Optional[InstanceContainer]:
|
||||
return self.container
|
||||
|
||||
def getContainer(self) -> Optional["InstanceContainer"]:
|
||||
if self._metadata is None:
|
||||
Logger.log("e", "Cannot get container for a ContainerNode without metadata.")
|
||||
return None
|
||||
|
||||
if self._container is None:
|
||||
container_id = self._metadata["id"]
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
container_list = ContainerRegistry.getInstance().findInstanceContainers(id = container_id)
|
||||
if not container_list:
|
||||
Logger.log("e", "Failed to lazy-load container [{container_id}]. Cannot find it.".format(container_id = container_id))
|
||||
## The container that this node's container ID refers to.
|
||||
#
|
||||
# This can be used to finally instantiate the container in order to put it
|
||||
# in the container stack.
|
||||
# \return A container.
|
||||
@property
|
||||
def container(self) -> Optional[InstanceContainer]:
|
||||
if not self._container:
|
||||
container_list = ContainerRegistry.getInstance().findInstanceContainers(id = self.container_id)
|
||||
if len(container_list) == 0:
|
||||
Logger.log("e", "Failed to lazy-load container [{container_id}]. Cannot find it.".format(container_id = self.container_id))
|
||||
error_message = ConfigurationErrorMessage.getInstance()
|
||||
error_message.addFaultyContainers(container_id)
|
||||
error_message.addFaultyContainers(self.container_id)
|
||||
return None
|
||||
self._container = container_list[0]
|
||||
|
||||
return self._container
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "%s[%s]" % (self.__class__.__name__, self.getMetaDataEntry("id"))
|
||||
return "%s[%s]" % (self.__class__.__name__, self.container_id)
|
129
cura/Machines/ContainerTree.py
Normal file
129
cura/Machines/ContainerTree.py
Normal file
|
@ -0,0 +1,129 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry # To listen to containers being added.
|
||||
from UM.Settings.Interfaces import ContainerInterface
|
||||
from UM.Signal import Signal
|
||||
import cura.CuraApplication # Imported like this to prevent circular dependencies.
|
||||
from cura.Machines.MachineNode import MachineNode
|
||||
from cura.Settings.GlobalStack import GlobalStack # To listen only to global stacks being added.
|
||||
|
||||
from typing import Dict, List, TYPE_CHECKING
|
||||
import time
|
||||
import UM.FlameProfiler
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.Machines.QualityGroup import QualityGroup
|
||||
from cura.Machines.QualityChangesGroup import QualityChangesGroup
|
||||
|
||||
|
||||
## This class contains a look-up tree for which containers are available at
|
||||
# which stages of configuration.
|
||||
#
|
||||
# The tree starts at the machine definitions. For every distinct definition
|
||||
# there will be one machine node here.
|
||||
#
|
||||
# All of the fallbacks for material choices, quality choices, etc. should be
|
||||
# encoded in this tree. There must always be at least one child node (for
|
||||
# nodes that have children) but that child node may be a node representing the
|
||||
# empty instance container.
|
||||
class ContainerTree:
|
||||
__instance = None
|
||||
|
||||
@classmethod
|
||||
def getInstance(cls):
|
||||
if cls.__instance is None:
|
||||
cls.__instance = ContainerTree()
|
||||
return cls.__instance
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.machines = {} # type: Dict[str, MachineNode] # Mapping from definition ID to machine nodes.
|
||||
self.materialsChanged = Signal() # Emitted when any of the material nodes in the tree got changed.
|
||||
|
||||
container_registry = ContainerRegistry.getInstance()
|
||||
container_registry.containerAdded.connect(self._machineAdded)
|
||||
self._loadAll()
|
||||
|
||||
## Get the quality groups available for the currently activated printer.
|
||||
#
|
||||
# This contains all quality groups, enabled or disabled. To check whether
|
||||
# the quality group can be activated, test for the
|
||||
# ``QualityGroup.is_available`` property.
|
||||
# \return For every quality type, one quality group.
|
||||
def getCurrentQualityGroups(self) -> Dict[str, "QualityGroup"]:
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if global_stack is None:
|
||||
return {}
|
||||
variant_names = [extruder.variant.getName() for extruder in global_stack.extruderList]
|
||||
material_bases = [extruder.material.getMetaDataEntry("base_file") for extruder in global_stack.extruderList]
|
||||
extruder_enabled = [extruder.isEnabled for extruder in global_stack.extruderList]
|
||||
return self.machines[global_stack.definition.getId()].getQualityGroups(variant_names, material_bases, extruder_enabled)
|
||||
|
||||
## Get the quality changes groups available for the currently activated
|
||||
# printer.
|
||||
#
|
||||
# This contains all quality changes groups, enabled or disabled. To check
|
||||
# whether the quality changes group can be activated, test for the
|
||||
# ``QualityChangesGroup.is_available`` property.
|
||||
# \return A list of all quality changes groups.
|
||||
def getCurrentQualityChangesGroups(self) -> List["QualityChangesGroup"]:
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if global_stack is None:
|
||||
return []
|
||||
variant_names = [extruder.variant.getName() for extruder in global_stack.extruderList]
|
||||
material_bases = [extruder.material.getMetaDataEntry("base_file") for extruder in global_stack.extruderList]
|
||||
extruder_enabled = [extruder.isEnabled for extruder in global_stack.extruderList]
|
||||
return self.machines[global_stack.definition.getId()].getQualityChangesGroups(variant_names, material_bases, extruder_enabled)
|
||||
|
||||
# Add a machine node by the id of it's definition.
|
||||
# This is automatically called by the _machineAdded function, but it's sometimes needed to add a machine node
|
||||
# faster than would have been done when waiting on any signals (for instance; when creating an entirely new machine)
|
||||
@UM.FlameProfiler.profile
|
||||
def addMachineNodeByDefinitionId(self, definition_id: str) -> None:
|
||||
if definition_id in self.machines:
|
||||
return # Already have this definition ID.
|
||||
|
||||
start_time = time.time()
|
||||
self.machines[definition_id] = MachineNode(definition_id)
|
||||
self.machines[definition_id].materialsChanged.connect(self.materialsChanged)
|
||||
Logger.log("d", "Adding container tree for {definition_id} took {duration} seconds.".format(definition_id = definition_id, duration = time.time() - start_time))
|
||||
|
||||
## Builds the initial container tree.
|
||||
def _loadAll(self):
|
||||
Logger.log("i", "Building container tree.")
|
||||
start_time = time.time()
|
||||
all_stacks = ContainerRegistry.getInstance().findContainerStacks()
|
||||
for stack in all_stacks:
|
||||
if not isinstance(stack, GlobalStack):
|
||||
continue # Only want to load global stacks. We don't need to create a tree for extruder definitions.
|
||||
definition_id = stack.definition.getId()
|
||||
if definition_id not in self.machines:
|
||||
definition_start_time = time.time()
|
||||
self.machines[definition_id] = MachineNode(definition_id)
|
||||
self.machines[definition_id].materialsChanged.connect(self.materialsChanged)
|
||||
Logger.log("d", "Adding container tree for {definition_id} took {duration} seconds.".format(definition_id = definition_id, duration = time.time() - definition_start_time))
|
||||
|
||||
Logger.log("d", "Building the container tree took %s seconds", time.time() - start_time)
|
||||
|
||||
## When a printer gets added, we need to build up the tree for that container.
|
||||
def _machineAdded(self, container_stack: ContainerInterface) -> None:
|
||||
if not isinstance(container_stack, GlobalStack):
|
||||
return # Not our concern.
|
||||
self.addMachineNodeByDefinitionId(container_stack.definition.getId())
|
||||
|
||||
## For debugging purposes, visualise the entire container tree as it stands
|
||||
# now.
|
||||
def _visualise_tree(self) -> str:
|
||||
lines = ["% CONTAINER TREE"] # Start with array and then combine into string, for performance.
|
||||
for machine in self.machines.values():
|
||||
lines.append(" # " + machine.container_id)
|
||||
for variant in machine.variants.values():
|
||||
lines.append(" * " + variant.container_id)
|
||||
for material in variant.materials.values():
|
||||
lines.append(" + " + material.container_id)
|
||||
for quality in material.qualities.values():
|
||||
lines.append(" - " + quality.container_id)
|
||||
for intent in quality.intents.values():
|
||||
lines.append(" . " + intent.container_id)
|
||||
return "\n".join(lines)
|
21
cura/Machines/IntentNode.py
Normal file
21
cura/Machines/IntentNode.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
|
||||
from cura.Machines.ContainerNode import ContainerNode
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.Machines.QualityNode import QualityNode
|
||||
|
||||
|
||||
## This class represents an intent profile in the container tree.
|
||||
#
|
||||
# This class has no more subnodes.
|
||||
class IntentNode(ContainerNode):
|
||||
def __init__(self, container_id: str, quality: "QualityNode") -> None:
|
||||
super().__init__(container_id)
|
||||
self.quality = quality
|
||||
self.intent_category = ContainerRegistry.getInstance().findContainersMetadata(id = container_id)[0].get("intent_category", "default")
|
186
cura/Machines/MachineNode.py
Normal file
186
cura/Machines/MachineNode.py
Normal file
|
@ -0,0 +1,186 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Dict, List
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.Signal import Signal
|
||||
from UM.Util import parseBool
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry # To find all the variants for this machine.
|
||||
|
||||
import cura.CuraApplication # Imported like this to prevent circular dependencies.
|
||||
from cura.Machines.ContainerNode import ContainerNode
|
||||
from cura.Machines.QualityChangesGroup import QualityChangesGroup # To construct groups of quality changes profiles that belong together.
|
||||
from cura.Machines.QualityGroup import QualityGroup # To construct groups of quality profiles that belong together.
|
||||
from cura.Machines.QualityNode import QualityNode
|
||||
from cura.Machines.VariantNode import VariantNode
|
||||
import UM.FlameProfiler
|
||||
|
||||
|
||||
## This class represents a machine in the container tree.
|
||||
#
|
||||
# The subnodes of these nodes are variants.
|
||||
class MachineNode(ContainerNode):
|
||||
def __init__(self, container_id: str) -> None:
|
||||
super().__init__(container_id)
|
||||
self.variants = {} # type: Dict[str, VariantNode] # Mapping variant names to their nodes.
|
||||
self.global_qualities = {} # type: Dict[str, QualityNode] # Mapping quality types to the global quality for those types.
|
||||
self.materialsChanged = Signal() # Emitted when one of the materials underneath this machine has been changed.
|
||||
|
||||
container_registry = ContainerRegistry.getInstance()
|
||||
try:
|
||||
my_metadata = container_registry.findContainersMetadata(id = container_id)[0]
|
||||
except IndexError:
|
||||
Logger.log("Unable to find metadata for container %s", container_id)
|
||||
my_metadata = {}
|
||||
# Some of the metadata is cached upon construction here.
|
||||
# ONLY DO THAT FOR METADATA THAT DOESN'T CHANGE DURING RUNTIME!
|
||||
# Otherwise you need to keep it up-to-date during runtime.
|
||||
self.has_materials = parseBool(my_metadata.get("has_materials", "true"))
|
||||
self.has_variants = parseBool(my_metadata.get("has_variants", "false"))
|
||||
self.has_machine_quality = parseBool(my_metadata.get("has_machine_quality", "false"))
|
||||
self.quality_definition = my_metadata.get("quality_definition", container_id) if self.has_machine_quality else "fdmprinter"
|
||||
self.exclude_materials = my_metadata.get("exclude_materials", [])
|
||||
self.preferred_variant_name = my_metadata.get("preferred_variant_name", "")
|
||||
self.preferred_material = my_metadata.get("preferred_material", "")
|
||||
self.preferred_quality_type = my_metadata.get("preferred_quality_type", "")
|
||||
|
||||
self._loadAll()
|
||||
|
||||
## Get the available quality groups for this machine.
|
||||
#
|
||||
# This returns all quality groups, regardless of whether they are
|
||||
# available to the combination of extruders or not. On the resulting
|
||||
# quality groups, the is_available property is set to indicate whether the
|
||||
# quality group can be selected according to the combination of extruders
|
||||
# in the parameters.
|
||||
# \param variant_names The names of the variants loaded in each extruder.
|
||||
# \param material_bases The base file names of the materials loaded in
|
||||
# each extruder.
|
||||
# \param extruder_enabled Whether or not the extruders are enabled. This
|
||||
# allows the function to set the is_available properly.
|
||||
# \return For each available quality type, a QualityGroup instance.
|
||||
def getQualityGroups(self, variant_names: List[str], material_bases: List[str], extruder_enabled: List[bool]) -> Dict[str, QualityGroup]:
|
||||
if len(variant_names) != len(material_bases) or len(variant_names) != len(extruder_enabled):
|
||||
Logger.log("e", "The number of extruders in the list of variants (" + str(len(variant_names)) + ") is not equal to the number of extruders in the list of materials (" + str(len(material_bases)) + ") or the list of enabled extruders (" + str(len(extruder_enabled)) + ").")
|
||||
return {}
|
||||
# For each extruder, find which quality profiles are available. Later we'll intersect the quality types.
|
||||
qualities_per_type_per_extruder = [{}] * len(variant_names) # type: List[Dict[str, QualityNode]]
|
||||
for extruder_nr, variant_name in enumerate(variant_names):
|
||||
if not extruder_enabled[extruder_nr]:
|
||||
continue # No qualities are available in this extruder. It'll get skipped when calculating the available quality types.
|
||||
material_base = material_bases[extruder_nr]
|
||||
if variant_name not in self.variants or material_base not in self.variants[variant_name].materials:
|
||||
# The printer has no variant/material-specific quality profiles. Use the global quality profiles.
|
||||
qualities_per_type_per_extruder[extruder_nr] = self.global_qualities
|
||||
else:
|
||||
# Use the actually specialised quality profiles.
|
||||
qualities_per_type_per_extruder[extruder_nr] = {node.quality_type: node for node in self.variants[variant_name].materials[material_base].qualities.values()}
|
||||
|
||||
# Create the quality group for each available type.
|
||||
quality_groups = {}
|
||||
for quality_type, global_quality_node in self.global_qualities.items():
|
||||
if not global_quality_node.container:
|
||||
Logger.log("w", "Node {0} doesn't have a container.".format(global_quality_node.container_id))
|
||||
continue
|
||||
quality_groups[quality_type] = QualityGroup(name = global_quality_node.getMetaDataEntry("name", "Unnamed profile"), quality_type = quality_type)
|
||||
quality_groups[quality_type].node_for_global = global_quality_node
|
||||
for extruder, qualities_per_type in enumerate(qualities_per_type_per_extruder):
|
||||
if quality_type in qualities_per_type:
|
||||
quality_groups[quality_type].nodes_for_extruders[extruder] = qualities_per_type[quality_type]
|
||||
|
||||
available_quality_types = set(quality_groups.keys())
|
||||
for extruder_nr, qualities_per_type in enumerate(qualities_per_type_per_extruder):
|
||||
if not extruder_enabled[extruder_nr]:
|
||||
continue
|
||||
available_quality_types.intersection_update(qualities_per_type.keys())
|
||||
for quality_type in available_quality_types:
|
||||
quality_groups[quality_type].is_available = True
|
||||
return quality_groups
|
||||
|
||||
## Returns all of the quality changes groups available to this printer.
|
||||
#
|
||||
# The quality changes groups store which quality type and intent category
|
||||
# they were made for, but not which material and nozzle. Instead for the
|
||||
# quality type and intent category, the quality changes will always be
|
||||
# available but change the quality type and intent category when
|
||||
# activated.
|
||||
#
|
||||
# The quality changes group does depend on the printer: Which quality
|
||||
# definition is used.
|
||||
#
|
||||
# The quality changes groups that are available do depend on the quality
|
||||
# types that are available, so it must still be known which extruders are
|
||||
# enabled and which materials and variants are loaded in them. This allows
|
||||
# setting the correct is_available flag.
|
||||
# \param variant_names The names of the variants loaded in each extruder.
|
||||
# \param material_bases The base file names of the materials loaded in
|
||||
# each extruder.
|
||||
# \param extruder_enabled For each extruder whether or not they are
|
||||
# enabled.
|
||||
# \return List of all quality changes groups for the printer.
|
||||
def getQualityChangesGroups(self, variant_names: List[str], material_bases: List[str], extruder_enabled: List[bool]) -> List[QualityChangesGroup]:
|
||||
machine_quality_changes = ContainerRegistry.getInstance().findContainersMetadata(type = "quality_changes", definition = self.quality_definition) # All quality changes for each extruder.
|
||||
|
||||
groups_by_name = {} #type: Dict[str, QualityChangesGroup] # Group quality changes profiles by their display name. The display name must be unique for quality changes. This finds profiles that belong together in a group.
|
||||
for quality_changes in machine_quality_changes:
|
||||
name = quality_changes["name"]
|
||||
if name not in groups_by_name:
|
||||
# CURA-6599
|
||||
# For some reason, QML will get null or fail to convert type for MachineManager.activeQualityChangesGroup() to
|
||||
# a QObject. Setting the object ownership to QQmlEngine.CppOwnership doesn't work, but setting the object
|
||||
# parent to application seems to work.
|
||||
from cura.CuraApplication import CuraApplication
|
||||
groups_by_name[name] = QualityChangesGroup(name, quality_type = quality_changes["quality_type"],
|
||||
intent_category = quality_changes.get("intent_category", "default"),
|
||||
parent = CuraApplication.getInstance())
|
||||
elif groups_by_name[name].intent_category == "default": # Intent category should be stored as "default" if everything is default or as the intent if any of the extruder have an actual intent.
|
||||
groups_by_name[name].intent_category = quality_changes.get("intent_category", "default")
|
||||
|
||||
if "position" in quality_changes: # An extruder profile.
|
||||
groups_by_name[name].metadata_per_extruder[int(quality_changes["position"])] = quality_changes
|
||||
else: # Global profile.
|
||||
groups_by_name[name].metadata_for_global = quality_changes
|
||||
|
||||
quality_groups = self.getQualityGroups(variant_names, material_bases, extruder_enabled)
|
||||
for quality_changes_group in groups_by_name.values():
|
||||
if quality_changes_group.quality_type not in quality_groups:
|
||||
quality_changes_group.is_available = False
|
||||
else:
|
||||
# Quality changes group is available iff the quality group it depends on is available. Irrespective of whether the intent category is available.
|
||||
quality_changes_group.is_available = quality_groups[quality_changes_group.quality_type].is_available
|
||||
|
||||
return list(groups_by_name.values())
|
||||
|
||||
## Gets the preferred global quality node, going by the preferred quality
|
||||
# type.
|
||||
#
|
||||
# If the preferred global quality is not in there, an arbitrary global
|
||||
# quality is taken.
|
||||
# If there are no global qualities, an empty quality is returned.
|
||||
def preferredGlobalQuality(self) -> "QualityNode":
|
||||
return self.global_qualities.get(self.preferred_quality_type, next(iter(self.global_qualities.values())))
|
||||
|
||||
## (Re)loads all variants under this printer.
|
||||
@UM.FlameProfiler.profile
|
||||
def _loadAll(self) -> None:
|
||||
container_registry = ContainerRegistry.getInstance()
|
||||
if not self.has_variants:
|
||||
self.variants["empty"] = VariantNode("empty_variant", machine = self)
|
||||
else:
|
||||
# Find all the variants for this definition ID.
|
||||
variants = container_registry.findInstanceContainersMetadata(type = "variant", definition = self.container_id, hardware_type = "nozzle")
|
||||
for variant in variants:
|
||||
variant_name = variant["name"]
|
||||
if variant_name not in self.variants:
|
||||
self.variants[variant_name] = VariantNode(variant["id"], machine = self)
|
||||
self.variants[variant_name].materialsChanged.connect(self.materialsChanged)
|
||||
if not self.variants:
|
||||
self.variants["empty"] = VariantNode("empty_variant", machine = self)
|
||||
|
||||
# Find the global qualities for this printer.
|
||||
global_qualities = container_registry.findInstanceContainersMetadata(type = "quality", definition = self.quality_definition, global_quality = "True") # First try specific to this printer.
|
||||
if len(global_qualities) == 0: # This printer doesn't override the global qualities.
|
||||
global_qualities = container_registry.findInstanceContainersMetadata(type = "quality", definition = "fdmprinter", global_quality = "True") # Otherwise pick the global global qualities.
|
||||
for global_quality in global_qualities:
|
||||
self.global_qualities[global_quality["quality_type"]] = QualityNode(global_quality["id"], parent = self)
|
|
@ -1,23 +1,23 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from collections import defaultdict, OrderedDict
|
||||
from collections import defaultdict
|
||||
import copy
|
||||
import uuid
|
||||
from typing import Dict, Optional, TYPE_CHECKING, Any, Set, List, cast, Tuple
|
||||
from typing import Dict, Optional, TYPE_CHECKING, Any, List, cast
|
||||
|
||||
from PyQt5.Qt import QTimer, QObject, pyqtSignal, pyqtSlot
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
|
||||
from UM.Decorators import deprecated
|
||||
from UM.Logger import Logger
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Settings.SettingFunction import SettingFunction
|
||||
from UM.Util import parseBool
|
||||
import cura.CuraApplication # Imported like this to prevent circular imports.
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
|
||||
|
||||
from .MaterialNode import MaterialNode
|
||||
from .MaterialGroup import MaterialGroup
|
||||
from .VariantType import VariantType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||
|
@ -37,30 +37,26 @@ if TYPE_CHECKING:
|
|||
# because it's simple.
|
||||
#
|
||||
class MaterialManager(QObject):
|
||||
__instance = None
|
||||
|
||||
@classmethod
|
||||
@deprecated("Use the ContainerTree structure instead.", since = "4.3")
|
||||
def getInstance(cls) -> "MaterialManager":
|
||||
if cls.__instance is None:
|
||||
cls.__instance = MaterialManager()
|
||||
return cls.__instance
|
||||
|
||||
materialsUpdated = pyqtSignal() # Emitted whenever the material lookup tables are updated.
|
||||
favoritesUpdated = pyqtSignal() # Emitted whenever the favorites are changed
|
||||
|
||||
def __init__(self, container_registry, parent = None):
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
self._application = Application.getInstance()
|
||||
self._container_registry = container_registry # type: ContainerRegistry
|
||||
|
||||
# Material_type -> generic material metadata
|
||||
self._fallback_materials_map = dict() # type: Dict[str, Dict[str, Any]]
|
||||
|
||||
# Root_material_id -> MaterialGroup
|
||||
self._material_group_map = dict() # type: Dict[str, MaterialGroup]
|
||||
|
||||
# Approximate diameter str
|
||||
self._diameter_machine_nozzle_buildplate_material_map = dict() # type: Dict[str, Dict[str, MaterialNode]]
|
||||
|
||||
# We're using these two maps to convert between the specific diameter material id and the generic material id
|
||||
# because the generic material ids are used in qualities and definitions, while the specific diameter material is meant
|
||||
# i.e. generic_pla -> generic_pla_175
|
||||
# root_material_id -> approximate diameter str -> root_material_id for that diameter
|
||||
self._material_diameter_map = defaultdict(dict) # type: Dict[str, Dict[str, str]]
|
||||
|
||||
# Material id including diameter (generic_pla_175) -> material root id (generic_pla)
|
||||
self._diameter_material_map = dict() # type: Dict[str, str]
|
||||
|
||||
|
@ -68,218 +64,19 @@ class MaterialManager(QObject):
|
|||
# GUID -> a list of material_groups
|
||||
self._guid_material_groups_map = defaultdict(list) # type: Dict[str, List[MaterialGroup]]
|
||||
|
||||
# The machine definition ID for the non-machine-specific materials.
|
||||
# This is used as the last fallback option if the given machine-specific material(s) cannot be found.
|
||||
self._default_machine_definition_id = "fdmprinter"
|
||||
self._default_approximate_diameter_for_quality_search = "3"
|
||||
|
||||
# When a material gets added/imported, there can be more than one InstanceContainers. In those cases, we don't
|
||||
# want to react on every container/metadata changed signal. The timer here is to buffer it a bit so we don't
|
||||
# react too many time.
|
||||
self._update_timer = QTimer(self)
|
||||
self._update_timer.setInterval(300)
|
||||
self._update_timer.setSingleShot(True)
|
||||
self._update_timer.timeout.connect(self._updateMaps)
|
||||
|
||||
self._container_registry.containerMetaDataChanged.connect(self._onContainerMetadataChanged)
|
||||
self._container_registry.containerAdded.connect(self._onContainerMetadataChanged)
|
||||
self._container_registry.containerRemoved.connect(self._onContainerMetadataChanged)
|
||||
|
||||
self._favorites = set() # type: Set[str]
|
||||
|
||||
def initialize(self) -> None:
|
||||
# Find all materials and put them in a matrix for quick search.
|
||||
material_metadatas = {metadata["id"]: metadata for metadata in
|
||||
self._container_registry.findContainersMetadata(type = "material") if
|
||||
metadata.get("GUID")} # type: Dict[str, Dict[str, Any]]
|
||||
|
||||
self._material_group_map = dict()
|
||||
|
||||
# Map #1
|
||||
# root_material_id -> MaterialGroup
|
||||
for material_id, material_metadata in material_metadatas.items():
|
||||
# We don't store empty material in the lookup tables
|
||||
if material_id == "empty_material":
|
||||
continue
|
||||
|
||||
root_material_id = material_metadata.get("base_file", "")
|
||||
if root_material_id not in material_metadatas: #Not a registered material profile. Don't store this in the look-up tables.
|
||||
continue
|
||||
if root_material_id not in self._material_group_map:
|
||||
self._material_group_map[root_material_id] = MaterialGroup(root_material_id, MaterialNode(material_metadatas[root_material_id]))
|
||||
self._material_group_map[root_material_id].is_read_only = self._container_registry.isReadOnly(root_material_id)
|
||||
group = self._material_group_map[root_material_id]
|
||||
|
||||
# Store this material in the group of the appropriate root material.
|
||||
if material_id != root_material_id:
|
||||
new_node = MaterialNode(material_metadata)
|
||||
group.derived_material_node_list.append(new_node)
|
||||
|
||||
# Order this map alphabetically so it's easier to navigate in a debugger
|
||||
self._material_group_map = OrderedDict(sorted(self._material_group_map.items(), key = lambda x: x[0]))
|
||||
|
||||
# Map #1.5
|
||||
# GUID -> material group list
|
||||
self._guid_material_groups_map = defaultdict(list)
|
||||
for root_material_id, material_group in self._material_group_map.items():
|
||||
guid = material_group.root_material_node.getMetaDataEntry("GUID", "")
|
||||
self._guid_material_groups_map[guid].append(material_group)
|
||||
|
||||
# Map #2
|
||||
# Lookup table for material type -> fallback material metadata, only for read-only materials
|
||||
grouped_by_type_dict = dict() # type: Dict[str, Any]
|
||||
material_types_without_fallback = set()
|
||||
for root_material_id, material_node in self._material_group_map.items():
|
||||
material_type = material_node.root_material_node.getMetaDataEntry("material", "")
|
||||
if material_type not in grouped_by_type_dict:
|
||||
grouped_by_type_dict[material_type] = {"generic": None,
|
||||
"others": []}
|
||||
material_types_without_fallback.add(material_type)
|
||||
brand = material_node.root_material_node.getMetaDataEntry("brand", "")
|
||||
if brand.lower() == "generic":
|
||||
to_add = True
|
||||
if material_type in grouped_by_type_dict:
|
||||
diameter = material_node.root_material_node.getMetaDataEntry("approximate_diameter", "")
|
||||
if diameter != self._default_approximate_diameter_for_quality_search:
|
||||
to_add = False # don't add if it's not the default diameter
|
||||
|
||||
if to_add:
|
||||
# Checking this first allow us to differentiate between not read only materials:
|
||||
# - if it's in the list, it means that is a new material without fallback
|
||||
# - if it is not, then it is a custom material with a fallback material (parent)
|
||||
if material_type in material_types_without_fallback:
|
||||
grouped_by_type_dict[material_type] = material_node.root_material_node._metadata
|
||||
material_types_without_fallback.remove(material_type)
|
||||
|
||||
# Remove the materials that have no fallback materials
|
||||
for material_type in material_types_without_fallback:
|
||||
del grouped_by_type_dict[material_type]
|
||||
self._fallback_materials_map = grouped_by_type_dict
|
||||
|
||||
# Map #3
|
||||
# There can be multiple material profiles for the same material with different diameters, such as "generic_pla"
|
||||
# and "generic_pla_175". This is inconvenient when we do material-specific quality lookup because a quality can
|
||||
# be for either "generic_pla" or "generic_pla_175", but not both. This map helps to get the correct material ID
|
||||
# for quality search.
|
||||
self._material_diameter_map = defaultdict(dict)
|
||||
self._diameter_material_map = dict()
|
||||
|
||||
# Group the material IDs by the same name, material, brand, and color but with different diameters.
|
||||
material_group_dict = dict() # type: Dict[Tuple[Any], Dict[str, str]]
|
||||
keys_to_fetch = ("name", "material", "brand", "color")
|
||||
for root_material_id, machine_node in self._material_group_map.items():
|
||||
root_material_metadata = machine_node.root_material_node._metadata
|
||||
|
||||
key_data_list = [] # type: List[Any]
|
||||
for key in keys_to_fetch:
|
||||
key_data_list.append(machine_node.root_material_node.getMetaDataEntry(key))
|
||||
key_data = cast(Tuple[Any], tuple(key_data_list)) # type: Tuple[Any]
|
||||
|
||||
# If the key_data doesn't exist, it doesn't matter if the material is read only...
|
||||
if key_data not in material_group_dict:
|
||||
material_group_dict[key_data] = dict()
|
||||
else:
|
||||
# ...but if key_data exists, we just overwrite it if the material is read only, otherwise we skip it
|
||||
if not machine_node.is_read_only:
|
||||
continue
|
||||
approximate_diameter = machine_node.root_material_node.getMetaDataEntry("approximate_diameter", "")
|
||||
material_group_dict[key_data][approximate_diameter] = machine_node.root_material_node.getMetaDataEntry("id", "")
|
||||
|
||||
# Map [root_material_id][diameter] -> root_material_id for this diameter
|
||||
for data_dict in material_group_dict.values():
|
||||
for root_material_id1 in data_dict.values():
|
||||
if root_material_id1 in self._material_diameter_map:
|
||||
continue
|
||||
diameter_map = data_dict
|
||||
for root_material_id2 in data_dict.values():
|
||||
self._material_diameter_map[root_material_id2] = diameter_map
|
||||
|
||||
default_root_material_id = data_dict.get(self._default_approximate_diameter_for_quality_search)
|
||||
if default_root_material_id is None:
|
||||
default_root_material_id = list(data_dict.values())[0] # no default diameter present, just take "the" only one
|
||||
for root_material_id in data_dict.values():
|
||||
self._diameter_material_map[root_material_id] = default_root_material_id
|
||||
|
||||
# Map #4
|
||||
# "machine" -> "nozzle name" -> "buildplate name" -> "root material ID" -> specific material InstanceContainer
|
||||
self._diameter_machine_nozzle_buildplate_material_map = dict()
|
||||
for material_metadata in material_metadatas.values():
|
||||
self.__addMaterialMetadataIntoLookupTree(material_metadata)
|
||||
|
||||
favorites = self._application.getPreferences().getValue("cura/favorite_materials")
|
||||
for item in favorites.split(";"):
|
||||
self._favorites.add(item)
|
||||
|
||||
self._favorites = set(cura.CuraApplication.CuraApplication.getInstance().getPreferences().getValue("cura/favorite_materials").split(";"))
|
||||
self.materialsUpdated.emit()
|
||||
|
||||
def __addMaterialMetadataIntoLookupTree(self, material_metadata: Dict[str, Any]) -> None:
|
||||
material_id = material_metadata["id"]
|
||||
self._update_timer = QTimer(self)
|
||||
self._update_timer.setInterval(300)
|
||||
|
||||
# We don't store empty material in the lookup tables
|
||||
if material_id == "empty_material":
|
||||
return
|
||||
self._update_timer.setSingleShot(True)
|
||||
self._update_timer.timeout.connect(self.materialsUpdated)
|
||||
|
||||
root_material_id = material_metadata["base_file"]
|
||||
definition = material_metadata["definition"]
|
||||
approximate_diameter = str(material_metadata["approximate_diameter"])
|
||||
|
||||
if approximate_diameter not in self._diameter_machine_nozzle_buildplate_material_map:
|
||||
self._diameter_machine_nozzle_buildplate_material_map[approximate_diameter] = {}
|
||||
|
||||
machine_nozzle_buildplate_material_map = self._diameter_machine_nozzle_buildplate_material_map[
|
||||
approximate_diameter]
|
||||
if definition not in machine_nozzle_buildplate_material_map:
|
||||
machine_nozzle_buildplate_material_map[definition] = MaterialNode()
|
||||
|
||||
# This is a list of information regarding the intermediate nodes:
|
||||
# nozzle -> buildplate
|
||||
nozzle_name = material_metadata.get("variant_name")
|
||||
buildplate_name = material_metadata.get("buildplate_name")
|
||||
intermediate_node_info_list = [(nozzle_name, VariantType.NOZZLE),
|
||||
(buildplate_name, VariantType.BUILD_PLATE),
|
||||
]
|
||||
|
||||
variant_manager = self._application.getVariantManager()
|
||||
|
||||
machine_node = machine_nozzle_buildplate_material_map[definition]
|
||||
current_node = machine_node
|
||||
current_intermediate_node_info_idx = 0
|
||||
error_message = None # type: Optional[str]
|
||||
while current_intermediate_node_info_idx < len(intermediate_node_info_list):
|
||||
variant_name, variant_type = intermediate_node_info_list[current_intermediate_node_info_idx]
|
||||
if variant_name is not None:
|
||||
# The new material has a specific variant, so it needs to be added to that specific branch in the tree.
|
||||
variant = variant_manager.getVariantNode(definition, variant_name, variant_type)
|
||||
if variant is None:
|
||||
error_message = "Material {id} contains a variant {name} that does not exist.".format(
|
||||
id = material_metadata["id"], name = variant_name)
|
||||
break
|
||||
|
||||
# Update the current node to advance to a more specific branch
|
||||
if variant_name not in current_node.children_map:
|
||||
current_node.children_map[variant_name] = MaterialNode()
|
||||
current_node = current_node.children_map[variant_name]
|
||||
|
||||
current_intermediate_node_info_idx += 1
|
||||
|
||||
if error_message is not None:
|
||||
Logger.log("e", "%s It will not be added into the material lookup tree.", error_message)
|
||||
self._container_registry.addWrongContainerId(material_metadata["id"])
|
||||
return
|
||||
|
||||
# Add the material to the current tree node, which is the deepest (the most specific) branch we can find.
|
||||
# Sanity check: Make sure that there is no duplicated materials.
|
||||
if root_material_id in current_node.material_map:
|
||||
Logger.log("e", "Duplicated material [%s] with root ID [%s]. It has already been added.",
|
||||
material_id, root_material_id)
|
||||
ConfigurationErrorMessage.getInstance().addFaultyContainers(root_material_id)
|
||||
return
|
||||
|
||||
current_node.material_map[root_material_id] = MaterialNode(material_metadata)
|
||||
|
||||
def _updateMaps(self):
|
||||
Logger.log("i", "Updating material lookup data ...")
|
||||
self.initialize()
|
||||
container_registry = ContainerRegistry.getInstance()
|
||||
container_registry.containerMetaDataChanged.connect(self._onContainerMetadataChanged)
|
||||
container_registry.containerAdded.connect(self._onContainerMetadataChanged)
|
||||
container_registry.containerRemoved.connect(self._onContainerMetadataChanged)
|
||||
|
||||
def _onContainerMetadataChanged(self, container):
|
||||
self._onContainerChanged(container)
|
||||
|
@ -290,13 +87,22 @@ class MaterialManager(QObject):
|
|||
return
|
||||
|
||||
# update the maps
|
||||
|
||||
self._update_timer.start()
|
||||
|
||||
def getMaterialGroup(self, root_material_id: str) -> Optional[MaterialGroup]:
|
||||
return self._material_group_map.get(root_material_id)
|
||||
|
||||
def getRootMaterialIDForDiameter(self, root_material_id: str, approximate_diameter: str) -> str:
|
||||
return self._material_diameter_map.get(root_material_id, {}).get(approximate_diameter, root_material_id)
|
||||
original_material = CuraContainerRegistry.getInstance().findInstanceContainersMetadata(id=root_material_id)[0]
|
||||
if original_material["approximate_diameter"] == approximate_diameter:
|
||||
return root_material_id
|
||||
|
||||
matching_materials = CuraContainerRegistry.getInstance().findInstanceContainersMetadata(type = "material", brand = original_material["brand"], definition = original_material["definition"], material = original_material["material"], color_name = original_material["color_name"])
|
||||
for material in matching_materials:
|
||||
if material["approximate_diameter"] == approximate_diameter:
|
||||
return material["id"]
|
||||
return root_material_id
|
||||
|
||||
def getRootMaterialIDWithoutDiameter(self, root_material_id: str) -> str:
|
||||
return self._diameter_material_map.get(root_material_id, "")
|
||||
|
@ -308,73 +114,26 @@ class MaterialManager(QObject):
|
|||
def getAllMaterialGroups(self) -> Dict[str, "MaterialGroup"]:
|
||||
return self._material_group_map
|
||||
|
||||
#
|
||||
# Return a dict with all root material IDs (k) and ContainerNodes (v) that's suitable for the given setup.
|
||||
#
|
||||
def getAvailableMaterials(self, machine_definition: "DefinitionContainer", nozzle_name: Optional[str],
|
||||
buildplate_name: Optional[str], diameter: float) -> Dict[str, MaterialNode]:
|
||||
# round the diameter to get the approximate diameter
|
||||
rounded_diameter = str(round(diameter))
|
||||
if rounded_diameter not in self._diameter_machine_nozzle_buildplate_material_map:
|
||||
Logger.log("i", "Cannot find materials with diameter [%s] (rounded to [%s])", diameter, rounded_diameter)
|
||||
return dict()
|
||||
|
||||
machine_definition_id = machine_definition.getId()
|
||||
|
||||
# If there are nozzle-and-or-buildplate materials, get the nozzle-and-or-buildplate material
|
||||
machine_nozzle_buildplate_material_map = self._diameter_machine_nozzle_buildplate_material_map[rounded_diameter]
|
||||
machine_node = machine_nozzle_buildplate_material_map.get(machine_definition_id)
|
||||
default_machine_node = machine_nozzle_buildplate_material_map.get(self._default_machine_definition_id)
|
||||
nozzle_node = None
|
||||
buildplate_node = None
|
||||
if nozzle_name is not None and machine_node is not None:
|
||||
nozzle_node = machine_node.getChildNode(nozzle_name)
|
||||
# Get buildplate node if possible
|
||||
if nozzle_node is not None and buildplate_name is not None:
|
||||
buildplate_node = nozzle_node.getChildNode(buildplate_name)
|
||||
|
||||
nodes_to_check = [buildplate_node, nozzle_node, machine_node, default_machine_node]
|
||||
# Fallback mechanism of finding materials:
|
||||
# 1. buildplate-specific material
|
||||
# 2. nozzle-specific material
|
||||
# 3. machine-specific material
|
||||
# 4. generic material (for fdmprinter)
|
||||
machine_exclude_materials = machine_definition.getMetaDataEntry("exclude_materials", [])
|
||||
|
||||
material_id_metadata_dict = dict() # type: Dict[str, MaterialNode]
|
||||
excluded_materials = set()
|
||||
for current_node in nodes_to_check:
|
||||
if current_node is None:
|
||||
continue
|
||||
|
||||
# Only exclude the materials that are explicitly specified in the "exclude_materials" field.
|
||||
# Do not exclude other materials that are of the same type.
|
||||
for material_id, node in current_node.material_map.items():
|
||||
if material_id in machine_exclude_materials:
|
||||
excluded_materials.add(material_id)
|
||||
continue
|
||||
|
||||
if material_id not in material_id_metadata_dict:
|
||||
material_id_metadata_dict[material_id] = node
|
||||
|
||||
if excluded_materials:
|
||||
Logger.log("d", "Exclude materials {excluded_materials} for machine {machine_definition_id}".format(excluded_materials = ", ".join(excluded_materials), machine_definition_id = machine_definition_id))
|
||||
|
||||
return material_id_metadata_dict
|
||||
## Gives a dictionary of all root material IDs and their associated
|
||||
# MaterialNodes from the ContainerTree that are available for the given
|
||||
# printer and variant.
|
||||
def getAvailableMaterials(self, definition_id: str, nozzle_name: Optional[str]) -> Dict[str, MaterialNode]:
|
||||
return ContainerTree.getInstance().machines[definition_id].variants[nozzle_name].materials
|
||||
|
||||
#
|
||||
# A convenience function to get available materials for the given machine with the extruder position.
|
||||
#
|
||||
def getAvailableMaterialsForMachineExtruder(self, machine: "GlobalStack",
|
||||
extruder_stack: "ExtruderStack") -> Optional[Dict[str, MaterialNode]]:
|
||||
buildplate_name = machine.getBuildplateName()
|
||||
extruder_stack: "ExtruderStack") -> Dict[str, MaterialNode]:
|
||||
nozzle_name = None
|
||||
if extruder_stack.variant.getId() != "empty_variant":
|
||||
nozzle_name = extruder_stack.variant.getName()
|
||||
diameter = extruder_stack.getApproximateMaterialDiameter()
|
||||
|
||||
# Fetch the available materials (ContainerNode) for the current active machine and extruder setup.
|
||||
return self.getAvailableMaterials(machine.definition, nozzle_name, buildplate_name, diameter)
|
||||
materials = self.getAvailableMaterials(machine.definition.getId(), nozzle_name)
|
||||
compatible_material_diameter = extruder_stack.getApproximateMaterialDiameter()
|
||||
result = {key: material for key, material in materials.items() if material.container and float(material.container.getMetaDataEntry("approximate_diameter")) == compatible_material_diameter}
|
||||
return result
|
||||
|
||||
#
|
||||
# Gets MaterialNode for the given extruder and machine with the given material name.
|
||||
|
@ -384,41 +143,22 @@ class MaterialManager(QObject):
|
|||
#
|
||||
def getMaterialNode(self, machine_definition_id: str, nozzle_name: Optional[str],
|
||||
buildplate_name: Optional[str], diameter: float, root_material_id: str) -> Optional["MaterialNode"]:
|
||||
# round the diameter to get the approximate diameter
|
||||
rounded_diameter = str(round(diameter))
|
||||
if rounded_diameter not in self._diameter_machine_nozzle_buildplate_material_map:
|
||||
Logger.log("i", "Cannot find materials with diameter [%s] (rounded to [%s]) for root material id [%s]",
|
||||
diameter, rounded_diameter, root_material_id)
|
||||
container_tree = ContainerTree.getInstance()
|
||||
machine_node = container_tree.machines.get(machine_definition_id)
|
||||
if machine_node is None:
|
||||
Logger.log("w", "Could not find machine with definition %s in the container tree", machine_definition_id)
|
||||
return None
|
||||
|
||||
# If there are nozzle materials, get the nozzle-specific material
|
||||
machine_nozzle_buildplate_material_map = self._diameter_machine_nozzle_buildplate_material_map[rounded_diameter] # type: Dict[str, MaterialNode]
|
||||
machine_node = machine_nozzle_buildplate_material_map.get(machine_definition_id)
|
||||
nozzle_node = None
|
||||
buildplate_node = None
|
||||
variant_node = machine_node.variants.get(nozzle_name)
|
||||
if variant_node is None:
|
||||
Logger.log("w", "Could not find variant %s for machine with definition %s in the container tree", nozzle_name, machine_definition_id )
|
||||
return None
|
||||
|
||||
# Fallback for "fdmprinter" if the machine-specific materials cannot be found
|
||||
if machine_node is None:
|
||||
machine_node = machine_nozzle_buildplate_material_map.get(self._default_machine_definition_id)
|
||||
if machine_node is not None and nozzle_name is not None:
|
||||
nozzle_node = machine_node.getChildNode(nozzle_name)
|
||||
if nozzle_node is not None and buildplate_name is not None:
|
||||
buildplate_node = nozzle_node.getChildNode(buildplate_name)
|
||||
material_node = variant_node.materials.get(root_material_id)
|
||||
|
||||
# Fallback mechanism of finding materials:
|
||||
# 1. buildplate-specific material
|
||||
# 2. nozzle-specific material
|
||||
# 3. machine-specific material
|
||||
# 4. generic material (for fdmprinter)
|
||||
nodes_to_check = [buildplate_node, nozzle_node, machine_node,
|
||||
machine_nozzle_buildplate_material_map.get(self._default_machine_definition_id)]
|
||||
|
||||
material_node = None
|
||||
for node in nodes_to_check:
|
||||
if node is not None:
|
||||
material_node = node.material_map.get(root_material_id)
|
||||
if material_node:
|
||||
break
|
||||
if material_node is None:
|
||||
Logger.log("w", "Could not find material %s for machine with definition %s and variant %s in the container tree", root_material_id, machine_definition_id, nozzle_name)
|
||||
return None
|
||||
|
||||
return material_node
|
||||
|
||||
|
@ -430,27 +170,12 @@ class MaterialManager(QObject):
|
|||
#
|
||||
def getMaterialNodeByType(self, global_stack: "GlobalStack", position: str, nozzle_name: str,
|
||||
buildplate_name: Optional[str], material_guid: str) -> Optional["MaterialNode"]:
|
||||
node = None
|
||||
machine_definition = global_stack.definition
|
||||
extruder_definition = global_stack.extruders[position].definition
|
||||
if parseBool(machine_definition.getMetaDataEntry("has_materials", False)):
|
||||
material_diameter = extruder_definition.getProperty("material_diameter", "value")
|
||||
if isinstance(material_diameter, SettingFunction):
|
||||
material_diameter = material_diameter(global_stack)
|
||||
extruder = global_stack.extruderList[int(position)]
|
||||
variant_name = extruder.variant.getName()
|
||||
approximate_diameter = extruder.getApproximateMaterialDiameter()
|
||||
|
||||
# Look at the guid to material dictionary
|
||||
root_material_id = None
|
||||
for material_group in self._guid_material_groups_map[material_guid]:
|
||||
root_material_id = cast(str, material_group.root_material_node.getMetaDataEntry("id", ""))
|
||||
break
|
||||
|
||||
if not root_material_id:
|
||||
Logger.log("i", "Cannot find materials with guid [%s] ", material_guid)
|
||||
return None
|
||||
|
||||
node = self.getMaterialNode(machine_definition.getId(), nozzle_name, buildplate_name,
|
||||
material_diameter, root_material_id)
|
||||
return node
|
||||
return self.getMaterialNode(machine_definition.getId(), variant_name, buildplate_name, approximate_diameter, material_guid)
|
||||
|
||||
# There are 2 ways to get fallback materials;
|
||||
# - A fallback by type (@sa getFallbackMaterialIdByMaterialType), which adds the generic version of this material
|
||||
|
@ -475,7 +200,7 @@ class MaterialManager(QObject):
|
|||
return results
|
||||
|
||||
#
|
||||
# Used by QualityManager. Built-in quality profiles may be based on generic material IDs such as "generic_pla".
|
||||
# Built-in quality profiles may be based on generic material IDs such as "generic_pla".
|
||||
# For materials such as ultimaker_pla_orange, no quality profiles may be found, so we should fall back to use
|
||||
# the generic material IDs to search for qualities.
|
||||
#
|
||||
|
@ -501,200 +226,101 @@ class MaterialManager(QObject):
|
|||
## Get default material for given global stack, extruder position and extruder nozzle name
|
||||
# you can provide the extruder_definition and then the position is ignored (useful when building up global stack in CuraStackBuilder)
|
||||
def getDefaultMaterial(self, global_stack: "GlobalStack", position: str, nozzle_name: Optional[str],
|
||||
extruder_definition: Optional["DefinitionContainer"] = None) -> Optional["MaterialNode"]:
|
||||
node = None
|
||||
|
||||
buildplate_name = global_stack.getBuildplateName()
|
||||
machine_definition = global_stack.definition
|
||||
|
||||
# The extruder-compatible material diameter in the extruder definition may not be the correct value because
|
||||
# the user can change it in the definition_changes container.
|
||||
if extruder_definition is None:
|
||||
extruder_stack_or_definition = global_stack.extruders[position]
|
||||
is_extruder_stack = True
|
||||
extruder_definition: Optional["DefinitionContainer"] = None) -> "MaterialNode":
|
||||
definition_id = global_stack.definition.getId()
|
||||
machine_node = ContainerTree.getInstance().machines[definition_id]
|
||||
if nozzle_name in machine_node.variants:
|
||||
nozzle_node = machine_node.variants[nozzle_name]
|
||||
else:
|
||||
extruder_stack_or_definition = extruder_definition
|
||||
is_extruder_stack = False
|
||||
Logger.log("w", "Could not find variant {nozzle_name} for machine with definition {definition_id} in the container tree".format(nozzle_name = nozzle_name, definition_id = definition_id))
|
||||
nozzle_node = next(iter(machine_node.variants))
|
||||
|
||||
if extruder_stack_or_definition and parseBool(global_stack.getMetaDataEntry("has_materials", False)):
|
||||
if is_extruder_stack:
|
||||
material_diameter = extruder_stack_or_definition.getCompatibleMaterialDiameter()
|
||||
else:
|
||||
material_diameter = extruder_stack_or_definition.getProperty("material_diameter", "value")
|
||||
if not parseBool(global_stack.getMetaDataEntry("has_materials", False)):
|
||||
return next(iter(nozzle_node.materials))
|
||||
|
||||
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(), nozzle_name, buildplate_name,
|
||||
material_diameter, root_material_id)
|
||||
return node
|
||||
if extruder_definition is not None:
|
||||
material_diameter = extruder_definition.getProperty("material_diameter", "value")
|
||||
else:
|
||||
material_diameter = global_stack.extruders[position].getCompatibleMaterialDiameter()
|
||||
approximate_material_diameter = round(material_diameter)
|
||||
|
||||
return nozzle_node.preferredMaterial(approximate_material_diameter)
|
||||
|
||||
def removeMaterialByRootId(self, root_material_id: str):
|
||||
material_group = self.getMaterialGroup(root_material_id)
|
||||
if not material_group:
|
||||
Logger.log("i", "Unable to remove the material with id %s, because it doesn't exist.", root_material_id)
|
||||
return
|
||||
container_registry = CuraContainerRegistry.getInstance()
|
||||
results = container_registry.findContainers(id = root_material_id)
|
||||
if not results:
|
||||
container_registry.addWrongContainerId(root_material_id)
|
||||
|
||||
nodes_to_remove = [material_group.root_material_node] + material_group.derived_material_node_list
|
||||
# Sort all nodes with respect to the container ID lengths in the ascending order so the base material container
|
||||
# will be the first one to be removed. We need to do this to ensure that all containers get loaded & deleted.
|
||||
nodes_to_remove = sorted(nodes_to_remove, key = lambda x: len(x.getMetaDataEntry("id", "")))
|
||||
# Try to load all containers first. If there is any faulty ones, they will be put into the faulty container
|
||||
# list, so removeContainer() can ignore those ones.
|
||||
for node in nodes_to_remove:
|
||||
container_id = node.getMetaDataEntry("id", "")
|
||||
results = self._container_registry.findContainers(id = container_id)
|
||||
if not results:
|
||||
self._container_registry.addWrongContainerId(container_id)
|
||||
for node in nodes_to_remove:
|
||||
self._container_registry.removeContainer(node.getMetaDataEntry("id", ""))
|
||||
for result in results:
|
||||
container_registry.removeContainer(result.getMetaDataEntry("id", ""))
|
||||
|
||||
#
|
||||
# Methods for GUI
|
||||
#
|
||||
@pyqtSlot("QVariant", result=bool)
|
||||
@pyqtSlot("QVariant", result = bool)
|
||||
def canMaterialBeRemoved(self, material_node: "MaterialNode"):
|
||||
# Check if the material is active in any extruder train. In that case, the material shouldn't be removed!
|
||||
# In the future we might enable this again, but right now, it's causing a ton of issues if we do (since it
|
||||
# corrupts the configuration)
|
||||
root_material_id = material_node.getMetaDataEntry("base_file")
|
||||
material_group = self.getMaterialGroup(root_material_id)
|
||||
if not material_group:
|
||||
return False
|
||||
root_material_id = material_node.base_file
|
||||
ids_to_remove = {metadata.get("id", "") for metadata in CuraContainerRegistry.getInstance().findInstanceContainersMetadata(base_file = root_material_id)}
|
||||
|
||||
nodes_to_remove = [material_group.root_material_node] + material_group.derived_material_node_list
|
||||
ids_to_remove = [node.getMetaDataEntry("id", "") for node in nodes_to_remove]
|
||||
|
||||
for extruder_stack in self._container_registry.findContainerStacks(type="extruder_train"):
|
||||
for extruder_stack in CuraContainerRegistry.getInstance().findContainerStacks(type = "extruder_train"):
|
||||
if extruder_stack.material.getId() in ids_to_remove:
|
||||
return False
|
||||
return True
|
||||
|
||||
## Change the user-visible name of a material.
|
||||
# \param material_node The ContainerTree node of the material to rename.
|
||||
# \param name The new name for the material.
|
||||
@pyqtSlot("QVariant", str)
|
||||
def setMaterialName(self, material_node: "MaterialNode", name: str) -> None:
|
||||
root_material_id = material_node.getMetaDataEntry("base_file")
|
||||
if root_material_id is None:
|
||||
return
|
||||
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)
|
||||
if material_group:
|
||||
container = material_group.root_material_node.getContainer()
|
||||
if container:
|
||||
container.setName(name)
|
||||
return cura.CuraApplication.CuraApplication.getMaterialManagementModel().setMaterialName(material_node, name)
|
||||
|
||||
## Deletes a material from Cura.
|
||||
#
|
||||
# Removes the given material.
|
||||
#
|
||||
# This function does not do any safety checking any more. Please call this
|
||||
# function only if:
|
||||
# - The material is not read-only.
|
||||
# - The material is not used in any stacks.
|
||||
# If the material was not lazy-loaded yet, this will fully load the
|
||||
# container. When removing this material node, all other materials with
|
||||
# the same base fill will also be removed.
|
||||
# \param material_node The material to remove.
|
||||
@pyqtSlot("QVariant")
|
||||
def removeMaterial(self, material_node: "MaterialNode") -> None:
|
||||
root_material_id = material_node.getMetaDataEntry("base_file")
|
||||
if root_material_id is not None:
|
||||
self.removeMaterialByRootId(root_material_id)
|
||||
return cura.CuraApplication.CuraApplication.getMaterialManagementModel().setMaterialName(material_node)
|
||||
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
def duplicateMaterialByRootId(self, root_material_id: str, new_base_id: Optional[str] = None, new_metadata: Optional[Dict[str, Any]] = None) -> Optional[str]:
|
||||
result = cura.CuraApplication.CuraApplication.getInstance().getMaterialManagementModel().duplicateMaterialByBaseFile(root_material_id, new_base_id, new_metadata)
|
||||
if result is None:
|
||||
return "ERROR"
|
||||
return result
|
||||
|
||||
## Creates a duplicate of a material with the same GUID and base_file
|
||||
# metadata.
|
||||
# \param material_node The node representing the material to duplicate.
|
||||
# \param new_base_id A new material ID for the base material. The IDs of
|
||||
# the submaterials will be based off this one. If not provided, a material
|
||||
# ID will be generated automatically.
|
||||
# \param new_metadata Metadata for the new material. If not provided, this
|
||||
# will be duplicated from the original material.
|
||||
# \return The root material ID of the duplicate material.
|
||||
@pyqtSlot("QVariant", result = str)
|
||||
def duplicateMaterial(self, material_node: MaterialNode, new_base_id: Optional[str] = None, new_metadata: Dict[str, Any] = None) -> Optional[str]:
|
||||
root_material_id = cast(str, material_node.getMetaDataEntry("base_file", ""))
|
||||
|
||||
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()
|
||||
if not base_container:
|
||||
return None
|
||||
|
||||
# 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()
|
||||
if not container_to_copy:
|
||||
continue
|
||||
# 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"):
|
||||
nozzle_name = container_to_copy.getMetaDataEntry("variant_name")
|
||||
new_id += "_" + nozzle_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)
|
||||
|
||||
# if the duplicated material was favorite then the new material should also be added to favorite.
|
||||
if root_material_id in self.getFavorites():
|
||||
self.addFavorite(new_base_id)
|
||||
|
||||
return new_base_id
|
||||
def duplicateMaterial(self, material_node: MaterialNode, new_base_id: Optional[str] = None, new_metadata: Optional[Dict[str, Any]] = None) -> str:
|
||||
result = cura.CuraApplication.CuraApplication.getInstance().getMaterialManagementModel().duplicateMaterial(material_node, new_base_id, new_metadata)
|
||||
if result is None:
|
||||
return "ERROR"
|
||||
return result
|
||||
|
||||
## Create a new material by cloning the preferred material for the current
|
||||
# material diameter and generate a new GUID.
|
||||
#
|
||||
# Create a new material by cloning Generic PLA for the current material diameter and generate a new GUID.
|
||||
# Returns the ID of the newly created material.
|
||||
# The material type is explicitly left to be the one from the preferred
|
||||
# material, since this allows the user to still have SOME profiles to work
|
||||
# with.
|
||||
# \return The ID of the newly created material.
|
||||
@pyqtSlot(result = str)
|
||||
def createMaterial(self) -> str:
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
# Ensure all settings are saved.
|
||||
self._application.saveSettings()
|
||||
|
||||
machine_manager = self._application.getMachineManager()
|
||||
extruder_stack = machine_manager.activeStack
|
||||
|
||||
machine_definition = self._application.getGlobalContainerStack().definition
|
||||
root_material_id = machine_definition.getMetaDataEntry("preferred_material", default = "generic_pla")
|
||||
|
||||
approximate_diameter = str(extruder_stack.approximateMaterialDiameter)
|
||||
root_material_id = self.getRootMaterialIDForDiameter(root_material_id, approximate_diameter)
|
||||
material_group = self.getMaterialGroup(root_material_id)
|
||||
|
||||
if not material_group: # This should never happen
|
||||
Logger.log("w", "Cannot get the material group of %s.", root_material_id)
|
||||
return ""
|
||||
|
||||
# Create a new ID & container to hold the data.
|
||||
new_id = 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
|
||||
return cura.CuraApplication.CuraApplication.getMaterialManagementModel().createMaterial()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def addFavorite(self, root_material_id: str) -> None:
|
||||
|
@ -702,8 +328,8 @@ class MaterialManager(QObject):
|
|||
self.materialsUpdated.emit()
|
||||
|
||||
# Ensure all settings are saved.
|
||||
self._application.getPreferences().setValue("cura/favorite_materials", ";".join(list(self._favorites)))
|
||||
self._application.saveSettings()
|
||||
cura.CuraApplication.CuraApplication.getInstance().getPreferences().setValue("cura/favorite_materials", ";".join(list(self._favorites)))
|
||||
cura.CuraApplication.CuraApplication.getInstance().saveSettings()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def removeFavorite(self, root_material_id: str) -> None:
|
||||
|
@ -715,8 +341,8 @@ class MaterialManager(QObject):
|
|||
self.materialsUpdated.emit()
|
||||
|
||||
# Ensure all settings are saved.
|
||||
self._application.getPreferences().setValue("cura/favorite_materials", ";".join(list(self._favorites)))
|
||||
self._application.saveSettings()
|
||||
cura.CuraApplication.CuraApplication.getInstance().getPreferences().setValue("cura/favorite_materials", ";".join(list(self._favorites)))
|
||||
cura.CuraApplication.CuraApplication.getInstance().saveSettings()
|
||||
|
||||
@pyqtSlot()
|
||||
def getFavorites(self):
|
||||
|
|
|
@ -1,25 +1,136 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from typing import Optional, Dict, Any
|
||||
from collections import OrderedDict
|
||||
from .ContainerNode import ContainerNode
|
||||
|
||||
from typing import Any, Optional, TYPE_CHECKING
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Settings.Interfaces import ContainerInterface
|
||||
from UM.Signal import Signal
|
||||
from cura.Machines.ContainerNode import ContainerNode
|
||||
from cura.Machines.QualityNode import QualityNode
|
||||
import UM.FlameProfiler
|
||||
if TYPE_CHECKING:
|
||||
from typing import Dict
|
||||
from cura.Machines.VariantNode import VariantNode
|
||||
|
||||
## Represents a material in the container tree.
|
||||
#
|
||||
# A MaterialNode is a node in the material lookup tree/map/table. It contains 2 (extra) fields:
|
||||
# - material_map: a one-to-one map of "material_root_id" to material_node.
|
||||
# - children_map: the key-value map for child nodes of this node. This is used in a lookup tree.
|
||||
#
|
||||
#
|
||||
# Its subcontainers are quality profiles.
|
||||
class MaterialNode(ContainerNode):
|
||||
__slots__ = ("material_map", "children_map")
|
||||
def __init__(self, container_id: str, variant: "VariantNode") -> None:
|
||||
super().__init__(container_id)
|
||||
self.variant = variant
|
||||
self.qualities = {} # type: Dict[str, QualityNode] # Mapping container IDs to quality profiles.
|
||||
self.materialChanged = Signal() # Triggered when the material is removed or its metadata is updated.
|
||||
|
||||
def __init__(self, metadata: Optional[Dict[str, Any]] = None) -> None:
|
||||
super().__init__(metadata = metadata)
|
||||
self.material_map = {} # type: Dict[str, MaterialNode] # material_root_id -> material_node
|
||||
container_registry = ContainerRegistry.getInstance()
|
||||
my_metadata = container_registry.findContainersMetadata(id = container_id)[0]
|
||||
self.base_file = my_metadata["base_file"]
|
||||
self.material_type = my_metadata["material"]
|
||||
self.guid = my_metadata["GUID"]
|
||||
self._loadAll()
|
||||
container_registry.containerRemoved.connect(self._onRemoved)
|
||||
container_registry.containerMetaDataChanged.connect(self._onMetadataChanged)
|
||||
|
||||
# We overide this as we want to indicate that MaterialNodes can only contain other material nodes.
|
||||
self.children_map = OrderedDict() # type: OrderedDict[str, "MaterialNode"]
|
||||
## Finds the preferred quality for this printer with this material and this
|
||||
# variant loaded.
|
||||
#
|
||||
# If the preferred quality is not available, an arbitrary quality is
|
||||
# returned. If there is a configuration mistake (like a typo in the
|
||||
# preferred quality) this returns a random available quality. If there are
|
||||
# no available qualities, this will return the empty quality node.
|
||||
# \return The node for the preferred quality, or any arbitrary quality if
|
||||
# there is no match.
|
||||
def preferredQuality(self) -> QualityNode:
|
||||
for quality_id, quality_node in self.qualities.items():
|
||||
if self.variant.machine.preferred_quality_type == quality_node.quality_type:
|
||||
return quality_node
|
||||
fallback = next(iter(self.qualities.values())) # Should only happen with empty quality node.
|
||||
Logger.log("w", "Could not find preferred quality type {preferred_quality_type} for material {material_id} and variant {variant_id}, falling back to {fallback}.".format(
|
||||
preferred_quality_type = self.variant.machine.preferred_quality_type,
|
||||
material_id = self.container_id,
|
||||
variant_id = self.variant.container_id,
|
||||
fallback = fallback.container_id
|
||||
))
|
||||
return fallback
|
||||
|
||||
def getChildNode(self, child_key: str) -> Optional["MaterialNode"]:
|
||||
return self.children_map.get(child_key)
|
||||
@UM.FlameProfiler.profile
|
||||
def _loadAll(self) -> None:
|
||||
container_registry = ContainerRegistry.getInstance()
|
||||
# Find all quality profiles that fit on this material.
|
||||
if not self.variant.machine.has_machine_quality: # Need to find the global qualities.
|
||||
qualities = container_registry.findInstanceContainersMetadata(type = "quality", definition = "fdmprinter")
|
||||
elif not self.variant.machine.has_materials:
|
||||
qualities = container_registry.findInstanceContainersMetadata(type="quality", definition=self.variant.machine.quality_definition)
|
||||
else:
|
||||
if self.variant.machine.has_variants:
|
||||
# Need to find the qualities that specify a material profile with the same material type.
|
||||
qualities = container_registry.findInstanceContainersMetadata(type = "quality", definition = self.variant.machine.quality_definition, variant = self.variant.variant_name, material = self.container_id) # First try by exact material ID.
|
||||
else:
|
||||
qualities = container_registry.findInstanceContainersMetadata(type="quality", definition=self.variant.machine.quality_definition, material=self.container_id)
|
||||
if not qualities:
|
||||
my_material_type = self.material_type
|
||||
if self.variant.machine.has_variants:
|
||||
qualities_any_material = container_registry.findInstanceContainersMetadata(type = "quality", definition = self.variant.machine.quality_definition, variant = self.variant.variant_name)
|
||||
else:
|
||||
qualities_any_material = container_registry.findInstanceContainersMetadata(type="quality", definition = self.variant.machine.quality_definition)
|
||||
for material_metadata in container_registry.findInstanceContainersMetadata(type = "material", material = my_material_type):
|
||||
qualities.extend((quality for quality in qualities_any_material if quality.get("material") == material_metadata["id"]))
|
||||
|
||||
if not qualities: # No quality profiles found. Go by GUID then.
|
||||
my_guid = self.guid
|
||||
for material_metadata in container_registry.findInstanceContainersMetadata(type = "material", guid = my_guid):
|
||||
qualities.extend((quality for quality in qualities_any_material if quality["material"] == material_metadata["id"]))
|
||||
|
||||
if not qualities:
|
||||
# There are still some machines that should use global profiles in the extruder, so do that now.
|
||||
# These are mostly older machines that haven't received updates (so single extruder machines without specific qualities
|
||||
# but that do have materials and profiles specific to that machine)
|
||||
qualities.extend([quality for quality in qualities_any_material if quality.get("global_quality", "False") != "False"])
|
||||
|
||||
for quality in qualities:
|
||||
quality_id = quality["id"]
|
||||
if quality_id not in self.qualities:
|
||||
self.qualities[quality_id] = QualityNode(quality_id, parent = self)
|
||||
if not self.qualities:
|
||||
self.qualities["empty_quality"] = QualityNode("empty_quality", parent = self)
|
||||
|
||||
## Triggered when any container is removed, but only handles it when the
|
||||
# container is removed that this node represents.
|
||||
# \param container The container that was allegedly removed.
|
||||
def _onRemoved(self, container: ContainerInterface) -> None:
|
||||
if container.getId() == self.container_id:
|
||||
# Remove myself from my parent.
|
||||
if self.base_file in self.variant.materials:
|
||||
del self.variant.materials[self.base_file]
|
||||
if not self.variant.materials:
|
||||
self.variant.materials["empty_material"] = MaterialNode("empty_material", variant = self.variant)
|
||||
self.materialChanged.emit(self)
|
||||
|
||||
## Triggered when any metadata changed in any container, but only handles
|
||||
# it when the metadata of this node is changed.
|
||||
# \param container The container whose metadata changed.
|
||||
# \param kwargs Key-word arguments provided when changing the metadata.
|
||||
# These are ignored. As far as I know they are never provided to this
|
||||
# call.
|
||||
def _onMetadataChanged(self, container: ContainerInterface, **kwargs: Any) -> None:
|
||||
if container.getId() != self.container_id:
|
||||
return
|
||||
|
||||
new_metadata = container.getMetaData()
|
||||
old_base_file = self.base_file
|
||||
if new_metadata["base_file"] != old_base_file:
|
||||
self.base_file = new_metadata["base_file"]
|
||||
if old_base_file in self.variant.materials: # Move in parent node.
|
||||
del self.variant.materials[old_base_file]
|
||||
self.variant.materials[self.base_file] = self
|
||||
|
||||
old_material_type = self.material_type
|
||||
self.material_type = new_metadata["material"]
|
||||
old_guid = self.guid
|
||||
self.guid = new_metadata["GUID"]
|
||||
if self.base_file != old_base_file or self.material_type != old_material_type or self.guid != old_guid: # List of quality profiles could've changed.
|
||||
self.qualities = {}
|
||||
self._loadAll() # Re-load the quality profiles for this node.
|
||||
self.materialChanged.emit(self)
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Optional, Dict, Set
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
|
||||
|
||||
from UM.Qt.ListModel import ListModel
|
||||
|
||||
import cura.CuraApplication # Imported like this to prevent a circular reference.
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
from cura.Machines.MaterialNode import MaterialNode
|
||||
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
|
||||
|
||||
## This is the base model class for GenericMaterialsModel and MaterialBrandsModel.
|
||||
# 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
|
||||
from cura.Machines.MaterialNode import MaterialNode
|
||||
|
||||
|
||||
class BaseMaterialsModel(ListModel):
|
||||
|
||||
extruderPositionChanged = pyqtSignal()
|
||||
|
@ -24,19 +27,25 @@ class BaseMaterialsModel(ListModel):
|
|||
|
||||
self._application = CuraApplication.getInstance()
|
||||
|
||||
self._available_materials = {} # type: Dict[str, MaterialNode]
|
||||
self._favorite_ids = set() # type: Set[str]
|
||||
|
||||
# Make these managers available to all material models
|
||||
self._container_registry = self._application.getInstance().getContainerRegistry()
|
||||
self._machine_manager = self._application.getMachineManager()
|
||||
self._material_manager = self._application.getMaterialManager()
|
||||
|
||||
self._extruder_position = 0
|
||||
self._extruder_stack = None
|
||||
self._enabled = True
|
||||
|
||||
# Update the stack and the model data when the machine changes
|
||||
self._machine_manager.globalContainerChanged.connect(self._updateExtruderStack)
|
||||
self._updateExtruderStack()
|
||||
|
||||
# Update this model when switching machines
|
||||
# Update this model when switching machines, when adding materials or changing their metadata.
|
||||
self._machine_manager.activeStackChanged.connect(self._update)
|
||||
|
||||
# Update this model when list of materials changes
|
||||
self._material_manager.materialsUpdated.connect(self._update)
|
||||
ContainerTree.getInstance().materialsChanged.connect(self._materialsListChanged)
|
||||
self._application.getMaterialManagementModel().favoritesChanged.connect(self._update)
|
||||
|
||||
self.addRoleName(Qt.UserRole + 1, "root_material_id")
|
||||
self.addRoleName(Qt.UserRole + 2, "id")
|
||||
|
@ -55,13 +64,6 @@ class BaseMaterialsModel(ListModel):
|
|||
self.addRoleName(Qt.UserRole + 15, "container_node")
|
||||
self.addRoleName(Qt.UserRole + 16, "is_favorite")
|
||||
|
||||
self._extruder_position = 0
|
||||
self._extruder_stack = None
|
||||
|
||||
self._available_materials = None # type: Optional[Dict[str, MaterialNode]]
|
||||
self._favorite_ids = set() # type: Set[str]
|
||||
self._enabled = True
|
||||
|
||||
def _updateExtruderStack(self):
|
||||
global_stack = self._machine_manager.activeMachine
|
||||
if global_stack is None:
|
||||
|
@ -100,31 +102,57 @@ class BaseMaterialsModel(ListModel):
|
|||
self._update()
|
||||
self.enabledChanged.emit()
|
||||
|
||||
@pyqtProperty(bool, fset=setEnabled, notify=enabledChanged)
|
||||
@pyqtProperty(bool, fset = setEnabled, notify = enabledChanged)
|
||||
def enabled(self):
|
||||
return self._enabled
|
||||
|
||||
## Triggered when a list of materials changed somewhere in the container
|
||||
# tree. This change may trigger an _update() call when the materials
|
||||
# changed for the configuration that this model is looking for.
|
||||
def _materialsListChanged(self, material: MaterialNode) -> None:
|
||||
if self._extruder_stack is None:
|
||||
return
|
||||
if material.variant.container_id != self._extruder_stack.variant.getId():
|
||||
return
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if not global_stack:
|
||||
return
|
||||
if material.variant.machine.container_id != global_stack.definition.getId():
|
||||
return
|
||||
self._update()
|
||||
|
||||
## Triggered when the list of favorite materials is changed.
|
||||
def _favoritesChanged(self, material_base_file: str) -> None:
|
||||
if material_base_file in self._available_materials:
|
||||
self._update()
|
||||
|
||||
## This is an abstract method that needs to be implemented by the specific
|
||||
# models themselves.
|
||||
def _update(self):
|
||||
pass
|
||||
self._favorite_ids = set(cura.CuraApplication.CuraApplication.getInstance().getPreferences().getValue("cura/favorite_materials").split(";"))
|
||||
|
||||
# Update the available materials (ContainerNode) for the current active machine and extruder setup.
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if not global_stack.hasMaterials:
|
||||
return # There are no materials for this machine, so nothing to do.
|
||||
extruder_stack = global_stack.extruders.get(str(self._extruder_position))
|
||||
if not extruder_stack:
|
||||
return
|
||||
nozzle_name = extruder_stack.variant.getName()
|
||||
materials = ContainerTree.getInstance().machines[global_stack.definition.getId()].variants[nozzle_name].materials
|
||||
approximate_material_diameter = extruder_stack.getApproximateMaterialDiameter()
|
||||
self._available_materials = {key: material for key, material in materials.items() if float(material.container.getMetaDataEntry("approximate_diameter")) == approximate_material_diameter}
|
||||
|
||||
## This method is used by all material models in the beginning of the
|
||||
# _update() method in order to prevent errors. It's the same in all models
|
||||
# so it's placed here for easy access.
|
||||
def _canUpdate(self):
|
||||
global_stack = self._machine_manager.activeMachine
|
||||
|
||||
if global_stack is None or not self._enabled:
|
||||
return False
|
||||
|
||||
try:
|
||||
extruder_stack = global_stack.extruderList[self._extruder_position]
|
||||
except IndexError:
|
||||
return False
|
||||
|
||||
self._available_materials = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack, extruder_stack)
|
||||
if self._available_materials is None:
|
||||
extruder_position = str(self._extruder_position)
|
||||
if extruder_position not in global_stack.extruders:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
@ -132,7 +160,10 @@ class BaseMaterialsModel(ListModel):
|
|||
## This is another convenience function which is shared by all material
|
||||
# models so it's put here to avoid having so much duplicated code.
|
||||
def _createMaterialItem(self, root_material_id, container_node):
|
||||
metadata = container_node.getMetadata()
|
||||
metadata_list = CuraContainerRegistry.getInstance().findContainersMetadata(id = container_node.container_id)
|
||||
if not metadata_list:
|
||||
return None
|
||||
metadata = metadata_list[0]
|
||||
item = {
|
||||
"root_material_id": root_material_id,
|
||||
"id": metadata["id"],
|
||||
|
@ -153,4 +184,3 @@ class BaseMaterialsModel(ListModel):
|
|||
"is_favorite": root_material_id in self._favorite_ids
|
||||
}
|
||||
return item
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
|
@ -21,31 +21,9 @@ class BuildPlateModel(ListModel):
|
|||
self.addRoleName(self.NameRole, "name")
|
||||
self.addRoleName(self.ContainerNodeRole, "container_node")
|
||||
|
||||
self._application = Application.getInstance()
|
||||
self._variant_manager = self._application._variant_manager
|
||||
self._machine_manager = self._application.getMachineManager()
|
||||
|
||||
self._machine_manager.globalContainerChanged.connect(self._update)
|
||||
|
||||
self._update()
|
||||
|
||||
def _update(self):
|
||||
Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
|
||||
global_stack = self._machine_manager._global_container_stack
|
||||
if not global_stack:
|
||||
self.setItems([])
|
||||
return
|
||||
|
||||
has_variants = parseBool(global_stack.getMetaDataEntry("has_variant_buildplates", False))
|
||||
if not has_variants:
|
||||
self.setItems([])
|
||||
return
|
||||
|
||||
variant_dict = self._variant_manager.getVariantNodes(global_stack, variant_type = VariantType.BUILD_PLATE)
|
||||
|
||||
item_list = []
|
||||
for name, variant_node in variant_dict.items():
|
||||
item = {"name": name,
|
||||
"container_node": variant_node}
|
||||
item_list.append(item)
|
||||
self.setItems(item_list)
|
||||
self.setItems([])
|
||||
return
|
|
@ -1,31 +1,48 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
from UM.Logger import Logger
|
||||
|
||||
import cura.CuraApplication # Imported this way to prevent circular references.
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
from cura.Machines.Models.QualityProfilesDropDownMenuModel import QualityProfilesDropDownMenuModel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from PyQt5.QtCore import QObject
|
||||
from UM.Settings.Interfaces import ContainerInterface
|
||||
|
||||
#
|
||||
# This model is used for the custom profile items in the profile drop down menu.
|
||||
#
|
||||
|
||||
## This model is used for the custom profile items in the profile drop down
|
||||
# menu.
|
||||
class CustomQualityProfilesDropDownMenuModel(QualityProfilesDropDownMenuModel):
|
||||
|
||||
def _update(self):
|
||||
def __init__(self, parent: Optional["QObject"] = None) -> None:
|
||||
super().__init__(parent)
|
||||
|
||||
container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry()
|
||||
container_registry.containerAdded.connect(self._qualityChangesListChanged)
|
||||
container_registry.containerRemoved.connect(self._qualityChangesListChanged)
|
||||
container_registry.containerMetaDataChanged.connect(self._qualityChangesListChanged)
|
||||
|
||||
def _qualityChangesListChanged(self, container: "ContainerInterface") -> None:
|
||||
if container.getMetaDataEntry("type") == "quality_changes":
|
||||
self._update()
|
||||
|
||||
def _update(self) -> None:
|
||||
Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
|
||||
|
||||
active_global_stack = self._machine_manager.activeMachine
|
||||
active_global_stack = cura.CuraApplication.CuraApplication.getInstance().getMachineManager().activeMachine
|
||||
if active_global_stack is None:
|
||||
self.setItems([])
|
||||
Logger.log("d", "No active GlobalStack, set %s as empty.", self.__class__.__name__)
|
||||
return
|
||||
|
||||
quality_changes_group_dict = self._quality_manager.getQualityChangesGroups(active_global_stack)
|
||||
quality_changes_list = ContainerTree.getInstance().getCurrentQualityChangesGroups()
|
||||
|
||||
item_list = []
|
||||
for key in sorted(quality_changes_group_dict, key = lambda name: name.upper()):
|
||||
quality_changes_group = quality_changes_group_dict[key]
|
||||
|
||||
for quality_changes_group in sorted(quality_changes_list, key = lambda qgc: qgc.name.lower()):
|
||||
item = {"name": quality_changes_group.name,
|
||||
"layer_height": "",
|
||||
"layer_height_without_unit": "",
|
||||
|
|
|
@ -1,28 +1,33 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
|
||||
import cura.CuraApplication # To listen to changes to the preferences.
|
||||
|
||||
## Model that shows the list of favorite materials.
|
||||
class FavoriteMaterialsModel(BaseMaterialsModel):
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
cura.CuraApplication.CuraApplication.getInstance().getPreferences().preferenceChanged.connect(self._onFavoritesChanged)
|
||||
self._update()
|
||||
|
||||
## Triggered when any preference changes, but only handles it when the list
|
||||
# of favourites is changed.
|
||||
def _onFavoritesChanged(self, preference_key: str) -> None:
|
||||
if preference_key != "cura/favorite_materials":
|
||||
return
|
||||
self._update()
|
||||
|
||||
def _update(self):
|
||||
if not self._canUpdate():
|
||||
return
|
||||
|
||||
# Get updated list of favorites
|
||||
self._favorite_ids = self._material_manager.getFavorites()
|
||||
super()._update()
|
||||
|
||||
item_list = []
|
||||
|
||||
for root_material_id, container_node in self._available_materials.items():
|
||||
metadata = container_node.getMetadata()
|
||||
|
||||
# Do not include the materials from a to-be-removed package
|
||||
if bool(metadata.get("removed", False)):
|
||||
if bool(container_node.container.getMetaDataEntry("removed", False)):
|
||||
continue
|
||||
|
||||
# Only add results for favorite materials
|
||||
|
@ -30,7 +35,8 @@ class FavoriteMaterialsModel(BaseMaterialsModel):
|
|||
continue
|
||||
|
||||
item = self._createMaterialItem(root_material_id, container_node)
|
||||
item_list.append(item)
|
||||
if item:
|
||||
item_list.append(item)
|
||||
|
||||
# Sort the item list alphabetically by name
|
||||
item_list = sorted(item_list, key = lambda d: d["brand"].upper())
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
|
||||
|
@ -12,25 +12,22 @@ class GenericMaterialsModel(BaseMaterialsModel):
|
|||
def _update(self):
|
||||
if not self._canUpdate():
|
||||
return
|
||||
|
||||
# Get updated list of favorites
|
||||
self._favorite_ids = self._material_manager.getFavorites()
|
||||
super()._update()
|
||||
|
||||
item_list = []
|
||||
|
||||
for root_material_id, container_node in self._available_materials.items():
|
||||
metadata = container_node.getMetadata()
|
||||
|
||||
# Do not include the materials from a to-be-removed package
|
||||
if bool(metadata.get("removed", False)):
|
||||
if bool(container_node.container.getMetaDataEntry("removed", False)):
|
||||
continue
|
||||
|
||||
# Only add results for generic materials
|
||||
if metadata["brand"].lower() != "generic":
|
||||
if container_node.container.getMetaDataEntry("brand", "unknown").lower() != "generic":
|
||||
continue
|
||||
|
||||
item = self._createMaterialItem(root_material_id, container_node)
|
||||
item_list.append(item)
|
||||
if item:
|
||||
item_list.append(item)
|
||||
|
||||
# Sort the item list alphabetically by name
|
||||
item_list = sorted(item_list, key = lambda d: d["name"].upper())
|
||||
|
|
74
cura/Machines/Models/IntentCategoryModel.py
Normal file
74
cura/Machines/Models/IntentCategoryModel.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
#Copyright (c) 2019 Ultimaker B.V.
|
||||
#Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
import collections
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from cura.Machines.Models.IntentModel import IntentModel
|
||||
from cura.Settings.IntentManager import IntentManager
|
||||
from UM.Qt.ListModel import ListModel
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry #To update the list if anything changes.
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal
|
||||
import cura.CuraApplication
|
||||
if TYPE_CHECKING:
|
||||
from UM.Settings.ContainerRegistry import ContainerInterface
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
## Lists the intent categories that are available for the current printer
|
||||
# configuration.
|
||||
class IntentCategoryModel(ListModel):
|
||||
NameRole = Qt.UserRole + 1
|
||||
IntentCategoryRole = Qt.UserRole + 2
|
||||
WeightRole = Qt.UserRole + 3
|
||||
QualitiesRole = Qt.UserRole + 4
|
||||
|
||||
#Translations to user-visible string. Ordered by weight.
|
||||
#TODO: Create a solution for this name and weight to be used dynamically.
|
||||
name_translation = collections.OrderedDict() #type: "collections.OrderedDict[str,str]"
|
||||
name_translation["default"] = catalog.i18nc("@label", "Default")
|
||||
name_translation["engineering"] = catalog.i18nc("@label", "Engineering")
|
||||
name_translation["smooth"] = catalog.i18nc("@label", "Smooth")
|
||||
|
||||
modelUpdated = pyqtSignal()
|
||||
|
||||
## Creates a new model for a certain intent category.
|
||||
# \param The category to list the intent profiles for.
|
||||
def __init__(self, intent_category: str) -> None:
|
||||
super().__init__()
|
||||
self._intent_category = intent_category
|
||||
|
||||
self.addRoleName(self.NameRole, "name")
|
||||
self.addRoleName(self.IntentCategoryRole, "intent_category")
|
||||
self.addRoleName(self.WeightRole, "weight")
|
||||
self.addRoleName(self.QualitiesRole, "qualities")
|
||||
|
||||
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerChange)
|
||||
ContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChange)
|
||||
cura.CuraApplication.CuraApplication.getInstance().getMachineManager().activeStackChanged.connect(self.update)
|
||||
|
||||
self.update()
|
||||
|
||||
## Updates the list of intents if an intent profile was added or removed.
|
||||
def _onContainerChange(self, container: "ContainerInterface") -> None:
|
||||
if container.getMetaDataEntry("type") == "intent":
|
||||
self.update()
|
||||
|
||||
## Updates the list of intents.
|
||||
def update(self) -> None:
|
||||
available_categories = IntentManager.getInstance().currentAvailableIntentCategories()
|
||||
result = []
|
||||
for category in available_categories:
|
||||
qualities = IntentModel()
|
||||
qualities.setIntentCategory(category)
|
||||
result.append({
|
||||
"name": self.name_translation.get(category, catalog.i18nc("@label", "Unknown")),
|
||||
"intent_category": category,
|
||||
"weight": list(self.name_translation.keys()).index(category),
|
||||
"qualities": qualities
|
||||
})
|
||||
result.sort(key = lambda k: k["weight"])
|
||||
self.setItems(result)
|
131
cura/Machines/Models/IntentModel.py
Normal file
131
cura/Machines/Models/IntentModel.py
Normal file
|
@ -0,0 +1,131 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from typing import Optional, Dict, Any, Set, List
|
||||
|
||||
from PyQt5.QtCore import Qt, QObject, pyqtProperty, pyqtSignal
|
||||
|
||||
import cura.CuraApplication
|
||||
from UM.Qt.ListModel import ListModel
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
from cura.Machines.MaterialNode import MaterialNode
|
||||
from cura.Machines.Models.MachineModelUtils import fetchLayerHeight
|
||||
from cura.Machines.QualityGroup import QualityGroup
|
||||
|
||||
|
||||
class IntentModel(ListModel):
|
||||
NameRole = Qt.UserRole + 1
|
||||
QualityTypeRole = Qt.UserRole + 2
|
||||
LayerHeightRole = Qt.UserRole + 3
|
||||
AvailableRole = Qt.UserRole + 4
|
||||
IntentRole = Qt.UserRole + 5
|
||||
|
||||
def __init__(self, parent: Optional[QObject] = None) -> None:
|
||||
super().__init__(parent)
|
||||
|
||||
self.addRoleName(self.NameRole, "name")
|
||||
self.addRoleName(self.QualityTypeRole, "quality_type")
|
||||
self.addRoleName(self.LayerHeightRole, "layer_height")
|
||||
self.addRoleName(self.AvailableRole, "available")
|
||||
self.addRoleName(self.IntentRole, "intent_category")
|
||||
|
||||
self._intent_category = "engineering"
|
||||
|
||||
machine_manager = cura.CuraApplication.CuraApplication.getInstance().getMachineManager()
|
||||
machine_manager.globalContainerChanged.connect(self._update)
|
||||
machine_manager.extruderChanged.connect(self._update) # We also need to update if an extruder gets disabled
|
||||
ContainerRegistry.getInstance().containerAdded.connect(self._onChanged)
|
||||
ContainerRegistry.getInstance().containerRemoved.connect(self._onChanged)
|
||||
self._layer_height_unit = "" # This is cached
|
||||
self._update()
|
||||
|
||||
intentCategoryChanged = pyqtSignal()
|
||||
|
||||
def setIntentCategory(self, new_category: str) -> None:
|
||||
if self._intent_category != new_category:
|
||||
self._intent_category = new_category
|
||||
self.intentCategoryChanged.emit()
|
||||
self._update()
|
||||
|
||||
@pyqtProperty(str, fset = setIntentCategory, notify = intentCategoryChanged)
|
||||
def intentCategory(self) -> str:
|
||||
return self._intent_category
|
||||
|
||||
def _onChanged(self, container):
|
||||
if container.getMetaDataEntry("type") == "intent":
|
||||
self._update()
|
||||
|
||||
def _update(self) -> None:
|
||||
new_items = [] # type: List[Dict[str, Any]]
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if not global_stack:
|
||||
self.setItems(new_items)
|
||||
return
|
||||
quality_groups = ContainerTree.getInstance().getCurrentQualityGroups()
|
||||
|
||||
material_nodes = self._getActiveMaterials()
|
||||
|
||||
added_quality_type_set = set() # type: Set[str]
|
||||
for material_node in material_nodes:
|
||||
intents = self._getIntentsForMaterial(material_node, quality_groups)
|
||||
for intent in intents:
|
||||
if intent["quality_type"] not in added_quality_type_set:
|
||||
new_items.append(intent)
|
||||
added_quality_type_set.add(intent["quality_type"])
|
||||
|
||||
# Now that we added all intents that we found something for, ensure that we set add ticks (and layer_heights)
|
||||
# for all groups that we don't have anything for (and set it to not available)
|
||||
for quality_type, quality_group in quality_groups.items():
|
||||
# Add the intents that are of the correct category
|
||||
if quality_type not in added_quality_type_set:
|
||||
layer_height = fetchLayerHeight(quality_group)
|
||||
new_items.append({"name": "Unavailable",
|
||||
"quality_type": quality_type,
|
||||
"layer_height": layer_height,
|
||||
"intent_category": self._intent_category,
|
||||
"available": False})
|
||||
added_quality_type_set.add(quality_type)
|
||||
|
||||
new_items = sorted(new_items, key = lambda x: x["layer_height"])
|
||||
self.setItems(new_items)
|
||||
|
||||
## Get the active materials for all extruders. No duplicates will be returned
|
||||
def _getActiveMaterials(self) -> Set["MaterialNode"]:
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if global_stack is None:
|
||||
return set()
|
||||
|
||||
container_tree = ContainerTree.getInstance()
|
||||
machine_node = container_tree.machines[global_stack.definition.getId()]
|
||||
nodes = set() # type: Set[MaterialNode]
|
||||
|
||||
for extruder in global_stack.extruderList:
|
||||
active_variant_name = extruder.variant.getMetaDataEntry("name")
|
||||
active_variant_node = machine_node.variants[active_variant_name]
|
||||
active_material_node = active_variant_node.materials[extruder.material.getMetaDataEntry("base_file")]
|
||||
nodes.add(active_material_node)
|
||||
|
||||
return nodes
|
||||
|
||||
def _getIntentsForMaterial(self, active_material_node: "MaterialNode", quality_groups: Dict[str, "QualityGroup"]) -> List[Dict[str, Any]]:
|
||||
extruder_intents = [] # type: List[Dict[str, Any]]
|
||||
|
||||
for quality_id, quality_node in active_material_node.qualities.items():
|
||||
if quality_node.quality_type not in quality_groups: # Don't add the empty quality type (or anything else that would crash, defensively).
|
||||
continue
|
||||
quality_group = quality_groups[quality_node.quality_type]
|
||||
layer_height = fetchLayerHeight(quality_group)
|
||||
|
||||
for intent_id, intent_node in quality_node.intents.items():
|
||||
if intent_node.intent_category != self._intent_category:
|
||||
continue
|
||||
extruder_intents.append({"name": quality_group.name,
|
||||
"quality_type": quality_group.quality_type,
|
||||
"layer_height": layer_height,
|
||||
"available": quality_group.is_available,
|
||||
"intent_category": self._intent_category
|
||||
})
|
||||
return extruder_intents
|
||||
|
||||
def __repr__(self):
|
||||
return str(self.items)
|
37
cura/Machines/Models/MachineModelUtils.py
Normal file
37
cura/Machines/Models/MachineModelUtils.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from UM.Settings.SettingFunction import SettingFunction
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.Machines.QualityGroup import QualityGroup
|
||||
|
||||
layer_height_unit = ""
|
||||
|
||||
|
||||
def fetchLayerHeight(quality_group: "QualityGroup") -> float:
|
||||
from cura.CuraApplication import CuraApplication
|
||||
global_stack = CuraApplication.getInstance().getMachineManager().activeMachine
|
||||
|
||||
default_layer_height = global_stack.definition.getProperty("layer_height", "value")
|
||||
|
||||
# Get layer_height from the quality profile for the GlobalStack
|
||||
if quality_group.node_for_global is None:
|
||||
return float(default_layer_height)
|
||||
container = quality_group.node_for_global.container
|
||||
|
||||
layer_height = default_layer_height
|
||||
if container and container.hasProperty("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 and container.hasProperty("layer_height", "value"):
|
||||
layer_height = container.getProperty("layer_height", "value")
|
||||
|
||||
if isinstance(layer_height, SettingFunction):
|
||||
layer_height = layer_height(global_stack)
|
||||
|
||||
return float(layer_height)
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSignal
|
||||
|
@ -29,8 +29,7 @@ class MaterialBrandsModel(BaseMaterialsModel):
|
|||
def _update(self):
|
||||
if not self._canUpdate():
|
||||
return
|
||||
# Get updated list of favorites
|
||||
self._favorite_ids = self._material_manager.getFavorites()
|
||||
super()._update()
|
||||
|
||||
brand_item_list = []
|
||||
brand_group_dict = {}
|
||||
|
@ -38,24 +37,25 @@ class MaterialBrandsModel(BaseMaterialsModel):
|
|||
# Part 1: Generate the entire tree of brands -> material types -> spcific materials
|
||||
for root_material_id, container_node in self._available_materials.items():
|
||||
# Do not include the materials from a to-be-removed package
|
||||
if bool(container_node.getMetaDataEntry("removed", False)):
|
||||
if bool(container_node.container.getMetaDataEntry("removed", False)):
|
||||
continue
|
||||
|
||||
# Add brands we haven't seen yet to the dict, skipping generics
|
||||
brand = container_node.getMetaDataEntry("brand", "")
|
||||
brand = container_node.container.getMetaDataEntry("brand", "")
|
||||
if brand.lower() == "generic":
|
||||
continue
|
||||
if brand not in brand_group_dict:
|
||||
brand_group_dict[brand] = {}
|
||||
|
||||
# Add material types we haven't seen yet to the dict
|
||||
material_type = container_node.getMetaDataEntry("material", "")
|
||||
material_type = container_node.container.getMetaDataEntry("material", "")
|
||||
if material_type not in brand_group_dict[brand]:
|
||||
brand_group_dict[brand][material_type] = []
|
||||
|
||||
# Now handle the individual materials
|
||||
item = self._createMaterialItem(root_material_id, container_node)
|
||||
brand_group_dict[brand][material_type].append(item)
|
||||
if item:
|
||||
brand_group_dict[brand][material_type].append(item)
|
||||
|
||||
# Part 2: Organize the tree into models
|
||||
#
|
||||
|
|
215
cura/Machines/Models/MaterialManagementModel.py
Normal file
215
cura/Machines/Models/MaterialManagementModel.py
Normal file
|
@ -0,0 +1,215 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import copy # To duplicate materials.
|
||||
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot # To allow the preference page proxy to be used from the actual preferences page.
|
||||
from typing import Any, Dict, Optional, TYPE_CHECKING
|
||||
import uuid # To generate new GUIDs for new materials.
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Logger import Logger
|
||||
|
||||
import cura.CuraApplication # Imported like this to prevent circular imports.
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry # To find the sets of materials belonging to each other, and currently loaded extruder stacks.
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.Machines.MaterialNode import MaterialNode
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
## Proxy class to the materials page in the preferences.
|
||||
#
|
||||
# This class handles the actions in that page, such as creating new materials,
|
||||
# renaming them, etc.
|
||||
class MaterialManagementModel(QObject):
|
||||
## Triggered when a favorite is added or removed.
|
||||
# \param The base file of the material is provided as parameter when this
|
||||
# emits.
|
||||
favoritesChanged = pyqtSignal(str)
|
||||
|
||||
## Can a certain material be deleted, or is it still in use in one of the
|
||||
# container stacks anywhere?
|
||||
#
|
||||
# We forbid the user from deleting a material if it's in use in any stack.
|
||||
# Deleting it while it's in use can lead to corrupted stacks. In the
|
||||
# future we might enable this functionality again (deleting the material
|
||||
# from those stacks) but for now it is easier to prevent the user from
|
||||
# doing this.
|
||||
# \param material_node The ContainerTree node of the material to check.
|
||||
# \return Whether or not the material can be removed.
|
||||
@pyqtSlot("QVariant", result = bool)
|
||||
def canMaterialBeRemoved(self, material_node: "MaterialNode") -> bool:
|
||||
container_registry = CuraContainerRegistry.getInstance()
|
||||
ids_to_remove = {metadata.get("id", "") for metadata in container_registry.findInstanceContainersMetadata(base_file = material_node.base_file)}
|
||||
for extruder_stack in container_registry.findContainerStacks(type = "extruder_train"):
|
||||
if extruder_stack.material.getId() in ids_to_remove:
|
||||
return False
|
||||
return True
|
||||
|
||||
## Change the user-visible name of a material.
|
||||
# \param material_node The ContainerTree node of the material to rename.
|
||||
# \param name The new name for the material.
|
||||
@pyqtSlot("QVariant", str)
|
||||
def setMaterialName(self, material_node: "MaterialNode", name: str) -> None:
|
||||
container_registry = CuraContainerRegistry.getInstance()
|
||||
root_material_id = material_node.base_file
|
||||
if container_registry.isReadOnly(root_material_id):
|
||||
Logger.log("w", "Cannot set name of read-only container %s.", root_material_id)
|
||||
return
|
||||
return container_registry.findContainers(id = root_material_id)[0].setName(name)
|
||||
|
||||
## Deletes a material from Cura.
|
||||
#
|
||||
# This function does not do any safety checking any more. Please call this
|
||||
# function only if:
|
||||
# - The material is not read-only.
|
||||
# - The material is not used in any stacks.
|
||||
# If the material was not lazy-loaded yet, this will fully load the
|
||||
# container. When removing this material node, all other materials with
|
||||
# the same base fill will also be removed.
|
||||
# \param material_node The material to remove.
|
||||
@pyqtSlot("QVariant")
|
||||
def removeMaterial(self, material_node: "MaterialNode") -> None:
|
||||
container_registry = CuraContainerRegistry.getInstance()
|
||||
materials_this_base_file = container_registry.findContainersMetadata(base_file = material_node.base_file)
|
||||
for material_metadata in materials_this_base_file:
|
||||
container_registry.removeContainer(material_metadata["id"])
|
||||
|
||||
## Creates a duplicate of a material with the same GUID and base_file
|
||||
# metadata.
|
||||
# \param base_file: The base file of the material to duplicate.
|
||||
# \param new_base_id A new material ID for the base material. The IDs of
|
||||
# the submaterials will be based off this one. If not provided, a material
|
||||
# ID will be generated automatically.
|
||||
# \param new_metadata Metadata for the new material. If not provided, this
|
||||
# will be duplicated from the original material.
|
||||
# \return The root material ID of the duplicate material.
|
||||
def duplicateMaterialByBaseFile(self, base_file: str, new_base_id: Optional[str] = None,
|
||||
new_metadata: Optional[Dict[str, Any]] = None) -> Optional[str]:
|
||||
container_registry = CuraContainerRegistry.getInstance()
|
||||
|
||||
root_materials = container_registry.findContainers(id = base_file)
|
||||
if not root_materials:
|
||||
Logger.log("i", "Unable to duplicate the root material with ID {root_id}, because it doesn't exist.".format(root_id = base_file))
|
||||
return None
|
||||
root_material = root_materials[0]
|
||||
|
||||
# Ensure that all settings are saved.
|
||||
application = cura.CuraApplication.CuraApplication.getInstance()
|
||||
application.saveSettings()
|
||||
|
||||
# Create a new ID and container to hold the data.
|
||||
if new_base_id is None:
|
||||
new_base_id = container_registry.uniqueName(root_material.getId())
|
||||
new_root_material = copy.deepcopy(root_material)
|
||||
new_root_material.getMetaData()["id"] = new_base_id
|
||||
new_root_material.getMetaData()["base_file"] = new_base_id
|
||||
if new_metadata is not None:
|
||||
new_root_material.getMetaData().update(new_metadata)
|
||||
new_containers = [new_root_material]
|
||||
|
||||
# Clone all submaterials.
|
||||
for container_to_copy in container_registry.findInstanceContainers(base_file = base_file):
|
||||
if container_to_copy.getId() == base_file:
|
||||
continue # We already have that one. Skip it.
|
||||
new_id = new_base_id
|
||||
definition = container_to_copy.getMetaDataEntry("definition")
|
||||
if definition != "fdmprinter":
|
||||
new_id += "_" + definition
|
||||
variant_name = container_to_copy.getMetaDataEntry("variant_name")
|
||||
if 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:
|
||||
new_container.getMetaData().update(new_metadata)
|
||||
new_containers.append(new_container)
|
||||
|
||||
for container_to_add in new_containers:
|
||||
container_to_add.setDirty(True)
|
||||
container_registry.addContainer(container_to_add)
|
||||
|
||||
# If the duplicated material was favorite then the new material should also be added to the favorites.
|
||||
favorites_set = set(application.getPreferences().getValue("cura/favorite_materials").split(";"))
|
||||
if base_file in favorites_set:
|
||||
favorites_set.add(new_base_id)
|
||||
application.getPreferences().setValue("cura/favorite_materials", ";".join(favorites_set))
|
||||
|
||||
return new_base_id
|
||||
|
||||
## Creates a duplicate of a material with the same GUID and base_file
|
||||
# metadata.
|
||||
# \param material_node The node representing the material to duplicate.
|
||||
# \param new_base_id A new material ID for the base material. The IDs of
|
||||
# the submaterials will be based off this one. If not provided, a material
|
||||
# ID will be generated automatically.
|
||||
# \param new_metadata Metadata for the new material. If not provided, this
|
||||
# will be duplicated from the original material.
|
||||
# \return The root material ID of the duplicate material.
|
||||
@pyqtSlot("QVariant", result = str)
|
||||
def duplicateMaterial(self, material_node: "MaterialNode", new_base_id: Optional[str] = None,
|
||||
new_metadata: Optional[Dict[str, Any]] = None) -> Optional[str]:
|
||||
return self.duplicateMaterialByBaseFile(material_node.base_file, new_base_id, new_metadata)
|
||||
|
||||
## Create a new material by cloning the preferred material for the current
|
||||
# material diameter and generate a new GUID.
|
||||
#
|
||||
# The material type is explicitly left to be the one from the preferred
|
||||
# material, since this allows the user to still have SOME profiles to work
|
||||
# with.
|
||||
# \return The ID of the newly created material.
|
||||
@pyqtSlot(result = str)
|
||||
def createMaterial(self) -> str:
|
||||
# Ensure all settings are saved.
|
||||
application = cura.CuraApplication.CuraApplication.getInstance()
|
||||
application.saveSettings()
|
||||
|
||||
# Find the preferred material.
|
||||
extruder_stack = application.getMachineManager().activeStack
|
||||
active_variant_name = extruder_stack.variant.getName()
|
||||
approximate_diameter = int(extruder_stack.approximateMaterialDiameter)
|
||||
global_container_stack = application.getGlobalContainerStack()
|
||||
if not global_container_stack:
|
||||
return ""
|
||||
machine_node = ContainerTree.getInstance().machines[global_container_stack.definition.getId()]
|
||||
preferred_material_node = machine_node.variants[active_variant_name].preferredMaterial(approximate_diameter)
|
||||
|
||||
# Create a new ID & new metadata for the new material.
|
||||
new_id = CuraContainerRegistry.getInstance().uniqueName("custom_material")
|
||||
new_metadata = {"name": catalog.i18nc("@label", "Custom Material"),
|
||||
"brand": catalog.i18nc("@label", "Custom"),
|
||||
"GUID": str(uuid.uuid4()),
|
||||
}
|
||||
|
||||
self.duplicateMaterial(preferred_material_node, new_base_id = new_id, new_metadata = new_metadata)
|
||||
return new_id
|
||||
|
||||
## Adds a certain material to the favorite materials.
|
||||
# \param material_base_file The base file of the material to add.
|
||||
@pyqtSlot(str)
|
||||
def addFavorite(self, material_base_file: str) -> None:
|
||||
application = cura.CuraApplication.CuraApplication.getInstance()
|
||||
favorites = application.getPreferences().getValue("cura/favorite_materials").split(";")
|
||||
if material_base_file not in favorites:
|
||||
favorites.append(material_base_file)
|
||||
application.getPreferences().setValue("cura/favorite_materials", ";".join(favorites))
|
||||
application.saveSettings()
|
||||
self.favoritesChanged.emit(material_base_file)
|
||||
|
||||
## Removes a certain material from the favorite materials.
|
||||
#
|
||||
# If the material was not in the favorite materials, nothing happens.
|
||||
@pyqtSlot(str)
|
||||
def removeFavorite(self, material_base_file: str) -> None:
|
||||
application = cura.CuraApplication.CuraApplication.getInstance()
|
||||
favorites = application.getPreferences().getValue("cura/favorite_materials").split(";")
|
||||
try:
|
||||
favorites.remove(material_base_file)
|
||||
application.getPreferences().setValue("cura/favorite_materials", ";".join(favorites))
|
||||
application.saveSettings()
|
||||
self.favoritesChanged.emit(material_base_file)
|
||||
except ValueError: # Material was not in the favorites list.
|
||||
Logger.log("w", "Material {material_base_file} was already not a favorite material.".format(material_base_file = material_base_file))
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
|
@ -6,9 +6,7 @@ from PyQt5.QtCore import Qt
|
|||
from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
from UM.Qt.ListModel import ListModel
|
||||
from UM.Util import parseBool
|
||||
|
||||
from cura.Machines.VariantType import VariantType
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
|
||||
|
||||
class NozzleModel(ListModel):
|
||||
|
@ -25,7 +23,6 @@ class NozzleModel(ListModel):
|
|||
|
||||
self._application = Application.getInstance()
|
||||
self._machine_manager = self._application.getMachineManager()
|
||||
self._variant_manager = self._application.getVariantManager()
|
||||
|
||||
self._machine_manager.globalContainerChanged.connect(self._update)
|
||||
self._update()
|
||||
|
@ -37,19 +34,14 @@ class NozzleModel(ListModel):
|
|||
if global_stack is None:
|
||||
self.setItems([])
|
||||
return
|
||||
machine_node = ContainerTree.getInstance().machines[global_stack.definition.getId()]
|
||||
|
||||
has_variants = parseBool(global_stack.getMetaDataEntry("has_variants", False))
|
||||
if not has_variants:
|
||||
self.setItems([])
|
||||
return
|
||||
|
||||
variant_node_dict = self._variant_manager.getVariantNodes(global_stack, VariantType.NOZZLE)
|
||||
if not variant_node_dict:
|
||||
if not machine_node.has_variants:
|
||||
self.setItems([])
|
||||
return
|
||||
|
||||
item_list = []
|
||||
for hotend_name, container_node in sorted(variant_node_dict.items(), key = lambda i: i[0].upper()):
|
||||
for hotend_name, container_node in sorted(machine_node.variants.items(), key = lambda i: i[0].upper()):
|
||||
item = {"id": hotend_name,
|
||||
"hotend_name": hotend_name,
|
||||
"container_node": container_node
|
||||
|
@ -57,4 +49,4 @@ class NozzleModel(ListModel):
|
|||
|
||||
item_list.append(item)
|
||||
|
||||
self.setItems(item_list)
|
||||
self.setItems(item_list)
|
|
@ -1,10 +1,28 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSlot
|
||||
from typing import Any, cast, Dict, Optional, TYPE_CHECKING
|
||||
from PyQt5.QtCore import pyqtSlot, QObject, Qt
|
||||
|
||||
from UM.Qt.ListModel import ListModel
|
||||
from UM.Logger import Logger
|
||||
from UM.Qt.ListModel import ListModel
|
||||
from UM.Settings.InstanceContainer import InstanceContainer # To create new profiles.
|
||||
|
||||
import cura.CuraApplication # Imported this way to prevent circular imports.
|
||||
from cura.Settings.ContainerManager import ContainerManager
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
from cura.Settings.cura_empty_instance_containers import empty_quality_changes_container
|
||||
from cura.Settings.IntentManager import IntentManager
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from UM.Settings.Interfaces import ContainerInterface
|
||||
from cura.Machines.QualityChangesGroup import QualityChangesGroup
|
||||
from cura.Settings.ExtruderStack import ExtruderStack
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
|
||||
|
||||
#
|
||||
# This the QML model for the quality management page.
|
||||
|
@ -13,27 +31,245 @@ class QualityManagementModel(ListModel):
|
|||
NameRole = Qt.UserRole + 1
|
||||
IsReadOnlyRole = Qt.UserRole + 2
|
||||
QualityGroupRole = Qt.UserRole + 3
|
||||
QualityChangesGroupRole = Qt.UserRole + 4
|
||||
QualityTypeRole = Qt.UserRole + 4
|
||||
QualityChangesGroupRole = Qt.UserRole + 5
|
||||
IntentCategoryRole = Qt.UserRole + 6
|
||||
SectionNameRole = Qt.UserRole + 7
|
||||
|
||||
def __init__(self, parent = None):
|
||||
def __init__(self, parent: Optional["QObject"] = None) -> None:
|
||||
super().__init__(parent)
|
||||
|
||||
self.addRoleName(self.NameRole, "name")
|
||||
self.addRoleName(self.IsReadOnlyRole, "is_read_only")
|
||||
self.addRoleName(self.QualityGroupRole, "quality_group")
|
||||
self.addRoleName(self.QualityTypeRole, "quality_type")
|
||||
self.addRoleName(self.QualityChangesGroupRole, "quality_changes_group")
|
||||
self.addRoleName(self.IntentCategoryRole, "intent_category")
|
||||
self.addRoleName(self.SectionNameRole, "section_name")
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
self._container_registry = CuraApplication.getInstance().getContainerRegistry()
|
||||
self._machine_manager = CuraApplication.getInstance().getMachineManager()
|
||||
self._extruder_manager = CuraApplication.getInstance().getExtruderManager()
|
||||
self._quality_manager = CuraApplication.getInstance().getQualityManager()
|
||||
application = cura.CuraApplication.CuraApplication.getInstance()
|
||||
container_registry = application.getContainerRegistry()
|
||||
self._machine_manager = application.getMachineManager()
|
||||
self._extruder_manager = application.getExtruderManager()
|
||||
|
||||
self._machine_manager.globalContainerChanged.connect(self._update)
|
||||
self._quality_manager.qualitiesUpdated.connect(self._update)
|
||||
container_registry.containerAdded.connect(self._qualityChangesListChanged)
|
||||
container_registry.containerRemoved.connect(self._qualityChangesListChanged)
|
||||
container_registry.containerMetaDataChanged.connect(self._qualityChangesListChanged)
|
||||
|
||||
self._update()
|
||||
|
||||
## Deletes a custom profile. It will be gone forever.
|
||||
# \param quality_changes_group The quality changes group representing the
|
||||
# profile to delete.
|
||||
@pyqtSlot(QObject)
|
||||
def removeQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup") -> None:
|
||||
Logger.log("i", "Removing quality changes group {group_name}".format(group_name = quality_changes_group.name))
|
||||
removed_quality_changes_ids = set()
|
||||
container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry()
|
||||
for metadata in [quality_changes_group.metadata_for_global] + list(quality_changes_group.metadata_per_extruder.values()):
|
||||
container_id = metadata["id"]
|
||||
container_registry.removeContainer(container_id)
|
||||
removed_quality_changes_ids.add(container_id)
|
||||
|
||||
# Reset all machines that have activated this custom profile.
|
||||
for global_stack in container_registry.findContainerStacks(type = "machine"):
|
||||
if global_stack.qualityChanges.getId() in removed_quality_changes_ids:
|
||||
global_stack.qualityChanges = empty_quality_changes_container
|
||||
for extruder_stack in container_registry.findContainerStacks(type = "extruder_train"):
|
||||
if extruder_stack.qualityChanges.getId() in removed_quality_changes_ids:
|
||||
extruder_stack.qualityChanges = empty_quality_changes_container
|
||||
|
||||
## Rename a custom profile.
|
||||
#
|
||||
# Because the names must be unique, the new name may not actually become
|
||||
# the name that was given. The actual name is returned by this function.
|
||||
# \param quality_changes_group The custom profile that must be renamed.
|
||||
# \param new_name The desired name for the profile.
|
||||
# \return The actual new name of the profile, after making the name
|
||||
# unique.
|
||||
@pyqtSlot(QObject, str, result = str)
|
||||
def renameQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup", new_name: str) -> str:
|
||||
Logger.log("i", "Renaming QualityChangesGroup {old_name} to {new_name}.".format(old_name = quality_changes_group.name, new_name = new_name))
|
||||
if new_name == quality_changes_group.name:
|
||||
Logger.log("i", "QualityChangesGroup name {name} unchanged.".format(name = quality_changes_group.name))
|
||||
return new_name
|
||||
|
||||
application = cura.CuraApplication.CuraApplication.getInstance()
|
||||
container_registry = application.getContainerRegistry()
|
||||
new_name = container_registry.uniqueName(new_name)
|
||||
# CURA-6842
|
||||
# FIXME: setName() will trigger metaDataChanged signal that are connected with type Qt.AutoConnection. In this
|
||||
# case, setName() will trigger direct connections which in turn causes the quality changes group and the models
|
||||
# to update. Because multiple containers need to be renamed, and every time a container gets renamed, updates
|
||||
# gets triggered and this results in partial updates. For example, if we rename the global quality changes
|
||||
# container first, the rest of the system still thinks that I have selected "my_profile" instead of
|
||||
# "my_new_profile", but an update already gets triggered, and the quality changes group that's selected will
|
||||
# have no container for the global stack, because "my_profile" just got renamed to "my_new_profile". This results
|
||||
# in crashes because the rest of the system assumes that all data in a QualityChangesGroup will be correct.
|
||||
#
|
||||
# Renaming the container for the global stack in the end seems to be ok, because the assumption is mostly based
|
||||
# on the quality changes container for the global stack.
|
||||
for metadata in quality_changes_group.metadata_per_extruder.values():
|
||||
extruder_container = cast(InstanceContainer, container_registry.findContainers(id = metadata["id"])[0])
|
||||
extruder_container.setName(new_name)
|
||||
global_container = cast(InstanceContainer, container_registry.findContainers(id=quality_changes_group.metadata_for_global["id"])[0])
|
||||
global_container.setName(new_name)
|
||||
|
||||
quality_changes_group.name = new_name
|
||||
|
||||
application.getMachineManager().activeQualityChanged.emit()
|
||||
application.getMachineManager().activeQualityGroupChanged.emit()
|
||||
|
||||
return new_name
|
||||
|
||||
## Duplicates a given quality profile OR quality changes profile.
|
||||
# \param new_name The desired name of the new profile. This will be made
|
||||
# unique, so it might end up with a different name.
|
||||
# \param quality_model_item The item of this model to duplicate, as
|
||||
# dictionary. See the descriptions of the roles of this list model.
|
||||
@pyqtSlot(str, "QVariantMap")
|
||||
def duplicateQualityChanges(self, new_name: str, quality_model_item: Dict[str, Any]) -> None:
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if not global_stack:
|
||||
Logger.log("i", "No active global stack, cannot duplicate quality (changes) profile.")
|
||||
return
|
||||
|
||||
container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry()
|
||||
new_name = container_registry.uniqueName(new_name)
|
||||
|
||||
intent_category = quality_model_item["intent_category"]
|
||||
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, intent_category, new_name,
|
||||
global_stack, extruder_stack = None)
|
||||
container_registry.addContainer(new_quality_changes)
|
||||
else:
|
||||
for metadata in [quality_changes_group.metadata_for_global] + list(quality_changes_group.metadata_per_extruder.values()):
|
||||
containers = container_registry.findContainers(id = metadata["id"])
|
||||
if not containers:
|
||||
continue
|
||||
container = containers[0]
|
||||
new_id = container_registry.uniqueName(container.getId())
|
||||
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.
|
||||
# \param base_name The new name for the quality changes profile. The final
|
||||
# name of the profile might be different from this, because it needs to be
|
||||
# made unique.
|
||||
@pyqtSlot(str)
|
||||
def createQualityChanges(self, base_name: str) -> None:
|
||||
machine_manager = cura.CuraApplication.CuraApplication.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
|
||||
container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry()
|
||||
unique_name = container_registry.uniqueName(base_name)
|
||||
|
||||
# Go through the active stacks and create quality_changes containers from the user containers.
|
||||
container_manager = ContainerManager.getInstance()
|
||||
stack_list = [global_stack] + list(global_stack.extruders.values())
|
||||
for stack in stack_list:
|
||||
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_stack = None
|
||||
intent_category = None
|
||||
if stack.getMetaDataEntry("position") is not None:
|
||||
extruder_stack = stack
|
||||
intent_category = stack.intent.getMetaDataEntry("intent_category")
|
||||
new_changes = self._createQualityChanges(quality_container.getMetaDataEntry("quality_type"), intent_category, unique_name, global_stack, extruder_stack)
|
||||
container_manager._performMerge(new_changes, quality_changes_container, clear_settings = False)
|
||||
container_manager._performMerge(new_changes, stack.userChanges)
|
||||
|
||||
container_registry.addContainer(new_changes)
|
||||
|
||||
## Create a quality changes container with the given set-up.
|
||||
# \param quality_type The quality type of the new container.
|
||||
# \param intent_category The intent category of the new container.
|
||||
# \param new_name The name of the container. This name must be unique.
|
||||
# \param machine The global stack to create the profile for.
|
||||
# \param extruder_stack The extruder stack to create the profile for. If
|
||||
# not provided, only a global container will be created.
|
||||
def _createQualityChanges(self, quality_type: str, intent_category: Optional[str], new_name: str, machine: "GlobalStack", extruder_stack: Optional["ExtruderStack"]) -> "InstanceContainer":
|
||||
container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry()
|
||||
base_id = machine.definition.getId() if extruder_stack is None else extruder_stack.getId()
|
||||
new_id = base_id + "_" + new_name
|
||||
new_id = new_id.lower().replace(" ", "_")
|
||||
new_id = 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.setMetaDataEntry("type", "quality_changes")
|
||||
quality_changes.setMetaDataEntry("quality_type", quality_type)
|
||||
if intent_category is not None:
|
||||
quality_changes.setMetaDataEntry("intent_category", intent_category)
|
||||
|
||||
# If we are creating a container for an extruder, ensure we add that to the container.
|
||||
if extruder_stack is not None:
|
||||
quality_changes.setMetaDataEntry("position", extruder_stack.getMetaDataEntry("position"))
|
||||
|
||||
# If the machine specifies qualities should be filtered, ensure we match the current criteria.
|
||||
machine_definition_id = ContainerTree.getInstance().machines[machine.definition.getId()].quality_definition
|
||||
quality_changes.setDefinition(machine_definition_id)
|
||||
|
||||
quality_changes.setMetaDataEntry("setting_version", cura.CuraApplication.CuraApplication.getInstance().SettingVersion)
|
||||
return quality_changes
|
||||
|
||||
## Triggered when any container changed.
|
||||
#
|
||||
# This filters the updates to the container manager: When it applies to
|
||||
# the list of quality changes, we need to update our list.
|
||||
def _qualityChangesListChanged(self, container: "ContainerInterface") -> None:
|
||||
if container.getMetaDataEntry("type") == "quality_changes":
|
||||
self._update()
|
||||
|
||||
@pyqtSlot("QVariantMap", result = str)
|
||||
def getQualityItemDisplayName(self, quality_model_item: Dict[str, Any]) -> str:
|
||||
quality_group = quality_model_item["quality_group"]
|
||||
is_read_only = quality_model_item["is_read_only"]
|
||||
intent_category = quality_model_item["intent_category"]
|
||||
|
||||
quality_level_name = "Not Supported"
|
||||
if quality_group is not None:
|
||||
quality_level_name = quality_group.name
|
||||
|
||||
display_name = quality_level_name
|
||||
|
||||
if intent_category != "default":
|
||||
intent_display_name = catalog.i18nc("@label", intent_category.capitalize())
|
||||
display_name = "{intent_name} - {the_rest}".format(intent_name = intent_display_name,
|
||||
the_rest = display_name)
|
||||
|
||||
# A custom quality
|
||||
if not is_read_only:
|
||||
display_name = "{custom_profile_name} - {the_rest}".format(custom_profile_name = quality_model_item["name"],
|
||||
the_rest = display_name)
|
||||
|
||||
return display_name
|
||||
|
||||
def _update(self):
|
||||
Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
|
||||
|
||||
|
@ -42,18 +278,19 @@ class QualityManagementModel(ListModel):
|
|||
self.setItems([])
|
||||
return
|
||||
|
||||
quality_group_dict = self._quality_manager.getQualityGroups(global_stack)
|
||||
quality_changes_group_dict = self._quality_manager.getQualityChangesGroups(global_stack)
|
||||
container_tree = ContainerTree.getInstance()
|
||||
quality_group_dict = container_tree.getCurrentQualityGroups()
|
||||
quality_changes_group_list = container_tree.getCurrentQualityChangesGroups()
|
||||
|
||||
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:
|
||||
if not available_quality_types and not quality_changes_group_list:
|
||||
# Nothing to show
|
||||
self.setItems([])
|
||||
return
|
||||
|
||||
item_list = []
|
||||
# Create quality group items
|
||||
# Create quality group items (intent category = "default")
|
||||
for quality_group in quality_group_dict.values():
|
||||
if not quality_group.is_available:
|
||||
continue
|
||||
|
@ -61,19 +298,45 @@ class QualityManagementModel(ListModel):
|
|||
item = {"name": quality_group.name,
|
||||
"is_read_only": True,
|
||||
"quality_group": quality_group,
|
||||
"quality_changes_group": None}
|
||||
"quality_type": quality_group.quality_type,
|
||||
"quality_changes_group": None,
|
||||
"intent_category": "default",
|
||||
"section_name": catalog.i18nc("@label", "Default"),
|
||||
}
|
||||
item_list.append(item)
|
||||
# Sort by quality names
|
||||
item_list = sorted(item_list, key = lambda x: x["name"].upper())
|
||||
|
||||
# Create intent items (non-default)
|
||||
available_intent_list = IntentManager.getInstance().getCurrentAvailableIntents()
|
||||
available_intent_list = [i for i in available_intent_list if i[0] != "default"]
|
||||
result = []
|
||||
for intent_category, quality_type in available_intent_list:
|
||||
result.append({
|
||||
"name": quality_group_dict[quality_type].name, # Use the quality name as the display name
|
||||
"is_read_only": True,
|
||||
"quality_group": quality_group_dict[quality_type],
|
||||
"quality_type": quality_type,
|
||||
"quality_changes_group": None,
|
||||
"intent_category": intent_category,
|
||||
"section_name": catalog.i18nc("@label", intent_category.capitalize()),
|
||||
})
|
||||
# Sort by quality_type for each intent category
|
||||
result = sorted(result, key = lambda x: (x["intent_category"], x["quality_type"]))
|
||||
item_list += result
|
||||
|
||||
# Create quality_changes group items
|
||||
quality_changes_item_list = []
|
||||
for quality_changes_group in quality_changes_group_dict.values():
|
||||
for quality_changes_group in quality_changes_group_list:
|
||||
quality_group = quality_group_dict.get(quality_changes_group.quality_type)
|
||||
item = {"name": quality_changes_group.name,
|
||||
"is_read_only": False,
|
||||
"quality_group": quality_group,
|
||||
"quality_changes_group": quality_changes_group}
|
||||
"quality_type": quality_group.quality_type,
|
||||
"quality_changes_group": quality_changes_group,
|
||||
"intent_category": quality_changes_group.intent_category,
|
||||
"section_name": catalog.i18nc("@label", "Custom profiles"),
|
||||
}
|
||||
quality_changes_item_list.append(item)
|
||||
|
||||
# Sort quality_changes items by names and append to the item list
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import Qt, QTimer
|
||||
|
||||
from UM.Application import Application
|
||||
import cura.CuraApplication # Imported this way to prevent circular dependencies.
|
||||
from UM.Logger import Logger
|
||||
from UM.Qt.ListModel import ListModel
|
||||
from UM.Settings.SettingFunction import SettingFunction
|
||||
|
||||
from cura.Machines.QualityManager import QualityGroup
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
from cura.Machines.Models.MachineModelUtils import fetchLayerHeight
|
||||
|
||||
|
||||
#
|
||||
|
@ -36,14 +35,13 @@ class QualityProfilesDropDownMenuModel(ListModel):
|
|||
self.addRoleName(self.QualityChangesGroupRole, "quality_changes_group")
|
||||
self.addRoleName(self.IsExperimentalRole, "is_experimental")
|
||||
|
||||
self._application = Application.getInstance()
|
||||
self._machine_manager = self._application.getMachineManager()
|
||||
self._quality_manager = Application.getInstance().getQualityManager()
|
||||
application = cura.CuraApplication.CuraApplication.getInstance()
|
||||
machine_manager = application.getMachineManager()
|
||||
|
||||
self._application.globalContainerStackChanged.connect(self._onChange)
|
||||
self._machine_manager.activeQualityGroupChanged.connect(self._onChange)
|
||||
self._machine_manager.extruderChanged.connect(self._onChange)
|
||||
self._quality_manager.qualitiesUpdated.connect(self._onChange)
|
||||
application.globalContainerStackChanged.connect(self._onChange)
|
||||
machine_manager.activeQualityGroupChanged.connect(self._onChange)
|
||||
machine_manager.activeStackChanged.connect(self._onChange)
|
||||
machine_manager.extruderChanged.connect(self._onChange)
|
||||
|
||||
self._layer_height_unit = "" # This is cached
|
||||
|
||||
|
@ -60,25 +58,36 @@ class QualityProfilesDropDownMenuModel(ListModel):
|
|||
def _update(self):
|
||||
Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
|
||||
|
||||
global_stack = self._machine_manager.activeMachine
|
||||
# CURA-6836
|
||||
# LabelBar is a repeater that creates labels for quality layer heights. Because of an optimization in
|
||||
# UM.ListModel, the model will not remove all items and recreate new ones every time there's an update.
|
||||
# Because LabelBar uses Repeater with Labels anchoring to "undefined" in certain cases, the anchoring will be
|
||||
# kept the same as before.
|
||||
self.setItems([])
|
||||
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if global_stack is None:
|
||||
self.setItems([])
|
||||
Logger.log("d", "No active GlobalStack, set quality profile model as empty.")
|
||||
return
|
||||
|
||||
if not self._layer_height_unit:
|
||||
unit = global_stack.definition.getProperty("layer_height", "unit")
|
||||
if not unit:
|
||||
unit = ""
|
||||
self._layer_height_unit = unit
|
||||
|
||||
# Check for material compatibility
|
||||
if not self._machine_manager.activeMaterialsCompatible():
|
||||
if not cura.CuraApplication.CuraApplication.getInstance().getMachineManager().activeMaterialsCompatible():
|
||||
Logger.log("d", "No active material compatibility, set quality profile model as empty.")
|
||||
self.setItems([])
|
||||
return
|
||||
|
||||
quality_group_dict = self._quality_manager.getQualityGroups(global_stack)
|
||||
quality_group_dict = ContainerTree.getInstance().getCurrentQualityGroups()
|
||||
|
||||
item_list = []
|
||||
for key in sorted(quality_group_dict):
|
||||
quality_group = quality_group_dict[key]
|
||||
|
||||
layer_height = self._fetchLayerHeight(quality_group)
|
||||
for quality_group in quality_group_dict.values():
|
||||
layer_height = fetchLayerHeight(quality_group)
|
||||
|
||||
item = {"name": quality_group.name,
|
||||
"quality_type": quality_group.quality_type,
|
||||
|
@ -94,32 +103,3 @@ class QualityProfilesDropDownMenuModel(ListModel):
|
|||
item_list = sorted(item_list, key = lambda x: x["layer_height"])
|
||||
|
||||
self.setItems(item_list)
|
||||
|
||||
def _fetchLayerHeight(self, quality_group: "QualityGroup") -> float:
|
||||
global_stack = self._machine_manager.activeMachine
|
||||
if not self._layer_height_unit:
|
||||
unit = global_stack.definition.getProperty("layer_height", "unit")
|
||||
if not unit:
|
||||
unit = ""
|
||||
self._layer_height_unit = unit
|
||||
|
||||
default_layer_height = global_stack.definition.getProperty("layer_height", "value")
|
||||
|
||||
# Get layer_height from the quality profile for the GlobalStack
|
||||
if quality_group.node_for_global is None:
|
||||
return float(default_layer_height)
|
||||
container = quality_group.node_for_global.getContainer()
|
||||
|
||||
layer_height = default_layer_height
|
||||
if container and container.hasProperty("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 and container.hasProperty("layer_height", "value"):
|
||||
layer_height = container.getProperty("layer_height", "value")
|
||||
|
||||
if isinstance(layer_height, SettingFunction):
|
||||
layer_height = layer_height(global_stack)
|
||||
|
||||
return float(layer_height)
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, Qt
|
||||
|
||||
from UM.Application import Application
|
||||
import cura.CuraApplication
|
||||
from UM.Logger import Logger
|
||||
from UM.Qt.ListModel import ListModel
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
|
@ -35,15 +35,13 @@ class QualitySettingsModel(ListModel):
|
|||
self.addRoleName(self.CategoryRole, "category")
|
||||
|
||||
self._container_registry = ContainerRegistry.getInstance()
|
||||
self._application = Application.getInstance()
|
||||
self._quality_manager = self._application.getQualityManager()
|
||||
self._application = cura.CuraApplication.CuraApplication.getInstance()
|
||||
self._application.getMachineManager().activeStackChanged.connect(self._update)
|
||||
|
||||
self._selected_position = self.GLOBAL_STACK_POSITION #Must be either GLOBAL_STACK_POSITION or an extruder position (0, 1, etc.)
|
||||
self._selected_quality_item = None # The selected quality in the quality management page
|
||||
self._i18n_catalog = None
|
||||
|
||||
self._quality_manager.qualitiesUpdated.connect(self._update)
|
||||
|
||||
self._update()
|
||||
|
||||
selectedPositionChanged = pyqtSignal()
|
||||
|
@ -93,21 +91,33 @@ class QualitySettingsModel(ListModel):
|
|||
quality_node = quality_group.nodes_for_extruders.get(str(self._selected_position))
|
||||
settings_keys = quality_group.getAllKeys()
|
||||
quality_containers = []
|
||||
if quality_node is not None and quality_node.getContainer() is not None:
|
||||
quality_containers.append(quality_node.getContainer())
|
||||
if quality_node is not None and quality_node.container is not None:
|
||||
quality_containers.append(quality_node.container)
|
||||
|
||||
# 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._selected_position == self.GLOBAL_STACK_POSITION:
|
||||
quality_changes_node = quality_changes_group.node_for_global
|
||||
container_registry = ContainerRegistry.getInstance()
|
||||
global_containers = container_registry.findContainers(id = quality_changes_group.metadata_for_global["id"])
|
||||
global_container = None if len(global_containers) == 0 else global_containers[0]
|
||||
extruders_containers = {pos: container_registry.findContainers(id = quality_changes_group.metadata_per_extruder[pos]["id"]) for pos in quality_changes_group.metadata_per_extruder}
|
||||
extruders_container = {pos: None if not containers else containers[0] for pos, containers in extruders_containers.items()}
|
||||
if self._selected_position == self.GLOBAL_STACK_POSITION and global_container:
|
||||
quality_changes_metadata = global_container.getMetaData()
|
||||
else:
|
||||
quality_changes_node = quality_changes_group.nodes_for_extruders.get(str(self._selected_position))
|
||||
if quality_changes_node is not None and quality_changes_node.getContainer() is not None: # it can be None if number of extruders are changed during runtime
|
||||
quality_containers.insert(0, quality_changes_node.getContainer())
|
||||
settings_keys.update(quality_changes_group.getAllKeys())
|
||||
quality_changes_metadata = extruders_container.get(str(self._selected_position))
|
||||
if quality_changes_metadata is not None: # It can be None if number of extruders are changed during runtime.
|
||||
container = container_registry.findContainers(id = quality_changes_metadata["id"])
|
||||
if container:
|
||||
quality_containers.insert(0, container[0])
|
||||
|
||||
# We iterate over all definitions instead of settings in a quality/qualtiy_changes group is because in the GUI,
|
||||
if global_container:
|
||||
settings_keys.update(global_container.getAllKeys())
|
||||
for container in extruders_container.values():
|
||||
if container:
|
||||
settings_keys.update(container.getAllKeys())
|
||||
|
||||
# We iterate over all definitions instead of settings in a quality/quality_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 = ""
|
||||
|
|
|
@ -1,33 +1,37 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
|
||||
|
||||
from .QualityGroup import QualityGroup
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.Machines.QualityNode import QualityNode
|
||||
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal
|
||||
|
||||
|
||||
class QualityChangesGroup(QualityGroup):
|
||||
def __init__(self, name: str, quality_type: str, parent = None) -> None:
|
||||
super().__init__(name, quality_type, parent)
|
||||
self._container_registry = Application.getInstance().getContainerRegistry()
|
||||
## Data struct to group several quality changes instance containers together.
|
||||
#
|
||||
# Each group represents one "custom profile" as the user sees it, which
|
||||
# contains an instance container for the global stack and one instance
|
||||
# container per extruder.
|
||||
class QualityChangesGroup(QObject):
|
||||
|
||||
def addNode(self, node: "QualityNode") -> None:
|
||||
extruder_position = node.getMetaDataEntry("position")
|
||||
def __init__(self, name: str, quality_type: str, intent_category: str, parent: Optional["QObject"] = None) -> None:
|
||||
super().__init__(parent)
|
||||
self._name = name
|
||||
self.quality_type = quality_type
|
||||
self.intent_category = intent_category
|
||||
self.is_available = False
|
||||
self.metadata_for_global = {} # type: Dict[str, Any]
|
||||
self.metadata_per_extruder = {} # type: Dict[int, Dict[str, Any]]
|
||||
|
||||
if extruder_position is None and self.node_for_global is not None or extruder_position in self.nodes_for_extruders: #We would be overwriting another node.
|
||||
ConfigurationErrorMessage.getInstance().addFaultyContainers(node.getMetaDataEntry("id"))
|
||||
return
|
||||
nameChanged = pyqtSignal()
|
||||
|
||||
if extruder_position is None: # Then we're a global quality changes profile.
|
||||
self.node_for_global = node
|
||||
else: # This is an extruder's quality changes profile.
|
||||
self.nodes_for_extruders[extruder_position] = node
|
||||
def setName(self, name: str) -> None:
|
||||
if self._name != name:
|
||||
self._name = name
|
||||
self.nameChanged.emit()
|
||||
|
||||
@pyqtProperty(str, fset = setName, notify = nameChanged)
|
||||
def name(self) -> str:
|
||||
return self._name
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "%s[<%s>, available = %s]" % (self.__class__.__name__, self.name, self.is_available)
|
||||
return "{class_name}[{name}, available = {is_available}]".format(class_name = self.__class__.__name__, name = self.name, is_available = self.is_available)
|
||||
|
|
|
@ -1,32 +1,38 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Dict, Optional, List, Set
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSlot
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.Util import parseBool
|
||||
|
||||
from cura.Machines.ContainerNode import ContainerNode
|
||||
|
||||
|
||||
## A QualityGroup represents a group of quality containers that must be applied
|
||||
# to each ContainerStack when it's used.
|
||||
#
|
||||
# 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
|
||||
# A concrete example: When there are two extruders and the user selects the
|
||||
# quality type "normal", this quality type must be applied to all stacks in a
|
||||
# machine, although each stack can have different containers. So one global
|
||||
# profile gets put on the global stack and one extruder profile gets put on
|
||||
# each extruder stack. This quality group then contains the following
|
||||
# profiles (for instance):
|
||||
# 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) -> None:
|
||||
super().__init__(parent)
|
||||
# The purpose of these quality groups is to group the containers that can be
|
||||
# applied to a configuration, so that when a quality level is selected, the
|
||||
# container can directly be applied to each stack instead of looking them up
|
||||
# again.
|
||||
class QualityGroup:
|
||||
## Constructs a new group.
|
||||
# \param name The user-visible name for the group.
|
||||
# \param quality_type The quality level that each profile in this group
|
||||
# has.
|
||||
def __init__(self, name: str, quality_type: str) -> None:
|
||||
self.name = name
|
||||
self.node_for_global = None # type: Optional[ContainerNode]
|
||||
self.nodes_for_extruders = {} # type: Dict[int, ContainerNode]
|
||||
|
@ -34,7 +40,6 @@ class QualityGroup(QObject):
|
|||
self.is_available = False
|
||||
self.is_experimental = False
|
||||
|
||||
@pyqtSlot(result = str)
|
||||
def getName(self) -> str:
|
||||
return self.name
|
||||
|
||||
|
@ -43,7 +48,7 @@ class QualityGroup(QObject):
|
|||
for node in [self.node_for_global] + list(self.nodes_for_extruders.values()):
|
||||
if node is None:
|
||||
continue
|
||||
container = node.getContainer()
|
||||
container = node.container
|
||||
if container:
|
||||
result.update(container.getAllKeys())
|
||||
return result
|
||||
|
@ -60,12 +65,18 @@ class QualityGroup(QObject):
|
|||
self.node_for_global = node
|
||||
|
||||
# Update is_experimental flag
|
||||
is_experimental = parseBool(node.getMetaDataEntry("is_experimental", False))
|
||||
if not node.container:
|
||||
Logger.log("w", "Node {0} doesn't have a container.".format(node.container_id))
|
||||
return
|
||||
is_experimental = parseBool(node.container.getMetaDataEntry("is_experimental", False))
|
||||
self.is_experimental |= is_experimental
|
||||
|
||||
def setExtruderNode(self, position: int, node: "ContainerNode") -> None:
|
||||
self.nodes_for_extruders[position] = node
|
||||
|
||||
# Update is_experimental flag
|
||||
is_experimental = parseBool(node.getMetaDataEntry("is_experimental", False))
|
||||
if not node.container:
|
||||
Logger.log("w", "Node {0} doesn't have a container.".format(node.container_id))
|
||||
return
|
||||
is_experimental = parseBool(node.container.getMetaDataEntry("is_experimental", False))
|
||||
self.is_experimental |= is_experimental
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import TYPE_CHECKING, Optional, cast, Dict, List, Set
|
||||
from typing import Any, Dict, List, Optional, TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtCore import QObject, QTimer, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot
|
||||
|
||||
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
|
||||
from UM.Logger import Logger
|
||||
from UM.Util import parseBool
|
||||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
from UM.Decorators import deprecated
|
||||
|
||||
import cura.CuraApplication
|
||||
from cura.Settings.ExtruderStack import ExtruderStack
|
||||
|
||||
from cura.Machines.ContainerTree import ContainerTree # The implementation that replaces this manager, to keep the deprecated interface working.
|
||||
from .QualityChangesGroup import QualityChangesGroup
|
||||
from .QualityGroup import QualityGroup
|
||||
from .QualityNode import QualityNode
|
||||
|
||||
|
@ -19,7 +21,6 @@ if TYPE_CHECKING:
|
|||
from UM.Settings.Interfaces import DefinitionContainerInterface
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
from .QualityChangesGroup import QualityChangesGroup
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
|
||||
#
|
||||
|
@ -33,17 +34,24 @@ if TYPE_CHECKING:
|
|||
# because it's simple.
|
||||
#
|
||||
class QualityManager(QObject):
|
||||
__instance = None
|
||||
|
||||
@classmethod
|
||||
@deprecated("Use the ContainerTree structure instead.", since = "4.3")
|
||||
def getInstance(cls) -> "QualityManager":
|
||||
if cls.__instance is None:
|
||||
cls.__instance = QualityManager()
|
||||
return cls.__instance
|
||||
|
||||
qualitiesUpdated = pyqtSignal()
|
||||
|
||||
def __init__(self, application: "CuraApplication", parent = None) -> None:
|
||||
def __init__(self, parent = None) -> None:
|
||||
super().__init__(parent)
|
||||
self._application = application
|
||||
self._material_manager = self._application.getMaterialManager()
|
||||
self._container_registry = self._application.getContainerRegistry()
|
||||
application = cura.CuraApplication.CuraApplication.getInstance()
|
||||
self._container_registry = application.getContainerRegistry()
|
||||
|
||||
self._empty_quality_container = self._application.empty_quality_container
|
||||
self._empty_quality_changes_container = self._application.empty_quality_changes_container
|
||||
self._empty_quality_container = application.empty_quality_container
|
||||
self._empty_quality_changes_container = application.empty_quality_changes_container
|
||||
|
||||
# For quality lookup
|
||||
self._machine_nozzle_buildplate_material_quality_type_to_quality_dict = {} # type: Dict[str, QualityNode]
|
||||
|
@ -57,88 +65,6 @@ class QualityManager(QObject):
|
|||
self._container_registry.containerAdded.connect(self._onContainerMetadataChanged)
|
||||
self._container_registry.containerRemoved.connect(self._onContainerMetadataChanged)
|
||||
|
||||
# When a custom quality gets added/imported, there can be more than one InstanceContainers. In those cases,
|
||||
# we don't want to react on every container/metadata changed signal. The timer here is to buffer it a bit so
|
||||
# we don't react too many time.
|
||||
self._update_timer = QTimer(self)
|
||||
self._update_timer.setInterval(300)
|
||||
self._update_timer.setSingleShot(True)
|
||||
self._update_timer.timeout.connect(self._updateMaps)
|
||||
|
||||
def initialize(self) -> None:
|
||||
# Initialize the lookup tree for quality profiles with following structure:
|
||||
# <machine> -> <nozzle> -> <buildplate> -> <material>
|
||||
# <machine> -> <material>
|
||||
|
||||
self._machine_nozzle_buildplate_material_quality_type_to_quality_dict = {} # for quality lookup
|
||||
self._machine_quality_type_to_quality_changes_dict = {} # for quality_changes lookup
|
||||
|
||||
quality_metadata_list = self._container_registry.findContainersMetadata(type = "quality")
|
||||
for metadata in quality_metadata_list:
|
||||
if metadata["id"] == "empty_quality":
|
||||
continue
|
||||
|
||||
definition_id = metadata["definition"]
|
||||
quality_type = metadata["quality_type"]
|
||||
|
||||
root_material_id = metadata.get("material")
|
||||
nozzle_name = metadata.get("variant")
|
||||
buildplate_name = metadata.get("buildplate")
|
||||
is_global_quality = metadata.get("global_quality", False)
|
||||
is_global_quality = is_global_quality or (root_material_id is None and nozzle_name is None and buildplate_name is None)
|
||||
|
||||
# Sanity check: material+variant and is_global_quality cannot be present at the same time
|
||||
if is_global_quality and (root_material_id or nozzle_name):
|
||||
ConfigurationErrorMessage.getInstance().addFaultyContainers(metadata["id"])
|
||||
continue
|
||||
|
||||
if definition_id not in self._machine_nozzle_buildplate_material_quality_type_to_quality_dict:
|
||||
self._machine_nozzle_buildplate_material_quality_type_to_quality_dict[definition_id] = QualityNode()
|
||||
machine_node = cast(QualityNode, self._machine_nozzle_buildplate_material_quality_type_to_quality_dict[definition_id])
|
||||
|
||||
if is_global_quality:
|
||||
# For global qualities, save data in the machine node
|
||||
machine_node.addQualityMetadata(quality_type, metadata)
|
||||
continue
|
||||
|
||||
current_node = machine_node
|
||||
intermediate_node_info_list = [nozzle_name, buildplate_name, root_material_id]
|
||||
current_intermediate_node_info_idx = 0
|
||||
|
||||
while current_intermediate_node_info_idx < len(intermediate_node_info_list):
|
||||
node_name = intermediate_node_info_list[current_intermediate_node_info_idx]
|
||||
if node_name is not None:
|
||||
# There is specific information, update the current node to go deeper so we can add this quality
|
||||
# at the most specific branch in the lookup tree.
|
||||
if node_name not in current_node.children_map:
|
||||
current_node.children_map[node_name] = QualityNode()
|
||||
current_node = cast(QualityNode, current_node.children_map[node_name])
|
||||
|
||||
current_intermediate_node_info_idx += 1
|
||||
|
||||
current_node.addQualityMetadata(quality_type, metadata)
|
||||
|
||||
# Initialize the lookup tree for quality_changes profiles with following structure:
|
||||
# <machine> -> <quality_type> -> <name>
|
||||
quality_changes_metadata_list = self._container_registry.findContainersMetadata(type = "quality_changes")
|
||||
for metadata in quality_changes_metadata_list:
|
||||
if metadata["id"] == "empty_quality_changes":
|
||||
continue
|
||||
|
||||
machine_definition_id = metadata["definition"]
|
||||
quality_type = metadata["quality_type"]
|
||||
|
||||
if machine_definition_id not in self._machine_quality_type_to_quality_changes_dict:
|
||||
self._machine_quality_type_to_quality_changes_dict[machine_definition_id] = QualityNode()
|
||||
machine_node = self._machine_quality_type_to_quality_changes_dict[machine_definition_id]
|
||||
machine_node.addQualityChangesMetadata(quality_type, metadata)
|
||||
|
||||
Logger.log("d", "Lookup tables updated.")
|
||||
self.qualitiesUpdated.emit()
|
||||
|
||||
def _updateMaps(self) -> None:
|
||||
self.initialize()
|
||||
|
||||
def _onContainerMetadataChanged(self, container: InstanceContainer) -> None:
|
||||
self._onContainerChanged(container)
|
||||
|
||||
|
@ -147,211 +73,28 @@ class QualityManager(QObject):
|
|||
if container_type not in ("quality", "quality_changes"):
|
||||
return
|
||||
|
||||
# update the cache table
|
||||
self._update_timer.start()
|
||||
|
||||
# Updates the given quality groups' availabilities according to which extruders are being used/ enabled.
|
||||
def _updateQualityGroupsAvailability(self, machine: "GlobalStack", quality_group_list) -> None:
|
||||
used_extruders = set()
|
||||
for i in range(machine.getProperty("machine_extruder_count", "value")):
|
||||
try:
|
||||
extruder = machine.extruderList[int(i)]
|
||||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
if extruder.isEnabled:
|
||||
used_extruders.add(str(i))
|
||||
|
||||
# Update the "is_available" flag for each quality group.
|
||||
for quality_group in quality_group_list:
|
||||
is_available = True
|
||||
if quality_group.node_for_global is None:
|
||||
is_available = False
|
||||
if is_available:
|
||||
for position in used_extruders:
|
||||
if position not in quality_group.nodes_for_extruders:
|
||||
is_available = False
|
||||
break
|
||||
|
||||
quality_group.is_available = is_available
|
||||
|
||||
# Returns a dict of "custom profile name" -> QualityChangesGroup
|
||||
def getQualityChangesGroups(self, machine: "GlobalStack") -> dict:
|
||||
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
|
||||
|
||||
machine_node = self._machine_quality_type_to_quality_changes_dict.get(machine_definition_id)
|
||||
if not machine_node:
|
||||
Logger.log("i", "Cannot find node for machine def [%s] in QualityChanges lookup table", machine_definition_id)
|
||||
return dict()
|
||||
|
||||
# Update availability for each QualityChangesGroup:
|
||||
# A custom profile is always available as long as the quality_type it's based on is available
|
||||
quality_group_dict = self.getQualityGroups(machine)
|
||||
available_quality_type_list = [qt for qt, qg in quality_group_dict.items() if qg.is_available]
|
||||
|
||||
# Iterate over all quality_types in the machine node
|
||||
quality_changes_group_dict = dict()
|
||||
for quality_type, quality_changes_node in machine_node.quality_type_map.items():
|
||||
for quality_changes_name, quality_changes_group in quality_changes_node.children_map.items():
|
||||
quality_changes_group_dict[quality_changes_name] = quality_changes_group
|
||||
quality_changes_group.is_available = quality_type in available_quality_type_list
|
||||
|
||||
return quality_changes_group_dict
|
||||
def getQualityChangesGroups(self, machine: "GlobalStack") -> List[QualityChangesGroup]:
|
||||
variant_names = [extruder.variant.getName() for extruder in machine.extruderList]
|
||||
material_bases = [extruder.material.getMetaDataEntry("base_file") for extruder in machine.extruderList]
|
||||
extruder_enabled = [extruder.isEnabled for extruder in machine.extruderList]
|
||||
machine_node = ContainerTree.getInstance().machines[machine.definition.getId()]
|
||||
return machine_node.getQualityChangesGroups(variant_names, material_bases, extruder_enabled)
|
||||
|
||||
## Gets the quality groups for the current printer.
|
||||
#
|
||||
# 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[str, QualityGroup]:
|
||||
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
|
||||
|
||||
# To find the quality container for the GlobalStack, check in the following fall-back manner:
|
||||
# (1) the machine-specific node
|
||||
# (2) the generic node
|
||||
machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get(machine_definition_id)
|
||||
|
||||
# Check if this machine has specific quality profiles for its extruders, if so, when looking up extruder
|
||||
# qualities, we should not fall back to use the global qualities.
|
||||
has_extruder_specific_qualities = False
|
||||
if machine_node:
|
||||
if machine_node.children_map:
|
||||
has_extruder_specific_qualities = True
|
||||
|
||||
default_machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get(self._default_machine_definition_id)
|
||||
|
||||
nodes_to_check = [] # type: List[QualityNode]
|
||||
if machine_node is not None:
|
||||
nodes_to_check.append(machine_node)
|
||||
if default_machine_node is not None:
|
||||
nodes_to_check.append(default_machine_node)
|
||||
|
||||
# Iterate over all quality_types in the machine node
|
||||
quality_group_dict = {}
|
||||
for node in nodes_to_check:
|
||||
if node and node.quality_type_map:
|
||||
quality_node = list(node.quality_type_map.values())[0]
|
||||
is_global_quality = parseBool(quality_node.getMetaDataEntry("global_quality", False))
|
||||
if not is_global_quality:
|
||||
continue
|
||||
|
||||
for quality_type, quality_node in node.quality_type_map.items():
|
||||
quality_group = QualityGroup(quality_node.getMetaDataEntry("name", ""), quality_type)
|
||||
quality_group.setGlobalNode(quality_node)
|
||||
quality_group_dict[quality_type] = quality_group
|
||||
break
|
||||
|
||||
buildplate_name = machine.getBuildplateName()
|
||||
|
||||
# Iterate over all extruders to find quality containers for each extruder
|
||||
for position, extruder in machine.extruders.items():
|
||||
nozzle_name = None
|
||||
if extruder.variant.getId() != "empty_variant":
|
||||
nozzle_name = extruder.variant.getName()
|
||||
|
||||
# This is a list of root material IDs to use for searching for suitable quality profiles.
|
||||
# The root material IDs in this list are in prioritized order.
|
||||
root_material_id_list = []
|
||||
has_material = False # flag indicating whether this extruder has a material assigned
|
||||
root_material_id = None
|
||||
if extruder.material.getId() != "empty_material":
|
||||
has_material = True
|
||||
root_material_id = extruder.material.getMetaDataEntry("base_file")
|
||||
# Convert possible generic_pla_175 -> generic_pla
|
||||
root_material_id = self._material_manager.getRootMaterialIDWithoutDiameter(root_material_id)
|
||||
root_material_id_list.append(root_material_id)
|
||||
|
||||
# Also try to get the fallback materials
|
||||
fallback_ids = self._material_manager.getFallBackMaterialIdsByMaterial(extruder.material)
|
||||
|
||||
if fallback_ids:
|
||||
root_material_id_list.extend(fallback_ids)
|
||||
|
||||
# Weed out duplicates while preserving the order.
|
||||
seen = set() # type: Set[str]
|
||||
root_material_id_list = [x for x in root_material_id_list if x not in seen and not seen.add(x)] # type: ignore
|
||||
|
||||
# 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-nozzle-buildplate-and-material-specific qualities if exist
|
||||
# 2. machine-nozzle-and-material-specific qualities if exist
|
||||
# 3. machine-nozzle-specific qualities if exist
|
||||
# 4. machine-material-specific qualities if exist
|
||||
# 5. machine-specific global qualities if exist, otherwise generic global qualities
|
||||
# NOTE: We DO NOT fail back to generic global qualities if machine-specific global qualities exist.
|
||||
# This is because when a machine defines its own global qualities such as Normal, Fine, etc.,
|
||||
# it is intended to maintain those specific qualities ONLY. If we still fail back to the generic
|
||||
# global qualities, there can be unimplemented quality types e.g. "coarse", and this is not
|
||||
# correct.
|
||||
# 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.
|
||||
node_info_list_0 = [nozzle_name, buildplate_name, root_material_id] # type: List[Optional[str]]
|
||||
nodes_to_check = []
|
||||
|
||||
# This function tries to recursively find the deepest (the most specific) branch and add those nodes to
|
||||
# the search list in the order described above. So, by iterating over that search node list, we first look
|
||||
# in the more specific branches and then the less specific (generic) ones.
|
||||
def addNodesToCheck(node: Optional[QualityNode], nodes_to_check_list: List[QualityNode], node_info_list, node_info_idx: int) -> None:
|
||||
if node is None:
|
||||
return
|
||||
|
||||
if node_info_idx < len(node_info_list):
|
||||
node_name = node_info_list[node_info_idx]
|
||||
if node_name is not None:
|
||||
current_node = node.getChildNode(node_name)
|
||||
if current_node is not None and has_material:
|
||||
addNodesToCheck(current_node, nodes_to_check_list, node_info_list, node_info_idx + 1)
|
||||
|
||||
if has_material:
|
||||
for rmid in root_material_id_list:
|
||||
material_node = node.getChildNode(rmid)
|
||||
if material_node:
|
||||
nodes_to_check_list.append(material_node)
|
||||
break
|
||||
|
||||
nodes_to_check_list.append(node)
|
||||
|
||||
addNodesToCheck(machine_node, nodes_to_check, node_info_list_0, 0)
|
||||
|
||||
# The last fall back will be the global qualities (either from the machine-specific node or the generic
|
||||
# node), but we only use one. For details see the overview comments above.
|
||||
|
||||
if machine_node is not None and machine_node.quality_type_map:
|
||||
nodes_to_check += [machine_node]
|
||||
elif default_machine_node is not None:
|
||||
nodes_to_check += [default_machine_node]
|
||||
|
||||
for node_idx, node in enumerate(nodes_to_check):
|
||||
if node and node.quality_type_map:
|
||||
if has_extruder_specific_qualities:
|
||||
# Only include variant qualities; skip non global qualities
|
||||
quality_node = list(node.quality_type_map.values())[0]
|
||||
is_global_quality = parseBool(quality_node.getMetaDataEntry("global_quality", False))
|
||||
if is_global_quality:
|
||||
continue
|
||||
|
||||
for quality_type, quality_node in node.quality_type_map.items():
|
||||
if quality_type not in quality_group_dict:
|
||||
quality_group = QualityGroup(quality_node.getMetaDataEntry("name", ""), quality_type)
|
||||
quality_group_dict[quality_type] = quality_group
|
||||
|
||||
quality_group = quality_group_dict[quality_type]
|
||||
if position not in quality_group.nodes_for_extruders:
|
||||
quality_group.setExtruderNode(position, quality_node)
|
||||
|
||||
# If the machine has its own specific qualities, for extruders, it should skip the global qualities
|
||||
# and use the material/variant specific qualities.
|
||||
if has_extruder_specific_qualities:
|
||||
if node_idx == len(nodes_to_check) - 1:
|
||||
break
|
||||
|
||||
# Update availabilities for each quality group
|
||||
self._updateQualityGroupsAvailability(machine, quality_group_dict.values())
|
||||
|
||||
return quality_group_dict
|
||||
# Both available and unavailable quality groups will be included. Whether
|
||||
# a quality group is available can be known via the field
|
||||
# ``QualityGroup.is_available``. For more details, see QualityGroup.
|
||||
# \return A dictionary with quality types as keys and the quality groups
|
||||
# for those types as values.
|
||||
def getQualityGroups(self, global_stack: "GlobalStack") -> Dict[str, QualityGroup]:
|
||||
# Gather up the variant names and material base files for each extruder.
|
||||
variant_names = [extruder.variant.getName() for extruder in global_stack.extruderList]
|
||||
material_bases = [extruder.material.getMetaDataEntry("base_file") for extruder in global_stack.extruderList]
|
||||
extruder_enabled = [extruder.isEnabled for extruder in global_stack.extruderList]
|
||||
definition_id = global_stack.definition.getId()
|
||||
return ContainerTree.getInstance().machines[definition_id].getQualityGroups(variant_names, material_bases, extruder_enabled)
|
||||
|
||||
def getQualityGroupsForMachineDefinition(self, machine: "GlobalStack") -> Dict[str, QualityGroup]:
|
||||
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
|
||||
|
@ -367,167 +110,85 @@ class QualityManager(QObject):
|
|||
# Iterate over all quality_types in the machine node
|
||||
quality_group_dict = dict()
|
||||
for node in nodes_to_check:
|
||||
if node and node.quality_type_map:
|
||||
for quality_type, quality_node in node.quality_type_map.items():
|
||||
quality_group = QualityGroup(quality_node.getMetaDataEntry("name", ""), quality_type)
|
||||
quality_group.setGlobalNode(quality_node)
|
||||
quality_group_dict[quality_type] = quality_group
|
||||
break
|
||||
if node and node.quality_type:
|
||||
quality_group = QualityGroup(node.getMetaDataEntry("name", ""), node.quality_type)
|
||||
quality_group.setGlobalNode(node)
|
||||
quality_group_dict[node.quality_type] = quality_group
|
||||
|
||||
return quality_group_dict
|
||||
|
||||
## Get the quality group for the preferred quality type for a certain
|
||||
# global stack.
|
||||
#
|
||||
# If the preferred quality type is not available, ``None`` will be
|
||||
# returned.
|
||||
# \param machine The global stack of the machine to get the preferred
|
||||
# quality group for.
|
||||
# \return The preferred quality group, or ``None`` if that is not
|
||||
# available.
|
||||
def getDefaultQualityType(self, machine: "GlobalStack") -> Optional[QualityGroup]:
|
||||
preferred_quality_type = machine.definition.getMetaDataEntry("preferred_quality_type")
|
||||
quality_group_dict = self.getQualityGroups(machine)
|
||||
quality_group = quality_group_dict.get(preferred_quality_type)
|
||||
return quality_group
|
||||
machine_node = ContainerTree.getInstance().machines[machine.definition.getId()]
|
||||
quality_groups = self.getQualityGroups(machine)
|
||||
result = quality_groups.get(machine_node.preferred_quality_type)
|
||||
if result is not None and result.is_available:
|
||||
return result
|
||||
return None # If preferred quality type is not available, leave it up for the caller.
|
||||
|
||||
|
||||
#
|
||||
# Methods for GUI
|
||||
#
|
||||
|
||||
#
|
||||
# Remove the given quality changes group.
|
||||
#
|
||||
## Deletes a custom profile. It will be gone forever.
|
||||
# \param quality_changes_group The quality changes group representing the
|
||||
# profile to delete.
|
||||
@pyqtSlot(QObject)
|
||||
def removeQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup") -> None:
|
||||
Logger.log("i", "Removing quality changes group [%s]", quality_changes_group.name)
|
||||
removed_quality_changes_ids = set()
|
||||
for node in quality_changes_group.getAllNodes():
|
||||
container_id = node.getMetaDataEntry("id")
|
||||
self._container_registry.removeContainer(container_id)
|
||||
removed_quality_changes_ids.add(container_id)
|
||||
|
||||
# Reset all machines that have activated this quality changes to empty.
|
||||
for global_stack in self._container_registry.findContainerStacks(type = "machine"):
|
||||
if global_stack.qualityChanges.getId() in removed_quality_changes_ids:
|
||||
global_stack.qualityChanges = self._empty_quality_changes_container
|
||||
for extruder_stack in self._container_registry.findContainerStacks(type = "extruder_train"):
|
||||
if extruder_stack.qualityChanges.getId() in removed_quality_changes_ids:
|
||||
extruder_stack.qualityChanges = self._empty_quality_changes_container
|
||||
return cura.CuraApplication.CuraApplication.getInstance().getQualityManagementModel().removeQualityChangesGroup(quality_changes_group)
|
||||
|
||||
## Rename a custom profile.
|
||||
#
|
||||
# Rename a set of quality changes containers. Returns the new name.
|
||||
#
|
||||
# Because the names must be unique, the new name may not actually become
|
||||
# the name that was given. The actual name is returned by this function.
|
||||
# \param quality_changes_group The custom profile that must be renamed.
|
||||
# \param new_name The desired name for the profile.
|
||||
# \return The actual new name of the profile, after making the name
|
||||
# unique.
|
||||
@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
|
||||
return cura.CuraApplication.CuraApplication.getInstance().getQualityManagementModel().renameQualityChangesGroup(quality_changes_group, new_name)
|
||||
|
||||
new_name = self._container_registry.uniqueName(new_name)
|
||||
for node in quality_changes_group.getAllNodes():
|
||||
container = node.getContainer()
|
||||
if container:
|
||||
container.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.
|
||||
#
|
||||
## Duplicates a given quality profile OR quality changes profile.
|
||||
# \param new_name The desired name of the new profile. This will be made
|
||||
# unique, so it might end up with a different name.
|
||||
# \param quality_model_item The item of this model to duplicate, as
|
||||
# dictionary. See the descriptions of the roles of this list model.
|
||||
@pyqtSlot(str, "QVariantMap")
|
||||
def duplicateQualityChanges(self, quality_changes_name: str, quality_model_item) -> None:
|
||||
global_stack = self._application.getGlobalContainerStack()
|
||||
if not global_stack:
|
||||
Logger.log("i", "No active global stack, cannot duplicate quality changes.")
|
||||
return
|
||||
def duplicateQualityChanges(self, quality_changes_name: str, quality_model_item: Dict[str, Any]) -> None:
|
||||
return cura.CuraApplication.CuraApplication.getInstance().getQualityManagementModel().duplicateQualityChanges(quality_changes_name, quality_model_item)
|
||||
|
||||
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_name = self._container_registry.uniqueName(quality_changes_name)
|
||||
new_quality_changes = self._createQualityChanges(quality_group.quality_type, new_name,
|
||||
global_stack, 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()
|
||||
if not container:
|
||||
continue
|
||||
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.
|
||||
## 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.
|
||||
# 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.
|
||||
# \param base_name The new name for the quality changes profile. The final
|
||||
# name of the profile might be different from this, because it needs to be
|
||||
# made unique.
|
||||
@pyqtSlot(str)
|
||||
def createQualityChanges(self, base_name: str) -> None:
|
||||
machine_manager = self._application.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
|
||||
|
||||
quality_type = quality_container.getMetaDataEntry("quality_type")
|
||||
extruder_stack = None
|
||||
if isinstance(stack, ExtruderStack):
|
||||
extruder_stack = stack
|
||||
new_changes = self._createQualityChanges(quality_type, unique_name, global_stack, extruder_stack)
|
||||
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_stack: Optional["ExtruderStack"]) -> "InstanceContainer":
|
||||
base_id = machine.definition.getId() if extruder_stack is None else extruder_stack.getId()
|
||||
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.setMetaDataEntry("type", "quality_changes")
|
||||
quality_changes.setMetaDataEntry("quality_type", quality_type)
|
||||
|
||||
# If we are creating a container for an extruder, ensure we add that to the container
|
||||
if extruder_stack is not None:
|
||||
quality_changes.setMetaDataEntry("position", extruder_stack.getMetaDataEntry("position"))
|
||||
|
||||
# If the machine specifies qualities should be filtered, ensure we match the current criteria.
|
||||
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
|
||||
quality_changes.setDefinition(machine_definition_id)
|
||||
|
||||
quality_changes.setMetaDataEntry("setting_version", self._application.SettingVersion)
|
||||
return quality_changes
|
||||
return cura.CuraApplication.CuraApplication.getInstance().getQualityManagementModel().createQualityChanges(base_name)
|
||||
|
||||
## Create a quality changes container with the given set-up.
|
||||
# \param quality_type The quality type of the new container.
|
||||
# \param new_name The name of the container. This name must be unique.
|
||||
# \param machine The global stack to create the profile for.
|
||||
# \param extruder_stack The extruder stack to create the profile for. If
|
||||
# not provided, only a global container will be created.
|
||||
def _createQualityChanges(self, quality_type: str, new_name: str, machine: "GlobalStack", extruder_stack: Optional["ExtruderStack"]) -> "InstanceContainer":
|
||||
return cura.CuraApplication.CuraApplication.getInstance().getQualityManagementModel()._createQualityChanges(quality_type, new_name, machine, extruder_stack)
|
||||
|
||||
#
|
||||
# Gets the machine definition ID that can be used to search for Quality containers that are suitable for the given
|
||||
|
|
|
@ -1,38 +1,44 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Optional, Dict, cast, Any
|
||||
from typing import Union, TYPE_CHECKING
|
||||
|
||||
from .ContainerNode import ContainerNode
|
||||
from .QualityChangesGroup import QualityChangesGroup
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from cura.Machines.ContainerNode import ContainerNode
|
||||
from cura.Machines.IntentNode import IntentNode
|
||||
import UM.FlameProfiler
|
||||
if TYPE_CHECKING:
|
||||
from typing import Dict
|
||||
from cura.Machines.MaterialNode import MaterialNode
|
||||
from cura.Machines.MachineNode import MachineNode
|
||||
|
||||
|
||||
## Represents a quality profile in the container tree.
|
||||
#
|
||||
# QualityNode is used for BOTH quality and quality_changes containers.
|
||||
# This may either be a normal quality profile or a global quality profile.
|
||||
#
|
||||
# Its subcontainers are intent profiles.
|
||||
class QualityNode(ContainerNode):
|
||||
def __init__(self, container_id: str, parent: Union["MaterialNode", "MachineNode"]) -> None:
|
||||
super().__init__(container_id)
|
||||
self.parent = parent
|
||||
self.intents = {} # type: Dict[str, IntentNode]
|
||||
|
||||
def __init__(self, metadata: Optional[Dict[str, Any]] = None) -> None:
|
||||
super().__init__(metadata = metadata)
|
||||
self.quality_type_map = {} # type: Dict[str, QualityNode] # quality_type -> QualityNode for InstanceContainer
|
||||
my_metadata = ContainerRegistry.getInstance().findContainersMetadata(id = container_id)[0]
|
||||
self.quality_type = my_metadata["quality_type"]
|
||||
# The material type of the parent doesn't need to be the same as this due to generic fallbacks.
|
||||
self._material = my_metadata.get("material")
|
||||
self._loadAll()
|
||||
|
||||
def getChildNode(self, child_key: str) -> Optional["QualityNode"]:
|
||||
return self.children_map.get(child_key)
|
||||
@UM.FlameProfiler.profile
|
||||
def _loadAll(self) -> None:
|
||||
container_registry = ContainerRegistry.getInstance()
|
||||
|
||||
def addQualityMetadata(self, quality_type: str, metadata: Dict[str, Any]):
|
||||
if quality_type not in self.quality_type_map:
|
||||
self.quality_type_map[quality_type] = QualityNode(metadata)
|
||||
# Find all intent profiles that fit the current configuration.
|
||||
from cura.Machines.MachineNode import MachineNode
|
||||
if not isinstance(self.parent, MachineNode): # Not a global profile.
|
||||
for intent in container_registry.findInstanceContainersMetadata(type = "intent", definition = self.parent.variant.machine.quality_definition, variant = self.parent.variant.variant_name, material = self._material, quality_type = self.quality_type):
|
||||
self.intents[intent["id"]] = IntentNode(intent["id"], quality = self)
|
||||
|
||||
def getQualityNode(self, quality_type: str) -> Optional["QualityNode"]:
|
||||
return self.quality_type_map.get(quality_type)
|
||||
|
||||
def addQualityChangesMetadata(self, quality_type: str, metadata: Dict[str, Any]):
|
||||
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]
|
||||
cast(QualityChangesGroup, quality_changes_group).addNode(QualityNode(metadata))
|
||||
self.intents["empty_intent"] = IntentNode("empty_intent", quality = self)
|
||||
# Otherwise, there are no intents for global profiles.
|
|
@ -1,16 +1,17 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from collections import OrderedDict
|
||||
from typing import Optional, TYPE_CHECKING, Dict
|
||||
|
||||
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
|
||||
from UM.Decorators import deprecated
|
||||
from UM.Logger import Logger
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Util import parseBool
|
||||
|
||||
from cura.Machines.ContainerNode import ContainerNode
|
||||
from cura.Machines.VariantType import VariantType, ALL_VARIANT_TYPES
|
||||
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -35,70 +36,20 @@ if TYPE_CHECKING:
|
|||
# A container is loaded when getVariant() is called to load a variant InstanceContainer.
|
||||
#
|
||||
class VariantManager:
|
||||
__instance = None
|
||||
|
||||
def __init__(self, container_registry: ContainerRegistry) -> None:
|
||||
self._container_registry = container_registry
|
||||
@classmethod
|
||||
@deprecated("Use the ContainerTree structure instead.", since = "4.3")
|
||||
def getInstance(cls) -> "VariantManager":
|
||||
if cls.__instance is None:
|
||||
cls.__instance = VariantManager()
|
||||
return cls.__instance
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._machine_to_variant_dict_map = dict() # type: Dict[str, Dict["VariantType", Dict[str, ContainerNode]]]
|
||||
self._machine_to_buildplate_dict_map = dict() # type: Dict[str, Dict[str, ContainerNode]]
|
||||
|
||||
self._exclude_variant_id_list = ["empty_variant"]
|
||||
|
||||
#
|
||||
# Initializes the VariantManager including:
|
||||
# - initializing the variant lookup table based on the metadata in ContainerRegistry.
|
||||
#
|
||||
def initialize(self) -> None:
|
||||
self._machine_to_variant_dict_map = OrderedDict()
|
||||
self._machine_to_buildplate_dict_map = OrderedDict()
|
||||
|
||||
# Cache all variants from the container registry to a variant map for better searching and organization.
|
||||
variant_metadata_list = self._container_registry.findContainersMetadata(type = "variant")
|
||||
for variant_metadata in variant_metadata_list:
|
||||
if variant_metadata["id"] in self._exclude_variant_id_list:
|
||||
Logger.log("d", "Exclude variant [%s]", variant_metadata["id"])
|
||||
continue
|
||||
|
||||
variant_name = variant_metadata["name"]
|
||||
variant_definition = variant_metadata["definition"]
|
||||
if variant_definition not in self._machine_to_variant_dict_map:
|
||||
self._machine_to_variant_dict_map[variant_definition] = OrderedDict()
|
||||
for variant_type in ALL_VARIANT_TYPES:
|
||||
self._machine_to_variant_dict_map[variant_definition][variant_type] = dict()
|
||||
|
||||
try:
|
||||
variant_type = variant_metadata["hardware_type"]
|
||||
except KeyError:
|
||||
Logger.log("w", "Variant %s does not specify a hardware_type; assuming 'nozzle'", variant_metadata["id"])
|
||||
variant_type = VariantType.NOZZLE
|
||||
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.
|
||||
ConfigurationErrorMessage.getInstance().addFaultyContainers(variant_metadata["id"])
|
||||
continue #Then ignore this variant. This now chooses one of the two variants arbitrarily and deletes the other one! No guarantees!
|
||||
|
||||
variant_dict[variant_name] = ContainerNode(metadata = variant_metadata)
|
||||
|
||||
# If the variant is a buildplate then fill also the buildplate map
|
||||
if variant_type == VariantType.BUILD_PLATE:
|
||||
if variant_definition not in self._machine_to_buildplate_dict_map:
|
||||
self._machine_to_buildplate_dict_map[variant_definition] = OrderedDict()
|
||||
|
||||
try:
|
||||
variant_container = self._container_registry.findContainers(type = "variant", id = variant_metadata["id"])[0]
|
||||
except IndexError as e:
|
||||
# It still needs to break, but we want to know what variant ID made it break.
|
||||
msg = "Unable to find build plate variant with the id [%s]" % variant_metadata["id"]
|
||||
Logger.logException("e", msg)
|
||||
raise IndexError(msg)
|
||||
|
||||
buildplate_type = variant_container.getProperty("machine_buildplate_type", "value")
|
||||
if buildplate_type not in self._machine_to_buildplate_dict_map[variant_definition]:
|
||||
self._machine_to_variant_dict_map[variant_definition][buildplate_type] = dict()
|
||||
|
||||
self._machine_to_buildplate_dict_map[variant_definition][buildplate_type] = variant_dict[variant_name]
|
||||
|
||||
#
|
||||
# Gets the variant InstanceContainer with the given information.
|
||||
# Almost the same as getVariantMetadata() except that this returns an InstanceContainer if present.
|
||||
|
@ -107,7 +58,7 @@ class VariantManager:
|
|||
variant_type: Optional["VariantType"] = None) -> Optional["ContainerNode"]:
|
||||
if variant_type is None:
|
||||
variant_node = None
|
||||
variant_type_dict = self._machine_to_variant_dict_map[machine_definition_id]
|
||||
variant_type_dict = self._machine_to_variant_dict_map.get("machine_definition_id", {})
|
||||
for variant_dict in variant_type_dict.values():
|
||||
if variant_name in variant_dict:
|
||||
variant_node = variant_dict[variant_name]
|
||||
|
@ -145,8 +96,3 @@ class VariantManager:
|
|||
if preferred_variant_name:
|
||||
node = self.getVariantNode(machine_definition_id, preferred_variant_name, variant_type)
|
||||
return node
|
||||
|
||||
def getBuildplateVariantNode(self, machine_definition_id: str, buildplate_type: str) -> Optional["ContainerNode"]:
|
||||
if machine_definition_id in self._machine_to_buildplate_dict_map:
|
||||
return self._machine_to_buildplate_dict_map[machine_definition_id].get(buildplate_type)
|
||||
return None
|
||||
|
|
162
cura/Machines/VariantNode.py
Normal file
162
cura/Machines/VariantNode.py
Normal file
|
@ -0,0 +1,162 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Settings.Interfaces import ContainerInterface
|
||||
from UM.Signal import Signal
|
||||
from cura.Machines.ContainerNode import ContainerNode
|
||||
from cura.Machines.MaterialNode import MaterialNode
|
||||
import UM.FlameProfiler
|
||||
if TYPE_CHECKING:
|
||||
from typing import Dict
|
||||
from cura.Machines.MachineNode import MachineNode
|
||||
|
||||
|
||||
## This class represents an extruder variant in the container tree.
|
||||
#
|
||||
# The subnodes of these nodes are materials.
|
||||
#
|
||||
# This node contains materials with ALL filament diameters underneath it. The
|
||||
# tree of this variant is not specific to one global stack, so because the
|
||||
# list of materials can be different per stack depending on the compatible
|
||||
# material diameter setting, we cannot filter them here. Filtering must be
|
||||
# done in the model.
|
||||
class VariantNode(ContainerNode):
|
||||
def __init__(self, container_id: str, machine: "MachineNode") -> None:
|
||||
super().__init__(container_id)
|
||||
self.machine = machine
|
||||
self.materials = {} # type: Dict[str, MaterialNode] # Mapping material base files to their nodes.
|
||||
self.materialsChanged = Signal()
|
||||
|
||||
container_registry = ContainerRegistry.getInstance()
|
||||
self.variant_name = container_registry.findContainersMetadata(id = container_id)[0]["name"] # Store our own name so that we can filter more easily.
|
||||
container_registry.containerAdded.connect(self._materialAdded)
|
||||
container_registry.containerRemoved.connect(self._materialRemoved)
|
||||
self._loadAll()
|
||||
|
||||
## (Re)loads all materials under this variant.
|
||||
@UM.FlameProfiler.profile
|
||||
def _loadAll(self) -> None:
|
||||
container_registry = ContainerRegistry.getInstance()
|
||||
|
||||
if not self.machine.has_materials:
|
||||
self.materials["empty_material"] = MaterialNode("empty_material", variant = self)
|
||||
return # There should not be any materials loaded for this printer.
|
||||
|
||||
# Find all the materials for this variant's name.
|
||||
else: # Printer has its own material profiles. Look for material profiles with this printer's definition.
|
||||
base_materials = container_registry.findInstanceContainersMetadata(type = "material", definition = "fdmprinter")
|
||||
printer_specific_materials = container_registry.findInstanceContainersMetadata(type = "material", definition = self.machine.container_id, variant_name = None)
|
||||
variant_specific_materials = container_registry.findInstanceContainersMetadata(type = "material", definition = self.machine.container_id, variant_name = self.variant_name) # If empty_variant, this won't return anything.
|
||||
materials_per_base_file = {material["base_file"]: material for material in base_materials}
|
||||
materials_per_base_file.update({material["base_file"]: material for material in printer_specific_materials}) # Printer-specific profiles override global ones.
|
||||
materials_per_base_file.update({material["base_file"]: material for material in variant_specific_materials}) # Variant-specific profiles override all of those.
|
||||
materials = list(materials_per_base_file.values())
|
||||
|
||||
# Filter materials based on the exclude_materials property.
|
||||
filtered_materials = [material for material in materials if material["id"] not in self.machine.exclude_materials]
|
||||
|
||||
for material in filtered_materials:
|
||||
base_file = material["base_file"]
|
||||
if base_file not in self.materials:
|
||||
self.materials[base_file] = MaterialNode(material["id"], variant = self)
|
||||
self.materials[base_file].materialChanged.connect(self.materialsChanged)
|
||||
if not self.materials:
|
||||
self.materials["empty_material"] = MaterialNode("empty_material", variant = self)
|
||||
|
||||
## Finds the preferred material for this printer with this nozzle in one of
|
||||
# the extruders.
|
||||
#
|
||||
# If the preferred material is not available, an arbitrary material is
|
||||
# returned. If there is a configuration mistake (like a typo in the
|
||||
# preferred material) this returns a random available material. If there
|
||||
# are no available materials, this will return the empty material node.
|
||||
# \param approximate_diameter The desired approximate diameter of the
|
||||
# material.
|
||||
# \return The node for the preferred material, or any arbitrary material
|
||||
# if there is no match.
|
||||
def preferredMaterial(self, approximate_diameter: int) -> MaterialNode:
|
||||
for base_material, material_node in self.materials.items():
|
||||
if self.machine.preferred_material in base_material and approximate_diameter == int(material_node.getMetaDataEntry("approximate_diameter")):
|
||||
return material_node
|
||||
# First fallback: Choose any material with matching diameter.
|
||||
for material_node in self.materials.values():
|
||||
if approximate_diameter == int(material_node.getMetaDataEntry("approximate_diameter")):
|
||||
return material_node
|
||||
fallback = next(iter(self.materials.values())) # Should only happen with empty material node.
|
||||
Logger.log("w", "Could not find preferred material {preferred_material} with diameter {diameter} for variant {variant_id}, falling back to {fallback}.".format(
|
||||
preferred_material = self.machine.preferred_material,
|
||||
diameter = approximate_diameter,
|
||||
variant_id = self.container_id,
|
||||
fallback = fallback.container_id
|
||||
))
|
||||
return fallback
|
||||
|
||||
## When a material gets added to the set of profiles, we need to update our
|
||||
# tree here.
|
||||
@UM.FlameProfiler.profile
|
||||
def _materialAdded(self, container: ContainerInterface) -> None:
|
||||
if container.getMetaDataEntry("type") != "material":
|
||||
return # Not interested.
|
||||
if not self.machine.has_materials:
|
||||
return # We won't add any materials.
|
||||
material_definition = container.getMetaDataEntry("definition")
|
||||
|
||||
base_file = container.getMetaDataEntry("base_file")
|
||||
if base_file in self.machine.exclude_materials:
|
||||
return # Material is forbidden for this printer.
|
||||
if base_file not in self.materials: # Completely new base file. Always better than not having a file as long as it matches our set-up.
|
||||
if material_definition != "fdmprinter" and material_definition != self.machine.container_id:
|
||||
return
|
||||
material_variant = container.getMetaDataEntry("variant_name", "empty")
|
||||
if material_variant != "empty" and material_variant != self.variant_name:
|
||||
return
|
||||
else: # We already have this base profile. Replace the base profile if the new one is more specific.
|
||||
new_definition = container.getMetaDataEntry("definition")
|
||||
if new_definition == "fdmprinter":
|
||||
return # Just as unspecific or worse.
|
||||
if new_definition != self.machine.container_id:
|
||||
return # Doesn't match this set-up.
|
||||
original_metadata = ContainerRegistry.getInstance().findContainersMetadata(id = self.materials[base_file].container_id)[0]
|
||||
original_variant = original_metadata.get("variant_name", "empty")
|
||||
if original_variant != "empty" or container.getMetaDataEntry("variant_name", "empty") == "empty":
|
||||
return # Original was already specific or just as unspecific as the new one.
|
||||
|
||||
if "empty_material" in self.materials:
|
||||
del self.materials["empty_material"]
|
||||
self.materials[base_file] = MaterialNode(container.getId(), variant = self)
|
||||
self.materials[base_file].materialChanged.connect(self.materialsChanged)
|
||||
self.materialsChanged.emit(self.materials[base_file])
|
||||
|
||||
@UM.FlameProfiler.profile
|
||||
def _materialRemoved(self, container: ContainerInterface) -> None:
|
||||
if container.getMetaDataEntry("type") != "material":
|
||||
return # Only interested in materials.
|
||||
base_file = container.getMetaDataEntry("base_file")
|
||||
if base_file not in self.materials:
|
||||
return # We don't track this material anyway. No need to remove it.
|
||||
|
||||
original_node = self.materials[base_file]
|
||||
del self.materials[base_file]
|
||||
self.materialsChanged.emit(original_node)
|
||||
|
||||
# Now a different material from the same base file may have been hidden because it was not as specific as the one we deleted.
|
||||
# Search for any submaterials from that base file that are still left.
|
||||
materials_same_base_file = ContainerRegistry.getInstance().findContainersMetadata(base_file = base_file)
|
||||
if materials_same_base_file:
|
||||
most_specific_submaterial = materials_same_base_file[0]
|
||||
for submaterial in materials_same_base_file:
|
||||
if submaterial["definition"] == self.machine.container_id:
|
||||
if most_specific_submaterial["definition"] == "fdmprinter":
|
||||
most_specific_submaterial = submaterial
|
||||
if most_specific_submaterial.get("variant_name", "empty") == "empty" and submaterial.get("variant_name", "empty") == self.variant_name:
|
||||
most_specific_submaterial = submaterial
|
||||
self.materials[base_file] = MaterialNode(most_specific_submaterial["id"], variant = self)
|
||||
self.materialsChanged.emit(self.materials[base_file])
|
||||
|
||||
if not self.materials: # The last available material just got deleted and there is nothing with the same base file to replace it.
|
||||
self.materials["empty_material"] = MaterialNode("empty_material", variant = self)
|
||||
self.materialsChanged.emit(self.materials["empty_material"])
|
|
@ -225,7 +225,7 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
if printer.printerConfiguration is not None and printer.printerConfiguration.hasAnyMaterialLoaded():
|
||||
all_configurations.add(printer.printerConfiguration)
|
||||
all_configurations.update(printer.availableConfigurations)
|
||||
new_configurations = sorted(all_configurations, key = lambda config: config.printerType)
|
||||
new_configurations = sorted(all_configurations, key = lambda config: config.printerType or "")
|
||||
if new_configurations != self._unique_configurations:
|
||||
self._unique_configurations = new_configurations
|
||||
self.uniqueConfigurationsChanged.emit()
|
||||
|
@ -233,7 +233,7 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
# Returns the unique configurations of the printers within this output device
|
||||
@pyqtProperty("QStringList", notify = uniqueConfigurationsChanged)
|
||||
def uniquePrinterTypes(self) -> List[str]:
|
||||
return list(sorted(set([configuration.printerType for configuration in self._unique_configurations])))
|
||||
return list(sorted(set([configuration.printerType or "" for configuration in self._unique_configurations])))
|
||||
|
||||
def _onPrintersChanged(self) -> None:
|
||||
for printer in self._printers:
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import os
|
||||
import urllib.parse
|
||||
import uuid
|
||||
from typing import Dict, Union, Any, TYPE_CHECKING, List, cast
|
||||
from typing import Any, cast, Dict, List, TYPE_CHECKING, Union
|
||||
|
||||
from PyQt5.QtCore import QObject, QUrl
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.FlameProfiler import pyqtSlot
|
||||
from UM.Logger import Logger
|
||||
|
@ -17,21 +16,19 @@ from UM.MimeTypeDatabase import MimeTypeDatabase, MimeTypeNotFoundError
|
|||
from UM.Platform import Platform
|
||||
from UM.SaveFile import SaveFile
|
||||
from UM.Settings.ContainerFormatError import ContainerFormatError
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Settings.ContainerStack import ContainerStack
|
||||
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
|
||||
import cura.CuraApplication
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Machines.ContainerNode import ContainerNode
|
||||
from cura.Machines.MaterialNode import MaterialNode
|
||||
from cura.Machines.QualityChangesGroup import QualityChangesGroup
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from cura.Settings.MachineManager import MachineManager
|
||||
from cura.Machines.MaterialManager import MaterialManager
|
||||
from cura.Machines.QualityManager import QualityManager
|
||||
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
@ -52,17 +49,11 @@ class ContainerManager(QObject):
|
|||
except TypeError:
|
||||
super().__init__()
|
||||
|
||||
self._application = application # type: CuraApplication
|
||||
self._plugin_registry = self._application.getPluginRegistry() # type: PluginRegistry
|
||||
self._container_registry = self._application.getContainerRegistry() # type: CuraContainerRegistry
|
||||
self._machine_manager = self._application.getMachineManager() # type: MachineManager
|
||||
self._material_manager = self._application.getMaterialManager() # type: MaterialManager
|
||||
self._quality_manager = self._application.getQualityManager() # type: QualityManager
|
||||
self._container_name_filters = {} # type: Dict[str, Dict[str, Any]]
|
||||
|
||||
@pyqtSlot(str, str, result=str)
|
||||
def getContainerMetaDataEntry(self, container_id: str, entry_names: str) -> str:
|
||||
metadatas = self._container_registry.findContainersMetadata(id = container_id)
|
||||
metadatas = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry().findContainersMetadata(id = container_id)
|
||||
if not metadatas:
|
||||
Logger.log("w", "Could not get metadata of container %s because it was not found.", container_id)
|
||||
return ""
|
||||
|
@ -91,15 +82,19 @@ class ContainerManager(QObject):
|
|||
# Update: In order for QML to use objects and sub objects, those (sub) objects must all be QObject. Is that what we want?
|
||||
@pyqtSlot("QVariant", str, str)
|
||||
def setContainerMetaDataEntry(self, container_node: "ContainerNode", entry_name: str, entry_value: str) -> bool:
|
||||
root_material_id = container_node.getMetaDataEntry("base_file", "")
|
||||
if self._container_registry.isReadOnly(root_material_id):
|
||||
if container_node.container is None:
|
||||
Logger.log("w", "Container node {0} doesn't have a container.".format(container_node.container_id))
|
||||
return False
|
||||
root_material_id = container_node.container.getMetaDataEntry("base_file", "")
|
||||
container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry()
|
||||
if container_registry.isReadOnly(root_material_id):
|
||||
Logger.log("w", "Cannot set metadata of read-only container %s.", root_material_id)
|
||||
return False
|
||||
|
||||
material_group = self._material_manager.getMaterialGroup(root_material_id)
|
||||
if material_group is None:
|
||||
Logger.log("w", "Unable to find material group for: %s.", root_material_id)
|
||||
root_material_query = container_registry.findContainers(id = root_material_id)
|
||||
if not root_material_query:
|
||||
Logger.log("w", "Unable to find root material: {root_material}.".format(root_material = root_material_id))
|
||||
return False
|
||||
root_material = root_material_query[0]
|
||||
|
||||
entries = entry_name.split("/")
|
||||
entry_name = entries.pop()
|
||||
|
@ -107,7 +102,7 @@ class ContainerManager(QObject):
|
|||
sub_item_changed = False
|
||||
if entries:
|
||||
root_name = entries.pop(0)
|
||||
root = material_group.root_material_node.getMetaDataEntry(root_name)
|
||||
root = root_material.getMetaDataEntry(root_name)
|
||||
|
||||
item = root
|
||||
for _ in range(len(entries)):
|
||||
|
@ -120,16 +115,14 @@ class ContainerManager(QObject):
|
|||
entry_name = root_name
|
||||
entry_value = root
|
||||
|
||||
container = material_group.root_material_node.getContainer()
|
||||
if container is not None:
|
||||
container.setMetaDataEntry(entry_name, entry_value)
|
||||
if sub_item_changed: #If it was only a sub-item that has changed then the setMetaDataEntry won't correctly notice that something changed, and we must manually signal that the metadata changed.
|
||||
container.metaDataChanged.emit(container)
|
||||
root_material.setMetaDataEntry(entry_name, entry_value)
|
||||
if sub_item_changed: #If it was only a sub-item that has changed then the setMetaDataEntry won't correctly notice that something changed, and we must manually signal that the metadata changed.
|
||||
root_material.metaDataChanged.emit(root_material)
|
||||
return True
|
||||
|
||||
@pyqtSlot(str, result = str)
|
||||
def makeUniqueName(self, original_name: str) -> str:
|
||||
return self._container_registry.uniqueName(original_name)
|
||||
return cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry().uniqueName(original_name)
|
||||
|
||||
## Get a list of string that can be used as name filters for a Qt File Dialog
|
||||
#
|
||||
|
@ -184,7 +177,7 @@ class ContainerManager(QObject):
|
|||
else:
|
||||
mime_type = self._container_name_filters[file_type]["mime"]
|
||||
|
||||
containers = self._container_registry.findContainers(id = container_id)
|
||||
containers = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry().findContainers(id = container_id)
|
||||
if not containers:
|
||||
return {"status": "error", "message": "Container not found"}
|
||||
container = containers[0]
|
||||
|
@ -242,12 +235,13 @@ class ContainerManager(QObject):
|
|||
except MimeTypeNotFoundError:
|
||||
return {"status": "error", "message": "Could not determine mime type of file"}
|
||||
|
||||
container_type = self._container_registry.getContainerForMimeType(mime_type)
|
||||
container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry()
|
||||
container_type = container_registry.getContainerForMimeType(mime_type)
|
||||
if not container_type:
|
||||
return {"status": "error", "message": "Could not find a container to handle the specified file."}
|
||||
|
||||
container_id = urllib.parse.unquote_plus(mime_type.stripExtension(os.path.basename(file_url)))
|
||||
container_id = self._container_registry.uniqueName(container_id)
|
||||
container_id = container_registry.uniqueName(container_id)
|
||||
|
||||
container = container_type(container_id)
|
||||
|
||||
|
@ -263,7 +257,7 @@ class ContainerManager(QObject):
|
|||
|
||||
container.setDirty(True)
|
||||
|
||||
self._container_registry.addContainer(container)
|
||||
container_registry.addContainer(container)
|
||||
|
||||
return {"status": "success", "message": "Successfully imported container {0}".format(container.getName())}
|
||||
|
||||
|
@ -275,44 +269,55 @@ class ContainerManager(QObject):
|
|||
# \return \type{bool} True if successful, False if not.
|
||||
@pyqtSlot(result = bool)
|
||||
def updateQualityChanges(self) -> bool:
|
||||
global_stack = self._machine_manager.activeMachine
|
||||
application = cura.CuraApplication.CuraApplication.getInstance()
|
||||
global_stack = application.getMachineManager().activeMachine
|
||||
if not global_stack:
|
||||
return False
|
||||
|
||||
self._machine_manager.blurSettings.emit()
|
||||
application.getMachineManager().blurSettings.emit()
|
||||
|
||||
current_quality_changes_name = global_stack.qualityChanges.getName()
|
||||
current_quality_type = global_stack.quality.getMetaDataEntry("quality_type")
|
||||
extruder_stacks = list(global_stack.extruders.values())
|
||||
container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry()
|
||||
machine_definition_id = ContainerTree.getInstance().machines[global_stack.definition.getId()].quality_definition
|
||||
for stack in [global_stack] + extruder_stacks:
|
||||
# Find the quality_changes container for this stack and merge the contents of the top container into it.
|
||||
quality_changes = stack.qualityChanges
|
||||
|
||||
if quality_changes.getId() == "empty_quality_changes":
|
||||
quality_changes = self._quality_manager._createQualityChanges(current_quality_type, current_quality_changes_name,
|
||||
global_stack, stack)
|
||||
self._container_registry.addContainer(quality_changes)
|
||||
quality_changes = InstanceContainer(container_registry.uniqueName((stack.getId() + "_" + current_quality_changes_name).lower().replace(" ", "_")))
|
||||
quality_changes.setName(current_quality_changes_name)
|
||||
quality_changes.setMetaDataEntry("type", "quality_changes")
|
||||
quality_changes.setMetaDataEntry("quality_type", current_quality_type)
|
||||
if stack.getMetaDataEntry("position") is not None: # Extruder stacks.
|
||||
quality_changes.setMetaDataEntry("position", stack.getMetaDataEntry("position"))
|
||||
quality_changes.setMetaDataEntry("intent_category", stack.quality.getMetaDataEntry("intent_category", "default"))
|
||||
quality_changes.setMetaDataEntry("setting_version", application.SettingVersion)
|
||||
quality_changes.setDefinition(machine_definition_id)
|
||||
container_registry.addContainer(quality_changes)
|
||||
stack.qualityChanges = quality_changes
|
||||
|
||||
if not quality_changes or self._container_registry.isReadOnly(quality_changes.getId()):
|
||||
if not quality_changes or container_registry.isReadOnly(quality_changes.getId()):
|
||||
Logger.log("e", "Could not update quality of a nonexistant or read only quality profile in stack %s", stack.getId())
|
||||
continue
|
||||
|
||||
self._performMerge(quality_changes, stack.getTop())
|
||||
|
||||
self._machine_manager.activeQualityChangesGroupChanged.emit()
|
||||
cura.CuraApplication.CuraApplication.getInstance().getMachineManager().activeQualityChangesGroupChanged.emit()
|
||||
|
||||
return True
|
||||
|
||||
## Clear the top-most (user) containers of the active stacks.
|
||||
@pyqtSlot()
|
||||
def clearUserContainers(self) -> None:
|
||||
self._machine_manager.blurSettings.emit()
|
||||
machine_manager = cura.CuraApplication.CuraApplication.getInstance().getMachineManager()
|
||||
machine_manager.blurSettings.emit()
|
||||
|
||||
send_emits_containers = []
|
||||
|
||||
# Go through global and extruder stacks and clear their topmost container (the user settings).
|
||||
global_stack = self._machine_manager.activeMachine
|
||||
global_stack = machine_manager.activeMachine
|
||||
extruder_stacks = list(global_stack.extruders.values())
|
||||
for stack in [global_stack] + extruder_stacks:
|
||||
container = stack.userChanges
|
||||
|
@ -320,40 +325,39 @@ class ContainerManager(QObject):
|
|||
send_emits_containers.append(container)
|
||||
|
||||
# user changes are possibly added to make the current setup match the current enabled extruders
|
||||
self._machine_manager.correctExtruderSettings()
|
||||
machine_manager.correctExtruderSettings()
|
||||
|
||||
for container in send_emits_containers:
|
||||
container.sendPostponedEmits()
|
||||
|
||||
## Get a list of materials that have the same GUID as the reference material
|
||||
#
|
||||
# \param material_id \type{str} the id of the material for which to get the linked materials.
|
||||
# \return \type{list} a list of names of materials with the same GUID
|
||||
# \param material_node The node representing the material for which to get
|
||||
# the same GUID.
|
||||
# \param exclude_self Whether to include the name of the material you
|
||||
# provided.
|
||||
# \return A list of names of materials with the same GUID.
|
||||
@pyqtSlot("QVariant", bool, result = "QStringList")
|
||||
def getLinkedMaterials(self, material_node: "MaterialNode", exclude_self: bool = False):
|
||||
guid = material_node.getMetaDataEntry("GUID", "")
|
||||
|
||||
self_root_material_id = material_node.getMetaDataEntry("base_file")
|
||||
material_group_list = self._material_manager.getMaterialGroupListByGUID(guid)
|
||||
|
||||
linked_material_names = []
|
||||
if material_group_list:
|
||||
for material_group in material_group_list:
|
||||
if exclude_self and material_group.name == self_root_material_id:
|
||||
continue
|
||||
linked_material_names.append(material_group.root_material_node.getMetaDataEntry("name", ""))
|
||||
return linked_material_names
|
||||
def getLinkedMaterials(self, material_node: "MaterialNode", exclude_self: bool = False) -> List[str]:
|
||||
same_guid = ContainerRegistry.getInstance().findInstanceContainersMetadata(guid = material_node.guid)
|
||||
if exclude_self:
|
||||
return [metadata["name"] for metadata in same_guid if metadata["base_file"] != material_node.base_file]
|
||||
else:
|
||||
return [metadata["name"] for metadata in same_guid]
|
||||
|
||||
## Unlink a material from all other materials by creating a new GUID
|
||||
# \param material_id \type{str} the id of the material to create a new GUID for.
|
||||
@pyqtSlot("QVariant")
|
||||
def unlinkMaterial(self, material_node: "MaterialNode") -> None:
|
||||
# Get the material group
|
||||
material_group = self._material_manager.getMaterialGroup(material_node.getMetaDataEntry("base_file", ""))
|
||||
|
||||
if material_group is None:
|
||||
if material_node.container is None:
|
||||
Logger.log("w", "Material node {0} doesn't have a container.".format(material_node.container_id))
|
||||
return
|
||||
root_material_query = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry().findInstanceContainers(id = material_node.getMetaDataEntry("base_file", ""))
|
||||
if not root_material_query:
|
||||
Logger.log("w", "Unable to find material group for %s", material_node)
|
||||
return
|
||||
root_material = root_material_query[0]
|
||||
|
||||
# Generate a new GUID
|
||||
new_guid = str(uuid.uuid4())
|
||||
|
@ -361,9 +365,7 @@ class ContainerManager(QObject):
|
|||
# Update the GUID
|
||||
# NOTE: We only need to set the root material container because XmlMaterialProfile.setMetaDataEntry() will
|
||||
# take care of the derived containers too
|
||||
container = material_group.root_material_node.getContainer()
|
||||
if container is not None:
|
||||
container.setMetaDataEntry("GUID", new_guid)
|
||||
root_material.setMetaDataEntry("GUID", new_guid)
|
||||
|
||||
def _performMerge(self, merge_into: InstanceContainer, merge: InstanceContainer, clear_settings: bool = True) -> None:
|
||||
if merge == merge_into:
|
||||
|
@ -377,14 +379,16 @@ class ContainerManager(QObject):
|
|||
|
||||
def _updateContainerNameFilters(self) -> None:
|
||||
self._container_name_filters = {}
|
||||
for plugin_id, container_type in self._container_registry.getContainerTypes():
|
||||
plugin_registry = cura.CuraApplication.CuraApplication.getInstance().getPluginRegistry()
|
||||
container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry()
|
||||
for plugin_id, container_type in container_registry.getContainerTypes():
|
||||
# Ignore default container types since those are not plugins
|
||||
if container_type in (InstanceContainer, ContainerStack, DefinitionContainer):
|
||||
continue
|
||||
|
||||
serialize_type = ""
|
||||
try:
|
||||
plugin_metadata = self._plugin_registry.getMetaData(plugin_id)
|
||||
plugin_metadata = plugin_registry.getMetaData(plugin_id)
|
||||
if plugin_metadata:
|
||||
serialize_type = plugin_metadata["settings_container"]["type"]
|
||||
else:
|
||||
|
@ -392,7 +396,7 @@ class ContainerManager(QObject):
|
|||
except KeyError as e:
|
||||
continue
|
||||
|
||||
mime_type = self._container_registry.getMimeTypeForContainer(container_type)
|
||||
mime_type = container_registry.getMimeTypeForContainer(container_type)
|
||||
if mime_type is None:
|
||||
continue
|
||||
entry = {
|
||||
|
@ -428,7 +432,7 @@ class ContainerManager(QObject):
|
|||
path = file_url.toLocalFile()
|
||||
if not path:
|
||||
return {"status": "error", "message": catalog.i18nc("@info:status", "Invalid file URL:") + " " + str(file_url)}
|
||||
return self._container_registry.importProfile(path)
|
||||
return cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry().importProfile(path)
|
||||
|
||||
@pyqtSlot(QObject, QUrl, str)
|
||||
def exportQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup", file_url: QUrl, file_type: str) -> None:
|
||||
|
@ -438,8 +442,11 @@ class ContainerManager(QObject):
|
|||
if not path:
|
||||
return
|
||||
|
||||
container_list = [cast(InstanceContainer, n.getContainer()) for n in quality_changes_group.getAllNodes() if n.getContainer() is not None]
|
||||
self._container_registry.exportQualityProfile(container_list, path, file_type)
|
||||
container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry()
|
||||
container_list = [cast(InstanceContainer, container_registry.findContainers(id = quality_changes_group.metadata_for_global["id"])[0])] # type: List[InstanceContainer]
|
||||
for metadata in quality_changes_group.metadata_per_extruder.values():
|
||||
container_list.append(cast(InstanceContainer, container_registry.findContainers(id = metadata["id"])[0]))
|
||||
cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry().exportQualityProfile(container_list, path, file_type)
|
||||
|
||||
__instance = None # type: ContainerManager
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ from UM.Message import Message
|
|||
from UM.Platform import Platform
|
||||
from UM.PluginRegistry import PluginRegistry # For getting the possible profile writers to write with.
|
||||
from UM.Resources import Resources
|
||||
from UM.Util import parseBool
|
||||
from cura.ReaderWriters.ProfileWriter import ProfileWriter
|
||||
|
||||
from . import ExtruderStack
|
||||
|
@ -28,7 +29,7 @@ from . import GlobalStack
|
|||
|
||||
import cura.CuraApplication
|
||||
from cura.Settings.cura_empty_instance_containers import empty_quality_container
|
||||
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
from cura.ReaderWriters.ProfileReader import NoProfileException, ProfileReader
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
|
@ -101,7 +102,8 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
|
||||
## Exports an profile to a file
|
||||
#
|
||||
# \param container_list \type{list} the containers to export
|
||||
# \param container_list \type{list} the containers to export. This is not
|
||||
# necessarily in any order!
|
||||
# \param file_name \type{str} the full path and filename to export to.
|
||||
# \param file_type \type{str} the file type with the format "<description> (*.<extension>)"
|
||||
# \return True if the export succeeded, false otherwise.
|
||||
|
@ -177,6 +179,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if not global_stack:
|
||||
return {"status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename>!", "Can't import profile from <filename>{0}</filename> before a printer is added.", file_name)}
|
||||
container_tree = ContainerTree.getInstance()
|
||||
|
||||
machine_extruders = []
|
||||
for position in sorted(global_stack.extruders):
|
||||
|
@ -226,7 +229,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
# Make sure we have a profile_definition in the file:
|
||||
if profile_definition is None:
|
||||
break
|
||||
machine_definitions = self.findDefinitionContainers(id = profile_definition)
|
||||
machine_definitions = self.findContainers(id = profile_definition)
|
||||
if not machine_definitions:
|
||||
Logger.log("e", "Incorrect profile [%s]. Unknown machine type [%s]", file_name, profile_definition)
|
||||
return {"status": "error",
|
||||
|
@ -236,8 +239,9 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
|
||||
# Get the expected machine definition.
|
||||
# i.e.: We expect gcode for a UM2 Extended to be defined as normal UM2 gcode...
|
||||
profile_definition = getMachineDefinitionIDForQualitySearch(machine_definition)
|
||||
expected_machine_definition = getMachineDefinitionIDForQualitySearch(global_stack.definition)
|
||||
has_machine_quality = parseBool(machine_definition.getMetaDataEntry("has_machine_quality", "false"))
|
||||
profile_definition = machine_definition.getMetaDataEntry("quality_definition", machine_definition.getId()) if has_machine_quality else "fdmprinter"
|
||||
expected_machine_definition = container_tree.machines[global_stack.definition.getId()].quality_definition
|
||||
|
||||
# And check if the profile_definition matches either one (showing error if not):
|
||||
if profile_definition != expected_machine_definition:
|
||||
|
@ -268,7 +272,6 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
profile.setMetaDataEntry("type", "quality_changes")
|
||||
profile.setMetaDataEntry("definition", expected_machine_definition)
|
||||
profile.setMetaDataEntry("quality_type", quality_type)
|
||||
profile.setMetaDataEntry("position", "0")
|
||||
profile.setDirty(True)
|
||||
if idx == 0:
|
||||
# Move all per-extruder settings to the first extruder's quality_changes
|
||||
|
@ -384,14 +387,13 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_stack is None:
|
||||
return None
|
||||
definition_id = getMachineDefinitionIDForQualitySearch(global_stack.definition)
|
||||
definition_id = ContainerTree.getInstance().machines[global_stack.definition.getId()].quality_definition
|
||||
profile.setDefinition(definition_id)
|
||||
|
||||
# Check to make sure the imported profile actually makes sense in context of the current configuration.
|
||||
# This prevents issues where importing a "draft" profile for a machine without "draft" qualities would report as
|
||||
# successfully imported but then fail to show up.
|
||||
quality_manager = cura.CuraApplication.CuraApplication.getInstance()._quality_manager
|
||||
quality_group_dict = quality_manager.getQualityGroupsForMachineDefinition(global_stack)
|
||||
quality_group_dict = ContainerTree.getInstance().getCurrentQualityGroups()
|
||||
# "not_supported" profiles can be imported.
|
||||
if quality_type != empty_quality_container.getMetaDataEntry("quality_type") and quality_type not in quality_group_dict:
|
||||
return catalog.i18nc("@info:status", "Could not find a quality type {0} for the current configuration.", quality_type)
|
||||
|
@ -614,6 +616,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
extruder_quality_changes_container.setMetaDataEntry("setting_version", application.SettingVersion)
|
||||
extruder_quality_changes_container.setMetaDataEntry("position", extruder_definition.getMetaDataEntry("position"))
|
||||
extruder_quality_changes_container.setMetaDataEntry("quality_type", machine_quality_changes.getMetaDataEntry("quality_type"))
|
||||
extruder_quality_changes_container.setMetaDataEntry("intent_category", "default") # Intent categories weren't a thing back then.
|
||||
extruder_quality_changes_container.setDefinition(machine_quality_changes.getDefinition().getId())
|
||||
|
||||
self.addContainer(extruder_quality_changes_container)
|
||||
|
|
|
@ -87,6 +87,19 @@ class CuraContainerStack(ContainerStack):
|
|||
def qualityChanges(self) -> InstanceContainer:
|
||||
return cast(InstanceContainer, self._containers[_ContainerIndexes.QualityChanges])
|
||||
|
||||
## Set the intent container.
|
||||
#
|
||||
# \param new_intent The new intent container. It is expected to have a "type" metadata entry with the value "intent".
|
||||
def setIntent(self, new_intent: InstanceContainer, postpone_emit: bool = False) -> None:
|
||||
self.replaceContainer(_ContainerIndexes.Intent, new_intent, postpone_emit = postpone_emit)
|
||||
|
||||
## Get the quality container.
|
||||
#
|
||||
# \return The intent container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||
@pyqtProperty(InstanceContainer, fset = setIntent, notify = pyqtContainersChanged)
|
||||
def intent(self) -> InstanceContainer:
|
||||
return cast(InstanceContainer, self._containers[_ContainerIndexes.Intent])
|
||||
|
||||
## Set the quality container.
|
||||
#
|
||||
# \param new_quality The new quality container. It is expected to have a "type" metadata entry with the value "quality".
|
||||
|
@ -330,16 +343,18 @@ class CuraContainerStack(ContainerStack):
|
|||
class _ContainerIndexes:
|
||||
UserChanges = 0
|
||||
QualityChanges = 1
|
||||
Quality = 2
|
||||
Material = 3
|
||||
Variant = 4
|
||||
DefinitionChanges = 5
|
||||
Definition = 6
|
||||
Intent = 2
|
||||
Quality = 3
|
||||
Material = 4
|
||||
Variant = 5
|
||||
DefinitionChanges = 6
|
||||
Definition = 7
|
||||
|
||||
# Simple hash map to map from index to "type" metadata entry
|
||||
IndexTypeMap = {
|
||||
UserChanges: "user",
|
||||
QualityChanges: "quality_changes",
|
||||
Intent: "intent",
|
||||
Quality: "quality",
|
||||
Material: "material",
|
||||
Variant: "variant",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Optional
|
||||
|
@ -8,7 +8,8 @@ from UM.Logger import Logger
|
|||
from UM.Settings.Interfaces import DefinitionContainerInterface
|
||||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
|
||||
from cura.Machines.VariantType import VariantType
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
from cura.Machines.MachineNode import MachineNode
|
||||
from .GlobalStack import GlobalStack
|
||||
from .ExtruderStack import ExtruderStack
|
||||
|
||||
|
@ -26,9 +27,8 @@ class CuraStackBuilder:
|
|||
def createMachine(cls, name: str, definition_id: str) -> Optional[GlobalStack]:
|
||||
from cura.CuraApplication import CuraApplication
|
||||
application = CuraApplication.getInstance()
|
||||
variant_manager = application.getVariantManager()
|
||||
quality_manager = application.getQualityManager()
|
||||
registry = application.getContainerRegistry()
|
||||
container_tree = ContainerTree.getInstance()
|
||||
|
||||
definitions = registry.findDefinitionContainers(id = definition_id)
|
||||
if not definitions:
|
||||
|
@ -37,14 +37,11 @@ class CuraStackBuilder:
|
|||
return None
|
||||
|
||||
machine_definition = definitions[0]
|
||||
|
||||
# get variant container for the global stack
|
||||
global_variant_container = application.empty_variant_container
|
||||
global_variant_node = variant_manager.getDefaultVariantNode(machine_definition, VariantType.BUILD_PLATE)
|
||||
if global_variant_node:
|
||||
global_variant_container = global_variant_node.getContainer()
|
||||
if not global_variant_container:
|
||||
global_variant_container = application.empty_variant_container
|
||||
# The container tree listens to the containerAdded signal to add the definition and build the tree,
|
||||
# but that signal is emitted with a delay which might not have passed yet.
|
||||
# Therefore we must make sure that it's manually added here.
|
||||
container_tree.addMachineNodeByDefinitionId(machine_definition.getId())
|
||||
machine_node = container_tree.machines[machine_definition.getId()]
|
||||
|
||||
generated_name = registry.createUniqueName("machine", "", name, machine_definition.getName())
|
||||
# Make sure the new name does not collide with any definition or (quality) profile
|
||||
|
@ -56,9 +53,9 @@ class CuraStackBuilder:
|
|||
new_global_stack = cls.createGlobalStack(
|
||||
new_stack_id = generated_name,
|
||||
definition = machine_definition,
|
||||
variant_container = global_variant_container,
|
||||
variant_container = application.empty_variant_container,
|
||||
material_container = application.empty_material_container,
|
||||
quality_container = application.empty_quality_container,
|
||||
quality_container = machine_node.preferredGlobalQuality().container,
|
||||
)
|
||||
new_global_stack.setName(generated_name)
|
||||
|
||||
|
@ -67,33 +64,9 @@ class CuraStackBuilder:
|
|||
for position in extruder_dict:
|
||||
cls.createExtruderStackWithDefaultSetup(new_global_stack, position)
|
||||
|
||||
for new_extruder in new_global_stack.extruders.values(): #Only register the extruders if we're sure that all of them are correct.
|
||||
for new_extruder in new_global_stack.extruders.values(): # Only register the extruders if we're sure that all of them are correct.
|
||||
registry.addContainer(new_extruder)
|
||||
|
||||
preferred_quality_type = machine_definition.getMetaDataEntry("preferred_quality_type")
|
||||
quality_group_dict = quality_manager.getQualityGroups(new_global_stack)
|
||||
if not quality_group_dict:
|
||||
# There is no available quality group, set all quality containers to empty.
|
||||
new_global_stack.quality = application.empty_quality_container
|
||||
for extruder_stack in new_global_stack.extruders.values():
|
||||
extruder_stack.quality = application.empty_quality_container
|
||||
else:
|
||||
# Set the quality containers to the preferred quality type if available, otherwise use the first quality
|
||||
# type that's available.
|
||||
if preferred_quality_type not in quality_group_dict:
|
||||
Logger.log("w", "The preferred quality {quality_type} doesn't exist for this set-up. Choosing a random one.".format(quality_type = preferred_quality_type))
|
||||
preferred_quality_type = next(iter(quality_group_dict))
|
||||
quality_group = quality_group_dict.get(preferred_quality_type)
|
||||
|
||||
new_global_stack.quality = quality_group.node_for_global.getContainer()
|
||||
if not new_global_stack.quality:
|
||||
new_global_stack.quality = application.empty_quality_container
|
||||
for position, extruder_stack in new_global_stack.extruders.items():
|
||||
if position in quality_group.nodes_for_extruders and quality_group.nodes_for_extruders[position].getContainer():
|
||||
extruder_stack.quality = quality_group.nodes_for_extruders[position].getContainer()
|
||||
else:
|
||||
extruder_stack.quality = application.empty_quality_container
|
||||
|
||||
# Register the global stack after the extruder stacks are created. This prevents the registry from adding another
|
||||
# extruder stack because the global stack didn't have one yet (which is enforced since Cura 3.1).
|
||||
registry.addContainer(new_global_stack)
|
||||
|
@ -108,37 +81,32 @@ class CuraStackBuilder:
|
|||
def createExtruderStackWithDefaultSetup(cls, global_stack: "GlobalStack", extruder_position: int) -> None:
|
||||
from cura.CuraApplication import CuraApplication
|
||||
application = CuraApplication.getInstance()
|
||||
variant_manager = application.getVariantManager()
|
||||
material_manager = application.getMaterialManager()
|
||||
registry = application.getContainerRegistry()
|
||||
|
||||
# get variant container for extruders
|
||||
extruder_variant_container = application.empty_variant_container
|
||||
extruder_variant_node = variant_manager.getDefaultVariantNode(global_stack.definition, VariantType.NOZZLE,
|
||||
global_stack = global_stack)
|
||||
extruder_variant_name = None
|
||||
if extruder_variant_node:
|
||||
extruder_variant_container = extruder_variant_node.getContainer()
|
||||
if not extruder_variant_container:
|
||||
extruder_variant_container = application.empty_variant_container
|
||||
extruder_variant_name = extruder_variant_container.getName()
|
||||
|
||||
# Get the extruder definition.
|
||||
extruder_definition_dict = global_stack.getMetaDataEntry("machine_extruder_trains")
|
||||
extruder_definition_id = extruder_definition_dict[str(extruder_position)]
|
||||
try:
|
||||
extruder_definition = registry.findDefinitionContainers(id = extruder_definition_id)[0]
|
||||
except IndexError as e:
|
||||
except IndexError:
|
||||
# It still needs to break, but we want to know what extruder ID made it break.
|
||||
msg = "Unable to find extruder definition with the id [%s]" % extruder_definition_id
|
||||
Logger.logException("e", msg)
|
||||
raise IndexError(msg)
|
||||
|
||||
# get material container for extruders
|
||||
material_container = application.empty_material_container
|
||||
material_node = material_manager.getDefaultMaterial(global_stack, str(extruder_position), extruder_variant_name,
|
||||
extruder_definition = extruder_definition)
|
||||
if material_node and material_node.getContainer():
|
||||
material_container = material_node.getContainer()
|
||||
# Find out what filament diameter we need.
|
||||
approximate_diameter = round(extruder_definition.getProperty("material_diameter", "value")) # Can't be modified by definition changes since we are just initialising the stack here.
|
||||
|
||||
# Find the preferred containers.
|
||||
machine_node = ContainerTree.getInstance().machines[global_stack.definition.getId()]
|
||||
extruder_variant_node = machine_node.variants.get(machine_node.preferred_variant_name)
|
||||
if not extruder_variant_node:
|
||||
Logger.log("w", "Could not find preferred nozzle {nozzle_name}. Falling back to {fallback}.".format(nozzle_name = machine_node.preferred_variant_name, fallback = next(iter(machine_node.variants))))
|
||||
extruder_variant_node = next(iter(machine_node.variants.values()))
|
||||
extruder_variant_container = extruder_variant_node.container
|
||||
material_node = extruder_variant_node.preferredMaterial(approximate_diameter)
|
||||
material_container = material_node.container
|
||||
quality_node = material_node.preferredQuality()
|
||||
|
||||
new_extruder_id = registry.uniqueName(extruder_definition_id)
|
||||
new_extruder = cls.createExtruderStack(
|
||||
|
@ -148,7 +116,7 @@ class CuraStackBuilder:
|
|||
position = extruder_position,
|
||||
variant_container = extruder_variant_container,
|
||||
material_container = material_container,
|
||||
quality_container = application.empty_quality_container
|
||||
quality_container = quality_node.container
|
||||
)
|
||||
new_extruder.setNextStack(global_stack)
|
||||
|
||||
|
@ -190,6 +158,7 @@ class CuraStackBuilder:
|
|||
stack.variant = variant_container
|
||||
stack.material = material_container
|
||||
stack.quality = quality_container
|
||||
stack.intent = application.empty_intent_container
|
||||
stack.qualityChanges = application.empty_quality_changes_container
|
||||
stack.userChanges = user_container
|
||||
|
||||
|
@ -238,6 +207,7 @@ class CuraStackBuilder:
|
|||
stack.variant = variant_container
|
||||
stack.material = material_container
|
||||
stack.quality = quality_container
|
||||
stack.intent = application.empty_intent_container
|
||||
stack.qualityChanges = application.empty_quality_changes_container
|
||||
stack.userChanges = user_container
|
||||
|
||||
|
|
165
cura/Settings/IntentManager.py
Normal file
165
cura/Settings/IntentManager.py
Normal file
|
@ -0,0 +1,165 @@
|
|||
#Copyright (c) 2019 Ultimaker B.V.
|
||||
#Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple, TYPE_CHECKING
|
||||
import cura.CuraApplication
|
||||
from UM.Logger import Logger
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
from cura.Settings.cura_empty_instance_containers import empty_intent_container
|
||||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
|
||||
|
||||
## Front-end for querying which intents are available for a certain
|
||||
# configuration.
|
||||
class IntentManager(QObject):
|
||||
__instance = None
|
||||
|
||||
## This class is a singleton.
|
||||
@classmethod
|
||||
def getInstance(cls):
|
||||
if not cls.__instance:
|
||||
cls.__instance = IntentManager()
|
||||
return cls.__instance
|
||||
|
||||
intentCategoryChanged = pyqtSignal() #Triggered when we switch categories.
|
||||
|
||||
## Gets the metadata dictionaries of all intent profiles for a given
|
||||
# configuration.
|
||||
#
|
||||
# \param definition_id ID of the printer.
|
||||
# \param nozzle_name Name of the nozzle.
|
||||
# \param material_base_file The base_file of the material.
|
||||
# \return A list of metadata dictionaries matching the search criteria, or
|
||||
# an empty list if nothing was found.
|
||||
def intentMetadatas(self, definition_id: str, nozzle_name: str, material_base_file: str) -> List[Dict[str, Any]]:
|
||||
material_node = ContainerTree.getInstance().machines[definition_id].variants[nozzle_name].materials[material_base_file]
|
||||
intent_metadatas = []
|
||||
for quality_node in material_node.qualities.values():
|
||||
for intent_node in quality_node.intents.values():
|
||||
intent_metadatas.append(intent_node.getMetadata())
|
||||
return intent_metadatas
|
||||
|
||||
## Collects and returns all intent categories available for the given
|
||||
# parameters. Note that the 'default' category is always available.
|
||||
#
|
||||
# \param definition_id ID of the printer.
|
||||
# \param nozzle_name Name of the nozzle.
|
||||
# \param material_id ID of the material.
|
||||
# \return A set of intent category names.
|
||||
def intentCategories(self, definition_id: str, nozzle_id: str, material_id: str) -> List[str]:
|
||||
categories = set()
|
||||
for intent in self.intentMetadatas(definition_id, nozzle_id, material_id):
|
||||
categories.add(intent["intent_category"])
|
||||
categories.add("default") #The "empty" intent is not an actual profile specific to the configuration but we do want it to appear in the categories list.
|
||||
return list(categories)
|
||||
|
||||
## List of intents to be displayed in the interface.
|
||||
#
|
||||
# For the interface this will have to be broken up into the different
|
||||
# intent categories. That is up to the model there.
|
||||
#
|
||||
# \return A list of tuples of intent_category and quality_type. The actual
|
||||
# instance may vary per extruder.
|
||||
def getCurrentAvailableIntents(self) -> List[Tuple[str, str]]:
|
||||
application = cura.CuraApplication.CuraApplication.getInstance()
|
||||
global_stack = application.getGlobalContainerStack()
|
||||
if global_stack is None:
|
||||
return [("default", "normal")]
|
||||
# TODO: We now do this (return a default) if the global stack is missing, but not in the code below,
|
||||
# even though there should always be defaults. The problem then is what to do with the quality_types.
|
||||
# Currently _also_ inconsistent with 'currentAvailableIntentCategories', which _does_ return default.
|
||||
quality_groups = ContainerTree.getInstance().getCurrentQualityGroups()
|
||||
available_quality_types = {quality_group.quality_type for quality_group in quality_groups.values() if quality_group.node_for_global is not None}
|
||||
|
||||
final_intent_ids = set() # type: Set[str]
|
||||
current_definition_id = global_stack.definition.getId()
|
||||
for extruder_stack in global_stack.extruderList:
|
||||
nozzle_name = extruder_stack.variant.getMetaDataEntry("name")
|
||||
material_id = extruder_stack.material.getMetaDataEntry("base_file")
|
||||
final_intent_ids |= {metadata["id"] for metadata in self.intentMetadatas(current_definition_id, nozzle_name, material_id) if metadata.get("quality_type") in available_quality_types}
|
||||
|
||||
result = set() # type: Set[Tuple[str, str]]
|
||||
for intent_id in final_intent_ids:
|
||||
intent_metadata = application.getContainerRegistry().findContainersMetadata(id = intent_id)[0]
|
||||
result.add((intent_metadata["intent_category"], intent_metadata["quality_type"]))
|
||||
return list(result)
|
||||
|
||||
## List of intent categories available in either of the extruders.
|
||||
#
|
||||
# This is purposefully inconsistent with the way that the quality types
|
||||
# are listed. The quality types will show all quality types available in
|
||||
# the printer using any configuration. This will only list the intent
|
||||
# categories that are available using the current configuration (but the
|
||||
# union over the extruders).
|
||||
# \return List of all categories in the current configurations of all
|
||||
# extruders.
|
||||
def currentAvailableIntentCategories(self) -> List[str]:
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if global_stack is None:
|
||||
return ["default"]
|
||||
current_definition_id = global_stack.definition.getId()
|
||||
final_intent_categories = set() # type: Set[str]
|
||||
for extruder_stack in global_stack.extruderList:
|
||||
nozzle_name = extruder_stack.variant.getMetaDataEntry("name")
|
||||
material_id = extruder_stack.material.getMetaDataEntry("base_file")
|
||||
final_intent_categories.update(self.intentCategories(current_definition_id, nozzle_name, material_id))
|
||||
return list(final_intent_categories)
|
||||
|
||||
## The intent that gets selected by default when no intent is available for
|
||||
# the configuration, an extruder can't match the intent that the user
|
||||
# selects, or just when creating a new printer.
|
||||
def getDefaultIntent(self) -> InstanceContainer:
|
||||
return empty_intent_container
|
||||
|
||||
@pyqtProperty(str, notify = intentCategoryChanged)
|
||||
def currentIntentCategory(self) -> str:
|
||||
application = cura.CuraApplication.CuraApplication.getInstance()
|
||||
active_extruder_stack = application.getMachineManager().activeStack
|
||||
if active_extruder_stack is None:
|
||||
return ""
|
||||
return active_extruder_stack.intent.getMetaDataEntry("intent_category", "")
|
||||
|
||||
## Apply intent on the stacks.
|
||||
@pyqtSlot(str, str)
|
||||
def selectIntent(self, intent_category: str, quality_type: str) -> None:
|
||||
Logger.log("i", "Attempting to set intent_category to [%s] and quality type to [%s]", intent_category, quality_type)
|
||||
old_intent_category = self.currentIntentCategory
|
||||
application = cura.CuraApplication.CuraApplication.getInstance()
|
||||
global_stack = application.getGlobalContainerStack()
|
||||
if global_stack is None:
|
||||
return
|
||||
current_definition_id = global_stack.definition.getId()
|
||||
machine_node = ContainerTree.getInstance().machines[current_definition_id]
|
||||
for extruder_stack in global_stack.extruderList:
|
||||
nozzle_name = extruder_stack.variant.getMetaDataEntry("name")
|
||||
material_id = extruder_stack.material.getMetaDataEntry("base_file")
|
||||
|
||||
material_node = machine_node.variants[nozzle_name].materials[material_id]
|
||||
|
||||
# Since we want to switch to a certain quality type, check the tree if we have one.
|
||||
quality_node = None
|
||||
for q_node in material_node.qualities.values():
|
||||
if q_node.quality_type == quality_type:
|
||||
quality_node = q_node
|
||||
|
||||
if quality_node is None:
|
||||
Logger.log("w", "Unable to find quality_type [%s] for extruder [%s]", quality_type, extruder_stack.getId())
|
||||
continue
|
||||
|
||||
# Check that quality node if we can find a matching intent.
|
||||
intent_id = None
|
||||
for id, intent_node in quality_node.intents.items():
|
||||
if intent_node.intent_category == intent_category:
|
||||
intent_id = id
|
||||
intent = application.getContainerRegistry().findContainers(id = intent_id)
|
||||
if intent:
|
||||
extruder_stack.intent = intent[0]
|
||||
else:
|
||||
extruder_stack.intent = self.getDefaultIntent()
|
||||
application.getMachineManager().setQualityGroupByQualityType(quality_type)
|
||||
if old_intent_category != intent_category:
|
||||
self.intentCategoryChanged.emit()
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import time
|
||||
|
@ -22,7 +22,12 @@ from UM.Message import Message
|
|||
from UM.Settings.SettingFunction import SettingFunction
|
||||
from UM.Signal import postponeSignals, CompressTechnique
|
||||
|
||||
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
|
||||
import cura.CuraApplication # Imported like this to prevent circular references.
|
||||
|
||||
from cura.Machines.ContainerNode import ContainerNode
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
from cura.Machines.Models.IntentCategoryModel import IntentCategoryModel
|
||||
|
||||
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice, ConnectionType
|
||||
from cura.PrinterOutput.Models.PrinterConfigurationModel import PrinterConfigurationModel
|
||||
from cura.PrinterOutput.Models.ExtruderConfigurationModel import ExtruderConfigurationModel
|
||||
|
@ -32,7 +37,7 @@ from cura.Settings.ExtruderManager import ExtruderManager
|
|||
from cura.Settings.ExtruderStack import ExtruderStack
|
||||
from cura.Settings.cura_empty_instance_containers import (empty_definition_changes_container, empty_variant_container,
|
||||
empty_material_container, empty_quality_container,
|
||||
empty_quality_changes_container)
|
||||
empty_quality_changes_container, empty_intent_container)
|
||||
|
||||
from .CuraStackBuilder import CuraStackBuilder
|
||||
|
||||
|
@ -41,13 +46,10 @@ catalog = i18nCatalog("cura")
|
|||
from cura.Settings.GlobalStack import GlobalStack
|
||||
if TYPE_CHECKING:
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Settings.CuraContainerStack import CuraContainerStack
|
||||
from cura.Machines.MaterialManager import MaterialManager
|
||||
from cura.Machines.QualityManager import QualityManager
|
||||
from cura.Machines.VariantManager import VariantManager
|
||||
from cura.Machines.ContainerNode import ContainerNode
|
||||
from cura.Machines.MaterialNode import MaterialNode
|
||||
from cura.Machines.QualityChangesGroup import QualityChangesGroup
|
||||
from cura.Machines.QualityGroup import QualityGroup
|
||||
from cura.Machines.VariantNode import VariantNode
|
||||
|
||||
|
||||
class MachineManager(QObject):
|
||||
|
@ -58,8 +60,6 @@ class MachineManager(QObject):
|
|||
self._global_container_stack = None # type: Optional[GlobalStack]
|
||||
|
||||
self._current_root_material_id = {} # type: Dict[str, str]
|
||||
self._current_quality_group = None # type: Optional[QualityGroup]
|
||||
self._current_quality_changes_group = None # type: Optional[QualityChangesGroup]
|
||||
|
||||
self._default_extruder_position = "0" # to be updated when extruders are switched on and off
|
||||
|
||||
|
@ -118,22 +118,20 @@ class MachineManager(QObject):
|
|||
if containers:
|
||||
containers[0].nameChanged.connect(self._onMaterialNameChanged)
|
||||
|
||||
self._material_manager = self._application.getMaterialManager() # type: MaterialManager
|
||||
self._variant_manager = self._application.getVariantManager() # type: VariantManager
|
||||
self._quality_manager = self._application.getQualityManager() # type: QualityManager
|
||||
|
||||
# When the materials lookup table gets updated, it can mean that a material has its name changed, which should
|
||||
# be reflected on the GUI. This signal emission makes sure that it happens.
|
||||
self._material_manager.materialsUpdated.connect(self.rootMaterialChanged)
|
||||
# When the materials get updated, it can be that an activated material's diameter gets changed. In that case,
|
||||
# a material update should be triggered to make sure that the machine still has compatible materials activated.
|
||||
self._material_manager.materialsUpdated.connect(self._updateUponMaterialMetadataChange)
|
||||
self.rootMaterialChanged.connect(self._onRootMaterialChanged)
|
||||
|
||||
# Emit the printerConnectedStatusChanged when either globalContainerChanged or outputDevicesChanged are emitted
|
||||
self.globalContainerChanged.connect(self.printerConnectedStatusChanged)
|
||||
self.outputDevicesChanged.connect(self.printerConnectedStatusChanged)
|
||||
|
||||
# For updating active quality display name
|
||||
self.activeQualityChanged.connect(self.activeQualityDisplayNameChanged)
|
||||
self.activeIntentChanged.connect(self.activeQualityDisplayNameChanged)
|
||||
self.activeQualityGroupChanged.connect(self.activeQualityDisplayNameChanged)
|
||||
self.activeQualityChangesGroupChanged.connect(self.activeQualityDisplayNameChanged)
|
||||
|
||||
activeQualityDisplayNameChanged = pyqtSignal()
|
||||
|
||||
activeQualityGroupChanged = pyqtSignal()
|
||||
activeQualityChangesGroupChanged = pyqtSignal()
|
||||
|
||||
|
@ -141,6 +139,7 @@ class MachineManager(QObject):
|
|||
activeMaterialChanged = pyqtSignal()
|
||||
activeVariantChanged = pyqtSignal()
|
||||
activeQualityChanged = pyqtSignal()
|
||||
activeIntentChanged = pyqtSignal()
|
||||
activeStackChanged = pyqtSignal() # Emitted whenever the active stack is changed (ie: when changing between extruders, changing a profile, but not when changing a value)
|
||||
extruderChanged = pyqtSignal()
|
||||
|
||||
|
@ -222,10 +221,6 @@ class MachineManager(QObject):
|
|||
|
||||
def _onGlobalContainerChanged(self) -> None:
|
||||
if self._global_container_stack:
|
||||
try:
|
||||
self._global_container_stack.nameChanged.disconnect(self._onMachineNameChanged)
|
||||
except TypeError: # pyQtSignal gives a TypeError when disconnecting from something that was already disconnected.
|
||||
pass
|
||||
try:
|
||||
self._global_container_stack.containersChanged.disconnect(self._onContainersChanged)
|
||||
except TypeError:
|
||||
|
@ -250,7 +245,6 @@ class MachineManager(QObject):
|
|||
if self._global_container_stack:
|
||||
self._application.getPreferences().setValue("cura/active_machine", self._global_container_stack.getId())
|
||||
|
||||
self._global_container_stack.nameChanged.connect(self._onMachineNameChanged)
|
||||
self._global_container_stack.containersChanged.connect(self._onContainersChanged)
|
||||
self._global_container_stack.propertyChanged.connect(self._onPropertyChanged)
|
||||
|
||||
|
@ -270,16 +264,23 @@ class MachineManager(QObject):
|
|||
extruder_stack.propertyChanged.connect(self._onPropertyChanged)
|
||||
extruder_stack.containersChanged.connect(self._onContainersChanged)
|
||||
|
||||
self._onRootMaterialChanged()
|
||||
|
||||
self.activeQualityGroupChanged.emit()
|
||||
|
||||
def _onActiveExtruderStackChanged(self) -> None:
|
||||
self.blurSettings.emit() # Ensure no-one has focus.
|
||||
if self._active_container_stack is not None:
|
||||
self._active_container_stack.pyqtContainersChanged.disconnect(self.activeStackChanged) # Unplug from the old one.
|
||||
self._active_container_stack = ExtruderManager.getInstance().getActiveExtruderStack()
|
||||
if self._active_container_stack is not None:
|
||||
self._active_container_stack.pyqtContainersChanged.connect(self.activeStackChanged) # Plug into the new one.
|
||||
|
||||
def __emitChangedSignals(self) -> None:
|
||||
self.activeQualityChanged.emit()
|
||||
self.activeVariantChanged.emit()
|
||||
self.activeMaterialChanged.emit()
|
||||
self.activeIntentChanged.emit()
|
||||
|
||||
self.rootMaterialChanged.emit()
|
||||
self.numberExtrudersEnabledChanged.emit()
|
||||
|
@ -292,57 +293,6 @@ class MachineManager(QObject):
|
|||
# Notify UI items, such as the "changed" star in profile pull down menu.
|
||||
self.activeStackValueChanged.emit()
|
||||
|
||||
## Given a global_stack, make sure that it's all valid by searching for this quality group and applying it again
|
||||
def _initMachineState(self, global_stack: "CuraContainerStack") -> None:
|
||||
material_dict = {}
|
||||
for position, extruder in enumerate(global_stack.extruderList):
|
||||
material_dict[str(position)] = extruder.material.getMetaDataEntry("base_file")
|
||||
self._current_root_material_id = material_dict
|
||||
|
||||
# Update materials to make sure that the diameters match with the machine's
|
||||
for position, _ in enumerate(global_stack.extruderList):
|
||||
self.updateMaterialWithVariant(str(position))
|
||||
|
||||
global_quality = global_stack.quality
|
||||
quality_type = global_quality.getMetaDataEntry("quality_type")
|
||||
global_quality_changes = global_stack.qualityChanges
|
||||
global_quality_changes_name = global_quality_changes.getName()
|
||||
|
||||
# Try to set the same quality/quality_changes as the machine specified.
|
||||
# If the quality/quality_changes is not available, switch to the default or the first quality that's available.
|
||||
same_quality_found = False
|
||||
quality_groups = self._application.getQualityManager().getQualityGroups(global_stack)
|
||||
|
||||
if global_quality_changes.getId() != "empty_quality_changes":
|
||||
quality_changes_groups = self._application.getQualityManager().getQualityChangesGroups(global_stack)
|
||||
new_quality_changes_group = quality_changes_groups.get(global_quality_changes_name)
|
||||
if new_quality_changes_group is not None:
|
||||
self._setQualityChangesGroup(new_quality_changes_group)
|
||||
same_quality_found = True
|
||||
Logger.log("i", "Machine '%s' quality changes set to '%s'",
|
||||
global_stack.getName(), new_quality_changes_group.name)
|
||||
else:
|
||||
new_quality_group = quality_groups.get(quality_type)
|
||||
if new_quality_group is not None:
|
||||
self._setQualityGroup(new_quality_group, empty_quality_changes = True)
|
||||
same_quality_found = True
|
||||
Logger.log("i", "Machine '%s' quality set to '%s'",
|
||||
global_stack.getName(), new_quality_group.quality_type)
|
||||
|
||||
# Could not find the specified quality/quality_changes, switch to the preferred quality if available,
|
||||
# otherwise the first quality that's available, otherwise empty (not supported).
|
||||
if not same_quality_found:
|
||||
Logger.log("i", "Machine '%s' could not find quality_type '%s' and quality_changes '%s'. "
|
||||
"Available quality types are [%s]. Switching to default quality.",
|
||||
global_stack.getName(), quality_type, global_quality_changes_name,
|
||||
", ".join(quality_groups.keys()))
|
||||
preferred_quality_type = global_stack.getMetaDataEntry("preferred_quality_type")
|
||||
quality_group = quality_groups.get(preferred_quality_type)
|
||||
if quality_group is None:
|
||||
if quality_groups:
|
||||
quality_group = list(quality_groups.values())[0]
|
||||
self._setQualityGroup(quality_group, empty_quality_changes = True)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def setActiveMachine(self, stack_id: str) -> None:
|
||||
self.blurSettings.emit() # Ensure no-one has focus.
|
||||
|
@ -353,7 +303,7 @@ class MachineManager(QObject):
|
|||
if not containers:
|
||||
return
|
||||
|
||||
global_stack = containers[0]
|
||||
global_stack = cast(GlobalStack, containers[0])
|
||||
|
||||
# Make sure that the default machine actions for this machine have been added
|
||||
self._application.getMachineActionManager().addDefaultMachineActions(global_stack)
|
||||
|
@ -367,7 +317,6 @@ class MachineManager(QObject):
|
|||
self._global_container_stack = global_stack
|
||||
self._application.setGlobalContainerStack(global_stack)
|
||||
ExtruderManager.getInstance()._globalContainerStackChanged()
|
||||
self._initMachineState(global_stack)
|
||||
self._onGlobalContainerChanged()
|
||||
|
||||
# Switch to the first enabled extruder
|
||||
|
@ -612,6 +561,7 @@ class MachineManager(QObject):
|
|||
#
|
||||
# \return The material ids in all stacks
|
||||
@pyqtProperty("QVariantMap", notify = activeMaterialChanged)
|
||||
@deprecated("use Cura.MachineManager.activeStack.extruders instead.", "4.3")
|
||||
def allActiveMaterialIds(self) -> Dict[str, str]:
|
||||
result = {}
|
||||
|
||||
|
@ -629,22 +579,15 @@ class MachineManager(QObject):
|
|||
# This is indicated together with the name of the active quality profile.
|
||||
#
|
||||
# \return The layer height of the currently active quality profile. If
|
||||
# there is no quality profile, this returns 0.
|
||||
# there is no quality profile, this returns the default layer height.
|
||||
@pyqtProperty(float, notify = activeQualityGroupChanged)
|
||||
def activeQualityLayerHeight(self) -> float:
|
||||
if not self._global_container_stack:
|
||||
return 0
|
||||
if self._current_quality_changes_group:
|
||||
value = self._global_container_stack.getRawProperty("layer_height", "value", skip_until_container = self._global_container_stack.qualityChanges.getId())
|
||||
if isinstance(value, SettingFunction):
|
||||
value = value(self._global_container_stack)
|
||||
return value
|
||||
elif self._current_quality_group:
|
||||
value = self._global_container_stack.getRawProperty("layer_height", "value", skip_until_container = self._global_container_stack.quality.getId())
|
||||
if isinstance(value, SettingFunction):
|
||||
value = value(self._global_container_stack)
|
||||
return value
|
||||
return 0
|
||||
value = self._global_container_stack.getRawProperty("layer_height", "value", skip_until_container = self._global_container_stack.qualityChanges.getId())
|
||||
if isinstance(value, SettingFunction):
|
||||
value = value(self._global_container_stack)
|
||||
return value
|
||||
|
||||
@pyqtProperty(str, notify = activeVariantChanged)
|
||||
def globalVariantName(self) -> str:
|
||||
|
@ -656,27 +599,60 @@ class MachineManager(QObject):
|
|||
|
||||
@pyqtProperty(str, notify = activeQualityGroupChanged)
|
||||
def activeQualityType(self) -> str:
|
||||
quality_type = ""
|
||||
if self._active_container_stack:
|
||||
if self._current_quality_group:
|
||||
quality_type = self._current_quality_group.quality_type
|
||||
return quality_type
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if not global_stack:
|
||||
return ""
|
||||
return global_stack.quality.getMetaDataEntry("quality_type")
|
||||
|
||||
@pyqtProperty(bool, notify = activeQualityGroupChanged)
|
||||
def isActiveQualitySupported(self) -> bool:
|
||||
is_supported = False
|
||||
if self._global_container_stack:
|
||||
if self._current_quality_group:
|
||||
is_supported = self._current_quality_group.is_available
|
||||
return is_supported
|
||||
global_container_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if not global_container_stack:
|
||||
return False
|
||||
active_quality_group = self.activeQualityGroup()
|
||||
if active_quality_group is None:
|
||||
return False
|
||||
return active_quality_group.is_available
|
||||
|
||||
@pyqtProperty(bool, notify = activeQualityGroupChanged)
|
||||
def isActiveQualityExperimental(self) -> bool:
|
||||
is_experimental = False
|
||||
if self._global_container_stack:
|
||||
if self._current_quality_group:
|
||||
is_experimental = self._current_quality_group.is_experimental
|
||||
return is_experimental
|
||||
global_container_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if not global_container_stack:
|
||||
return False
|
||||
return Util.parseBool(global_container_stack.quality.getMetaDataEntry("is_experimental", False))
|
||||
|
||||
@pyqtProperty(str, notify = activeIntentChanged)
|
||||
def activeIntentCategory(self) -> str:
|
||||
global_container_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
|
||||
if not global_container_stack:
|
||||
return ""
|
||||
intent_category = "default"
|
||||
for extruder in global_container_stack.extruderList:
|
||||
category = extruder.intent.getMetaDataEntry("intent_category", "default")
|
||||
if category != "default" and category != intent_category:
|
||||
intent_category = category
|
||||
|
||||
return intent_category
|
||||
|
||||
# Provies a list of extruder positions that have a different intent from the active one.
|
||||
@pyqtProperty("QStringList", notify=activeIntentChanged)
|
||||
def extruderPositionsWithNonActiveIntent(self):
|
||||
global_container_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
|
||||
if not global_container_stack:
|
||||
return []
|
||||
|
||||
active_intent_category = self.activeIntentCategory
|
||||
result = []
|
||||
for extruder in global_container_stack.extruderList:
|
||||
if not extruder.isEnabled:
|
||||
continue
|
||||
category = extruder.intent.getMetaDataEntry("intent_category", "default")
|
||||
if category != active_intent_category:
|
||||
result.append(str(int(extruder.getMetaDataEntry("position")) + 1))
|
||||
|
||||
return result
|
||||
|
||||
## Returns whether there is anything unsupported in the current set-up.
|
||||
#
|
||||
|
@ -764,9 +740,10 @@ class MachineManager(QObject):
|
|||
# \returns DefinitionID (string) if found, empty string otherwise
|
||||
@pyqtProperty(str, notify = globalContainerChanged)
|
||||
def activeQualityDefinitionId(self) -> str:
|
||||
if self._global_container_stack:
|
||||
return getMachineDefinitionIDForQualitySearch(self._global_container_stack.definition)
|
||||
return ""
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if not global_stack:
|
||||
return ""
|
||||
return ContainerTree.getInstance().machines[global_stack.definition.getId()].quality_definition
|
||||
|
||||
## Gets how the active definition calls variants
|
||||
# Caveat: per-definition-variant-title is currently not translated (though the fallback is)
|
||||
|
@ -1074,19 +1051,19 @@ class MachineManager(QObject):
|
|||
self.forceUpdateAllSettings()
|
||||
# Also trigger the build plate compatibility to update
|
||||
self.activeMaterialChanged.emit()
|
||||
|
||||
def _onMachineNameChanged(self) -> None:
|
||||
self.globalContainerChanged.emit()
|
||||
self.activeIntentChanged.emit()
|
||||
|
||||
def _onMaterialNameChanged(self) -> None:
|
||||
self.activeMaterialChanged.emit()
|
||||
|
||||
## Get the signals that signal that the containers changed for all stacks.
|
||||
#
|
||||
# This includes the global stack and all extruder stacks. So if any
|
||||
# container changed anywhere.
|
||||
def _getContainerChangedSignals(self) -> List[Signal]:
|
||||
if self._global_container_stack is None:
|
||||
return []
|
||||
stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||
stacks.append(self._global_container_stack)
|
||||
return [ s.containersChanged for s in stacks ]
|
||||
return [s.containersChanged for s in ExtruderManager.getInstance().getActiveExtruderStacks() + [self._global_container_stack]]
|
||||
|
||||
@pyqtSlot(str, str, str)
|
||||
def setSettingForAllExtruders(self, setting_name: str, property_name: str, property_value: str) -> None:
|
||||
|
@ -1155,8 +1132,6 @@ class MachineManager(QObject):
|
|||
def _setEmptyQuality(self) -> None:
|
||||
if self._global_container_stack is None:
|
||||
return
|
||||
self._current_quality_group = None
|
||||
self._current_quality_changes_group = None
|
||||
self._global_container_stack.quality = empty_quality_container
|
||||
self._global_container_stack.qualityChanges = empty_quality_changes_container
|
||||
for extruder in self._global_container_stack.extruderList:
|
||||
|
@ -1165,6 +1140,7 @@ class MachineManager(QObject):
|
|||
|
||||
self.activeQualityGroupChanged.emit()
|
||||
self.activeQualityChangesGroupChanged.emit()
|
||||
self._updateIntentWithQuality()
|
||||
|
||||
def _setQualityGroup(self, quality_group: Optional["QualityGroup"], empty_quality_changes: bool = True) -> None:
|
||||
if self._global_container_stack is None:
|
||||
|
@ -1173,40 +1149,33 @@ class MachineManager(QObject):
|
|||
self._setEmptyQuality()
|
||||
return
|
||||
|
||||
if quality_group.node_for_global is None or quality_group.node_for_global.getContainer() is None:
|
||||
if quality_group.node_for_global is None or quality_group.node_for_global.container is None:
|
||||
return
|
||||
for node in quality_group.nodes_for_extruders.values():
|
||||
if node.getContainer() is None:
|
||||
if node.container is None:
|
||||
return
|
||||
|
||||
self._current_quality_group = quality_group
|
||||
if empty_quality_changes:
|
||||
self._current_quality_changes_group = None
|
||||
|
||||
# Set quality and quality_changes for the GlobalStack
|
||||
self._global_container_stack.quality = quality_group.node_for_global.getContainer()
|
||||
self._global_container_stack.quality = quality_group.node_for_global.container
|
||||
if empty_quality_changes:
|
||||
self._global_container_stack.qualityChanges = empty_quality_changes_container
|
||||
|
||||
# Set quality and quality_changes for each ExtruderStack
|
||||
for position, node in quality_group.nodes_for_extruders.items():
|
||||
try:
|
||||
self._global_container_stack.extruderList[int(position)].quality = node.getContainer()
|
||||
if empty_quality_changes:
|
||||
self._global_container_stack.extruderList[int(position)].qualityChanges = empty_quality_changes_container
|
||||
except IndexError:
|
||||
continue # This can be ignored as in some cases the quality group gets set for an extruder that's not active (eg custom fff printer)
|
||||
self._global_container_stack.extruders[str(position)].quality = node.container
|
||||
if empty_quality_changes:
|
||||
self._global_container_stack.extruders[str(position)].qualityChanges = empty_quality_changes_container
|
||||
|
||||
self.activeQualityGroupChanged.emit()
|
||||
self.activeQualityChangesGroupChanged.emit()
|
||||
self._updateIntentWithQuality()
|
||||
|
||||
def _fixQualityChangesGroupToNotSupported(self, quality_changes_group: "QualityChangesGroup") -> None:
|
||||
nodes = [quality_changes_group.node_for_global] + list(quality_changes_group.nodes_for_extruders.values())
|
||||
containers = [n.getContainer() for n in nodes if n is not None]
|
||||
for container in containers:
|
||||
if container:
|
||||
container.setMetaDataEntry("quality_type", "not_supported")
|
||||
metadatas = [quality_changes_group.metadata_for_global] + list(quality_changes_group.metadata_per_extruder.values())
|
||||
for metadata in metadatas:
|
||||
metadata["quality_type"] = "not_supported" # This actually changes the metadata of the container since they are stored by reference!
|
||||
quality_changes_group.quality_type = "not_supported"
|
||||
quality_changes_group.intent_category = "default"
|
||||
|
||||
def _setQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup") -> None:
|
||||
if self._global_container_stack is None:
|
||||
|
@ -1215,68 +1184,76 @@ class MachineManager(QObject):
|
|||
# A custom quality can be created based on "not supported".
|
||||
# In that case, do not set quality containers to empty.
|
||||
quality_group = None
|
||||
if quality_type != "not_supported":
|
||||
quality_group_dict = self._quality_manager.getQualityGroups(self._global_container_stack)
|
||||
quality_group = quality_group_dict.get(quality_type)
|
||||
if quality_type != "not_supported": # Find the quality group that the quality changes was based on.
|
||||
quality_group = ContainerTree.getInstance().getCurrentQualityGroups().get(quality_type)
|
||||
if quality_group is None:
|
||||
self._fixQualityChangesGroupToNotSupported(quality_changes_group)
|
||||
|
||||
container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry()
|
||||
quality_changes_container = empty_quality_changes_container
|
||||
quality_container = empty_quality_container # type: Optional[InstanceContainer]
|
||||
if quality_changes_group.node_for_global and quality_changes_group.node_for_global.getContainer():
|
||||
quality_changes_container = cast(InstanceContainer, quality_changes_group.node_for_global.getContainer())
|
||||
if quality_group is not None and quality_group.node_for_global and quality_group.node_for_global.getContainer():
|
||||
quality_container = quality_group.node_for_global.getContainer()
|
||||
quality_container = empty_quality_container # type: InstanceContainer
|
||||
if quality_changes_group.metadata_for_global:
|
||||
global_containers = container_registry.findContainers(id = quality_changes_group.metadata_for_global["id"])
|
||||
if global_containers:
|
||||
quality_changes_container = global_containers[0]
|
||||
if quality_changes_group.metadata_for_global:
|
||||
containers = container_registry.findContainers(id = quality_changes_group.metadata_for_global["id"])
|
||||
if containers:
|
||||
quality_changes_container = cast(InstanceContainer, containers[0])
|
||||
if quality_group is not None and quality_group.node_for_global and quality_group.node_for_global.container:
|
||||
quality_container = quality_group.node_for_global.container
|
||||
|
||||
self._global_container_stack.quality = quality_container
|
||||
self._global_container_stack.qualityChanges = quality_changes_container
|
||||
|
||||
for extruder in self._global_container_stack.extruderList:
|
||||
position = int(extruder.getMetaDataEntry("position", "0"))
|
||||
quality_changes_node = quality_changes_group.nodes_for_extruders.get(position)
|
||||
for position, extruder in self._global_container_stack.extruders.items():
|
||||
quality_node = None
|
||||
if quality_group is not None:
|
||||
quality_node = quality_group.nodes_for_extruders.get(position)
|
||||
quality_node = quality_group.nodes_for_extruders.get(int(position))
|
||||
|
||||
quality_changes_container = empty_quality_changes_container
|
||||
quality_container = empty_quality_container
|
||||
if quality_changes_node and quality_changes_node.getContainer():
|
||||
quality_changes_container = cast(InstanceContainer, quality_changes_node.getContainer())
|
||||
if quality_node and quality_node.getContainer():
|
||||
quality_container = quality_node.getContainer()
|
||||
quality_changes_metadata = quality_changes_group.metadata_per_extruder.get(int(position))
|
||||
if quality_changes_metadata:
|
||||
containers = container_registry.findContainers(id = quality_changes_metadata["id"])
|
||||
if containers:
|
||||
quality_changes_container = cast(InstanceContainer, containers[0])
|
||||
if quality_node and quality_node.container:
|
||||
quality_container = quality_node.container
|
||||
|
||||
extruder.quality = quality_container
|
||||
extruder.qualityChanges = quality_changes_container
|
||||
|
||||
self._current_quality_group = quality_group
|
||||
self._current_quality_changes_group = quality_changes_group
|
||||
self.setIntentByCategory(quality_changes_group.intent_category)
|
||||
|
||||
self.activeQualityGroupChanged.emit()
|
||||
self.activeQualityChangesGroupChanged.emit()
|
||||
|
||||
def _setVariantNode(self, position: str, container_node: "ContainerNode") -> None:
|
||||
if container_node.getContainer() is None or self._global_container_stack is None:
|
||||
def _setVariantNode(self, position: str, variant_node: "VariantNode") -> None:
|
||||
if self._global_container_stack is None:
|
||||
return
|
||||
self._global_container_stack.extruderList[int(position)].variant = container_node.getContainer()
|
||||
self._global_container_stack.extruders[position].variant = variant_node.container
|
||||
self.activeVariantChanged.emit()
|
||||
|
||||
def _setGlobalVariant(self, container_node: "ContainerNode") -> None:
|
||||
if self._global_container_stack is None:
|
||||
return
|
||||
self._global_container_stack.variant = container_node.getContainer()
|
||||
self._global_container_stack.variant = container_node.container
|
||||
if not self._global_container_stack.variant:
|
||||
self._global_container_stack.variant = self._application.empty_variant_container
|
||||
|
||||
def _setMaterial(self, position: str, container_node: Optional["ContainerNode"] = None) -> None:
|
||||
def _setMaterial(self, position: str, material_node: Optional["MaterialNode"] = None) -> None:
|
||||
if self._global_container_stack is None:
|
||||
return
|
||||
if container_node and container_node.getContainer():
|
||||
self._global_container_stack.extruderList[int(position)].material = container_node.getContainer()
|
||||
root_material_id = container_node.getMetaDataEntry("base_file", None)
|
||||
if material_node and material_node.container:
|
||||
material_container = material_node.container
|
||||
self._global_container_stack.extruders[position].material = material_container
|
||||
root_material_id = material_container.getMetaDataEntry("base_file", None)
|
||||
else:
|
||||
self._global_container_stack.extruderList[int(position)].material = empty_material_container
|
||||
root_material_id = None
|
||||
# The _current_root_material_id is used in the MaterialMenu to see which material is selected
|
||||
if root_material_id != self._current_root_material_id[position]:
|
||||
if position not in self._current_root_material_id or root_material_id != self._current_root_material_id[position]:
|
||||
self._current_root_material_id[position] = root_material_id
|
||||
self.rootMaterialChanged.emit()
|
||||
|
||||
|
@ -1293,13 +1270,12 @@ class MachineManager(QObject):
|
|||
|
||||
## Update current quality type and machine after setting material
|
||||
def _updateQualityWithMaterial(self, *args: Any) -> None:
|
||||
if self._global_container_stack is None:
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if global_stack is None:
|
||||
return
|
||||
Logger.log("d", "Updating quality/quality_changes due to material change")
|
||||
current_quality_type = None
|
||||
if self._current_quality_group:
|
||||
current_quality_type = self._current_quality_group.quality_type
|
||||
candidate_quality_groups = self._quality_manager.getQualityGroups(self._global_container_stack)
|
||||
current_quality_type = global_stack.quality.getMetaDataEntry("quality_type")
|
||||
candidate_quality_groups = ContainerTree.getInstance().getCurrentQualityGroups()
|
||||
available_quality_types = {qt for qt, g in candidate_quality_groups.items() if g.is_available}
|
||||
|
||||
Logger.log("d", "Current quality type = [%s]", current_quality_type)
|
||||
|
@ -1310,7 +1286,7 @@ class MachineManager(QObject):
|
|||
return
|
||||
|
||||
if not available_quality_types:
|
||||
if self._current_quality_changes_group is None:
|
||||
if global_stack.qualityChanges == empty_quality_changes_container:
|
||||
Logger.log("i", "No available quality types found, setting all qualities to empty (Not Supported).")
|
||||
self._setEmptyQuality()
|
||||
return
|
||||
|
@ -1323,6 +1299,9 @@ class MachineManager(QObject):
|
|||
# The current quality type is not available so we use the preferred quality type if it's available,
|
||||
# otherwise use one of the available quality types.
|
||||
quality_type = sorted(list(available_quality_types))[0]
|
||||
if self._global_container_stack is None:
|
||||
Logger.log("e", "Global stack not present!")
|
||||
return
|
||||
preferred_quality_type = self._global_container_stack.getMetaDataEntry("preferred_quality_type")
|
||||
if preferred_quality_type in available_quality_types:
|
||||
quality_type = preferred_quality_type
|
||||
|
@ -1331,6 +1310,38 @@ class MachineManager(QObject):
|
|||
current_quality_type, quality_type)
|
||||
self._setQualityGroup(candidate_quality_groups[quality_type], empty_quality_changes = True)
|
||||
|
||||
## Update the current intent after the quality changed
|
||||
def _updateIntentWithQuality(self):
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if global_stack is None:
|
||||
return
|
||||
Logger.log("d", "Updating intent due to quality change")
|
||||
|
||||
category = "default"
|
||||
|
||||
for extruder in global_stack.extruderList:
|
||||
if not extruder.isEnabled:
|
||||
continue
|
||||
current_category = extruder.intent.getMetaDataEntry("intent_category", "default")
|
||||
if current_category != "default" and current_category != category:
|
||||
category = current_category
|
||||
continue
|
||||
# It's also possible that the qualityChanges has an opinion about the intent_category.
|
||||
# This is in the case that a QC was made on an intent, but none of the materials have that intent.
|
||||
# If the user switches back, we do want the intent to be selected again.
|
||||
#
|
||||
# Do not ask empty quality changes for intent category.
|
||||
if extruder.qualityChanges.getId() == empty_quality_changes_container.getId():
|
||||
continue
|
||||
current_category = extruder.qualityChanges.getMetaDataEntry("intent_category", "default")
|
||||
if current_category != "default" and current_category != category:
|
||||
category = current_category
|
||||
self.setIntentByCategory(category)
|
||||
|
||||
## Update the material profile in the current stacks when the variant is
|
||||
# changed.
|
||||
# \param position The extruder stack to update. If provided with None, all
|
||||
# extruder stacks will be updated.
|
||||
def updateMaterialWithVariant(self, position: Optional[str]) -> None:
|
||||
if self._global_container_stack is None:
|
||||
return
|
||||
|
@ -1339,10 +1350,6 @@ class MachineManager(QObject):
|
|||
else:
|
||||
position_list = [position]
|
||||
|
||||
buildplate_name = None
|
||||
if self._global_container_stack.variant.getId() != "empty_variant":
|
||||
buildplate_name = self._global_container_stack.variant.getName()
|
||||
|
||||
for position_item in position_list:
|
||||
try:
|
||||
extruder = self._global_container_stack.extruderList[int(position_item)]
|
||||
|
@ -1350,39 +1357,35 @@ class MachineManager(QObject):
|
|||
continue
|
||||
|
||||
current_material_base_name = extruder.material.getMetaDataEntry("base_file")
|
||||
current_nozzle_name = None
|
||||
if extruder.variant.getId() != empty_variant_container.getId():
|
||||
current_nozzle_name = extruder.variant.getMetaDataEntry("name")
|
||||
current_nozzle_name = extruder.variant.getMetaDataEntry("name")
|
||||
|
||||
material_diameter = extruder.getCompatibleMaterialDiameter()
|
||||
candidate_materials = self._material_manager.getAvailableMaterials(
|
||||
self._global_container_stack.definition,
|
||||
current_nozzle_name,
|
||||
buildplate_name,
|
||||
material_diameter)
|
||||
# If we can keep the current material after the switch, try to do so.
|
||||
nozzle_node = ContainerTree.getInstance().machines[self._global_container_stack.definition.getId()].variants[current_nozzle_name]
|
||||
candidate_materials = nozzle_node.materials
|
||||
old_approximate_material_diameter = int(extruder.material.getMetaDataEntry("approximate_diameter", default = 3))
|
||||
new_approximate_material_diameter = int(self._global_container_stack.extruderList[int(position_item)].getApproximateMaterialDiameter())
|
||||
|
||||
if not candidate_materials:
|
||||
self._setMaterial(position_item, container_node = None)
|
||||
continue
|
||||
|
||||
if current_material_base_name in candidate_materials:
|
||||
# Only switch to the old candidate material if the approximate material diameter of the extruder stays the
|
||||
# same.
|
||||
if new_approximate_material_diameter == old_approximate_material_diameter and \
|
||||
current_material_base_name in candidate_materials: # The current material is also available after the switch. Retain it.
|
||||
new_material = candidate_materials[current_material_base_name]
|
||||
self._setMaterial(position_item, new_material)
|
||||
continue
|
||||
|
||||
# The current material is not available, find the preferred one
|
||||
material_node = self._material_manager.getDefaultMaterial(self._global_container_stack, position_item, current_nozzle_name)
|
||||
if material_node is not None:
|
||||
self._setMaterial(position_item, material_node)
|
||||
else:
|
||||
# The current material is not available, find the preferred one.
|
||||
if position is not None:
|
||||
approximate_material_diameter = int(self._global_container_stack.extruderList[int(position_item)].getApproximateMaterialDiameter())
|
||||
material_node = nozzle_node.preferredMaterial(approximate_material_diameter)
|
||||
self._setMaterial(position_item, material_node)
|
||||
|
||||
## Given a printer definition name, select the right machine instance. In case it doesn't exist, create a new
|
||||
# instance with the same network key.
|
||||
@pyqtSlot(str)
|
||||
def switchPrinterType(self, machine_name: str) -> None:
|
||||
Logger.log("i", "Attempting to switch the printer type to [%s]", machine_name)
|
||||
# Don't switch if the user tries to change to the same type of printer
|
||||
if self._global_container_stack is None or self.activeMachineDefinitionName == machine_name:
|
||||
return
|
||||
Logger.log("i", "Attempting to switch the printer type to [%s]", machine_name)
|
||||
# Get the definition id corresponding to this machine name
|
||||
machine_definition_id = CuraContainerRegistry.getInstance().findDefinitionContainers(name = machine_name)[0].getId()
|
||||
# Try to find a machine with the same network key
|
||||
|
@ -1412,6 +1415,7 @@ class MachineManager(QObject):
|
|||
if self._global_container_stack is None:
|
||||
return
|
||||
self.blurSettings.emit()
|
||||
container_registry = CuraContainerRegistry.getInstance()
|
||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||
self.switchPrinterType(configuration.printerType)
|
||||
|
||||
|
@ -1446,36 +1450,25 @@ class MachineManager(QObject):
|
|||
disabled_used_extruder_position_set.add(int(position))
|
||||
|
||||
else:
|
||||
variant_container_node = self._variant_manager.getVariantNode(self._global_container_stack.definition.getId(),
|
||||
extruder_configuration.hotendID)
|
||||
material_container_node = self._material_manager.getMaterialNodeByType(self._global_container_stack,
|
||||
position,
|
||||
extruder_configuration.hotendID,
|
||||
configuration.buildplateConfiguration,
|
||||
extruder_configuration.material.guid)
|
||||
if variant_container_node:
|
||||
self._setVariantNode(position, variant_container_node)
|
||||
else:
|
||||
self._global_container_stack.extruderList[int(position)].variant = empty_variant_container
|
||||
machine_node = ContainerTree.getInstance().machines.get(self._global_container_stack.definition.getId())
|
||||
variant_node = machine_node.variants.get(extruder_configuration.hotendID)
|
||||
self._setVariantNode(position, variant_node)
|
||||
|
||||
if material_container_node:
|
||||
self._setMaterial(position, material_container_node)
|
||||
else:
|
||||
self._global_container_stack.extruderList[int(position)].material = empty_material_container
|
||||
self._global_container_stack.extruderList[int(position)].setEnabled(True)
|
||||
# Find the material profile that the printer has stored.
|
||||
# This might find one of the duplicates if the user duplicated the material to sync with. But that's okay; both have this GUID so both are correct.
|
||||
approximate_diameter = int(self._global_container_stack.extruderList[int(position)].getApproximateMaterialDiameter())
|
||||
materials_with_guid = container_registry.findInstanceContainersMetadata(GUID = extruder_configuration.material.guid, approximate_diameter = str(approximate_diameter), ignore_case = True)
|
||||
material_container_node = variant_node.preferredMaterial(approximate_diameter)
|
||||
if materials_with_guid: # We also have the material profile that the printer wants to share.
|
||||
base_file = materials_with_guid[0]["base_file"]
|
||||
material_container_node = variant_node.materials.get(base_file, material_container_node)
|
||||
|
||||
self._setMaterial(position, material_container_node)
|
||||
self._global_container_stack.extruders[position].setEnabled(True)
|
||||
self.updateMaterialWithVariant(position)
|
||||
|
||||
self.updateDefaultExtruder()
|
||||
self.updateNumberExtrudersEnabled()
|
||||
|
||||
if configuration.buildplateConfiguration is not None:
|
||||
global_variant_container_node = self._variant_manager.getBuildplateVariantNode(self._global_container_stack.definition.getId(), configuration.buildplateConfiguration)
|
||||
if global_variant_container_node:
|
||||
self._setGlobalVariant(global_variant_container_node)
|
||||
else:
|
||||
self._global_container_stack.variant = empty_variant_container
|
||||
else:
|
||||
self._global_container_stack.variant = empty_variant_container
|
||||
self._updateQualityWithMaterial()
|
||||
|
||||
if need_to_show_message:
|
||||
|
@ -1509,17 +1502,12 @@ class MachineManager(QObject):
|
|||
def setMaterialById(self, position: str, root_material_id: str) -> None:
|
||||
if self._global_container_stack is None:
|
||||
return
|
||||
buildplate_name = None
|
||||
if self._global_container_stack.variant.getId() != "empty_variant":
|
||||
buildplate_name = self._global_container_stack.variant.getName()
|
||||
|
||||
machine_definition_id = self._global_container_stack.definition.id
|
||||
position = str(position)
|
||||
extruder_stack = self._global_container_stack.extruderList[int(position)]
|
||||
nozzle_name = extruder_stack.variant.getName()
|
||||
material_diameter = extruder_stack.getApproximateMaterialDiameter()
|
||||
material_node = self._material_manager.getMaterialNode(machine_definition_id, nozzle_name, buildplate_name,
|
||||
material_diameter, root_material_id)
|
||||
material_node = ContainerTree.getInstance().machines[machine_definition_id].variants[nozzle_name].materials[root_material_id]
|
||||
self.setMaterial(position, material_node)
|
||||
|
||||
## Global_stack: if you want to provide your own global_stack instead of the current active one
|
||||
|
@ -1527,7 +1515,7 @@ class MachineManager(QObject):
|
|||
@pyqtSlot(str, "QVariant")
|
||||
def setMaterial(self, position: str, container_node, global_stack: Optional["GlobalStack"] = None) -> None:
|
||||
if global_stack is not None and global_stack != self._global_container_stack:
|
||||
global_stack.extruderList[int(position)].material = container_node.getContainer()
|
||||
global_stack.extruders[position].material = container_node.container
|
||||
return
|
||||
position = str(position)
|
||||
self.blurSettings.emit()
|
||||
|
@ -1548,11 +1536,11 @@ class MachineManager(QObject):
|
|||
self.setVariant(position, variant_node)
|
||||
|
||||
@pyqtSlot(str, "QVariant")
|
||||
def setVariant(self, position: str, container_node: "ContainerNode") -> None:
|
||||
def setVariant(self, position: str, variant_node: "VariantNode") -> None:
|
||||
position = str(position)
|
||||
self.blurSettings.emit()
|
||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||
self._setVariantNode(position, container_node)
|
||||
self._setVariantNode(position, variant_node)
|
||||
self.updateMaterialWithVariant(position)
|
||||
self._updateQualityWithMaterial()
|
||||
|
||||
|
@ -1565,9 +1553,7 @@ class MachineManager(QObject):
|
|||
if self._global_container_stack is None:
|
||||
return
|
||||
# Get all the quality groups for this global stack and filter out by quality_type
|
||||
quality_group_dict = self._quality_manager.getQualityGroups(self._global_container_stack)
|
||||
quality_group = quality_group_dict[quality_type]
|
||||
self.setQualityGroup(quality_group)
|
||||
self.setQualityGroup(ContainerTree.getInstance().getCurrentQualityGroups()[quality_type])
|
||||
|
||||
## Optionally provide global_stack if you want to use your own
|
||||
# The active global_stack is treated differently.
|
||||
|
@ -1581,11 +1567,11 @@ class MachineManager(QObject):
|
|||
Logger.log("e", "Could not set quality group [%s] because it has no node_for_global", str(quality_group))
|
||||
return
|
||||
# This is not changing the quality for the active machine !!!!!!!!
|
||||
global_stack.quality = quality_group.node_for_global.getContainer()
|
||||
for extruder_nr, extruder_stack in global_stack.extruders.items():
|
||||
global_stack.quality = quality_group.node_for_global.container
|
||||
for extruder_nr, extruder_stack in enumerate(global_stack.extruderList):
|
||||
quality_container = empty_quality_container
|
||||
if extruder_nr in quality_group.nodes_for_extruders:
|
||||
container = quality_group.nodes_for_extruders[extruder_nr].getContainer()
|
||||
container = quality_group.nodes_for_extruders[extruder_nr].container
|
||||
quality_container = container if container is not None else quality_container
|
||||
extruder_stack.quality = quality_container
|
||||
return
|
||||
|
@ -1598,9 +1584,90 @@ class MachineManager(QObject):
|
|||
if not no_dialog and self.hasUserSettings and self._application.getPreferences().getValue("cura/active_mode") == 1:
|
||||
self._application.discardOrKeepProfileChanges()
|
||||
|
||||
@pyqtProperty(QObject, fset = setQualityGroup, notify = activeQualityGroupChanged)
|
||||
# The display name map of currently active quality.
|
||||
# The display name has 2 parts, a main part and a suffix part.
|
||||
# This display name is:
|
||||
# - For built-in qualities (quality/intent): the quality type name, such as "Fine", "Normal", etc.
|
||||
# - For custom qualities: <custom_quality_name> - <intent_name> - <quality_type_name>
|
||||
# Examples:
|
||||
# - "my_profile - Fine" (only based on a default quality, no intent involved)
|
||||
# - "my_profile - Engineering - Fine" (based on an intent)
|
||||
@pyqtProperty("QVariantMap", notify = activeQualityDisplayNameChanged)
|
||||
def activeQualityDisplayNameMap(self) -> Dict[str, str]:
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if global_stack is None:
|
||||
return {"main": "",
|
||||
"suffix": ""}
|
||||
|
||||
display_name = global_stack.quality.getName()
|
||||
|
||||
intent_category = self.activeIntentCategory
|
||||
if intent_category != "default":
|
||||
intent_display_name = IntentCategoryModel.name_translation.get(intent_category,
|
||||
catalog.i18nc("@label", "Unknown"))
|
||||
display_name = "{intent_name} - {the_rest}".format(intent_name = intent_display_name,
|
||||
the_rest = display_name)
|
||||
|
||||
main_part = display_name
|
||||
suffix_part = ""
|
||||
|
||||
# Not a custom quality
|
||||
if global_stack.qualityChanges != empty_quality_changes_container:
|
||||
main_part = self.activeQualityOrQualityChangesName
|
||||
suffix_part = display_name
|
||||
|
||||
return {"main": main_part,
|
||||
"suffix": suffix_part}
|
||||
|
||||
## Change the intent category of the current printer.
|
||||
#
|
||||
# All extruders can change their profiles. If an intent profile is
|
||||
# available with the desired intent category, that one will get chosen.
|
||||
# Otherwise the intent profile will be left to the empty profile, which
|
||||
# represents the "default" intent category.
|
||||
# \param intent_category The intent category to change to.
|
||||
@pyqtSlot(str)
|
||||
def setIntentByCategory(self, intent_category: str) -> None:
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if global_stack is None:
|
||||
return
|
||||
container_tree = ContainerTree.getInstance()
|
||||
for extruder in global_stack.extruderList:
|
||||
definition_id = global_stack.definition.getId()
|
||||
variant_name = extruder.variant.getName()
|
||||
material_base_file = extruder.material.getMetaDataEntry("base_file")
|
||||
quality_id = extruder.quality.getId()
|
||||
if quality_id == empty_quality_container.getId():
|
||||
extruder.intent = empty_intent_container
|
||||
continue
|
||||
quality_node = container_tree.machines[definition_id].variants[variant_name].materials[material_base_file].qualities[quality_id]
|
||||
|
||||
for intent_node in quality_node.intents.values():
|
||||
if intent_node.intent_category == intent_category: # Found an intent with the correct category.
|
||||
extruder.intent = intent_node.container
|
||||
break
|
||||
else: # No intent had the correct category.
|
||||
extruder.intent = empty_intent_container
|
||||
|
||||
## Get the currently activated quality group.
|
||||
#
|
||||
# If no printer is added yet or the printer doesn't have quality profiles,
|
||||
# this returns ``None``.
|
||||
# \return The currently active quality group.
|
||||
def activeQualityGroup(self) -> Optional["QualityGroup"]:
|
||||
return self._current_quality_group
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if not global_stack or global_stack.quality == empty_quality_container:
|
||||
return None
|
||||
return ContainerTree.getInstance().getCurrentQualityGroups().get(self.activeQualityType)
|
||||
|
||||
## Get the name of the active quality group.
|
||||
# \return The name of the active quality group.
|
||||
@pyqtProperty(str, notify = activeQualityGroupChanged)
|
||||
def activeQualityGroupName(self) -> str:
|
||||
quality_group = self.activeQualityGroup()
|
||||
if quality_group is None:
|
||||
return ""
|
||||
return quality_group.getName()
|
||||
|
||||
@pyqtSlot(QObject)
|
||||
def setQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup", no_dialog: bool = False) -> None:
|
||||
|
@ -1617,30 +1684,50 @@ class MachineManager(QObject):
|
|||
if self._global_container_stack is None:
|
||||
return
|
||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||
self._setQualityGroup(self._current_quality_group)
|
||||
self._setQualityGroup(self.activeQualityGroup())
|
||||
for stack in [self._global_container_stack] + list(self._global_container_stack.extruders.values()):
|
||||
stack.userChanges.clear()
|
||||
|
||||
@pyqtProperty(QObject, fset = setQualityChangesGroup, notify = activeQualityChangesGroupChanged)
|
||||
def activeQualityChangesGroup(self) -> Optional["QualityChangesGroup"]:
|
||||
return self._current_quality_changes_group
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if global_stack is None or global_stack.qualityChanges == empty_quality_changes_container:
|
||||
return None
|
||||
|
||||
all_group_list = ContainerTree.getInstance().getCurrentQualityChangesGroups()
|
||||
the_group = None
|
||||
for group in all_group_list: # Match on the container ID of the global stack to find the quality changes group belonging to the active configuration.
|
||||
if group.metadata_for_global and group.metadata_for_global["id"] == global_stack.qualityChanges.getId():
|
||||
the_group = group
|
||||
break
|
||||
|
||||
return the_group
|
||||
|
||||
@pyqtProperty(bool, notify = activeQualityChangesGroupChanged)
|
||||
def hasCustomQuality(self) -> bool:
|
||||
return self._current_quality_changes_group is not None
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
return global_stack is None or global_stack.qualityChanges != empty_quality_changes_container
|
||||
|
||||
@pyqtProperty(str, notify = activeQualityGroupChanged)
|
||||
def activeQualityOrQualityChangesName(self) -> str:
|
||||
name = empty_quality_container.getName()
|
||||
if self._current_quality_changes_group:
|
||||
name = self._current_quality_changes_group.name
|
||||
elif self._current_quality_group:
|
||||
name = self._current_quality_group.name
|
||||
return name
|
||||
global_container_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if not global_container_stack:
|
||||
return empty_quality_container.getName()
|
||||
if global_container_stack.qualityChanges != empty_quality_changes_container:
|
||||
return global_container_stack.qualityChanges.getName()
|
||||
return global_container_stack.quality.getName()
|
||||
|
||||
@pyqtProperty(bool, notify = activeQualityGroupChanged)
|
||||
def hasNotSupportedQuality(self) -> bool:
|
||||
return self._current_quality_group is None and self._current_quality_changes_group is None
|
||||
global_container_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
return (not global_container_stack is None) and global_container_stack.quality == empty_quality_container and global_container_stack.qualityChanges == empty_quality_changes_container
|
||||
|
||||
@pyqtProperty(bool, notify = activeQualityGroupChanged)
|
||||
def isActiveQualityCustom(self) -> bool:
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if global_stack is None:
|
||||
return False
|
||||
return global_stack.qualityChanges != empty_quality_changes_container
|
||||
|
||||
def _updateUponMaterialMetadataChange(self) -> None:
|
||||
if self._global_container_stack is None:
|
||||
|
@ -1667,7 +1754,3 @@ class MachineManager(QObject):
|
|||
abbr_machine += stripped_word
|
||||
|
||||
return abbr_machine
|
||||
|
||||
# Gets all machines that belong to the given group_id.
|
||||
def getMachinesInGroup(self, group_id: str) -> List["GlobalStack"]:
|
||||
return self._container_registry.findContainerStacks(type = "machine", group_id = group_id)
|
||||
|
|
|
@ -25,6 +25,9 @@ EMPTY_MATERIAL_CONTAINER_ID = "empty_material"
|
|||
empty_material_container = copy.deepcopy(empty_container)
|
||||
empty_material_container.setMetaDataEntry("id", EMPTY_MATERIAL_CONTAINER_ID)
|
||||
empty_material_container.setMetaDataEntry("type", "material")
|
||||
empty_material_container.setMetaDataEntry("base_file", "empty_material")
|
||||
empty_material_container.setMetaDataEntry("GUID", "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF")
|
||||
empty_material_container.setMetaDataEntry("material", "empty")
|
||||
|
||||
# Empty quality
|
||||
EMPTY_QUALITY_CONTAINER_ID = "empty_quality"
|
||||
|
@ -41,6 +44,15 @@ empty_quality_changes_container = copy.deepcopy(empty_container)
|
|||
empty_quality_changes_container.setMetaDataEntry("id", EMPTY_QUALITY_CHANGES_CONTAINER_ID)
|
||||
empty_quality_changes_container.setMetaDataEntry("type", "quality_changes")
|
||||
empty_quality_changes_container.setMetaDataEntry("quality_type", "not_supported")
|
||||
empty_quality_changes_container.setMetaDataEntry("intent_category", "not_supported")
|
||||
|
||||
# Empty intent
|
||||
EMPTY_INTENT_CONTAINER_ID = "empty_intent"
|
||||
empty_intent_container = copy.deepcopy(empty_container)
|
||||
empty_intent_container.setMetaDataEntry("id", EMPTY_INTENT_CONTAINER_ID)
|
||||
empty_intent_container.setMetaDataEntry("type", "intent")
|
||||
empty_intent_container.setMetaDataEntry("intent_category", "default")
|
||||
empty_intent_container.setName(catalog.i18nc("@info:No intent profile selected", "Default"))
|
||||
|
||||
|
||||
# All empty container IDs set
|
||||
|
@ -51,6 +63,7 @@ ALL_EMPTY_CONTAINER_ID_SET = {
|
|||
EMPTY_MATERIAL_CONTAINER_ID,
|
||||
EMPTY_QUALITY_CONTAINER_ID,
|
||||
EMPTY_QUALITY_CHANGES_CONTAINER_ID,
|
||||
EMPTY_INTENT_CONTAINER_ID
|
||||
}
|
||||
|
||||
|
||||
|
@ -73,4 +86,6 @@ __all__ = ["EMPTY_CONTAINER_ID",
|
|||
"empty_quality_container",
|
||||
"ALL_EMPTY_CONTAINER_ID_SET",
|
||||
"isEmptyContainer",
|
||||
"EMPTY_INTENT_CONTAINER_ID",
|
||||
"empty_intent_container"
|
||||
]
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSlot
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
|
@ -42,7 +43,7 @@ class MachineSettingsManager(QObject):
|
|||
# it was moved to the machine manager instead. Now this method just calls the machine manager.
|
||||
self._application.getMachineManager().setActiveMachineExtruderCount(extruder_count)
|
||||
|
||||
# Function for the Machine Settings panel (QML) to update after the usre changes "Number of Extruders".
|
||||
# Function for the Machine Settings panel (QML) to update after the user changes "Number of Extruders".
|
||||
#
|
||||
# fieldOfView: The Ultimaker 2 family (not 2+) does not have materials in Cura by default, because the material is
|
||||
# to be set on the printer. But when switching to Marlin flavor, the printer firmware can not change/insert material
|
||||
|
@ -51,8 +52,6 @@ class MachineSettingsManager(QObject):
|
|||
@pyqtSlot()
|
||||
def updateHasMaterialsMetadata(self):
|
||||
machine_manager = self._application.getMachineManager()
|
||||
material_manager = self._application.getMaterialManager()
|
||||
|
||||
global_stack = machine_manager.activeMachine
|
||||
|
||||
definition = global_stack.definition
|
||||
|
@ -76,7 +75,10 @@ class MachineSettingsManager(QObject):
|
|||
# set materials
|
||||
for position in extruder_positions:
|
||||
if has_materials:
|
||||
material_node = material_manager.getDefaultMaterial(global_stack, position, None)
|
||||
extruder = global_stack.extruderList[int(position)]
|
||||
approximate_diameter = extruder.getApproximateMaterialDiameter()
|
||||
variant_node = ContainerTree.getInstance().machines[global_stack.definition.getId()].variants[extruder.variant.getName()]
|
||||
material_node = variant_node.preferredMaterial(approximate_diameter)
|
||||
machine_manager.setMaterial(position, material_node)
|
||||
|
||||
self.forceUpdate()
|
||||
|
|
|
@ -19,12 +19,12 @@ from UM.Scene.SceneNode import SceneNode #For typing.
|
|||
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
|
||||
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
|
||||
from cura.Scene.ZOffsetDecorator import ZOffsetDecorator
|
||||
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
|
||||
|
||||
|
||||
try:
|
||||
|
@ -131,7 +131,7 @@ class ThreeMFReader(MeshReader):
|
|||
um_node.callDecoration("setActiveExtruder", default_stack.getId())
|
||||
|
||||
# Get the definition & set it
|
||||
definition_id = getMachineDefinitionIDForQualitySearch(global_container_stack.definition)
|
||||
definition_id = ContainerTree.getInstance().machines[global_container_stack.definition.getId()].quality_definition
|
||||
um_node.callDecoration("getStack").getTop().setDefinition(definition_id)
|
||||
|
||||
setting_container = um_node.callDecoration("getStack").getTop()
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from configparser import ConfigParser
|
||||
import zipfile
|
||||
import os
|
||||
from typing import Dict, List, Tuple, cast
|
||||
from typing import cast, Dict, List, Optional, Tuple
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
@ -14,7 +14,6 @@ from UM.Application import Application
|
|||
from UM.Logger import Logger
|
||||
from UM.Message import Message
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Signal import postponeSignals, CompressTechnique
|
||||
from UM.Settings.ContainerFormatError import ContainerFormatError
|
||||
from UM.Settings.ContainerStack import ContainerStack
|
||||
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||
|
@ -24,11 +23,12 @@ from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
|
|||
from UM.Job import Job
|
||||
from UM.Preferences import Preferences
|
||||
|
||||
from cura.Machines.VariantType import VariantType
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
from cura.Settings.CuraStackBuilder import CuraStackBuilder
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
from cura.Settings.ExtruderStack import ExtruderStack
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
from cura.Settings.IntentManager import IntentManager
|
||||
from cura.Settings.CuraContainerStack import _ContainerIndexes
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Utils.Threading import call_on_qt_thread
|
||||
|
@ -41,7 +41,7 @@ i18n_catalog = i18nCatalog("cura")
|
|||
|
||||
|
||||
class ContainerInfo:
|
||||
def __init__(self, file_name: str, serialized: str, parser: ConfigParser) -> None:
|
||||
def __init__(self, file_name: Optional[str], serialized: Optional[str], parser: Optional[ConfigParser]) -> None:
|
||||
self.file_name = file_name
|
||||
self.serialized = serialized
|
||||
self.parser = parser
|
||||
|
@ -65,6 +65,7 @@ class MachineInfo:
|
|||
self.metadata_dict = {} # type: Dict[str, str]
|
||||
|
||||
self.quality_type = None
|
||||
self.intent_category = None
|
||||
self.custom_quality_name = None
|
||||
self.quality_changes_info = None
|
||||
self.variant_info = None
|
||||
|
@ -84,6 +85,7 @@ class ExtruderInfo:
|
|||
|
||||
self.definition_changes_info = None
|
||||
self.user_changes_info = None
|
||||
self.intent_info = None
|
||||
|
||||
|
||||
## Base implementation for reading 3MF workspace files.
|
||||
|
@ -266,6 +268,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
|
||||
quality_name = ""
|
||||
custom_quality_name = ""
|
||||
intent_name = ""
|
||||
intent_category = ""
|
||||
num_settings_overridden_by_quality_changes = 0 # How many settings are changed by the quality changes
|
||||
num_user_settings = 0
|
||||
quality_changes_conflict = False
|
||||
|
@ -323,6 +327,10 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
elif container_type == "quality":
|
||||
if not quality_name:
|
||||
quality_name = parser["general"]["name"]
|
||||
elif container_type == "intent":
|
||||
if not intent_name:
|
||||
intent_name = parser["general"]["name"]
|
||||
intent_category = parser["metadata"]["intent_category"]
|
||||
elif container_type == "user":
|
||||
num_user_settings += len(parser["values"])
|
||||
elif container_type in self._ignored_instance_container_types:
|
||||
|
@ -348,6 +356,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
# To simplify this, only check if the global stack exists or not
|
||||
global_stack_id = self._stripFileToId(global_stack_file)
|
||||
serialized = archive.open(global_stack_file).read().decode("utf-8")
|
||||
serialized = GlobalStack._updateSerialized(serialized, global_stack_file)
|
||||
machine_name = self._getMachineNameFromSerializedStack(serialized)
|
||||
self._machine_info.metadata_dict = self._getMetaDataDictFromSerializedStack(serialized)
|
||||
|
||||
|
@ -444,6 +453,10 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
extruder_info.user_changes_info = instance_container_info_dict[user_changes_id]
|
||||
self._machine_info.extruder_info_dict[position] = extruder_info
|
||||
|
||||
intent_id = parser["containers"][str(_ContainerIndexes.Intent)]
|
||||
if intent_id not in ("empty", "empty_intent"):
|
||||
extruder_info.intent_info = instance_container_info_dict[intent_id]
|
||||
|
||||
if not machine_conflict and containers_found_dict["machine"]:
|
||||
if position not in global_stack.extruders:
|
||||
continue
|
||||
|
@ -508,6 +521,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
self._machine_info.definition_id = machine_definition_id
|
||||
self._machine_info.quality_type = quality_type
|
||||
self._machine_info.custom_quality_name = quality_name
|
||||
self._machine_info.intent_category = intent_category
|
||||
|
||||
if machine_conflict and not self._is_same_machine_type:
|
||||
machine_conflict = False
|
||||
|
@ -528,6 +542,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
self._dialog.setNumVisibleSettings(num_visible_settings)
|
||||
self._dialog.setQualityName(quality_name)
|
||||
self._dialog.setQualityType(quality_type)
|
||||
self._dialog.setIntentName(intent_name)
|
||||
self._dialog.setNumSettingsOverriddenByQualityChanges(num_settings_overridden_by_quality_changes)
|
||||
self._dialog.setNumUserSettings(num_user_settings)
|
||||
self._dialog.setActiveMode(active_mode)
|
||||
|
@ -571,26 +586,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
# \param file_name
|
||||
@call_on_qt_thread
|
||||
def read(self, file_name):
|
||||
container_registry = ContainerRegistry.getInstance()
|
||||
signals = [container_registry.containerAdded,
|
||||
container_registry.containerRemoved,
|
||||
container_registry.containerMetaDataChanged]
|
||||
#
|
||||
# We now have different managers updating their lookup tables upon container changes. It is critical to make
|
||||
# sure that the managers have a complete set of data when they update.
|
||||
#
|
||||
# In project loading, lots of the container-related signals are loosely emitted, which can create timing gaps
|
||||
# for incomplete data update or other kinds of issues to happen.
|
||||
#
|
||||
# To avoid this, we postpone all signals so they don't get emitted immediately. But, please also be aware that,
|
||||
# because of this, do not expect to have the latest data in the lookup tables in project loading.
|
||||
#
|
||||
with postponeSignals(*signals, compress = CompressTechnique.NoCompression):
|
||||
return self._read(file_name)
|
||||
|
||||
def _read(self, file_name):
|
||||
application = CuraApplication.getInstance()
|
||||
material_manager = application.getMaterialManager()
|
||||
|
||||
archive = zipfile.ZipFile(file_name, "r")
|
||||
|
||||
|
@ -688,7 +684,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
if self._resolve_strategies["material"] == "override":
|
||||
# Remove the old materials and then deserialize the one from the project
|
||||
root_material_id = material_container.getMetaDataEntry("base_file")
|
||||
material_manager.removeMaterialByRootId(root_material_id)
|
||||
application.getContainerRegistry().removeContainer(root_material_id)
|
||||
elif self._resolve_strategies["material"] == "new":
|
||||
# Note that we *must* deserialize it with a new ID, as multiple containers will be
|
||||
# auto created & added.
|
||||
|
@ -742,9 +738,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
if self._machine_info.quality_changes_info is None:
|
||||
return
|
||||
|
||||
application = CuraApplication.getInstance()
|
||||
quality_manager = application.getQualityManager()
|
||||
|
||||
# If we have custom profiles, load them
|
||||
quality_changes_name = self._machine_info.quality_changes_info.name
|
||||
if self._machine_info.quality_changes_info is not None:
|
||||
|
@ -752,12 +745,12 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
self._machine_info.quality_changes_info.name)
|
||||
|
||||
# Get the correct extruder definition IDs for quality changes
|
||||
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
|
||||
machine_definition_id_for_quality = getMachineDefinitionIDForQualitySearch(global_stack.definition)
|
||||
machine_definition_id_for_quality = ContainerTree.getInstance().machines[global_stack.definition.getId()].quality_definition
|
||||
machine_definition_for_quality = self._container_registry.findDefinitionContainers(id = machine_definition_id_for_quality)[0]
|
||||
|
||||
quality_changes_info = self._machine_info.quality_changes_info
|
||||
quality_changes_quality_type = quality_changes_info.global_info.parser["metadata"]["quality_type"]
|
||||
quality_changes_intent_category_per_extruder = {position: info.parser["metadata"].get("intent_category", "default") for position, info in quality_changes_info.extruder_info_dict.items()}
|
||||
|
||||
quality_changes_name = quality_changes_info.name
|
||||
create_new = self._resolve_strategies.get("quality_changes") != "override"
|
||||
|
@ -768,13 +761,12 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
quality_changes_name = self._container_registry.uniqueName(quality_changes_name)
|
||||
for position, container_info in container_info_dict.items():
|
||||
extruder_stack = None
|
||||
intent_category = None # type: Optional[str]
|
||||
if position is not None:
|
||||
extruder_stack = global_stack.extruders[position]
|
||||
container = quality_manager._createQualityChanges(quality_changes_quality_type,
|
||||
quality_changes_name,
|
||||
global_stack, extruder_stack)
|
||||
intent_category = quality_changes_intent_category_per_extruder[position]
|
||||
container = self._createNewQualityChanges(quality_changes_quality_type, intent_category, quality_changes_name, global_stack, extruder_stack)
|
||||
container_info.container = container
|
||||
container.setDirty(True)
|
||||
self._container_registry.addContainer(container)
|
||||
|
||||
Logger.log("d", "Created new quality changes container [%s]", container.getId())
|
||||
|
@ -802,11 +794,10 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
if not global_stack.extruders:
|
||||
ExtruderManager.getInstance().fixSingleExtrusionMachineExtruderDefinition(global_stack)
|
||||
extruder_stack = global_stack.extruders["0"]
|
||||
intent_category = quality_changes_intent_category_per_extruder["0"]
|
||||
|
||||
container = quality_manager._createQualityChanges(quality_changes_quality_type, quality_changes_name,
|
||||
global_stack, extruder_stack)
|
||||
container = self._createNewQualityChanges(quality_changes_quality_type, intent_category, quality_changes_name, global_stack, extruder_stack)
|
||||
container_info.container = container
|
||||
container.setDirty(True)
|
||||
self._container_registry.addContainer(container)
|
||||
|
||||
Logger.log("d", "Created new quality changes container [%s]", container.getId())
|
||||
|
@ -832,10 +823,9 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
|
||||
if container_info.container is None:
|
||||
extruder_stack = global_stack.extruders[position]
|
||||
container = quality_manager._createQualityChanges(quality_changes_quality_type, quality_changes_name,
|
||||
global_stack, extruder_stack)
|
||||
intent_category = quality_changes_intent_category_per_extruder[position]
|
||||
container = self._createNewQualityChanges(quality_changes_quality_type, intent_category, quality_changes_name, global_stack, extruder_stack)
|
||||
container_info.container = container
|
||||
container.setDirty(True)
|
||||
self._container_registry.addContainer(container)
|
||||
|
||||
for key, value in container_info.parser["values"].items():
|
||||
|
@ -843,6 +833,45 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
|
||||
self._machine_info.quality_changes_info.name = quality_changes_name
|
||||
|
||||
## Helper class to create a new quality changes profile.
|
||||
#
|
||||
# This will then later be filled with the appropriate data.
|
||||
# \param quality_type The quality type of the new profile.
|
||||
# \param intent_category The intent category of the new profile.
|
||||
# \param name The name for the profile. This will later be made unique so
|
||||
# it doesn't need to be unique yet.
|
||||
# \param global_stack The global stack showing the configuration that the
|
||||
# profile should be created for.
|
||||
# \param extruder_stack The extruder stack showing the configuration that
|
||||
# the profile should be created for. If this is None, it will be created
|
||||
# for the global stack.
|
||||
def _createNewQualityChanges(self, quality_type: str, intent_category: Optional[str], name: str, global_stack: GlobalStack, extruder_stack: Optional[ExtruderStack]) -> InstanceContainer:
|
||||
container_registry = CuraApplication.getInstance().getContainerRegistry()
|
||||
base_id = global_stack.definition.getId() if extruder_stack is None else extruder_stack.getId()
|
||||
new_id = base_id + "_" + name
|
||||
new_id = new_id.lower().replace(" ", "_")
|
||||
new_id = container_registry.uniqueName(new_id)
|
||||
|
||||
# Create a new quality_changes container for the quality.
|
||||
quality_changes = InstanceContainer(new_id)
|
||||
quality_changes.setName(name)
|
||||
quality_changes.setMetaDataEntry("type", "quality_changes")
|
||||
quality_changes.setMetaDataEntry("quality_type", quality_type)
|
||||
if intent_category is not None:
|
||||
quality_changes.setMetaDataEntry("intent_category", intent_category)
|
||||
|
||||
# If we are creating a container for an extruder, ensure we add that to the container.
|
||||
if extruder_stack is not None:
|
||||
quality_changes.setMetaDataEntry("position", extruder_stack.getMetaDataEntry("position"))
|
||||
|
||||
# If the machine specifies qualities should be filtered, ensure we match the current criteria.
|
||||
machine_definition_id = ContainerTree.getInstance().machines[global_stack.definition.getId()].quality_definition
|
||||
quality_changes.setDefinition(machine_definition_id)
|
||||
|
||||
quality_changes.setMetaDataEntry("setting_version", CuraApplication.getInstance().SettingVersion)
|
||||
quality_changes.setDirty(True)
|
||||
return quality_changes
|
||||
|
||||
@staticmethod
|
||||
def _clearStack(stack):
|
||||
application = CuraApplication.getInstance()
|
||||
|
@ -903,45 +932,30 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
extruder_stack.userChanges.setProperty(key, "value", value)
|
||||
|
||||
def _applyVariants(self, global_stack, extruder_stack_dict):
|
||||
application = CuraApplication.getInstance()
|
||||
variant_manager = application.getVariantManager()
|
||||
machine_node = ContainerTree.getInstance().machines[global_stack.definition.getId()]
|
||||
|
||||
# Take the global variant from the machine info if available.
|
||||
if self._machine_info.variant_info is not None:
|
||||
parser = self._machine_info.variant_info.parser
|
||||
variant_name = parser["general"]["name"]
|
||||
|
||||
variant_type = VariantType.BUILD_PLATE
|
||||
|
||||
node = variant_manager.getVariantNode(global_stack.definition.getId(), variant_name, variant_type)
|
||||
if node is not None and node.getContainer() is not None:
|
||||
global_stack.variant = node.getContainer()
|
||||
variant_name = self._machine_info.variant_info.parser["general"]["name"]
|
||||
if variant_name in machine_node.variants:
|
||||
global_stack.variant = machine_node.variants[variant_name].container
|
||||
else:
|
||||
Logger.log("w", "Could not find global variant '{0}'.".format(variant_name))
|
||||
|
||||
for position, extruder_stack in extruder_stack_dict.items():
|
||||
if position not in self._machine_info.extruder_info_dict:
|
||||
continue
|
||||
extruder_info = self._machine_info.extruder_info_dict[position]
|
||||
if extruder_info.variant_info is None:
|
||||
# If there is no variant_info, try to use the default variant. Otherwise, leave it be.
|
||||
node = variant_manager.getDefaultVariantNode(global_stack.definition, VariantType.NOZZLE, global_stack)
|
||||
if node is not None and node.getContainer() is not None:
|
||||
extruder_stack.variant = node.getContainer()
|
||||
continue
|
||||
parser = extruder_info.variant_info.parser
|
||||
|
||||
variant_name = parser["general"]["name"]
|
||||
variant_type = VariantType.NOZZLE
|
||||
|
||||
node = variant_manager.getVariantNode(global_stack.definition.getId(), variant_name, variant_type)
|
||||
if node is not None and node.getContainer() is not None:
|
||||
extruder_stack.variant = node.getContainer()
|
||||
# If there is no variant_info, try to use the default variant. Otherwise, any available variant.
|
||||
node = machine_node.variants.get(machine_node.preferred_variant_name, next(iter(machine_node.variants.values())))
|
||||
else:
|
||||
variant_name = extruder_info.variant_info.parser["general"]["name"]
|
||||
node = ContainerTree.getInstance().machines[global_stack.definition.getId()].variants[variant_name]
|
||||
extruder_stack.variant = node.container
|
||||
|
||||
def _applyMaterials(self, global_stack, extruder_stack_dict):
|
||||
application = CuraApplication.getInstance()
|
||||
material_manager = application.getMaterialManager()
|
||||
|
||||
# Force update lookup tables first
|
||||
material_manager.initialize()
|
||||
|
||||
machine_node = ContainerTree.getInstance().machines[global_stack.definition.getId()]
|
||||
for position, extruder_stack in extruder_stack_dict.items():
|
||||
if position not in self._machine_info.extruder_info_dict:
|
||||
continue
|
||||
|
@ -952,18 +966,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
root_material_id = extruder_info.root_material_id
|
||||
root_material_id = self._old_new_materials.get(root_material_id, root_material_id)
|
||||
|
||||
build_plate_id = global_stack.variant.getId()
|
||||
|
||||
# get material diameter of this extruder
|
||||
machine_material_diameter = extruder_stack.getCompatibleMaterialDiameter()
|
||||
material_node = material_manager.getMaterialNode(global_stack.definition.getId(),
|
||||
extruder_stack.variant.getName(),
|
||||
build_plate_id,
|
||||
machine_material_diameter,
|
||||
root_material_id)
|
||||
|
||||
if material_node is not None and material_node.getContainer() is not None:
|
||||
extruder_stack.material = material_node.getContainer() # type: InstanceContainer
|
||||
material_node = machine_node.variants[extruder_stack.variant.getName()].materials[root_material_id]
|
||||
extruder_stack.material = material_node.container # type: InstanceContainer
|
||||
|
||||
def _applyChangesToMachine(self, global_stack, extruder_stack_dict):
|
||||
# Clear all first
|
||||
|
@ -979,10 +983,12 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
# prepare the quality to select
|
||||
self._quality_changes_to_apply = None
|
||||
self._quality_type_to_apply = None
|
||||
self._intent_category_to_apply = None
|
||||
if self._machine_info.quality_changes_info is not None:
|
||||
self._quality_changes_to_apply = self._machine_info.quality_changes_info.name
|
||||
else:
|
||||
self._quality_type_to_apply = self._machine_info.quality_type
|
||||
self._intent_category_to_apply = self._machine_info.intent_category
|
||||
|
||||
# Set enabled/disabled for extruders
|
||||
for position, extruder_stack in extruder_stack_dict.items():
|
||||
|
@ -1001,12 +1007,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
def _updateActiveMachine(self, global_stack):
|
||||
# Actually change the active machine.
|
||||
machine_manager = Application.getInstance().getMachineManager()
|
||||
material_manager = Application.getInstance().getMaterialManager()
|
||||
quality_manager = Application.getInstance().getQualityManager()
|
||||
|
||||
# Force update the lookup maps first
|
||||
material_manager.initialize()
|
||||
quality_manager.initialize()
|
||||
container_tree = ContainerTree.getInstance()
|
||||
|
||||
machine_manager.setActiveMachine(global_stack.getId())
|
||||
|
||||
|
@ -1016,21 +1017,20 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
global_stack.setMetaDataEntry(key, value)
|
||||
|
||||
if self._quality_changes_to_apply:
|
||||
quality_changes_group_dict = quality_manager.getQualityChangesGroups(global_stack)
|
||||
if self._quality_changes_to_apply not in quality_changes_group_dict:
|
||||
quality_changes_group_list = container_tree.getCurrentQualityChangesGroups()
|
||||
quality_changes_group = next((qcg for qcg in quality_changes_group_list if qcg.name == self._quality_changes_to_apply), None)
|
||||
if not quality_changes_group:
|
||||
Logger.log("e", "Could not find quality_changes [%s]", self._quality_changes_to_apply)
|
||||
return
|
||||
quality_changes_group = quality_changes_group_dict[self._quality_changes_to_apply]
|
||||
machine_manager.setQualityChangesGroup(quality_changes_group, no_dialog = True)
|
||||
else:
|
||||
self._quality_type_to_apply = self._quality_type_to_apply.lower()
|
||||
quality_group_dict = quality_manager.getQualityGroups(global_stack)
|
||||
quality_group_dict = container_tree.getCurrentQualityGroups()
|
||||
if self._quality_type_to_apply in quality_group_dict:
|
||||
quality_group = quality_group_dict[self._quality_type_to_apply]
|
||||
else:
|
||||
Logger.log("i", "Could not find quality type [%s], switch to default", self._quality_type_to_apply)
|
||||
preferred_quality_type = global_stack.getMetaDataEntry("preferred_quality_type")
|
||||
quality_group_dict = quality_manager.getQualityGroups(global_stack)
|
||||
quality_group = quality_group_dict.get(preferred_quality_type)
|
||||
if quality_group is None:
|
||||
Logger.log("e", "Could not get preferred quality type [%s]", preferred_quality_type)
|
||||
|
@ -1038,6 +1038,11 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
if quality_group is not None:
|
||||
machine_manager.setQualityGroup(quality_group, no_dialog = True)
|
||||
|
||||
# Also apply intent if available
|
||||
available_intent_category_list = IntentManager.getInstance().currentAvailableIntentCategories()
|
||||
if self._intent_category_to_apply is not None and self._intent_category_to_apply in available_intent_category_list:
|
||||
machine_manager.setIntentByCategory(self._intent_category_to_apply)
|
||||
|
||||
# Notify everything/one that is to notify about changes.
|
||||
global_stack.containersChanged.emit(global_stack.getTop())
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ class WorkspaceDialog(QObject):
|
|||
self._quality_name = ""
|
||||
self._num_settings_overridden_by_quality_changes = 0
|
||||
self._quality_type = ""
|
||||
self._intent_name = ""
|
||||
self._machine_name = ""
|
||||
self._machine_type = ""
|
||||
self._variant_type = ""
|
||||
|
@ -60,6 +61,7 @@ class WorkspaceDialog(QObject):
|
|||
hasVisibleSettingsFieldChanged = pyqtSignal()
|
||||
numSettingsOverridenByQualityChangesChanged = pyqtSignal()
|
||||
qualityTypeChanged = pyqtSignal()
|
||||
intentNameChanged = pyqtSignal()
|
||||
machineNameChanged = pyqtSignal()
|
||||
materialLabelsChanged = pyqtSignal()
|
||||
objectsOnPlateChanged = pyqtSignal()
|
||||
|
@ -166,6 +168,15 @@ class WorkspaceDialog(QObject):
|
|||
self._quality_name = quality_name
|
||||
self.qualityNameChanged.emit()
|
||||
|
||||
@pyqtProperty(str, notify = intentNameChanged)
|
||||
def intentName(self) -> str:
|
||||
return self._intent_name
|
||||
|
||||
def setIntentName(self, intent_name: str) -> None:
|
||||
if self._intent_name != intent_name:
|
||||
self._intent_name = intent_name
|
||||
self.intentNameChanged.emit()
|
||||
|
||||
@pyqtProperty(str, notify=activeModeChanged)
|
||||
def activeMode(self):
|
||||
return self._active_mode
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
// Copyright (c) 2016 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.1
|
||||
import QtQuick.Controls 1.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Window 2.1
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Window 2.2
|
||||
|
||||
import UM 1.1 as UM
|
||||
|
||||
|
@ -24,7 +24,7 @@ UM.Dialog
|
|||
onClosing: manager.notifyClosed()
|
||||
onVisibleChanged:
|
||||
{
|
||||
if(visible)
|
||||
if (visible)
|
||||
{
|
||||
machineResolveComboBox.currentIndex = 0
|
||||
qualityChangesResolveComboBox.currentIndex = 0
|
||||
|
@ -223,6 +223,21 @@ UM.Dialog
|
|||
}
|
||||
}
|
||||
Row
|
||||
{
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@action:label", "Intent")
|
||||
width: (parent.width / 3) | 0
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: manager.intentName
|
||||
width: (parent.width / 3) | 0
|
||||
}
|
||||
}
|
||||
Row
|
||||
{
|
||||
width: parent.width
|
||||
height: manager.numUserSettings != 0 ? childrenRect.height : 0
|
||||
|
|
|
@ -8,7 +8,7 @@ from UM.Logger import Logger
|
|||
from UM.Settings.ContainerFormatError import ContainerFormatError
|
||||
from UM.Settings.InstanceContainer import InstanceContainer # The new profile to make.
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
from cura.ReaderWriters.ProfileReader import ProfileReader
|
||||
|
||||
import zipfile
|
||||
|
@ -97,7 +97,7 @@ class CuraProfileReader(ProfileReader):
|
|||
if global_stack is None:
|
||||
return None
|
||||
|
||||
active_quality_definition = getMachineDefinitionIDForQualitySearch(global_stack.definition)
|
||||
active_quality_definition = ContainerTree.getInstance().machines[global_stack.definition.getId()].quality_definition
|
||||
if profile.getMetaDataEntry("definition") != active_quality_definition:
|
||||
profile.setMetaDataEntry("definition", active_quality_definition)
|
||||
return profile
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import re # For escaping characters in the settings.
|
||||
|
@ -9,8 +9,7 @@ from UM.Mesh.MeshWriter import MeshWriter
|
|||
from UM.Logger import Logger
|
||||
from UM.Application import Application
|
||||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
|
||||
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
@ -117,17 +116,24 @@ class GCodeWriter(MeshWriter):
|
|||
# \return A serialised string of the settings.
|
||||
def _serialiseSettings(self, stack):
|
||||
container_registry = self._application.getContainerRegistry()
|
||||
quality_manager = self._application.getQualityManager()
|
||||
|
||||
prefix = self._setting_keyword + str(GCodeWriter.version) + " " # The prefix to put before each line.
|
||||
prefix_length = len(prefix)
|
||||
|
||||
quality_type = stack.quality.getMetaDataEntry("quality_type")
|
||||
container_with_profile = stack.qualityChanges
|
||||
machine_definition_id_for_quality = ContainerTree.getInstance().machines[stack.definition.getId()].quality_definition
|
||||
if container_with_profile.getId() == "empty_quality_changes":
|
||||
# If the global quality changes is empty, create a new one
|
||||
quality_name = container_registry.uniqueName(stack.quality.getName())
|
||||
container_with_profile = quality_manager._createQualityChanges(quality_type, quality_name, stack, None)
|
||||
quality_id = container_registry.uniqueName((stack.definition.getId() + "_" + quality_name).lower().replace(" ", "_"))
|
||||
container_with_profile = InstanceContainer(quality_id)
|
||||
container_with_profile.setName(quality_name)
|
||||
container_with_profile.setMetaDataEntry("type", "quality_changes")
|
||||
container_with_profile.setMetaDataEntry("quality_type", quality_type)
|
||||
if stack.getMetaDataEntry("position") is not None: # For extruder stacks, the quality changes should include an intent category.
|
||||
container_with_profile.setMetaDataEntry("intent_category", stack.intent.getMetaDataEntry("intent_category", "default"))
|
||||
container_with_profile.setDefinition(machine_definition_id_for_quality)
|
||||
|
||||
flat_global_container = self._createFlattenedContainerInstance(stack.userChanges, container_with_profile)
|
||||
# If the quality changes is not set, we need to set type manually
|
||||
|
@ -139,7 +145,6 @@ class GCodeWriter(MeshWriter):
|
|||
flat_global_container.setMetaDataEntry("quality_type", stack.quality.getMetaDataEntry("quality_type", "normal"))
|
||||
|
||||
# Get the machine definition ID for quality profiles
|
||||
machine_definition_id_for_quality = getMachineDefinitionIDForQualitySearch(stack.definition)
|
||||
flat_global_container.setMetaDataEntry("definition", machine_definition_id_for_quality)
|
||||
|
||||
serialized = flat_global_container.serialize()
|
||||
|
@ -151,7 +156,12 @@ class GCodeWriter(MeshWriter):
|
|||
if extruder_quality.getId() == "empty_quality_changes":
|
||||
# Same story, if quality changes is empty, create a new one
|
||||
quality_name = container_registry.uniqueName(stack.quality.getName())
|
||||
extruder_quality = quality_manager._createQualityChanges(quality_type, quality_name, stack, None)
|
||||
quality_id = container_registry.uniqueName((stack.definition.getId() + "_" + quality_name).lower().replace(" ", "_"))
|
||||
extruder_quality = InstanceContainer(quality_id)
|
||||
extruder_quality.setName(quality_name)
|
||||
extruder_quality.setMetaDataEntry("type", "quality_changes")
|
||||
extruder_quality.setMetaDataEntry("quality_type", quality_type)
|
||||
extruder_quality.setDefinition(machine_definition_id_for_quality)
|
||||
|
||||
flat_extruder_quality = self._createFlattenedContainerInstance(extruder.userChanges, extruder_quality)
|
||||
# If the quality changes is not set, we need to set type manually
|
||||
|
|
|
@ -11,7 +11,9 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
|
|||
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||
from UM.Util import parseBool
|
||||
|
||||
import cura.CuraApplication # Imported like this to prevent circular dependencies.
|
||||
from cura.MachineAction import MachineAction
|
||||
from cura.Machines.ContainerTree import ContainerTree # To re-build the machine node when hasMaterials changes.
|
||||
from cura.Settings.CuraStackBuilder import CuraStackBuilder
|
||||
from cura.Settings.cura_empty_instance_containers import isEmptyContainer
|
||||
|
||||
|
@ -41,6 +43,9 @@ class MachineSettingsAction(MachineAction):
|
|||
self._backend = self._application.getBackend()
|
||||
self.onFinished.connect(self._onFinished)
|
||||
|
||||
# If the g-code flavour changes between UltiGCode and another flavour, we need to update the container tree.
|
||||
self._application.globalContainerStackChanged.connect(self._updateHasMaterialsInContainerTree)
|
||||
|
||||
# Which container index in a stack to store machine setting changes.
|
||||
@pyqtProperty(int, constant = True)
|
||||
def storeContainerIndex(self) -> int:
|
||||
|
@ -51,6 +56,18 @@ class MachineSettingsAction(MachineAction):
|
|||
if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine":
|
||||
self._application.getMachineActionManager().addSupportedAction(container.getId(), self.getKey())
|
||||
|
||||
## Triggered when the global container stack changes or when the g-code
|
||||
# flavour setting is changed.
|
||||
def _updateHasMaterialsInContainerTree(self) -> None:
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if global_stack is None:
|
||||
return
|
||||
machine_node = ContainerTree.getInstance().machines[global_stack.definition.getId()]
|
||||
|
||||
if machine_node.has_materials != parseBool(global_stack.getMetaDataEntry("has_materials")): # May have changed due to the g-code flavour.
|
||||
machine_node.has_materials = parseBool(global_stack.getMetaDataEntry("has_materials"))
|
||||
machine_node._loadAll()
|
||||
|
||||
def _reset(self):
|
||||
global_stack = self._application.getMachineManager().activeMachine
|
||||
if not global_stack:
|
||||
|
@ -98,11 +115,8 @@ class MachineSettingsAction(MachineAction):
|
|||
return
|
||||
|
||||
machine_manager = self._application.getMachineManager()
|
||||
material_manager = self._application.getMaterialManager()
|
||||
extruder_positions = list(global_stack.extruders.keys())
|
||||
has_materials = global_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode"
|
||||
|
||||
material_node = None
|
||||
if has_materials:
|
||||
global_stack.setMetaDataEntry("has_materials", True)
|
||||
else:
|
||||
|
@ -111,11 +125,15 @@ class MachineSettingsAction(MachineAction):
|
|||
if "has_materials" in global_stack.getMetaData():
|
||||
global_stack.removeMetaDataEntry("has_materials")
|
||||
|
||||
self._updateHasMaterialsInContainerTree()
|
||||
|
||||
# set materials
|
||||
for position in extruder_positions:
|
||||
if has_materials:
|
||||
material_node = material_manager.getDefaultMaterial(global_stack, position, None)
|
||||
machine_manager.setMaterial(position, material_node)
|
||||
machine_node = ContainerTree.getInstance().machines[global_stack.definition.getId()]
|
||||
for position, extruder in enumerate(global_stack.extruderList):
|
||||
#Find out what material we need to default to.
|
||||
approximate_diameter = round(extruder.getProperty("material_diameter", "value"))
|
||||
material_node = machine_node.variants[extruder.variant.getName()].preferredMaterial(approximate_diameter)
|
||||
machine_manager.setMaterial(str(position), material_node)
|
||||
|
||||
self._application.globalContainerStackChanged.emit()
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import json
|
||||
|
@ -19,6 +19,7 @@ from UM.Version import Version
|
|||
from cura import ApplicationMetadata
|
||||
from cura import UltimakerCloudAuthentication
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
|
||||
from .AuthorsModel import AuthorsModel
|
||||
from .PackagesModel import PackagesModel
|
||||
|
@ -359,15 +360,22 @@ class Toolbox(QObject, Extension):
|
|||
@pyqtSlot()
|
||||
def resetMaterialsQualitiesAndUninstall(self) -> None:
|
||||
application = CuraApplication.getInstance()
|
||||
material_manager = application.getMaterialManager()
|
||||
quality_manager = application.getQualityManager()
|
||||
machine_manager = application.getMachineManager()
|
||||
container_tree = ContainerTree.getInstance()
|
||||
|
||||
for global_stack, extruder_nr, container_id in self._package_used_materials:
|
||||
default_material_node = material_manager.getDefaultMaterial(global_stack, extruder_nr, global_stack.extruders[extruder_nr].variant.getName())
|
||||
extruder = global_stack.extruderList[int(extruder_nr)]
|
||||
approximate_diameter = extruder.getApproximateMaterialDiameter()
|
||||
variant_node = container_tree.machines[global_stack.definition.getId()].variants[extruder.variant.getName()]
|
||||
default_material_node = variant_node.preferredMaterial(approximate_diameter)
|
||||
machine_manager.setMaterial(extruder_nr, default_material_node, global_stack = global_stack)
|
||||
for global_stack, extruder_nr, container_id in self._package_used_qualities:
|
||||
default_quality_group = quality_manager.getDefaultQualityType(global_stack)
|
||||
variant_names = [extruder.variant.getName() for extruder in global_stack.extruderList]
|
||||
material_bases = [extruder.material.getMetaDataEntry("base_file") for extruder in global_stack.extruderList]
|
||||
extruder_enabled = [extruder.isEnabled for extruder in global_stack.extruderList]
|
||||
definition_id = global_stack.definition.getId()
|
||||
machine_node = container_tree.machines[definition_id]
|
||||
default_quality_group = machine_node.getQualityGroups(variant_names, material_bases, extruder_enabled)[machine_node.preferred_quality_type]
|
||||
machine_manager.setQualityGroup(default_quality_group, global_stack = global_stack)
|
||||
|
||||
if self._package_id_to_uninstall is not None:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#Copyright (c) 2018 Ultimaker B.V.
|
||||
#Copyright (c) 2019 Ultimaker B.V.
|
||||
#Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import cast
|
||||
|
@ -7,13 +7,13 @@ from Charon.VirtualFile import VirtualFile #To open UFP files.
|
|||
from Charon.OpenMode import OpenMode #To indicate that we want to write to UFP files.
|
||||
from io import StringIO #For converting g-code to bytes.
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
from UM.Mesh.MeshWriter import MeshWriter #The writer we need to implement.
|
||||
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
|
||||
from UM.PluginRegistry import PluginRegistry #To get the g-code writer.
|
||||
from PyQt5.QtCore import QBuffer
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Snapshot import Snapshot
|
||||
from cura.Utils.Threading import call_on_qt_thread
|
||||
|
||||
|
@ -83,9 +83,9 @@ class UFPWriter(MeshWriter):
|
|||
Logger.log("d", "Thumbnail not created, cannot save it")
|
||||
|
||||
# Store the material.
|
||||
application = Application.getInstance()
|
||||
application = CuraApplication.getInstance()
|
||||
machine_manager = application.getMachineManager()
|
||||
material_manager = application.getMaterialManager()
|
||||
container_registry = application.getContainerRegistry()
|
||||
global_stack = machine_manager.activeMachine
|
||||
|
||||
material_extension = "xml.fdm_material"
|
||||
|
@ -111,12 +111,12 @@ class UFPWriter(MeshWriter):
|
|||
continue
|
||||
|
||||
material_root_id = material.getMetaDataEntry("base_file")
|
||||
material_group = material_manager.getMaterialGroup(material_root_id)
|
||||
if material_group is None:
|
||||
Logger.log("e", "Cannot find material container with root id [%s]", material_root_id)
|
||||
material_root_query = container_registry.findContainers(id = material_root_id)
|
||||
if not material_root_query:
|
||||
Logger.log("e", "Cannot find material container with root id {root_id}".format(root_id = material_root_id))
|
||||
return False
|
||||
material_container = material_root_query[0]
|
||||
|
||||
material_container = material_group.root_material_node.getContainer()
|
||||
try:
|
||||
serialized_material = material_container.serialize()
|
||||
except NotImplementedError:
|
||||
|
|
|
@ -291,8 +291,8 @@ Cura.MachineAction
|
|||
MessageDialog
|
||||
{
|
||||
id: invalidIPAddressMessageDialog
|
||||
x: (parent.x + (parent.width) / 2) | 0
|
||||
y: (parent.y + (parent.height) / 2) | 0
|
||||
x: parent ? (parent.x + (parent.width) / 2) : 0
|
||||
y: parent ? (parent.y + (parent.height) / 2) : 0
|
||||
title: catalog.i18nc("@title:window", "Invalid IP address")
|
||||
text: catalog.i18nc("@text", "Please enter a valid IP address.")
|
||||
icon: StandardIcon.Warning
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from cura.PrinterOutput.Models.MaterialOutputModel import MaterialOutputModel
|
||||
|
||||
from ..BaseModel import BaseModel
|
||||
|
@ -24,32 +25,27 @@ class ClusterPrinterConfigurationMaterial(BaseModel):
|
|||
self.material = material
|
||||
super().__init__(**kwargs)
|
||||
|
||||
## Creates a material output model based on this cloud printer material.
|
||||
## Creates a material output model based on this cloud printer material.
|
||||
#
|
||||
# A material is chosen that matches the current GUID. If multiple such
|
||||
# materials are available, read-only materials are preferred and the
|
||||
# material with the earliest alphabetical name will be selected.
|
||||
# \return A material output model that matches the current GUID.
|
||||
def createOutputModel(self) -> MaterialOutputModel:
|
||||
material_manager = CuraApplication.getInstance().getMaterialManager()
|
||||
material_group_list = material_manager.getMaterialGroupListByGUID(self.guid) or []
|
||||
|
||||
# Sort the material groups by "is_read_only = True" first, and then the name alphabetically.
|
||||
read_only_material_group_list = list(filter(lambda x: x.is_read_only, material_group_list))
|
||||
non_read_only_material_group_list = list(filter(lambda x: not x.is_read_only, material_group_list))
|
||||
material_group = None
|
||||
if read_only_material_group_list:
|
||||
read_only_material_group_list = sorted(read_only_material_group_list, key = lambda x: x.name)
|
||||
material_group = read_only_material_group_list[0]
|
||||
elif non_read_only_material_group_list:
|
||||
non_read_only_material_group_list = sorted(non_read_only_material_group_list, key = lambda x: x.name)
|
||||
material_group = non_read_only_material_group_list[0]
|
||||
|
||||
if material_group:
|
||||
container = material_group.root_material_node.getContainer()
|
||||
color = container.getMetaDataEntry("color_code")
|
||||
brand = container.getMetaDataEntry("brand")
|
||||
material_type = container.getMetaDataEntry("material")
|
||||
name = container.getName()
|
||||
container_registry = ContainerRegistry.getInstance()
|
||||
same_guid = container_registry.findInstanceContainersMetadata(GUID = self.guid)
|
||||
if same_guid:
|
||||
read_only = sorted(filter(lambda metadata: container_registry.isReadOnly(metadata["id"]), same_guid), key = lambda metadata: metadata["name"])
|
||||
if read_only:
|
||||
material_metadata = read_only[0]
|
||||
else:
|
||||
material_metadata = min(same_guid, key = lambda metadata: metadata["name"])
|
||||
else:
|
||||
color = self.color
|
||||
brand = self.brand
|
||||
material_type = self.material
|
||||
name = "Empty" if self.material == "empty" else "Unknown"
|
||||
material_metadata = {
|
||||
"color_code": self.color,
|
||||
"brand": self.brand,
|
||||
"material": self.material,
|
||||
"name": "Empty" if self.material == "empty" else "Unknown"
|
||||
}
|
||||
|
||||
return MaterialOutputModel(guid=self.guid, type=material_type, brand=brand, color=color, name=name)
|
||||
return MaterialOutputModel(guid = self.guid, type = material_metadata["material"], brand = material_metadata["brand"], color = material_metadata["color_code"], name = material_metadata["name"])
|
||||
|
|
|
@ -6,6 +6,7 @@ from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
|
|||
|
||||
from UM.Job import Job
|
||||
from UM.Logger import Logger
|
||||
from UM.Settings import ContainerRegistry
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
from ..Models.Http.ClusterMaterial import ClusterMaterial
|
||||
|
@ -67,10 +68,10 @@ class SendMaterialJob(Job):
|
|||
# \param materials_to_send A set with id's of materials that must be sent.
|
||||
def _sendMaterials(self, materials_to_send: Set[str]) -> None:
|
||||
container_registry = CuraApplication.getInstance().getContainerRegistry()
|
||||
material_manager = CuraApplication.getInstance().getMaterialManager()
|
||||
material_group_dict = material_manager.getAllMaterialGroups()
|
||||
all_materials = container_registry.findInstanceContainersMetadata(type = "material")
|
||||
all_base_files = {material["base_file"] for material in all_materials if "base_file" in material} # Filters out uniques by making it a set. Don't include files without base file (i.e. empty material).
|
||||
|
||||
for root_material_id in material_group_dict:
|
||||
for root_material_id in all_base_files:
|
||||
if root_material_id not in materials_to_send:
|
||||
# If the material does not have to be sent we skip it.
|
||||
continue
|
||||
|
@ -127,20 +128,18 @@ class SendMaterialJob(Job):
|
|||
@staticmethod
|
||||
def _getLocalMaterials() -> Dict[str, LocalMaterial]:
|
||||
result = {} # type: Dict[str, LocalMaterial]
|
||||
material_manager = CuraApplication.getInstance().getMaterialManager()
|
||||
material_group_dict = material_manager.getAllMaterialGroups()
|
||||
all_materials = CuraApplication.getInstance().getContainerRegistry().findInstanceContainersMetadata(type = "material")
|
||||
all_base_files = [material for material in all_materials if material["id"] == material.get("base_file")] # Don't send materials without base_file: The empty material doesn't need to be sent.
|
||||
|
||||
# Find the latest version of all material containers in the registry.
|
||||
for root_material_id, material_group in material_group_dict.items():
|
||||
material_metadata = material_group.root_material_node.getMetadata()
|
||||
|
||||
for material_metadata in all_base_files:
|
||||
try:
|
||||
# material version must be an int
|
||||
material_metadata["version"] = int(material_metadata["version"])
|
||||
|
||||
# Create a new local material
|
||||
local_material = LocalMaterial(**material_metadata)
|
||||
local_material.id = root_material_id
|
||||
local_material.id = material_metadata["id"]
|
||||
|
||||
if local_material.GUID not in result or \
|
||||
local_material.GUID not in result or \
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Uranium is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtProperty
|
||||
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Application import Application
|
||||
from UM.Util import parseBool
|
||||
|
||||
from cura.MachineAction import MachineAction
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
## The Ultimaker 2 can have a few revisions & upgrades.
|
||||
class UM2UpgradeSelection(MachineAction):
|
||||
def __init__(self):
|
||||
super().__init__("UM2UpgradeSelection", catalog.i18nc("@action", "Select upgrades"))
|
||||
self._qml_url = "UM2UpgradeSelectionMachineAction.qml"
|
||||
|
||||
self._container_registry = ContainerRegistry.getInstance()
|
||||
|
||||
self._current_global_stack = None
|
||||
|
||||
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
|
||||
self._reset()
|
||||
|
||||
def _reset(self):
|
||||
self.hasVariantsChanged.emit()
|
||||
|
||||
def _onGlobalStackChanged(self):
|
||||
if self._current_global_stack:
|
||||
self._current_global_stack.metaDataChanged.disconnect(self._onGlobalStackMetaDataChanged)
|
||||
|
||||
self._current_global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if self._current_global_stack:
|
||||
self._current_global_stack.metaDataChanged.connect(self._onGlobalStackMetaDataChanged)
|
||||
self._reset()
|
||||
|
||||
def _onGlobalStackMetaDataChanged(self):
|
||||
self._reset()
|
||||
|
||||
hasVariantsChanged = pyqtSignal()
|
||||
|
||||
def setHasVariants(self, has_variants = True):
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
variant_container = global_container_stack.extruders["0"].variant
|
||||
|
||||
if has_variants:
|
||||
global_container_stack.setMetaDataEntry("has_variants", True)
|
||||
|
||||
# Set the variant container to a sane default
|
||||
empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
|
||||
if type(variant_container) == type(empty_container):
|
||||
search_criteria = { "type": "variant", "definition": "ultimaker2", "id": "*0.4*" }
|
||||
containers = self._container_registry.findInstanceContainers(**search_criteria)
|
||||
if containers:
|
||||
global_container_stack.extruders["0"].variant = containers[0]
|
||||
else:
|
||||
# The metadata entry is stored in an ini, and ini files are parsed as strings only.
|
||||
# Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False.
|
||||
if "has_variants" in global_container_stack.getMetaData():
|
||||
global_container_stack.removeMetaDataEntry("has_variants")
|
||||
|
||||
# Set the variant container to an empty variant
|
||||
global_container_stack.extruders["0"].variant = ContainerRegistry.getInstance().getEmptyInstanceContainer()
|
||||
|
||||
Application.getInstance().globalContainerStackChanged.emit()
|
||||
self._reset()
|
||||
|
||||
@pyqtProperty(bool, fset = setHasVariants, notify = hasVariantsChanged)
|
||||
def hasVariants(self):
|
||||
if self._current_global_stack:
|
||||
return parseBool(self._current_global_stack.getMetaDataEntry("has_variants", "false"))
|
|
@ -1,55 +0,0 @@
|
|||
// Copyright (c) 2019 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
|
||||
import UM 1.3 as UM
|
||||
import Cura 1.1 as Cura
|
||||
|
||||
|
||||
Cura.MachineAction
|
||||
{
|
||||
UM.I18nCatalog { id: catalog; name: "cura"; }
|
||||
anchors.fill: parent
|
||||
|
||||
Item
|
||||
{
|
||||
id: upgradeSelectionMachineAction
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").width * 5
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width * 4
|
||||
|
||||
Label
|
||||
{
|
||||
id: pageDescription
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label", "Please select any upgrades made to this Ultimaker 2.")
|
||||
font: UM.Theme.getFont("medium")
|
||||
color: UM.Theme.getColor("text")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Cura.CheckBox
|
||||
{
|
||||
id: olssonBlockCheckBox
|
||||
anchors.top: pageDescription.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
|
||||
height: UM.Theme.getSize("setting_control").height
|
||||
|
||||
text: catalog.i18nc("@label", "Olsson Block")
|
||||
checked: manager.hasVariants
|
||||
onClicked: manager.hasVariants = checked
|
||||
|
||||
Connections
|
||||
{
|
||||
target: manager
|
||||
onHasVariantsChanged: olssonBlockCheckBox.checked = manager.hasVariants
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,8 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from . import BedLevelMachineAction
|
||||
from . import UMOUpgradeSelection
|
||||
from . import UM2UpgradeSelection
|
||||
|
||||
def getMetaData():
|
||||
return {}
|
||||
|
@ -11,6 +10,5 @@ def getMetaData():
|
|||
def register(app):
|
||||
return { "machine_action": [
|
||||
BedLevelMachineAction.BedLevelMachineAction(),
|
||||
UMOUpgradeSelection.UMOUpgradeSelection(),
|
||||
UM2UpgradeSelection.UM2UpgradeSelection()
|
||||
UMOUpgradeSelection.UMOUpgradeSelection()
|
||||
]}
|
||||
|
|
|
@ -56,4 +56,4 @@ def getMetaData() -> Dict[str, Any]:
|
|||
|
||||
|
||||
def register(app: "Application") -> Dict[str, Any]:
|
||||
return { "version_upgrade": upgrade }
|
||||
return { "version_upgrade": upgrade }
|
||||
|
|
|
@ -4,6 +4,14 @@ import io
|
|||
from UM.VersionUpgrade import VersionUpgrade
|
||||
|
||||
_renamed_container_id_map = {
|
||||
"ultimaker2_0.25": "ultimaker2_olsson_0.25",
|
||||
"ultimaker2_0.4": "ultimaker2_olsson_0.4",
|
||||
"ultimaker2_0.6": "ultimaker2_olsson_0.6",
|
||||
"ultimaker2_0.8": "ultimaker2_olsson_0.8",
|
||||
"ultimaker2_extended_0.25": "ultimaker2_extended_olsson_0.25",
|
||||
"ultimaker2_extended_0.4": "ultimaker2_extended_olsson_0.4",
|
||||
"ultimaker2_extended_0.6": "ultimaker2_extended_olsson_0.6",
|
||||
"ultimaker2_extended_0.8": "ultimaker2_extended_olsson_0.8",
|
||||
# HMS434 "extra coarse", "super coarse", and "ultra coarse" are removed.
|
||||
"hms434_global_Extra_Coarse_Quality": "hms434_global_Normal_Quality",
|
||||
"hms434_global_Super_Coarse_Quality": "hms434_global_Normal_Quality",
|
||||
|
@ -49,6 +57,10 @@ class VersionUpgrade43to44(VersionUpgrade):
|
|||
# Update version number.
|
||||
parser["metadata"]["setting_version"] = "10"
|
||||
|
||||
# Intent profiles were added, so the quality changes should match with no intent (so "default")
|
||||
if parser["metadata"].get("type", "") == "quality_changes":
|
||||
parser["metadata"]["intent_category"] = "default"
|
||||
|
||||
result = io.StringIO()
|
||||
parser.write(result)
|
||||
return [filename], [result.getvalue()]
|
||||
|
@ -64,6 +76,29 @@ class VersionUpgrade43to44(VersionUpgrade):
|
|||
parser["metadata"]["setting_version"] = "10"
|
||||
|
||||
if "containers" in parser:
|
||||
# With the ContainerTree refactor, UM2 with Olsson block got moved to a separate definition.
|
||||
if "6" in parser["containers"]:
|
||||
if parser["containers"]["6"] == "ultimaker2":
|
||||
if "metadata" in parser and "has_variants" in parser["metadata"] and parser["metadata"]["has_variants"] == "True": # This is an Olsson block upgraded UM2!
|
||||
parser["containers"]["6"] = "ultimaker2_olsson"
|
||||
del parser["metadata"]["has_variants"]
|
||||
elif parser["containers"]["6"] == "ultimaker2_extended":
|
||||
if "metadata" in parser and "has_variants" in parser["metadata"] and parser["metadata"]["has_variants"] == "True": # This is an Olsson block upgraded UM2E!
|
||||
parser["containers"]["6"] = "ultimaker2_extended_olsson"
|
||||
del parser["metadata"]["has_variants"]
|
||||
|
||||
# We should only have 6 levels when we start.
|
||||
if "7" in parser["containers"]:
|
||||
return ([], [])
|
||||
|
||||
# We added the intent container in Cura 4.4. This means that all other containers move one step down.
|
||||
parser["containers"]["7"] = parser["containers"]["6"]
|
||||
parser["containers"]["6"] = parser["containers"]["5"]
|
||||
parser["containers"]["5"] = parser["containers"]["4"]
|
||||
parser["containers"]["4"] = parser["containers"]["3"]
|
||||
parser["containers"]["3"] = parser["containers"]["2"]
|
||||
parser["containers"]["2"] = "empty_intent"
|
||||
|
||||
# Update renamed containers
|
||||
for key, value in parser["containers"].items():
|
||||
if value in _renamed_container_id_map:
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import configparser
|
||||
|
||||
import VersionUpgrade43to44
|
||||
|
||||
before_update = """[general]
|
||||
version = 4
|
||||
name = Ultimaker 3
|
||||
id = Ultimaker 3
|
||||
|
||||
[metadata]
|
||||
type = machine
|
||||
|
||||
[containers]
|
||||
0 = user_profile
|
||||
1 = quality_changes
|
||||
2 = quality
|
||||
3 = material
|
||||
4 = variant
|
||||
5 = definition_changes
|
||||
6 = definition
|
||||
"""
|
||||
|
||||
|
||||
def test_upgrade():
|
||||
upgrader = VersionUpgrade43to44.VersionUpgrade43to44()
|
||||
file_name, new_data = upgrader.upgradeStack(before_update, "whatever")
|
||||
parser = configparser.ConfigParser(interpolation=None)
|
||||
parser.read_string(new_data[0])
|
||||
assert parser["containers"]["0"] == "user_profile"
|
||||
assert parser["containers"]["1"] == "quality_changes"
|
||||
assert parser["containers"]["2"] == "empty_intent"
|
||||
assert parser["containers"]["3"] == "quality"
|
||||
assert parser["containers"]["4"] == "material"
|
||||
assert parser["containers"]["5"] == "variant"
|
||||
assert parser["containers"]["6"] == "definition_changes"
|
||||
assert parser["containers"]["7"] == "definition"
|
|
@ -17,6 +17,7 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
|
|||
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
from cura.Machines.VariantType import VariantType
|
||||
|
||||
try:
|
||||
|
@ -74,37 +75,20 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
if k in self.__material_properties_setting_map:
|
||||
new_setting_values_dict[self.__material_properties_setting_map[k]] = v
|
||||
|
||||
# Prevent recursion
|
||||
if not apply_to_all:
|
||||
super().setMetaDataEntry(key, value)
|
||||
if not apply_to_all: # Historical: If you only want to modify THIS container. We only used that to prevent recursion but with the below code that's no longer necessary.
|
||||
container_query = registry.findContainers(id = self.getId())
|
||||
else:
|
||||
container_query = registry.findContainers(base_file = self.getMetaDataEntry("base_file"))
|
||||
|
||||
for container in container_query:
|
||||
if key not in container.getMetaData() or container.getMetaData()[key] != value:
|
||||
container.getMetaData()[key] = value
|
||||
container.setDirty(True)
|
||||
container.metaDataChanged.emit(container)
|
||||
for k, v in new_setting_values_dict.items():
|
||||
self.setProperty(k, "value", v)
|
||||
return
|
||||
|
||||
# Get the MaterialGroup
|
||||
material_manager = CuraApplication.getInstance().getMaterialManager()
|
||||
root_material_id = self.getMetaDataEntry("base_file") #if basefile is self.getId, this is a basefile.
|
||||
material_group = material_manager.getMaterialGroup(root_material_id)
|
||||
if not material_group: #If the profile is not registered in the registry but loose/temporary, it will not have a base file tree.
|
||||
super().setMetaDataEntry(key, value)
|
||||
for k, v in new_setting_values_dict.items():
|
||||
self.setProperty(k, "value", v)
|
||||
return
|
||||
# Update the root material container
|
||||
root_material_container = material_group.root_material_node.getContainer()
|
||||
if root_material_container is not None:
|
||||
root_material_container.setMetaDataEntry(key, value, apply_to_all = False)
|
||||
for k, v in new_setting_values_dict.items():
|
||||
root_material_container.setProperty(k, "value", v)
|
||||
|
||||
# Update all containers derived from it
|
||||
for node in material_group.derived_material_node_list:
|
||||
container = node.getContainer()
|
||||
if container is not None:
|
||||
container.setMetaDataEntry(key, value, apply_to_all = False)
|
||||
for k, v in new_setting_values_dict.items():
|
||||
container.setProperty(k, "value", v)
|
||||
|
||||
## Overridden from InstanceContainer, similar to setMetaDataEntry.
|
||||
# without this function the setName would only set the name of the specific nozzle / material / machine combination container
|
||||
# The function is a bit tricky. It will not set the name of all containers if it has the correct name itself.
|
||||
|
@ -225,10 +209,8 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
for instance in self.findInstances():
|
||||
self._addSettingElement(builder, instance)
|
||||
|
||||
machine_container_map = {} # type: Dict[str, InstanceContainer]
|
||||
machine_variant_map = {} # type: Dict[str, Dict[str, Any]]
|
||||
|
||||
variant_manager = CuraApplication.getInstance().getVariantManager()
|
||||
machine_container_map = {} # type: Dict[str, InstanceContainer]
|
||||
machine_variant_map = {} # type: Dict[str, Dict[str, Any]]
|
||||
|
||||
root_material_id = self.getMetaDataEntry("base_file") # if basefile is self.getId, this is a basefile.
|
||||
all_containers = registry.findInstanceContainers(base_file = root_material_id)
|
||||
|
@ -245,13 +227,13 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
machine_variant_map[definition_id] = {}
|
||||
|
||||
variant_name = container.getMetaDataEntry("variant_name")
|
||||
if variant_name:
|
||||
variant_dict = {"variant_node": variant_manager.getVariantNode(definition_id, variant_name),
|
||||
"material_container": container}
|
||||
machine_variant_map[definition_id][variant_name] = variant_dict
|
||||
if not variant_name:
|
||||
machine_container_map[definition_id] = container
|
||||
continue
|
||||
|
||||
machine_container_map[definition_id] = container
|
||||
variant_dict = {"variant_type": container.getMetaDataEntry("hardware_type", "nozzle"),
|
||||
"material_container": container}
|
||||
machine_variant_map[definition_id][variant_name] = variant_dict
|
||||
|
||||
# Map machine human-readable names to IDs
|
||||
product_id_map = self.getProductIdMap()
|
||||
|
@ -284,8 +266,7 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
# Find all hotend sub-profiles corresponding to this material and machine and add them to this profile.
|
||||
buildplate_dict = {} # type: Dict[str, Any]
|
||||
for variant_name, variant_dict in machine_variant_map[definition_id].items():
|
||||
variant_type = variant_dict["variant_node"].getMetaDataEntry("hardware_type", str(VariantType.NOZZLE))
|
||||
variant_type = VariantType(variant_type)
|
||||
variant_type = VariantType(variant_dict["variant_type"])
|
||||
if variant_type == VariantType.NOZZLE:
|
||||
# The hotend identifier is not the containers name, but its "name".
|
||||
builder.start("hotend", {"id": variant_name})
|
||||
|
@ -348,7 +329,7 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
stream = io.BytesIO()
|
||||
tree = ET.ElementTree(root)
|
||||
# this makes sure that the XML header states encoding="utf-8"
|
||||
tree.write(stream, encoding = "utf-8", xml_declaration=True)
|
||||
tree.write(stream, encoding = "utf-8", xml_declaration = True)
|
||||
|
||||
return stream.getvalue().decode("utf-8")
|
||||
|
||||
|
@ -699,32 +680,6 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
if is_new_material:
|
||||
containers_to_add.append(new_material)
|
||||
|
||||
# Find the buildplates compatibility
|
||||
buildplates = machine.iterfind("./um:buildplate", self.__namespaces)
|
||||
buildplate_map = {}
|
||||
buildplate_map["buildplate_compatible"] = {}
|
||||
buildplate_map["buildplate_recommended"] = {}
|
||||
for buildplate in buildplates:
|
||||
buildplate_id = buildplate.get("id")
|
||||
if buildplate_id is None:
|
||||
continue
|
||||
|
||||
variant_manager = CuraApplication.getInstance().getVariantManager()
|
||||
variant_node = variant_manager.getVariantNode(machine_id, buildplate_id,
|
||||
variant_type = VariantType.BUILD_PLATE)
|
||||
if not variant_node:
|
||||
continue
|
||||
|
||||
_, buildplate_unmapped_settings_dict = self._getSettingsDictForNode(buildplate)
|
||||
|
||||
buildplate_compatibility = buildplate_unmapped_settings_dict.get("hardware compatible",
|
||||
machine_compatibility)
|
||||
buildplate_recommended = buildplate_unmapped_settings_dict.get("hardware recommended",
|
||||
machine_compatibility)
|
||||
|
||||
buildplate_map["buildplate_compatible"][buildplate_id] = buildplate_compatibility
|
||||
buildplate_map["buildplate_recommended"][buildplate_id] = buildplate_recommended
|
||||
|
||||
hotends = machine.iterfind("./um:hotend", self.__namespaces)
|
||||
for hotend in hotends:
|
||||
# The "id" field for hotends in material profiles is actually name
|
||||
|
@ -732,11 +687,6 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
if hotend_name is None:
|
||||
continue
|
||||
|
||||
variant_manager = CuraApplication.getInstance().getVariantManager()
|
||||
variant_node = variant_manager.getVariantNode(machine_id, hotend_name, VariantType.NOZZLE)
|
||||
if not variant_node:
|
||||
continue
|
||||
|
||||
hotend_mapped_settings, hotend_unmapped_settings = self._getSettingsDictForNode(hotend)
|
||||
hotend_compatibility = hotend_unmapped_settings.get("hardware compatible", machine_compatibility)
|
||||
|
||||
|
@ -760,9 +710,6 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
new_hotend_material.getMetaData()["compatible"] = hotend_compatibility
|
||||
new_hotend_material.getMetaData()["machine_manufacturer"] = machine_manufacturer
|
||||
new_hotend_material.getMetaData()["definition"] = machine_id
|
||||
if buildplate_map["buildplate_compatible"]:
|
||||
new_hotend_material.getMetaData()["buildplate_compatible"] = buildplate_map["buildplate_compatible"]
|
||||
new_hotend_material.getMetaData()["buildplate_recommended"] = buildplate_map["buildplate_recommended"]
|
||||
|
||||
cached_hotend_setting_properties = cached_machine_setting_properties.copy()
|
||||
cached_hotend_setting_properties.update(hotend_mapped_settings)
|
||||
|
@ -774,61 +721,6 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
if is_new_material:
|
||||
containers_to_add.append(new_hotend_material)
|
||||
|
||||
#
|
||||
# Build plates in hotend
|
||||
#
|
||||
buildplates = hotend.iterfind("./um:buildplate", self.__namespaces)
|
||||
for buildplate in buildplates:
|
||||
# The "id" field for buildplate in material profiles is actually name
|
||||
buildplate_name = buildplate.get("id")
|
||||
if buildplate_name is None:
|
||||
continue
|
||||
|
||||
variant_manager = CuraApplication.getInstance().getVariantManager()
|
||||
variant_node = variant_manager.getVariantNode(machine_id, buildplate_name, VariantType.BUILD_PLATE)
|
||||
if not variant_node:
|
||||
continue
|
||||
|
||||
buildplate_mapped_settings, buildplate_unmapped_settings = self._getSettingsDictForNode(buildplate)
|
||||
buildplate_compatibility = buildplate_unmapped_settings.get("hardware compatible",
|
||||
buildplate_map["buildplate_compatible"])
|
||||
buildplate_recommended = buildplate_unmapped_settings.get("hardware recommended",
|
||||
buildplate_map["buildplate_recommended"])
|
||||
|
||||
# Generate container ID for the hotend-and-buildplate-specific material container
|
||||
new_hotend_and_buildplate_specific_material_id = new_hotend_specific_material_id + "_" + buildplate_name.replace(" ", "_")
|
||||
|
||||
# Same as machine compatibility, keep the derived material containers consistent with the parent material
|
||||
if ContainerRegistry.getInstance().isLoaded(new_hotend_and_buildplate_specific_material_id):
|
||||
new_hotend_and_buildplate_material = ContainerRegistry.getInstance().findContainers(id = new_hotend_and_buildplate_specific_material_id)[0]
|
||||
is_new_material = False
|
||||
else:
|
||||
new_hotend_and_buildplate_material = XmlMaterialProfile(new_hotend_and_buildplate_specific_material_id)
|
||||
is_new_material = True
|
||||
|
||||
new_hotend_and_buildplate_material.setMetaData(copy.deepcopy(new_hotend_material.getMetaData()))
|
||||
new_hotend_and_buildplate_material.getMetaData()["id"] = new_hotend_and_buildplate_specific_material_id
|
||||
new_hotend_and_buildplate_material.getMetaData()["name"] = self.getName()
|
||||
new_hotend_and_buildplate_material.getMetaData()["variant_name"] = hotend_name
|
||||
new_hotend_and_buildplate_material.getMetaData()["buildplate_name"] = buildplate_name
|
||||
new_hotend_and_buildplate_material.setDefinition(machine_id)
|
||||
# Don't use setMetadata, as that overrides it for all materials with same base file
|
||||
new_hotend_and_buildplate_material.getMetaData()["compatible"] = buildplate_compatibility
|
||||
new_hotend_and_buildplate_material.getMetaData()["machine_manufacturer"] = machine_manufacturer
|
||||
new_hotend_and_buildplate_material.getMetaData()["definition"] = machine_id
|
||||
new_hotend_and_buildplate_material.getMetaData()["buildplate_compatible"] = buildplate_compatibility
|
||||
new_hotend_and_buildplate_material.getMetaData()["buildplate_recommended"] = buildplate_recommended
|
||||
|
||||
cached_hotend_and_buildplate_setting_properties = cached_hotend_setting_properties.copy()
|
||||
cached_hotend_and_buildplate_setting_properties.update(buildplate_mapped_settings)
|
||||
|
||||
new_hotend_and_buildplate_material.setCachedValues(cached_hotend_and_buildplate_setting_properties)
|
||||
|
||||
new_hotend_and_buildplate_material._dirty = False
|
||||
|
||||
if is_new_material:
|
||||
containers_to_add.append(new_hotend_and_buildplate_material)
|
||||
|
||||
# there is only one ID for a machine. Once we have reached here, it means we have already found
|
||||
# a workable ID for that machine, so there is no need to continue
|
||||
break
|
||||
|
|
|
@ -815,6 +815,23 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"VersionUpgrade43to44": {
|
||||
"package_info": {
|
||||
"package_id": "VersionUpgrade43to44",
|
||||
"package_type": "plugin",
|
||||
"display_name": "Version Upgrade 4.3 to 4.4",
|
||||
"description": "Upgrades configurations from Cura 4.3 to Cura 4.4.",
|
||||
"package_version": "1.0.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
"display_name": "Ultimaker B.V.",
|
||||
"email": "plugins@ultimaker.com",
|
||||
"website": "https://ultimaker.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
"X3DReader": {
|
||||
"package_info": {
|
||||
"package_id": "X3DReader",
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
"file_formats": "text/x-gcode;application/x-stl-ascii;application/x-stl-binary;application/x-wavefront-obj;application/x3g",
|
||||
"visible": false,
|
||||
"has_materials": true,
|
||||
"has_variants": false,
|
||||
"has_machine_quality": false,
|
||||
"preferred_material": "generic_pla",
|
||||
"preferred_quality_type": "normal",
|
||||
"machine_extruder_trains":
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
"file_formats": "text/x-gcode",
|
||||
|
||||
"has_materials": true,
|
||||
"has_machine_materials": true,
|
||||
"preferred_material": "generic_pla",
|
||||
"exclude_materials": [
|
||||
"chromatik_pla",
|
||||
|
|
|
@ -14,8 +14,6 @@
|
|||
"has_materials": false,
|
||||
"has_machine_quality": true,
|
||||
"preferred_variant_name": "0.4 mm",
|
||||
"first_start_actions": ["UM2UpgradeSelection"],
|
||||
"supported_actions":["UM2UpgradeSelection"],
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "ultimaker2_extruder_0"
|
||||
|
|
12
resources/definitions/ultimaker2_extended_olsson.def.json
Normal file
12
resources/definitions/ultimaker2_extended_olsson.def.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"version": 2,
|
||||
"name": "Ultimaker 2 Extended with Olsson",
|
||||
"inherits": "ultimaker2_extended",
|
||||
"metadata": {
|
||||
"has_variants": true
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Ultimaker 2 Extended with Olsson" }
|
||||
}
|
||||
}
|
|
@ -11,7 +11,6 @@
|
|||
"platform": "ultimaker2go_platform.obj",
|
||||
"platform_texture": "Ultimaker2Gobackplate.png",
|
||||
"platform_offset": [0, 0, 0],
|
||||
"first_start_actions": [],
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "ultimaker2_go_extruder_0"
|
||||
|
|
13
resources/definitions/ultimaker2_olsson.def.json
Normal file
13
resources/definitions/ultimaker2_olsson.def.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"version": 2,
|
||||
"name": "Ultimaker 2 with Olsson Block",
|
||||
"inherits": "ultimaker2",
|
||||
"metadata": {
|
||||
"has_variants": true,
|
||||
"quality_definition": "ultimaker2"
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Ultimaker 2 with Olsson Block" }
|
||||
}
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.1
|
||||
import QtQuick.Controls 1.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Window 2.1
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Window 2.2
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
@ -139,22 +139,6 @@ UM.Dialog
|
|||
}
|
||||
}
|
||||
}
|
||||
Row
|
||||
{
|
||||
visible: Cura.MachineManager.hasVariantBuildplates
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@action:label", "Build plate")
|
||||
width: Math.floor(scroll.width / 3) | 0
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: Cura.activeStack != null ? Cura.MachineManager.activeStack.variant.name : ""
|
||||
width: Math.floor(scroll.width / 3) | 0
|
||||
}
|
||||
}
|
||||
Repeater
|
||||
{
|
||||
width: parent.width
|
||||
|
@ -256,6 +240,23 @@ UM.Dialog
|
|||
width: Math.floor(scroll.width / 3) | 0
|
||||
}
|
||||
}
|
||||
|
||||
// Intent
|
||||
Row
|
||||
{
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@action:label", "Intent")
|
||||
width: Math.floor(scroll.width / 3) | 0
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: Cura.MachineManager.activeIntentCategory
|
||||
width: Math.floor(scroll.width / 3) | 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
65
resources/qml/LabelBar.qml
Normal file
65
resources/qml/LabelBar.qml
Normal file
|
@ -0,0 +1,65 @@
|
|||
// Copyright (c) 2019 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
import UM 1.2 as UM
|
||||
|
||||
// The labelBar shows a set of labels that are evenly spaced from one another.
|
||||
// The first item is aligned to the left, the last is aligned to the right.
|
||||
// It's intended to be used together with RadioCheckBar. As such, it needs
|
||||
// to know what the used itemSize is, so it can ensure the labels are aligned correctly.
|
||||
Item
|
||||
{
|
||||
id: base
|
||||
property var model: null
|
||||
property string modelKey: ""
|
||||
property int itemSize: 14
|
||||
height: childrenRect.height
|
||||
RowLayout
|
||||
{
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
spacing: 0
|
||||
Repeater
|
||||
{
|
||||
id: repeater
|
||||
model: base.model
|
||||
|
||||
Item
|
||||
{
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: Math.round(index + 1 === repeater.count || repeater.count <= 1 ? itemSize : base.width / (repeater.count - 1))
|
||||
height: label.height
|
||||
|
||||
Label
|
||||
{
|
||||
id: label
|
||||
text: model[modelKey]
|
||||
color: UM.Theme.getColor("text")
|
||||
font: UM.Theme.getFont("default")
|
||||
renderType: Text.NativeRendering
|
||||
height: contentHeight
|
||||
anchors
|
||||
{
|
||||
// Some magic to ensure that the items are aligned properly.
|
||||
// We want the following:
|
||||
// First item should be aligned to the left, no margin.
|
||||
// Last item should be aligned to the right, no margin.
|
||||
// The middle item(s) should be aligned to the center of the "item" it's showing (hence half the itemsize as offset).
|
||||
// We want the center of the label to align with the center of the item, so we negatively offset by half the contentWidth
|
||||
right: index + 1 === repeater.count ? parent.right: undefined
|
||||
left: index + 1 === repeater.count || index === 0 ? undefined: parent.left
|
||||
leftMargin: Math.round((itemSize - contentWidth) * 0.5)
|
||||
|
||||
// For some reason, the last label in the row gets misaligned with Qt 5.10. This lines seems to
|
||||
// fix it.
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 1.4
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
Menu
|
||||
{
|
||||
id: menu
|
||||
title: "Build plate"
|
||||
|
||||
property var buildPlateModel: CuraApplication.getBuildPlateModel()
|
||||
|
||||
Instantiator
|
||||
{
|
||||
model: menu.buildPlateModel
|
||||
|
||||
MenuItem {
|
||||
text: model.name
|
||||
checkable: true
|
||||
checked: model.name == Cura.MachineManager.globalVariantName
|
||||
exclusiveGroup: group
|
||||
onTriggered: {
|
||||
Cura.MachineManager.setGlobalVariant(model.container_node);
|
||||
}
|
||||
}
|
||||
|
||||
onObjectAdded: menu.insertItem(index, object)
|
||||
onObjectRemoved: menu.removeItem(object)
|
||||
}
|
||||
|
||||
ExclusiveGroup { id: group }
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.0
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import UM 1.2 as UM
|
||||
|
@ -99,12 +99,14 @@ Cura.ExpandablePopup
|
|||
left: extruderIcon.right
|
||||
leftMargin: UM.Theme.getSize("default_margin").width
|
||||
top: typeAndBrandNameLabel.bottom
|
||||
right: parent.right
|
||||
rightMargin: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Placeholder text if there is a configuration to select but no materials (so we can't show the materials per extruder).
|
||||
// Placeholder text if there is a configuration to select but no materials (so we can't show the materials per extruder).
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Select configuration")
|
||||
|
|
|
@ -14,7 +14,7 @@ Menu
|
|||
|
||||
property int extruderIndex: 0
|
||||
property string currentRootMaterialId: Cura.MachineManager.currentRootMaterialId[extruderIndex]
|
||||
property string activeMaterialId: Cura.MachineManager.allActiveMaterialIds[Cura.ExtruderManager.extruderIds[extruderIndex]]
|
||||
property string activeMaterialId: Cura.MachineManager.activeMachine.extruderList[extruderIndex].material.id
|
||||
property bool updateModels: true
|
||||
Cura.FavoriteMaterialsModel
|
||||
{
|
||||
|
@ -52,10 +52,10 @@ Menu
|
|||
checkable: true
|
||||
checked: model.root_material_id === menu.currentRootMaterialId
|
||||
onTriggered: Cura.MachineManager.setMaterial(extruderIndex, model.container_node)
|
||||
exclusiveGroup: group
|
||||
exclusiveGroup: favoriteGroup // One favorite and one item from the others can be active at the same time.
|
||||
}
|
||||
onObjectAdded: menu.insertItem(index, object)
|
||||
onObjectRemoved: menu.removeItem(object) // TODO: This ain't gonna work, removeItem() takes an index, not object
|
||||
onObjectRemoved: menu.removeItem(index)
|
||||
}
|
||||
|
||||
MenuSeparator {}
|
||||
|
@ -77,7 +77,7 @@ Menu
|
|||
onTriggered: Cura.MachineManager.setMaterial(extruderIndex, model.container_node)
|
||||
}
|
||||
onObjectAdded: genericMenu.insertItem(index, object)
|
||||
onObjectRemoved: genericMenu.removeItem(object) // TODO: This ain't gonna work, removeItem() takes an index, not object
|
||||
onObjectRemoved: genericMenu.removeItem(index)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -126,10 +126,16 @@ Menu
|
|||
onObjectRemoved: menu.removeItem(object)
|
||||
}
|
||||
|
||||
ExclusiveGroup {
|
||||
ExclusiveGroup
|
||||
{
|
||||
id: group
|
||||
}
|
||||
|
||||
ExclusiveGroup
|
||||
{
|
||||
id: favoriteGroup
|
||||
}
|
||||
|
||||
MenuSeparator {}
|
||||
|
||||
MenuItem
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 1.4
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
Menu
|
||||
{
|
||||
id: menu
|
||||
|
||||
Instantiator
|
||||
{
|
||||
model: Cura.QualityProfilesDropDownMenuModel
|
||||
|
||||
MenuItem
|
||||
{
|
||||
text:
|
||||
{
|
||||
var full_text = (model.layer_height != "") ? model.name + " - " + model.layer_height + model.layer_height_unit : model.name
|
||||
full_text += model.is_experimental ? " - " + catalog.i18nc("@label", "Experimental") : ""
|
||||
return full_text
|
||||
}
|
||||
checkable: true
|
||||
checked: Cura.MachineManager.activeQualityOrQualityChangesName == model.name
|
||||
exclusiveGroup: group
|
||||
onTriggered: Cura.MachineManager.setQualityGroup(model.quality_group)
|
||||
visible: model.available
|
||||
}
|
||||
|
||||
onObjectAdded: menu.insertItem(index, object)
|
||||
onObjectRemoved: menu.removeItem(object)
|
||||
}
|
||||
|
||||
MenuSeparator
|
||||
{
|
||||
id: customSeparator
|
||||
visible: Cura.CustomQualityProfilesDropDownMenuModel.count > 0
|
||||
}
|
||||
|
||||
Instantiator
|
||||
{
|
||||
id: customProfileInstantiator
|
||||
model: Cura.CustomQualityProfilesDropDownMenuModel
|
||||
|
||||
Connections
|
||||
{
|
||||
target: Cura.CustomQualityProfilesDropDownMenuModel
|
||||
onModelReset: customSeparator.visible = Cura.CustomQualityProfilesDropDownMenuModel.count > 0
|
||||
}
|
||||
|
||||
MenuItem
|
||||
{
|
||||
text: model.name
|
||||
checkable: true
|
||||
checked: Cura.MachineManager.activeQualityOrQualityChangesName == model.name
|
||||
exclusiveGroup: group
|
||||
onTriggered: Cura.MachineManager.setQualityChangesGroup(model.quality_changes_group)
|
||||
}
|
||||
|
||||
onObjectAdded:
|
||||
{
|
||||
customSeparator.visible = model.count > 0;
|
||||
menu.insertItem(index, object);
|
||||
}
|
||||
onObjectRemoved:
|
||||
{
|
||||
customSeparator.visible = model.count > 0;
|
||||
menu.removeItem(object);
|
||||
}
|
||||
}
|
||||
|
||||
ExclusiveGroup { id: group; }
|
||||
|
||||
MenuSeparator { id: profileMenuSeparator }
|
||||
|
||||
MenuItem { action: Cura.Actions.addProfile }
|
||||
MenuItem { action: Cura.Actions.updateProfile }
|
||||
MenuItem { action: Cura.Actions.resetProfile }
|
||||
MenuSeparator { }
|
||||
MenuItem { action: Cura.Actions.manageProfiles }
|
||||
}
|
|
@ -22,7 +22,7 @@ Menu
|
|||
Menu
|
||||
{
|
||||
title: modelData.name
|
||||
|
||||
property var extruder: Cura.MachineManager.getExtruder(model.index)
|
||||
NozzleMenu { title: Cura.MachineManager.activeDefinitionVariantsName; visible: Cura.MachineManager.hasVariants; extruderIndex: index }
|
||||
MaterialMenu { title: catalog.i18nc("@title:menu", "&Material"); visible: Cura.MachineManager.hasMaterials; extruderIndex: index }
|
||||
|
||||
|
@ -41,14 +41,14 @@ Menu
|
|||
{
|
||||
text: catalog.i18nc("@action:inmenu", "Enable Extruder")
|
||||
onTriggered: Cura.MachineManager.setExtruderEnabled(model.index, true)
|
||||
visible: !Cura.MachineManager.getExtruder(model.index).isEnabled
|
||||
visible: extruder === null ? false : !extruder.isEnabled
|
||||
}
|
||||
|
||||
MenuItem
|
||||
{
|
||||
text: catalog.i18nc("@action:inmenu", "Disable Extruder")
|
||||
onTriggered: Cura.MachineManager.setExtruderEnabled(index, false)
|
||||
visible: Cura.MachineManager.getExtruder(model.index).isEnabled
|
||||
visible: extruder === null ? false : extruder.isEnabled
|
||||
enabled: Cura.MachineManager.numberExtrudersEnabled > 1
|
||||
}
|
||||
|
||||
|
@ -57,14 +57,6 @@ Menu
|
|||
onObjectRemoved: base.removeItem(object)
|
||||
}
|
||||
|
||||
// TODO Only show in dev mode. Remove check when feature ready
|
||||
BuildplateMenu
|
||||
{
|
||||
title: catalog.i18nc("@title:menu", "&Build plate")
|
||||
visible: CuraSDKVersion == "dev" && Cura.MachineManager.hasVariantBuildplates
|
||||
}
|
||||
ProfileMenu { title: catalog.i18nc("@title:settings", "&Profile") }
|
||||
|
||||
MenuSeparator { }
|
||||
|
||||
MenuItem { action: Cura.Actions.configureSettingVisibility }
|
||||
|
|
|
@ -7,7 +7,7 @@ import QtQuick.Layouts 1.3
|
|||
import QtQuick.Dialogs 1.2
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.0 as Cura
|
||||
import Cura 1.5 as Cura
|
||||
|
||||
Item
|
||||
{
|
||||
|
@ -17,6 +17,8 @@ Item
|
|||
property var resetEnabled: false
|
||||
property var currentItem: null
|
||||
|
||||
property var materialManagementModel: CuraApplication.getMaterialManagementModel()
|
||||
|
||||
property var hasCurrentItem: base.currentItem != null
|
||||
property var isCurrentItemActivated:
|
||||
{
|
||||
|
@ -119,7 +121,7 @@ Item
|
|||
onClicked:
|
||||
{
|
||||
forceActiveFocus();
|
||||
base.newRootMaterialIdToSwitchTo = CuraApplication.getMaterialManager().createMaterial();
|
||||
base.newRootMaterialIdToSwitchTo = base.materialManagementModel.createMaterial();
|
||||
base.toActivateNewMaterial = true;
|
||||
}
|
||||
}
|
||||
|
@ -134,7 +136,7 @@ Item
|
|||
onClicked:
|
||||
{
|
||||
forceActiveFocus();
|
||||
base.newRootMaterialIdToSwitchTo = CuraApplication.getMaterialManager().duplicateMaterial(base.currentItem.container_node);
|
||||
base.newRootMaterialIdToSwitchTo = base.materialManagementModel.duplicateMaterial(base.currentItem.container_node);
|
||||
base.toActivateNewMaterial = true;
|
||||
}
|
||||
}
|
||||
|
@ -145,7 +147,8 @@ Item
|
|||
id: removeMenuButton
|
||||
text: catalog.i18nc("@action:button", "Remove")
|
||||
iconName: "list-remove"
|
||||
enabled: base.hasCurrentItem && !base.currentItem.is_read_only && !base.isCurrentItemActivated && CuraApplication.getMaterialManager().canMaterialBeRemoved(base.currentItem.container_node)
|
||||
enabled: base.hasCurrentItem && !base.currentItem.is_read_only && !base.isCurrentItemActivated && base.materialManagementModel.canMaterialBeRemoved(base.currentItem.container_node)
|
||||
|
||||
onClicked:
|
||||
{
|
||||
forceActiveFocus();
|
||||
|
@ -294,7 +297,7 @@ Item
|
|||
{
|
||||
// Set the active material as the fallback. It will be selected when the current material is deleted
|
||||
base.newRootMaterialIdToSwitchTo = base.active_root_material_id
|
||||
CuraApplication.getMaterialManager().removeMaterial(base.currentItem.container_node);
|
||||
base.materialManagementModel.removeMaterial(base.currentItem.container_node);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -82,11 +82,12 @@ Rectangle
|
|||
{
|
||||
if (materialSlot.is_favorite)
|
||||
{
|
||||
CuraApplication.getMaterialManager().removeFavorite(material.root_material_id)
|
||||
return
|
||||
CuraApplication.getMaterialManagementModel().removeFavorite(material.root_material_id)
|
||||
}
|
||||
else
|
||||
{
|
||||
CuraApplication.getMaterialManagementModel().addFavorite(material.root_material_id)
|
||||
}
|
||||
CuraApplication.getMaterialManager().addFavorite(material.root_material_id)
|
||||
return
|
||||
}
|
||||
style: ButtonStyle
|
||||
{
|
||||
|
|
|
@ -23,6 +23,7 @@ TabView
|
|||
property real secondColumnWidth: (width * 0.40) | 0
|
||||
property string containerId: ""
|
||||
property var materialPreferenceValues: UM.Preferences.getValue("cura/material_settings") ? JSON.parse(UM.Preferences.getValue("cura/material_settings")) : {}
|
||||
property var materialManagementModel: CuraApplication.getMaterialManagementModel()
|
||||
|
||||
property double spoolLength: calculateSpoolLength()
|
||||
property real costPerMeter: calculateCostPerMeter()
|
||||
|
@ -565,7 +566,7 @@ TabView
|
|||
}
|
||||
|
||||
// update the values
|
||||
CuraApplication.getMaterialManager().setMaterialName(base.currentMaterialNode, new_name)
|
||||
base.materialManagementModel.setMaterialName(base.currentMaterialNode, new_name)
|
||||
properties.name = new_name
|
||||
}
|
||||
|
||||
|
|
|
@ -38,14 +38,28 @@ Tab
|
|||
property var setting: qualitySettings.getItem(styleData.row)
|
||||
height: childrenRect.height
|
||||
width: (parent != null) ? parent.width : 0
|
||||
text: (styleData.value.substr(0,1) == "=") ? styleData.value : ""
|
||||
text:
|
||||
{
|
||||
if (styleData.value === undefined)
|
||||
{
|
||||
return ""
|
||||
}
|
||||
return (styleData.value.substr(0,1) == "=") ? styleData.value : ""
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.right: parent.right
|
||||
text: (styleData.value.substr(0,1) == "=") ? catalog.i18nc("@info:status", "Calculated") : styleData.value
|
||||
text:
|
||||
{
|
||||
if (styleData.value === undefined)
|
||||
{
|
||||
return ""
|
||||
}
|
||||
return (styleData.value.substr(0,1) == "=") ? catalog.i18nc("@info:status", "Calculated") : styleData.value
|
||||
}
|
||||
font.strikeout: styleData.column == 1 && setting.user_value != "" && base.isQualityItemCurrentlyActivated
|
||||
font.italic: setting.profile_value_source == "quality_changes" || (setting.user_value != "" && base.isQualityItemCurrentlyActivated)
|
||||
opacity: font.strikeout ? 0.5 : 1
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Copyright (c) 2019 Ultimaker B.V.
|
||||
// Uranium is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
|
@ -7,26 +7,24 @@ import QtQuick.Layouts 1.3
|
|||
import QtQuick.Dialogs 1.2
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.0 as Cura
|
||||
import Cura 1.6 as Cura
|
||||
|
||||
|
||||
Item
|
||||
{
|
||||
id: base
|
||||
|
||||
property QtObject qualityManager: CuraApplication.getQualityManager()
|
||||
property var resetEnabled: false // Keep PreferencesDialog happy
|
||||
property var extrudersModel: CuraApplication.getExtrudersModel()
|
||||
property var qualityManagementModel: CuraApplication.getQualityManagementModel()
|
||||
|
||||
UM.I18nCatalog { id: catalog; name: "cura"; }
|
||||
|
||||
Cura.QualityManagementModel {
|
||||
id: qualitiesModel
|
||||
}
|
||||
|
||||
Label {
|
||||
Label
|
||||
{
|
||||
id: titleLabel
|
||||
anchors {
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
|
@ -38,28 +36,41 @@ Item
|
|||
|
||||
property var hasCurrentItem: base.currentItem != null
|
||||
|
||||
property var currentItem: {
|
||||
property var currentItem:
|
||||
{
|
||||
var current_index = qualityListView.currentIndex;
|
||||
return (current_index == -1) ? null : qualitiesModel.getItem(current_index);
|
||||
return (current_index == -1) ? null : base.qualityManagementModel.getItem(current_index);
|
||||
}
|
||||
|
||||
property var currentItemName: hasCurrentItem ? base.currentItem.name : ""
|
||||
property var currentItemDisplayName: hasCurrentItem ? base.qualityManagementModel.getQualityItemDisplayName(base.currentItem) : ""
|
||||
|
||||
property var isCurrentItemActivated: {
|
||||
if (!base.currentItem) {
|
||||
property var isCurrentItemActivated:
|
||||
{
|
||||
if (!base.currentItem)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.currentItem.name == Cura.MachineManager.activeQualityOrQualityChangesName;
|
||||
if (base.currentItem.is_read_only)
|
||||
{
|
||||
return (base.currentItem.name == Cura.MachineManager.activeQualityOrQualityChangesName) && (base.currentItem.intent_category == Cura.MachineManager.activeIntentCategory);
|
||||
}
|
||||
else
|
||||
{
|
||||
return base.currentItem.name == Cura.MachineManager.activeQualityOrQualityChangesName;
|
||||
}
|
||||
}
|
||||
|
||||
property var canCreateProfile: {
|
||||
property var canCreateProfile:
|
||||
{
|
||||
return isCurrentItemActivated && Cura.MachineManager.hasUserSettings;
|
||||
}
|
||||
|
||||
Row // Button Row
|
||||
{
|
||||
id: buttonRow
|
||||
anchors {
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
top: titleLabel.bottom
|
||||
|
@ -73,10 +84,14 @@ Item
|
|||
text: catalog.i18nc("@action:button", "Activate")
|
||||
iconName: "list-activate"
|
||||
enabled: !isCurrentItemActivated
|
||||
onClicked: {
|
||||
if (base.currentItem.is_read_only) {
|
||||
Cura.MachineManager.setQualityGroup(base.currentItem.quality_group);
|
||||
} else {
|
||||
onClicked:
|
||||
{
|
||||
if (base.currentItem.is_read_only)
|
||||
{
|
||||
Cura.IntentManager.selectIntent(base.currentItem.intent_category, base.currentItem.quality_type);
|
||||
}
|
||||
else
|
||||
{
|
||||
Cura.MachineManager.setQualityChangesGroup(base.currentItem.quality_changes_group);
|
||||
}
|
||||
}
|
||||
|
@ -91,7 +106,8 @@ Item
|
|||
enabled: base.canCreateProfile && !Cura.MachineManager.stacksHaveErrors
|
||||
visible: base.canCreateProfile
|
||||
|
||||
onClicked: {
|
||||
onClicked:
|
||||
{
|
||||
createQualityDialog.object = Cura.ContainerManager.makeUniqueName(base.currentItem.name);
|
||||
createQualityDialog.open();
|
||||
createQualityDialog.selectText();
|
||||
|
@ -107,7 +123,8 @@ Item
|
|||
enabled: !base.canCreateProfile
|
||||
visible: !base.canCreateProfile
|
||||
|
||||
onClicked: {
|
||||
onClicked:
|
||||
{
|
||||
duplicateQualityDialog.object = Cura.ContainerManager.makeUniqueName(base.currentItem.name);
|
||||
duplicateQualityDialog.open();
|
||||
duplicateQualityDialog.selectText();
|
||||
|
@ -121,7 +138,8 @@ Item
|
|||
text: catalog.i18nc("@action:button", "Remove")
|
||||
iconName: "list-remove"
|
||||
enabled: base.hasCurrentItem && !base.currentItem.is_read_only && !base.isCurrentItemActivated
|
||||
onClicked: {
|
||||
onClicked:
|
||||
{
|
||||
forceActiveFocus();
|
||||
confirmRemoveQualityDialog.open();
|
||||
}
|
||||
|
@ -134,7 +152,8 @@ Item
|
|||
text: catalog.i18nc("@action:button", "Rename")
|
||||
iconName: "edit-rename"
|
||||
enabled: base.hasCurrentItem && !base.currentItem.is_read_only
|
||||
onClicked: {
|
||||
onClicked:
|
||||
{
|
||||
renameQualityDialog.object = base.currentItem.name;
|
||||
renameQualityDialog.open();
|
||||
renameQualityDialog.selectText();
|
||||
|
@ -147,7 +166,8 @@ Item
|
|||
id: importMenuButton
|
||||
text: catalog.i18nc("@action:button", "Import")
|
||||
iconName: "document-import"
|
||||
onClicked: {
|
||||
onClicked:
|
||||
{
|
||||
importDialog.open();
|
||||
}
|
||||
}
|
||||
|
@ -159,7 +179,8 @@ Item
|
|||
text: catalog.i18nc("@action:button", "Export")
|
||||
iconName: "document-export"
|
||||
enabled: base.hasCurrentItem && !base.currentItem.is_read_only
|
||||
onClicked: {
|
||||
onClicked:
|
||||
{
|
||||
exportDialog.open();
|
||||
}
|
||||
}
|
||||
|
@ -185,7 +206,7 @@ Item
|
|||
{
|
||||
base.newQualityNameToSelect = newName; // We want to switch to the new profile once it's created
|
||||
base.toActivateNewQuality = true;
|
||||
base.qualityManager.createQualityChanges(newName);
|
||||
base.qualityManagementModel.createQualityChanges(newName);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -195,7 +216,7 @@ Item
|
|||
// This connection makes sure that we will switch to the correct quality after the model gets updated
|
||||
Connections
|
||||
{
|
||||
target: qualitiesModel
|
||||
target: base.qualityManagementModel
|
||||
onItemsChanged:
|
||||
{
|
||||
var toSelectItemName = base.currentItem == null ? "" : base.currentItem.name;
|
||||
|
@ -208,9 +229,9 @@ Item
|
|||
if (toSelectItemName != "")
|
||||
{
|
||||
// Select the required quality name if given
|
||||
for (var idx = 0; idx < qualitiesModel.count; ++idx)
|
||||
for (var idx = 0; idx < base.qualityManagementModel.count; ++idx)
|
||||
{
|
||||
var item = qualitiesModel.getItem(idx);
|
||||
var item = base.qualityManagementModel.getItem(idx);
|
||||
if (item.name == toSelectItemName)
|
||||
{
|
||||
// Switch to the newly created profile if needed
|
||||
|
@ -240,7 +261,7 @@ Item
|
|||
object: "<new name>"
|
||||
onAccepted:
|
||||
{
|
||||
base.qualityManager.duplicateQualityChanges(newName, base.currentItem);
|
||||
base.qualityManagementModel.duplicateQualityChanges(newName, base.currentItem);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -257,7 +278,7 @@ Item
|
|||
|
||||
onYes:
|
||||
{
|
||||
base.qualityManager.removeQualityChangesGroup(base.currentItem.quality_changes_group);
|
||||
base.qualityManagementModel.removeQualityChangesGroup(base.currentItem.quality_changes_group);
|
||||
// reset current item to the first if available
|
||||
qualityListView.currentIndex = -1; // Reset selection.
|
||||
}
|
||||
|
@ -271,7 +292,7 @@ Item
|
|||
object: "<new name>"
|
||||
onAccepted:
|
||||
{
|
||||
var actualNewName = base.qualityManager.renameQualityChangesGroup(base.currentItem.quality_changes_group, newName);
|
||||
var actualNewName = base.qualityManagementModel.renameQualityChangesGroup(base.currentItem.quality_changes_group, newName);
|
||||
base.newQualityNameToSelect = actualNewName; // Select the new name after the model gets updated
|
||||
}
|
||||
}
|
||||
|
@ -282,19 +303,22 @@ Item
|
|||
id: importDialog
|
||||
title: catalog.i18nc("@title:window", "Import Profile")
|
||||
selectExisting: true
|
||||
nameFilters: qualitiesModel.getFileNameFilters("profile_reader")
|
||||
nameFilters: base.qualityManagementModel.getFileNameFilters("profile_reader")
|
||||
folder: CuraApplication.getDefaultPath("dialog_profile_path")
|
||||
onAccepted:
|
||||
{
|
||||
var result = Cura.ContainerManager.importProfile(fileUrl);
|
||||
messageDialog.text = result.message;
|
||||
if (result.status == "ok") {
|
||||
if (result.status == "ok")
|
||||
{
|
||||
messageDialog.icon = StandardIcon.Information;
|
||||
}
|
||||
else if (result.status == "duplicate") {
|
||||
else if (result.status == "duplicate")
|
||||
{
|
||||
messageDialog.icon = StandardIcon.Warning;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
messageDialog.icon = StandardIcon.Critical;
|
||||
}
|
||||
messageDialog.open();
|
||||
|
@ -308,14 +332,15 @@ Item
|
|||
id: exportDialog
|
||||
title: catalog.i18nc("@title:window", "Export Profile")
|
||||
selectExisting: false
|
||||
nameFilters: qualitiesModel.getFileNameFilters("profile_writer")
|
||||
nameFilters: base.qualityManagementModel.getFileNameFilters("profile_writer")
|
||||
folder: CuraApplication.getDefaultPath("dialog_profile_path")
|
||||
onAccepted:
|
||||
{
|
||||
var result = Cura.ContainerManager.exportQualityChangesGroup(base.currentItem.quality_changes_group,
|
||||
fileUrl, selectedNameFilter);
|
||||
|
||||
if (result && result.status == "error") {
|
||||
if (result && result.status == "error")
|
||||
{
|
||||
messageDialog.icon = StandardIcon.Critical;
|
||||
messageDialog.text = result.message;
|
||||
messageDialog.open();
|
||||
|
@ -326,10 +351,12 @@ Item
|
|||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Item
|
||||
{
|
||||
id: contentsItem
|
||||
|
||||
anchors {
|
||||
anchors
|
||||
{
|
||||
top: titleLabel.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
|
@ -343,7 +370,8 @@ Item
|
|||
|
||||
Item
|
||||
{
|
||||
anchors {
|
||||
anchors
|
||||
{
|
||||
top: buttonRow.bottom
|
||||
topMargin: UM.Theme.getSize("default_margin").height
|
||||
left: parent.left
|
||||
|
@ -351,12 +379,16 @@ Item
|
|||
bottom: parent.bottom
|
||||
}
|
||||
|
||||
SystemPalette { id: palette }
|
||||
SystemPalette
|
||||
{
|
||||
id: palette
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: captionLabel
|
||||
anchors {
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
}
|
||||
|
@ -369,14 +401,16 @@ Item
|
|||
ScrollView
|
||||
{
|
||||
id: profileScrollView
|
||||
anchors {
|
||||
anchors
|
||||
{
|
||||
top: captionLabel.visible ? captionLabel.bottom : parent.top
|
||||
topMargin: captionLabel.visible ? UM.Theme.getSize("default_margin").height : 0
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Rectangle
|
||||
{
|
||||
parent: viewport
|
||||
anchors.fill: parent
|
||||
color: palette.light
|
||||
|
@ -390,16 +424,16 @@ Item
|
|||
{
|
||||
id: qualityListView
|
||||
|
||||
model: qualitiesModel
|
||||
model: base.qualityManagementModel
|
||||
|
||||
Component.onCompleted:
|
||||
{
|
||||
var selectedItemName = Cura.MachineManager.activeQualityOrQualityChangesName;
|
||||
|
||||
// Select the required quality name if given
|
||||
for (var idx = 0; idx < qualitiesModel.count; idx++)
|
||||
for (var idx = 0; idx < base.qualityManagementModel.count; idx++)
|
||||
{
|
||||
var item = qualitiesModel.getItem(idx);
|
||||
var item = base.qualityManagementModel.getItem(idx);
|
||||
if (item.name == selectedItemName)
|
||||
{
|
||||
currentIndex = idx;
|
||||
|
@ -408,7 +442,7 @@ Item
|
|||
}
|
||||
}
|
||||
|
||||
section.property: "is_read_only"
|
||||
section.property: "section_name"
|
||||
section.delegate: Rectangle
|
||||
{
|
||||
height: childrenRect.height
|
||||
|
@ -417,7 +451,7 @@ Item
|
|||
{
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_lining").width
|
||||
text: section == "true" ? catalog.i18nc("@label", "Default profiles") : catalog.i18nc("@label", "Custom profiles")
|
||||
text: section
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
|
@ -441,14 +475,27 @@ Item
|
|||
width: Math.floor((parent.width * 0.8))
|
||||
text: model.name
|
||||
elide: Text.ElideRight
|
||||
font.italic: model.name == Cura.MachineManager.activeQualityOrQualityChangesName
|
||||
font.italic:
|
||||
{
|
||||
if (model.is_read_only)
|
||||
{
|
||||
// For built-in qualities, it needs to match both the intent category and the quality name
|
||||
return model.name == Cura.MachineManager.activeQualityOrQualityChangesName && model.intent_category == Cura.MachineManager.activeIntentCategory
|
||||
}
|
||||
else
|
||||
{
|
||||
// For custom qualities, it only needs to match the name
|
||||
return model.name == Cura.MachineManager.activeQualityOrQualityChangesName
|
||||
}
|
||||
}
|
||||
color: parent.isCurrentItem ? palette.highlightedText : palette.text
|
||||
}
|
||||
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
onClicked:
|
||||
{
|
||||
parent.ListView.view.currentIndex = model.index;
|
||||
}
|
||||
}
|
||||
|
@ -461,7 +508,8 @@ Item
|
|||
{
|
||||
id: detailsPanel
|
||||
|
||||
anchors {
|
||||
anchors
|
||||
{
|
||||
left: profileScrollView.right
|
||||
leftMargin: UM.Theme.getSize("default_margin").width
|
||||
top: parent.top
|
||||
|
@ -481,15 +529,17 @@ Item
|
|||
width: parent.width
|
||||
height: childrenRect.height
|
||||
|
||||
Label {
|
||||
text: base.currentItemName
|
||||
Label
|
||||
{
|
||||
text: base.currentItemDisplayName
|
||||
font: UM.Theme.getFont("large_bold")
|
||||
}
|
||||
}
|
||||
|
||||
Flow {
|
||||
Flow
|
||||
{
|
||||
id: currentSettingsActions
|
||||
visible: base.hasCurrentItem && base.currentItem.name == Cura.MachineManager.activeQualityOrQualityChangesName
|
||||
visible: base.hasCurrentItem && base.currentItem.name == Cura.MachineManager.activeQualityOrQualityChangesName && base.currentItem.intent_category == Cura.MachineManager.activeIntentCategory
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: profileName.bottom
|
||||
|
@ -510,7 +560,8 @@ Item
|
|||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
Column
|
||||
{
|
||||
id: profileNotices
|
||||
anchors.top: currentSettingsActions.visible ? currentSettingsActions.bottom : currentSettingsActions.anchors.top
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
|
@ -518,14 +569,16 @@ Item
|
|||
anchors.right: parent.right
|
||||
spacing: UM.Theme.getSize("default_margin").height
|
||||
|
||||
Label {
|
||||
Label
|
||||
{
|
||||
id: defaultsMessage
|
||||
visible: false
|
||||
text: catalog.i18nc("@action:label", "This profile uses the defaults specified by the printer, so it has no settings/overrides in the list below.")
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
Label {
|
||||
Label
|
||||
{
|
||||
id: noCurrentSettingsMessage
|
||||
visible: base.isCurrentItemActivated && !Cura.MachineManager.hasUserSettings
|
||||
text: catalog.i18nc("@action:label", "Your current settings match the selected profile.")
|
||||
|
@ -534,7 +587,6 @@ Item
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
TabView
|
||||
{
|
||||
anchors.left: parent.left
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.0
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Controls 1.4 as OldControls
|
||||
|
||||
import UM 1.3 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
import Cura 1.6 as Cura
|
||||
import ".."
|
||||
|
||||
Item
|
||||
{
|
||||
|
@ -17,19 +18,175 @@ Item
|
|||
|
||||
property var extrudersModel: CuraApplication.getExtrudersModel()
|
||||
|
||||
// Profile selector row
|
||||
GlobalProfileSelector
|
||||
Item
|
||||
{
|
||||
id: globalProfileRow
|
||||
id: intent
|
||||
height: childrenRect.height
|
||||
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
topMargin: parent.padding
|
||||
topMargin: UM.Theme.getSize("default_margin").height
|
||||
left: parent.left
|
||||
leftMargin: parent.padding
|
||||
right: parent.right
|
||||
rightMargin: parent.padding
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: profileLabel
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
right: intentSelection.left
|
||||
}
|
||||
text: catalog.i18nc("@label", "Profile")
|
||||
font: UM.Theme.getFont("medium")
|
||||
renderType: Text.NativeRendering
|
||||
color: UM.Theme.getColor("text")
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
NoIntentIcon
|
||||
{
|
||||
affected_extruders: Cura.MachineManager.extruderPositionsWithNonActiveIntent
|
||||
intent_type: Cura.MachineManager.activeIntentCategory
|
||||
anchors.right: intentSelection.left
|
||||
anchors.rightMargin: UM.Theme.getSize("narrow_margin").width
|
||||
width: Math.round(profileLabel.height * 0.5)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: width
|
||||
visible: affected_extruders.length
|
||||
}
|
||||
|
||||
Button
|
||||
{
|
||||
id: intentSelection
|
||||
onClicked: menu.opened ? menu.close() : menu.open()
|
||||
text: generateActiveQualityText()
|
||||
|
||||
anchors.right: parent.right
|
||||
width: UM.Theme.getSize("print_setup_big_item").width
|
||||
height: textLabel.contentHeight + 2 * UM.Theme.getSize("narrow_margin").height
|
||||
hoverEnabled: true
|
||||
|
||||
baselineOffset: null // If we don't do this, there is a binding loop. WHich is a bit weird, since we override the contentItem anyway...
|
||||
|
||||
contentItem: Label
|
||||
{
|
||||
id: textLabel
|
||||
text: intentSelection.text
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text")
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.verticalCenter: intentSelection.verticalCenter
|
||||
height: contentHeight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
background: Rectangle
|
||||
{
|
||||
id: backgroundItem
|
||||
border.color: intentSelection.hovered ? UM.Theme.getColor("setting_control_border_highlight") : UM.Theme.getColor("setting_control_border")
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
radius: UM.Theme.getSize("default_radius").width
|
||||
color: UM.Theme.getColor("main_background")
|
||||
}
|
||||
|
||||
function generateActiveQualityText()
|
||||
{
|
||||
var resultMap = Cura.MachineManager.activeQualityDisplayNameMap
|
||||
var resultMain = resultMap["main"]
|
||||
var resultSuffix = resultMap["suffix"]
|
||||
var result = ""
|
||||
|
||||
if (Cura.MachineManager.isActiveQualityExperimental)
|
||||
{
|
||||
resultSuffix += " (Experimental)"
|
||||
}
|
||||
|
||||
if (Cura.MachineManager.isActiveQualitySupported)
|
||||
{
|
||||
if (Cura.MachineManager.activeQualityLayerHeight > 0)
|
||||
{
|
||||
result = resultMain
|
||||
if (resultSuffix)
|
||||
{
|
||||
result += " - "
|
||||
}
|
||||
result += "<font color=\"" + UM.Theme.getColor("text_detail") + "\">"
|
||||
if (resultSuffix)
|
||||
{
|
||||
result += resultSuffix
|
||||
}
|
||||
result += " - "
|
||||
result += Cura.MachineManager.activeQualityLayerHeight + "mm"
|
||||
result += "</font>"
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
UM.SimpleButton
|
||||
{
|
||||
id: customisedSettings
|
||||
|
||||
visible: Cura.MachineManager.hasUserSettings
|
||||
width: UM.Theme.getSize("print_setup_icon").width
|
||||
height: UM.Theme.getSize("print_setup_icon").height
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: downArrow.left
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
|
||||
color: hovered ? UM.Theme.getColor("setting_control_button_hover") : UM.Theme.getColor("setting_control_button");
|
||||
iconSource: UM.Theme.getIcon("star")
|
||||
|
||||
onClicked:
|
||||
{
|
||||
forceActiveFocus();
|
||||
Cura.Actions.manageProfiles.trigger()
|
||||
}
|
||||
onEntered:
|
||||
{
|
||||
var content = catalog.i18nc("@tooltip", "Some setting/override values are different from the values stored in the profile.\n\nClick to open the profile manager.")
|
||||
base.showTooltip(intent, Qt.point(-UM.Theme.getSize("default_margin").width, 0), content)
|
||||
}
|
||||
onExited: base.hideTooltip()
|
||||
}
|
||||
UM.RecolorImage
|
||||
{
|
||||
id: downArrow
|
||||
|
||||
|
||||
source: UM.Theme.getIcon("arrow_bottom")
|
||||
width: UM.Theme.getSize("standard_arrow").width
|
||||
height: UM.Theme.getSize("standard_arrow").height
|
||||
|
||||
anchors
|
||||
{
|
||||
right: parent.right
|
||||
verticalCenter: parent.verticalCenter
|
||||
rightMargin: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
|
||||
color: UM.Theme.getColor("setting_control_button")
|
||||
}
|
||||
}
|
||||
|
||||
QualitiesWithIntentMenu
|
||||
{
|
||||
id: menu
|
||||
y: intentSelection.y + intentSelection.height
|
||||
x: intentSelection.x
|
||||
width: intentSelection.width
|
||||
}
|
||||
}
|
||||
|
||||
UM.TabRow
|
||||
|
@ -40,7 +197,7 @@ Item
|
|||
|
||||
anchors
|
||||
{
|
||||
top: globalProfileRow.bottom
|
||||
top: intent.bottom
|
||||
topMargin: UM.Theme.getSize("default_margin").height
|
||||
left: parent.left
|
||||
leftMargin: parent.padding
|
||||
|
@ -99,7 +256,7 @@ Item
|
|||
{
|
||||
anchors
|
||||
{
|
||||
top: tabBar.visible ? tabBar.bottom : globalProfileRow.bottom
|
||||
top: tabBar.visible ? tabBar.bottom : intent.bottom
|
||||
topMargin: -UM.Theme.getSize("default_lining").width
|
||||
left: parent.left
|
||||
leftMargin: parent.padding
|
||||
|
|
|
@ -1,100 +0,0 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 1.1
|
||||
import QtQuick.Controls.Styles 1.1
|
||||
import QtQuick.Layouts 1.2
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
Item
|
||||
{
|
||||
id: globalProfileRow
|
||||
height: childrenRect.height
|
||||
|
||||
Label
|
||||
{
|
||||
id: globalProfileLabel
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
right: globalProfileSelection.left
|
||||
}
|
||||
text: catalog.i18nc("@label", "Profile")
|
||||
font: UM.Theme.getFont("medium")
|
||||
color: UM.Theme.getColor("text")
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
ToolButton
|
||||
{
|
||||
id: globalProfileSelection
|
||||
|
||||
text: generateActiveQualityText()
|
||||
width: UM.Theme.getSize("print_setup_big_item").width
|
||||
height: UM.Theme.getSize("print_setup_big_item").height
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
right: parent.right
|
||||
}
|
||||
tooltip: Cura.MachineManager.activeQualityOrQualityChangesName
|
||||
style: UM.Theme.styles.print_setup_header_button
|
||||
activeFocusOnPress: true
|
||||
menu: Cura.ProfileMenu { }
|
||||
|
||||
function generateActiveQualityText()
|
||||
{
|
||||
var result = Cura.MachineManager.activeQualityOrQualityChangesName
|
||||
if (Cura.MachineManager.isActiveQualityExperimental)
|
||||
{
|
||||
result += " (Experimental)"
|
||||
}
|
||||
|
||||
if (Cura.MachineManager.isActiveQualitySupported)
|
||||
{
|
||||
if (Cura.MachineManager.activeQualityLayerHeight > 0)
|
||||
{
|
||||
result += " <font color=\"" + UM.Theme.getColor("text_detail") + "\">"
|
||||
result += " - "
|
||||
result += Cura.MachineManager.activeQualityLayerHeight + "mm"
|
||||
result += "</font>"
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
UM.SimpleButton
|
||||
{
|
||||
id: customisedSettings
|
||||
|
||||
visible: Cura.MachineManager.hasUserSettings
|
||||
width: UM.Theme.getSize("print_setup_icon").width
|
||||
height: UM.Theme.getSize("print_setup_icon").height
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Math.round(UM.Theme.getSize("setting_preferences_button_margin").width - UM.Theme.getSize("thick_margin").width)
|
||||
|
||||
color: hovered ? UM.Theme.getColor("setting_control_button_hover") : UM.Theme.getColor("setting_control_button");
|
||||
iconSource: UM.Theme.getIcon("star")
|
||||
|
||||
onClicked:
|
||||
{
|
||||
forceActiveFocus();
|
||||
Cura.Actions.manageProfiles.trigger()
|
||||
}
|
||||
onEntered:
|
||||
{
|
||||
var content = catalog.i18nc("@tooltip","Some setting/override values are different from the values stored in the profile.\n\nClick to open the profile manager.")
|
||||
base.showTooltip(globalProfileRow, Qt.point(-UM.Theme.getSize("default_margin").width, 0), content)
|
||||
}
|
||||
onExited: base.hideTooltip()
|
||||
}
|
||||
}
|
||||
}
|
54
resources/qml/PrintSetupSelector/Custom/MenuButton.qml
Normal file
54
resources/qml/PrintSetupSelector/Custom/MenuButton.qml
Normal file
|
@ -0,0 +1,54 @@
|
|||
// Copyright (c) 2019 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.6 as Cura
|
||||
|
||||
Button
|
||||
{
|
||||
// This is a work around for a qml issue. Since the default button uses a private implementation for contentItem
|
||||
// (the so called IconText), which handles the mnemonic conversion (aka; ensuring that &Button) text property
|
||||
// is rendered with the B underlined. Since we're also forced to mix controls 1.0 and 2.0 actions together,
|
||||
// we need a special property for the text of the label if we do want it to be rendered correclty, but don't want
|
||||
// another shortcut to be added (which will cause for "QQuickAction::event: Ambiguous shortcut overload: " to
|
||||
// happen.
|
||||
property string labelText: ""
|
||||
id: button
|
||||
hoverEnabled: true
|
||||
|
||||
background: Rectangle
|
||||
{
|
||||
id: backgroundRectangle
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: button.checked ? UM.Theme.getColor("setting_control_border_highlight") : "transparent"
|
||||
color: button.hovered ? UM.Theme.getColor("action_button_hovered") : "transparent"
|
||||
radius: UM.Theme.getSize("action_button_radius").width
|
||||
}
|
||||
|
||||
// Workarround to ensure that the mnemonic highlighting happens correctly
|
||||
function replaceText(txt)
|
||||
{
|
||||
var index = txt.indexOf("&")
|
||||
if(index >= 0)
|
||||
{
|
||||
txt = txt.replace(txt.substr(index, 2), ("<u>" + txt.substr(index + 1, 1) + "</u>"))
|
||||
}
|
||||
return txt
|
||||
}
|
||||
|
||||
contentItem: Label
|
||||
{
|
||||
id: textLabel
|
||||
text: button.text != "" ? replaceText(button.text) : replaceText(button.labelText)
|
||||
height: contentHeight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
anchors.left: button.left
|
||||
anchors.leftMargin: UM.Theme.getSize("wide_margin").width
|
||||
renderType: Text.NativeRendering
|
||||
font: UM.Theme.getFont("default")
|
||||
color: button.enabled ? UM.Theme.getColor("text") :UM.Theme.getColor("text_inactive")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,315 @@
|
|||
// Copyright (c) 2019 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.6 as Cura
|
||||
|
||||
Popup
|
||||
{
|
||||
id: popup
|
||||
implicitWidth: 400
|
||||
property var dataModel: Cura.IntentCategoryModel {}
|
||||
|
||||
property int defaultMargin: UM.Theme.getSize("default_margin").width
|
||||
property color backgroundColor: UM.Theme.getColor("main_background")
|
||||
property color borderColor: UM.Theme.getColor("lining")
|
||||
|
||||
topPadding: UM.Theme.getSize("narrow_margin").height
|
||||
rightPadding: UM.Theme.getSize("default_lining").width
|
||||
leftPadding: UM.Theme.getSize("default_lining").width
|
||||
|
||||
padding: 0
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
|
||||
background: Cura.RoundedRectangle
|
||||
{
|
||||
color: backgroundColor
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: borderColor
|
||||
cornerSide: Cura.RoundedRectangle.Direction.Down
|
||||
}
|
||||
|
||||
ButtonGroup
|
||||
{
|
||||
id: buttonGroup
|
||||
exclusive: true
|
||||
onClicked: popup.visible = false
|
||||
}
|
||||
|
||||
contentItem: Column
|
||||
{
|
||||
// This repeater adds the intent labels
|
||||
ScrollView
|
||||
{
|
||||
property real maximumHeight: screenScaleFactor * 400
|
||||
contentHeight: dataColumn.height
|
||||
height: Math.min(contentHeight, maximumHeight)
|
||||
clip: true
|
||||
|
||||
ScrollBar.vertical.policy: height == maximumHeight ? ScrollBar.AlwaysOn: ScrollBar.AlwaysOff
|
||||
|
||||
Column
|
||||
{
|
||||
id: dataColumn
|
||||
width: parent.width
|
||||
Repeater
|
||||
{
|
||||
model: dataModel
|
||||
delegate: Item
|
||||
{
|
||||
// We need to set it like that, otherwise we'd have to set the sub model with model: model.qualities
|
||||
// Which obviously won't work due to naming conflicts.
|
||||
property variant subItemModel: model.qualities
|
||||
|
||||
height: childrenRect.height
|
||||
width: popup.contentWidth
|
||||
|
||||
Label
|
||||
{
|
||||
id: headerLabel
|
||||
text: model.name
|
||||
renderType: Text.NativeRendering
|
||||
height: visible ? contentHeight: 0
|
||||
enabled: false
|
||||
visible: qualitiesList.visibleChildren.length > 0
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
|
||||
Column
|
||||
{
|
||||
id: qualitiesList
|
||||
anchors.top: headerLabel.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
// We set it by means of a binding, since then we can use the when condition, which we need to
|
||||
// prevent a binding loop.
|
||||
Binding
|
||||
{
|
||||
target: parent
|
||||
property: "height"
|
||||
value: parent.childrenRect.height
|
||||
when: parent.visibleChildren.length > 0
|
||||
}
|
||||
|
||||
// Add the qualities that belong to the intent
|
||||
Repeater
|
||||
{
|
||||
visible: false
|
||||
model: subItemModel
|
||||
MenuButton
|
||||
{
|
||||
id: button
|
||||
|
||||
onClicked: Cura.IntentManager.selectIntent(model.intent_category, model.quality_type)
|
||||
|
||||
width: parent.width
|
||||
checkable: true
|
||||
visible: model.available
|
||||
text: model.name + " - " + model.layer_height + " mm"
|
||||
checked:
|
||||
{
|
||||
if (Cura.MachineManager.hasCustomQuality)
|
||||
{
|
||||
// When user created profile is active, no quality tickbox should be active.
|
||||
return false;
|
||||
}
|
||||
return Cura.MachineManager.activeQualityType == model.quality_type && Cura.MachineManager.activeIntentCategory == model.intent_category;
|
||||
}
|
||||
ButtonGroup.group: buttonGroup
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//Another "intent category" for custom profiles.
|
||||
Item
|
||||
{
|
||||
height: childrenRect.height
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: customProfileHeader
|
||||
text: catalog.i18nc("@label:header", "Custom profiles")
|
||||
renderType: Text.NativeRendering
|
||||
height: visible ? contentHeight: 0
|
||||
enabled: false
|
||||
visible: profilesList.visibleChildren.length > 1
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
|
||||
Column
|
||||
{
|
||||
id: profilesList
|
||||
anchors
|
||||
{
|
||||
top: customProfileHeader.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
//We set it by means of a binding, since then we can use the
|
||||
//"when" condition, which we need to prevent a binding loop.
|
||||
Binding
|
||||
{
|
||||
target: parent
|
||||
property: "height"
|
||||
value: parent.childrenRect.height
|
||||
when: parent.visibleChildren.length > 1
|
||||
}
|
||||
|
||||
//Add all the custom profiles.
|
||||
Repeater
|
||||
{
|
||||
model: Cura.CustomQualityProfilesDropDownMenuModel
|
||||
MenuButton
|
||||
{
|
||||
onClicked: Cura.MachineManager.setQualityChangesGroup(model.quality_changes_group)
|
||||
|
||||
width: parent.width
|
||||
checkable: true
|
||||
visible: model.available
|
||||
text: model.name
|
||||
checked:
|
||||
{
|
||||
var active_quality_group = Cura.MachineManager.activeQualityChangesGroup
|
||||
|
||||
if (active_quality_group != null)
|
||||
{
|
||||
return active_quality_group.name == model.quality_changes_group.name
|
||||
}
|
||||
return false
|
||||
}
|
||||
ButtonGroup.group: buttonGroup
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
height: UM.Theme.getSize("default_lining").height
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
color: borderColor
|
||||
}
|
||||
|
||||
MenuButton
|
||||
{
|
||||
labelText: Cura.Actions.addProfile.text
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
enabled: Cura.Actions.addProfile.enabled
|
||||
onClicked:
|
||||
{
|
||||
Cura.Actions.addProfile.trigger()
|
||||
popup.visible = false
|
||||
}
|
||||
}
|
||||
MenuButton
|
||||
{
|
||||
labelText: Cura.Actions.updateProfile.text
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
enabled: Cura.Actions.updateProfile.enabled
|
||||
|
||||
onClicked:
|
||||
{
|
||||
popup.visible = false
|
||||
Cura.Actions.updateProfile.trigger()
|
||||
}
|
||||
}
|
||||
MenuButton
|
||||
{
|
||||
text: catalog.i18nc("@action:button", "Discard current changes")
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
enabled: Cura.MachineManager.hasUserSettings
|
||||
|
||||
onClicked:
|
||||
{
|
||||
popup.visible = false
|
||||
Cura.ContainerManager.clearUserContainers()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
height: UM.Theme.getSize("default_lining").width
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
color: borderColor
|
||||
}
|
||||
|
||||
MenuButton
|
||||
{
|
||||
id: manageProfilesButton
|
||||
text: Cura.Actions.manageProfiles.text
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
height: textLabel.contentHeight + 2 * UM.Theme.getSize("narrow_margin").height
|
||||
|
||||
contentItem: Item
|
||||
{
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
|
||||
Label
|
||||
{
|
||||
id: textLabel
|
||||
text: manageProfilesButton.text
|
||||
height: contentHeight
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width + UM.Theme.getSize("narrow_margin").width
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
renderType: Text.NativeRendering
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text")
|
||||
}
|
||||
Label
|
||||
{
|
||||
id: shortcutLabel
|
||||
text: Cura.Actions.manageProfiles.shortcut
|
||||
height: contentHeight
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
renderType: Text.NativeRendering
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text")
|
||||
}
|
||||
}
|
||||
onClicked:
|
||||
{
|
||||
popup.visible = false
|
||||
Cura.Actions.manageProfiles.trigger()
|
||||
}
|
||||
}
|
||||
// spacer
|
||||
Item
|
||||
{
|
||||
width: 2
|
||||
height: UM.Theme.getSize("default_radius").width
|
||||
}
|
||||
}
|
||||
}
|
36
resources/qml/PrintSetupSelector/NoIntentIcon.qml
Normal file
36
resources/qml/PrintSetupSelector/NoIntentIcon.qml
Normal file
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) 2019 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.6 as Cura
|
||||
|
||||
Item
|
||||
{
|
||||
id: icon
|
||||
property var affected_extruders
|
||||
property var intent_type: ""
|
||||
|
||||
implicitWidth: UM.Theme.getSize("section_icon").width
|
||||
implicitHeight: UM.Theme.getSize("section_icon").height
|
||||
|
||||
UM.RecolorImage
|
||||
{
|
||||
source: UM.Theme.getIcon("info")
|
||||
color: UM.Theme.getColor("icon")
|
||||
anchors.fill: parent
|
||||
}
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill: parent
|
||||
hoverEnabled: parent.visible
|
||||
onEntered:
|
||||
{
|
||||
var tooltipContent = catalog.i18ncp("@label %1 is filled in with the type of a profile. %2 is filled with a list of numbers (eg '1' or '1, 2')", "There is no %1 profile for the configuration in extruder %2. The default intent will be used instead", "There is no %1 profile for the configurations in extruders %2. The default intent will be used instead", affected_extruders.length).arg(intent_type).arg(affected_extruders)
|
||||
base.showTooltip(icon.parent, Qt.point(-UM.Theme.getSize("thick_margin").width, 0), tooltipContent)
|
||||
}
|
||||
onExited: base.hideTooltip()
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
|
@ -20,10 +20,16 @@ RowLayout
|
|||
{
|
||||
if (Cura.MachineManager.activeStack)
|
||||
{
|
||||
var text = Cura.MachineManager.activeQualityOrQualityChangesName
|
||||
var resultMap = Cura.MachineManager.activeQualityDisplayNameMap
|
||||
var text = resultMap["main"]
|
||||
if (resultMap["suffix"])
|
||||
{
|
||||
text += " - " + resultMap["suffix"]
|
||||
}
|
||||
|
||||
if (!Cura.MachineManager.hasNotSupportedQuality)
|
||||
{
|
||||
text += " " + layerHeight.properties.value + "mm"
|
||||
text += " - " + layerHeight.properties.value + "mm"
|
||||
text += Cura.MachineManager.isActiveQualityExperimental ? " - " + catalog.i18nc("@label", "Experimental") : ""
|
||||
}
|
||||
return text
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
|
@ -27,7 +27,6 @@ Item
|
|||
|
||||
Column
|
||||
{
|
||||
width: parent.width - 2 * parent.padding
|
||||
spacing: UM.Theme.getSize("wide_margin").height
|
||||
|
||||
anchors
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Copyright (c) 2019 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls 2.3 as Controls2
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.0 as Cura
|
||||
import Cura 1.6 as Cura
|
||||
import ".."
|
||||
|
||||
|
||||
//
|
||||
// Quality profile
|
||||
//
|
||||
Item
|
||||
{
|
||||
id: qualityRow
|
||||
|
@ -20,436 +18,153 @@ Item
|
|||
property real labelColumnWidth: Math.round(width / 3)
|
||||
property real settingsColumnWidth: width - labelColumnWidth
|
||||
|
||||
Timer
|
||||
{
|
||||
id: qualitySliderChangeTimer
|
||||
interval: 50
|
||||
running: false
|
||||
repeat: false
|
||||
onTriggered:
|
||||
{
|
||||
var item = Cura.QualityProfilesDropDownMenuModel.getItem(qualitySlider.value);
|
||||
Cura.MachineManager.activeQualityGroup = item.quality_group;
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: qualityModel.update()
|
||||
|
||||
Connections
|
||||
{
|
||||
target: Cura.QualityProfilesDropDownMenuModel
|
||||
onItemsChanged: qualityModel.update()
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: base
|
||||
onVisibleChanged:
|
||||
{
|
||||
// update needs to be called when the widgets are visible, otherwise the step width calculation
|
||||
// will fail because the width of an invisible item is 0.
|
||||
if (visible)
|
||||
{
|
||||
qualityModel.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListModel
|
||||
{
|
||||
id: qualityModel
|
||||
|
||||
property var totalTicks: 0
|
||||
property var availableTotalTicks: 0
|
||||
property var existingQualityProfile: 0
|
||||
|
||||
property var qualitySliderActiveIndex: 0
|
||||
property var qualitySliderStepWidth: 0
|
||||
property var qualitySliderAvailableMin: 0
|
||||
property var qualitySliderAvailableMax: 0
|
||||
property var qualitySliderMarginRight: 0
|
||||
|
||||
function update ()
|
||||
{
|
||||
reset()
|
||||
|
||||
var availableMin = -1
|
||||
var availableMax = -1
|
||||
|
||||
for (var i = 0; i < Cura.QualityProfilesDropDownMenuModel.rowCount(); i++)
|
||||
{
|
||||
var qualityItem = Cura.QualityProfilesDropDownMenuModel.getItem(i)
|
||||
|
||||
// Add each quality item to the UI quality model
|
||||
qualityModel.append(qualityItem)
|
||||
|
||||
// Set selected value
|
||||
if (Cura.MachineManager.activeQualityType == qualityItem.quality_type)
|
||||
{
|
||||
// set to -1 when switching to user created profile so all ticks are clickable
|
||||
if (Cura.MachineManager.hasCustomQuality)
|
||||
{
|
||||
qualityModel.qualitySliderActiveIndex = -1
|
||||
}
|
||||
else
|
||||
{
|
||||
qualityModel.qualitySliderActiveIndex = i
|
||||
}
|
||||
|
||||
qualityModel.existingQualityProfile = 1
|
||||
}
|
||||
|
||||
// Set min available
|
||||
if (qualityItem.available && availableMin == -1)
|
||||
{
|
||||
availableMin = i
|
||||
}
|
||||
|
||||
// Set max available
|
||||
if (qualityItem.available)
|
||||
{
|
||||
availableMax = i
|
||||
}
|
||||
}
|
||||
|
||||
// Set total available ticks for active slider part
|
||||
if (availableMin != -1)
|
||||
{
|
||||
qualityModel.availableTotalTicks = availableMax - availableMin + 1
|
||||
}
|
||||
|
||||
// Calculate slider values
|
||||
calculateSliderStepWidth(qualityModel.totalTicks)
|
||||
calculateSliderMargins(availableMin, availableMax, qualityModel.totalTicks)
|
||||
|
||||
qualityModel.qualitySliderAvailableMin = availableMin
|
||||
qualityModel.qualitySliderAvailableMax = availableMax
|
||||
}
|
||||
|
||||
function calculateSliderStepWidth (totalTicks)
|
||||
{
|
||||
// Do not use Math.round otherwise the tickmarks won't be aligned
|
||||
qualityModel.qualitySliderStepWidth = totalTicks != 0 ?
|
||||
((settingsColumnWidth - UM.Theme.getSize("print_setup_slider_handle").width) / (totalTicks)) : 0
|
||||
}
|
||||
|
||||
function calculateSliderMargins (availableMin, availableMax, totalTicks)
|
||||
{
|
||||
if (availableMin == -1 || (availableMin == 0 && availableMax == 0))
|
||||
{
|
||||
// Do not use Math.round otherwise the tickmarks won't be aligned
|
||||
qualityModel.qualitySliderMarginRight = settingsColumnWidth / 2
|
||||
}
|
||||
else if (availableMin == availableMax)
|
||||
{
|
||||
// Do not use Math.round otherwise the tickmarks won't be aligned
|
||||
qualityModel.qualitySliderMarginRight = (totalTicks - availableMin) * qualitySliderStepWidth
|
||||
}
|
||||
else
|
||||
{
|
||||
// Do not use Math.round otherwise the tickmarks won't be aligned
|
||||
qualityModel.qualitySliderMarginRight = (totalTicks - availableMax) * qualitySliderStepWidth
|
||||
}
|
||||
}
|
||||
|
||||
function reset () {
|
||||
qualityModel.clear()
|
||||
qualityModel.availableTotalTicks = 0
|
||||
qualityModel.existingQualityProfile = 0
|
||||
|
||||
// check, the ticks count cannot be less than zero
|
||||
qualityModel.totalTicks = Math.max(0, Cura.QualityProfilesDropDownMenuModel.rowCount() - 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Here are the elements that are shown in the left column
|
||||
Item
|
||||
|
||||
Column
|
||||
{
|
||||
id: titleRow
|
||||
width: labelColumnWidth
|
||||
height: childrenRect.height
|
||||
|
||||
Cura.IconWithText
|
||||
{
|
||||
id: qualityRowTitle
|
||||
source: UM.Theme.getIcon("category_layer_height")
|
||||
text: catalog.i18nc("@label", "Layer Height")
|
||||
font: UM.Theme.getFont("medium")
|
||||
anchors.left: parent.left
|
||||
anchors.right: customisedSettings.left
|
||||
}
|
||||
|
||||
UM.SimpleButton
|
||||
{
|
||||
id: customisedSettings
|
||||
|
||||
visible: Cura.SimpleModeSettingsManager.isProfileCustomized || Cura.MachineManager.hasCustomQuality
|
||||
height: visible ? UM.Theme.getSize("print_setup_icon").height : 0
|
||||
width: height
|
||||
anchors
|
||||
{
|
||||
right: parent.right
|
||||
rightMargin: UM.Theme.getSize("default_margin").width
|
||||
leftMargin: UM.Theme.getSize("default_margin").width
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
color: hovered ? UM.Theme.getColor("setting_control_button_hover") : UM.Theme.getColor("setting_control_button")
|
||||
iconSource: UM.Theme.getIcon("reset")
|
||||
|
||||
onClicked:
|
||||
{
|
||||
// if the current profile is user-created, switch to a built-in quality
|
||||
Cura.MachineManager.resetToUseDefaultQuality()
|
||||
}
|
||||
onEntered:
|
||||
{
|
||||
var tooltipContent = catalog.i18nc("@tooltip","You have modified some profile settings. If you want to change these go to custom mode.")
|
||||
base.showTooltip(qualityRow, Qt.point(-UM.Theme.getSize("thick_margin").width, 0), tooltipContent)
|
||||
}
|
||||
onExited: base.hideTooltip()
|
||||
}
|
||||
}
|
||||
|
||||
// Show titles for the each quality slider ticks
|
||||
Item
|
||||
{
|
||||
anchors.left: speedSlider.left
|
||||
anchors.top: speedSlider.bottom
|
||||
height: childrenRect.height
|
||||
|
||||
Repeater
|
||||
{
|
||||
model: qualityModel
|
||||
|
||||
Label
|
||||
{
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.top: parent.top
|
||||
// The height has to be set manually, otherwise it's not automatically calculated in the repeater
|
||||
height: UM.Theme.getSize("default_margin").height
|
||||
color: (Cura.MachineManager.activeMachine != null && Cura.QualityProfilesDropDownMenuModel.getItem(index).available) ? UM.Theme.getColor("quality_slider_available") : UM.Theme.getColor("quality_slider_unavailable")
|
||||
text:
|
||||
{
|
||||
var result = ""
|
||||
if(Cura.MachineManager.activeMachine != null)
|
||||
{
|
||||
result = Cura.QualityProfilesDropDownMenuModel.getItem(index).layer_height
|
||||
|
||||
if(result == undefined)
|
||||
{
|
||||
result = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
result = Number(Math.round(result + "e+2") + "e-2"); //Round to 2 decimals. Javascript makes this difficult...
|
||||
if (result == undefined || result != result) //Parse failure.
|
||||
{
|
||||
result = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
x:
|
||||
{
|
||||
// Make sure the text aligns correctly with each tick
|
||||
if (qualityModel.totalTicks == 0)
|
||||
{
|
||||
// If there is only one tick, align it centrally
|
||||
return Math.round(((settingsColumnWidth) - width) / 2)
|
||||
}
|
||||
else if (index == 0)
|
||||
{
|
||||
return Math.round(settingsColumnWidth / qualityModel.totalTicks) * index
|
||||
}
|
||||
else if (index == qualityModel.totalTicks)
|
||||
{
|
||||
return Math.round(settingsColumnWidth / qualityModel.totalTicks) * index - width
|
||||
}
|
||||
else
|
||||
{
|
||||
return Math.round((settingsColumnWidth / qualityModel.totalTicks) * index - (width / 2))
|
||||
}
|
||||
}
|
||||
font: UM.Theme.getFont("default")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Print speed slider
|
||||
// Two sliders are created, one at the bottom with the unavailable qualities
|
||||
// and the other at the top with the available quality profiles and so the handle to select them.
|
||||
Item
|
||||
{
|
||||
id: speedSlider
|
||||
height: childrenRect.height
|
||||
|
||||
anchors
|
||||
{
|
||||
left: titleRow.right
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
verticalCenter: titleRow.verticalCenter
|
||||
}
|
||||
|
||||
// Draw unavailable slider
|
||||
Slider
|
||||
spacing: UM.Theme.getSize("default_margin").height
|
||||
|
||||
Controls2.ButtonGroup
|
||||
{
|
||||
id: unavailableSlider
|
||||
id: activeProfileButtonGroup
|
||||
exclusive: true
|
||||
onClicked: Cura.IntentManager.selectIntent(button.modelData.intent_category, button.modelData.quality_type)
|
||||
}
|
||||
|
||||
width: parent.width
|
||||
height: qualitySlider.height // Same height as the slider that is on top
|
||||
updateValueWhileDragging : false
|
||||
tickmarksEnabled: true
|
||||
|
||||
minimumValue: 0
|
||||
// maximumValue must be greater than minimumValue to be able to see the handle. While the value is strictly
|
||||
// speaking not always correct, it seems to have the correct behavior (switching from 0 available to 1 available)
|
||||
maximumValue: qualityModel.totalTicks
|
||||
stepSize: 1
|
||||
|
||||
style: SliderStyle
|
||||
Item
|
||||
{
|
||||
height: childrenRect.height
|
||||
anchors
|
||||
{
|
||||
//Draw Unvailable line
|
||||
groove: Item
|
||||
{
|
||||
Rectangle
|
||||
{
|
||||
height: UM.Theme.getSize("print_setup_slider_groove").height
|
||||
width: control.width - UM.Theme.getSize("print_setup_slider_handle").width
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: UM.Theme.getColor("quality_slider_unavailable")
|
||||
}
|
||||
}
|
||||
|
||||
handle: Item {}
|
||||
|
||||
tickmarks: Repeater
|
||||
{
|
||||
id: qualityRepeater
|
||||
model: qualityModel.totalTicks > 0 ? qualityModel : 0
|
||||
|
||||
Rectangle
|
||||
{
|
||||
color: Cura.QualityProfilesDropDownMenuModel.getItem(index).available ? UM.Theme.getColor("quality_slider_available") : UM.Theme.getColor("quality_slider_unavailable")
|
||||
implicitWidth: UM.Theme.getSize("print_setup_slider_tickmarks").width
|
||||
implicitHeight: UM.Theme.getSize("print_setup_slider_tickmarks").height
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
// Do not use Math.round otherwise the tickmarks won't be aligned
|
||||
x: ((UM.Theme.getSize("print_setup_slider_handle").width / 2) - (implicitWidth / 2) + (qualityModel.qualitySliderStepWidth * index))
|
||||
radius: Math.round(implicitWidth / 2)
|
||||
}
|
||||
}
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
// Create a mouse area on top of the unavailable profiles to show a specific tooltip
|
||||
MouseArea
|
||||
Cura.IconWithText
|
||||
{
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
enabled: !Cura.MachineManager.hasCustomQuality
|
||||
id: profileLabel
|
||||
source: UM.Theme.getIcon("category_layer_height")
|
||||
text: catalog.i18nc("@label", "Profiles")
|
||||
font: UM.Theme.getFont("medium")
|
||||
width: labelColumnWidth
|
||||
}
|
||||
UM.SimpleButton
|
||||
{
|
||||
id: customisedSettings
|
||||
|
||||
visible: Cura.SimpleModeSettingsManager.isProfileCustomized || Cura.MachineManager.hasCustomQuality
|
||||
height: visible ? UM.Theme.getSize("print_setup_icon").height : 0
|
||||
width: height
|
||||
anchors
|
||||
{
|
||||
right: profileLabel.right
|
||||
rightMargin: UM.Theme.getSize("default_margin").width
|
||||
leftMargin: UM.Theme.getSize("default_margin").width
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
color: hovered ? UM.Theme.getColor("setting_control_button_hover") : UM.Theme.getColor("setting_control_button")
|
||||
iconSource: UM.Theme.getIcon("reset")
|
||||
|
||||
onClicked:
|
||||
{
|
||||
// if the current profile is user-created, switch to a built-in quality
|
||||
Cura.MachineManager.resetToUseDefaultQuality()
|
||||
}
|
||||
onEntered:
|
||||
{
|
||||
var tooltipContent = catalog.i18nc("@tooltip", "This quality profile is not available for your current material and nozzle configuration. Please change these to enable this quality profile.")
|
||||
base.showTooltip(qualityRow, Qt.point(-UM.Theme.getSize("thick_margin").width, customisedSettings.height), tooltipContent)
|
||||
var tooltipContent = catalog.i18nc("@tooltip","You have modified some profile settings. If you want to change these go to custom mode.")
|
||||
base.showTooltip(qualityRow, Qt.point(-UM.Theme.getSize("thick_margin").width, 0), tooltipContent)
|
||||
}
|
||||
onExited: base.hideTooltip()
|
||||
}
|
||||
}
|
||||
|
||||
// Draw available slider
|
||||
Slider
|
||||
{
|
||||
id: qualitySlider
|
||||
|
||||
width: qualityModel.qualitySliderStepWidth * (qualityModel.availableTotalTicks - 1) + UM.Theme.getSize("print_setup_slider_handle").width
|
||||
height: UM.Theme.getSize("print_setup_slider_handle").height // The handle is the widest element of the slider
|
||||
enabled: qualityModel.totalTicks > 0 && !Cura.SimpleModeSettingsManager.isProfileCustomized
|
||||
visible: qualityModel.availableTotalTicks > 0
|
||||
updateValueWhileDragging : false
|
||||
|
||||
anchors
|
||||
Cura.LabelBar
|
||||
{
|
||||
right: parent.right
|
||||
rightMargin: qualityModel.qualitySliderMarginRight
|
||||
}
|
||||
|
||||
minimumValue: qualityModel.qualitySliderAvailableMin >= 0 ? qualityModel.qualitySliderAvailableMin : 0
|
||||
// maximumValue must be greater than minimumValue to be able to see the handle. While the value is strictly
|
||||
// speaking not always correct, it seems to have the correct behavior (switching from 0 available to 1 available)
|
||||
maximumValue: qualityModel.qualitySliderAvailableMax >= 1 ? qualityModel.qualitySliderAvailableMax : 1
|
||||
stepSize: 1
|
||||
|
||||
value: qualityModel.qualitySliderActiveIndex
|
||||
|
||||
style: SliderStyle
|
||||
{
|
||||
// Draw Available line
|
||||
groove: Item
|
||||
id: labelbar
|
||||
anchors
|
||||
{
|
||||
Rectangle
|
||||
{
|
||||
height: UM.Theme.getSize("print_setup_slider_groove").height
|
||||
width: control.width - UM.Theme.getSize("print_setup_slider_handle").width
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
// Do not use Math.round otherwise the tickmarks won't be aligned
|
||||
x: UM.Theme.getSize("print_setup_slider_handle").width / 2
|
||||
color: UM.Theme.getColor("quality_slider_available")
|
||||
}
|
||||
left: profileLabel.right
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
handle: Rectangle
|
||||
{
|
||||
id: qualityhandleButton
|
||||
color: UM.Theme.getColor("primary")
|
||||
implicitWidth: UM.Theme.getSize("print_setup_slider_handle").width
|
||||
implicitHeight: implicitWidth
|
||||
radius: Math.round(implicitWidth / 2)
|
||||
visible: !Cura.SimpleModeSettingsManager.isProfileCustomized && !Cura.MachineManager.hasCustomQuality && qualityModel.existingQualityProfile
|
||||
}
|
||||
}
|
||||
|
||||
onValueChanged:
|
||||
{
|
||||
// only change if an active machine is set and the slider is visible at all.
|
||||
if (Cura.MachineManager.activeMachine != null && visible)
|
||||
{
|
||||
// prevent updating during view initializing. Trigger only if the value changed by user
|
||||
if (qualitySlider.value != qualityModel.qualitySliderActiveIndex && qualityModel.qualitySliderActiveIndex != -1)
|
||||
{
|
||||
// start updating with short delay
|
||||
qualitySliderChangeTimer.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This mouse area is only used to capture the onHover state and don't propagate it to the unavailable mouse area
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
enabled: !Cura.MachineManager.hasCustomQuality
|
||||
model: Cura.QualityProfilesDropDownMenuModel
|
||||
modelKey: "layer_height"
|
||||
}
|
||||
}
|
||||
|
||||
// This mouse area will only take the mouse events and show a tooltip when the profile in use is
|
||||
// a user created profile
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
visible: Cura.MachineManager.hasCustomQuality
|
||||
|
||||
onEntered:
|
||||
Repeater
|
||||
{
|
||||
model: Cura.IntentCategoryModel {}
|
||||
Item
|
||||
{
|
||||
var tooltipContent = catalog.i18nc("@tooltip", "A custom profile is currently active. To enable the quality slider, choose a default quality profile in Custom tab")
|
||||
base.showTooltip(qualityRow, Qt.point(-UM.Theme.getSize("thick_margin").width, customisedSettings.height), tooltipContent)
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
height: intentCategoryLabel.height
|
||||
|
||||
Label
|
||||
{
|
||||
id: intentCategoryLabel
|
||||
text: model.name
|
||||
width: labelColumnWidth - UM.Theme.getSize("section_icon").width
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("section_icon").width + UM.Theme.getSize("narrow_margin").width
|
||||
font: UM.Theme.getFont("medium")
|
||||
color: UM.Theme.getColor("text")
|
||||
renderType: Text.NativeRendering
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
NoIntentIcon
|
||||
{
|
||||
affected_extruders: Cura.MachineManager.extruderPositionsWithNonActiveIntent
|
||||
intent_type: model.name
|
||||
anchors.right: intentCategoryLabel.right
|
||||
anchors.rightMargin: UM.Theme.getSize("narrow_margin").width
|
||||
width: intentCategoryLabel.height * 0.75
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: width
|
||||
visible: Cura.MachineManager.activeIntentCategory == model.intent_category && affected_extruders.length
|
||||
}
|
||||
|
||||
Cura.RadioCheckbar
|
||||
{
|
||||
anchors
|
||||
{
|
||||
left: intentCategoryLabel.right
|
||||
right: parent.right
|
||||
}
|
||||
dataModel: model["qualities"]
|
||||
buttonGroup: activeProfileButtonGroup
|
||||
|
||||
function checkedFunction(modelItem)
|
||||
{
|
||||
if(Cura.MachineManager.hasCustomQuality)
|
||||
{
|
||||
// When user created profile is active, no quality tickbox should be active.
|
||||
return false
|
||||
}
|
||||
|
||||
if(modelItem === null)
|
||||
{
|
||||
return false
|
||||
}
|
||||
return Cura.MachineManager.activeQualityType == modelItem.quality_type && Cura.MachineManager.activeIntentCategory == modelItem.intent_category
|
||||
}
|
||||
|
||||
isCheckedFunction: checkedFunction
|
||||
}
|
||||
}
|
||||
onExited: base.hideTooltip()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
152
resources/qml/RadioCheckbar.qml
Normal file
152
resources/qml/RadioCheckbar.qml
Normal file
|
@ -0,0 +1,152 @@
|
|||
// Copyright (c) 2019 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.3
|
||||
import UM 1.1 as UM
|
||||
|
||||
Item
|
||||
{
|
||||
id: base
|
||||
property ButtonGroup buttonGroup: null
|
||||
|
||||
property color activeColor: UM.Theme.getColor("primary")
|
||||
property color inactiveColor: UM.Theme.getColor("slider_groove")
|
||||
property color defaultItemColor: UM.Theme.getColor("small_button_active")
|
||||
property int checkboxSize: Math.round(UM.Theme.getSize("radio_button").height * 0.75)
|
||||
property int inactiveMarkerSize: 2 * barSize
|
||||
property int barSize: UM.Theme.getSize("slider_groove_radius").height
|
||||
property var isCheckedFunction // Function that accepts the modelItem and returns if the item should be active.
|
||||
|
||||
implicitWidth: 200 * screenScaleFactor
|
||||
implicitHeight: checkboxSize
|
||||
|
||||
property var dataModel: null
|
||||
|
||||
// The horizontal inactive bar that sits behind the buttons
|
||||
Rectangle
|
||||
{
|
||||
id: inactiveLine
|
||||
color: inactiveColor
|
||||
|
||||
height: barSize
|
||||
|
||||
anchors
|
||||
{
|
||||
left: buttonBar.left
|
||||
right: buttonBar.right
|
||||
leftMargin: Math.round((checkboxSize - inactiveMarkerSize) / 2)
|
||||
rightMargin: Math.round((checkboxSize - inactiveMarkerSize) / 2)
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RowLayout
|
||||
{
|
||||
id: buttonBar
|
||||
anchors.top: parent.top
|
||||
height: checkboxSize
|
||||
width: parent.width
|
||||
spacing: 0
|
||||
|
||||
Repeater
|
||||
{
|
||||
id: repeater
|
||||
model: base.dataModel
|
||||
height: checkboxSize
|
||||
Item
|
||||
{
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
// The last item of the repeater needs to be shorter, as we don't need another part to fit
|
||||
// the horizontal bar. The others should essentially not be limited.
|
||||
Layout.maximumWidth: index + 1 === repeater.count ? activeComponent.width : 200000000
|
||||
|
||||
property bool isEnabled: model.available
|
||||
// The horizontal bar between the checkable options.
|
||||
// Note that the horizontal bar points towards the previous item.
|
||||
Rectangle
|
||||
{
|
||||
property Item previousItem: repeater.itemAt(index - 1)
|
||||
|
||||
height: barSize
|
||||
width: Math.round(buttonBar.width / (repeater.count - 1) - activeComponent.width - 2)
|
||||
color: defaultItemColor
|
||||
|
||||
anchors
|
||||
{
|
||||
right: activeComponent.left
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
visible: previousItem !== null && previousItem.isEnabled && isEnabled
|
||||
}
|
||||
Loader
|
||||
{
|
||||
id: activeComponent
|
||||
sourceComponent: isEnabled? checkboxComponent : disabledComponent
|
||||
width: checkboxSize
|
||||
|
||||
property var modelItem: model
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component
|
||||
{
|
||||
id: disabledComponent
|
||||
Item
|
||||
{
|
||||
height: checkboxSize
|
||||
width: checkboxSize
|
||||
|
||||
Rectangle
|
||||
{
|
||||
// This can (and should) be done wiht a verticalCenter. For some reason it does work in QtCreator
|
||||
// but not when using the exact same QML in Cura.
|
||||
anchors.verticalCenter: parent ? parent.verticalCenter : undefined
|
||||
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
|
||||
height: inactiveMarkerSize
|
||||
width: inactiveMarkerSize
|
||||
radius: Math.round(width / 2)
|
||||
color: inactiveColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component
|
||||
{
|
||||
id: checkboxComponent
|
||||
CheckBox
|
||||
{
|
||||
id: checkbox
|
||||
ButtonGroup.group: buttonGroup
|
||||
width: checkboxSize
|
||||
height: checkboxSize
|
||||
property var modelData: modelItem
|
||||
|
||||
checked: isCheckedFunction(modelItem)
|
||||
indicator: Rectangle
|
||||
{
|
||||
height: checkboxSize
|
||||
width: checkboxSize
|
||||
radius: Math.round(width / 2)
|
||||
|
||||
border.color: defaultItemColor
|
||||
|
||||
Rectangle
|
||||
{
|
||||
anchors
|
||||
{
|
||||
fill: parent
|
||||
}
|
||||
radius: Math.round(width / 2)
|
||||
color: activeColor
|
||||
visible: checkbox.checked
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,7 +20,6 @@ SettingItem
|
|||
textRole: "value"
|
||||
|
||||
anchors.fill: parent
|
||||
highlighted: base.hovered
|
||||
|
||||
onActivated:
|
||||
{
|
||||
|
|
|
@ -86,7 +86,11 @@ Item
|
|||
{
|
||||
id: machineList
|
||||
|
||||
cacheBuffer: 1000000 // Set a large cache to effectively just cache every list item.
|
||||
// CURA-6793
|
||||
// Enabling the buffer seems to cause the blank items issue. When buffer is enabled, if the ListView's
|
||||
// individual item has a dynamic change on its visibility, the ListView doesn't redraw itself.
|
||||
// The default value of cacheBuffer is platform-dependent, so we explicitly disable it here.
|
||||
cacheBuffer: 0
|
||||
|
||||
model: UM.DefinitionContainersModel
|
||||
{
|
||||
|
|
|
@ -14,40 +14,34 @@ import Cura 1.1 as Cura
|
|||
ComboBox
|
||||
{
|
||||
id: control
|
||||
property bool highlighted: false
|
||||
|
||||
states: [
|
||||
State
|
||||
{
|
||||
name: "disabled"
|
||||
when: !control.enabled
|
||||
PropertyChanges { target: backgroundRectangle.border; color: UM.Theme.getColor("setting_control_disabled_border")}
|
||||
PropertyChanges { target: backgroundRectangle; color: UM.Theme.getColor("setting_control_disabled")}
|
||||
PropertyChanges { target: contentLabel; color: UM.Theme.getColor("setting_control_disabled_text")}
|
||||
},
|
||||
State
|
||||
{
|
||||
name: "highlighted"
|
||||
when: control.hovered || control.activeFocus
|
||||
PropertyChanges { target: backgroundRectangle.border; color: UM.Theme.getColor("setting_control_border_highlight") }
|
||||
PropertyChanges { target: backgroundRectangle; color: UM.Theme.getColor("setting_control_highlight")}
|
||||
}
|
||||
]
|
||||
|
||||
background: Rectangle
|
||||
{
|
||||
color:
|
||||
{
|
||||
if (!enabled)
|
||||
{
|
||||
return UM.Theme.getColor("setting_control_disabled")
|
||||
}
|
||||
|
||||
if (control.hovered || control.activeFocus || control.highlighted)
|
||||
{
|
||||
return UM.Theme.getColor("setting_control_highlight")
|
||||
}
|
||||
|
||||
return UM.Theme.getColor("setting_control")
|
||||
}
|
||||
id: backgroundRectangle
|
||||
color: UM.Theme.getColor("setting_control")
|
||||
|
||||
radius: UM.Theme.getSize("setting_control_radius").width
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color:
|
||||
{
|
||||
if (!enabled)
|
||||
{
|
||||
return UM.Theme.getColor("setting_control_disabled_border")
|
||||
}
|
||||
border.color: UM.Theme.getColor("setting_control_border")
|
||||
|
||||
if (control.hovered || control.activeFocus || control.highlighted)
|
||||
{
|
||||
return UM.Theme.getColor("setting_control_border_highlight")
|
||||
}
|
||||
|
||||
return UM.Theme.getColor("setting_control_border")
|
||||
}
|
||||
}
|
||||
|
||||
indicator: UM.RecolorImage
|
||||
|
@ -67,6 +61,7 @@ ComboBox
|
|||
|
||||
contentItem: Label
|
||||
{
|
||||
id: contentLabel
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("setting_unit_margin").width
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
@ -76,7 +71,7 @@ ComboBox
|
|||
textFormat: Text.PlainText
|
||||
renderType: Text.NativeRendering
|
||||
font: UM.Theme.getFont("default")
|
||||
color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text")
|
||||
color: UM.Theme.getColor("setting_control_text")
|
||||
elide: Text.ElideRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ setting_version = 10
|
|||
type = quality
|
||||
quality_type = coarse
|
||||
weight = 3
|
||||
global_quality = True
|
||||
|
||||
[values]
|
||||
layer_height = 0.08
|
||||
|
|
|
@ -8,6 +8,7 @@ setting_version = 10
|
|||
type = quality
|
||||
quality_type = draft
|
||||
weight = -2
|
||||
global_quality = True
|
||||
|
||||
[values]
|
||||
layer_height = 0.1
|
||||
|
|
|
@ -8,6 +8,7 @@ setting_version = 10
|
|||
type = quality
|
||||
quality_type = extra_high
|
||||
weight = 0
|
||||
global_quality = True
|
||||
|
||||
[values]
|
||||
layer_height = 0.02
|
||||
|
|
|
@ -8,6 +8,7 @@ setting_version = 10
|
|||
type = quality
|
||||
quality_type = high
|
||||
weight = 1
|
||||
global_quality = True
|
||||
|
||||
[values]
|
||||
layer_height = 0.04
|
||||
|
|
|
@ -8,6 +8,7 @@ setting_version = 10
|
|||
type = quality
|
||||
quality_type = normal
|
||||
weight = 0
|
||||
global_quality = True
|
||||
|
||||
[values]
|
||||
layer_height = 0.06
|
||||
|
|
|
@ -8,6 +8,7 @@ setting_version = 10
|
|||
type = quality
|
||||
quality_type = draft
|
||||
weight = -2
|
||||
global_quality = True
|
||||
|
||||
[values]
|
||||
brim_width = 4.0
|
||||
|
|
|
@ -8,6 +8,7 @@ setting_version = 10
|
|||
type = quality
|
||||
quality_type = high
|
||||
weight = 1
|
||||
global_quality = True
|
||||
|
||||
[values]
|
||||
brim_width = 4.0
|
||||
|
|
|
@ -8,6 +8,7 @@ setting_version = 10
|
|||
type = quality
|
||||
quality_type = normal
|
||||
weight = 0
|
||||
global_quality = True
|
||||
|
||||
[values]
|
||||
brim_width = 4.0
|
||||
|
|
|
@ -34,8 +34,8 @@ layer_start_x = 250
|
|||
layer_start_y = 250
|
||||
coasting_enable = False
|
||||
wall_line_count = 2
|
||||
material_print_temperature = =default_material_print_temperature
|
||||
material_initial_print_temperature = =material_print_temperature
|
||||
material_final_print_temperature = =material_print_temperature
|
||||
material_print_temperature = =default_material_print_temperature
|
||||
material_initial_print_temperature = =material_print_temperature
|
||||
material_final_print_temperature = =material_print_temperature
|
||||
z_seam_corner = z_seam_corner_none
|
||||
optimize_wall_printing_order = True
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
[general]
|
||||
version = 4
|
||||
name = Fast
|
||||
definition = ultimaker_s5
|
||||
|
||||
[metadata]
|
||||
setting_version = 10
|
||||
type = quality
|
||||
quality_type = draft
|
||||
weight = -2
|
||||
material = generic_abs
|
||||
variant = AA 0.4
|
||||
buildplate = Aluminum
|
||||
|
||||
[values]
|
||||
machine_nozzle_cool_down_speed = 0.85
|
||||
machine_nozzle_heat_up_speed = 1.5
|
||||
material_print_temperature = =default_material_print_temperature + 20
|
||||
material_initial_print_temperature = =material_print_temperature - 15
|
||||
material_final_print_temperature = =material_print_temperature - 20
|
||||
prime_tower_enable = False
|
||||
skin_overlap = 20
|
||||
speed_print = 60
|
||||
speed_layer_0 = 20
|
||||
speed_topbottom = =math.ceil(speed_print * 35 / 60)
|
||||
speed_wall = =math.ceil(speed_print * 45 / 60)
|
||||
speed_wall_0 = =math.ceil(speed_wall * 35 / 45)
|
||||
wall_thickness = 1
|
||||
|
||||
infill_line_width = =round(line_width * 0.4 / 0.35, 2)
|
||||
speed_infill = =math.ceil(speed_print * 50 / 60)
|
||||
|
||||
material_bed_temperature_layer_0 = 100
|
||||
default_material_bed_temperature = 90
|
||||
prime_blob_enable = False
|
||||
layer_height_0 = 0.17
|
|
@ -1,35 +0,0 @@
|
|||
[general]
|
||||
version = 4
|
||||
name = Normal
|
||||
definition = ultimaker_s5
|
||||
|
||||
[metadata]
|
||||
setting_version = 10
|
||||
type = quality
|
||||
quality_type = fast
|
||||
weight = -1
|
||||
material = generic_abs
|
||||
variant = AA 0.4
|
||||
buildplate = Aluminum
|
||||
|
||||
[values]
|
||||
cool_min_speed = 7
|
||||
machine_nozzle_cool_down_speed = 0.85
|
||||
machine_nozzle_heat_up_speed = 1.5
|
||||
material_print_temperature = =default_material_print_temperature + 15
|
||||
material_initial_print_temperature = =material_print_temperature - 15
|
||||
material_final_print_temperature = =material_print_temperature - 20
|
||||
prime_tower_enable = False
|
||||
speed_print = 60
|
||||
speed_layer_0 = 20
|
||||
speed_topbottom = =math.ceil(speed_print * 30 / 60)
|
||||
speed_wall = =math.ceil(speed_print * 40 / 60)
|
||||
speed_wall_0 = =math.ceil(speed_wall * 30 / 40)
|
||||
|
||||
infill_line_width = =round(line_width * 0.4 / 0.35, 2)
|
||||
speed_infill = =math.ceil(speed_print * 45 / 60)
|
||||
|
||||
material_bed_temperature_layer_0 = 100
|
||||
default_material_bed_temperature = 90
|
||||
prime_blob_enable = False
|
||||
layer_height_0 = 0.17
|
|
@ -1,34 +0,0 @@
|
|||
[general]
|
||||
version = 4
|
||||
name = Extra Fine
|
||||
definition = ultimaker_s5
|
||||
|
||||
[metadata]
|
||||
setting_version = 10
|
||||
type = quality
|
||||
quality_type = high
|
||||
weight = 1
|
||||
material = generic_abs
|
||||
variant = AA 0.4
|
||||
buildplate = Aluminum
|
||||
|
||||
[values]
|
||||
cool_min_speed = 12
|
||||
machine_nozzle_cool_down_speed = 0.8
|
||||
machine_nozzle_heat_up_speed = 1.5
|
||||
material_print_temperature = =default_material_print_temperature + 5
|
||||
material_initial_print_temperature = =material_print_temperature - 15
|
||||
material_final_print_temperature = =material_print_temperature - 20
|
||||
prime_tower_enable = False
|
||||
speed_print = 50
|
||||
speed_layer_0 = 20
|
||||
speed_topbottom = =math.ceil(speed_print * 30 / 50)
|
||||
speed_wall = =math.ceil(speed_print * 30 / 50)
|
||||
|
||||
infill_line_width = =round(line_width * 0.4 / 0.35, 2)
|
||||
speed_infill = =math.ceil(speed_print * 40 / 50)
|
||||
|
||||
material_bed_temperature_layer_0 = 100
|
||||
default_material_bed_temperature = 90
|
||||
prime_blob_enable = False
|
||||
layer_height_0 = 0.17
|
|
@ -1,34 +0,0 @@
|
|||
[general]
|
||||
version = 4
|
||||
name = Fine
|
||||
definition = ultimaker_s5
|
||||
|
||||
[metadata]
|
||||
setting_version = 10
|
||||
type = quality
|
||||
quality_type = normal
|
||||
weight = 0
|
||||
material = generic_abs
|
||||
variant = AA 0.4
|
||||
buildplate = Aluminum
|
||||
|
||||
[values]
|
||||
machine_nozzle_cool_down_speed = 0.85
|
||||
machine_nozzle_heat_up_speed = 1.5
|
||||
material_print_temperature = =default_material_print_temperature + 10
|
||||
material_initial_print_temperature = =material_print_temperature - 15
|
||||
material_final_print_temperature = =material_print_temperature - 20
|
||||
prime_tower_enable = False
|
||||
speed_print = 55
|
||||
speed_layer_0 = 20
|
||||
speed_topbottom = =math.ceil(speed_print * 30 / 55)
|
||||
speed_wall = =math.ceil(speed_print * 30 / 55)
|
||||
|
||||
infill_line_width = =round(line_width * 0.4 / 0.35, 2)
|
||||
speed_infill = =math.ceil(speed_print * 40 / 55)
|
||||
|
||||
material_bed_temperature_layer_0 = 100
|
||||
default_material_bed_temperature = 90
|
||||
prime_blob_enable = False
|
||||
layer_height_0 = 0.17
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
[general]
|
||||
version = 4
|
||||
name = Fast
|
||||
definition = ultimaker_s5
|
||||
|
||||
[metadata]
|
||||
setting_version = 10
|
||||
type = quality
|
||||
quality_type = draft
|
||||
weight = -2
|
||||
material = generic_cpe_plus
|
||||
variant = AA 0.4
|
||||
buildplate = Aluminum
|
||||
|
||||
[values]
|
||||
acceleration_enabled = True
|
||||
acceleration_print = 4000
|
||||
cool_fan_speed_max = 80
|
||||
cool_min_speed = 5
|
||||
infill_line_width = =round(line_width * 0.35 / 0.35, 2)
|
||||
infill_overlap = 0
|
||||
infill_wipe_dist = 0
|
||||
jerk_enabled = True
|
||||
jerk_print = 25
|
||||
machine_min_cool_heat_time_window = 15
|
||||
material_final_print_temperature = =material_print_temperature - 10
|
||||
material_initial_print_temperature = =material_print_temperature - 5
|
||||
material_print_temperature = =default_material_print_temperature + 10
|
||||
material_print_temperature_layer_0 = =material_print_temperature
|
||||
multiple_mesh_overlap = 0
|
||||
prime_tower_enable = True
|
||||
prime_tower_wipe_enabled = True
|
||||
retraction_combing_max_distance = 50
|
||||
retraction_extrusion_window = 1
|
||||
retraction_hop = 0.2
|
||||
retraction_hop_enabled = False
|
||||
retraction_hop_only_when_collides = True
|
||||
skin_overlap = 20
|
||||
speed_layer_0 = 20
|
||||
speed_print = 50
|
||||
speed_topbottom = =math.ceil(speed_print * 40 / 50)
|
||||
|
||||
speed_wall = =math.ceil(speed_print * 50 / 50)
|
||||
speed_wall_0 = =math.ceil(speed_wall * 40 / 50)
|
||||
support_bottom_distance = =support_z_distance
|
||||
support_z_distance = =layer_height
|
||||
wall_0_inset = 0
|
||||
wall_thickness = 1
|
||||
|
||||
material_bed_temperature_layer_0 = 115
|
||||
default_material_bed_temperature = 105
|
||||
prime_blob_enable = False
|
||||
layer_height_0 = 0.17
|
||||
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue