Move Cura setting related classes to cura/Settings

Contributes to CURA-342
This commit is contained in:
Arjen Hiemstra 2016-07-04 12:07:29 +02:00
parent 7008e047f6
commit 0e28b331fb
7 changed files with 9 additions and 0 deletions

View file

@ -0,0 +1,62 @@
# Copyright (c) 2016 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
import UM.Settings
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
class ContainerManager(QObject):
def __init__(self, parent = None):
super().__init__(parent)
self._registry = UM.Settings.ContainerRegistry.getInstance()
@pyqtSlot(str)
def duplicateContainer(self, container_id):
containers = self._registry.findInstanceContainers(id = container_id)
if not containers:
return
new_name = self_registry.uniqueName(containers[0].getName())
new_material = containers[0].duplicate(new_name)
self._registry.addContainer(new_material)
@pyqtSlot(str, str)
def renameContainer(self, container_id, new_name):
pass
@pyqtSlot(str)
def removeContainer(self, container_id):
pass
@pyqtSlot(str, str, str)
def setContainerMetaDataEntry(self, container_id, entry_name, entry_value):
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = container_id)
if not containers:
return
container = containers[0]
entries = entry_name.split("/")
entry_name = entries.pop()
if entries:
root_name = entries.pop(0)
root = container.getMetaDataEntry(root_name)
item = root
for entry in entries:
item = item.get(entries.pop(0), { })
item[entry_name] = entry_value
entry_name = root_name
entry_value = root
containers[0].setMetaDataEntry(entry_name, entry_value)
def createContainerManager(engine, js_engine):
return ContainerManager()

View file

@ -0,0 +1,93 @@
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):
self.clear()
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
keys.sort()
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("")
self.appendItem({
"key": key,
"values": values,
"label": definition.label,
"unit": definition.unit,
"category": category.label
})
## Set the ids of the containers which have the settings this model should list.
# Also makes sure the model updates when the containers have property changes
def setContainers(self, container_ids):
for container in self._containers:
container.propertyChanged.disconnect(self._onPropertyChanged)
self._container_ids = container_ids
self._containers = []
for container_id in self._container_ids:
containers = ContainerRegistry.getInstance().findContainers(id = container_id)
if containers:
containers[0].propertyChanged.connect(self._onPropertyChanged)
self._containers.append(containers[0])
self._update()
containersChanged = pyqtSignal()
@pyqtProperty("QVariantList", fset = setContainers, notify = containersChanged)
def containers(self):
return self.container_ids

View file

@ -0,0 +1,209 @@
# Copyright (c) 2016 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
import os
import os.path
import re
from PyQt5.QtWidgets import QMessageBox
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Application import Application
from UM.Logger import Logger
from UM.Message import Message
from UM.Platform import Platform
from UM.PluginRegistry import PluginRegistry #For getting the possible profile writers to write with.
from UM.Util import parseBool
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
class CuraContainerRegistry(ContainerRegistry):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
## Create a name that is not empty and unique
# \param container_type \type{string} Type of the container (machine, quality, ...)
# \param current_name \type{} Current name of the container, which may be an acceptable option
# \param new_name \type{string} Base name, which may not be unique
# \param fallback_name \type{string} Name to use when (stripped) new_name is empty
# \return \type{string} Name that is unique for the specified type and name/id
def createUniqueName(self, container_type, current_name, new_name, fallback_name):
new_name = new_name.strip()
num_check = re.compile("(.*?)\s*#\d+$").match(new_name)
if num_check:
new_name = num_check.group(1)
if new_name == "":
new_name = fallback_name
unique_name = new_name
i = 1
# In case we are renaming, the current name of the container is also a valid end-result
while self._containerExists(container_type, unique_name) and unique_name != current_name:
i += 1
unique_name = "%s #%d" % (new_name, i)
return unique_name
## Check if a container with of a certain type and a certain name or id exists
# Both the id and the name are checked, because they may not be the same and it is better if they are both unique
# \param container_type \type{string} Type of the container (machine, quality, ...)
# \param container_name \type{string} Name to check
def _containerExists(self, container_type, container_name):
container_class = ContainerStack if container_type == "machine" else InstanceContainer
return self.findContainers(container_class, id = container_name, type = container_type) or \
self.findContainers(container_class, name = container_name, type = container_type)
## Exports an profile to a file
#
# \param instance_id \type{str} the ID of the profile 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_id, file_name, file_type):
Logger.log('d', 'exportProfile instance_id: '+str(instance_id))
# 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.
if split < 0: # Not found. Invalid format.
Logger.log("e", "Invalid file format identifier %s", file_type)
return
description = file_type[:split]
extension = file_type[split + 4:-1] # Leave out the " (*." and ")".
if not file_name.endswith("." + extension): # Auto-fill the extension if the user did not provide any.
file_name += "." + extension
# On Windows, QML FileDialog properly asks for overwrite confirm, but not on other platforms, so handle those ourself.
if not Platform.isWindows():
if os.path.exists(file_name):
result = QMessageBox.question(None, catalog.i18nc("@title:window", "File Already Exists"),
catalog.i18nc("@label", "The file <filename>{0}</filename> already exists. Are you sure you want to overwrite it?").format(file_name))
if result == QMessageBox.No:
return
containers = ContainerRegistry.getInstance().findInstanceContainers(id=instance_id)
if not containers:
return
container = containers[0]
profile_writer = self._findProfileWriter(extension, description)
try:
success = profile_writer.write(file_name, container)
except Exception as e:
Logger.log("e", "Failed to export profile to %s: %s", file_name, str(e))
m = Message(catalog.i18nc("@info:status", "Failed to export profile to <filename>{0}</filename>: <message>{1}</message>", file_name, str(e)), lifetime = 0)
m.show()
return
if not success:
Logger.log("w", "Failed to export profile to %s: Writer plugin reported failure.", file_name)
m = Message(catalog.i18nc("@info:status", "Failed to export profile to <filename>{0}</filename>: Writer plugin reported failure.", file_name), lifetime = 0)
m.show()
return
m = Message(catalog.i18nc("@info:status", "Exported profile to <filename>{0}</filename>", file_name))
m.show()
## Gets the plugin object matching the criteria
# \param extension
# \param description
# \return The plugin object matching the given extension and description.
def _findProfileWriter(self, extension, description):
plugin_registry = PluginRegistry.getInstance()
for plugin_id, meta_data in self._getIOPlugins("profile_writer"):
for supported_type in meta_data["profile_writer"]: # All file types this plugin can supposedly write.
supported_extension = supported_type.get("extension", None)
if supported_extension == extension: # This plugin supports a file type with the same extension.
supported_description = supported_type.get("description", None)
if supported_description == description: # The description is also identical. Assume it's the same file type.
return plugin_registry.getPluginObject(plugin_id)
return None
## Imports a profile from a file
#
# \param file_name \type{str} the full path and filename of the profile to import
# \return \type{Dict} dict with a 'status' key containing the string 'ok' or 'error', and a 'message' key
# containing a message for the user
def importProfile(self, file_name):
if not file_name:
return { "status": "error", "message": catalog.i18nc("@info:status", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, "Invalid path")}
plugin_registry = PluginRegistry.getInstance()
for plugin_id, meta_data in self._getIOPlugins("profile_reader"):
profile_reader = plugin_registry.getPluginObject(plugin_id)
try:
profile = profile_reader.read(file_name) #Try to open the file with the profile reader.
except Exception as e:
#Note that this will fail quickly. That is, if any profile reader throws an exception, it will stop reading. It will only continue reading if the reader returned None.
Logger.log("e", "Failed to import profile from %s: %s", file_name, str(e))
return { "status": "error", "message": catalog.i18nc("@info:status", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, str(e))}
if profile: #Success!
profile.setReadOnly(False)
new_name = self.createUniqueName("quality", "", os.path.splitext(os.path.basename(file_name))[0],
catalog.i18nc("@label", "Custom profile"))
profile.setName(new_name)
profile._id = new_name
if self._machineHasOwnQualities():
profile.setDefinition(self._activeDefinition())
if self._machineHasOwnMaterials():
profile.addMetaDataEntry("material", self._activeMaterialId())
else:
profile.setDefinition(ContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")[0])
ContainerRegistry.getInstance().addContainer(profile)
return { "status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile.getName()) }
#If it hasn't returned by now, none of the plugins loaded the profile successfully.
return { "status": "error", "message": catalog.i18nc("@info:status", "Profile {0} has an unknown file type.", file_name)}
## Gets a list of profile writer plugins
# \return List of tuples of (plugin_id, meta_data).
def _getIOPlugins(self, io_type):
plugin_registry = PluginRegistry.getInstance()
active_plugin_ids = plugin_registry.getActivePlugins()
result = []
for plugin_id in active_plugin_ids:
meta_data = plugin_registry.getMetaData(plugin_id)
if io_type in meta_data:
result.append( (plugin_id, meta_data) )
return result
## Gets the active definition
# \return the active definition object or None if there is no definition
def _activeDefinition(self):
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack:
definition = global_container_stack.getBottom()
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):
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack:
return global_container_stack.getMetaDataEntry("has_materials", False)
return False
## Gets the ID of the active material
# \return the ID of the active material or the empty string
def _activeMaterialId(self):
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack:
material = global_container_stack.findContainer({"type": "material"})
if material:
return material.getId()
return ""
## Returns true if the current machien requires its own quality profiles
# \return true if the current machien requires its own quality profiles
def _machineHasOwnQualities(self):
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack:
return parseBool(global_container_stack.getMetaDataEntry("has_machine_quality", False))
return False

View file

@ -0,0 +1,221 @@
# Copyright (c) 2016 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from PyQt5.QtCore import pyqtSignal, pyqtProperty, pyqtSlot, QObject, QVariant #For communicating data and events to Qt.
import UM.Application #To get the global container stack to find the current machine.
import UM.Logger
import UM.Settings.ContainerRegistry #Finding containers by ID.
## Manages all existing extruder stacks.
#
# This keeps a list of extruder stacks for each machine.
class ExtruderManager(QObject):
## Signal to notify other components when the list of extruders changes.
extrudersChanged = pyqtSignal(QVariant)
## Notify when the user switches the currently active extruder.
activeExtruderChanged = pyqtSignal()
## Registers listeners and such to listen to changes to the extruders.
def __init__(self, parent = None):
super().__init__(parent)
self._extruder_trains = { } #Per machine, a dictionary of extruder container stack IDs.
self._active_extruder_index = 0
UM.Application.getInstance().globalContainerStackChanged.connect(self._addCurrentMachineExtruders)
## Gets the unique identifier of the currently active extruder stack.
#
# The currently active extruder stack is the stack that is currently being
# edited.
#
# \return The unique ID of the currently active extruder stack.
@pyqtProperty(str, notify = activeExtruderChanged)
def activeExtruderStackId(self):
if not UM.Application.getInstance().getGlobalContainerStack():
return None #No active machine, so no active extruder.
try:
return self._extruder_trains[UM.Application.getInstance().getGlobalContainerStack().getBottom().getId()][str(self._active_extruder_index)].getId()
except KeyError: #Extruder index could be -1 if the global tab is selected, or the entry doesn't exist if the machine definition is wrong.
return None
## The instance of the singleton pattern.
#
# It's None if the extruder manager hasn't been created yet.
__instance = None
## Gets an instance of the extruder manager, or creates one if no instance
# exists yet.
#
# This is an implementation of singleton. If an extruder manager already
# exists, it is re-used.
#
# \return The extruder manager.
@classmethod
def getInstance(cls):
if not cls.__instance:
cls.__instance = ExtruderManager()
return cls.__instance
## Changes the active extruder by index.
#
# \param index The index of the new active extruder.
@pyqtSlot(int)
def setActiveExtruderIndex(self, index):
self._active_extruder_index = index
self.activeExtruderChanged.emit()
def getActiveExtruderStack(self):
global_container_stack = UM.Application.getInstance().getGlobalContainerStack()
if global_container_stack:
global_definition_container = UM.Application.getInstance().getGlobalContainerStack().getBottom()
if global_definition_container:
if global_definition_container.getId() in self._extruder_trains:
if str(self._active_extruder_index) in self._extruder_trains[global_definition_container.getId()]:
return self._extruder_trains[global_definition_container.getId()][str(self._active_extruder_index)]
## Adds all extruders of a specific machine definition to the extruder
# manager.
#
# \param machine_definition The machine to add the extruders for.
def addMachineExtruders(self, machine_definition):
machine_id = machine_definition.getId()
if machine_id not in self._extruder_trains:
self._extruder_trains[machine_id] = { }
container_registry = UM.Settings.ContainerRegistry.getInstance()
if not container_registry: #Then we shouldn't have any machine definition either. In any case, there are no extruder trains then so bye bye.
return
#Add the extruder trains that don't exist yet.
for extruder_definition in container_registry.findDefinitionContainers(machine = machine_definition.getId()):
position = extruder_definition.getMetaDataEntry("position", None)
if not position:
UM.Logger.log("w", "Extruder definition %s specifies no position metadata entry.", extruder_definition.getId())
if not container_registry.findContainerStacks(machine = machine_id, position = position): #Doesn't exist yet.
self.createExtruderTrain(extruder_definition, machine_definition, position)
#Gets the extruder trains that we just created as well as any that still existed.
extruder_trains = container_registry.findContainerStacks(type = "extruder_train", machine = machine_definition.getId())
for extruder_train in extruder_trains:
self._extruder_trains[machine_id][extruder_train.getMetaDataEntry("position")] = extruder_train
#Ensure that the extruder train stacks are linked to global stack.
extruder_train.setNextStack(UM.Application.getInstance().getGlobalContainerStack())
if extruder_trains:
self.extrudersChanged.emit(machine_definition)
## Creates a container stack for an extruder train.
#
# The container stack has an extruder definition at the bottom, which is
# linked to a machine definition. Then it has a variant profile, a material
# profile, a quality profile and a user profile, in that order.
#
# The resulting container stack is added to the registry.
#
# \param extruder_definition The extruder to create the extruder train
# for.
# \param machine_definition The machine that the extruder train belongs
# to.
# \param position The position of this extruder train in the extruder
# slots of the machine.
def createExtruderTrain(self, extruder_definition, machine_definition, position):
#Cache some things.
container_registry = UM.Settings.ContainerRegistry.getInstance()
machine_id = machine_definition.getId()
#Create a container stack for this extruder.
extruder_stack_id = container_registry.uniqueName(extruder_definition.getId())
container_stack = UM.Settings.ContainerStack(extruder_stack_id)
container_stack.setName(extruder_definition.getName()) #Take over the display name to display the stack with.
container_stack.addMetaDataEntry("type", "extruder_train")
container_stack.addMetaDataEntry("machine", machine_definition.getId())
container_stack.addMetaDataEntry("position", position)
container_stack.addContainer(extruder_definition)
#Find the variant to use for this extruder.
variant = container_registry.getEmptyInstanceContainer()
if machine_definition.getMetaDataEntry("has_variants"):
#First add any variant. Later, overwrite with preference if the preference is valid.
variants = container_registry.findInstanceContainers(definition = machine_id, type = "variant")
if len(variants) >= 1:
variant = variants[0]
preferred_variant_id = machine_definition.getMetaDataEntry("preferred_variant")
if preferred_variant_id:
preferred_variants = container_registry.findInstanceContainers(id = preferred_variant_id, type = "variant")
if len(preferred_variants) >= 1:
variant = preferred_variants[0]
else:
UM.Logger.log("w", "The preferred variant \"%s\" of machine %s doesn't exist or is not a variant profile.", preferred_variant_id, machine_id)
#And leave it at the default variant.
container_stack.addContainer(variant)
#Find a material to use for this variant.
material = container_registry.getEmptyInstanceContainer()
if machine_definition.getMetaDataEntry("has_materials"):
#First add any material. Later, overwrite with preference if the preference is valid.
if machine_definition.getMetaDataEntry("has_variant_materials", default = "False") == "True":
materials = container_registry.findInstanceContainers(type = "material", definition = machine_id, variant = variant.getId())
else:
materials = container_registry.findInstanceContainers(type = "material", definition = machine_id)
if len(materials) >= 1:
material = materials[0]
preferred_material_id = machine_definition.getMetaDataEntry("preferred_material")
if preferred_material_id:
preferred_materials = container_registry.findInstanceContainers(id = preferred_material_id, type = "material")
if len(preferred_materials) >= 1:
material = preferred_materials[0]
else:
UM.Logger.log("w", "The preferred material \"%s\" of machine %s doesn't exist or is not a material profile.", preferred_material_id, machine_id)
#And leave it at the default material.
container_stack.addContainer(material)
#Find a quality to use for this extruder.
quality = container_registry.getEmptyInstanceContainer()
#First add any quality. Later, overwrite with preference if the preference is valid.
qualities = container_registry.findInstanceContainers(type = "quality")
if len(qualities) >= 1:
quality = qualities[0]
preferred_quality_id = machine_definition.getMetaDataEntry("preferred_quality")
if preferred_quality_id:
preferred_quality = container_registry.findInstanceContainers(id = preferred_quality_id, type = "quality")
if len(preferred_quality) >= 1:
quality = preferred_quality[0]
else:
UM.Logger.log("w", "The preferred quality \"%s\" of machine %s doesn't exist or is not a quality profile.", preferred_quality_id, machine_id)
#And leave it at the default quality.
container_stack.addContainer(quality)
user_profile = container_registry.findInstanceContainers(id = extruder_stack_id + "_current_settings")
if user_profile: #There was already a user profile, loaded from settings.
user_profile = user_profile[0]
else:
user_profile = UM.Settings.InstanceContainer(extruder_stack_id + "_current_settings") #Add an empty user profile.
user_profile.addMetaDataEntry("type", "user")
user_profile.setDefinition(machine_definition)
container_registry.addContainer(user_profile)
container_stack.addContainer(user_profile)
container_stack.setNextStack(UM.Application.getInstance().getGlobalContainerStack())
container_registry.addContainer(container_stack)
## Generates extruders for a specific machine.
#
# \param machine_id The machine to get the extruders of.
def getMachineExtruders(self, machine_id):
if machine_id not in self._extruder_trains:
UM.Logger.log("w", "Tried to get the extruder trains for machine %s, which doesn't exist.", machine_id)
return
for name in self._extruder_trains[machine_id]:
yield self._extruder_trains[machine_id][name]
## Adds the extruders of the currently active machine.
def _addCurrentMachineExtruders(self):
global_stack = UM.Application.getInstance().getGlobalContainerStack()
if global_stack and global_stack.getBottom():
self.addMachineExtruders(global_stack.getBottom())

View file

@ -0,0 +1,133 @@
# Copyright (c) 2016 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
import cura.ExtruderManager
import UM.Qt.ListModel
## Model that holds extruders.
#
# This model is designed for use by any list of extruders, but specifically
# intended for drop-down lists of the current machine's extruders in place of
# settings.
class ExtrudersModel(UM.Qt.ListModel.ListModel):
# The ID of the container stack for the extruder.
IdRole = Qt.UserRole + 1
## Human-readable name of the extruder.
NameRole = Qt.UserRole + 2
## Colour of the material loaded in the extruder.
ColourRole = Qt.UserRole + 3
## Index of the extruder, which is also the value of the setting itself.
#
# An index of 0 indicates the first extruder, an index of 1 the second
# one, and so on. This is the value that will be saved in instance
# containers.
IndexRole = Qt.UserRole + 4
## List of colours to display if there is no material or the material has no known
# colour.
defaultColours = ["#ffc924", "#86ec21", "#22eeee", "#245bff", "#9124ff", "#ff24c8"]
## Initialises the extruders model, defining the roles and listening for
# changes in the data.
#
# \param parent Parent QtObject of this list.
def __init__(self, parent = None):
super().__init__(parent)
self.addRoleName(self.IdRole, "id")
self.addRoleName(self.NameRole, "name")
self.addRoleName(self.ColourRole, "colour")
self.addRoleName(self.IndexRole, "index")
self._add_global = False
self._active_extruder_stack = None
#Listen to changes.
manager = cura.ExtruderManager.ExtruderManager.getInstance()
manager.extrudersChanged.connect(self._updateExtruders) #When the list of extruders changes in general.
UM.Application.getInstance().globalContainerStackChanged.connect(self._updateExtruders) #When the current machine changes.
self._updateExtruders()
manager.activeExtruderChanged.connect(self._onActiveExtruderChanged)
self._onActiveExtruderChanged()
def setAddGlobal(self, add):
if add != self._add_global:
self._add_global = add
self._updateExtruders()
self.addGlobalChanged.emit()
addGlobalChanged = pyqtSignal()
@pyqtProperty(bool, fset = setAddGlobal, notify = addGlobalChanged)
def addGlobal(self):
return self._add_global
def _onActiveExtruderChanged(self):
manager = cura.ExtruderManager.ExtruderManager.getInstance()
active_extruder_stack = manager.getActiveExtruderStack()
if self._active_extruder_stack != active_extruder_stack:
if self._active_extruder_stack:
self._active_extruder_stack.containersChanged.disconnect(self._onExtruderStackContainersChanged)
if active_extruder_stack:
# Update the model when the material container is changed
active_extruder_stack.containersChanged.connect(self._onExtruderStackContainersChanged)
self._active_extruder_stack = active_extruder_stack
def _onExtruderStackContainersChanged(self, container):
# The ExtrudersModel needs to be updated when the material-name or -color changes, because the user identifies extruders by material-name
if container.getMetaDataEntry("type") == "material":
self._updateExtruders()
modelChanged = pyqtSignal()
## Update the list of extruders.
#
# This should be called whenever the list of extruders changes.
def _updateExtruders(self):
self.clear()
manager = cura.ExtruderManager.ExtruderManager.getInstance()
global_container_stack = UM.Application.getInstance().getGlobalContainerStack()
if not global_container_stack:
return #There is no machine to get the extruders of.
if self._add_global:
material = global_container_stack.findContainer({ "type": "material" })
colour = material.getMetaDataEntry("color_code", default = self.defaultColours[0]) if material else self.defaultColours[0]
item = {
"id": global_container_stack.getId(),
"name": "Global",
"colour": colour,
"index": -1
}
self.appendItem(item)
for extruder in manager.getMachineExtruders(global_container_stack.getBottom().getId()):
extruder_name = extruder.getName()
material = extruder.findContainer({ "type": "material" })
if material:
extruder_name = "%s (%s)" % (material.getName(), extruder_name)
position = extruder.getBottom().getMetaDataEntry("position", default = "0") #Position in the definition.
try:
position = int(position)
except ValueError: #Not a proper int.
position = -1
default_colour = self.defaultColours[position] if position >= 0 and position < len(self.defaultColours) else defaultColours[0]
colour = material.getMetaDataEntry("color_code", default = default_colour) if material else default_colour
item = { #Construct an item with only the relevant information.
"id": extruder.getId(),
"name": extruder_name,
"colour": colour,
"index": position
}
self.appendItem(item)
self.sort(lambda item: item["index"])
self.modelChanged.emit()

View file

@ -0,0 +1,81 @@
# Copyright (c) 2016 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
import copy
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
from UM.Signal import Signal, signalemitter
from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.ContainerRegistry import ContainerRegistry
import UM.Logger
from UM.Application import Application
## A decorator that adds a container stack to a Node. This stack should be queried for all settings regarding
# the linked node. The Stack in question will refer to the global stack (so that settings that are not defined by
# this stack still resolve.
@signalemitter
class SettingOverrideDecorator(SceneNodeDecorator):
## Event indicating that the user selected a different extruder.
activeExtruderChanged = Signal()
def __init__(self):
super().__init__()
self._stack = ContainerStack(stack_id = id(self))
self._stack.setDirty(False) # This stack does not need to be saved.
self._instance = InstanceContainer(container_id = "SettingOverrideInstanceContainer")
self._stack.addContainer(self._instance)
self._extruder_stack = None #Stack upon which our stack is based.
self._stack.propertyChanged.connect(self._onSettingChanged)
ContainerRegistry.getInstance().addContainer(self._stack)
Application.getInstance().globalContainerStackChanged.connect(self._updateNextStack)
self.activeExtruderChanged.connect(self._updateNextStack)
self._updateNextStack()
def __deepcopy__(self, memo):
## Create a fresh decorator object
deep_copy = SettingOverrideDecorator()
## Copy the instance
deep_copy._instance = copy.deepcopy(self._instance, memo)
## Set the copied instance as the first (and only) instance container of the stack.
deep_copy._stack.replaceContainer(0, deep_copy._instance)
return deep_copy
## Gets the currently active extruder to print this object with.
#
# \return An extruder's container stack.
def getActiveExtruder(self):
return self._extruder_stack
def _onSettingChanged(self, instance, property_name): # Reminder: 'property' is a built-in function
if property_name == "value": # Only reslice if the value has changed.
Application.getInstance().getBackend().forceSlice()
## Makes sure that the stack upon which the container stack is placed is
# kept up to date.
def _updateNextStack(self):
if self._extruder_stack:
extruder_stack = ContainerRegistry.getInstance().findContainerStacks(id = self._extruder_stack)
if extruder_stack:
old_extruder_stack_id = self._stack.getNextStack().getId()
self._stack.setNextStack(extruder_stack[0])
if self._stack.getNextStack().getId() != old_extruder_stack_id: #Only reslice if the extruder changed.
Application.getInstance().getBackend().forceSlice()
else:
UM.Logger.log("e", "Extruder stack %s below per-object settings does not exist.", self._extruder_stack)
else:
self._stack.setNextStack(Application.getInstance().getGlobalContainerStack())
## Changes the extruder with which to print this node.
#
# \param extruder_stack_id The new extruder stack to print with.
def setActiveExtruder(self, extruder_stack_id):
self._extruder_stack = extruder_stack_id
self.activeExtruderChanged.emit()
def getStack(self):
return self._stack

View file

@ -1,2 +1,11 @@
# Copyright (c) 2016 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from .MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler
from .ContainerManager import ContainerManager
from .ContainerSettingsModel import ContainerSettingsModel
from .CuraContainerRegistry import CuraContainerRegistry
from .ExtruderManager import ExtruderManager
from .ExtrudersModel import ExtrudersModel
from .MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler
from .SettingOverrideDecorator import SettingOverrideDecorator