Browse Source

Merge branch 'feature_quality_changes'

Ghostkeeper 8 years ago
parent
commit
bb90c593b9

+ 10 - 5
cura/CuraApplication.py

@@ -85,10 +85,10 @@ class CuraApplication(QtApplication):
         self._open_file_queue = []  # Files to open when plug-ins are loaded.
 
         # Need to do this before ContainerRegistry tries to load the machines
-        SettingDefinition.addSupportedProperty("settable_per_mesh", DefinitionPropertyType.Any, default = True)
-        SettingDefinition.addSupportedProperty("settable_per_extruder", DefinitionPropertyType.Any, default = True)
-        SettingDefinition.addSupportedProperty("settable_per_meshgroup", DefinitionPropertyType.Any, default = True)
-        SettingDefinition.addSupportedProperty("settable_globally", DefinitionPropertyType.Any, default = True)
+        SettingDefinition.addSupportedProperty("settable_per_mesh", DefinitionPropertyType.Any, default = True, read_only = True)
+        SettingDefinition.addSupportedProperty("settable_per_extruder", DefinitionPropertyType.Any, default = True, read_only = True)
+        SettingDefinition.addSupportedProperty("settable_per_meshgroup", DefinitionPropertyType.Any, default = True, read_only = True)
+        SettingDefinition.addSupportedProperty("settable_globally", DefinitionPropertyType.Any, default = True, read_only = True)
         SettingDefinition.addSupportedProperty("global_inherits_stack", DefinitionPropertyType.Function, default = "-1")
         SettingDefinition.addSettingType("extruder", None, str, Validator)
 
@@ -182,6 +182,10 @@ class CuraApplication(QtApplication):
         empty_quality_container._id = "empty_quality"
         empty_quality_container.addMetaDataEntry("type", "quality")
         ContainerRegistry.getInstance().addContainer(empty_quality_container)
+        empty_quality_changes_container = copy.deepcopy(empty_container)
+        empty_quality_changes_container._id = "empty_quality_changes"
+        empty_quality_changes_container.addMetaDataEntry("type", "quality_changes")
+        ContainerRegistry.getInstance().addContainer(empty_quality_changes_container)
 
         ContainerRegistry.getInstance().load()
 
@@ -308,7 +312,7 @@ class CuraApplication(QtApplication):
             path = None
             if instance_type == "material":
                 path = Resources.getStoragePath(self.ResourceTypes.MaterialInstanceContainer, file_name)
-            elif instance_type == "quality":
+            elif instance_type == "quality" or instance_type == "quality_changes":
                 path = Resources.getStoragePath(self.ResourceTypes.QualityInstanceContainer, file_name)
             elif instance_type == "user":
                 path = Resources.getStoragePath(self.ResourceTypes.UserInstanceContainer, file_name)
@@ -476,6 +480,7 @@ class CuraApplication(QtApplication):
 
         qmlRegisterType(cura.Settings.ContainerSettingsModel, "Cura", 1, 0, "ContainerSettingsModel")
         qmlRegisterType(cura.Settings.MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler")
+        qmlRegisterType(cura.Settings.QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel")
 
         qmlRegisterSingletonType(cura.Settings.ContainerManager, "Cura", 1, 0, "ContainerManager", cura.Settings.ContainerManager.createContainerManager)
 

+ 266 - 7
cura/Settings/ContainerManager.py

@@ -14,6 +14,8 @@ import UM.Platform
 import UM.MimeTypeDatabase
 import UM.Logger
 
+import cura.Settings
+
 from UM.MimeTypeDatabase import MimeTypeNotFoundError
 
 from UM.i18n import i18nCatalog
@@ -135,12 +137,11 @@ class ContainerManager(QObject):
 
         merge = containers[0]
 
-        if type(merge) != type(merge_into):
+        if not isinstance(merge, type(merge_into)):
             UM.Logger.log("w", "Cannot merge two containers of different types")
             return False
 
-        for key in merge.getAllKeys():
-            merge_into.setProperty(key, "value", merge.getProperty(key, "value"))
+        self._performMerge(merge_into, merge)
 
         return True
 
@@ -350,6 +351,188 @@ class ContainerManager(QObject):
 
         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.
+    #
+    #   This will go through the active global stack and all active extruder stacks and merge the changes from the user
+    #   container into the quality_changes container. After that, the user container is cleared.
+    #
+    #   \return \type{bool} True if successful, False if not.
+    @pyqtSlot(result = bool)
+    def updateQualityChanges(self):
+        global_stack = UM.Application.getInstance().getGlobalContainerStack()
+        if not global_stack:
+            return False
+
+        UM.Application.getInstance().getMachineManager().blurSettings.emit()
+
+        for stack in cura.Settings.ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
+            # Find the quality_changes container for this stack and merge the contents of the top container into it.
+            quality_changes = stack.findContainer(type = "quality_changes")
+            if not quality_changes or quality_changes.isReadOnly():
+                UM.Logger.log("e", "Could not update quality of a nonexistant or read only quality profile in stack %s", stack.getId())
+                continue
+
+            self._performMerge(quality_changes, stack.getTop())
+
+        UM.Application.getInstance().getMachineManager().activeQualityChanged.emit()
+
+        return True
+
+    ##  Clear the top-most (user) containers of the active stacks.
+    @pyqtSlot()
+    def clearUserContainers(self):
+        UM.Application.getInstance().getMachineManager().blurSettings.emit()
+
+        # Go through global and extruder stacks and clear their topmost container (the user settings).
+        for stack in cura.Settings.ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
+            stack.getTop().clear()
+
+    ##  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(result = bool)
+    def createQualityChanges(self):
+        global_stack = UM.Application.getInstance().getGlobalContainerStack()
+        if not global_stack:
+            return False
+
+        quality_container = global_stack.findContainer(type = "quality")
+        if not quality_container:
+            UM.Logger.log("w", "No quality container found in stack %s, cannot create profile", global_stack.getId())
+            return False
+
+        UM.Application.getInstance().getMachineManager().blurSettings.emit()
+
+        unique_name = UM.Settings.ContainerRegistry.getInstance().uniqueName(quality_container.getName())
+
+        # Go through the active stacks and create quality_changes containers from the user containers.
+        for stack in cura.Settings.ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
+            user_container = stack.getTop()
+            quality_container = stack.findContainer(type = "quality")
+            quality_changes_container = stack.findContainer(type = "quality_changes")
+            if not quality_container or not quality_changes_container:
+                UM.Logger.log("w", "No quality or quality changes container found in stack %s, ignoring it", stack.getId())
+                continue
+
+            new_changes = self._createQualityChanges(quality_container, unique_name, stack.getId())
+            self._performMerge(new_changes, user_container)
+
+            UM.Settings.ContainerRegistry.getInstance().addContainer(new_changes)
+            stack.replaceContainer(stack.getContainerIndex(quality_changes_container), new_changes)
+
+        UM.Application.getInstance().getMachineManager().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):
+        if not quality_name:
+            return False
+
+        for container in self._getFilteredContainers(name = quality_name, type = "quality_changes"):
+            UM.Settings.ContainerRegistry.getInstance().removeContainer(container.getId())
+
+        return True
+
+    ##  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):
+        if not quality_name or not new_name:
+            return False
+
+        if quality_name == new_name:
+            return True
+
+        global_stack = UM.Application.getInstance().getGlobalContainerStack()
+        if not global_stack:
+            return False
+
+        UM.Application.getInstance().getMachineManager().blurSettings.emit()
+
+        new_name = UM.Settings.ContainerRegistry.getInstance().uniqueName(new_name)
+
+        container_registry = UM.Settings.ContainerRegistry.getInstance()
+        for container in self._getFilteredContainers(name = quality_name, type = "quality_changes"):
+            stack_id = container.getMetaDataEntry("extruder", global_stack.getId())
+            container_registry.renameContainer(container.getId(), new_name, self._createUniqueId(stack_id, new_name))
+
+        UM.Application.getInstance().getMachineManager().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, result = str)
+    def duplicateQualityOrQualityChanges(self, quality_name):
+        global_stack = UM.Application.getInstance().getGlobalContainerStack()
+        if not global_stack or not quality_name:
+            return ""
+
+        containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(name = quality_name)
+        if not containers:
+            return ""
+
+        new_name = UM.Settings.ContainerRegistry.getInstance().uniqueName(quality_name)
+
+        container_type = containers[0].getMetaDataEntry("type")
+        if container_type == "quality":
+            for container in self._getFilteredContainers(name = quality_name, type = "quality"):
+                for stack in cura.Settings.ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
+                    new_changes = self._createQualityChanges(container, new_name, stack.getId())
+                    UM.Settings.ContainerRegistry.getInstance().addContainer(new_changes)
+        elif container_type == "quality_changes":
+            for container in self._getFilteredContainers(name = quality_name, type = "quality_changes"):
+                stack_id = container.getMetaDataEntry("extruder", global_stack.getId())
+                new_container = container.duplicate(self._createUniqueId(stack_id, new_name), new_name)
+                UM.Settings.ContainerRegistry.getInstance().addContainer(new_container)
+        else:
+            return ""
+
+        return new_name
+
+    # Factory function, used by QML
+    @staticmethod
+    def createContainerManager(engine, js_engine):
+        return ContainerManager()
+
+    def _performMerge(self, merge_into, merge):
+        assert isinstance(merge, type(merge_into))
+
+        if merge == merge_into:
+            return
+
+        for key in merge.getAllKeys():
+            merge_into.setProperty(key, "value", merge.getProperty(key, "value"))
+
+        merge.clear()
+
     def _updateContainerNameFilters(self):
         self._container_name_filters = {}
         for plugin_id, container_type in UM.Settings.ContainerRegistry.getContainerTypes():
@@ -394,7 +577,83 @@ class ContainerManager(QObject):
             name_filter = "{0} ({1})".format(mime_type.comment, suffix_list)
             self._container_name_filters[name_filter] = entry
 
-    # Factory function, used by QML
-    @staticmethod
-    def createContainerManager(engine, js_engine):
-        return ContainerManager()
+    ##  Return a generator that iterates over a set of containers that are filtered by machine and material when needed.
+    #
+    #   \param kwargs Initial search criteria that the containers need to match.
+    #
+    #   \return A generator that iterates over the list of containers matching the search criteria.
+    def _getFilteredContainers(self, **kwargs):
+        global_stack = UM.Application.getInstance().getGlobalContainerStack()
+        if not global_stack:
+            return False
+
+        criteria = kwargs
+
+        filter_by_material = False
+
+        if global_stack.getMetaDataEntry("has_machine_quality"):
+            criteria["definition"] = global_stack.getBottom().getId()
+
+            filter_by_material = global_stack.getMetaDataEntry("has_materials")
+
+        material_ids = []
+        if filter_by_material:
+            for stack in cura.Settings.ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
+                material_ids.append(stack.findContainer(type = "material").getId())
+
+        containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**criteria)
+        for container in containers:
+            # If the machine specifies we should filter by material, exclude containers that do not match any active material.
+            if filter_by_material and container.getMetaDataEntry("material") not in material_ids:
+                continue
+
+            yield container
+
+    ##  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 stack_id The ID of the container stack the new container "belongs to". It is used primarily to ensure a unique ID.
+    #
+    #   \return A new quality_changes container with the specified container as base.
+    def _createQualityChanges(self, quality_container, new_name, stack_id):
+        global_stack = UM.Application.getInstance().getGlobalContainerStack()
+        assert global_stack is not None
+
+        # Create a new quality_changes container for the quality.
+        quality_changes = UM.Settings.InstanceContainer(self._createUniqueId(stack_id, new_name))
+        quality_changes.setName(new_name)
+        quality_changes.addMetaDataEntry("type", "quality_changes")
+        quality_changes.addMetaDataEntry("quality", quality_container.getMetaDataEntry("quality_type"))
+
+        # If we are creating a container for an extruder, ensure we add that to the container
+        if stack_id != global_stack.getId():
+            quality_changes.addMetaDataEntry("extruder", stack_id)
+
+        # If the machine specifies qualities should be filtered, ensure we match the current criteria.
+        if not global_stack.getMetaDataEntry("has_machine_quality"):
+            quality_changes.setDefinition(UM.Settings.ContainerRegistry.getInstance().findContainers(id = "fdmprinter")[0])
+        else:
+            quality_changes.setDefinition(global_stack.getBottom())
+
+            if global_stack.getMetaDataEntry("has_materials"):
+                material = quality_container.getMetaDataEntry("material")
+                quality_changes.addMetaDataEntry("material", material)
+
+        return quality_changes

+ 17 - 0
cura/Settings/ExtruderManager.py

@@ -235,6 +235,9 @@ class ExtruderManager(QObject):
 
         container_stack.addContainer(quality)
 
+        empty_quality_changes = container_registry.findInstanceContainers(id = "empty_quality_changes")[0]
+        container_stack.addContainer(empty_quality_changes)
+
         user_profile = container_registry.findInstanceContainers(type = "user", extruder = extruder_stack_id)
         if user_profile: # There was already a user profile, loaded from settings.
             user_profile = user_profile[0]
@@ -274,6 +277,20 @@ class ExtruderManager(QObject):
         for name in self._extruder_trains[machine_id]:
             yield self._extruder_trains[machine_id][name]
 
+    ##  Returns a generator that will iterate over the global stack and per-extruder stacks.
+    #
+    #   The first generated element is the global container stack. After that any extruder stacks are generated.
+    def getActiveGlobalAndExtruderStacks(self):
+        global_stack = UM.Application.getInstance().getGlobalContainerStack()
+        if not global_stack:
+            return
+
+        yield global_stack
+
+        global_id = global_stack.getId()
+        for name in self._extruder_trains[global_id]:
+            yield self._extruder_trains[global_id][name]
+
     def __globalContainerStackChanged(self):
         self._addCurrentMachineExtruders()
         self.activeExtruderChanged.emit()

+ 150 - 238
cura/Settings/MachineManager.py

@@ -26,16 +26,17 @@ class MachineManager(QObject):
         self._global_container_stack = None
 
         Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)
+        ##  When the global container is changed, active material probably needs to be updated.
+        self.globalContainerChanged.connect(self.activeMaterialChanged)
+        self.globalContainerChanged.connect(self.activeVariantChanged)
+        self.globalContainerChanged.connect(self.activeQualityChanged)
+
         self._active_stack_valid = None
         self._onGlobalContainerChanged()
 
         ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged)
         self._onActiveExtruderStackChanged()
 
-        ##  When the global container is changed, active material probably needs to be updated.
-        self.globalContainerChanged.connect(self.activeMaterialChanged)
-        self.globalContainerChanged.connect(self.activeVariantChanged)
-        self.globalContainerChanged.connect(self.activeQualityChanged)
         ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeMaterialChanged)
         ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeVariantChanged)
         ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeQualityChanged)
@@ -47,6 +48,7 @@ class MachineManager(QObject):
         self._empty_variant_container = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = "empty_variant")[0]
         self._empty_material_container = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = "empty_material")[0]
         self._empty_quality_container = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = "empty_quality")[0]
+        self._empty_quality_changes_container = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = "empty_quality_changes")[0]
 
         Preferences.getInstance().addPreference("cura/active_machine", "")
 
@@ -184,107 +186,11 @@ class MachineManager(QObject):
             if old_index is not None:
                 extruder_manager.setActiveExtruderIndex(old_index)
 
-    def _onGlobalPropertyChanged(self, key, property_name):
-        if property_name == "value":
-            ## We can get recursion issues. So we store a list of keys that we are still handling to prevent this.
-            if key in self._global_event_keys:
-                return
-            self._global_event_keys.add(key)
-            self.globalValueChanged.emit()
-
-            if self._active_container_stack and self._active_container_stack != self._global_container_stack:
-                # Make the global current settings mirror the stack values appropriate for this setting
-                if self._active_container_stack.getProperty("extruder_nr", "value") == int(self._active_container_stack.getProperty(key, "global_inherits_stack")):
-
-                    new_value = self._active_container_stack.getProperty(key, "value")
-                    self._global_container_stack.getTop().setProperty(key, "value", new_value)
-
-                # Global-only setting values should be set on all extruders and the global stack
-                if not self._global_container_stack.getProperty(key, "settable_per_extruder"):
-                    extruder_stacks = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
-                    target_stack_position = int(self._active_container_stack.getProperty(key, "global_inherits_stack"))
-                    if target_stack_position == -1:  # Prevent -1 from selecting wrong stack.
-                        target_stack = self._active_container_stack
-                    else:
-                        target_stack = extruder_stacks[target_stack_position]
-                    new_value = target_stack.getProperty(key, "value")
-                    target_stack_has_user_value = target_stack.getTop().getInstance(key) != None
-                    for extruder_stack in extruder_stacks:
-                        if extruder_stack != target_stack:
-                            if target_stack_has_user_value:
-                                extruder_stack.getTop().setProperty(key, "value", new_value)
-                            else:
-                                # Remove from the value from the other stacks as well, unless the
-                                # top value from the other stacklevels is different than the new value
-                                for container in extruder_stack.getContainers():
-                                    if container.__class__ == UM.Settings.InstanceContainer and container.getInstance(key) != None:
-                                        if container.getProperty(key, "value") != new_value:
-                                            # It could be that the setting needs to be removed instead of updated.
-                                            temp = extruder_stack
-                                            containers = extruder_stack.getContainers()
-                                            # Ensure we have the entire 'chain'
-                                            while temp.getNextStack():
-                                                temp = temp.getNextStack()
-                                                containers.extend(temp.getContainers())
-                                            instance_needs_removal = False
-
-                                            if len(containers) > 1:
-                                                for index in range(1, len(containers)):
-                                                    deeper_container = containers[index]
-                                                    if deeper_container.getProperty(key, "value") is None:
-                                                        continue  # Deeper container does not have the value, so continue.
-                                                    if deeper_container.getProperty(key, "value") == new_value:
-                                                        # Removal will result in correct value, so do that.
-                                                        # We do this to prevent the reset from showing up unneeded.
-                                                        instance_needs_removal = True
-                                                        break
-                                                    else:
-                                                        # Container has the value, but it's not the same. Stop looking.
-                                                        break
-                                            if instance_needs_removal:
-                                                extruder_stack.getTop().removeInstance(key)
-                                            else:
-                                                extruder_stack.getTop().setProperty(key, "value", new_value)
-                                        else:
-                                            # Check if we really need to remove something.
-                                            if extruder_stack.getProperty(key, "value") != new_value:
-                                                extruder_stack.getTop().removeInstance(key)
-                                        break
-                    if self._global_container_stack.getProperty(key, "value") != new_value:
-                        self._global_container_stack.getTop().setProperty(key, "value", new_value)
-            self._global_event_keys.remove(key)
-
-        if property_name == "global_inherits_stack":
-            if self._active_container_stack and self._active_container_stack != self._global_container_stack:
-                # Update the global user value when the "global_inherits_stack" function points to a different stack
-                extruder_stacks = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
-                target_stack_position = int(self._active_container_stack.getProperty(key, "global_inherits_stack"))
-                if target_stack_position == -1:  # Prevent -1 from selecting wrong stack.
-                    target_stack = self._active_container_stack
-                else:
-                    target_stack = extruder_stacks[target_stack_position]
-
-                new_value = target_stack.getProperty(key, "value")
-                if self._global_container_stack.getProperty(key, "value") != new_value:
-                    self._global_container_stack.getTop().setProperty(key, "value", new_value)
-
-        if property_name == "validationState":
-            if self._active_stack_valid:
-                changed_validation_state = self._active_container_stack.getProperty(key, property_name)
-                if changed_validation_state in (UM.Settings.ValidatorState.Exception, UM.Settings.ValidatorState.MaximumError, UM.Settings.ValidatorState.MinimumError):
-                    self._active_stack_valid = False
-                    self.activeValidationChanged.emit()
-            else:
-                has_errors = self._checkStackForErrors(self._active_container_stack)
-                if not has_errors:
-                    self._active_stack_valid = True
-                    self.activeValidationChanged.emit()
-
     def _onGlobalContainerChanged(self):
         if self._global_container_stack:
             self._global_container_stack.nameChanged.disconnect(self._onMachineNameChanged)
             self._global_container_stack.containersChanged.disconnect(self._onInstanceContainersChanged)
-            self._global_container_stack.propertyChanged.disconnect(self._onGlobalPropertyChanged)
+            self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
 
             material = self._global_container_stack.findContainer({"type": "material"})
             material.nameChanged.disconnect(self._onMaterialNameChanged)
@@ -301,7 +207,7 @@ class MachineManager(QObject):
             Preferences.getInstance().setValue("cura/active_machine", self._global_container_stack.getId())
             self._global_container_stack.nameChanged.connect(self._onMachineNameChanged)
             self._global_container_stack.containersChanged.connect(self._onInstanceContainersChanged)
-            self._global_container_stack.propertyChanged.connect(self._onGlobalPropertyChanged)
+            self._global_container_stack.propertyChanged.connect(self._onPropertyChanged)
             material = self._global_container_stack.findContainer({"type": "material"})
             material.nameChanged.connect(self._onMaterialNameChanged)
 
@@ -312,11 +218,11 @@ class MachineManager(QObject):
         self.blurSettings.emit()  # Ensure no-one has focus.
         if self._active_container_stack and self._active_container_stack != self._global_container_stack:
             self._active_container_stack.containersChanged.disconnect(self._onInstanceContainersChanged)
-            self._active_container_stack.propertyChanged.disconnect(self._onGlobalPropertyChanged)
+            self._active_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
         self._active_container_stack = ExtruderManager.getInstance().getActiveExtruderStack()
         if self._active_container_stack:
             self._active_container_stack.containersChanged.connect(self._onInstanceContainersChanged)
-            self._active_container_stack.propertyChanged.connect(self._onGlobalPropertyChanged)
+            self._active_container_stack.propertyChanged.connect(self._onPropertyChanged)
         else:
             self._active_container_stack = self._global_container_stack
         self._active_stack_valid = not self._checkStackForErrors(self._active_container_stack)
@@ -325,17 +231,6 @@ class MachineManager(QObject):
     def _onInstanceContainersChanged(self, container):
         container_type = container.getMetaDataEntry("type")
 
-        if self._active_container_stack and self._active_container_stack != self._global_container_stack:
-            if int(self._active_container_stack.getProperty("extruder_nr", "value")) == 0:
-                global_container = self._global_container_stack.findContainer({"type": container_type})
-                if global_container and global_container != container:
-                    container_index = self._global_container_stack.getContainerIndex(global_container)
-                    self._global_container_stack.replaceContainer(container_index, container)
-
-            for key in container.getAllKeys():
-                # Make sure the values in this profile are distributed to other stacks if necessary
-                self._onGlobalPropertyChanged(key, "value")
-
         if container_type == "material":
             self.activeMaterialChanged.emit()
         elif container_type == "variant":
@@ -343,6 +238,23 @@ class MachineManager(QObject):
         elif container_type == "quality":
             self.activeQualityChanged.emit()
 
+    def _onPropertyChanged(self, key, property_name):
+        if property_name == "validationState":
+            if self._active_stack_valid:
+                if self._active_container_stack.getProperty(key, "settable_per_extruder"):
+                    changed_validation_state = self._active_container_stack.getProperty(key, property_name)
+                else:
+                    changed_validation_state = self._global_container_stack.getProperty(key, property_name)
+                if changed_validation_state in (UM.Settings.ValidatorState.Exception, UM.Settings.ValidatorState.MaximumError, UM.Settings.ValidatorState.MinimumError):
+                    self._active_stack_valid = False
+                    self.activeValidationChanged.emit()
+            else:
+                if not self._checkStackForErrors(self._active_container_stack) and not self._checkStackForErrors(self._global_container_stack):
+                    self._active_stack_valid = True
+                    self.activeValidationChanged.emit()
+
+        self.activeStackChanged.emit()
+
     @pyqtSlot(str)
     def setActiveMachine(self, stack_id):
         containers = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(id = stack_id)
@@ -370,7 +282,6 @@ class MachineManager(QObject):
             current_settings_instance_container.setDefinition(definitions[0])
             container_registry.addContainer(current_settings_instance_container)
 
-            # If a definition is found, its a list. Should only have one item.
             new_global_stack.addContainer(definition)
             if variant_instance_container:
                 new_global_stack.addContainer(variant_instance_container)
@@ -378,6 +289,8 @@ class MachineManager(QObject):
                 new_global_stack.addContainer(material_instance_container)
             if quality_instance_container:
                 new_global_stack.addContainer(quality_instance_container)
+
+            new_global_stack.addContainer(self._empty_quality_changes_container)
             new_global_stack.addContainer(current_settings_instance_container)
 
             ExtruderManager.getInstance().addMachineExtruders(definition, new_global_stack.getId())
@@ -418,11 +331,17 @@ class MachineManager(QObject):
     ##  Check if the global_container has instances in the user container
     @pyqtProperty(bool, notify = activeStackChanged)
     def hasUserSettings(self):
-        if not self._active_container_stack:
+        if not self._global_container_stack:
             return False
 
-        user_settings = self._active_container_stack.getTop().findInstances(**{})
-        return len(user_settings) != 0
+        if self._global_container_stack.getTop().findInstances():
+            return True
+
+        for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()):
+            if stack.getTop().findInstances():
+                return True
+
+        return False
 
     ##  Check if the global profile does not contain error states
     #   Note that the _active_stack_valid is cached due to performance issues
@@ -477,6 +396,22 @@ class MachineManager(QObject):
 
         return ""
 
+    @pyqtProperty("QVariantMap", notify = activeMaterialChanged)
+    def allActiveMaterialIds(self):
+        if not self._global_container_stack:
+            return {}
+
+        result = {}
+
+        for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
+            material_container = stack.findContainer(type = "material")
+            if not material_container:
+                continue
+
+            result[stack.getId()] = material_container.getId()
+
+        return result
+
     @pyqtProperty(str, notify=activeQualityChanged)
     def activeQualityMaterialId(self):
         if self._active_container_stack:
@@ -489,6 +424,9 @@ class MachineManager(QObject):
     @pyqtProperty(str, notify=activeQualityChanged)
     def activeQualityName(self):
         if self._active_container_stack:
+            quality = self._active_container_stack.findContainer({"type": "quality_changes"})
+            if quality and quality != self._empty_quality_changes_container:
+                return quality.getName()
             quality = self._active_container_stack.findContainer({"type": "quality"})
             if quality:
                 return quality.getName()
@@ -496,12 +434,31 @@ class MachineManager(QObject):
 
     @pyqtProperty(str, notify=activeQualityChanged)
     def activeQualityId(self):
-        if self._active_container_stack:
-            quality = self._active_container_stack.findContainer({"type": "quality"})
+        if self._global_container_stack:
+            quality = self._global_container_stack.findContainer({"type": "quality_changes"})
+            if quality and quality != self._empty_quality_changes_container:
+                return quality.getId()
+            quality = self._global_container_stack.findContainer({"type": "quality"})
             if quality:
                 return quality.getId()
         return ""
 
+    @pyqtProperty(str, notify = activeQualityChanged)
+    def activeQualityType(self):
+        if self._global_container_stack:
+            quality = self._global_container_stack.findContainer(type = "quality")
+            if quality:
+                return quality.getMetaDataEntry("quality_type")
+        return ""
+
+    @pyqtProperty(str, notify = activeQualityChanged)
+    def activeQualityChangesId(self):
+        if self._global_container_stack:
+            changes = self._global_container_stack.findContainer(type = "quality_changes")
+            if changes:
+                return changes.getId()
+        return ""
+
     ## Check if a container is read_only
     @pyqtSlot(str, result = bool)
     def isReadOnly(self, container_id):
@@ -523,104 +480,6 @@ class MachineManager(QObject):
             if extruder_stack != self._active_container_stack and extruder_stack.getProperty(key, "value") != new_value:
                 extruder_stack.getTop().setProperty(key, "value", new_value)
 
-    @pyqtSlot(result = str)
-    def newQualityContainerFromQualityAndUser(self):
-        new_container_id = self.duplicateContainer(self.activeQualityId)
-        if new_container_id == "":
-            return
-        self.blurSettings.emit()
-        self.updateQualityContainerFromUserContainer(new_container_id)
-        self.setActiveQuality(new_container_id)
-        return new_container_id
-
-    @pyqtSlot(str, result=str)
-    def duplicateContainer(self, container_id):
-        if not self._active_container_stack:
-            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_container = containers[0].duplicate(new_name, new_name)
-
-            UM.Settings.ContainerRegistry.getInstance().addContainer(new_container)
-
-            return new_name
-
-        return ""
-
-    @pyqtSlot(str, str)
-    def renameQualityContainer(self, container_id, new_name):
-        containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = container_id, type = "quality")
-        if containers:
-            new_name = self._createUniqueName("quality", containers[0].getName(), new_name,
-                                              catalog.i18nc("@label", "Custom profile"))
-
-            if containers[0].getName() == new_name:
-                # Nothing to do.
-                return
-
-            # As we also want the id of the container to be changed (so that profile name is the name of the file
-            # on disk. We need to create a new instance and remove it (so the old file of the container is removed)
-            # If we don't do that, we might get duplicates & other weird issues.
-            new_container = UM.Settings.InstanceContainer("")
-            new_container.deserialize(containers[0].serialize())
-
-            # Actually set the name
-            new_container.setName(new_name)
-            new_container._id = new_name  # Todo: Fix proper id change function for this.
-
-            # Add the "new" container.
-            UM.Settings.ContainerRegistry.getInstance().addContainer(new_container)
-
-            # Ensure that the renamed profile is saved -before- we remove the old profile.
-            Application.getInstance().saveSettings()
-
-            # Actually set & remove new / old quality.
-            self.setActiveQuality(new_name)
-            self.removeQualityContainer(containers[0].getId())
-
-    @pyqtSlot(str)
-    def removeQualityContainer(self, container_id):
-        containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = container_id)
-        if not containers or not self._active_container_stack:
-            return
-
-        # If the container that is being removed is the currently active container, set another machine as the active container
-        activate_new_container = container_id == self.activeQualityId
-
-        UM.Settings.ContainerRegistry.getInstance().removeContainer(container_id)
-
-        if activate_new_container:
-            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(str)
-    @pyqtSlot()
-    def updateQualityContainerFromUserContainer(self, quality_id = None):
-        if not self._active_container_stack:
-            return
-
-        if quality_id:
-            quality = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = quality_id, type = "quality")
-            if quality:
-                quality = quality[0]
-        else:
-            quality = self._active_container_stack.findContainer({"type": "quality"})
-
-        if not quality:
-            return
-
-        user_settings = self._active_container_stack.getTop()
-
-        for key in user_settings.getAllKeys():
-            quality.setProperty(key, "value", user_settings.getProperty(key, "value"))
-        self.clearUserSettings()  # As all users settings are noq a quality, remove them.
-
-
     @pyqtSlot(str)
     def setActiveMaterial(self, material_id):
         containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = material_id)
@@ -667,32 +526,81 @@ class MachineManager(QObject):
     @pyqtSlot(str)
     def setActiveQuality(self, quality_id):
         containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = quality_id)
-        if not containers or not self._active_container_stack:
+        if not containers or not self._global_container_stack:
             return
 
-        old_quality = self._active_container_stack.findContainer({"type": "quality"})
-        if old_quality and old_quality != containers[0]:
+        quality_container = None
+        quality_changes_container = self._empty_quality_changes_container
+
+        container_type = containers[0].getMetaDataEntry("type")
+
+        if container_type == "quality":
+            quality_container = containers[0]
+        elif container_type == "quality_changes":
+            quality_changes_container = containers[0]
+            containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(
+                quality_type = quality_changes_container.getMetaDataEntry("quality"))
+            if not containers:
+                Logger.log("e", "Could not find quality %s for changes %s, not changing quality", quality_changes_container.getMetaDataEntry("quality"), quality_changes_container.getId())
+                return
+            quality_container = containers[0]
+        else:
+            Logger.log("e", "Tried to set quality to a container that is not of the right type")
+            return
+
+        quality_type = quality_container.getMetaDataEntry("quality_type")
+        if not quality_type:
+            quality_type = quality_changes_container.getName()
+
+        for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
+            extruder_id = stack.getId() if stack != self._global_container_stack else None
+
+            criteria = { "quality_type": quality_type, "extruder": extruder_id }
+
+            if self._global_container_stack.getMetaDataEntry("has_machine_quality"):
+                material = stack.findContainer(type = "material")
+                criteria["material"] = material.getId()
+
+            stack_quality = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**criteria)
+            if not stack_quality:
+                criteria.pop("extruder")
+                stack_quality = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**criteria)
+                if not stack_quality:
+                    stack_quality = quality_container
+                else:
+                    stack_quality = stack_quality[0]
+            else:
+                stack_quality = stack_quality[0]
+
+            if quality_changes_container != self._empty_quality_changes_container:
+                stack_quality_changes = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(name = quality_changes_container.getName(), extruder = extruder_id)[0]
+            else:
+                stack_quality_changes = self._empty_quality_changes_container
+
+            old_quality = stack.findContainer(type = "quality")
             old_quality.nameChanged.disconnect(self._onQualityNameChanged)
+            old_changes = stack.findContainer(type = "quality_changes")
+            old_changes.nameChanged.disconnect(self._onQualityNameChanged)
 
-            quality_index = self._active_container_stack.getContainerIndex(old_quality)
+            stack.replaceContainer(stack.getContainerIndex(old_quality), stack_quality)
+            stack.replaceContainer(stack.getContainerIndex(old_changes), stack_quality_changes)
 
-            self._active_container_stack.replaceContainer(quality_index, containers[0])
+            stack_quality.nameChanged.connect(self._onQualityNameChanged)
+            stack_quality_changes.nameChanged.connect(self._onQualityNameChanged)
 
-            containers[0].nameChanged.connect(self._onQualityNameChanged)
+        if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1:
+            # Ask the user if the user profile should be cleared or not (discarding the current settings)
+            # In Simple Mode we assume the user always wants to keep the (limited) current settings
+            details = catalog.i18nc("@label", "You made changes to the following setting(s):")
+            user_settings = self._active_container_stack.getTop().findInstances(**{})
+            for setting in user_settings:
+                details = details + "\n    " + setting.definition.label
 
-            if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1:
-                # Ask the user if the user profile should be cleared or not (discarding the current settings)
-                # In Simple Mode we assume the user always wants to keep the (limited) current settings
-                details = catalog.i18nc("@label", "You made changes to the following setting(s):")
-                user_settings = self._active_container_stack.getTop().findInstances(**{})
-                for setting in user_settings:
-                    details = details + "\n    " + setting.definition.label
+            Application.getInstance().messageBox(catalog.i18nc("@window:title", "Switched profiles"), catalog.i18nc("@label", "Do you want to transfer your changed settings to this profile?"),
+                                                 catalog.i18nc("@label", "If you transfer your settings they will override settings in the profile."), details,
+                                                 buttons = QMessageBox.Yes + QMessageBox.No, icon = QMessageBox.Question, callback = self._keepUserSettingsDialogCallback)
 
-                Application.getInstance().messageBox(catalog.i18nc("@window:title", "Switched profiles"), catalog.i18nc("@label", "Do you want to transfer your changed settings to this profile?"),
-                                                     catalog.i18nc("@label", "If you transfer your settings they will override settings in the profile."), details,
-                                                     buttons = QMessageBox.Yes + QMessageBox.No, icon = QMessageBox.Question, callback = self._keepUserSettingsDialogCallback)
-        else:
-            Logger.log("w", "While trying to set the active quality, no quality was found to replace.")
+        self.activeQualityChanged.emit()
 
     def _keepUserSettingsDialogCallback(self, button):
         if button == QMessageBox.Yes:
@@ -700,7 +608,11 @@ class MachineManager(QObject):
             pass
         elif button == QMessageBox.No:
             # No, discard the settings in the user profile
-            self.clearUserSettings()
+            global_stack = Application.getInstance().getGlobalContainerStack()
+            for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()):
+                extruder.getTop().clear()
+
+            global_stack.getTop().clear()
 
     @pyqtProperty(str, notify = activeVariantChanged)
     def activeVariantName(self):

+ 156 - 0
cura/Settings/QualitySettingsModel.py

@@ -0,0 +1,156 @@
+# Copyright (c) 2016 Ultimaker B.V.
+# Cura is released under the terms of the AGPLv3 or higher.
+
+import collections
+
+from PyQt5.QtCore import pyqtProperty, pyqtSignal, Qt
+
+import UM.Application
+import UM.Logger
+import UM.Qt
+import UM.Settings
+
+
+class QualitySettingsModel(UM.Qt.ListModel.ListModel):
+    KeyRole = Qt.UserRole + 1
+    LabelRole = Qt.UserRole + 2
+    UnitRole = Qt.UserRole + 3
+    ProfileValueRole = Qt.UserRole + 4
+    UserValueRole = Qt.UserRole + 5
+    CategoryRole = Qt.UserRole + 6
+
+    def __init__(self, parent = None):
+        super().__init__(parent = parent)
+
+        self._extruder_id = None
+        self._quality = None
+        self._material = 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.UserValueRole, "user_value")
+        self.addRoleName(self.CategoryRole, "category")
+
+    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 setQuality(self, quality):
+        if quality != self._quality:
+            self._quality = quality
+            self._update()
+            self.qualityChanged.emit()
+
+    qualityChanged = pyqtSignal()
+    @pyqtProperty(str, fset = setQuality, notify = qualityChanged)
+    def quality(self):
+        return self._quality
+
+    def setMaterial(self, material):
+        if material != self._material:
+            self._material = material
+            self._update()
+            self.materialChanged.emit()
+
+    materialChanged = pyqtSignal()
+    @pyqtProperty(str, fset = setMaterial, notify = materialChanged)
+    def material(self):
+        return self._material
+
+    def _update(self):
+        if not self._quality:
+            return
+
+        self.clear()
+
+        settings = collections.OrderedDict()
+        definition_container = UM.Application.getInstance().getGlobalContainerStack().getBottom()
+
+        containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = self._quality)
+        if not containers:
+            UM.Logger.log("w", "Could not find a quality container with id %s", self._quality)
+            return
+
+        quality_container = None
+        quality_changes_container = None
+
+        if containers[0].getMetaDataEntry("type") == "quality":
+            quality_container = containers[0]
+        else:
+            quality_changes_container = containers[0]
+
+            criteria = { "type": "quality", "quality_type": quality_changes_container.getMetaDataEntry("quality"), "definition": quality_changes_container.getDefinition().getId() }
+
+            if self._material:
+                criteria["material"] = self._material
+
+            quality_container = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**criteria)
+            if not quality_container:
+                UM.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")
+
+        criteria = { "type": "quality", "quality_type": quality_type }
+
+        if self._material:
+            criteria["material"] = self._material
+
+        criteria["extruder"] = self._extruder_id
+
+        containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**criteria)
+        if not containers:
+            # Try again, this time without extruder
+            criteria.pop("extruder")
+            containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**criteria)
+
+            if not containers:
+                UM.Logger.log("Could not find any quality containers matching the search criteria %s", criteria)
+                return
+
+        if quality_changes_container:
+            changes = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "quality_changes", quality = quality_type, extruder = self._extruder_id)
+            if changes:
+                containers.extend(changes)
+
+        current_category = ""
+        for definition in definition_container.findDefinitions():
+            if definition.type == "category":
+                current_category = definition.label
+                continue
+
+            profile_value = None
+            for container in containers:
+                new_value = container.getProperty(definition.key, "value")
+                if new_value:
+                    profile_value = new_value
+
+            user_value = None
+            if not self._extruder_id:
+                user_value = UM.Application.getInstance().getGlobalContainerStack().getTop().getProperty(definition.key, "value")
+            else:
+                extruder_stack = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(id = self._extruder_id)
+                if extruder_stack:
+                    user_value = extruder_stack[0].getTop().getProperty(definition.key, "value")
+
+            if not profile_value and not user_value:
+                continue
+
+            self.appendItem({
+                "key": definition.key,
+                "label": definition.label,
+                "unit": definition.unit,
+                "profile_value": "" if profile_value is None else str(profile_value),  # it is for display only
+                "user_value": "" if user_value is None else str(user_value),
+                "category": current_category
+            })

+ 1 - 0
cura/Settings/__init__.py

@@ -10,3 +10,4 @@ from .ExtrudersModel import ExtrudersModel
 from .MachineManager import MachineManager
 from .MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler
 from .SettingOverrideDecorator import SettingOverrideDecorator
+from .QualitySettingsModel import QualitySettingsModel

+ 2 - 2
resources/qml/Actions.qml

@@ -122,7 +122,7 @@ Item
         id: updateProfileAction;
         enabled: Cura.MachineManager.isActiveStackValid && Cura.MachineManager.hasUserSettings && !Cura.MachineManager.isReadOnly(Cura.MachineManager.activeQualityId)
         text: catalog.i18nc("@action:inmenu menubar:profile","&Update profile with current settings");
-        onTriggered: Cura.MachineManager.updateQualityContainerFromUserContainer()
+        onTriggered: Cura.ContainerManager.updateQualityChanges();
     }
 
     Action
@@ -130,7 +130,7 @@ Item
         id: resetProfileAction;
         enabled: Cura.MachineManager.hasUserSettings
         text: catalog.i18nc("@action:inmenu menubar:profile","&Discard current settings");
-        onTriggered: Cura.MachineManager.clearUserSettings();
+        onTriggered: Cura.ContainerManager.clearUserContainers();
     }
 
     Action

+ 2 - 2
resources/qml/Cura.qml

@@ -150,7 +150,7 @@ UM.MainWindow
 
                         MenuSeparator { }
 
-                        MenuItem { text: "Set as Active Extruder" }
+                        MenuItem { text: catalog.i18nc("@action:inmenu", "Set as Active Extruder"); onTriggered: Cura.ExtruderManager.setActiveExtruderIndex(model.index) }
                     }
                     onObjectAdded: settingsMenu.insertItem(index, object)
                     onObjectRemoved: settingsMenu.removeItem(object)
@@ -459,7 +459,7 @@ UM.MainWindow
         target: Cura.Actions.addProfile
         onTriggered:
         {
-            Cura.MachineManager.newQualityContainerFromQualityAndUser();
+            Cura.ContainerManager.createQualityChanges();
             preferences.setPage(4);
             preferences.show();
 

+ 4 - 5
resources/qml/Menus/ProfileMenu.qml

@@ -7,7 +7,7 @@ import QtQuick.Controls 1.1
 import UM 1.2 as UM
 import Cura 1.0 as Cura
 
- Menu
+Menu
 {
     id: menu
 
@@ -15,14 +15,14 @@ import Cura 1.0 as Cura
     {
         model: UM.InstanceContainersModel
         {
-            filter: menu.getFilter({ "read_only": true });
+            filter: menu.getFilter({ "type": "quality" });
         }
 
         MenuItem
         {
             text: model.name
             checkable: true
-            checked: Cura.MachineManager.activeQualityId == model.id
+            checked: Cura.MachineManager.activeQualityChangesId == "empty_quality_changes" && Cura.MachineManager.activeQualityType == model.metadata.quality_type
             exclusiveGroup: group
             onTriggered: Cura.MachineManager.setActiveQuality(model.id)
         }
@@ -38,7 +38,7 @@ import Cura 1.0 as Cura
         id: customProfileInstantiator
         model: UM.InstanceContainersModel
         {
-            filter: menu.getFilter({ "read_only": false });
+            filter: { "type": "quality_changes", "extruder": null, "definition": Cura.MachineManager.filterQualityByMachine ? Cura.MachineManager.activeDefinitionId : "fdmprinter" };
             onModelReset: customSeparator.visible = rowCount() > 0
         }
 
@@ -76,7 +76,6 @@ import Cura 1.0 as Cura
     function getFilter(initial_conditions)
     {
         var result = initial_conditions;
-        result.type = "quality"
 
         if(Cura.MachineManager.filterQualityByMachine)
         {

+ 38 - 0
resources/qml/Preferences/ProfileTab.qml

@@ -0,0 +1,38 @@
+// Copyright (c) 2016 Ultimaker B.V.
+// Cura is released under the terms of the AGPLv3 or higher.
+
+import QtQuick 2.1
+import QtQuick.Controls 1.1
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+Tab
+{
+    id: base
+
+    property string extruderId: "";
+    property string quality: "";
+    property string material: "";
+
+    TableView
+    {
+        anchors.fill: parent
+        anchors.margins: UM.Theme.getSize("default_margin").width
+
+        TableViewColumn { role: "label"; title: catalog.i18nc("@title:column", "Setting") }
+        TableViewColumn { role: "profile_value"; title: catalog.i18nc("@title:column", "Profile Value"); }
+        TableViewColumn { role: "user_value"; title: catalog.i18nc("@title:column", "User Value"); visible: quality == Cura.MachineManager.activeQualityId }
+        TableViewColumn { role: "unit"; title: catalog.i18nc("@title:column", "Unit") }
+
+        section.property: "category"
+        section.delegate: Label { text: section }
+
+        model: Cura.QualitySettingsModel
+        {
+            extruderId: base.extruderId != "" ? base.extruderId : null;
+            quality: base.quality != null ? base.quality : "";
+            material: base.material
+        }
+    }
+}

Some files were not shown because too many files changed in this diff