diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 93e781cd5e..9e17ce028d 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -41,7 +41,7 @@ class ExtruderManager(QObject): def __init__(self, parent = None): super().__init__(parent) self._extruder_trains = { } #Per machine, a dictionary of extruder container stack IDs. Only for separately defined extruders. - self._active_extruder_index = 0 + self._active_extruder_index = -1 # Indicates the index of the active extruder stack. -1 means no active extruder stack self._selected_object_extruders = [] Application.getInstance().globalContainerStackChanged.connect(self.__globalContainerStackChanged) self._global_container_stack_definition_id = None @@ -74,14 +74,18 @@ class ExtruderManager(QObject): except KeyError: return 0 + ## Gets a dict with the extruder stack ids with the extruder number as the key. + # The key "-1" indicates the global stack id. + # @pyqtProperty("QVariantMap", notify = extrudersChanged) def extruderIds(self): - map = {} + extruder_stack_ids = {} global_stack_id = Application.getInstance().getGlobalContainerStack().getId() + extruder_stack_ids["-1"] = global_stack_id if global_stack_id in self._extruder_trains: for position in self._extruder_trains[global_stack_id]: - map[position] = self._extruder_trains[global_stack_id][position].getId() - return map + extruder_stack_ids[position] = self._extruder_trains[global_stack_id][position].getId() + return extruder_stack_ids @pyqtSlot(str, result = str) def getQualityChangesIdByExtruderStackId(self, id: str) -> str: @@ -513,17 +517,33 @@ class ExtruderManager(QObject): global_stack = Application.getInstance().getGlobalContainerStack() result = [] + machine_extruder_count = global_stack.getProperty("machine_extruder_count", "value") + + # In case the printer is using one extruder, shouldn't exist active extruder stacks + if machine_extruder_count == 1: + return result + if global_stack and global_stack.getId() in self._extruder_trains: for extruder in sorted(self._extruder_trains[global_stack.getId()]): result.append(self._extruder_trains[global_stack.getId()][extruder]) - return result[:global_stack.getProperty("machine_extruder_count", "value")] + return result[:machine_extruder_count] def __globalContainerStackChanged(self) -> None: global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack and global_container_stack.getBottom() and global_container_stack.getBottom().getId() != self._global_container_stack_definition_id: self._global_container_stack_definition_id = global_container_stack.getBottom().getId() self.globalContainerStackDefinitionChanged.emit() + + # If the global container changed, the number of extruders could be changed and so the active_extruder_index is updated + extruder_count = global_container_stack.getProperty("machine_extruder_count", "value") + if extruder_count > 1: + if self._active_extruder_index == -1: + self.setActiveExtruderIndex(0) + else: + if self._active_extruder_index != -1: + self.setActiveExtruderIndex(-1) + self.activeExtruderChanged.emit() self.resetSelectedObjectExtruders() diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 4a321d0529..82edebec60 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -271,7 +271,6 @@ class MachineManager(QObject): extruder_stack.containersChanged.disconnect(self._onInstanceContainersChanged) self._global_container_stack = Application.getInstance().getGlobalContainerStack() - self._active_container_stack = self._global_container_stack self.globalContainerChanged.emit() @@ -303,6 +302,9 @@ class MachineManager(QObject): quality = self._global_container_stack.quality quality.nameChanged.connect(self._onQualityNameChanged) + + self._active_container_stack = self._global_container_stack + self._error_check_timer.start() ## Update self._stacks_valid according to _checkStacksForErrors and emit if change. @@ -543,16 +545,22 @@ class MachineManager(QObject): return result + ## Gets a dict with the active materials ids set in all extruder stacks and the global stack + # (when there is one extruder, the material is set in the global stack) + # + # \return The material ids in all stacks @pyqtProperty("QVariantMap", notify = activeMaterialChanged) def allActiveMaterialIds(self) -> Dict[str, str]: result = {} active_stacks = ExtruderManager.getInstance().getActiveExtruderStacks() - if active_stacks is not None: #If we have a global stack. + + result[self._global_container_stack.getId()] = self._global_container_stack.material.getId() + + if active_stacks is not None: # If we have extruder stacks for stack in active_stacks: material_container = stack.material if not material_container: continue - result[stack.getId()] = material_container.getId() return result diff --git a/cura/Settings/ProfilesModel.py b/cura/Settings/ProfilesModel.py index 2600645b91..331292e255 100644 --- a/cura/Settings/ProfilesModel.py +++ b/cura/Settings/ProfilesModel.py @@ -98,16 +98,10 @@ class ProfilesModel(InstanceContainersModel): extruder_manager = ExtruderManager.getInstance() active_extruder = extruder_manager.getActiveExtruderStack() extruder_stacks = extruder_manager.getActiveExtruderStacks() - if extruder_stacks: - if multiple_extrusion: - # Place the active extruder at the front of the list. - if active_extruder in extruder_stacks: - extruder_stacks.remove(active_extruder) - extruder_stacks = [active_extruder] + extruder_stacks - else: - # The active extruder is the first in the list and only the active extruder is use to compute the usable qualities - active_extruder = None - extruder_stacks = [] + if multiple_extrusion: + # Place the active extruder at the front of the list. + extruder_stacks.remove(active_extruder) + extruder_stacks = [active_extruder] + extruder_stacks # Get a list of usable/available qualities for this machine and material qualities = QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_container_stack, diff --git a/cura/Settings/QualityAndUserProfilesModel.py b/cura/Settings/QualityAndUserProfilesModel.py index 6b898b4934..51b4c1bb1a 100644 --- a/cura/Settings/QualityAndUserProfilesModel.py +++ b/cura/Settings/QualityAndUserProfilesModel.py @@ -31,16 +31,10 @@ class QualityAndUserProfilesModel(ProfilesModel): extruder_manager = ExtruderManager.getInstance() active_extruder = extruder_manager.getActiveExtruderStack() extruder_stacks = extruder_manager.getActiveExtruderStacks() - if extruder_stacks: - if multiple_extrusion: - # Place the active extruder at the front of the list. - if active_extruder in extruder_stacks: - extruder_stacks.remove(active_extruder) - extruder_stacks = [active_extruder] + extruder_stacks - else: - # The active extruder is the first in the list and only the active extruder is use to compute the usable qualities - active_extruder = None - extruder_stacks = [] + if multiple_extrusion: + # Place the active extruder at the front of the list. + extruder_stacks.remove(active_extruder) + extruder_stacks = [active_extruder] + extruder_stacks # Fetch the list of useable qualities across all extruders. # The actual list of quality profiles come from the first extruder in the extruder list. @@ -52,9 +46,13 @@ class QualityAndUserProfilesModel(ProfilesModel): if multiple_extrusion: # If the printer has multiple extruders then quality changes related to the current extruder are kept - filtered_quality_changes = [qc for qc in quality_changes_list if qc.getMetaDataEntry("quality_type") in quality_type_set and qc.getMetaDataEntry("extruder") == active_extruder.definition.getId()] + filtered_quality_changes = [qc for qc in quality_changes_list if qc.getMetaDataEntry("quality_type") in quality_type_set and + qc.getMetaDataEntry("extruder") is not None and + qc.getMetaDataEntry("extruder") == active_extruder.definition.getMetaDataEntry("quality_definition") or + qc.getMetaDataEntry("extruder") == active_extruder.definition.getId()] else: # If not, the quality changes of the global stack are selected - filtered_quality_changes = [qc for qc in quality_changes_list if qc.getMetaDataEntry("quality_type") in quality_type_set and qc.getMetaDataEntry("extruder") is None] + filtered_quality_changes = [qc for qc in quality_changes_list if qc.getMetaDataEntry("quality_type") in quality_type_set and + qc.getMetaDataEntry("extruder") is None] return quality_list + filtered_quality_changes diff --git a/cura/Settings/UserProfilesModel.py b/cura/Settings/UserProfilesModel.py index 1979a59e27..b9710f7df2 100644 --- a/cura/Settings/UserProfilesModel.py +++ b/cura/Settings/UserProfilesModel.py @@ -31,16 +31,10 @@ class UserProfilesModel(ProfilesModel): extruder_manager = ExtruderManager.getInstance() active_extruder = extruder_manager.getActiveExtruderStack() extruder_stacks = extruder_manager.getActiveExtruderStacks() - if extruder_stacks: - if multiple_extrusion: - # Place the active extruder at the front of the list. - if active_extruder in extruder_stacks: - extruder_stacks.remove(active_extruder) - extruder_stacks = [active_extruder] + extruder_stacks - else: - # The active extruder is the first in the list and only the active extruder is use to compute the usable qualities - active_extruder = None - extruder_stacks = [] + if multiple_extrusion: + # Place the active extruder at the front of the list. + extruder_stacks.remove(active_extruder) + extruder_stacks = [active_extruder] + extruder_stacks # Fetch the list of useable qualities across all extruders. # The actual list of quality profiles come from the first extruder in the extruder list. @@ -52,9 +46,13 @@ class UserProfilesModel(ProfilesModel): if multiple_extrusion: # If the printer has multiple extruders then quality changes related to the current extruder are kept - filtered_quality_changes = [qc for qc in quality_changes_list if qc.getMetaDataEntry("quality_type") in quality_type_set and qc.getMetaDataEntry("extruder") == active_extruder.definition.getId()] + filtered_quality_changes = [qc for qc in quality_changes_list if qc.getMetaDataEntry("quality_type") in quality_type_set and + qc.getMetaDataEntry("extruder") is not None and + qc.getMetaDataEntry("extruder") == active_extruder.definition.getMetaDataEntry("quality_definition") or + qc.getMetaDataEntry("extruder") == active_extruder.definition.getId()] else: # If not, the quality changes of the global stack are selected - filtered_quality_changes = [qc for qc in quality_changes_list if qc.getMetaDataEntry("quality_type") in quality_type_set and qc.getMetaDataEntry("extruder") is None] + filtered_quality_changes = [qc for qc in quality_changes_list if qc.getMetaDataEntry("quality_type") in quality_type_set and + qc.getMetaDataEntry("extruder") is None] return filtered_quality_changes diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index f37fd54680..22a9291acb 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -693,54 +693,53 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # -- # load extruder stack files - if extruder_count_from_global_stack > 1: - try: - for extruder_stack_file in extruder_stack_files: - container_id = self._stripFileToId(extruder_stack_file) - extruder_file_content = archive.open(extruder_stack_file, "r").read().decode("utf-8") + try: + for extruder_stack_file in extruder_stack_files: + container_id = self._stripFileToId(extruder_stack_file) + extruder_file_content = archive.open(extruder_stack_file, "r").read().decode("utf-8") - if self._resolve_strategies["machine"] == "override": - # deserialize new extruder stack over the current ones - stack = self._overrideExtruderStack(global_stack, extruder_file_content) + if self._resolve_strategies["machine"] == "override": + # deserialize new extruder stack over the current ones + stack = self._overrideExtruderStack(global_stack, extruder_file_content) - elif self._resolve_strategies["machine"] == "new": - new_id = extruder_stack_id_map[container_id] - stack = ExtruderStack(new_id) + elif self._resolve_strategies["machine"] == "new": + new_id = extruder_stack_id_map[container_id] + stack = ExtruderStack(new_id) - # HACK: the global stack can have a new name, so we need to make sure that this extruder stack - # references to the new name instead of the old one. Normally, this can be done after - # deserialize() by setting the metadata, but in the case of ExtruderStack, deserialize() - # also does addExtruder() to its machine stack, so we have to make sure that it's pointing - # to the right machine BEFORE deserialization. - extruder_config = configparser.ConfigParser() - extruder_config.read_string(extruder_file_content) - extruder_config.set("metadata", "machine", global_stack_id_new) - tmp_string_io = io.StringIO() - extruder_config.write(tmp_string_io) - extruder_file_content = tmp_string_io.getvalue() + # HACK: the global stack can have a new name, so we need to make sure that this extruder stack + # references to the new name instead of the old one. Normally, this can be done after + # deserialize() by setting the metadata, but in the case of ExtruderStack, deserialize() + # also does addExtruder() to its machine stack, so we have to make sure that it's pointing + # to the right machine BEFORE deserialization. + extruder_config = configparser.ConfigParser() + extruder_config.read_string(extruder_file_content) + extruder_config.set("metadata", "machine", global_stack_id_new) + tmp_string_io = io.StringIO() + extruder_config.write(tmp_string_io) + extruder_file_content = tmp_string_io.getvalue() - stack.deserialize(extruder_file_content) + stack.deserialize(extruder_file_content) - # Ensure a unique ID and name - stack._id = new_id + # Ensure a unique ID and name + stack._id = new_id - self._container_registry.addContainer(stack) - extruder_stacks_added.append(stack) - containers_added.append(stack) - else: - Logger.log("w", "Unknown resolve strategy: %s", self._resolve_strategies["machine"]) + self._container_registry.addContainer(stack) + extruder_stacks_added.append(stack) + containers_added.append(stack) + else: + Logger.log("w", "Unknown resolve strategy: %s", self._resolve_strategies["machine"]) - # Create a new definition_changes container if it was empty - if stack.definitionChanges == self._container_registry.getEmptyInstanceContainer(): - stack.setDefinitionChanges(CuraStackBuilder.createDefinitionChangesContainer(stack, stack._id + "_settings")) + # Create a new definition_changes container if it was empty + if stack.definitionChanges == self._container_registry.getEmptyInstanceContainer(): + stack.setDefinitionChanges(CuraStackBuilder.createDefinitionChangesContainer(stack, stack._id + "_settings")) - extruder_stacks.append(stack) - except: - Logger.logException("w", "We failed to serialize the stack. Trying to clean up.") - # Something went really wrong. Try to remove any data that we added. - for container in containers_added: - self._container_registry.removeContainer(container.getId()) - return + extruder_stacks.append(stack) + except: + Logger.logException("w", "We failed to serialize the stack. Trying to clean up.") + # Something went really wrong. Try to remove any data that we added. + for container in containers_added: + self._container_registry.removeContainer(container.getId()) + return # # Replacing the old containers if resolve is "new". diff --git a/plugins/MachineSettingsAction/MachineSettingsAction.py b/plugins/MachineSettingsAction/MachineSettingsAction.py index 2c7e7c1fc0..80258ef548 100755 --- a/plugins/MachineSettingsAction/MachineSettingsAction.py +++ b/plugins/MachineSettingsAction/MachineSettingsAction.py @@ -112,13 +112,7 @@ class MachineSettingsAction(MachineAction): if not self._global_container_stack: return 0 - # If there is a printer that originally is multi-extruder, it's not allowed to change the number of extruders - # It's just allowed in case of Custom FDM printers - definition_container = self._global_container_stack.getBottom() - if definition_container.getId() == "custom": - return len(self._global_container_stack.getMetaDataEntry("machine_extruder_trains")) - return 0 - + return len(self._global_container_stack.getMetaDataEntry("machine_extruder_trains")) @pyqtSlot(int) def setMachineExtruderCount(self, extruder_count): @@ -176,7 +170,6 @@ class MachineSettingsAction(MachineAction): node.callDecoration("setActiveExtruder", extruder_manager.getExtruderStack(extruder_count - 1).getId()) definition_changes_container.setProperty("machine_extruder_count", "value", extruder_count) - self.forceUpdate() if extruder_count > 1: # Multiextrusion @@ -221,6 +214,8 @@ class MachineSettingsAction(MachineAction): preferences.setValue("cura/choice_on_profile_override", choice_on_profile_override) + self.forceUpdate() + @pyqtSlot() def forceUpdate(self): diff --git a/resources/extruders/ultimaker3_extended_extruder_left.def.json b/resources/extruders/ultimaker3_extended_extruder_left.def.json index 2fcf1d015a..2d81424bc6 100644 --- a/resources/extruders/ultimaker3_extended_extruder_left.def.json +++ b/resources/extruders/ultimaker3_extended_extruder_left.def.json @@ -5,7 +5,8 @@ "inherits": "fdmextruder", "metadata": { "machine": "ultimaker3_extended", - "position": "0" + "position": "0", + "quality_definition": "ultimaker3_extruder_left" }, "overrides": { diff --git a/resources/extruders/ultimaker3_extended_extruder_right.def.json b/resources/extruders/ultimaker3_extended_extruder_right.def.json index b60cc82dd7..7cdd5876c1 100644 --- a/resources/extruders/ultimaker3_extended_extruder_right.def.json +++ b/resources/extruders/ultimaker3_extended_extruder_right.def.json @@ -5,7 +5,8 @@ "inherits": "fdmextruder", "metadata": { "machine": "ultimaker3_extended", - "position": "1" + "position": "1", + "quality_definition": "ultimaker3_extruder_right" }, "overrides": { diff --git a/resources/extruders/ultimaker3_extruder_left.def.json b/resources/extruders/ultimaker3_extruder_left.def.json index 6f07718b63..9f5ed34692 100644 --- a/resources/extruders/ultimaker3_extruder_left.def.json +++ b/resources/extruders/ultimaker3_extruder_left.def.json @@ -5,7 +5,8 @@ "inherits": "fdmextruder", "metadata": { "machine": "ultimaker3", - "position": "0" + "position": "0", + "quality_definition": "ultimaker3_extruder_left" }, "overrides": { diff --git a/resources/extruders/ultimaker3_extruder_right.def.json b/resources/extruders/ultimaker3_extruder_right.def.json index bc51b0da4b..7298a552b7 100644 --- a/resources/extruders/ultimaker3_extruder_right.def.json +++ b/resources/extruders/ultimaker3_extruder_right.def.json @@ -5,7 +5,8 @@ "inherits": "fdmextruder", "metadata": { "machine": "ultimaker3", - "position": "1" + "position": "1", + "quality_definition": "ultimaker3_extruder_right" }, "overrides": { diff --git a/resources/qml/Menus/MaterialMenu.qml b/resources/qml/Menus/MaterialMenu.qml index 00def7d063..1688bc228a 100644 --- a/resources/qml/Menus/MaterialMenu.qml +++ b/resources/qml/Menus/MaterialMenu.qml @@ -39,11 +39,8 @@ Menu visible: printerConnected && Cura.MachineManager.printerOutputDevices[0].materialNames.length > extruderIndex onTriggered: { - var activeExtruderIndex = ExtruderManager.activeExtruderIndex; - ExtruderManager.setActiveExtruderIndex(extruderIndex); var materialId = Cura.MachineManager.printerOutputDevices[0].materialIds[extruderIndex]; var items = materialsModel.items; - // materialsModel.find cannot be used because we need to look inside the metadata property of items for(var i in items) { if (items[i]["metadata"]["GUID"] == materialId) @@ -52,7 +49,6 @@ Menu break; } } - ExtruderManager.setActiveExtruderIndex(activeExtruderIndex); } } @@ -72,6 +68,8 @@ Menu exclusiveGroup: group onTriggered: { + // This workaround is done because of the application menus for materials and variants for multiextrusion printers. + // The extruder menu would always act on the correspoding extruder only, instead of acting on the extruder selected in the UI. var activeExtruderIndex = ExtruderManager.activeExtruderIndex; ExtruderManager.setActiveExtruderIndex(extruderIndex); Cura.MachineManager.setActiveMaterial(model.id); @@ -113,6 +111,8 @@ Menu exclusiveGroup: group onTriggered: { + // This workaround is done because of the application menus for materials and variants for multiextrusion printers. + // The extruder menu would always act on the correspoding extruder only, instead of acting on the extruder selected in the UI. var activeExtruderIndex = ExtruderManager.activeExtruderIndex; ExtruderManager.setActiveExtruderIndex(extruderIndex); Cura.MachineManager.setActiveMaterial(model.id); diff --git a/resources/qml/SidebarHeader.qml b/resources/qml/SidebarHeader.qml index 9bc18b32d9..d20feca8d6 100644 --- a/resources/qml/SidebarHeader.qml +++ b/resources/qml/SidebarHeader.qml @@ -91,12 +91,7 @@ Column Connections { target: Cura.MachineManager - onGlobalContainerChanged: - { - forceActiveFocus() // Changing focus applies the currently-being-typed values so it can change the displayed setting values. - var extruder_index = (machineExtruderCount.properties.value == 1) ? -1 : 0 - ExtruderManager.setActiveExtruderIndex(extruder_index); - } + onGlobalContainerChanged: forceActiveFocus() // Changing focus applies the currently-being-typed values so it can change the displayed setting values. } delegate: Button diff --git a/resources/qml/SidebarSimple.qml b/resources/qml/SidebarSimple.qml index eba4ad48cd..0a80ae9339 100644 --- a/resources/qml/SidebarSimple.qml +++ b/resources/qml/SidebarSimple.qml @@ -66,6 +66,7 @@ Item { target: Cura.MachineManager onActiveQualityChanged: qualityModel.update() + onActiveMaterialChanged: qualityModel.update() } ListModel