Merge branch 'master' into feature_sync_button

This commit is contained in:
Diego Prado Gesto 2018-03-06 10:24:11 +01:00
commit 13e18c9d54
568 changed files with 6406 additions and 6604 deletions

View file

@ -1,16 +1,14 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import copy
import os.path
import urllib.parse
import uuid
from typing import Any, Dict, List, Union
from typing import Dict, Union
from PyQt5.QtCore import QObject, QUrl, QVariant
from UM.FlameProfiler import pyqtSlot
from PyQt5.QtWidgets import QMessageBox
from UM.Util import parseBool
from UM.PluginRegistry import PluginRegistry
from UM.SaveFile import SaveFile
@ -22,7 +20,6 @@ from UM.Application import Application
from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.DefinitionContainer import DefinitionContainer
from UM.Settings.InstanceContainer import InstanceContainer
from cura.QualityManager import QualityManager
from UM.MimeTypeDatabase import MimeTypeNotFoundError
from UM.Settings.ContainerRegistry import ContainerRegistry
@ -30,9 +27,11 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.i18n import i18nCatalog
from cura.Settings.ExtruderManager import ExtruderManager
from cura.Settings.ExtruderStack import ExtruderStack
catalog = i18nCatalog("cura")
## Manager class that contains common actions to deal with containers in Cura.
#
# This is primarily intended as a class to be able to perform certain actions
@ -42,154 +41,12 @@ class ContainerManager(QObject):
def __init__(self, parent = None):
super().__init__(parent)
self._application = Application.getInstance()
self._container_registry = ContainerRegistry.getInstance()
self._machine_manager = Application.getInstance().getMachineManager()
self._machine_manager = self._application.getMachineManager()
self._material_manager = self._application.getMaterialManager()
self._container_name_filters = {}
## Create a duplicate of the specified container
#
# This will create and add a duplicate of the container corresponding
# to the container ID.
#
# \param container_id \type{str} The ID of the container to duplicate.
#
# \return The ID of the new container, or an empty string if duplication failed.
@pyqtSlot(str, result = str)
def duplicateContainer(self, container_id):
#TODO: It should be able to duplicate a container of which only the metadata is known.
containers = self._container_registry.findContainers(id = container_id)
if not containers:
Logger.log("w", "Could duplicate container %s because it was not found.", container_id)
return ""
container = containers[0]
new_container = self.duplicateContainerInstance(container)
return new_container.getId()
## Create a duplicate of the given container instance
#
# This will create and add a duplicate of the container that was passed.
#
# \param container \type{ContainerInterface} The container to duplicate.
#
# \return The duplicated container, or None if duplication failed.
def duplicateContainerInstance(self, container):
new_container = None
new_name = self._container_registry.uniqueName(container.getName())
# Only InstanceContainer has a duplicate method at the moment.
# So fall back to serialize/deserialize when no duplicate method exists.
if hasattr(container, "duplicate"):
new_container = container.duplicate(new_name)
else:
new_container = container.__class__(new_name)
new_container.deserialize(container.serialize())
new_container.setName(new_name)
# TODO: we probably don't want to add it to the registry here!
if new_container:
self._container_registry.addContainer(new_container)
return new_container
## Change the name of a specified container to a new name.
#
# \param container_id \type{str} The ID of the container to change the name of.
# \param new_id \type{str} The new ID of the container.
# \param new_name \type{str} The new name of the specified container.
#
# \return True if successful, False if not.
@pyqtSlot(str, str, str, result = bool)
def renameContainer(self, container_id, new_id, new_name):
containers = self._container_registry.findContainers(id = container_id)
if not containers:
Logger.log("w", "Could rename container %s because it was not found.", container_id)
return False
container = containers[0]
# First, remove the container from the registry. This will clean up any files related to the container.
self._container_registry.removeContainer(container_id)
# Ensure we have a unique name for the container
new_name = self._container_registry.uniqueName(new_name)
# Then, update the name and ID of the container
container.setName(new_name)
container._id = new_id # TODO: Find a nicer way to set a new, unique ID
# Finally, re-add the container so it will be properly serialized again.
self._container_registry.addContainer(container)
return True
## Remove the specified container.
#
# \param container_id \type{str} The ID of the container to remove.
#
# \return True if the container was successfully removed, False if not.
@pyqtSlot(str, result = bool)
def removeContainer(self, container_id):
containers = self._container_registry.findContainers(id = container_id)
if not containers:
Logger.log("w", "Could not remove container %s because it was not found.", container_id)
return False
self._container_registry.removeContainer(containers[0].getId())
return True
## Merge a container with another.
#
# This will try to merge one container into the other, by going through the container
# and setting the right properties on the other container.
#
# \param merge_into_id \type{str} The ID of the container to merge into.
# \param merge_id \type{str} The ID of the container to merge.
#
# \return True if successfully merged, False if not.
@pyqtSlot(str, result = bool)
def mergeContainers(self, merge_into_id, merge_id):
containers = self._container_registry.findContainers(id = merge_into_id)
if not containers:
Logger.log("w", "Could merge into container %s because it was not found.", merge_into_id)
return False
merge_into = containers[0]
containers = self._container_registry.findContainers(id = merge_id)
if not containers:
Logger.log("w", "Could not merge container %s because it was not found", merge_id)
return False
merge = containers[0]
if not isinstance(merge, type(merge_into)):
Logger.log("w", "Cannot merge two containers of different types")
return False
self._performMerge(merge_into, merge)
return True
## Clear the contents of a container.
#
# \param container_id \type{str} The ID of the container to clear.
#
# \return True if successful, False if not.
@pyqtSlot(str, result = bool)
def clearContainer(self, container_id):
if self._container_registry.isReadOnly(container_id):
Logger.log("w", "Cannot clear read-only container %s", container_id)
return False
containers = self._container_registry.findContainers(id = container_id)
if not containers:
Logger.log("w", "Could clear container %s because it was not found.", container_id)
return False
containers[0].clear()
return True
@pyqtSlot(str, str, result=str)
def getContainerMetaDataEntry(self, container_id, entry_name):
metadatas = self._container_registry.findContainersMetadata(id = container_id)
@ -211,18 +68,15 @@ class ContainerManager(QObject):
# \param entry_value The new value of the entry.
#
# \return True if successful, False if not.
@pyqtSlot(str, str, str, result = bool)
def setContainerMetaDataEntry(self, container_id, entry_name, entry_value):
if self._container_registry.isReadOnly(container_id):
Logger.log("w", "Cannot set metadata of read-only container %s.", container_id)
# TODO: This is ONLY used by MaterialView for material containers. Maybe refactor this.
@pyqtSlot("QVariant", str, str)
def setContainerMetaDataEntry(self, container_node, entry_name, entry_value):
root_material_id = container_node.metadata["base_file"]
if self._container_registry.isReadOnly(root_material_id):
Logger.log("w", "Cannot set metadata of read-only container %s.", root_material_id)
return False
containers = self._container_registry.findContainers(id = container_id) #We need the complete container, since we need to know whether the container is read-only or not.
if not containers:
Logger.log("w", "Could not set metadata of container %s because it was not found.", container_id)
return False
container = containers[0]
material_group = self._material_manager.getMaterialGroup(root_material_id)
entries = entry_name.split("/")
entry_name = entries.pop()
@ -230,7 +84,7 @@ class ContainerManager(QObject):
sub_item_changed = False
if entries:
root_name = entries.pop(0)
root = container.getMetaDataEntry(root_name)
root = material_group.root_material_node.metadata.get(root_name)
item = root
for _ in range(len(entries)):
@ -243,12 +97,11 @@ class ContainerManager(QObject):
entry_name = root_name
entry_value = root
container = material_group.root_material_node.getContainer()
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)
return True
## Set a setting property of the specified container.
#
# This will set the specified property of the specified setting of the container
@ -306,60 +159,6 @@ class ContainerManager(QObject):
return container.getProperty(setting_key, property_name)
## Set the name of the specified container.
@pyqtSlot(str, str, result = bool)
def setContainerName(self, container_id, new_name):
if self._container_registry.isReadOnly(container_id):
Logger.log("w", "Cannot set name of read-only container %s.", container_id)
return False
containers = self._container_registry.findContainers(id = container_id) #We need to get the full container, not just metadata, since we need to know whether it's read-only.
if not containers:
Logger.log("w", "Could not set name of container %s because it was not found.", container_id)
return False
containers[0].setName(new_name)
return True
## Find instance containers matching certain criteria.
#
# This effectively forwards to
# ContainerRegistry::findInstanceContainersMetadata.
#
# \param criteria A dict of key - value pairs to search for.
#
# \return A list of container IDs that match the given criteria.
@pyqtSlot("QVariantMap", result = "QVariantList")
def findInstanceContainers(self, criteria):
return [entry["id"] for entry in self._container_registry.findInstanceContainersMetadata(**criteria)]
@pyqtSlot(str, result = bool)
def isContainerUsed(self, container_id):
Logger.log("d", "Checking if container %s is currently used", container_id)
# check if this is a material container. If so, check if any material with the same base is being used by any
# stacks.
container_ids_to_check = [container_id]
container_results = self._container_registry.findInstanceContainersMetadata(id = container_id, type = "material")
if container_results:
this_container = container_results[0]
material_base_file = this_container["id"]
if "base_file" in this_container:
material_base_file = this_container["base_file"]
# check all material container IDs with the same base
material_containers = self._container_registry.findInstanceContainersMetadata(base_file = material_base_file,
type = "material")
if material_containers:
container_ids_to_check = [container["id"] for container in material_containers]
all_stacks = self._container_registry.findContainerStacks()
for stack in all_stacks:
for used_container_id in container_ids_to_check:
if used_container_id in [child.getId() for child in stack.getContainers()]:
Logger.log("d", "The container is in use by %s", stack.getId())
return True
return False
@pyqtSlot(str, result = str)
def makeUniqueName(self, original_name):
return self._container_registry.uniqueName(original_name)
@ -399,7 +198,7 @@ class ContainerManager(QObject):
@pyqtSlot(str, str, QUrl, result = "QVariantMap")
def exportContainer(self, container_id: str, file_type: str, file_url_or_string: Union[QUrl, str]) -> Dict[str, str]:
if not container_id or not file_type or not file_url_or_string:
return { "status": "error", "message": "Invalid arguments"}
return {"status": "error", "message": "Invalid arguments"}
if isinstance(file_url_or_string, QUrl):
file_url = file_url_or_string.toLocalFile()
@ -407,20 +206,20 @@ class ContainerManager(QObject):
file_url = file_url_or_string
if not file_url:
return { "status": "error", "message": "Invalid path"}
return {"status": "error", "message": "Invalid path"}
mime_type = None
if not file_type in self._container_name_filters:
if file_type not in self._container_name_filters:
try:
mime_type = MimeTypeDatabase.getMimeTypeForFile(file_url)
except MimeTypeNotFoundError:
return { "status": "error", "message": "Unknown File Type" }
return {"status": "error", "message": "Unknown File Type"}
else:
mime_type = self._container_name_filters[file_type]["mime"]
containers = self._container_registry.findContainers(id = container_id)
if not containers:
return { "status": "error", "message": "Container not found"}
return {"status": "error", "message": "Container not found"}
container = containers[0]
if Platform.isOSX() and "." in file_url:
@ -437,12 +236,12 @@ class ContainerManager(QObject):
result = QMessageBox.question(None, catalog.i18nc("@title:window", "File Already Exists"),
catalog.i18nc("@label Don't translate the XML tag <filename>!", "The file <filename>{0}</filename> already exists. Are you sure you want to overwrite it?").format(file_url))
if result == QMessageBox.No:
return { "status": "cancelled", "message": "User cancelled"}
return {"status": "cancelled", "message": "User cancelled"}
try:
contents = container.serialize()
except NotImplementedError:
return { "status": "error", "message": "Unable to serialize container"}
return {"status": "error", "message": "Unable to serialize container"}
if contents is None:
return {"status": "error", "message": "Serialization returned None. Unable to write to file"}
@ -450,7 +249,7 @@ class ContainerManager(QObject):
with SaveFile(file_url, "w") as f:
f.write(contents)
return { "status": "success", "message": "Succesfully exported container", "path": file_url}
return {"status": "success", "message": "Successfully exported container", "path": file_url}
## Imports a profile from a file
#
@ -461,7 +260,7 @@ class ContainerManager(QObject):
@pyqtSlot(QUrl, result = "QVariantMap")
def importMaterialContainer(self, file_url_or_string: Union[QUrl, str]) -> Dict[str, str]:
if not file_url_or_string:
return { "status": "error", "message": "Invalid path"}
return {"status": "error", "message": "Invalid path"}
if isinstance(file_url_or_string, QUrl):
file_url = file_url_or_string.toLocalFile()
@ -469,16 +268,16 @@ class ContainerManager(QObject):
file_url = file_url_or_string
if not file_url or not os.path.exists(file_url):
return { "status": "error", "message": "Invalid path" }
return {"status": "error", "message": "Invalid path"}
try:
mime_type = MimeTypeDatabase.getMimeTypeForFile(file_url)
except MimeTypeNotFoundError:
return { "status": "error", "message": "Could not determine mime type of file" }
return {"status": "error", "message": "Could not determine mime type of file"}
container_type = self._container_registry.getContainerForMimeType(mime_type)
if not container_type:
return { "status": "error", "message": "Could not find a container to handle the specified file."}
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)
@ -489,7 +288,7 @@ class ContainerManager(QObject):
with open(file_url, "rt", encoding = "utf-8") as f:
container.deserialize(f.read())
except PermissionError:
return { "status": "error", "message": "Permission denied when trying to read the file"}
return {"status": "error", "message": "Permission denied when trying to read the file"}
except Exception as ex:
return {"status": "error", "message": str(ex)}
@ -497,7 +296,7 @@ class ContainerManager(QObject):
self._container_registry.addContainer(container)
return { "status": "success", "message": "Successfully imported container {0}".format(container.getName()) }
return {"status": "success", "message": "Successfully imported container {0}".format(container.getName())}
## Update the current active quality changes container with the settings from the user container.
#
@ -522,7 +321,7 @@ class ContainerManager(QObject):
self._performMerge(quality_changes, stack.getTop())
self._machine_manager.activeQualityChanged.emit()
self._machine_manager.activeQualityChangesGroupChanged.emit()
return True
@ -542,407 +341,37 @@ class ContainerManager(QObject):
for container in send_emits_containers:
container.sendPostponedEmits()
## 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.
#
# \return \type{bool} True if the operation was successfully, False if not.
@pyqtSlot(str, result = bool)
def createQualityChanges(self, base_name):
global_stack = Application.getInstance().getGlobalContainerStack()
if not global_stack:
return False
active_quality_name = self._machine_manager.activeQualityName
if active_quality_name == "":
Logger.log("w", "No quality container found in stack %s, cannot create profile", global_stack.getId())
return False
self._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.
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
user_container = stack.getTop()
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_id = None if stack is global_stack else QualityManager.getInstance().getParentMachineDefinition(stack.getBottom()).getId()
new_changes = self._createQualityChanges(quality_container, unique_name,
Application.getInstance().getGlobalContainerStack().getBottom(),
extruder_id)
self._performMerge(new_changes, quality_changes_container, clear_settings = False)
self._performMerge(new_changes, user_container)
self._container_registry.addContainer(new_changes)
stack.replaceContainer(stack.getContainerIndex(quality_changes_container), new_changes)
self._machine_manager.activeQualityChanged.emit()
return True
## Remove all quality changes containers matching a specified name.
#
# This will search for quality_changes containers matching the supplied name and remove them.
# Note that if the machine specifies that qualities should be filtered by machine and/or material
# only the containers related to the active machine/material are removed.
#
# \param quality_name The name of the quality changes to remove.
#
# \return \type{bool} True if successful, False if not.
@pyqtSlot(str, result = bool)
def removeQualityChanges(self, quality_name):
Logger.log("d", "Attempting to remove the quality change containers with name %s", quality_name)
containers_found = False
if not quality_name:
return containers_found # Without a name we will never find a container to remove.
# If the container that is being removed is the currently active quality, set another quality as the active quality
activate_quality = quality_name == self._machine_manager.activeQualityName
activate_quality_type = None
global_stack = Application.getInstance().getGlobalContainerStack()
if not global_stack or not quality_name:
return ""
machine_definition = QualityManager.getInstance().getParentMachineDefinition(global_stack.getBottom())
for container in QualityManager.getInstance().findQualityChangesByName(quality_name, machine_definition):
containers_found = True
if activate_quality and not activate_quality_type:
activate_quality_type = container.getMetaDataEntry("quality")
self._container_registry.removeContainer(container.getId())
if not containers_found:
Logger.log("d", "Unable to remove quality containers, as we did not find any by the name of %s", quality_name)
elif activate_quality:
definition_id = "fdmprinter" if not self._machine_manager.filterQualityByMachine else self._machine_manager.activeDefinitionId
containers = self._container_registry.findInstanceContainersMetadata(type = "quality", definition = definition_id, quality_type = activate_quality_type)
if containers:
self._machine_manager.setActiveQuality(containers[0]["id"])
self._machine_manager.activeQualityChanged.emit()
return containers_found
## Rename a set of quality changes containers.
#
# This will search for quality_changes containers matching the supplied name and rename them.
# Note that if the machine specifies that qualities should be filtered by machine and/or material
# only the containers related to the active machine/material are renamed.
#
# \param quality_name The name of the quality changes containers to rename.
# \param new_name The new name of the quality changes.
#
# \return True if successful, False if not.
@pyqtSlot(str, str, result = bool)
def renameQualityChanges(self, quality_name, new_name):
Logger.log("d", "User requested QualityChanges container rename of %s to %s", quality_name, new_name)
if not quality_name or not new_name:
return False
if quality_name == new_name:
Logger.log("w", "Unable to rename %s to %s, because they are the same.", quality_name, new_name)
return True
global_stack = Application.getInstance().getGlobalContainerStack()
if not global_stack:
return False
self._machine_manager.blurSettings.emit()
new_name = self._container_registry.uniqueName(new_name)
container_registry = self._container_registry
containers_to_rename = self._container_registry.findInstanceContainersMetadata(type = "quality_changes", name = quality_name)
for container in containers_to_rename:
stack_id = global_stack.getId()
if "extruder" in container:
stack_id = container["extruder"]
container_registry.renameContainer(container["id"], new_name, self._createUniqueId(stack_id, new_name))
if not containers_to_rename:
Logger.log("e", "Unable to rename %s, because we could not find the profile", quality_name)
self._machine_manager.activeQualityChanged.emit()
return True
## Duplicate a specified set of quality or quality_changes containers.
#
# This will search for containers matching the specified name. If the container is a "quality" type container, a new
# quality_changes container will be created with the specified quality as base. If the container is a "quality_changes"
# container, it is simply duplicated and renamed.
#
# \param quality_name The name of the quality to duplicate.
#
# \return A string containing the name of the duplicated containers, or an empty string if it failed.
@pyqtSlot(str, str, result = str)
def duplicateQualityOrQualityChanges(self, quality_name, base_name):
global_stack = Application.getInstance().getGlobalContainerStack()
if not global_stack or not quality_name:
return ""
machine_definition = global_stack.definition
active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
if active_stacks is None:
return ""
material_metadatas = [stack.material.getMetaData() for stack in active_stacks]
result = self._duplicateQualityOrQualityChangesForMachineType(quality_name, base_name,
QualityManager.getInstance().getParentMachineDefinition(machine_definition),
material_metadatas)
return result[0].getName() if result else ""
## Duplicate a quality or quality changes profile specific to a machine type
#
# \param quality_name The name of the quality or quality changes container to duplicate.
# \param base_name The desired name for the new container.
# \param machine_definition The machine with the specific machine type.
# \param material_metadatas Metadata of materials
# \return List of duplicated quality profiles.
def _duplicateQualityOrQualityChangesForMachineType(self, quality_name: str, base_name: str, machine_definition: DefinitionContainer, material_metadatas: List[Dict[str, Any]]) -> List[InstanceContainer]:
Logger.log("d", "Attempting to duplicate the quality %s", quality_name)
if base_name is None:
base_name = quality_name
# Try to find a Quality with the name.
container = QualityManager.getInstance().findQualityByName(quality_name, machine_definition, material_metadatas)
if container:
Logger.log("d", "We found a quality to duplicate.")
return self._duplicateQualityForMachineType(container, base_name, machine_definition)
Logger.log("d", "We found a quality_changes to duplicate.")
# Assume it is a quality changes.
return self._duplicateQualityChangesForMachineType(quality_name, base_name, machine_definition)
# Duplicate a quality profile
def _duplicateQualityForMachineType(self, quality_container, base_name, machine_definition) -> List[InstanceContainer]:
if base_name is None:
base_name = quality_container.getName()
new_name = self._container_registry.uniqueName(base_name)
new_change_instances = []
# Handle the global stack first.
global_changes = self._createQualityChanges(quality_container, new_name, machine_definition, None)
new_change_instances.append(global_changes)
self._container_registry.addContainer(global_changes)
# Handle the extruders if present.
extruders = machine_definition.getMetaDataEntry("machine_extruder_trains")
if extruders:
for extruder_id in extruders:
extruder = extruders[extruder_id]
new_changes = self._createQualityChanges(quality_container, new_name, machine_definition, extruder)
new_change_instances.append(new_changes)
self._container_registry.addContainer(new_changes)
return new_change_instances
# Duplicate a quality changes container
def _duplicateQualityChangesForMachineType(self, quality_changes_name, base_name, machine_definition) -> List[InstanceContainer]:
new_change_instances = []
for container in QualityManager.getInstance().findQualityChangesByName(quality_changes_name,
machine_definition):
base_id = container.getMetaDataEntry("extruder")
if not base_id:
base_id = container.getDefinition().getId()
new_unique_id = self._createUniqueId(base_id, base_name)
new_container = container.duplicate(new_unique_id, base_name)
new_change_instances.append(new_container)
self._container_registry.addContainer(new_container)
return new_change_instances
## Create a duplicate of a material, which has the same GUID and base_file metadata
#
# \return \type{str} the id of the newly created container.
@pyqtSlot(str, result = str)
def duplicateMaterial(self, material_id: str) -> str:
original = self._container_registry.findContainersMetadata(id = material_id)
if not original:
Logger.log("d", "Unable to duplicate the material with id %s, because it doesn't exist.", material_id)
return ""
original = original[0]
base_container_id = original.get("base_file")
base_container = self._container_registry.findContainers(id = base_container_id)
if not base_container:
Logger.log("d", "Unable to duplicate the material with id {material_id}, because base_file {base_container_id} doesn't exist.".format(material_id = material_id, base_container_id = base_container_id))
return ""
base_container = base_container[0]
#We'll copy all containers with the same base.
#This way the correct variant and machine still gets assigned when loading the copy of the material.
containers_to_copy = self._container_registry.findInstanceContainers(base_file = base_container_id)
# Ensure all settings are saved.
Application.getInstance().saveSettings()
# Create a new ID & container to hold the data.
new_containers = []
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
new_containers.append(new_base_container)
#Clone all of them.
clone_of_original = None #Keeping track of which one is the clone of the original material, since we need to return that.
for container_to_copy in containers_to_copy:
#Create unique IDs for every clone.
current_id = container_to_copy.getId()
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"):
variant = self._container_registry.findContainers(id = container_to_copy.getMetaDataEntry("variant"))[0]
new_id += "_" + variant.getName().replace(" ", "_")
if current_id == material_id:
clone_of_original = new_id
new_container = copy.deepcopy(container_to_copy)
new_container.getMetaData()["id"] = new_id
new_container.getMetaData()["base_file"] = new_base_id
new_containers.append(new_container)
for container_to_add in new_containers:
container_to_add.setDirty(True)
ContainerRegistry.getInstance().addContainer(container_to_add)
return self._getMaterialContainerIdForActiveMachine(clone_of_original)
## Create a duplicate of a material or it's original entry
#
# \return \type{str} the id of the newly created container.
@pyqtSlot(str, result = str)
def duplicateOriginalMaterial(self, material_id):
# check if the given material has a base file (i.e. was shipped by default)
base_file = self.getContainerMetaDataEntry(material_id, "base_file")
if base_file == "":
# there is no base file, so duplicate by ID
return self.duplicateMaterial(material_id)
else:
# there is a base file, so duplicate the original material
return self.duplicateMaterial(base_file)
## Create a new material by cloning Generic PLA for the current material diameter and setting the GUID to something unqiue
#
# \return \type{str} the id of the newly created container.
@pyqtSlot(result = str)
def createMaterial(self) -> str:
# Ensure all settings are saved.
Application.getInstance().saveSettings()
global_stack = Application.getInstance().getGlobalContainerStack()
if not global_stack:
return ""
approximate_diameter = str(round(global_stack.getProperty("material_diameter", "value")))
containers = self._container_registry.findInstanceContainersMetadata(id = "generic_pla*", approximate_diameter = approximate_diameter)
if not containers:
Logger.log("d", "Unable to create a new material by cloning Generic PLA, because it cannot be found for the material diameter for this machine.")
return ""
base_file = containers[0].get("base_file")
containers = self._container_registry.findInstanceContainers(id = base_file)
if not containers:
Logger.log("d", "Unable to create a new material by cloning Generic PLA, because the base file for Generic PLA for this machine can not be found.")
return ""
# Create a new ID & container to hold the data.
new_id = self._container_registry.uniqueName("custom_material")
container_type = type(containers[0]) # Always XMLMaterialProfile, since we specifically clone the base_file
duplicated_container = container_type(new_id)
# Instead of duplicating we load the data from the basefile again.
# This ensures that the inheritance goes well and all "cut up" subclasses of the xmlMaterial profile
# are also correctly created.
with open(containers[0].getPath(), encoding="utf-8") as f:
duplicated_container.deserialize(f.read())
duplicated_container.setMetaDataEntry("GUID", str(uuid.uuid4()))
duplicated_container.setMetaDataEntry("brand", catalog.i18nc("@label", "Custom"))
# We're defaulting to PLA, as machines with material profiles don't like material types they don't know.
# TODO: This is a hack, the only reason this is in now is to bandaid the problem as we're close to a release!
duplicated_container.setMetaDataEntry("material", "PLA")
duplicated_container.setName(catalog.i18nc("@label", "Custom Material"))
self._container_registry.addContainer(duplicated_container)
return self._getMaterialContainerIdForActiveMachine(new_id)
## Find the id of a material container based on the new material
# Utilty function that is shared between duplicateMaterial and createMaterial
#
# \param base_file \type{str} the id of the created container.
def _getMaterialContainerIdForActiveMachine(self, base_file):
global_stack = Application.getInstance().getGlobalContainerStack()
if not global_stack:
return base_file
has_machine_materials = parseBool(global_stack.getMetaDataEntry("has_machine_materials", default = False))
has_variant_materials = parseBool(global_stack.getMetaDataEntry("has_variant_materials", default = False))
has_variants = parseBool(global_stack.getMetaDataEntry("has_variants", default = False))
if has_machine_materials or has_variant_materials:
if has_variants:
materials = self._container_registry.findInstanceContainersMetadata(type = "material", base_file = base_file, definition = global_stack.getBottom().getId(), variant = self._machine_manager.activeVariantId)
else:
materials = self._container_registry.findInstanceContainersMetadata(type = "material", base_file = base_file, definition = global_stack.getBottom().getId())
if materials:
return materials[0]["id"]
Logger.log("w", "Unable to find a suitable container based on %s for the current machine.", base_file)
return "" # do not activate a new material if a container can not be found
return base_file
## 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
@pyqtSlot(str, result = "QStringList")
def getLinkedMaterials(self, material_id: str):
containers = self._container_registry.findInstanceContainersMetadata(id = material_id)
if not containers:
Logger.log("d", "Unable to find materials linked to material with id %s, because it doesn't exist.", material_id)
return []
@pyqtSlot("QVariant", result = "QStringList")
def getLinkedMaterials(self, material_node):
guid = material_node.metadata["GUID"]
material_container = containers[0]
material_base_file = material_container.get("base_file", "")
material_guid = material_container.get("GUID", "")
if not material_guid:
Logger.log("d", "Unable to find materials linked to material with id %s, because it doesn't have a GUID.", material_id)
return []
material_group_list = self._material_manager.getMaterialGroupListByGUID(guid)
containers = self._container_registry.findInstanceContainersMetadata(type = "material", GUID = material_guid)
linked_material_names = []
for container in containers:
if container["id"] in [material_id, material_base_file] or container.get("base_file") != container["id"]:
continue
linked_material_names.append(container["name"])
if material_group_list:
for material_group in material_group_list:
linked_material_names.append(material_group.root_material_node.metadata["name"])
return linked_material_names
## 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(str)
def unlinkMaterial(self, material_id: str):
containers = self._container_registry.findInstanceContainers(id=material_id)
if not containers:
Logger.log("d", "Unable to make the material with id %s unique, because it doesn't exist.", material_id)
return ""
@pyqtSlot("QVariant")
def unlinkMaterial(self, material_node):
# Get the material group
material_group = self._material_manager.getMaterialGroup(material_node.metadata["base_file"])
containers[0].setMetaDataEntry("GUID", str(uuid.uuid4()))
# Generate a new GUID
new_guid = str(uuid.uuid4())
# 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()
container.setMetaDataEntry("GUID", new_guid)
## Get the singleton instance for this class.
@classmethod
@ -1015,81 +444,6 @@ class ContainerManager(QObject):
name_filter = "{0} ({1})".format(mime_type.comment, suffix_list)
self._container_name_filters[name_filter] = entry
## Creates a unique ID for a container by prefixing the name with the stack ID.
#
# This method creates a unique ID for a container by prefixing it with a specified stack ID.
# This is done to ensure we have an easily identified ID for quality changes, which have the
# same name across several stacks.
#
# \param stack_id The ID of the stack to prepend.
# \param container_name The name of the container that we are creating a unique ID for.
#
# \return Container name prefixed with stack ID, in lower case with spaces replaced by underscores.
def _createUniqueId(self, stack_id, container_name):
result = stack_id + "_" + container_name
result = result.lower()
result.replace(" ", "_")
return result
## Create a quality changes container for a specified quality container.
#
# \param quality_container The quality container to create a changes container for.
# \param new_name The name of the new quality_changes container.
# \param machine_definition The machine definition this quality changes container is specific to.
# \param extruder_id
#
# \return A new quality_changes container with the specified container as base.
def _createQualityChanges(self, quality_container, new_name, machine_definition, extruder_id):
base_id = machine_definition.getId() if extruder_id is None else extruder_id
# Create a new quality_changes container for the quality.
quality_changes = InstanceContainer(self._createUniqueId(base_id, new_name))
quality_changes.setName(new_name)
quality_changes.addMetaDataEntry("type", "quality_changes")
quality_changes.addMetaDataEntry("quality_type", quality_container.getMetaDataEntry("quality_type"))
# If we are creating a container for an extruder, ensure we add that to the container
if extruder_id is not None:
quality_changes.addMetaDataEntry("extruder", extruder_id)
# If the machine specifies qualities should be filtered, ensure we match the current criteria.
if not machine_definition.getMetaDataEntry("has_machine_quality"):
quality_changes.setDefinition("fdmprinter")
else:
quality_changes.setDefinition(QualityManager.getInstance().getParentMachineDefinition(machine_definition).getId())
from cura.CuraApplication import CuraApplication
quality_changes.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
return quality_changes
## Import profiles from a list of file_urls.
# Each QUrl item must end with .curaprofile, or it will not be imported.
#
# \param QVariant<QUrl>, essentially a list with QUrl objects.
# \return Dict with keys status, text
@pyqtSlot("QVariantList", result="QVariantMap")
def importProfiles(self, file_urls):
status = "ok"
results = {"ok": [], "error": []}
for file_url in file_urls:
if not file_url.isValid():
continue
path = file_url.toLocalFile()
if not path:
continue
if not path.endswith(".curaprofile"):
continue
single_result = self._container_registry.importProfile(path)
if single_result["status"] == "error":
status = "error"
results[single_result["status"]].append(single_result["message"])
return {
"status": status,
"message": "\n".join(results["ok"] + results["error"])}
## Import single profile, file_url does not have to end with curaprofile
@pyqtSlot(QUrl, result="QVariantMap")
def importProfile(self, file_url):
@ -1100,11 +454,13 @@ class ContainerManager(QObject):
return
return self._container_registry.importProfile(path)
@pyqtSlot("QVariantList", QUrl, str)
def exportProfile(self, instance_id: str, file_url: QUrl, file_type: str) -> None:
@pyqtSlot(QObject, QUrl, str)
def exportQualityChangesGroup(self, quality_changes_group, file_url: QUrl, file_type: str):
if not file_url.isValid():
return
path = file_url.toLocalFile()
if not path:
return
self._container_registry.exportProfile(instance_id, path, file_type)
container_list = [n.getContainer() for n in quality_changes_group.getAllNodes()]
self._container_registry.exportQualityProfile(container_list, path, file_type)

View file

@ -1,97 +0,0 @@
# Copyright (c) 2016 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Application import Application
from UM.Qt.ListModel import ListModel
from PyQt5.QtCore import pyqtProperty, Qt, pyqtSignal, pyqtSlot, QUrl
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.SettingFunction import SettingFunction
class ContainerSettingsModel(ListModel):
LabelRole = Qt.UserRole + 1
CategoryRole = Qt.UserRole + 2
UnitRole = Qt.UserRole + 3
ValuesRole = Qt.UserRole + 4
def __init__(self, parent = None):
super().__init__(parent)
self.addRoleName(self.LabelRole, "label")
self.addRoleName(self.CategoryRole, "category")
self.addRoleName(self.UnitRole, "unit")
self.addRoleName(self.ValuesRole, "values")
self._container_ids = []
self._containers = []
def _onPropertyChanged(self, key, property_name):
if property_name == "value":
self._update()
def _update(self):
items = []
if len(self._container_ids) == 0:
return
keys = []
for container in self._containers:
keys = keys + list(container.getAllKeys())
keys = list(set(keys)) # remove duplicate keys
for key in keys:
definition = None
category = None
values = []
for container in self._containers:
instance = container.getInstance(key)
if instance:
definition = instance.definition
# Traverse up to find the category
category = definition
while category.type != "category":
category = category.parent
value = container.getProperty(key, "value")
if type(value) == SettingFunction:
values.append("=\u0192")
else:
values.append(container.getProperty(key, "value"))
else:
values.append("")
items.append({
"key": key,
"values": values,
"label": definition.label,
"unit": definition.unit,
"category": category.label
})
items.sort(key = lambda k: (k["category"], k["key"]))
self.setItems(items)
## Set the ids of the containers which have the settings this model should list.
# Also makes sure the model updates when the containers have property changes
def setContainers(self, container_ids):
for container in self._containers:
container.propertyChanged.disconnect(self._onPropertyChanged)
self._container_ids = container_ids
self._containers = []
for container_id in self._container_ids:
containers = ContainerRegistry.getInstance().findContainers(id = container_id)
if containers:
containers[0].propertyChanged.connect(self._onPropertyChanged)
self._containers.append(containers[0])
self._update()
containersChanged = pyqtSignal()
@pyqtProperty("QVariantList", fset = setContainers, notify = containersChanged)
def containers(self):
return self.container_ids

View file

@ -25,14 +25,15 @@ from UM.Resources import Resources
from . import ExtruderStack
from . import GlobalStack
from .ContainerManager import ContainerManager
from .ExtruderManager import ExtruderManager
from cura.CuraApplication import CuraApplication
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
class CuraContainerRegistry(ContainerRegistry):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@ -102,7 +103,7 @@ class CuraContainerRegistry(ContainerRegistry):
# \param instance_ids \type{list} the IDs of the profiles to export.
# \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>)"
def exportProfile(self, instance_ids, file_name, file_type):
def exportQualityProfile(self, container_list, file_name, file_type):
# Parse the fileType to deduce what plugin can save the file format.
# fileType has the format "<description> (*.<extension>)"
split = file_type.rfind(" (*.") # Find where the description ends and the extension starts.
@ -121,31 +122,10 @@ class CuraContainerRegistry(ContainerRegistry):
catalog.i18nc("@label Don't translate the XML tag <filename>!", "The file <filename>{0}</filename> already exists. Are you sure you want to overwrite it?").format(file_name))
if result == QMessageBox.No:
return
found_containers = []
extruder_positions = []
for instance_id in instance_ids:
containers = ContainerRegistry.getInstance().findInstanceContainers(id = instance_id)
if containers:
found_containers.append(containers[0])
# Determine the position of the extruder of this container
extruder_id = containers[0].getMetaDataEntry("extruder", "")
if extruder_id == "":
# Global stack
extruder_positions.append(-1)
else:
extruder_containers = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = extruder_id)
if extruder_containers:
extruder_positions.append(int(extruder_containers[0].get("position", 0)))
else:
extruder_positions.append(0)
# Ensure the profiles are always exported in order (global, extruder 0, extruder 1, ...)
found_containers = [containers for (positions, containers) in sorted(zip(extruder_positions, found_containers))]
profile_writer = self._findProfileWriter(extension, description)
try:
success = profile_writer.write(file_name, found_containers)
success = profile_writer.write(file_name, container_list)
except Exception as e:
Logger.log("e", "Failed to export profile to %s: %s", file_name, str(e))
m = Message(catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "Failed to export profile to <filename>{0}</filename>: <message>{1}</message>", file_name, str(e)),
@ -289,7 +269,7 @@ class CuraContainerRegistry(ContainerRegistry):
elif profile_index < len(machine_extruders) + 1:
# This is assumed to be an extruder profile
extruder_id = Application.getInstance().getMachineManager().getQualityDefinitionId(machine_extruders[profile_index - 1].getBottom())
extruder_id = machine_extruders[profile_index - 1].definition.getId()
if not profile.getMetaDataEntry("extruder"):
profile.addMetaDataEntry("extruder", extruder_id)
else:
@ -348,39 +328,16 @@ class CuraContainerRegistry(ContainerRegistry):
return catalog.i18nc("@info:status", "Profile is missing a quality type.")
quality_type_criteria = {"quality_type": quality_type}
if self._machineHasOwnQualities():
profile.setDefinition(self._activeQualityDefinition().getId())
if self._machineHasOwnMaterials():
active_material_id = self._activeMaterialId()
if active_material_id and active_material_id != "empty": # only update if there is an active material
profile.addMetaDataEntry("material", active_material_id)
quality_type_criteria["material"] = active_material_id
quality_type_criteria["definition"] = profile.getDefinition().getId()
else:
profile.setDefinition("fdmprinter")
quality_type_criteria["definition"] = "fdmprinter"
machine_definition = Application.getInstance().getGlobalContainerStack().getBottom()
del quality_type_criteria["definition"]
# materials = None
if "material" in quality_type_criteria:
# materials = ContainerRegistry.getInstance().findInstanceContainers(id = quality_type_criteria["material"])
del quality_type_criteria["material"]
# Do not filter quality containers here with materials because we are trying to import a profile, so it should
# NOT be restricted by the active materials on the current machine.
materials = None
global_stack = Application.getInstance().getGlobalContainerStack()
definition_id = getMachineDefinitionIDForQualitySearch(global_stack)
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.
from cura.QualityManager import QualityManager
qualities = QualityManager.getInstance()._getFilteredContainersForStack(machine_definition, materials, **quality_type_criteria)
if not qualities:
quality_manager = CuraApplication.getInstance()._quality_manager
quality_group_dict = quality_manager.getQualityGroupsForMachineDefinition(global_stack)
if 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)
ContainerRegistry.getInstance().addContainer(profile)
@ -400,18 +357,6 @@ class CuraContainerRegistry(ContainerRegistry):
result.append( (plugin_id, meta_data) )
return result
## Get the definition to use to select quality profiles for the active machine
# \return the active quality definition object or None if there is no quality definition
def _activeQualityDefinition(self):
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack:
definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(global_container_stack.getBottom())
definition = self.findDefinitionContainers(id = definition_id)[0]
if definition:
return definition
return None
## Returns true if the current machine requires its own materials
# \return True if the current machine requires its own materials
def _machineHasOwnMaterials(self):
@ -507,8 +452,6 @@ class CuraContainerRegistry(ContainerRegistry):
extruder_stack.setDefinition(extruder_definition)
extruder_stack.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position"))
from cura.CuraApplication import CuraApplication
# create a new definition_changes container for the extruder stack
definition_changes_id = self.uniqueName(extruder_stack.getId() + "_settings") if create_new_ids else extruder_stack.getId() + "_settings"
definition_changes_name = definition_changes_id
@ -567,26 +510,28 @@ class CuraContainerRegistry(ContainerRegistry):
self.addContainer(user_container)
extruder_stack.setUserChanges(user_container)
variant_id = "default"
application = CuraApplication.getInstance()
empty_variant = application.empty_variant_container
empty_material = application.empty_material_container
empty_quality = application.empty_quality_container
if machine.variant.getId() not in ("empty", "empty_variant"):
variant_id = machine.variant.getId()
variant = machine.variant
else:
variant_id = "empty_variant"
extruder_stack.setVariantById(variant_id)
variant = empty_variant
extruder_stack.variant = variant
material_id = "default"
if machine.material.getId() not in ("empty", "empty_material"):
material_id = machine.material.getId()
material = machine.material
else:
material_id = "empty_material"
extruder_stack.setMaterialById(material_id)
material = empty_material
extruder_stack.material = material
quality_id = "default"
if machine.quality.getId() not in ("empty", "empty_quality"):
quality_id = machine.quality.getId()
quality = machine.quality
else:
quality_id = "empty_quality"
extruder_stack.setQualityById(quality_id)
quality = empty_quality
extruder_stack.quality = quality
machine_quality_changes = machine.qualityChanges
if new_global_quality_changes is not None:
@ -598,7 +543,7 @@ class CuraContainerRegistry(ContainerRegistry):
extruder_quality_changes_container = extruder_quality_changes_container[0]
quality_changes_id = extruder_quality_changes_container.getId()
extruder_stack.setQualityChangesById(quality_changes_id)
extruder_stack.qualityChanges = self.findInstanceContainers(id = quality_changes_id)[0]
else:
# Some extruder quality_changes containers can be created at runtime as files in the qualities
# folder. Those files won't be loaded in the registry immediately. So we also need to search
@ -607,7 +552,7 @@ class CuraContainerRegistry(ContainerRegistry):
if extruder_quality_changes_container:
quality_changes_id = extruder_quality_changes_container.getId()
extruder_quality_changes_container.addMetaDataEntry("extruder", extruder_stack.definition.getId())
extruder_stack.setQualityChangesById(quality_changes_id)
extruder_stack.qualityChanges = self.findInstanceContainers(id = quality_changes_id)[0]
else:
# if we still cannot find a quality changes container for the extruder, create a new one
container_name = machine_quality_changes.getName()
@ -642,7 +587,7 @@ class CuraContainerRegistry(ContainerRegistry):
machine_quality_changes.removeInstance(qc_setting_key, postpone_emit=True)
else:
extruder_stack.setQualityChangesById("empty_quality_changes")
extruder_stack.qualityChanges = self.findInstanceContainers(id = "empty_quality_changes")[0]
self.addContainer(extruder_stack)

View file

@ -83,20 +83,6 @@ class CuraContainerStack(ContainerStack):
def setQualityChanges(self, new_quality_changes: InstanceContainer, postpone_emit = False) -> None:
self.replaceContainer(_ContainerIndexes.QualityChanges, new_quality_changes, postpone_emit = postpone_emit)
## Set the quality changes container by an ID.
#
# This will search for the specified container and set it. If no container was found, an error will be raised.
#
# \param new_quality_changes_id The ID of the new quality changes container.
#
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
def setQualityChangesById(self, new_quality_changes_id: str) -> None:
quality_changes = ContainerRegistry.getInstance().findInstanceContainers(id = new_quality_changes_id)
if quality_changes:
self.setQualityChanges(quality_changes[0])
else:
raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_quality_changes_id))
## Get the quality changes container.
#
# \return The quality changes container. Should always be a valid container, but can be equal to the empty InstanceContainer.
@ -110,31 +96,6 @@ class CuraContainerStack(ContainerStack):
def setQuality(self, new_quality: InstanceContainer, postpone_emit = False) -> None:
self.replaceContainer(_ContainerIndexes.Quality, new_quality, postpone_emit = postpone_emit)
## Set the quality container by an ID.
#
# This will search for the specified container and set it. If no container was found, an error will be raised.
# There is a special value for ID, which is "default". The "default" value indicates the quality should be set
# to whatever the machine definition specifies as "preferred" container, or a fallback value. See findDefaultQuality
# for details.
#
# \param new_quality_id The ID of the new quality container.
#
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
def setQualityById(self, new_quality_id: str) -> None:
quality = self._empty_quality
if new_quality_id == "default":
new_quality = self.findDefaultQuality()
if new_quality:
quality = new_quality
else:
qualities = ContainerRegistry.getInstance().findInstanceContainers(id = new_quality_id)
if qualities:
quality = qualities[0]
else:
raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_quality_id))
self.setQuality(quality)
## Get the quality container.
#
# \return The quality container. Should always be a valid container, but can be equal to the empty InstanceContainer.
@ -148,31 +109,6 @@ class CuraContainerStack(ContainerStack):
def setMaterial(self, new_material: InstanceContainer, postpone_emit = False) -> None:
self.replaceContainer(_ContainerIndexes.Material, new_material, postpone_emit = postpone_emit)
## Set the material container by an ID.
#
# This will search for the specified container and set it. If no container was found, an error will be raised.
# There is a special value for ID, which is "default". The "default" value indicates the quality should be set
# to whatever the machine definition specifies as "preferred" container, or a fallback value. See findDefaultMaterial
# for details.
#
# \param new_material_id The ID of the new material container.
#
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
def setMaterialById(self, new_material_id: str) -> None:
material = self._empty_material
if new_material_id == "default":
new_material = self.findDefaultMaterial()
if new_material:
material = new_material
else:
materials = ContainerRegistry.getInstance().findInstanceContainers(id = new_material_id)
if materials:
material = materials[0]
else:
raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_material_id))
self.setMaterial(material)
## Get the material container.
#
# \return The material container. Should always be a valid container, but can be equal to the empty InstanceContainer.
@ -186,31 +122,6 @@ class CuraContainerStack(ContainerStack):
def setVariant(self, new_variant: InstanceContainer) -> None:
self.replaceContainer(_ContainerIndexes.Variant, new_variant)
## Set the variant container by an ID.
#
# This will search for the specified container and set it. If no container was found, an error will be raised.
# There is a special value for ID, which is "default". The "default" value indicates the quality should be set
# to whatever the machine definition specifies as "preferred" container, or a fallback value. See findDefaultVariant
# for details.
#
# \param new_variant_id The ID of the new variant container.
#
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
def setVariantById(self, new_variant_id: str) -> None:
variant = self._empty_variant
if new_variant_id == "default":
new_variant = self.findDefaultVariantBuildplate() if self.getMetaDataEntry("type") == "machine" else self.findDefaultVariant()
if new_variant:
variant = new_variant
else:
variants = ContainerRegistry.getInstance().findInstanceContainers(id = new_variant_id)
if variants:
variant = variants[0]
else:
raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_variant_id))
self.setVariant(variant)
## Get the variant container.
#
# \return The variant container. Should always be a valid container, but can be equal to the empty InstanceContainer.
@ -224,18 +135,6 @@ class CuraContainerStack(ContainerStack):
def setDefinitionChanges(self, new_definition_changes: InstanceContainer) -> None:
self.replaceContainer(_ContainerIndexes.DefinitionChanges, new_definition_changes)
## Set the definition changes container by an ID.
#
# \param new_definition_changes_id The ID of the new definition changes container.
#
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
def setDefinitionChangesById(self, new_definition_changes_id: str) -> None:
new_definition_changes = ContainerRegistry.getInstance().findInstanceContainers(id = new_definition_changes_id)
if new_definition_changes:
self.setDefinitionChanges(new_definition_changes[0])
else:
raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_definition_changes_id))
## Get the definition changes container.
#
# \return The definition changes container. Should always be a valid container, but can be equal to the empty InstanceContainer.
@ -249,18 +148,6 @@ class CuraContainerStack(ContainerStack):
def setDefinition(self, new_definition: DefinitionContainerInterface) -> None:
self.replaceContainer(_ContainerIndexes.Definition, new_definition)
## Set the definition container by an ID.
#
# \param new_definition_id The ID of the new definition container.
#
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
def setDefinitionById(self, new_definition_id: str) -> None:
new_definition = ContainerRegistry.getInstance().findDefinitionContainers(id = new_definition_id)
if new_definition:
self.setDefinition(new_definition[0])
else:
raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_definition_id))
## Get the definition container.
#
# \return The definition container. Should always be a valid container, but can be equal to the empty InstanceContainer.
@ -348,6 +235,10 @@ class CuraContainerStack(ContainerStack):
elif container != self._empty_instance_container and container.getMetaDataEntry("type") != expected_type:
raise Exceptions.InvalidContainerError("Cannot replace container at index {index} with a container that is not of {type} type, but {actual_type} type.".format(index = index, type = expected_type, actual_type = container.getMetaDataEntry("type")))
current_container = self._containers[index]
if current_container.getId() == container.getId():
return
super().replaceContainer(index, container, postpone_emit)
## Overridden from ContainerStack
@ -391,243 +282,6 @@ class CuraContainerStack(ContainerStack):
self._containers = new_containers
## Find the variant that should be used as "default" variant.
#
# This will search for variants that match the current definition and pick the preferred one,
# if specified by the machine definition.
#
# The following criteria are used to find the default variant:
# - If the machine definition does not have a metadata entry "has_variants" set to True, return None
# - The definition of the variant should be the same as the machine definition for this stack.
# - The container should have a metadata entry "type" with value "variant".
# - If the machine definition has a metadata entry "preferred_variant", filter the variant IDs based on that.
#
# \return The container that should be used as default, or None if nothing was found or the machine does not use variants.
#
# \note This method assumes the stack has a valid machine definition.
def findDefaultVariant(self) -> Optional[ContainerInterface]:
definition = self._getMachineDefinition()
# has_variants can be overridden in other containers and stacks.
# In the case of UM2, it is overridden in the GlobalStack
if not self.getMetaDataEntry("has_variants"):
# If the machine does not use variants, we should never set a variant.
return None
# First add any variant. Later, overwrite with preference if the preference is valid.
variant = None
definition_id = self._findInstanceContainerDefinitionId(definition)
variants = ContainerRegistry.getInstance().findInstanceContainers(definition = definition_id, type = "variant")
if variants:
variant = variants[0]
preferred_variant_id = definition.getMetaDataEntry("preferred_variant")
if preferred_variant_id:
preferred_variants = ContainerRegistry.getInstance().findInstanceContainers(id = preferred_variant_id, definition = definition_id, type = "variant")
if preferred_variants:
variant = preferred_variants[0]
else:
Logger.log("w", "The preferred variant \"{variant}\" of stack {stack} does not exist or is not a variant.", variant = preferred_variant_id, stack = self.id)
# And leave it at the default variant.
if variant:
return variant
Logger.log("w", "Could not find a valid default variant for stack {stack}", stack = self.id)
return None
## Find the global variant that should be used as "default". This is used for the buildplates.
#
# This will search for variants that match the current definition and pick the preferred one,
# if specified by the machine definition.
#
# The following criteria are used to find the default global variant:
# - If the machine definition does not have a metadata entry "has_variant_buildplates" set to True, return None
# - The definition of the variant should be the same as the machine definition for this stack.
# - The container should have a metadata entry "type" with value "variant" and "hardware_type" with value "buildplate".
# - If the machine definition has a metadata entry "preferred_variant_buildplate", filter the variant IDs based on that.
#
# \return The container that should be used as default, or None if nothing was found or the machine does not use variants.
#
# \note This method assumes the stack has a valid machine definition.
def findDefaultVariantBuildplate(self) -> Optional[ContainerInterface]:
definition = self._getMachineDefinition()
# has_variant_buildplates can be overridden in other containers and stacks.
# In the case of UM2, it is overridden in the GlobalStack
if not self.getMetaDataEntry("has_variant_buildplates"):
# If the machine does not use variants, we should never set a variant.
return None
# First add any variant. Later, overwrite with preference if the preference is valid.
variant = None
definition_id = self._findInstanceContainerDefinitionId(definition)
variants = ContainerRegistry.getInstance().findInstanceContainers(definition = definition_id, type = "variant", hardware_type = "buildplate")
if variants:
variant = variants[0]
preferred_variant_buildplate_id = definition.getMetaDataEntry("preferred_variant_buildplate")
if preferred_variant_buildplate_id:
preferred_variant_buildplates = ContainerRegistry.getInstance().findInstanceContainers(id = preferred_variant_buildplate_id, definition = definition_id, type = "variant")
if preferred_variant_buildplates:
variant = preferred_variant_buildplates[0]
else:
Logger.log("w", "The preferred variant buildplate \"{variant}\" of stack {stack} does not exist or is not a variant.",
variant = preferred_variant_buildplate_id, stack = self.id)
# And leave it at the default variant.
if variant:
return variant
Logger.log("w", "Could not find a valid default buildplate variant for stack {stack}", stack = self.id)
return None
## Find the material that should be used as "default" material.
#
# This will search for materials that match the current definition and pick the preferred one,
# if specified by the machine definition.
#
# The following criteria are used to find the default material:
# - If the machine definition does not have a metadata entry "has_materials" set to True, return None
# - If the machine definition has a metadata entry "has_machine_materials", the definition of the material should
# be the same as the machine definition for this stack. Otherwise, the definition should be "fdmprinter".
# - The container should have a metadata entry "type" with value "material".
# - The material should have an approximate diameter that matches the machine
# - If the machine definition has a metadata entry "has_variants" and set to True, the "variant" metadata entry of
# the material should be the same as the ID of the variant in the stack. Only applies if "has_machine_materials" is also True.
# - If the stack currently has a material set, try to find a material that matches the current material by name.
# - Otherwise, if the machine definition has a metadata entry "preferred_material", try to find a material that matches the specified ID.
#
# \return The container that should be used as default, or None if nothing was found or the machine does not use materials.
def findDefaultMaterial(self) -> Optional[ContainerInterface]:
definition = self._getMachineDefinition()
if not definition.getMetaDataEntry("has_materials"):
# Machine does not use materials, never try to set it.
return None
search_criteria = {"type": "material"}
if definition.getMetaDataEntry("has_machine_materials"):
search_criteria["definition"] = self._findInstanceContainerDefinitionId(definition)
if definition.getMetaDataEntry("has_variants"):
search_criteria["variant"] = self.variant.id
else:
search_criteria["definition"] = "fdmprinter"
if self.material != self._empty_material:
search_criteria["name"] = self.material.name
else:
preferred_material = definition.getMetaDataEntry("preferred_material")
if preferred_material:
search_criteria["id"] = preferred_material
approximate_material_diameter = str(round(self.getProperty("material_diameter", "value")))
search_criteria["approximate_diameter"] = approximate_material_diameter
materials = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
if not materials:
Logger.log("w", "The preferred material \"{material}\" could not be found for stack {stack}", material = preferred_material, stack = self.id)
# We failed to find any materials matching the specified criteria, drop some specific criteria and try to find
# a material that sort-of matches what we want.
search_criteria.pop("variant", None)
search_criteria.pop("id", None)
search_criteria.pop("name", None)
materials = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
if not materials:
Logger.log("w", "Could not find a valid material for stack {stack}", stack = self.id)
return None
for material in materials:
# Prefer a read-only material
if ContainerRegistry.getInstance().isReadOnly(material.getId()):
return material
return materials[0]
## Find the quality that should be used as "default" quality.
#
# This will search for qualities that match the current definition and pick the preferred one,
# if specified by the machine definition.
#
# \return The container that should be used as default, or None if nothing was found.
def findDefaultQuality(self) -> Optional[ContainerInterface]:
definition = self._getMachineDefinition()
registry = ContainerRegistry.getInstance()
material_container = self.material if self.material.getId() not in (self._empty_material.getId(), self._empty_instance_container.getId()) else None
search_criteria = {"type": "quality"}
if definition.getMetaDataEntry("has_machine_quality"):
search_criteria["definition"] = self._findInstanceContainerDefinitionId(definition)
if definition.getMetaDataEntry("has_materials") and material_container:
search_criteria["material"] = material_container.id
else:
search_criteria["definition"] = "fdmprinter"
if self.quality != self._empty_quality:
search_criteria["name"] = self.quality.name
else:
preferred_quality = definition.getMetaDataEntry("preferred_quality")
if preferred_quality:
search_criteria["id"] = preferred_quality
containers = registry.findInstanceContainers(**search_criteria)
if containers:
return containers[0]
if "material" in search_criteria:
# First check if we can solve our material not found problem by checking if we can find quality containers
# that are assigned to the parents of this material profile.
try:
inherited_files = material_container.getInheritedFiles()
except AttributeError: # Material_container does not support inheritance.
inherited_files = []
if inherited_files:
for inherited_file in inherited_files:
# Extract the ID from the path we used to load the file.
search_criteria["material"] = os.path.basename(inherited_file).split(".")[0]
containers = registry.findInstanceContainers(**search_criteria)
if containers:
return containers[0]
# We still weren't able to find a quality for this specific material.
# Try to find qualities for a generic version of the material.
material_search_criteria = {"type": "material", "material": material_container.getMetaDataEntry("material"), "color_name": "Generic"}
if definition.getMetaDataEntry("has_machine_quality"):
if self.material != self._empty_instance_container:
material_search_criteria["definition"] = material_container.getMetaDataEntry("definition")
if definition.getMetaDataEntry("has_variants"):
material_search_criteria["variant"] = material_container.getMetaDataEntry("variant")
else:
material_search_criteria["definition"] = self._findInstanceContainerDefinitionId(definition)
if definition.getMetaDataEntry("has_variants") and self.variant != self._empty_instance_container:
material_search_criteria["variant"] = self.variant.id
else:
material_search_criteria["definition"] = "fdmprinter"
material_containers = registry.findInstanceContainersMetadata(**material_search_criteria)
# Try all materials to see if there is a quality profile available.
for material_container in material_containers:
search_criteria["material"] = material_container["id"]
containers = registry.findInstanceContainers(**search_criteria)
if containers:
return containers[0]
if "name" in search_criteria or "id" in search_criteria:
# If a quality by this name can not be found, try a wider set of search criteria
search_criteria.pop("name", None)
search_criteria.pop("id", None)
containers = registry.findInstanceContainers(**search_criteria)
if containers:
return containers[0]
return None
## protected:
# Helper to make sure we emit a PyQt signal on container changes.

View file

@ -1,15 +1,18 @@
# Copyright (c) 2017 Ultimaker B.V.
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Logger import Logger
from typing import Optional
from UM.Logger import Logger
from UM.Settings.Interfaces import DefinitionContainerInterface
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.SettingFunction import SettingFunction
from UM.Util import parseBool
from cura.Machines.VariantManager import VariantType
from .GlobalStack import GlobalStack
from .ExtruderStack import ExtruderStack
from typing import Optional
## Contains helper functions to create new machines.
@ -22,7 +25,13 @@ class CuraStackBuilder:
# \return The new global stack or None if an error occurred.
@classmethod
def createMachine(cls, name: str, definition_id: str) -> Optional[GlobalStack]:
from cura.CuraApplication import CuraApplication
application = CuraApplication.getInstance()
variant_manager = application.getVariantManager()
material_manager = application.getMaterialManager()
quality_manager = application.getQualityManager()
registry = ContainerRegistry.getInstance()
definitions = registry.findDefinitionContainers(id = definition_id)
if not definitions:
Logger.log("w", "Definition {definition} was not found!", definition = definition_id)
@ -30,7 +39,21 @@ class CuraStackBuilder:
machine_definition = definitions[0]
generated_name = registry.createUniqueName("machine", "", name, machine_definition.name)
# 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()
# get variant container for extruders
extruder_variant_container = application.empty_variant_container
extruder_variant_node = variant_manager.getDefaultVariantNode(machine_definition, VariantType.NOZZLE)
extruder_variant_name = None
if extruder_variant_node:
extruder_variant_container = extruder_variant_node.getContainer()
extruder_variant_name = extruder_variant_container.getName()
generated_name = registry.createUniqueName("machine", "", name, machine_definition.getName())
# Make sure the new name does not collide with any definition or (quality) profile
# createUniqueName() only looks at other stacks, but not at definitions or quality profiles
# Note that we don't go for uniqueName() immediately because that function matches with ignore_case set to true
@ -40,49 +63,55 @@ class CuraStackBuilder:
new_global_stack = cls.createGlobalStack(
new_stack_id = generated_name,
definition = machine_definition,
quality = "default",
material = "default",
variant = "default",
variant_container = global_variant_container,
material_container = application.empty_material_container,
quality_container = application.empty_quality_container,
)
new_global_stack.setName(generated_name)
extruder_definition = registry.findDefinitionContainers(machine = machine_definition.getId())
# get material container for extruders
material_container = application.empty_material_container
material_node = material_manager.getDefaultMaterial(new_global_stack, extruder_variant_name)
if material_node:
material_container = material_node.getContainer()
if not extruder_definition:
# create extruder stack for single extrusion machines that have no separate extruder definition files
extruder_definition = registry.findDefinitionContainers(id = "fdmextruder")[0]
new_extruder_id = registry.uniqueName(machine_definition.getName() + " " + extruder_definition.id)
# Create ExtruderStacks
extruder_dict = machine_definition.getMetaDataEntry("machine_extruder_trains")
for position, extruder_definition_id in extruder_dict.items():
# Sanity check: make sure that the positions in the extruder definitions are same as in the machine
# definition
extruder_definition = registry.findDefinitionContainers(id = extruder_definition_id)[0]
position_in_extruder_def = extruder_definition.getMetaDataEntry("position")
if position_in_extruder_def != position:
raise RuntimeError("Extruder position [%s] defined in extruder definition [%s] is not the same as in machine definition [%s] position [%s]" %
(position_in_extruder_def, extruder_definition_id, definition_id, position))
new_extruder_id = registry.uniqueName(extruder_definition_id)
new_extruder = cls.createExtruderStack(
new_extruder_id,
definition = extruder_definition,
machine_definition_id = machine_definition.getId(),
quality = "default",
material = "default",
variant = "default",
next_stack = new_global_stack
extruder_definition = extruder_definition,
machine_definition_id = definition_id,
position = position,
variant_container = extruder_variant_container,
material_container = material_container,
quality_container = application.empty_quality_container,
global_stack = new_global_stack,
)
new_extruder.setNextStack(new_global_stack)
new_global_stack.addExtruder(new_extruder)
registry.addContainer(new_extruder)
else:
# create extruder stack for each found extruder definition
for extruder_definition in registry.findDefinitionContainers(machine = machine_definition.id):
position = extruder_definition.getMetaDataEntry("position", None)
if not position:
Logger.log("w", "Extruder definition %s specifies no position metadata entry.", extruder_definition.id)
new_extruder_id = registry.uniqueName(extruder_definition.id)
new_extruder = cls.createExtruderStack(
new_extruder_id,
definition = extruder_definition,
machine_definition_id = machine_definition.getId(),
quality = "default",
material = "default",
variant = "default",
next_stack = new_global_stack
)
new_global_stack.addExtruder(new_extruder)
registry.addContainer(new_extruder)
preferred_quality_type = machine_definition.getMetaDataEntry("preferred_quality_type")
quality_group_dict = quality_manager.getQualityGroups(new_global_stack)
quality_group = quality_group_dict.get(preferred_quality_type)
new_global_stack.quality = quality_group.node_for_global.getContainer()
for position, extruder_stack in new_global_stack.extruders.items():
if position in quality_group.nodes_for_extruders:
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).
@ -100,43 +129,27 @@ class CuraStackBuilder:
#
# \return A new Global stack instance with the specified parameters.
@classmethod
def createExtruderStack(cls, new_stack_id: str, definition: DefinitionContainerInterface, machine_definition_id: str, **kwargs) -> ExtruderStack:
stack = ExtruderStack(new_stack_id)
stack.setName(definition.getName())
stack.setDefinition(definition)
stack.addMetaDataEntry("position", definition.getMetaDataEntry("position"))
if "next_stack" in kwargs:
# Add stacks before containers are added, since they may trigger a setting update.
stack.setNextStack(kwargs["next_stack"])
user_container = InstanceContainer(new_stack_id + "_user")
user_container.addMetaDataEntry("type", "user")
user_container.addMetaDataEntry("extruder", new_stack_id)
def createExtruderStack(cls, new_stack_id: str, extruder_definition: DefinitionContainerInterface, machine_definition_id: str,
position: int,
variant_container, material_container, quality_container, global_stack) -> ExtruderStack:
from cura.CuraApplication import CuraApplication
user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
user_container.setDefinition(machine_definition_id)
application = CuraApplication.getInstance()
stack.setUserChanges(user_container)
stack = ExtruderStack(new_stack_id, parent = global_stack)
stack.setName(extruder_definition.getName())
stack.setDefinition(extruder_definition)
# Important! The order here matters, because that allows the stack to
# assume the material and variant have already been set.
if "definition_changes" in kwargs:
stack.setDefinitionChangesById(kwargs["definition_changes"])
else:
stack.setDefinitionChanges(cls.createDefinitionChangesContainer(stack, new_stack_id + "_settings"))
stack.addMetaDataEntry("position", position)
if "variant" in kwargs:
stack.setVariantById(kwargs["variant"])
user_container = cls.createUserChangesContainer(new_stack_id + "_user", machine_definition_id, new_stack_id,
is_global_stack = False)
if "material" in kwargs:
stack.setMaterialById(kwargs["material"])
if "quality" in kwargs:
stack.setQualityById(kwargs["quality"])
if "quality_changes" in kwargs:
stack.setQualityChangesById(kwargs["quality_changes"])
stack.definitionChanges = cls.createDefinitionChangesContainer(stack, new_stack_id + "_settings")
stack.variant = variant_container
stack.material = material_container
stack.quality = quality_container
stack.qualityChanges = application.empty_quality_changes_container
stack.userChanges = user_container
# Only add the created containers to the registry after we have set all the other
# properties. This makes the create operation more transactional, since any problems
@ -153,44 +166,48 @@ class CuraStackBuilder:
#
# \return A new Global stack instance with the specified parameters.
@classmethod
def createGlobalStack(cls, new_stack_id: str, definition: DefinitionContainerInterface, **kwargs) -> GlobalStack:
def createGlobalStack(cls, new_stack_id: str, definition: DefinitionContainerInterface,
variant_container, material_container, quality_container) -> GlobalStack:
from cura.CuraApplication import CuraApplication
application = CuraApplication.getInstance()
stack = GlobalStack(new_stack_id)
stack.setDefinition(definition)
user_container = InstanceContainer(new_stack_id + "_user")
user_container.addMetaDataEntry("type", "user")
user_container.addMetaDataEntry("machine", new_stack_id)
from cura.CuraApplication import CuraApplication
user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
user_container.setDefinition(definition.getId())
# Create user container
user_container = cls.createUserChangesContainer(new_stack_id + "_user", definition.getId(), new_stack_id,
is_global_stack = True)
stack.setUserChanges(user_container)
# Important! The order here matters, because that allows the stack to
# assume the material and variant have already been set.
if "definition_changes" in kwargs:
stack.setDefinitionChangesById(kwargs["definition_changes"])
else:
stack.setDefinitionChanges(cls.createDefinitionChangesContainer(stack, new_stack_id + "_settings"))
if "variant" in kwargs:
stack.setVariantById(kwargs["variant"])
if "material" in kwargs:
stack.setMaterialById(kwargs["material"])
if "quality" in kwargs:
stack.setQualityById(kwargs["quality"])
if "quality_changes" in kwargs:
stack.setQualityChangesById(kwargs["quality_changes"])
stack.definitionChanges = cls.createDefinitionChangesContainer(stack, new_stack_id + "_settings")
stack.variant = variant_container
stack.material = material_container
stack.quality = quality_container
stack.qualityChanges = application.empty_quality_changes_container
stack.userChanges = user_container
ContainerRegistry.getInstance().addContainer(user_container)
return stack
@classmethod
def createDefinitionChangesContainer(cls, container_stack, container_name, container_index = None):
def createUserChangesContainer(cls, container_name: str, definition_id: str, stack_id: str,
is_global_stack: bool) -> "InstanceContainer":
from cura.CuraApplication import CuraApplication
unique_container_name = ContainerRegistry.getInstance().uniqueName(container_name)
container = InstanceContainer(unique_container_name)
container.setDefinition(definition_id)
container.addMetaDataEntry("type", "user")
container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
metadata_key_to_add = "machine" if is_global_stack else "extruder"
container.addMetaDataEntry(metadata_key_to_add, stack_id)
return container
@classmethod
def createDefinitionChangesContainer(cls, container_stack, container_name):
from cura.CuraApplication import CuraApplication
unique_container_name = ContainerRegistry.getInstance().uniqueName(container_name)

View file

@ -12,6 +12,7 @@ from UM.Scene.Selection import Selection
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
from UM.Settings.ContainerRegistry import ContainerRegistry # Finding containers by ID.
from UM.Settings.SettingFunction import SettingFunction
from UM.Settings.SettingInstance import SettingInstance
from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext
from typing import Optional, List, TYPE_CHECKING, Union
@ -30,28 +31,22 @@ class ExtruderManager(QObject):
def __init__(self, parent = None):
super().__init__(parent)
self._application = Application.getInstance()
self._extruder_trains = {} # Per machine, a dictionary of extruder container stack IDs. Only for separately defined extruders.
self._active_extruder_index = -1 # Indicates the index of the active extruder stack. -1 means no active extruder stack
self._selected_object_extruders = []
self._global_container_stack_definition_id = None
self._addCurrentMachineExtruders()
Application.getInstance().globalContainerStackChanged.connect(self.__globalContainerStackChanged)
#Application.getInstance().globalContainerStackChanged.connect(self._globalContainerStackChanged)
Selection.selectionChanged.connect(self.resetSelectedObjectExtruders)
## Signal to notify other components when the list of extruders for a machine definition changes.
extrudersChanged = pyqtSignal(QVariant)
## Signal to notify other components when the global container stack is switched to a definition
# that has different extruders than the previous global container stack
globalContainerStackDefinitionChanged = pyqtSignal()
## Notify when the user switches the currently active extruder.
activeExtruderChanged = pyqtSignal()
## The signal notifies subscribers if extruders are added
extrudersAdded = pyqtSignal()
## Gets the unique identifier of the currently active extruder stack.
#
# The currently active extruder stack is the stack that is currently being
@ -184,6 +179,7 @@ class ExtruderManager(QObject):
self._selected_object_extruders = []
self.selectedObjectExtrudersChanged.emit()
@pyqtSlot(result = QObject)
def getActiveExtruderStack(self) -> Optional["ExtruderStack"]:
global_container_stack = Application.getInstance().getGlobalContainerStack()
@ -371,12 +367,7 @@ class ExtruderManager(QObject):
return result[:machine_extruder_count]
def __globalContainerStackChanged(self) -> None:
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack and global_container_stack.getBottom() and global_container_stack.getBottom().getId() != self._global_container_stack_definition_id:
self._global_container_stack_definition_id = global_container_stack.getBottom().getId()
self.globalContainerStackDefinitionChanged.emit()
def _globalContainerStackChanged(self) -> None:
# If the global container changed, the machine changed and might have extruders that were not registered yet
self._addCurrentMachineExtruders()
@ -384,7 +375,7 @@ class ExtruderManager(QObject):
## Adds the extruders of the currently active machine.
def _addCurrentMachineExtruders(self) -> None:
global_stack = Application.getInstance().getGlobalContainerStack()
global_stack = self._application.getGlobalContainerStack()
extruders_changed = False
if global_stack:
@ -404,20 +395,82 @@ class ExtruderManager(QObject):
self._extruder_trains[global_stack_id][extruder_train.getMetaDataEntry("position")] = extruder_train
# regardless of what the next stack is, we have to set it again, because of signal routing. ???
extruder_train.setParent(global_stack)
extruder_train.setNextStack(global_stack)
extruders_changed = True
# FIX: We have to remove those settings here because we know that those values have been copied to all
# the extruders at this point.
for key in ("material_diameter", "machine_nozzle_size"):
if global_stack.definitionChanges.hasProperty(key, "value"):
global_stack.definitionChanges.removeInstance(key, postpone_emit = True)
self._fixMaterialDiameterAndNozzleSize(global_stack, extruder_trains)
if extruders_changed:
self.extrudersChanged.emit(global_stack_id)
self.extrudersAdded.emit()
self.setActiveExtruderIndex(0)
#
# This function tries to fix the problem with per-extruder-settable nozzle size and material diameter problems
# in early versions (3.0 - 3.2.1).
#
# In earlier versions, "nozzle size" and "material diameter" are only applicable to the complete machine, so all
# extruders share the same values. In this case, "nozzle size" and "material diameter" are saved in the
# GlobalStack's DefinitionChanges container.
#
# Later, we could have different "nozzle size" for each extruder, but "material diameter" could only be set for
# the entire machine. In this case, "nozzle size" should be saved in each ExtruderStack's DefinitionChanges, but
# "material diameter" still remains in the GlobalStack's DefinitionChanges.
#
# Lateer, both "nozzle size" and "material diameter" are settable per-extruder, and both settings should be saved
# in the ExtruderStack's DefinitionChanges.
#
# There were some bugs in upgrade so the data weren't saved correct as described above. This function tries fix
# this.
#
# One more thing is about material diameter and single-extrusion machines. Most single-extrusion machines don't
# specifically define their extruder definition, so they reuse "fdmextruder", but for those machines, they may
# define "material diameter = 1.75" in their machine definition, but in "fdmextruder", it's still "2.85". This
# causes a problem with incorrect default values.
#
# This is also fixed here in this way: If no "material diameter" is specified, it will look for the default value
# in both the Extruder's definition and the Global's definition. If 2 values don't match, we will use the value
# from the Global definition by setting it in the Extruder's DefinitionChanges container.
#
def _fixMaterialDiameterAndNozzleSize(self, global_stack, extruder_stack_list):
keys_to_copy = ["material_diameter", "machine_nozzle_size"] # these will be copied over to all extruders
extruder_positions_to_update = set()
for extruder_stack in extruder_stack_list:
for key in keys_to_copy:
# Only copy the value when this extruder doesn't have the value.
if extruder_stack.definitionChanges.hasProperty(key, "value"):
continue
setting_value_in_global_def_changes = global_stack.definitionChanges.getProperty(key, "value")
setting_value_in_global_def = global_stack.definition.getProperty(key, "value")
setting_value = setting_value_in_global_def
if setting_value_in_global_def_changes is not None:
setting_value = setting_value_in_global_def_changes
if setting_value == extruder_stack.definition.getProperty(key, "value"):
continue
setting_definition = global_stack.getSettingDefinition(key)
new_instance = SettingInstance(setting_definition, extruder_stack.definitionChanges)
new_instance.setProperty("value", setting_value)
new_instance.resetState() # Ensure that the state is not seen as a user state.
extruder_stack.definitionChanges.addInstance(new_instance)
extruder_stack.definitionChanges.setDirty(True)
# Make sure the material diameter is up to date for the extruder stack.
if key == "material_diameter":
position = int(extruder_stack.getMetaDataEntry("position"))
extruder_positions_to_update.add(position)
# We have to remove those settings here because we know that those values have been copied to all
# the extruders at this point.
for key in keys_to_copy:
if global_stack.definitionChanges.hasProperty(key, "value"):
global_stack.definitionChanges.removeInstance(key, postpone_emit = True)
# Update material diameter for extruders
for position in extruder_positions_to_update:
self.updateMaterialForDiameter(position, global_stack = global_stack)
## Get all extruder values for a certain setting.
#
# This is exposed to SettingFunction so it can be used in value functions.
@ -503,10 +556,11 @@ class ExtruderManager(QObject):
return ExtruderManager.getExtruderValues(key)
## Updates the material container to a material that matches the material diameter set for the printer
def updateMaterialForDiameter(self, extruder_position: int):
global_stack = Application.getInstance().getGlobalContainerStack()
def updateMaterialForDiameter(self, extruder_position: int, global_stack = None):
if not global_stack:
return
global_stack = Application.getInstance().getGlobalContainerStack()
if not global_stack:
return
if not global_stack.getMetaDataEntry("has_materials", False):
return

View file

@ -3,16 +3,16 @@
from typing import Any, TYPE_CHECKING, Optional
from UM.Application import Application
from PyQt5.QtCore import pyqtProperty
from UM.Decorators import override
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.Interfaces import ContainerInterface, PropertyEvaluationContext
from UM.Settings.SettingInstance import SettingInstance
from . import Exceptions
from .CuraContainerStack import CuraContainerStack
from .CuraContainerStack import CuraContainerStack, _ContainerIndexes
from .ExtruderManager import ExtruderManager
if TYPE_CHECKING:
@ -34,7 +34,7 @@ class ExtruderStack(CuraContainerStack):
#
# This will set the next stack and ensure that we register this stack as an extruder.
@override(ContainerStack)
def setNextStack(self, stack: ContainerStack) -> None:
def setNextStack(self, stack: CuraContainerStack) -> None:
super().setNextStack(stack)
stack.addExtruder(self)
self.addMetaDataEntry("machine", stack.id)
@ -42,75 +42,6 @@ class ExtruderStack(CuraContainerStack):
# For backward compatibility: Register the extruder with the Extruder Manager
ExtruderManager.getInstance().registerExtruder(self, stack.id)
# Now each machine will have at least one extruder stack. If this is the first extruder, the extruder-specific
# settings such as nozzle size and material diameter should be moved from the machine's definition_changes to
# the this extruder's definition_changes.
#
# We do this here because it is tooooo expansive to do it in the version upgrade: During the version upgrade,
# when we are upgrading a definition_changes container file, there is NO guarantee that other files such as
# machine an extruder stack files are upgraded before this, so we cannot read those files assuming they are in
# the latest format.
#
# MORE:
# For single-extrusion machines, nozzle size is saved in the global stack, so the nozzle size value should be
# carried to the first extruder.
# For material diameter, it was supposed to be applied to all extruders, so its value should be copied to all
# extruders.
keys_to_copy = ["material_diameter", "machine_nozzle_size"] # these will be copied over to all extruders
for key in keys_to_copy:
# Only copy the value when this extruder doesn't have the value.
if self.definitionChanges.hasProperty(key, "value"):
continue
# WARNING: this might be very dangerous and should be refactored ASAP!
#
# We cannot add a setting definition of "material_diameter" into the extruder's definition at runtime
# because all other machines which uses "fdmextruder" as the extruder definition will be affected.
#
# The problem is that single extrusion machines have their default material diameter defined in the global
# definitions. Now we automatically create an extruder stack for those machines using "fdmextruder"
# definition, which doesn't have the specific "material_diameter" and "machine_nozzle_size" defined for
# each machine. This results in wrong values which can be found in the MachineSettings dialog.
#
# To solve this, we put "material_diameter" back into the "fdmextruder" definition because modifying it in
# the extruder definition will affect all machines which uses the "fdmextruder" definition. Moreover, now
# we also check the value defined in the machine definition. If present, the value defined in the global
# stack's definition changes container will be copied. Otherwise, we will check if the default values in the
# machine definition and the extruder definition are the same, and if not, the default value in the machine
# definition will be copied to the extruder stack's definition changes.
#
setting_value_in_global_def_changes = stack.definitionChanges.getProperty(key, "value")
setting_value_in_global_def = stack.definition.getProperty(key, "value")
setting_value = setting_value_in_global_def
if setting_value_in_global_def_changes is not None:
setting_value = setting_value_in_global_def_changes
if setting_value == self.definition.getProperty(key, "value"):
continue
setting_definition = stack.getSettingDefinition(key)
new_instance = SettingInstance(setting_definition, self.definitionChanges)
new_instance.setProperty("value", setting_value)
new_instance.resetState() # Ensure that the state is not seen as a user state.
self.definitionChanges.addInstance(new_instance)
self.definitionChanges.setDirty(True)
# Make sure the material diameter is up to date for the extruder stack.
if key == "material_diameter":
from cura.CuraApplication import CuraApplication
machine_manager = CuraApplication.getInstance().getMachineManager()
position = self.getMetaDataEntry("position", "0")
func = lambda p = position: CuraApplication.getInstance().getExtruderManager().updateMaterialForDiameter(p)
machine_manager.machine_extruder_material_update_dict[stack.getId()].append(func)
# NOTE: We cannot remove the setting from the global stack's definition changes container because for
# material diameter, it needs to be applied to all extruders, but here we don't know how many extruders
# a machine actually has and how many extruders has already been loaded for that machine, so we have to
# keep this setting for any remaining extruders that haven't been loaded yet.
#
# Those settings will be removed in ExtruderManager which knows all those info.
@override(ContainerStack)
def getNextStack(self) -> Optional["GlobalStack"]:
return super().getNextStack()
@ -119,6 +50,29 @@ class ExtruderStack(CuraContainerStack):
def getLoadingPriority(cls) -> int:
return 3
## Return the filament diameter that the machine requires.
#
# If the machine has no requirement for the diameter, -1 is returned.
# \return The filament diameter for the printer
@property
def materialDiameter(self) -> float:
context = PropertyEvaluationContext(self)
context.context["evaluate_from_container_index"] = _ContainerIndexes.Variant
return self.getProperty("material_diameter", "value", context = context)
## Return the approximate filament diameter that the machine requires.
#
# The approximate material diameter is the material diameter rounded to
# the nearest millimetre.
#
# If the machine has no requirement for the diameter, -1 is returned.
#
# \return The approximate filament diameter for the printer
@pyqtProperty(float)
def approximateMaterialDiameter(self) -> float:
return round(float(self.materialDiameter))
## Overridden from ContainerStack
#
# It will perform a few extra checks when trying to get properties.
@ -187,11 +141,6 @@ class ExtruderStack(CuraContainerStack):
if has_global_dependencies:
self.getNextStack().propertiesChanged.emit(key, properties)
def findDefaultVariant(self):
# The default variant is defined in the machine stack and/or definition, so use the machine stack to find
# the default variant.
return self.getNextStack().findDefaultVariant()
extruder_stack_mime = MimeType(
name = "application/x-cura-extruderstack",

View file

@ -125,21 +125,6 @@ class GlobalStack(CuraContainerStack):
def setNextStack(self, next_stack: ContainerStack) -> None:
raise Exceptions.InvalidOperationError("Global stack cannot have a next stack!")
## Gets the approximate filament diameter that the machine requires.
#
# The approximate material diameter is the material diameter rounded to
# the nearest millimetre.
#
# If the machine has no requirement for the diameter, -1 is returned.
#
# \return The approximate filament diameter for the printer, as a string.
@pyqtProperty(str)
def approximateMaterialDiameter(self) -> str:
material_diameter = self.definition.getProperty("material_diameter", "value")
if material_diameter is None:
return "-1"
return str(round(float(material_diameter))) #Round, then convert back to string.
# protected:
# Determine whether or not we should try to get the "resolve" property instead of the

File diff suppressed because it is too large Load diff

View file

@ -1,57 +0,0 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QObject, pyqtSlot #To expose data to QML.
from cura.Settings.ContainerManager import ContainerManager
from UM.Logger import Logger
from UM.Message import Message #To create a warning message about material diameter.
from UM.i18n import i18nCatalog #Translated strings.
catalog = i18nCatalog("cura")
## Handles material-related data, processing requests to change them and
# providing data for the GUI.
#
# TODO: Move material-related managing over from the machine manager to here.
class MaterialManager(QObject):
## Creates the global values for the material manager to use.
def __init__(self, parent = None):
super().__init__(parent)
#Material diameter changed warning message.
self._material_diameter_warning_message = Message(catalog.i18nc("@info:status Has a cancel button next to it.",
"The selected material diameter causes the material to become incompatible with the current printer."), title = catalog.i18nc("@info:title", "Incompatible Material"))
self._material_diameter_warning_message.addAction("Undo", catalog.i18nc("@action:button", "Undo"), None, catalog.i18nc("@action", "Undo changing the material diameter."))
self._material_diameter_warning_message.actionTriggered.connect(self._materialWarningMessageAction)
## Creates an instance of the MaterialManager.
#
# This should only be called by PyQt to create the singleton instance of
# this class.
@staticmethod
def createMaterialManager(engine = None, script_engine = None):
return MaterialManager()
@pyqtSlot(str, str)
def showMaterialWarningMessage(self, material_id, previous_diameter):
self._material_diameter_warning_message.previous_diameter = previous_diameter #Make sure that the undo button can properly undo the action.
self._material_diameter_warning_message.material_id = material_id
self._material_diameter_warning_message.show()
## Called when clicking "undo" on the warning dialogue for disappeared
# materials.
#
# This executes the undo action, restoring the material diameter.
#
# \param button The identifier of the button that was pressed.
def _materialWarningMessageAction(self, message, button):
if button == "Undo":
container_manager = ContainerManager.getInstance()
container_manager.setContainerMetaDataEntry(self._material_diameter_warning_message.material_id, "properties/diameter", self._material_diameter_warning_message.previous_diameter)
approximate_previous_diameter = str(round(float(self._material_diameter_warning_message.previous_diameter)))
container_manager.setContainerMetaDataEntry(self._material_diameter_warning_message.material_id, "approximate_diameter", approximate_previous_diameter)
container_manager.setContainerProperty(self._material_diameter_warning_message.material_id, "material_diameter", "value", self._material_diameter_warning_message.previous_diameter);
message.hide()
else:
Logger.log("w", "Unknown button action for material diameter warning message: {action}".format(action = button))

View file

@ -3,6 +3,7 @@
import UM.Settings.Models.SettingVisibilityHandler
class MaterialSettingsVisibilityHandler(UM.Settings.Models.SettingVisibilityHandler.SettingVisibilityHandler):
def __init__(self, parent = None, *args, **kwargs):
super().__init__(parent = parent, *args, **kwargs)

View file

@ -1,37 +0,0 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Any, List
from UM.Settings.ContainerRegistry import ContainerRegistry #To listen for changes to the materials.
from UM.Settings.Models.InstanceContainersModel import InstanceContainersModel #We're extending this class.
## A model that shows a list of currently valid materials.
class MaterialsModel(InstanceContainersModel):
def __init__(self, parent = None):
super().__init__(parent)
ContainerRegistry.getInstance().containerMetaDataChanged.connect(self._onContainerMetaDataChanged)
## Called when the metadata of the container was changed.
#
# This makes sure that we only update when it was a material that changed.
#
# \param container The container whose metadata was changed.
def _onContainerMetaDataChanged(self, container):
if container.getMetaDataEntry("type") == "material": #Only need to update if a material was changed.
self._container_change_timer.start()
def _onContainerChanged(self, container):
if container.getMetaDataEntry("type", "") == "material":
super()._onContainerChanged(container)
## Group brand together
def _sortKey(self, item) -> List[Any]:
result = []
result.append(item["metadata"]["brand"])
result.append(item["metadata"]["material"])
result.append(item["metadata"]["name"])
result.append(item["metadata"]["color_name"])
result.append(item["metadata"]["id"])
result.extend(super()._sortKey(item))
return result

View file

@ -1,222 +0,0 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from collections import OrderedDict
from PyQt5.QtCore import Qt
from UM.Application import Application
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.Models.InstanceContainersModel import InstanceContainersModel
from cura.QualityManager import QualityManager
from cura.Settings.ExtruderManager import ExtruderManager
from typing import List, TYPE_CHECKING
if TYPE_CHECKING:
from cura.Settings.ExtruderStack import ExtruderStack
## QML Model for listing the current list of valid quality profiles.
#
class ProfilesModel(InstanceContainersModel):
LayerHeightRole = Qt.UserRole + 1001
LayerHeightWithoutUnitRole = Qt.UserRole + 1002
AvailableRole = Qt.UserRole + 1003
def __init__(self, parent = None):
super().__init__(parent)
self.addRoleName(self.LayerHeightRole, "layer_height")
self.addRoleName(self.LayerHeightWithoutUnitRole, "layer_height_without_unit")
self.addRoleName(self.AvailableRole, "available")
Application.getInstance().globalContainerStackChanged.connect(self._update)
Application.getInstance().getMachineManager().activeVariantChanged.connect(self._update)
Application.getInstance().getMachineManager().activeStackChanged.connect(self._update)
Application.getInstance().getMachineManager().activeMaterialChanged.connect(self._update)
self._empty_quality = ContainerRegistry.getInstance().findContainers(id = "empty_quality")[0]
# Factory function, used by QML
@staticmethod
def createProfilesModel(engine, js_engine):
return ProfilesModel.getInstance()
## Get the singleton instance for this class.
@classmethod
def getInstance(cls) -> "ProfilesModel":
# Note: Explicit use of class name to prevent issues with inheritance.
if not ProfilesModel.__instance:
ProfilesModel.__instance = cls()
return ProfilesModel.__instance
@classmethod
def hasInstance(cls) -> bool:
return ProfilesModel.__instance is not None
__instance = None # type: "ProfilesModel"
## Fetch the list of containers to display.
#
# See UM.Settings.Models.InstanceContainersModel._fetchInstanceContainers().
def _fetchInstanceContainers(self):
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack is None:
return {}, {}
global_stack_definition = global_container_stack.definition
# Get the list of extruders and place the selected extruder at the front of the list.
extruder_stacks = self._getOrderedExtruderStacksList()
materials = [extruder.material for extruder in extruder_stacks]
# Fetch the list of usable qualities across all extruders.
# The actual list of quality profiles come from the first extruder in the extruder list.
result = QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks)
# The usable quality types are set
quality_type_set = set([x.getMetaDataEntry("quality_type") for x in result])
# Fetch all qualities available for this machine and the materials selected in extruders
all_qualities = QualityManager.getInstance().findAllQualitiesForMachineAndMaterials(global_stack_definition, materials)
# If in the all qualities there is some of them that are not available due to incompatibility with materials
# we also add it so that they will appear in the slide quality bar. However in recomputeItems will be marked as
# not available so they will be shown in gray
for quality in all_qualities:
if quality.getMetaDataEntry("quality_type") not in quality_type_set:
result.append(quality)
if len(result) > 1 and self._empty_quality in result:
result.remove(self._empty_quality)
return {item.getId(): item for item in result}, {} #Only return true profiles for now, no metadata. The quality manager is not able to get only metadata yet.
## Re-computes the items in this model, and adds the layer height role.
def _recomputeItems(self):
# Some globals that we can re-use.
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack is None:
return
extruder_stacks = self._getOrderedExtruderStacksList()
container_registry = ContainerRegistry.getInstance()
# Get a list of usable/available qualities for this machine and material
qualities = QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks)
unit = global_container_stack.getBottom().getProperty("layer_height", "unit")
if not unit:
unit = ""
# group all quality items according to quality_types, so we know which profile suits the currently
# active machine and material, and later yield the right ones.
tmp_all_quality_items = OrderedDict()
for item in super()._recomputeItems():
profiles = container_registry.findContainersMetadata(id = item["id"])
if not profiles or "quality_type" not in profiles[0]:
quality_type = ""
else:
quality_type = profiles[0]["quality_type"]
if quality_type not in tmp_all_quality_items:
tmp_all_quality_items[quality_type] = {"suitable_container": None, "all_containers": []}
tmp_all_quality_items[quality_type]["all_containers"].append(item)
if tmp_all_quality_items[quality_type]["suitable_container"] is None:
tmp_all_quality_items[quality_type]["suitable_container"] = item
# reverse the ordering (finest first, coarsest last)
all_quality_items = OrderedDict()
for key in reversed(tmp_all_quality_items.keys()):
all_quality_items[key] = tmp_all_quality_items[key]
# First the suitable containers are set in the model
containers = []
for data_item in all_quality_items.values():
suitable_item = data_item["suitable_container"]
if suitable_item is not None:
containers.append(suitable_item)
# Once the suitable containers are collected, the rest of the containers are appended
for data_item in all_quality_items.values():
for item in data_item["all_containers"]:
if item not in containers:
containers.append(item)
# Now all the containers are set
for item in containers:
profile = container_registry.findContainers(id = item["id"])
# When for some reason there is no profile container in the registry
if not profile:
self._setItemLayerHeight(item, "", "")
item["available"] = False
yield item
continue
profile = profile[0]
# When there is a profile but it's an empty quality should. It's shown in the list (they are "Not Supported" profiles)
if profile.getId() == "empty_quality":
self._setItemLayerHeight(item, "", "")
item["available"] = True
yield item
continue
item["available"] = profile in qualities
# Easy case: This profile defines its own layer height.
if profile.hasProperty("layer_height", "value"):
self._setItemLayerHeight(item, profile.getProperty("layer_height", "value"), unit)
yield item
continue
machine_manager = Application.getInstance().getMachineManager()
# Quality-changes profile that has no value for layer height. Get the corresponding quality profile and ask that profile.
quality_type = profile.getMetaDataEntry("quality_type", None)
if quality_type:
quality_results = machine_manager.determineQualityAndQualityChangesForQualityType(quality_type)
for quality_result in quality_results:
if quality_result["stack"] is global_container_stack:
quality = quality_result["quality"]
break
else:
# No global container stack in the results:
if quality_results:
# Take any of the extruders.
quality = quality_results[0]["quality"]
else:
quality = None
if quality and quality.hasProperty("layer_height", "value"):
self._setItemLayerHeight(item, quality.getProperty("layer_height", "value"), unit)
yield item
continue
# Quality has no value for layer height either. Get the layer height from somewhere lower in the stack.
skip_until_container = global_container_stack.material
if not skip_until_container or skip_until_container == ContainerRegistry.getInstance().getEmptyInstanceContainer(): # No material in stack.
skip_until_container = global_container_stack.variant
if not skip_until_container or skip_until_container == ContainerRegistry.getInstance().getEmptyInstanceContainer(): # No variant in stack.
skip_until_container = global_container_stack.getBottom()
self._setItemLayerHeight(item, global_container_stack.getRawProperty("layer_height", "value", skip_until_container = skip_until_container.getId()), unit) # Fall through to the currently loaded material.
yield item
## Get a list of extruder stacks with the active extruder at the front of the list.
@staticmethod
def _getOrderedExtruderStacksList() -> List["ExtruderStack"]:
extruder_manager = ExtruderManager.getInstance()
extruder_stacks = extruder_manager.getActiveExtruderStacks()
active_extruder = extruder_manager.getActiveExtruderStack()
if active_extruder in extruder_stacks:
extruder_stacks.remove(active_extruder)
extruder_stacks = [active_extruder] + extruder_stacks
return extruder_stacks
@staticmethod
def _setItemLayerHeight(item, value, unit):
item["layer_height"] = str(value) + unit
item["layer_height_without_unit"] = str(value)

View file

@ -1,54 +0,0 @@
# Copyright (c) 2016 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Application import Application
from UM.Settings.ContainerRegistry import ContainerRegistry
from cura.QualityManager import QualityManager
from cura.Settings.ProfilesModel import ProfilesModel
from cura.Settings.ExtruderManager import ExtruderManager
## QML Model for listing the current list of valid quality and quality changes profiles.
#
class QualityAndUserProfilesModel(ProfilesModel):
def __init__(self, parent = None):
super().__init__(parent)
self._empty_quality = ContainerRegistry.getInstance().findInstanceContainers(id = "empty_quality")[0]
## Fetch the list of containers to display.
#
# See UM.Settings.Models.InstanceContainersModel._fetchInstanceContainers().
def _fetchInstanceContainers(self):
global_container_stack = Application.getInstance().getGlobalContainerStack()
if not global_container_stack:
return {}, {}
# Fetch the list of quality changes.
quality_manager = QualityManager.getInstance()
machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.definition)
quality_changes_list = quality_manager.findAllQualityChangesForMachine(machine_definition)
extruder_manager = ExtruderManager.getInstance()
active_extruder = extruder_manager.getActiveExtruderStack()
extruder_stacks = self._getOrderedExtruderStacksList()
# Fetch the list of usable qualities across all extruders.
# The actual list of quality profiles come from the first extruder in the extruder list.
quality_list = quality_manager.findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks)
# Filter the quality_change by the list of available quality_types
quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list])
# Also show custom profiles based on "Not Supported" quality profile
quality_type_set.add(self._empty_quality.getMetaDataEntry("quality_type"))
filtered_quality_changes = {qc.getId(): qc for qc in quality_changes_list if
qc.getMetaDataEntry("quality_type") in quality_type_set and
qc.getMetaDataEntry("extruder") is not None and
(qc.getMetaDataEntry("extruder") == active_extruder.definition.getMetaDataEntry("quality_definition") or
qc.getMetaDataEntry("extruder") == active_extruder.definition.getId())}
result = filtered_quality_changes
for q in quality_list:
if q.getId() != "empty_quality":
result[q.getId()] = q
return result, {} #Only return true profiles for now, no metadata. The quality manager is not able to get only metadata yet.

View file

@ -1,249 +0,0 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtProperty, pyqtSignal, Qt
from UM.Logger import Logger
import UM.Qt
from UM.Application import Application
from UM.Settings.ContainerRegistry import ContainerRegistry
import os
from UM.i18n import i18nCatalog
class QualitySettingsModel(UM.Qt.ListModel.ListModel):
KeyRole = Qt.UserRole + 1
LabelRole = Qt.UserRole + 2
UnitRole = Qt.UserRole + 3
ProfileValueRole = Qt.UserRole + 4
ProfileValueSourceRole = Qt.UserRole + 5
UserValueRole = Qt.UserRole + 6
CategoryRole = Qt.UserRole + 7
def __init__(self, parent = None):
super().__init__(parent = parent)
self._container_registry = ContainerRegistry.getInstance()
self._extruder_id = None
self._extruder_definition_id = None
self._quality_id = None
self._material_id = None
self._i18n_catalog = None
self.addRoleName(self.KeyRole, "key")
self.addRoleName(self.LabelRole, "label")
self.addRoleName(self.UnitRole, "unit")
self.addRoleName(self.ProfileValueRole, "profile_value")
self.addRoleName(self.ProfileValueSourceRole, "profile_value_source")
self.addRoleName(self.UserValueRole, "user_value")
self.addRoleName(self.CategoryRole, "category")
self._empty_quality = self._container_registry.findInstanceContainers(id = "empty_quality")[0]
def setExtruderId(self, extruder_id):
if extruder_id != self._extruder_id:
self._extruder_id = extruder_id
self._update()
self.extruderIdChanged.emit()
extruderIdChanged = pyqtSignal()
@pyqtProperty(str, fset = setExtruderId, notify = extruderIdChanged)
def extruderId(self):
return self._extruder_id
def setExtruderDefinition(self, extruder_definition):
if extruder_definition != self._extruder_definition_id:
self._extruder_definition_id = extruder_definition
self._update()
self.extruderDefinitionChanged.emit()
extruderDefinitionChanged = pyqtSignal()
@pyqtProperty(str, fset = setExtruderDefinition, notify = extruderDefinitionChanged)
def extruderDefinition(self):
return self._extruder_definition_id
def setQuality(self, quality):
if quality != self._quality_id:
self._quality_id = quality
self._update()
self.qualityChanged.emit()
qualityChanged = pyqtSignal()
@pyqtProperty(str, fset = setQuality, notify = qualityChanged)
def quality(self):
return self._quality_id
def setMaterial(self, material):
if material != self._material_id:
self._material_id = material
self._update()
self.materialChanged.emit()
materialChanged = pyqtSignal()
@pyqtProperty(str, fset = setMaterial, notify = materialChanged)
def material(self):
return self._material_id
def _update(self):
if not self._quality_id:
return
items = []
definition_container = Application.getInstance().getGlobalContainerStack().getBottom()
containers = self._container_registry.findInstanceContainers(id = self._quality_id)
if not containers:
Logger.log("w", "Could not find a quality container with id %s", self._quality_id)
return
quality_container = None
quality_changes_container = None
if containers[0].getMetaDataEntry("type") == "quality":
quality_container = containers[0]
else:
quality_changes_container = containers[0]
if quality_changes_container.getMetaDataEntry("quality_type") == self._empty_quality.getMetaDataEntry("quality_type"):
quality_container = self._empty_quality
else:
criteria = {
"type": "quality",
"quality_type": quality_changes_container.getMetaDataEntry("quality_type"),
"definition": quality_changes_container.getDefinition().getId()
}
quality_container = self._container_registry.findInstanceContainers(**criteria)
if not quality_container:
Logger.log("w", "Could not find a quality container matching quality changes %s", quality_changes_container.getId())
return
quality_container = quality_container[0]
quality_type = quality_container.getMetaDataEntry("quality_type")
if quality_type == "not_supported":
containers = []
else:
definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(quality_container.getDefinition())
definition = quality_container.getDefinition()
# Check if the definition container has a translation file.
definition_suffix = ContainerRegistry.getMimeTypeForContainer(type(definition)).preferredSuffix
catalog = i18nCatalog(os.path.basename(definition_id + "." + definition_suffix))
if catalog.hasTranslationLoaded():
self._i18n_catalog = catalog
for file_name in quality_container.getDefinition().getInheritedFiles():
catalog = i18nCatalog(os.path.basename(file_name))
if catalog.hasTranslationLoaded():
self._i18n_catalog = catalog
criteria = {"type": "quality", "quality_type": quality_type, "definition": definition_id}
if self._material_id and self._material_id != "empty_material":
criteria["material"] = self._material_id
criteria["extruder"] = self._extruder_id
containers = self._container_registry.findInstanceContainers(**criteria)
if not containers:
# Try again, this time without extruder
new_criteria = criteria.copy()
new_criteria.pop("extruder")
containers = self._container_registry.findInstanceContainers(**new_criteria)
if not containers and "material" in criteria:
# Try again, this time without material
criteria.pop("material", None)
containers = self._container_registry.findInstanceContainers(**criteria)
if not containers:
# Try again, this time without material or extruder
criteria.pop("extruder") # "material" has already been popped
containers = self._container_registry.findInstanceContainers(**criteria)
if not containers:
Logger.log("w", "Could not find any quality containers matching the search criteria %s" % str(criteria))
return
if quality_changes_container:
if quality_type == "not_supported":
criteria = {"type": "quality_changes", "quality_type": quality_type, "name": quality_changes_container.getName()}
else:
criteria = {"type": "quality_changes", "quality_type": quality_type, "definition": definition_id, "name": quality_changes_container.getName()}
if self._extruder_definition_id != "":
extruder_definitions = self._container_registry.findDefinitionContainers(id = self._extruder_definition_id)
if extruder_definitions:
criteria["extruder"] = Application.getInstance().getMachineManager().getQualityDefinitionId(extruder_definitions[0])
criteria["name"] = quality_changes_container.getName()
else:
criteria["extruder"] = None
changes = self._container_registry.findInstanceContainers(**criteria)
if changes:
containers.extend(changes)
global_container_stack = Application.getInstance().getGlobalContainerStack()
current_category = ""
for definition in definition_container.findDefinitions():
if definition.type == "category":
current_category = definition.label
if self._i18n_catalog:
current_category = self._i18n_catalog.i18nc(definition.key + " label", definition.label)
continue
profile_value = None
profile_value_source = ""
for container in containers:
new_value = container.getProperty(definition.key, "value")
if new_value is not None:
profile_value_source = container.getMetaDataEntry("type")
profile_value = new_value
# Global tab should use resolve (if there is one)
if not self._extruder_id:
resolve_value = global_container_stack.getProperty(definition.key, "resolve")
if resolve_value is not None and profile_value is not None and profile_value_source != "quality_changes":
profile_value = resolve_value
user_value = None
if not self._extruder_id:
user_value = global_container_stack.getTop().getProperty(definition.key, "value")
else:
extruder_stack = self._container_registry.findContainerStacks(id = self._extruder_id)
if extruder_stack:
user_value = extruder_stack[0].getTop().getProperty(definition.key, "value")
if profile_value is None and user_value is None:
continue
settable_per_extruder = global_container_stack.getProperty(definition.key, "settable_per_extruder")
# If a setting is not settable per extruder (global) and we're looking at an extruder tab, don't show this value.
if self._extruder_id != "" and not settable_per_extruder:
continue
# If a setting is settable per extruder (not global) and we're looking at global tab, don't show this value.
if self._extruder_id == "" and settable_per_extruder:
continue
label = definition.label
if self._i18n_catalog:
label = self._i18n_catalog.i18nc(definition.key + " label", label)
items.append({
"key": definition.key,
"label": label,
"unit": definition.unit,
"profile_value": "" if profile_value is None else str(profile_value), # it is for display only
"profile_value_source": profile_value_source,
"user_value": "" if user_value is None else str(user_value),
"category": current_category
})
self.setItems(items)

View file

@ -3,12 +3,14 @@
import copy
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
from UM.Signal import Signal, signalemitter
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Logger import Logger
from UM.Settings.Validator import ValidatorState
from PyQt5.QtCore import QTimer
from UM.Application import Application
from cura.Settings.PerObjectContainerStack import PerObjectContainerStack
@ -38,6 +40,10 @@ class SettingOverrideDecorator(SceneNodeDecorator):
self._extruder_stack = ExtruderManager.getInstance().getExtruderStack(0).getId()
self._is_non_printing_mesh = False
self._error_check_timer = QTimer()
self._error_check_timer.setInterval(250)
self._error_check_timer.setSingleShot(True)
self._error_check_timer.timeout.connect(self._checkStackForErrors)
self._stack.propertyChanged.connect(self._onSettingChanged)
@ -93,9 +99,21 @@ class SettingOverrideDecorator(SceneNodeDecorator):
# Trigger slice/need slicing if the value has changed.
if property_name == "value":
self._is_non_printing_mesh = any(bool(self._stack.getProperty(setting, "value")) for setting in self._non_printing_mesh_settings)
if not self._is_non_printing_mesh:
# self._error_check_timer.start()
self._checkStackForErrors()
Application.getInstance().getBackend().needsSlicing()
Application.getInstance().getBackend().tickle()
Application.getInstance().getBackend().needsSlicing()
Application.getInstance().getBackend().tickle()
def _checkStackForErrors(self):
hasErrors = False;
for key in self._stack.getAllKeys():
validation_state = self._stack.getProperty(key, "validationState")
if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError):
Logger.log("w", "Setting Per Object %s is not valid.", key)
hasErrors = True
break
Application.getInstance().getObjectsModel().setStacksHaveErrors(hasErrors)
## Makes sure that the stack upon which the container stack is placed is
# kept up to date.

View file

@ -1,85 +0,0 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Application import Application
from UM.Settings.ContainerRegistry import ContainerRegistry
from cura.QualityManager import QualityManager
from cura.Settings.ProfilesModel import ProfilesModel
from cura.Settings.ExtruderManager import ExtruderManager
## QML Model for listing the current list of valid quality changes profiles.
#
class UserProfilesModel(ProfilesModel):
def __init__(self, parent = None):
super().__init__(parent)
#Need to connect to the metaDataChanged signal of the active materials.
self.__current_extruders = []
self.__current_materials = []
Application.getInstance().getExtruderManager().extrudersChanged.connect(self.__onExtrudersChanged)
self.__onExtrudersChanged()
self.__current_materials = [extruder.material for extruder in self.__current_extruders]
for material in self.__current_materials:
material.metaDataChanged.connect(self._onContainerChanged)
self._empty_quality = ContainerRegistry.getInstance().findContainers(id = "empty_quality")[0]
## Fetch the list of containers to display.
#
# See UM.Settings.Models.InstanceContainersModel._fetchInstanceContainers().
def _fetchInstanceContainers(self):
global_container_stack = Application.getInstance().getGlobalContainerStack()
if not global_container_stack:
return {}, {}
# Fetch the list of quality changes.
quality_manager = QualityManager.getInstance()
machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.definition)
quality_changes_list = quality_manager.findAllQualityChangesForMachine(machine_definition)
extruder_manager = ExtruderManager.getInstance()
active_extruder = extruder_manager.getActiveExtruderStack()
extruder_stacks = self._getOrderedExtruderStacksList()
# Fetch the list of usable qualities across all extruders.
# The actual list of quality profiles come from the first extruder in the extruder list.
quality_list = quality_manager.findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks)
# Filter the quality_change by the list of available quality_types
quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list])
quality_type_set.add(self._empty_quality.getMetaDataEntry("quality_type"))
filtered_quality_changes = {qc.getId():qc for qc in quality_changes_list if
qc.getMetaDataEntry("quality_type") in quality_type_set and
qc.getMetaDataEntry("extruder") is not None and
(qc.getMetaDataEntry("extruder") == active_extruder.definition.getMetaDataEntry("quality_definition") or
qc.getMetaDataEntry("extruder") == active_extruder.definition.getId())}
return filtered_quality_changes, {} #Only return true profiles for now, no metadata. The quality manager is not able to get only metadata yet.
## Called when a container changed on an extruder stack.
#
# If it's the material we need to connect to the metaDataChanged signal of
# that.
def __onContainerChanged(self, new_container):
#Careful not to update when a quality or quality changes profile changed!
#If you then update you're going to have an infinite recursion because the update may change the container.
if new_container.getMetaDataEntry("type") == "material":
for material in self.__current_materials:
material.metaDataChanged.disconnect(self._onContainerChanged)
self.__current_materials = [extruder.material for extruder in self.__current_extruders]
for material in self.__current_materials:
material.metaDataChanged.connect(self._onContainerChanged)
## Called when the current set of extruders change.
#
# This makes sure that we are listening to the signal for when the
# materials change.
def __onExtrudersChanged(self):
for extruder in self.__current_extruders:
extruder.containersChanged.disconnect(self.__onContainerChanged)
self.__current_extruders = Application.getInstance().getExtruderManager().getExtruderStacks()
for extruder in self.__current_extruders:
extruder.containersChanged.connect(self.__onContainerChanged)