diff options
author | Simon Edwards <s.edwards@ultimaker.com> | 2016-09-20 17:06:26 +0300 |
---|---|---|
committer | U-ULTIMAKER\s.edwards <s.edwards@UM-MBP-282.cloud.ultimaker.com> | 2016-09-21 16:26:20 +0300 |
commit | 90dedc354c4e295e11e1826ff59d988067f2924e (patch) | |
tree | 419aa988423dd191cd0c9f9ed0c3741191ff09fb /cura | |
parent | 45b753082f3895c20b58cff0d54c9b1dbb7eca0c (diff) |
* Make sure that quality changes are machine dependent but material independent.
* Correctly handle machines which derived (subclasses) from other machines.
* Some refactoring and a lot more comments.
Contributes to CURA-2414 Quality changes profiles are created incorrectly
Diffstat (limited to 'cura')
-rw-r--r-- | cura/QualityManager.py | 132 | ||||
-rw-r--r-- | cura/Settings/ContainerManager.py | 148 | ||||
-rw-r--r-- | cura/Settings/MachineManager.py | 105 |
3 files changed, 278 insertions, 107 deletions
diff --git a/cura/QualityManager.py b/cura/QualityManager.py new file mode 100644 index 0000000000..9d7eac8a05 --- /dev/null +++ b/cura/QualityManager.py @@ -0,0 +1,132 @@ +# Copyright (c) 2016 Ultimaker B.V.
+# Cura is released under the terms of the AGPLv3 or higher.
+
+import UM.Application
+import cura.Settings.ExtruderManager
+import UM.Settings.ContainerRegistry
+
+# This collects a lot of quality and quality changes related code which was split between ContainerManager
+# and the MachineManager and really needs to usable from both.
+
+class QualityManager:
+
+ ## Get the singleton instance for this class.
+ @classmethod
+ def getInstance(cls):
+ # Note: Explicit use of class name to prevent issues with inheritance.
+ if QualityManager.__instance is None:
+ QualityManager.__instance = cls()
+ return QualityManager.__instance
+
+ __instance = None
+
+ ## Find a quality by name for a specific machine definition and materials.
+ #
+ # \param quality_name
+ # \param machine_definition (Optional) \type{ContainerInstance} If nothing is
+ # specified then the currently selected machine definition is used.
+ # \param material_containers (Optional) \type{List[ContainerInstance]} If nothing is specified then
+ # the current set of selected materials is used.
+ # \return the matching quality containers \type{List[ContainerInstance]}
+ def findQualityByName(self, quality_name, machine_definition=None, material_containers=None):
+ criteria = {"type": "quality", "name": quality_name}
+ return self._getFilteredContainersForStack(machine_definition, material_containers, **criteria)
+
+ ## Find a quality changes container by name.
+ #
+ # \param quality_changes_name \type{str} the name of the quality changes container.
+ # \param machine_definition (Optional) \type{ContainerInstance} If nothing is
+ # specified then the currently selected machine definition is used.
+ # \param material_containers (Optional) \type{List[ContainerInstance]} If nothing is specified then
+ # the current set of selected materials is used.
+ # \return the matching quality changes containers \type{List[ContainerInstance]}
+ def findQualityChangesByName(self, quality_changes_name, machine_definition=None, material_containers=None):
+ criteria = {"type": "quality_changes", "name": quality_changes_name}
+ return self._getFilteredContainersForStack(machine_definition, material_containers, **criteria)
+
+ ## Find a quality container by quality type.
+ #
+ # \param quality_type \type{str} the name of the quality type to search for.
+ # \param machine_definition (Optional) \type{ContainerInstance} If nothing is
+ # specified then the currently selected machine definition is used.
+ # \param material_containers (Optional) \type{List[ContainerInstance]} If nothing is specified then
+ # the current set of selected materials is used.
+ # \return the matching quality containers \type{List[ContainerInstance]}
+ def findQualityByQualityType(self, quality_type, machine_definition=None, material_containers=None):
+ criteria = {"type": "quality", "quality_type": quality_type}
+ return self._getFilteredContainersForStack(machine_definition, material_containers, **criteria)
+
+ def _getFilteredContainers(self, **kwargs):
+ return self._getFilteredContainersForStack(None, None **kwargs)
+
+ def _getFilteredContainersForStack(self, machine_definition=None, material_containers=None, **kwargs):
+ # Fill in any default values.
+ if machine_definition is None:
+ machine_definition = UM.Application.getInstance().getGlobalContainerStack().getBottom()
+ quality_definition = machine_definition.getMetaDataEntry("quality_definition")
+ if quality_definition is not None:
+ machine_definition = UM.Settings.ContainerRegistry.getInstance().findDefinitionContainers(id=quality_definition)[0]
+
+ machine_definition = self.getParentMachineDefinition(machine_definition)
+
+ if material_containers is None:
+ active_stacks = cura.Settings.ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
+ material_containers = [stack.findContainer(type="material") for stack in active_stacks]
+
+ criteria = kwargs
+ filter_by_material = False
+
+ if machine_definition.getMetaDataEntry("has_machine_quality"):
+ definition_id = machine_definition.getMetaDataEntry("quality_definition", machine_definition.getId())
+ criteria["definition"] = definition_id
+
+ filter_by_material = machine_definition.getMetaDataEntry("has_materials")
+
+ # Stick the material IDs in a set
+ if material_containers is None:
+ filter_by_material = False
+ else:
+ material_ids = set()
+ for material_instance in material_containers:
+ material_ids.add(material_instance.getId())
+
+ containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**criteria)
+
+ result = []
+ 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
+ result.append(container)
+ return result
+
+ ## Get the parent machine definition of a machine definition.
+ #
+ # \param machine_definition \type{DefinitionContainer} This may be a normal machine definition or
+ # an extruder definition.
+ # \return \type{DefinitionContainer} the parent machine definition. If the given machine
+ # definition doesn't have a parent then it is simply returned.
+ def getParentMachineDefinition(self, machine_definition):
+ container_registry = UM.Settings.ContainerRegistry.getInstance()
+
+ machine_entry = machine_definition.getMetaDataEntry("machine")
+ if machine_entry is None:
+ # We have a normal (whole) machine defintion
+ quality_definition = machine_definition.getMetaDataEntry("quality_definition")
+ if quality_definition is not None:
+ parent_machine_definition = container_registry.findDefinitionContainers(id=quality_definition)[0]
+ return self.getParentMachineDefinition(parent_machine_definition)
+ else:
+ return machine_definition
+ else:
+ # This looks like an extruder. Find the rest of the machine.
+ whole_machine = container_registry.findDefinitionContainers(id=machine_entry)[0]
+ parent_machine = self.getParentMachineDefinition(whole_machine)
+ if whole_machine is parent_machine:
+ # This extruder already belongs to a 'parent' machine def.
+ return machine_definition
+ else:
+ # Look up the corresponding extruder definition in the parent machine definition.
+ extruder_position = machine_definition.getMetaDataEntry("position")
+ parent_extruder_id = parent_machine.getMetaDataEntry("machine_extruder_trains")[extruder_position]
+ return container_registry.findDefinitionContainers(id=parent_extruder_id)[0]
diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index e3a0d22299..a0648587c2 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -15,6 +15,7 @@ import UM.MimeTypeDatabase import UM.Logger import cura.Settings +from cura.QualityManager import QualityManager from UM.MimeTypeDatabase import MimeTypeNotFoundError @@ -468,7 +469,7 @@ class ContainerManager(QObject): 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()) + new_changes = self._createQualityChanges(quality_container, unique_name, stack.getId(), UM.Application.getInstance().getGlobalContainerStack()) self._performMerge(new_changes, user_container) self._container_registry.addContainer(new_changes) @@ -566,33 +567,80 @@ class ContainerManager(QObject): global_stack = UM.Application.getInstance().getGlobalContainerStack() if not global_stack or not quality_name: return "" + machine_definition = global_stack.getBottom() + + active_stacks = cura.Settings.ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() + material_containers = [stack.findContainer(type="material") for stack in active_stacks] + + result = self._duplicateQualityOrQualityChangesForMachineType(quality_name, base_name, + QualityManager.getInstance().getParentMachineDefinition(machine_definition), + material_containers) + return result[0].getName() if result else "" + + ## Duplicate a quality or quality changes profile specific to a machine type + # + # \param quality_name \type{str} the name of the quality or quality changes container to duplicate. + # \param base_name \type{str} the desired name for the new container. + # \param machine_definition \type{DefinitionContainer} + # \param material_instances \type{List[InstanceContainer]} + # \return \type{str} the name of the newly created container. + def _duplicateQualityOrQualityChangesForMachineType(self, quality_name, base_name, machine_definition, material_instances): UM.Logger.log("d", "Attempting to duplicate the quality %s", quality_name) - containers = self._container_registry.findInstanceContainers(name = quality_name) - if not containers: + + # Try Quality + containers = QualityManager.getInstance().findQualityByName(quality_name, machine_definition, material_instances) + if containers: + container = containers[0] + if base_name is None: + base_name = quality_name + return self._duplicateQualityForMachineType(container, base_name, machine_definition) + + # Try quality changes. + containers = QualityManager.getInstance().findQualityChangesByName(quality_name, machine_definition, material_instances) + if containers: + container = containers[0] + if base_name is None: + base_name = quality_name + return self._duplicateQualityChangesForMachineType(container, base_name, machine_definition) + else: UM.Logger.log("d", "Unable to duplicate the quality %s, because it doesn't exist.", quality_name) return "" + # Duplicate a quality profile + def _duplicateQualityForMachineType(self, quality_container, base_name, machine_definition): if base_name is None: - base_name = quality_name - + base_name = quality_container.getName() new_name = self._container_registry.uniqueName(base_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()) - self._container_registry.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) - self._container_registry.addContainer(new_container) - else: - UM.Logger.log("w", "Unable to duplicate profile. It has the wrong type.") - return "" + new_change_instances = [] + + # Handle the global stack first. + new_changes = self._createQualityChanges(quality_container, new_name, machine_definition, None) + new_change_instances.append(new_changes) + self._container_registry.addContainer(new_changes) + + # Handle the extruders if present. + extruders = machine_definition.getMetaDataEntry("machine_extruder_trains") + if extruders: + for key in extruders: + value = extruders[key] + new_changes = self._createQualityChanges(quality_container, new_name, machine_definition, value) + new_change_instances.append(new_changes) + self._container_registry.addContainer(new_changes) + + return new_change_instances + + # Duplicate a quality changes container + def _duplicateQualityChangesForMachineType(self, quality_changes_container, base_name, machine_definition): + new_change_instances = [] + for container in QualityManager.getInstance().findQualityChangesByName(quality_changes_container.getName(), + machine_definition, None): + new_unique_id = self._createUniqueId(container.getId(), base_name) + new_container = container.duplicate(new_unique_id, base_name) + new_change_instances.append(new_container) + self._container_registry.addContainer(new_container) - return new_name + return new_change_instances @pyqtSlot(str, result = str) def duplicateMaterial(self, material_id): @@ -677,39 +725,13 @@ class ContainerManager(QObject): name_filter = "{0} ({1})".format(mime_type.comment, suffix_list) self._container_name_filters[name_filter] = entry - ## Return a generator that iterates over a set of containers that are filtered by machine and material when needed. + ## Get containers filtered by machine type and material if required. # # \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. + # \return A 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"): - definition = global_stack.getBottom() - definition_id = definition.getMetaDataEntry("quality_definition", definition.getId()) - criteria["definition"] = definition_id - - 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 = self._container_registry.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 + return QualityManager.getInstance()._getFilteredContainers(**kwargs) ## Creates a unique ID for a container by prefixing the name with the stack ID. # @@ -731,34 +753,26 @@ class ContainerManager(QObject): # # \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. + # \param machine_definition The machine definition this quality changes container is specific to. + # \param extruder_id # # \return A new quality_changes container with the specified container as base. - def _createQualityChanges(self, quality_container, new_name, stack_id): - global_stack = UM.Application.getInstance().getGlobalContainerStack() - assert global_stack is not None + def _createQualityChanges(self, quality_container, new_name, machine_definition, extruder_id): + base_id = machine_definition.getId() if extruder_id is None else extruder_id # Create a new quality_changes container for the quality. - quality_changes = UM.Settings.InstanceContainer(self._createUniqueId(stack_id, new_name)) + quality_changes = UM.Settings.InstanceContainer(self._createUniqueId(base_id, new_name)) quality_changes.setName(new_name) quality_changes.addMetaDataEntry("type", "quality_changes") - quality_changes.addMetaDataEntry("quality", quality_container.getMetaDataEntry("quality_type")) + quality_changes.addMetaDataEntry("quality_type", quality_container.getMetaDataEntry("quality_type")) # If we are creating a container for an extruder, ensure we add that to the container - if stack_id != global_stack.getId(): - quality_changes.addMetaDataEntry("extruder", stack_id) + if extruder_id is not None: + quality_changes.addMetaDataEntry("extruder", extruder_id) # If the machine specifies qualities should be filtered, ensure we match the current criteria. - if not global_stack.getMetaDataEntry("has_machine_quality"): + if not machine_definition.getMetaDataEntry("has_machine_quality"): quality_changes.setDefinition(self._container_registry.findContainers(id = "fdmprinter")[0]) else: - definition = global_stack.getBottom() - definition_id = definition.getMetaDataEntry("quality_definition", definition.getId()) - definition = self._container_registry.findContainers(id=definition_id)[0] - quality_changes.setDefinition(definition) - - if global_stack.getMetaDataEntry("has_materials"): - material = quality_container.getMetaDataEntry("material") - quality_changes.addMetaDataEntry("material", material) - + quality_changes.setDefinition(QualityManager.getInstance().getParentMachineDefinition(machine_definition)) return quality_changes diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 450e7606b9..451be66862 100644 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -12,6 +12,7 @@ from UM.Settings.SettingRelation import RelationType import UM.Settings +from cura.QualityManager import QualityManager from cura.PrinterOutputDevice import PrinterOutputDevice from . import ExtruderManager @@ -535,10 +536,10 @@ class MachineManager(QObject): if not old_material: Logger.log("w", "While trying to set the active material, no material was found to replace it.") return - if (old_quality_changes.getId() == "empty_quality_changes" or #Don't want the empty one. - old_quality_changes.getMetaDataEntry("material") != material_id): # The quality change is based off a different material; the quality change is probably a custom quality. + if old_quality_changes.getId() == "empty_quality_changes": old_quality_changes = None + self.blurSettings.emit() old_material.nameChanged.disconnect(self._onMaterialNameChanged) @@ -596,22 +597,26 @@ class MachineManager(QObject): Logger.log("d", "Attempting to change the active quality to %s", quality_id) self.blurSettings.emit() - quality_container = None + quality_changes_container = self._empty_quality_changes_container + # Quality profile come in two flavours: type=quality and type=quality_changes + # If we found a quality_changes profile then look up its parent quality profile. container_type = containers[0].getMetaDataEntry("type") # Get quality container and optionally the quality_changes container. 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")) + # Find a suitable quality container to match this quality changes container. + containers = QualityManager.getInstance().findQualityByQualityType(quality_changes_container.getMetaDataEntry("quality_type")) 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 @@ -620,25 +625,33 @@ class MachineManager(QObject): if not quality_type: quality_type = quality_changes_container.getName() - # Find suitable quality containers (by quality_type) for each stack and swap out the container. + # Find and swap in the quality changes containers for the global stack and each extruder stack. stacks = list(ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()) name_changed_connect_stacks = [] # Connect these stacks to the name changed callback - for stack in stacks: - extruder_id = stack.getId() if stack != self._global_container_stack else None - - criteria = { "quality_type": quality_type, "extruder": extruder_id } + for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks(): + if stack != self._global_container_stack: + # Must be an extruder stack. Use the ID of the extruders as specified by the machine definition. + extruder_id = QualityManager.getInstance().getParentMachineDefinition(stack.getBottom()).getId() + else: + extruder_id = None + criteria = { "quality_type": quality_type, "type": "quality", "extruder": extruder_id } + # Filter candidate quality containers by the current material in the stack. material = stack.findContainer(type = "material") if material and material is not self._empty_material_container: criteria["material"] = material.getId() + # Apply a filter when machines have their own specific quality profiles. if self._global_container_stack.getMetaDataEntry("has_machine_quality"): criteria["definition"] = self.activeQualityDefinitionId else: criteria["definition"] = "fdmprinter" + # Find the new quality container. stack_quality = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**criteria) if not stack_quality: + # Search again, except this time drop any extruder requirement. We should now get + # the same quality container as the one needed for the global stack. criteria.pop("extruder") stack_quality = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**criteria) if not stack_quality: @@ -648,28 +661,21 @@ class MachineManager(QObject): else: stack_quality = stack_quality[0] + # Find the new quality changes if one has been specified. 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] + changes = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers( + type="quality_changes", + quality_type=stack_quality.getMetaDataEntry("quality_type"), + name = quality_changes_container.getName(), extruder = extruder_id) + stack_quality_changes = changes[0] else: + # This is case of quality container and the no-op quality changes container. stack_quality_changes = self._empty_quality_changes_container - old_quality = stack.findContainer(type = "quality") - if old_quality: - old_quality.nameChanged.disconnect(self._onQualityNameChanged) - else: - Logger.log("w", "Could not find old quality while changing active quality.") - - old_changes = stack.findContainer(type = "quality_changes") - if old_changes: - old_changes.nameChanged.disconnect(self._onQualityNameChanged) - else: - Logger.log("w", "Could not find old quality_changes while changing active quality.") - - stack.replaceContainer(stack.getContainerIndex(old_quality), stack_quality, postpone_emit = True) - stack.replaceContainer(stack.getContainerIndex(old_changes), stack_quality_changes, postpone_emit = True) - name_changed_connect_stacks.append(stack_quality) name_changed_connect_stacks.append(stack_quality_changes) + self._replaceQualityOrQualityChangesInStack(stack, stack_quality, postpone_emit = True) + self._replaceQualityOrQualityChangesInStack(stack, stack_quality_changes, postpone_emit = True) # Send emits that are postponed in replaceContainer. # Here the stacks are finished replacing and every value can be resolved based on the current state. @@ -680,19 +686,41 @@ class MachineManager(QObject): stack.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 - - 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) + self._askUserToKeepOrClearCurrentSettings() self.activeQualityChanged.emit() + def _replaceQualityOrQualityChangesInStack(self, stack, container, postpone_emit = False): + # Disconnect the signal handling from the old container. + old_container = stack.findContainer(type=container.getMetaDataEntry("type")) + if old_container: + old_container.nameChanged.disconnect(self._onQualityNameChanged) + else: + Logger.log("w", "Could not find old "+ container.getMetaDataEntry("type") + " while changing active " + container.getMetaDataEntry("type") + ".") + + # Swap in the new container into the stack. + stack.replaceContainer(stack.getContainerIndex(old_container), container, postpone_emit = postpone_emit) + + # Attach the needed signal handling. + container.nameChanged.connect(self._onQualityNameChanged) + + def _askUserToKeepOrClearCurrentSettings(self): + # 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) + def _keepUserSettingsDialogCallback(self, button): if button == QMessageBox.Yes: # Yes, keep the settings in the user profile with this profile @@ -746,10 +774,7 @@ class MachineManager(QObject): # \param definition (DefinitionContainer) machine definition # \returns DefinitionID (string) if found, empty string otherwise def getQualityDefinitionId(self, definition): - definition_id = definition.getMetaDataEntry("quality_definition") - if not definition_id: - definition_id = definition.getId() - return definition_id + return QualityManager.getInstance().getParentMachineDefinition(definition).getId() ## Get the Variant ID to use to select quality profiles for the currently active variant # \returns VariantID (string) if found, empty string otherwise |