diff --git a/cura/ConvexHullDecorator.py b/cura/ConvexHullDecorator.py index 6639d12dc7..39cdc36232 100644 --- a/cura/ConvexHullDecorator.py +++ b/cura/ConvexHullDecorator.py @@ -101,7 +101,7 @@ class ConvexHullDecorator(SceneNodeDecorator): if key == "print_sequence" and property_name == "value": self._onChanged() - def _onChanged(self): + def _onChanged(self, *args): if self._convex_hull_job: self._convex_hull_job.cancel() self.setConvexHull(None) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 3e3ab28010..39c3ed924f 100644 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -18,6 +18,7 @@ from UM.JobQueue import JobQueue from UM.SaveFile import SaveFile from UM.Scene.Selection import Selection from UM.Scene.GroupDecorator import GroupDecorator +import UM.Settings.Validator from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation @@ -30,7 +31,7 @@ from UM.Settings.ContainerRegistry import ContainerRegistry from UM.i18n import i18nCatalog -from . import ExtruderManager +from . import ExtrudersModel from . import PlatformPhysics from . import BuildVolume from . import CameraAnimation @@ -89,6 +90,7 @@ class CuraApplication(QtApplication): # Need to do this before ContainerRegistry tries to load the machines SettingDefinition.addSupportedProperty("global_only", DefinitionPropertyType.Function, default = False) + SettingDefinition.addSettingType("extruder", int, str, UM.Settings.Validator) super().__init__(name = "cura", version = CuraVersion) @@ -327,8 +329,6 @@ class CuraApplication(QtApplication): qmlRegisterSingletonType(MachineManagerModel.MachineManagerModel, "Cura", 1, 0, "MachineManager", MachineManagerModel.createMachineManagerModel) - self._extruder_manager = ExtruderManager.ExtruderManager() - self.setMainQml(Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml")) self._qml_import_paths.append(Resources.getPath(self.ResourceTypes.QmlFiles)) self.initializeEngine() @@ -368,6 +368,8 @@ class CuraApplication(QtApplication): qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type") + qmlRegisterType(ExtrudersModel.ExtrudersModel, "Cura", 1, 0, "ExtrudersModel") + qmlRegisterSingletonType(QUrl.fromLocalFile(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml")), "Cura", 1, 0, "Actions") for path in Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.QmlFiles): diff --git a/cura/Extruder.py b/cura/Extruder.py index 5be05c713c..4bbe24183d 100644 --- a/cura/Extruder.py +++ b/cura/Extruder.py @@ -25,8 +25,8 @@ class Extruder: self._nozzles += container_registry.findInstanceContainers(type = "nozzle", definitions = self._definition.getId()) #Create a container stack for this extruder. - name = self._uniqueName(self._definition.getId()) - self._container_stack = UM.Settings.ContainerStack(name) + self._name = self._uniqueName(self._definition.getId()) + self._container_stack = UM.Settings.ContainerStack(self._name) self._container_stack.addMetaDataEntry("type", "extruder_train") self._container_stack.addContainer(self._definition) @@ -73,15 +73,38 @@ class Extruder: self._container_stack.addContainer(self._quality) #Add an empty user profile. - self._user_profile = UM.Settings.InstanceContainer(name + "_current_settings") + self._user_profile = UM.Settings.InstanceContainer(self._name + "_current_settings") self._user_profile.addMetaDataEntry("type", "user") self._container_stack.addContainer(self._user_profile) self._container_stack.setNextStack(UM.Application.getInstance().getGlobalContainerStack()) - nozzle_changed = UM.Signal.Signal() - material_changed = UM.Signal.Signal() - quality_changed = UM.Signal.Signal() + definition_changed = UM.Signal() + material_changed = UM.Signal() + name_changed = UM.Signal() + nozzle_changed = UM.Signal() + quality_changed = UM.Signal() + + ## Gets the definition container of this extruder. + # + # \return The definition container of this extruder. + @property + def definition(self): + return self._definition + + ## Changes the definition container of this extruder. + # + # \param value The new definition for this extruder. + @definition.setter + def definition(self, value): + try: + position = self._container_stack.index(self._definition) + except ValueError: #Definition is not in the list. Big trouble! + UM.Logger.log("e", "I've lost my old extruder definition, so I can't find where to insert the new definition.") + return + self._container_stack.replaceContainer(position, value) + self._definition = value + self.definition_changed.emit() ## Gets the currently active material on this extruder. # @@ -104,6 +127,22 @@ class Extruder: self._material = value self.material_changed.emit() + ## Gets the name of this extruder. + # + # \return The name of this extruder. + @property + def name(self): + return self._name + + ## Changes the name of this extruder. + # + # \param value The new name for this extruder. + @name.setter + def name(self, value): + self._name = value + self._container_stack.setName(value) #Also update in container stack, being defensive. + self.name_changed.emit() + ## Gets the currently active nozzle on this extruder. # # \return The currently active nozzle on this extruder. diff --git a/cura/ExtruderManager.py b/cura/ExtruderManager.py index 71b636ad6f..bfce380a70 100644 --- a/cura/ExtruderManager.py +++ b/cura/ExtruderManager.py @@ -1,13 +1,11 @@ # Copyright (c) 2016 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. -import re - from cura.Extruder import Extruder #The individual extruders managed by this manager. -from UM.Application import Application #To get the global container stack to find the current machine. -from UM.Logger import Logger -from UM.Settings.ContainerStack import ContainerStack #To create container stacks for each extruder. -from UM.Settings.ContainerRegistry import ContainerRegistry #Finding containers by ID. +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. +import UM.Signal #To notify other components of changes in the extruders. ## Class that handles the current extruder stack. @@ -16,19 +14,42 @@ from UM.Settings.ContainerRegistry import ContainerRegistry #Finding containers # and makes sure that whenever the machine is swapped, this list is kept up to # date. It also contains and updates the setting stacks for the extruders. class ExtruderManager: + ## The singleton instance of this manager. + __instance = None + + ## Signal to notify other components when the list of extruders changes. + extrudersChanged = UM.Signal() + ## Registers listeners and such to listen to changes to the extruders. def __init__(self): self._extruders = [] #Extruders for the current machine. self._global_container_stack = None + self._next_item = 0 #For when you use this class as iterator. - Application.getInstance().globalContainerStackChanged.connect(self._reconnectExtruderReload) #When the current machine changes, we need to reload all extruders belonging to the new machine. + UM.Application.getInstance().globalContainerStackChanged.connect(self._reconnectExtruderReload) #When the current machine changes, we need to reload all extruders belonging to the new machine. + + ## Gets an instance of this extruder manager. + # + # If an instance was already created, the old instance is returned. This + # implements the singleton pattern. + @classmethod + def getInstance(cls): + if not cls.__instance: + cls.__instance = ExtruderManager() + return cls.__instance + + ## Creates an iterator over the extruders in this manager. + # + # \return An iterator over the extruders in this manager. + def __iter__(self): + return iter(self._extruders) ## When the global container stack changes, this reconnects to the new # signal for containers changing. def _reconnectExtruderReload(self): if self._global_container_stack: self._global_container_stack.containersChanged.disconnect(self._reloadExtruders) #Disconnect from the old global container stack. - self._global_container_stack = Application.getInstance().getGlobalContainerStack() + self._global_container_stack = UM.Application.getInstance().getGlobalContainerStack() self._global_container_stack.containersChanged.connect(self._reloadExtruders) #When the current machine changes, we need to reload all extruders belonging to the new machine. ## (Re)loads all extruders of the currently active machine. @@ -36,18 +57,20 @@ class ExtruderManager: # This looks at the global container stack to see which machine is active. # Then it loads the extruders for that machine and loads each of them in a # list of extruders. - def _reloadExtruders(self): + def _reloadExtruders(self, *args): self._extruders = [] if not self._global_container_stack: #No machine has been added yet. + self.extrudersChanged.emit() #Yes, we just cleared the _extruders list! return #Then leave them empty! #Get the extruder definitions belonging to the current machine. machine = self._global_container_stack.getBottom() - extruder_train_ids = machine.getMetaData("machine_extruder_trains") + extruder_train_ids = machine.getMetaDataEntry("machine_extruder_trains") for extruder_train_id in extruder_train_ids: - extruder_definitions = ContainerRegistry.getInstance().findDefinitionContainers(id = extruder_train_id) #Should be only 1 definition if IDs are unique, but add the whole list anyway. + extruder_definitions = UM.Settings.ContainerRegistry.getInstance().findDefinitionContainers(id = extruder_train_id) #Should be only 1 definition if IDs are unique, but add the whole list anyway. if not extruder_definitions: #Empty list or error. - Logger.log("w", "Machine definition %s refers to an extruder train \"%s\", but no such extruder was found.", machine.getId(), extruder_train_id) + UM.Logger.log("w", "Machine definition %s refers to an extruder train \"%s\", but no such extruder was found.", machine.getId(), extruder_train_id) continue for extruder_definition in extruder_definitions: - self._extruders.append(Extruder(extruder_definition)) \ No newline at end of file + self._extruders.append(Extruder(extruder_definition)) + self.extrudersChanged.emit() \ No newline at end of file diff --git a/cura/ExtrudersModel.py b/cura/ExtrudersModel.py new file mode 100644 index 0000000000..eb63bc4257 --- /dev/null +++ b/cura/ExtrudersModel.py @@ -0,0 +1,56 @@ +# Copyright (c) 2016 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + +from PyQt5.QtCore import Qt + +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 extruders in place of settings. +class ExtrudersModel(UM.Qt.ListModel.ListModel): + ## Human-readable name of the extruder. + NameRole = Qt.UserRole + 1 + + ## Colour of the material loaded in the extruder. + ColourRole = Qt.UserRole + 2 + + ## 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 + 3 + + ## 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.NameRole, "name") + self.addRoleName(self.ColourRole, "colour") + self.addRoleName(self.IndexRole, "index") + + #Listen to changes. + manager = cura.ExtruderManager.ExtruderManager.getInstance() + manager.extrudersChanged.connect(self._updateExtruders) + self._updateExtruders() + + ## 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() + for index, extruder in enumerate(manager): + item = { #Construct an item with only the relevant information. + "name": extruder.name, + "colour": extruder.material.getMetaDataEntry("color_code", default = "#FFFF00"), + "index": index + } + self.appendItem(item) + self.sort(lambda item: item["index"]) \ No newline at end of file diff --git a/cura/SettingOverrideDecorator.py b/cura/SettingOverrideDecorator.py index 3e8815ec67..e212d93dac 100644 --- a/cura/SettingOverrideDecorator.py +++ b/cura/SettingOverrideDecorator.py @@ -18,11 +18,17 @@ class SettingOverrideDecorator(SceneNodeDecorator): self._instance = InstanceContainer(container_id = "SettingOverrideInstanceContainer") self._stack.addContainer(self._instance) + self._stack.propertyChanged.connect(self._onSettingChanged) + ContainerRegistry.getInstance().addContainer(self._stack) Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerStackChanged) self._onGlobalContainerStackChanged() + def _onSettingChanged(self, instance, property): + if property == "value": # Only reslice if the value has changed. + Application.getInstance().getBackend().forceSlice() + def _onGlobalContainerStackChanged(self): ## Ensure that the next stack is always the global stack. self._stack.setNextStack(Application.getInstance().getGlobalContainerStack()) diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index cf008e7b6f..0851261877 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -3,7 +3,6 @@ import numpy from string import Formatter -import traceback from enum import IntEnum from UM.Job import Job @@ -59,7 +58,7 @@ class StartSliceJob(Job): self.setResult(StartJobResult.Error) return - #Don't slice if there is a setting with an error value. + # Don't slice if there is a setting with an error value. for key in stack.getAllKeys(): validation_state = stack.getProperty(key, "validationState") if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError): @@ -69,6 +68,22 @@ class StartSliceJob(Job): Job.yieldThread() + # Don't slice if there is a per object setting with an error value. + for node in DepthFirstIterator(self._scene.getRoot()): + if type(node) is not SceneNode or not node.isSelectable(): + continue + + node_stack = node.callDecoration("getStack") + if node_stack: + for key in node_stack.getAllKeys(): + validation_state = node_stack.getProperty(key, "validationState") + if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError): + Logger.log("w", "Per object setting %s is not valid, but %s. Aborting slicing.", key, validation_state) + self.setResult(StartJobResult.SettingError) + return + + Job.yieldThread() + with self._scene.getSceneLock(): # Remove old layer data. for node in DepthFirstIterator(self._scene.getRoot()): diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingVisibilityHandler.py b/plugins/PerObjectSettingsTool/PerObjectSettingVisibilityHandler.py index 9ef2515bed..381d45b1c2 100644 --- a/plugins/PerObjectSettingsTool/PerObjectSettingVisibilityHandler.py +++ b/plugins/PerObjectSettingsTool/PerObjectSettingVisibilityHandler.py @@ -5,7 +5,8 @@ from UM.Logger import Logger from cura.SettingOverrideDecorator import SettingOverrideDecorator - +## The per object setting visibility handler ensures that only setting defintions that have a matching instance Container +# are returned as visible. class PerObjectSettingVisibilityHandler(QObject): def __init__(self, parent = None, *args, **kwargs): super().__init__(parent = parent, *args, **kwargs) diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsModel.py b/plugins/PerObjectSettingsTool/PerObjectSettingsModel.py deleted file mode 100644 index 74a1fbed9a..0000000000 --- a/plugins/PerObjectSettingsTool/PerObjectSettingsModel.py +++ /dev/null @@ -1,75 +0,0 @@ -# Copyright (c) 2015 Ultimaker B.V. -# Uranium is released under the terms of the AGPLv3 or higher. - -from PyQt5.QtCore import Qt, pyqtSlot, QUrl - -from UM.Application import Application -from UM.Qt.ListModel import ListModel -from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator -from UM.Scene.SceneNode import SceneNode -#from UM.Settings.SettingOverrideDecorator import SettingOverrideDecorator -#from UM.Settings.ProfileOverrideDecorator import ProfileOverrideDecorator - -from . import SettingOverrideModel - -class PerObjectSettingsModel(ListModel): - IdRole = Qt.UserRole + 1 # ID of the node - - def __init__(self, parent = None): - super().__init__(parent) - self._scene = Application.getInstance().getController().getScene() - self._root = self._scene.getRoot() - self.addRoleName(self.IdRole,"id") - - self._updateModel() - - @pyqtSlot("quint64", str) - def setObjectProfile(self, object_id, profile_name): - self.setProperty(self.find("id", object_id), "profile", profile_name) - - profile = None - '''if profile_name != "global": - profile = Application.getInstance().getMachineManager().findProfile(profile_name) - - node = self._scene.findObject(object_id) - if profile: - if not node.getDecorator(ProfileOverrideDecorator): - node.addDecorator(ProfileOverrideDecorator()) - node.callDecoration("setProfile", profile) - else: - if node.getDecorator(ProfileOverrideDecorator): - node.removeDecorator(ProfileOverrideDecorator)''' - - @pyqtSlot("quint64", str) - def addOverride(self, object_id, key): - machine = Application.getInstance().getMachineManager().getActiveMachineInstance() - if not machine: - return - - node = self._scene.findObject(object_id) - #if not node.getDecorator(SettingOverrideDecorator): - # node.addDecorator(SettingOverrideDecorator()) - - node.callDecoration("addSetting", key) - - @pyqtSlot("quint64", str) - def removerOverride(self, object_id, key): - node = self._scene.findObject(object_id) - node.callDecoration("removeSetting", key) - - #if len(node.callDecoration("getAllSettings")) == 0: - # node.removeDecorator(SettingOverrideDecorator) - - def _updateModel(self): - self.clear() - - for node in BreadthFirstIterator(self._root): - if type(node) is not SceneNode or not node.isSelectable(): - continue - - node_stack = node.callDecoration("getStack") - - if not node_stack: - self.appendItem({ - "id": id(node) - }) diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml index f873ec9a79..d707fe9810 100644 --- a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml +++ b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml @@ -45,6 +45,7 @@ Item { { Loader { + id: settingLoader width: UM.Theme.getSize("setting").width; height: UM.Theme.getSize("section").height; @@ -57,6 +58,12 @@ Item { //causing nasty issues when selecting different options. So disable asynchronous loading of enum type completely. asynchronous: model.type != "enum" + onLoaded: { + settingLoader.item.showRevertButton = false + settingLoader.item.showInheritButton = false + settingLoader.item.doDepthIdentation = false + } + source: { switch(model.type) // TODO: This needs to be fixed properly. Got frustrated with it not working, so this is the patch job! diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py b/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py index c74800e83d..6ae44c2671 100644 --- a/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py +++ b/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py @@ -6,14 +6,15 @@ from UM.Scene.Selection import Selection from UM.Application import Application from UM.Preferences import Preferences -from . import PerObjectSettingsModel +## This tool allows the user to add & change settings per node in the scene. +# The settings per object are kept in a ContainerStack, which is linked to a node by decorator. class PerObjectSettingsTool(Tool): def __init__(self): super().__init__() self._model = None - self.setExposedProperties("SelectedObjectId","ContainerID") + self.setExposedProperties("SelectedObjectId", "ContainerID") Preferences.getInstance().preferenceChanged.connect(self._onPreferenceChanged) Selection.selectionChanged.connect(self.propertyChanged) @@ -33,21 +34,13 @@ class PerObjectSettingsTool(Tool): return selected_object_id def getContainerID(self): + selected_object = Selection.getSelectedObject(0) + if selected_object.getParent().callDecoration("isGroup"): + selected_object = selected_object.getParent() try: - selected_object = Selection.getSelectedObject(0) - if selected_object.getParent().callDecoration("isGroup"): - selected_object = selected_object.getParent() - try: - return selected_object.callDecoration("getStack").getId() - except: - print(":(") - return - except: - print(":((") - return - - def setContainerID(self, value): - pass + return selected_object.callDecoration("getStack").getId() + except AttributeError: + return "" def _onPreferenceChanged(self, preference): if preference == "cura/active_mode": diff --git a/plugins/PerObjectSettingsTool/SettingOverrideModel.py b/plugins/PerObjectSettingsTool/SettingOverrideModel.py deleted file mode 100644 index d4bebfdfee..0000000000 --- a/plugins/PerObjectSettingsTool/SettingOverrideModel.py +++ /dev/null @@ -1,138 +0,0 @@ -# Copyright (c) 2015 Ultimaker B.V. -# Uranium is released under the terms of the AGPLv3 or higher. - -from PyQt5.QtCore import Qt, pyqtSlot, QUrl - -from UM.Application import Application -from UM.Qt.ListModel import ListModel -#from UM.Settings.SettingOverrideDecorator import SettingOverrideDecorator - -class SettingOverrideModel(ListModel): - KeyRole = Qt.UserRole + 1 - LabelRole = Qt.UserRole + 2 - DescriptionRole = Qt.UserRole + 3 - ValueRole = Qt.UserRole + 4 - TypeRole = Qt.UserRole + 5 - UnitRole = Qt.UserRole + 6 - ValidRole = Qt.UserRole + 7 - OptionsRole = Qt.UserRole + 8 - WarningDescriptionRole = Qt.UserRole + 9 - ErrorDescriptionRole = Qt.UserRole + 10 - GlobalOnlyRole = Qt.UserRole + 11 - - def __init__(self, node, parent = None): - super().__init__(parent) - - self._ignore_setting_change = None - - self._node = node - self._node.decoratorsChanged.connect(self._onDecoratorsChanged) - self._onDecoratorsChanged(None) - - #self._activeProfile = Application.getInstance().getMachineManager().getWorkingProfile() #To be able to get notified when a setting changes. - #self._activeProfile.settingValueChanged.connect(self._onProfileSettingValueChanged) - #Application.getInstance().getMachineManager().activeProfileChanged.connect(self._onProfileChanged) - - self.addRoleName(self.KeyRole, "key") - self.addRoleName(self.LabelRole, "label") - self.addRoleName(self.DescriptionRole, "description") - self.addRoleName(self.ValueRole,"value") - self.addRoleName(self.TypeRole, "type") - self.addRoleName(self.UnitRole, "unit") - self.addRoleName(self.ValidRole, "valid") - self.addRoleName(self.OptionsRole, "options") - self.addRoleName(self.WarningDescriptionRole, "warning_description") - self.addRoleName(self.ErrorDescriptionRole, "error_description") - self.addRoleName(self.GlobalOnlyRole, "global_only") - - @pyqtSlot(str, "QVariant") - def setSettingValue(self, key, value): - if not self._decorator: - return - - self._decorator.setSettingValue(key, value) - - def _onDecoratorsChanged(self, node): - return - '''if not self._node.getDecorator(SettingOverrideDecorator): - self.clear() - return - - self._decorator = self._node.getDecorator(SettingOverrideDecorator) - self._decorator.settingAdded.connect(self._onSettingsChanged) - self._decorator.settingRemoved.connect(self._onSettingsChanged) - self._decorator.settingValueChanged.connect(self._onSettingValueChanged) - self._onSettingsChanged()''' - - def _createOptionsModel(self, options): - if not options: - return None - - model = ListModel() - model.addRoleName(Qt.UserRole + 1, "value") - model.addRoleName(Qt.UserRole + 2, "name") - for value, name in options.items(): - model.appendItem({"value": str(value), "name": str(name)}) - return model - - ## Updates the active profile in this model if the active profile is - # changed. - # - # This links the settingValueChanged of the new profile to this model's - # _onSettingValueChanged function, so that it properly listens to those - # events again. - def _onProfileChanged(self): - if self._activeProfile: #Unlink from the old profile. - self._activeProfile.settingValueChanged.disconnect(self._onProfileSettingValueChanged) - old_profile = self._activeProfile - self._activeProfile = Application.getInstance().getMachineManager().getWorkingProfile() - self._activeProfile.settingValueChanged.connect(self._onProfileSettingValueChanged) #Re-link to the new profile. - for setting_name in old_profile.getChangedSettings().keys(): #Update all changed settings in the old and new profiles. - self._onProfileSettingValueChanged(setting_name) - for setting_name in self._activeProfile.getChangedSettings().keys(): - self._onProfileSettingValueChanged(setting_name) - - ## Updates the global_only property of a setting once a setting value - # changes. - # - # This method should only get called on settings that are dependent on the - # changed setting. - # - # \param setting_name The setting that needs to be updated. - def _onProfileSettingValueChanged(self, setting_name): - index = self.find("key", setting_name) - if index != -1: - self.setProperty(index, "global_only", Application.getInstance().getMachineManager().getActiveMachineInstance().getMachineDefinition().getSetting(setting_name).getGlobalOnly()) - - def _onSettingsChanged(self): - self.clear() - - items = [] - for key, setting in self._decorator.getAllSettings().items(): - value = self._decorator.getSettingValue(key) - items.append({ - "key": key, - "label": setting.getLabel(), - "description": setting.getDescription(), - "value": str(value), - "type": setting.getType(), - "unit": setting.getUnit(), - "valid": setting.validate(value), - "options": self._createOptionsModel(setting.getOptions()), - "warning_description": setting.getWarningDescription(), - "error_description": setting.getErrorDescription(), - "global_only": setting.getGlobalOnly() - }) - - items.sort(key = lambda i: i["key"]) - - for item in items: - self.appendItem(item) - - def _onSettingValueChanged(self, setting): - index = self.find("key", setting.getKey()) - value = self._decorator.getSettingValue(setting.getKey()) - if index != -1: - self.setProperty(index, "value", str(value)) - self.setProperty(index, "valid", setting.validate(value)) - self.setProperty(index, "global_only", setting.getGlobalOnly()) \ No newline at end of file diff --git a/resources/definitions/fdmextruder.def.json b/resources/definitions/fdmextruder.def.json index f755d72c4a..69797385a1 100644 --- a/resources/definitions/fdmextruder.def.json +++ b/resources/definitions/fdmextruder.def.json @@ -22,7 +22,7 @@ { "label": "Extruder", "description": "The extruder train used for printing. This is used in multi-extrusion.", - "type": "int", + "type": "extruder", "default_value": 0, "minimum_value": "0" }, diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 48952c2c99..ec27abd9f5 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -2125,7 +2125,7 @@ { "label": "Platform Adhesion Extruder", "description": "The extruder train to use for printing the skirt/brim/raft. This is used in multi-extrusion.", - "type": "int", + "type": "extruder", "default_value": 0, "minimum_value": "0", "maximum_value": "machine_extruder_count - 1", @@ -2135,7 +2135,7 @@ { "label": "Support Extruder", "description": "The extruder train to use for printing the support. This is used in multi-extrusion.", - "type": "int", + "type": "extruder", "default_value": 0, "minimum_value": "0", "maximum_value": "machine_extruder_count - 1", @@ -2145,7 +2145,7 @@ { "label": "Support Infill Extruder", "description": "The extruder train to use for printing the infill of the support. This is used in multi-extrusion.", - "type": "int", + "type": "extruder", "default_value": 0, "value": "support_extruder_nr", "minimum_value": "0", @@ -2156,7 +2156,7 @@ { "label": "First Layer Support Extruder", "description": "The extruder train to use for printing the first layer of support infill. This is used in multi-extrusion.", - "type": "int", + "type": "extruder", "default_value": 0, "value": "support_extruder_nr", "minimum_value": "0", @@ -2167,7 +2167,7 @@ { "label": "Support Roof Extruder", "description": "The extruder train to use for printing the roof of the support. This is used in multi-extrusion.", - "type": "int", + "type": "extruder", "default_value": 0, "value": "support_extruder_nr", "minimum_value": "0", diff --git a/resources/qml/Settings/SettingExtruder.qml b/resources/qml/Settings/SettingExtruder.qml new file mode 100644 index 0000000000..86ab728fc1 --- /dev/null +++ b/resources/qml/Settings/SettingExtruder.qml @@ -0,0 +1,112 @@ +// Copyright (c) 2016 Ultimaker B.V. +// Uranium is released under the terms of the AGPLv3 or higher. + +import QtQuick 2.1 +import QtQuick.Controls 1.1 +import QtQuick.Controls.Styles 1.1 + +import UM 1.1 as UM +import Cura 1.0 as Cura + +SettingItem +{ + id: base + + contents: ComboBox + { + id: control + + model: Cura.ExtrudersModel { + id: extruders_model + } + textRole: "name"; + + anchors.fill: parent + + MouseArea + { + anchors.fill: parent; + acceptedButtons: Qt.NoButton; + onWheel: wheel.accepted = true; + } + + style: ComboBoxStyle + { + background: Rectangle + { + color: + { + if (!enabled) + { + return UM.Theme.getColor("setting_control_disabled") + } + if(control.hovered || base.activeFocus) + { + return UM.Theme.getColor("setting_control_highlight") + } + else + { + return UM.Theme.getColor("setting_control") + } + } + border.width: UM.Theme.getSize("default_lining").width; + border.color: !enabled ? UM.Theme.getColor("setting_control_disabled_border") : control.hovered ? UM.Theme.getColor("setting_control_border_highlight") : UM.Theme.getColor("setting_control_border"); + } + label: Item + { + Label + { + anchors.left: parent.left; + anchors.leftMargin: UM.Theme.getSize("default_lining").width + anchors.right: downArrow.left; + anchors.rightMargin: UM.Theme.getSize("default_lining").width; + anchors.verticalCenter: parent.verticalCenter; + + text: control.currentText; + font: UM.Theme.getFont("default"); + color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text"); + + elide: Text.ElideRight; + verticalAlignment: Text.AlignVCenter; + } + + UM.RecolorImage + { + id: downArrow + anchors.right: parent.right; + anchors.rightMargin: UM.Theme.getSize("default_lining").width * 2; + anchors.verticalCenter: parent.verticalCenter; + + source: UM.Theme.getIcon("arrow_bottom") + width: UM.Theme.getSize("standard_arrow").width + height: UM.Theme.getSize("standard_arrow").height + sourceSize.width: width + 5 + sourceSize.height: width + 5 + + color: UM.Theme.getColor("setting_control_text"); + + } + } + } + + onActivated: provider.setPropertyValue("value", extruders_model.getItem(index).index) + onModelChanged: updateCurrentIndex(); + + Connections + { + target: provider + onPropertiesChanged: control.updateCurrentIndex() + } + + function updateCurrentIndex() { + for(var i = 0; i < extruders_model.rowCount(); ++i) { + if(extruders_model.getItem(i).index == provider.properties.value) { + currentIndex = i; + return; + } + } + + currentIndex = -1; + } + } +} diff --git a/resources/qml/Settings/SettingItem.qml b/resources/qml/Settings/SettingItem.qml index aa4ef5be85..f476349c42 100644 --- a/resources/qml/Settings/SettingItem.qml +++ b/resources/qml/Settings/SettingItem.qml @@ -18,6 +18,10 @@ Item { property alias contents: controlContainer.children; property alias hovered: mouse.containsMouse + property var showRevertButton: true + property var showInheritButton: true + property var doDepthIdentation: true + signal contextMenuRequested() signal showTooltip(string text); signal hideTooltip(); @@ -93,7 +97,7 @@ Item { id: label; anchors.left: parent.left; - anchors.leftMargin: (UM.Theme.getSize("section_icon_column").width + 5) + ((definition.depth - 1) * UM.Theme.getSize("setting_control_depth_margin").width) + anchors.leftMargin: doDepthIdentation ? (UM.Theme.getSize("section_icon_column").width + 5) + ((definition.depth - 1) * UM.Theme.getSize("setting_control_depth_margin").width) : 0 anchors.right: settingControls.left; anchors.verticalCenter: parent.verticalCenter @@ -124,7 +128,7 @@ Item { { id: revertButton; - visible: propertyProvider.stackLevel == 0 + visible: propertyProvider.stackLevel == 0 && base.showRevertButton height: parent.height; width: height; @@ -151,7 +155,7 @@ Item { id: inheritButton; //visible: has_profile_value && base.has_inherit_function && base.is_enabled - visible: propertyProvider.properties.state == "InstanceState.User" && propertyProvider.stackLevel > 0 + visible: propertyProvider.properties.state == "InstanceState.User" && propertyProvider.stackLevel > 0 && base.showInheritButton height: parent.height; width: height; diff --git a/resources/qml/Settings/SettingView.qml b/resources/qml/Settings/SettingView.qml index 9dabbaee98..1555149d22 100644 --- a/resources/qml/Settings/SettingView.qml +++ b/resources/qml/Settings/SettingView.qml @@ -48,7 +48,7 @@ ScrollView //Qt5.4.2 and earlier has a bug where this causes a crash: https://bugreports.qt.io/browse/QTBUG-35989 //In addition, while it works for 5.5 and higher, the ordering of the actual combo box drop down changes, - //causing nasty issues when selecting differnt options. So disable asynchronous loading of enum type completely. + //causing nasty issues when selecting different options. So disable asynchronous loading of enum type completely. asynchronous: model.type != "enum" active: model.type != undefined @@ -62,6 +62,8 @@ ScrollView return "SettingTextField.qml" case "enum": return "SettingComboBox.qml" + case "extruder": + return "SettingExtruder.qml" case "bool": return "SettingCheckBox.qml" case "str":