diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 7edb0bf8bd..37ba6cf7ad 100644 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -351,6 +351,7 @@ class CuraApplication(QtApplication): self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Loading interface...")) + ExtruderManager.ExtruderManager.getInstance() #Initialise extruder so as to listen to global container stack changes before the first global container stack is set. qmlRegisterSingletonType(MachineManagerModel.MachineManagerModel, "Cura", 1, 0, "MachineManager", MachineManagerModel.createMachineManagerModel) diff --git a/cura/ExtruderManager.py b/cura/ExtruderManager.py index df31cbacdf..8219066eca 100644 --- a/cura/ExtruderManager.py +++ b/cura/ExtruderManager.py @@ -1,7 +1,7 @@ # 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 #For communicating data and events to Qt. +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 @@ -13,7 +13,7 @@ import UM.Settings.ContainerRegistry #Finding containers by ID. # 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() + extrudersChanged = pyqtSignal(QVariant) ## Notify when the user switches the currently active extruder. activeExtruderChanged = pyqtSignal() @@ -21,8 +21,9 @@ class ExtruderManager(QObject): ## Registers listeners and such to listen to changes to the extruders. def __init__(self, parent = None): super().__init__(parent) - self._extruder_trains = { } #Extruders for the current machine. + 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. # @@ -39,7 +40,18 @@ class ExtruderManager(QObject): 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: @@ -68,7 +80,7 @@ class ExtruderManager(QObject): 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()) + 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. name = container_registry.uniqueName(extruder_definition.getId()) #Make a name based on the ID of the definition. self.createExtruderTrain(extruder_definition, machine_definition, name, position) @@ -78,24 +90,7 @@ class ExtruderManager(QObject): for extruder_train in extruder_trains: self._extruder_trains[machine_id][extruder_train.getMetaDataEntry("position")] = extruder_train.getId() if extruder_trains: - self.extrudersChanged.emit() - - ## (Re)populates the collections of extruders by machine. - def _repopulate(self): - self._extruder_trains = { } - if not UM.Application.getInstance().getGlobalContainerStack(): #No machine has been added yet. - self.extrudersChanged.emit() #Yes, we just cleared the _extruders list! - return #Then leave them empty! - - extruder_trains = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(type = "extruder_train") - for extruder_train in extruder_trains: - machine_id = extruder_train.getMetaDataEntry("machine") - if not machine_id: - continue - if machine_id not in self._extruder_trains: - self._extruder_trains[machine_id] = { } - self._extruder_trains[machine_id][extruder_train.getMetaDataEntry("position")] = extruder_train.getId() - self.extrudersChanged.emit() + self.extrudersChanged.emit(machine_definition) def createExtruderTrain(self, extruder_definition, machine_definition, extruder_train_id, position): container_registry = UM.Settings.ContainerRegistry.getInstance() @@ -162,3 +157,23 @@ class ExtruderManager(QObject): container_stack.setNextStack(UM.Application.getInstance().getGlobalContainerStack()) container_registry.addContainer(container_stack) + + ## Generates extruders for a specific machine. + def getMachineExtruders(self, machine_definition): + container_registry = UM.Settings.ContainerRegistry.getInstance() + machine_id = machine_definition.getId() + if not machine_id 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 _,extruder_train_id in self._extruder_trains[machine_id].items(): + extruder_train = container_registry.findContainerStacks(id = extruder_train_id) + if extruder_train: + yield extruder_train[0] + else: + UM.Logger.log("w", "Machine %s refers to an extruder train with ID %s, which doesn't exist.", machine_id, extruder_train_id) + + ## 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()) \ No newline at end of file diff --git a/cura/ExtrudersModel.py b/cura/ExtrudersModel.py index eb63bc4257..60bd60abab 100644 --- a/cura/ExtrudersModel.py +++ b/cura/ExtrudersModel.py @@ -9,7 +9,8 @@ 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. +# intended for drop-down lists of the current machine's extruders in place of +# settings. class ExtrudersModel(UM.Qt.ListModel.ListModel): ## Human-readable name of the extruder. NameRole = Qt.UserRole + 1 @@ -37,7 +38,8 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): #Listen to changes. manager = cura.ExtruderManager.ExtruderManager.getInstance() - manager.extrudersChanged.connect(self._updateExtruders) + manager.extrudersChanged.connect(self._updateExtruders) #When the list of extruders changes in general. + UM.Application.globalContainerStackChanged.connect(self._updateExtruders) #When the current machine changes. self._updateExtruders() ## Update the list of extruders. @@ -46,10 +48,15 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): def _updateExtruders(self): self.clear() manager = cura.ExtruderManager.ExtruderManager.getInstance() - for index, extruder in enumerate(manager): + global_container_stack = UM.Application.getInstance().getGlobalContainerStack() + if not global_container_stack: + return #There is no machine to get the extruders of. + for index, extruder in enumerate(manager.getMachineExtruders(global_container_stack.getBottom())): + material = extruder.findContainer({ "type": "material" }) + colour = material.getMetaDataEntry("color_code", default = "#FFFF00") if material else "#FFFF00" item = { #Construct an item with only the relevant information. - "name": extruder.name, - "colour": extruder.material.getMetaDataEntry("color_code", default = "#FFFF00"), + "name": extruder.getName(), + "colour": colour, "index": index } self.appendItem(item) diff --git a/cura/MachineManagerModel.py b/cura/MachineManagerModel.py index c764296a68..e7034412f5 100644 --- a/cura/MachineManagerModel.py +++ b/cura/MachineManagerModel.py @@ -9,6 +9,7 @@ from UM.Logger import Logger import UM.Settings from UM.Settings.Validator import ValidatorState from UM.Settings.InstanceContainer import InstanceContainer +from UM.Settings.ContainerStack import ContainerStack from . import ExtruderManager from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") @@ -35,8 +36,6 @@ class MachineManagerModel(QObject): active_machine_id = Preferences.getInstance().getValue("cura/active_machine") - self._active_extruder_index = 0 - if active_machine_id != "": # An active machine was saved, so restore it. self.setActiveMachine(active_machine_id) @@ -105,12 +104,11 @@ class MachineManagerModel(QObject): Application.getInstance().setGlobalContainerStack(containers[0]) @pyqtSlot(str, str) - def addMachine(self,name, definition_id): + def addMachine(self, name, definition_id): definitions = UM.Settings.ContainerRegistry.getInstance().findDefinitionContainers(id=definition_id) if definitions: definition = definitions[0] - name = self._createUniqueName("machine", name, definition.getName()) - + name = self._createUniqueName("machine", "", name, definition.getName()) new_global_stack = UM.Settings.ContainerStack(name) new_global_stack.addMetaDataEntry("type", "machine") UM.Settings.ContainerRegistry.getInstance().addContainer(new_global_stack) @@ -139,31 +137,39 @@ class MachineManagerModel(QObject): Application.getInstance().setGlobalContainerStack(new_global_stack) - # Create a name that is not empty and unique - def _createUniqueName(self, object_type, name, fallback_name): - name = name.strip() - num_check = re.compile("(.*?)\s*#\d+$").match(name) - if(num_check): - name = num_check.group(1) - if name == "": - name = fallback_name - unique_name = name - i = 1 + ## 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 - # Check both the id and the name, because they may not be the same and it is better if they are both unique - if object_type == "machine": - while UM.Settings.ContainerRegistry.getInstance().findContainerStacks(id = unique_name, type = "machine") or \ - UM.Settings.ContainerRegistry.getInstance().findContainerStacks(name = unique_name, type = "machine"): - i += 1 - unique_name = "%s #%d" % (name, i) - else: - while UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = unique_name, type = object_type) or \ - UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(name = unique_name, type = object_type): - i += 1 - unique_name = "%s #%d" % (name, i) + 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 UM.Settings.ContainerRegistry.getInstance().findContainers(container_class, id = container_name, type = container_type) or \ + UM.Settings.ContainerRegistry.getInstance().findContainers(container_class, name = container_name, type = container_type) + ## Convenience function to check if a stack has errors. def _checkStackForErrors(self, stack): if stack is None: @@ -260,9 +266,9 @@ class MachineManagerModel(QObject): if not self._global_container_stack: return - name = self._createUniqueName("quality", self.activeQualityName, catalog.i18nc("@label", "Custom profile")) - user_settings = self._global_container_stack.getTop() new_quality_container = InstanceContainer("") + name = self._createUniqueName("quality", "", self.activeQualityName, catalog.i18nc("@label", "Custom profile")) + user_settings = self._global_container_stack.getTop() ## Copy all values new_quality_container.deserialize(user_settings.serialize()) @@ -290,7 +296,7 @@ class MachineManagerModel(QObject): return containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id=container_id) if containers: - new_name = self._createUniqueName("quality", containers[0].getName(), catalog.i18nc("@label", "Custom profile")) + new_name = self._createUniqueName("quality", "", containers[0].getName(), catalog.i18nc("@label", "Custom profile")) new_container = InstanceContainer("") @@ -310,9 +316,9 @@ class MachineManagerModel(QObject): def renameQualityContainer(self, container_id, new_name): containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = container_id, type = "quality") if containers: - new_name = self._createUniqueName("machine", new_name, catalog.i18nc("@label", "Custom profile")) + new_name = self._createUniqueName("quality", containers[0].getName(), new_name, catalog.i18nc("@label", "Custom profile")) containers[0].setName(new_name) - UM.Settings.ContainerRegistry.getInstance().containerChanged.emit(containers[0]) + self.activeQualityChanged.emit() @pyqtSlot(str) @@ -327,11 +333,11 @@ class MachineManagerModel(QObject): UM.Settings.ContainerRegistry.getInstance().removeContainer(container_id) if activate_new_container: - old_container = self._global_container_stack.findInstanceContainers({"type": "quality"}) - containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = container_type) - if containers and old_container: - container_index = self._global_container_stack.getContainerIndex(old_container) - self._global_container_stack.replaceContainer(container_index, containers[0]) + definition_id = "fdmprinter" if not self.filterQualityByMachine else self.activeDefinitionId + containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "quality", definition = definition_id) + if containers: + self.setActiveQuality(containers[0].getId()) + self.activeQualityChanged.emit() @pyqtSlot() @@ -413,7 +419,7 @@ class MachineManagerModel(QObject): def renameMachine(self, machine_id, new_name): containers = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(id = machine_id) if containers: - new_name = self._createUniqueName("machine", new_name, containers[0].getBottom().getName()) + new_name = self._createUniqueName("machine", containers[0].getName(), new_name, containers[0].getBottom().getName()) containers[0].setName(new_name) self.globalContainerChanged.emit() diff --git a/cura/SettingOverrideDecorator.py b/cura/SettingOverrideDecorator.py index 04e77b0d3f..f9878e436c 100644 --- a/cura/SettingOverrideDecorator.py +++ b/cura/SettingOverrideDecorator.py @@ -15,6 +15,7 @@ class SettingOverrideDecorator(SceneNodeDecorator): 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) @@ -28,10 +29,10 @@ class SettingOverrideDecorator(SceneNodeDecorator): def __deepcopy__(self, memo): ## Create a fresh decorator object deep_copy = SettingOverrideDecorator() - ## Copy the stack - deep_copy._stack = copy.deepcopy(self._stack, memo) - ## Ensure that the id is unique. - deep_copy._stack._id = id(deep_copy) + ## 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 def _onSettingChanged(self, instance, property): diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml index c89485517e..667f842469 100644 --- a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml +++ b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml @@ -61,7 +61,7 @@ Item { onLoaded: { settingLoader.item.showRevertButton = false settingLoader.item.showInheritButton = false - settingLoader.item.doDepthIdentation = false + settingLoader.item.doDepthIndentation = false } sourceComponent: @@ -120,7 +120,7 @@ Item { containerStackId: UM.ActiveTool.properties.getValue("ContainerID") key: model.key - watchedProperties: [ "value", "enabled", "state", "validationState" ] + watchedProperties: [ "value", "enabled", "validationState" ] storeIndex: 0 } } diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 2443e08b47..da99c36d51 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -2138,6 +2138,7 @@ "default_value": 0, "minimum_value": "0", "maximum_value": "machine_extruder_count - 1", + "enabled": "support_enable", "global_only": "True", "children": { "support_infill_extruder_nr": diff --git a/resources/qml/Preferences/ProfilesPage.qml b/resources/qml/Preferences/ProfilesPage.qml index 28248b23f4..ed9c20f65f 100644 --- a/resources/qml/Preferences/ProfilesPage.qml +++ b/resources/qml/Preferences/ProfilesPage.qml @@ -237,7 +237,7 @@ UM.ManagementPage folder: base.model.getDefaultPath() onAccepted: { - var result = base.model.exportProfile(base.currentItem.id, base.currentItem.name, fileUrl, selectedNameFilter) + var result = base.model.exportProfile(base.currentItem.id, fileUrl) if(result && result.status == "error") { messageDialog.icon = StandardIcon.Critical diff --git a/resources/qml/ProfileSetup.qml b/resources/qml/ProfileSetup.qml index c6291c8c85..48d52c539a 100644 --- a/resources/qml/ProfileSetup.qml +++ b/resources/qml/ProfileSetup.qml @@ -143,7 +143,7 @@ Item{ UM.SimpleButton { id: customisedSettings - visible: UM.ActiveProfile.hasCustomisedValues + visible: Cura.MachineManager.hasUserSettings height: parent.height * 0.6 width: parent.height * 0.6 diff --git a/resources/qml/Settings/SettingItem.qml b/resources/qml/Settings/SettingItem.qml index e4edc267ef..4fa615134b 100644 --- a/resources/qml/Settings/SettingItem.qml +++ b/resources/qml/Settings/SettingItem.qml @@ -20,11 +20,11 @@ Item { property var showRevertButton: true property var showInheritButton: true - property var doDepthIdentation: true + property var doDepthIndentation: true // Create properties to put property provider stuff in (bindings break in qt 5.5.1 otherwise) property var state: propertyProvider.properties.state - property var stackLevel: propertyProvider.stackLevel + property var stackLevel: propertyProvider.stackLevels[0] signal contextMenuRequested() signal showTooltip(string text); @@ -101,7 +101,7 @@ Item { id: label; anchors.left: parent.left; - anchors.leftMargin: doDepthIdentation ? (UM.Theme.getSize("section_icon_column").width + 5) + ((definition.depth - 1) * UM.Theme.getSize("setting_control_depth_margin").width) : 0 + anchors.leftMargin: doDepthIndentation ? (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 @@ -166,7 +166,15 @@ Item { onClicked: { focus = true; - propertyProvider.removeFromContainer(base.stackLevel) + // Get the deepest entry of this setting that we can find. TODO: This is a bit naive, in some cases + // there might be multiple profiles saying something about the same setting. There is no strategy + // how to handle this as of yet. + var last_entry = propertyProvider.stackLevels.slice(-1)[0] + // Put that entry into the "top" instance container. + // This ensures that the value in any of the deeper containers need not be removed, which is + // needed for the reset button (which deletes the top value) to correctly go back to profile + // defaults. + propertyProvider.setPropertyValue("value", propertyProvider.getPropertyValue("value", last_entry)) } backgroundColor: UM.Theme.getColor("setting_control"); diff --git a/resources/qml/Settings/SettingTextField.qml b/resources/qml/Settings/SettingTextField.qml index 2a1fb96330..bf6fc34c93 100644 --- a/resources/qml/Settings/SettingTextField.qml +++ b/resources/qml/Settings/SettingTextField.qml @@ -90,21 +90,11 @@ SettingItem Keys.onReleased: { -// text = text.replace(",", ".") // User convenience. We use dots for decimal values -// if(parseFloat(text) != base.parentValue) -// { -// base.valueChanged(parseFloat(text)); -// } - propertyProvider.setPropertyValue("value", text) } onEditingFinished: { -// if(parseFloat(text) != base.parentValue) -// { -// base.valueChanged(parseFloat(text)); -// } propertyProvider.setPropertyValue("value", text) } @@ -121,33 +111,9 @@ SettingItem { target: input property: "text" - value: control.format(propertyProvider.properties.value) + value: propertyProvider.properties.value when: !input.activeFocus } } - - //Rounds a floating point number to 4 decimals. This prevents floating - //point rounding errors. - // - //input: The number to round. - //decimals: The number of decimals (digits after the radix) to round to. - //return: The rounded number. - function roundFloat(input, decimals) - { - //First convert to fixed-point notation to round the number to 4 decimals and not introduce new floating point errors. - //Then convert to a string (is implicit). The fixed-point notation will be something like "3.200". - //Then remove any trailing zeroes and the radix. - return input.toFixed(decimals).replace(/\.?0*$/, ""); //Match on periods, if any ( \.? ), followed by any number of zeros ( 0* ), then the end of string ( $ ). - } - - //Formats a value for display in the text field. - // - //This correctly handles formatting of float values. - // - //input: The string value to format. - //return: The formatted string. - function format(inputValue) { - return parseFloat(inputValue) ? roundFloat(parseFloat(inputValue), 4) : inputValue //If it's a float, round to four decimals. - } } } diff --git a/resources/qml/Settings/SettingView.qml b/resources/qml/Settings/SettingView.qml index 6261496c84..a6bb60f865 100644 --- a/resources/qml/Settings/SettingView.qml +++ b/resources/qml/Settings/SettingView.qml @@ -29,7 +29,8 @@ ScrollView model: UM.SettingDefinitionsModel { id: definitionsModel; containerId: Cura.MachineManager.activeDefinitionId - visibilityHandler: UM.SettingPreferenceVisibilityHandler {} + exclude: ["machine_settings"] + visibilityHandler: UM.SettingPreferenceVisibilityHandler { } } delegate: Loader