mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-08-09 14:55:03 -06:00
Merge branch 'master' into feature_sync_button
This commit is contained in:
commit
13e18c9d54
568 changed files with 6406 additions and 6604 deletions
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
@ -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))
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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)
|
|
@ -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.
|
|
@ -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)
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
Loading…
Add table
Add a link
Reference in a new issue