diff --git a/.gitignore b/.gitignore index 570c932d28..ac1e8eba92 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,7 @@ plugins/ProfileFlattener plugins/cura-god-mode-plugin plugins/cura-big-flame-graph plugins/cura-siemensnx-plugin +plugins/CuraVariSlicePlugin #Build stuff CMakeCache.txt diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index e87bfebd94..50f63e49d5 100755 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -876,15 +876,6 @@ class BuildVolume(SceneNode): return result - ## Private convenience function to get a setting from the adhesion - # extruder. - # - # \param setting_key The key of the setting to get. - # \param property The property to get from the setting. - # \return The property of the specified setting in the adhesion extruder. - def _getSettingFromAdhesionExtruder(self, setting_key, property = "value"): - return self._getSettingFromExtruder(setting_key, "adhesion_extruder_nr", property) - ## Private convenience function to get a setting from every extruder. # # For single extrusion machines, this gets the setting from the global @@ -899,44 +890,6 @@ class BuildVolume(SceneNode): all_values[i] = 0 return all_values - ## Private convenience function to get a setting from the support infill - # extruder. - # - # \param setting_key The key of the setting to get. - # \param property The property to get from the setting. - # \return The property of the specified setting in the support infill - # extruder. - def _getSettingFromSupportInfillExtruder(self, setting_key, property = "value"): - return self._getSettingFromExtruder(setting_key, "support_infill_extruder_nr", property) - - ## Helper function to get a setting from an extruder specified in another - # setting. - # - # \param setting_key The key of the setting to get. - # \param extruder_setting_key The key of the setting that specifies from - # which extruder to get the setting, if there are multiple extruders. - # \param property The property to get from the setting. - # \return The property of the specified setting in the specified extruder. - def _getSettingFromExtruder(self, setting_key, extruder_setting_key, property = "value"): - multi_extrusion = self._global_container_stack.getProperty("machine_extruder_count", "value") > 1 - - if not multi_extrusion: - stack = self._global_container_stack - else: - extruder_index = self._global_container_stack.getProperty(extruder_setting_key, "value") - - if str(extruder_index) == "-1": # If extruder index is -1 use global instead - stack = self._global_container_stack - else: - extruder_stack_id = ExtruderManager.getInstance().extruderIds[str(extruder_index)] - stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0] - - value = stack.getProperty(setting_key, property) - setting_type = stack.getProperty(setting_key, "type") - if not value and (setting_type == "int" or setting_type == "float"): - return 0 - return value - ## Convenience function to calculate the disallowed radius around the edge. # # This disallowed radius is to allow for space around the models that is @@ -945,6 +898,7 @@ class BuildVolume(SceneNode): def _getEdgeDisallowedSize(self): if not self._global_container_stack: return 0 + container_stack = self._global_container_stack used_extruders = ExtruderManager.getInstance().getUsedExtruderStacks() @@ -953,32 +907,44 @@ class BuildVolume(SceneNode): return 0.1 # Return a very small value, so we do draw disallowed area's near the edges. adhesion_type = container_stack.getProperty("adhesion_type", "value") + skirt_brim_line_width = self._global_container_stack.getProperty("skirt_brim_line_width", "value") + initial_layer_line_width_factor = self._global_container_stack.getProperty("initial_layer_line_width_factor", "value") if adhesion_type == "skirt": - skirt_distance = self._getSettingFromAdhesionExtruder("skirt_gap") - skirt_line_count = self._getSettingFromAdhesionExtruder("skirt_line_count") - bed_adhesion_size = skirt_distance + (self._getSettingFromAdhesionExtruder("skirt_brim_line_width") * skirt_line_count) * self._getSettingFromAdhesionExtruder("initial_layer_line_width_factor") / 100.0 - if len(used_extruders) > 1: - for extruder_stack in used_extruders: - bed_adhesion_size += extruder_stack.getProperty("skirt_brim_line_width", "value") * extruder_stack.getProperty("initial_layer_line_width_factor", "value") / 100.0 - #We don't create an additional line for the extruder we're printing the skirt with. - bed_adhesion_size -= self._getSettingFromAdhesionExtruder("skirt_brim_line_width", "value") * self._getSettingFromAdhesionExtruder("initial_layer_line_width_factor", "value") / 100.0 + skirt_distance = self._global_container_stack.getProperty("skirt_gap", "value") + skirt_line_count = self._global_container_stack.getProperty("skirt_line_count", "value") + + bed_adhesion_size = skirt_distance + (skirt_brim_line_width * skirt_line_count) * initial_layer_line_width_factor / 100.0 + + for extruder_stack in used_extruders: + bed_adhesion_size += extruder_stack.getProperty("skirt_brim_line_width", "value") * extruder_stack.getProperty("initial_layer_line_width_factor", "value") / 100.0 + + # We don't create an additional line for the extruder we're printing the skirt with. + bed_adhesion_size -= skirt_brim_line_width * initial_layer_line_width_factor / 100.0 + elif adhesion_type == "brim": - bed_adhesion_size = self._getSettingFromAdhesionExtruder("skirt_brim_line_width") * self._getSettingFromAdhesionExtruder("brim_line_count") * self._getSettingFromAdhesionExtruder("initial_layer_line_width_factor") / 100.0 - if self._global_container_stack.getProperty("machine_extruder_count", "value") > 1: - for extruder_stack in used_extruders: - bed_adhesion_size += extruder_stack.getProperty("skirt_brim_line_width", "value") * extruder_stack.getProperty("initial_layer_line_width_factor", "value") / 100.0 - #We don't create an additional line for the extruder we're printing the brim with. - bed_adhesion_size -= self._getSettingFromAdhesionExtruder("skirt_brim_line_width", "value") * self._getSettingFromAdhesionExtruder("initial_layer_line_width_factor", "value") / 100.0 + brim_line_count = self._global_container_stack.getProperty("brim_line_count", "value") + bed_adhesion_size = skirt_brim_line_width * brim_line_count * initial_layer_line_width_factor / 100.0 + + for extruder_stack in used_extruders: + bed_adhesion_size += extruder_stack.getProperty("skirt_brim_line_width", "value") * extruder_stack.getProperty("initial_layer_line_width_factor", "value") / 100.0 + + # We don't create an additional line for the extruder we're printing the brim with. + bed_adhesion_size -= skirt_brim_line_width * initial_layer_line_width_factor / 100.0 + elif adhesion_type == "raft": - bed_adhesion_size = self._getSettingFromAdhesionExtruder("raft_margin") + bed_adhesion_size = self._global_container_stack.getProperty("raft_margin", "value") + elif adhesion_type == "none": bed_adhesion_size = 0 + else: raise Exception("Unknown bed adhesion type. Did you forget to update the build volume calculations for your new bed adhesion type?") support_expansion = 0 - if self._getSettingFromSupportInfillExtruder("support_offset") and self._global_container_stack.getProperty("support_enable", "value"): - support_expansion += self._getSettingFromSupportInfillExtruder("support_offset") + support_enabled = self._global_container_stack.getProperty("support_enable", "value") + support_offset = self._global_container_stack.getProperty("support_offset", "value") + if support_enabled and support_offset: + support_expansion += support_offset farthest_shield_distance = 0 if container_stack.getProperty("draft_shield_enabled", "value"): diff --git a/cura/ConvexHullDecorator.py b/cura/ConvexHullDecorator.py index bfeb690192..50fa8ce7f6 100644 --- a/cura/ConvexHullDecorator.py +++ b/cura/ConvexHullDecorator.py @@ -302,24 +302,23 @@ class ConvexHullDecorator(SceneNodeDecorator): self._onChanged() ## Private convenience function to get a setting from the correct extruder (as defined by limit_to_extruder property). - def _getSettingProperty(self, setting_key, property = "value"): + def _getSettingProperty(self, setting_key, prop = "value"): per_mesh_stack = self._node.callDecoration("getStack") if per_mesh_stack: - return per_mesh_stack.getProperty(setting_key, property) - - multi_extrusion = self._global_stack.getProperty("machine_extruder_count", "value") > 1 - if not multi_extrusion: - return self._global_stack.getProperty(setting_key, property) + return per_mesh_stack.getProperty(setting_key, prop) extruder_index = self._global_stack.getProperty(setting_key, "limit_to_extruder") - if extruder_index == "-1": #No limit_to_extruder. + if extruder_index == "-1": + # No limit_to_extruder extruder_stack_id = self._node.callDecoration("getActiveExtruder") - if not extruder_stack_id: #Decoration doesn't exist. + if not extruder_stack_id: + # Decoration doesn't exist extruder_stack_id = ExtruderManager.getInstance().extruderIds["0"] extruder_stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0] - return extruder_stack.getProperty(setting_key, property) - else: #Limit_to_extruder is set. The global stack handles this then. - return self._global_stack.getProperty(setting_key, property) + return extruder_stack.getProperty(setting_key, prop) + else: + # Limit_to_extruder is set. The global stack handles this then + return self._global_stack.getProperty(setting_key, prop) ## Returns true if node is a descendant or the same as the root node. def __isDescendant(self, root, node): diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 96ca15741e..8c1ee8fc36 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -200,6 +200,7 @@ class CuraApplication(QtApplication): self._machine_action_manager = MachineActionManager.MachineActionManager() self._machine_manager = None # This is initialized on demand. + self._extruder_manager = None self._material_manager = None self._setting_inheritance_manager = None self._simple_mode_settings_manager = None @@ -260,14 +261,17 @@ class CuraApplication(QtApplication): # Since they are empty, they should never be serialized and instead just programmatically created. # We need them to simplify the switching between materials. empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() + empty_variant_container = copy.deepcopy(empty_container) empty_variant_container._id = "empty_variant" empty_variant_container.addMetaDataEntry("type", "variant") ContainerRegistry.getInstance().addContainer(empty_variant_container) + empty_material_container = copy.deepcopy(empty_container) empty_material_container._id = "empty_material" empty_material_container.addMetaDataEntry("type", "material") ContainerRegistry.getInstance().addContainer(empty_material_container) + empty_quality_container = copy.deepcopy(empty_container) empty_quality_container._id = "empty_quality" empty_quality_container.setName("Not Supported") @@ -275,6 +279,7 @@ class CuraApplication(QtApplication): empty_quality_container.addMetaDataEntry("type", "quality") empty_quality_container.addMetaDataEntry("supported", False) 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") @@ -425,7 +430,7 @@ class CuraApplication(QtApplication): def discardOrKeepProfileChangesClosed(self, option): if option == "discard": global_stack = self.getGlobalContainerStack() - for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()): + for extruder in self._extruder_manager.getMachineExtruders(global_stack.getId()): extruder.getTop().clear() global_stack.getTop().clear() @@ -433,7 +438,7 @@ class CuraApplication(QtApplication): # before slicing. To ensure that slicer uses right settings values elif option == "keep": global_stack = self.getGlobalContainerStack() - for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()): + for extruder in self._extruder_manager.getMachineExtruders(global_stack.getId()): user_extruder_container = extruder.getTop() if user_extruder_container: user_extruder_container.update() @@ -699,16 +704,13 @@ class CuraApplication(QtApplication): self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Loading interface...")) - # Initialise extruder so as to listen to global container stack changes before the first global container stack is set. - ExtruderManager.getInstance() + qmlRegisterSingletonType(ExtruderManager, "Cura", 1, 0, "ExtruderManager", self.getExtruderManager) qmlRegisterSingletonType(MachineManager, "Cura", 1, 0, "MachineManager", self.getMachineManager) qmlRegisterSingletonType(MaterialManager, "Cura", 1, 0, "MaterialManager", self.getMaterialManager) - qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager", - self.getSettingInheritanceManager) - qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 2, "SimpleModeSettingsManager", - self.getSimpleModeSettingsManager) - + qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager", self.getSettingInheritanceManager) + qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 2, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager) qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager) + self.setMainQml(Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml")) self._qml_import_paths.append(Resources.getPath(self.ResourceTypes.QmlFiles)) @@ -733,6 +735,11 @@ class CuraApplication(QtApplication): self._machine_manager = MachineManager.createMachineManager() return self._machine_manager + def getExtruderManager(self, *args): + if self._extruder_manager is None: + self._extruder_manager = ExtruderManager.createExtruderManager() + return self._extruder_manager + def getMaterialManager(self, *args): if self._material_manager is None: self._material_manager = MaterialManager.createMaterialManager() @@ -783,7 +790,6 @@ class CuraApplication(QtApplication): qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type") qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel") - qmlRegisterType(ContainerSettingsModel, "Cura", 1, 0, "ContainerSettingsModel") qmlRegisterSingletonType(ProfilesModel, "Cura", 1, 0, "ProfilesModel", ProfilesModel.createProfilesModel) qmlRegisterType(MaterialsModel, "Cura", 1, 0, "MaterialsModel") @@ -793,15 +799,12 @@ class CuraApplication(QtApplication): qmlRegisterType(QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel") qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator") qmlRegisterType(UserChangesModel, "Cura", 1, 1, "UserChangesModel") - qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.createContainerManager) # As of Qt5.7, it is necessary to get rid of any ".." in the path for the singleton to work. actions_url = QUrl.fromLocalFile(os.path.abspath(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml"))) qmlRegisterSingletonType(actions_url, "Cura", 1, 0, "Actions") - engine.rootContext().setContextProperty("ExtruderManager", ExtruderManager.getInstance()) - for path in Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.QmlFiles): type_name = os.path.splitext(os.path.basename(path))[0] if type_name in ("Cura", "Actions"): diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index e623bd860b..5cfe867bc2 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -17,7 +17,7 @@ from UM.Application import Application from UM.Logger import Logger from UM.Message import Message from UM.Platform import Platform -from UM.PluginRegistry import PluginRegistry #For getting the possible profile writers to write with. +from UM.PluginRegistry import PluginRegistry # For getting the possible profile writers to write with. from UM.Util import parseBool from . import ExtruderStack @@ -42,12 +42,13 @@ class CuraContainerRegistry(ContainerRegistry): # Global stack based on metadata information. @override(ContainerRegistry) def addContainer(self, container): + # Note: Intentional check with type() because we want to ignore subclasses if type(container) == ContainerStack: container = self._convertContainerStack(container) if isinstance(container, InstanceContainer) and type(container) != type(self.getEmptyInstanceContainer()): - #Check against setting version of the definition. + # Check against setting version of the definition. required_setting_version = CuraApplication.SettingVersion actual_setting_version = int(container.getMetaDataEntry("setting_version", default = 0)) if required_setting_version != actual_setting_version: @@ -256,7 +257,8 @@ class CuraContainerRegistry(ContainerRegistry): @override(ContainerRegistry) def load(self): super().load() - self._fixupExtruders() + self._registerSingleExtrusionMachinesExtruderStacks() + self._connectUpgradedExtruderStacksToMachines() ## Update an imported profile to match the current machine configuration. # @@ -299,10 +301,13 @@ class CuraContainerRegistry(ContainerRegistry): machine_definition = Application.getInstance().getGlobalContainerStack().getBottom() del quality_type_criteria["definition"] - materials = None + + # materials = None + if "material" in quality_type_criteria: - materials = ContainerRegistry.getInstance().findInstanceContainers(id = quality_type_criteria["material"]) + # materials = ContainerRegistry.getInstance().findInstanceContainers(id = quality_type_criteria["material"]) del quality_type_criteria["material"] + # Do not filter quality containers here with materials because we are trying to import a profile, so it should # NOT be restricted by the active materials on the current machine. materials = None @@ -360,8 +365,8 @@ class CuraContainerRegistry(ContainerRegistry): return global_container_stack.material.getId() return "" - ## Returns true if the current machien requires its own quality profiles - # \return true if the current machien requires its own quality profiles + ## Returns true if the current machine requires its own quality profiles + # \return true if the current machine requires its own quality profiles def _machineHasOwnQualities(self): global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: @@ -394,12 +399,65 @@ class CuraContainerRegistry(ContainerRegistry): return new_stack + def _registerSingleExtrusionMachinesExtruderStacks(self): + machines = ContainerRegistry.getInstance().findContainerStacks(machine_extruder_trains = {"0": "fdmextruder"}) + for machine in machines: + self.addExtruderStackForSingleExtrusionMachine(machine, "fdmextruder") + + def addExtruderStackForSingleExtrusionMachine(self, machine, extruder_id): + new_extruder_id = extruder_id + extruder_stack = None + + # if extruders are defined in the machine definition use those instead + if machine.extruders and len(machine.extruders) > 0: + new_extruder_id = machine.extruders["0"].getId() + extruder_stack = machine.extruders["0"] + + # if the extruder stack doesn't exist yet we create and add it + if not extruder_stack: + extruder_definitions = self.findDefinitionContainers(id = new_extruder_id) + if not extruder_definitions: + Logger.log("w", "Could not find definition containers for extruder %s", new_extruder_id) + return + + extruder_definition = extruder_definitions[0] + unique_name = self.uniqueName(machine.getName() + " " + new_extruder_id) + + extruder_stack = ExtruderStack.ExtruderStack(unique_name) + extruder_stack.setName(extruder_definition.getName()) + extruder_stack.setDefinition(extruder_definition) + extruder_stack.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position")) + extruder_stack.setNextStack(machine) + + if machine.userChanges: + # set existing user changes if found + extruder_stack.setUserChanges(machine.userChanges) + else: + # create empty user changes container otherwise + user_container = InstanceContainer(extruder_stack.id + "_user") + user_container.addMetaDataEntry("type", "user") + user_container.addMetaDataEntry("machine", extruder_stack.getId()) + from cura.CuraApplication import CuraApplication + user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) + user_container.setDefinition(extruder_definition) + extruder_stack.setUserChanges(user_container) + self.addContainer(user_container) + + variant_id = "default" + if machine.variant.getId() != "empty_variant": + variant_id = machine.variant.getId() + extruder_stack.setVariantById(variant_id) + extruder_stack.setMaterialById("default") + extruder_stack.setQualityById("default") + + self.addContainer(extruder_stack) + # Fix the extruders that were upgraded to ExtruderStack instances during addContainer. # The stacks are now responsible for setting the next stack on deserialize. However, # due to problems with loading order, some stacks may not have the proper next stack # set after upgrading, because the proper global stack was not yet loaded. This method # makes sure those extruders also get the right stack set. - def _fixupExtruders(self): + def _connectUpgradedExtruderStacksToMachines(self): extruder_stacks = self.findContainers(ExtruderStack.ExtruderStack) for extruder_stack in extruder_stacks: if extruder_stack.getNextStack(): diff --git a/cura/Settings/CuraContainerStack.py b/cura/Settings/CuraContainerStack.py index 2d3bf683f6..2a804def4d 100755 --- a/cura/Settings/CuraContainerStack.py +++ b/cura/Settings/CuraContainerStack.py @@ -396,7 +396,9 @@ class CuraContainerStack(ContainerStack): # \note This method assumes the stack has a valid machine definition. def findDefaultVariant(self) -> Optional[ContainerInterface]: definition = self._getMachineDefinition() - if not definition.getMetaDataEntry("has_variants"): + # has_variants can be overridden in other containers and stacks. + # In the case of UM2, it is overridden in the GlobalStack + if not self.getMetaDataEntry("has_variants"): # If the machine does not use variants, we should never set a variant. return None diff --git a/cura/Settings/CuraStackBuilder.py b/cura/Settings/CuraStackBuilder.py index 09815da319..a661237722 100644 --- a/cura/Settings/CuraStackBuilder.py +++ b/cura/Settings/CuraStackBuilder.py @@ -47,21 +47,40 @@ class CuraStackBuilder: new_global_stack.setName(generated_name) - for extruder_definition in registry.findDefinitionContainers(machine = machine_definition.id): - position = extruder_definition.getMetaDataEntry("position", None) - if not position: - Logger.log("w", "Extruder definition %s specifies no position metadata entry.", extruder_definition.id) + extruder_definition = registry.findDefinitionContainers(machine = machine_definition.getId()) - new_extruder_id = registry.uniqueName(extruder_definition.id) + if not extruder_definition: + # create extruder stack for single extrusion machines that have no separate extruder definition files + extruder_definition = registry.findDefinitionContainers(id = "fdmextruder")[0] + new_extruder_id = registry.uniqueName(machine_definition.getName() + " " + extruder_definition.id) new_extruder = cls.createExtruderStack( new_extruder_id, - definition = extruder_definition, - machine_definition = machine_definition, - quality = "default", - material = "default", - variant = "default", - next_stack = new_global_stack + definition=extruder_definition, + machine_definition=machine_definition, + quality="default", + material="default", + variant="default", + next_stack=new_global_stack ) + new_global_stack.addExtruder(new_extruder) + else: + # create extruder stack for each found extruder definition + for extruder_definition in registry.findDefinitionContainers(machine = machine_definition.id): + position = extruder_definition.getMetaDataEntry("position", None) + if not position: + Logger.log("w", "Extruder definition %s specifies no position metadata entry.", extruder_definition.id) + + new_extruder_id = registry.uniqueName(extruder_definition.id) + new_extruder = cls.createExtruderStack( + new_extruder_id, + definition = extruder_definition, + machine_definition = machine_definition, + quality = "default", + material = "default", + variant = "default", + next_stack = new_global_stack + ) + new_global_stack.addExtruder(new_extruder) return new_global_stack @@ -79,7 +98,9 @@ class CuraStackBuilder: stack.setName(definition.getName()) stack.setDefinition(definition) stack.addMetaDataEntry("position", definition.getMetaDataEntry("position")) - if "next_stack" in kwargs: #Add stacks before containers are added, since they may trigger a setting update. + + if "next_stack" in kwargs: + # Add stacks before containers are added, since they may trigger a setting update. stack.setNextStack(kwargs["next_stack"]) user_container = InstanceContainer(new_stack_id + "_user") diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index c8daca7f92..34b283107d 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -1,21 +1,18 @@ # Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant #For communicating data and events to Qt. +from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant # For communicating data and events to Qt. from UM.FlameProfiler import pyqtSlot -from UM.Application import Application #To get the global container stack to find the current machine. +from UM.Application import Application # To get the global container stack to find the current machine. from UM.Logger import Logger -from UM.Decorators import deprecated from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.SceneNode import SceneNode from UM.Scene.Selection import Selection from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator -from UM.Settings.ContainerRegistry import ContainerRegistry #Finding containers by ID. -from UM.Settings.InstanceContainer import InstanceContainer +from UM.Settings.ContainerRegistry import ContainerRegistry # Finding containers by ID. from UM.Settings.SettingFunction import SettingFunction from UM.Settings.ContainerStack import ContainerStack -from UM.Settings.Interfaces import DefinitionContainerInterface from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext from typing import Optional, List, TYPE_CHECKING, Union @@ -28,6 +25,20 @@ if TYPE_CHECKING: # # This keeps a list of extruder stacks for each machine. class ExtruderManager(QObject): + + ## Registers listeners and such to listen to changes to the extruders. + def __init__(self, parent = None): + super().__init__(parent) + + self._extruder_trains = {} # Per machine, a dictionary of extruder container stack IDs. Only for separately defined extruders. + self._active_extruder_index = -1 # Indicates the index of the active extruder stack. -1 means no active extruder stack + self._selected_object_extruders = [] + self._global_container_stack_definition_id = None + self._addCurrentMachineExtruders() + + Application.getInstance().globalContainerStackChanged.connect(self.__globalContainerStackChanged) + Selection.selectionChanged.connect(self.resetSelectedObjectExtruders) + ## Signal to notify other components when the list of extruders for a machine definition changes. extrudersChanged = pyqtSignal(QVariant) @@ -38,18 +49,6 @@ class ExtruderManager(QObject): ## Notify when the user switches the currently active extruder. activeExtruderChanged = pyqtSignal() - ## Registers listeners and such to listen to changes to the extruders. - 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 = -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 - self._addCurrentMachineExtruders() - - Selection.selectionChanged.connect(self.resetSelectedObjectExtruders) - ## Gets the unique identifier of the currently active extruder stack. # # The currently active extruder stack is the stack that is currently being @@ -59,10 +58,10 @@ class ExtruderManager(QObject): @pyqtProperty(str, notify = activeExtruderChanged) def activeExtruderStackId(self) -> Optional[str]: if not Application.getInstance().getGlobalContainerStack(): - return None # No active machine, so no active extruder. + return None # No active machine, so no active extruder. try: return self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()][str(self._active_extruder_index)].getId() - except KeyError: # Extruder index could be -1 if the global tab is selected, or the entry doesn't exist if the machine definition is wrong. + except KeyError: # Extruder index could be -1 if the global tab is selected, or the entry doesn't exist if the machine definition is wrong. return None ## Return extruder count according to extruder trains. @@ -76,23 +75,23 @@ class ExtruderManager(QObject): 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): 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]: 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: + def getQualityChangesIdByExtruderStackId(self, extruder_stack_id: str) -> str: for position in self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()]: extruder = self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()][position] - if extruder.getId() == id: + if extruder.getId() == extruder_stack_id: return extruder.qualityChanges.getId() ## The instance of the singleton pattern. @@ -100,6 +99,10 @@ class ExtruderManager(QObject): # It's None if the extruder manager hasn't been created yet. __instance = None + @staticmethod + def createExtruderManager(): + return ExtruderManager().getInstance() + ## Gets an instance of the extruder manager, or creates one if no instance # exists yet. # @@ -185,6 +188,7 @@ class ExtruderManager(QObject): if global_container_stack.getId() in self._extruder_trains: if str(self._active_extruder_index) in self._extruder_trains[global_container_stack.getId()]: return self._extruder_trains[global_container_stack.getId()][str(self._active_extruder_index)] + return None ## Get an extruder stack by index @@ -203,40 +207,6 @@ class ExtruderManager(QObject): result.append(self.getExtruderStack(i)) return result - ## Adds all extruders of a specific machine definition to the extruder - # manager. - # - # \param machine_definition The machine definition to add the extruders for. - # \param machine_id The machine_id to add the extruders for. - @deprecated("Use CuraStackBuilder", "2.6") - def addMachineExtruders(self, machine_definition: DefinitionContainerInterface, machine_id: str) -> None: - changed = False - machine_definition_id = machine_definition.getId() - if machine_id not in self._extruder_trains: - self._extruder_trains[machine_id] = { } - changed = True - container_registry = ContainerRegistry.getInstance() - if container_registry: - # Add the extruder trains that don't exist yet. - for extruder_definition in container_registry.findDefinitionContainers(machine = machine_definition_id): - position = extruder_definition.getMetaDataEntry("position", None) - if not position: - Logger.log("w", "Extruder definition %s specifies no position metadata entry.", extruder_definition.getId()) - if not container_registry.findContainerStacks(machine = machine_id, position = position): # Doesn't exist yet. - self.createExtruderTrain(extruder_definition, machine_definition, position, machine_id) - changed = True - - # Gets the extruder trains that we just created as well as any that still existed. - extruder_trains = container_registry.findContainerStacks(type = "extruder_train", machine = machine_id) - for extruder_train in extruder_trains: - self._extruder_trains[machine_id][extruder_train.getMetaDataEntry("position")] = extruder_train - - # regardless of what the next stack is, we have to set it again, because of signal routing. - extruder_train.setNextStack(Application.getInstance().getGlobalContainerStack()) - changed = True - if changed: - self.extrudersChanged.emit(machine_id) - def registerExtruder(self, extruder_train, machine_id): changed = False @@ -256,138 +226,6 @@ class ExtruderManager(QObject): if changed: self.extrudersChanged.emit(machine_id) - ## Creates a container stack for an extruder train. - # - # The container stack has an extruder definition at the bottom, which is - # linked to a machine definition. Then it has a variant profile, a material - # profile, a quality profile and a user profile, in that order. - # - # The resulting container stack is added to the registry. - # - # \param extruder_definition The extruder to create the extruder train for. - # \param machine_definition The machine that the extruder train belongs to. - # \param position The position of this extruder train in the extruder slots of the machine. - # \param machine_id The id of the "global" stack this extruder is linked to. - @deprecated("Use CuraStackBuilder::createExtruderStack", "2.6") - def createExtruderTrain(self, extruder_definition: DefinitionContainerInterface, machine_definition: DefinitionContainerInterface, - position, machine_id: str) -> None: - # Cache some things. - container_registry = ContainerRegistry.getInstance() - machine_definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(machine_definition) - - # Create a container stack for this extruder. - extruder_stack_id = container_registry.uniqueName(extruder_definition.getId()) - container_stack = ContainerStack(extruder_stack_id) - container_stack.setName(extruder_definition.getName()) # Take over the display name to display the stack with. - container_stack.addMetaDataEntry("type", "extruder_train") - container_stack.addMetaDataEntry("machine", machine_id) - container_stack.addMetaDataEntry("position", position) - container_stack.addContainer(extruder_definition) - - # Find the variant to use for this extruder. - variant = container_registry.findInstanceContainers(id = "empty_variant")[0] - if machine_definition.getMetaDataEntry("has_variants"): - # First add any variant. Later, overwrite with preference if the preference is valid. - variants = container_registry.findInstanceContainers(definition = machine_definition_id, type = "variant") - if len(variants) >= 1: - variant = variants[0] - preferred_variant_id = machine_definition.getMetaDataEntry("preferred_variant") - if preferred_variant_id: - preferred_variants = container_registry.findInstanceContainers(id = preferred_variant_id, definition = machine_definition_id, type = "variant") - if len(preferred_variants) >= 1: - variant = preferred_variants[0] - else: - Logger.log("w", "The preferred variant \"%s\" of machine %s doesn't exist or is not a variant profile.", preferred_variant_id, machine_id) - # And leave it at the default variant. - container_stack.addContainer(variant) - - # Find a material to use for this variant. - material = container_registry.findInstanceContainers(id = "empty_material")[0] - if machine_definition.getMetaDataEntry("has_materials"): - # First add any material. Later, overwrite with preference if the preference is valid. - machine_has_variant_materials = machine_definition.getMetaDataEntry("has_variant_materials", default = False) - if machine_has_variant_materials or machine_has_variant_materials == "True": - materials = container_registry.findInstanceContainers(type = "material", definition = machine_definition_id, variant = variant.getId()) - else: - materials = container_registry.findInstanceContainers(type = "material", definition = machine_definition_id) - if len(materials) >= 1: - material = materials[0] - preferred_material_id = machine_definition.getMetaDataEntry("preferred_material") - if preferred_material_id: - global_stack = ContainerRegistry.getInstance().findContainerStacks(id = machine_id) - if global_stack: - approximate_material_diameter = str(round(global_stack[0].getProperty("material_diameter", "value"))) - else: - approximate_material_diameter = str(round(machine_definition.getProperty("material_diameter", "value"))) - - search_criteria = { "type": "material", "id": preferred_material_id, "approximate_diameter": approximate_material_diameter} - if machine_definition.getMetaDataEntry("has_machine_materials"): - search_criteria["definition"] = machine_definition_id - - if machine_definition.getMetaDataEntry("has_variants") and variant: - search_criteria["variant"] = variant.id - else: - search_criteria["definition"] = "fdmprinter" - - preferred_materials = container_registry.findInstanceContainers(**search_criteria) - if len(preferred_materials) >= 1: - # In some cases we get multiple materials. In that case, prefer materials that are marked as read only. - read_only_preferred_materials = [preferred_material for preferred_material in preferred_materials if preferred_material.isReadOnly()] - if len(read_only_preferred_materials) >= 1: - material = read_only_preferred_materials[0] - else: - material = preferred_materials[0] - else: - Logger.log("w", "The preferred material \"%s\" of machine %s doesn't exist or is not a material profile.", preferred_material_id, machine_id) - # And leave it at the default material. - container_stack.addContainer(material) - - # Find a quality to use for this extruder. - quality = container_registry.getEmptyInstanceContainer() - - search_criteria = { "type": "quality" } - if machine_definition.getMetaDataEntry("has_machine_quality"): - search_criteria["definition"] = machine_definition_id - if machine_definition.getMetaDataEntry("has_materials") and material: - search_criteria["material"] = material.id - else: - search_criteria["definition"] = "fdmprinter" - - preferred_quality = machine_definition.getMetaDataEntry("preferred_quality") - if preferred_quality: - search_criteria["id"] = preferred_quality - - containers = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria) - if not containers and preferred_quality: - Logger.log("w", "The preferred quality \"%s\" of machine %s doesn't exist or is not a quality profile.", preferred_quality, machine_id) - search_criteria.pop("id", None) - containers = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria) - if containers: - quality = containers[0] - - 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] - else: - user_profile = InstanceContainer(extruder_stack_id + "_current_settings") # Add an empty user profile. - user_profile.addMetaDataEntry("type", "user") - user_profile.addMetaDataEntry("extruder", extruder_stack_id) - from cura.CuraApplication import CuraApplication - user_profile.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) - user_profile.setDefinition(machine_definition) - container_registry.addContainer(user_profile) - container_stack.addContainer(user_profile) - - # regardless of what the next stack is, we have to set it again, because of signal routing. - container_stack.setNextStack(Application.getInstance().getGlobalContainerStack()) - - container_registry.addContainer(container_stack) - def getAllExtruderValues(self, setting_key): return self.getAllExtruderSettings(setting_key, "value") @@ -396,16 +234,12 @@ class ExtruderManager(QObject): # \param setting_key \type{str} The setting to get the property of. # \param property \type{str} The property to get. # \return \type{List} the list of results - def getAllExtruderSettings(self, setting_key, property): - global_container_stack = Application.getInstance().getGlobalContainerStack() - if global_container_stack.getProperty("machine_extruder_count", "value") <= 1: - return [global_container_stack.getProperty(setting_key, property)] - + def getAllExtruderSettings(self, setting_key: str, prop: str): result = [] for index in self.extruderIds: extruder_stack_id = self.extruderIds[str(index)] - stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0] - result.append(stack.getProperty(setting_key, property)) + extruder_stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0] + result.append(extruder_stack.getProperty(setting_key, prop)) return result ## Gets the extruder stacks that are actually being used at the moment. @@ -422,20 +256,25 @@ class ExtruderManager(QObject): global_stack = Application.getInstance().getGlobalContainerStack() container_registry = ContainerRegistry.getInstance() - if global_stack.getProperty("machine_extruder_count", "value") <= 1: #For single extrusion. - return [global_stack] - used_extruder_stack_ids = set() - #Get the extruders of all meshes in the scene. + # Get the extruders of all meshes in the scene support_enabled = False support_bottom_enabled = False support_roof_enabled = False + scene_root = Application.getInstance().getController().getScene().getRoot() - meshes = [node for node in DepthFirstIterator(scene_root) if type(node) is SceneNode and node.isSelectable()] #Only use the nodes that will be printed. + + # If no extruders are registered in the extruder manager yet, return an empty array + if len(self.extruderIds) == 0: + return [] + + # Get the extruders of all printable meshes in the scene + meshes = [node for node in DepthFirstIterator(scene_root) if type(node) is SceneNode and node.isSelectable()] for mesh in meshes: extruder_stack_id = mesh.callDecoration("getActiveExtruder") - if not extruder_stack_id: #No per-object settings for this node. + if not extruder_stack_id: + # No per-object settings for this node extruder_stack_id = self.extruderIds["0"] used_extruder_stack_ids.add(extruder_stack_id) @@ -471,9 +310,10 @@ class ExtruderManager(QObject): if support_roof_enabled: used_extruder_stack_ids.add(self.extruderIds[str(global_stack.getProperty("support_roof_extruder_nr", "value"))]) - #The platform adhesion extruder. Not used if using none. + # The platform adhesion extruder. Not used if using none. if global_stack.getProperty("adhesion_type", "value") != "none": used_extruder_stack_ids.add(self.extruderIds[str(global_stack.getProperty("adhesion_extruder_nr", "value"))]) + try: return [container_registry.findContainerStacks(id = stack_id)[0] for stack_id in used_extruder_stack_ids] except IndexError: # One or more of the extruders was not found. @@ -520,10 +360,6 @@ class ExtruderManager(QObject): 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]) @@ -536,24 +372,39 @@ class ExtruderManager(QObject): 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() + # If the global container changed, the machine changed and might have extruders that were not registered yet + self._addCurrentMachineExtruders() self.resetSelectedObjectExtruders() ## Adds the extruders of the currently active machine. def _addCurrentMachineExtruders(self) -> None: global_stack = Application.getInstance().getGlobalContainerStack() - if global_stack and global_stack.getBottom(): - self.addMachineExtruders(global_stack.getBottom(), global_stack.getId()) + extruders_changed = False + + if global_stack: + container_registry = ContainerRegistry.getInstance() + global_stack_id = global_stack.getId() + + # Gets the extruder trains that we just created as well as any that still existed. + extruder_trains = container_registry.findContainerStacks(type = "extruder_train", machine = global_stack_id) + + # Make sure the extruder trains for the new machine can be placed in the set of sets + if global_stack_id not in self._extruder_trains: + self._extruder_trains[global_stack_id] = {} + extruders_changed = True + + # Register the extruder trains by position + for extruder_train in extruder_trains: + self._extruder_trains[global_stack_id][extruder_train.getMetaDataEntry("position")] = extruder_train + + # regardless of what the next stack is, we have to set it again, because of signal routing. ??? + extruder_train.setNextStack(global_stack) + extruders_changed = True + + if extruders_changed: + self.extrudersChanged.emit(global_stack_id) + self.setActiveExtruderIndex(0) ## Get all extruder values for a certain setting. # @@ -632,7 +483,7 @@ class ExtruderManager(QObject): # # This is exposed to qml for display purposes # - # \param key The key of the setting to retieve values for. + # \param key The key of the setting to retrieve values for. # # \return String representing the extruder values @pyqtSlot(str, result="QVariant") @@ -656,7 +507,8 @@ class ExtruderManager(QObject): value = extruder.getRawProperty(key, "value") if isinstance(value, SettingFunction): value = value(extruder) - else: #Just a value from global. + else: + # Just a value from global. value = Application.getInstance().getGlobalContainerStack().getProperty(key, "value") return value diff --git a/cura/Settings/ExtruderStack.py b/cura/Settings/ExtruderStack.py index d8ff6645a8..fe7068b7ea 100644 --- a/cura/Settings/ExtruderStack.py +++ b/cura/Settings/ExtruderStack.py @@ -115,6 +115,11 @@ class ExtruderStack(CuraContainerStack): if has_global_dependencies: self.getNextStack().propertiesChanged.emit(key, properties) + def findDefaultVariant(self): + # The default variant is defined in the machine stack and/or definition, so use the machine stack to find + # the default variant. + return self.getNextStack().findDefaultVariant() + extruder_stack_mime = MimeType( name = "application/x-cura-extruderstack", diff --git a/cura/Settings/ExtrudersModel.py b/cura/Settings/ExtrudersModel.py index b13e51723b..40d13461cc 100644 --- a/cura/Settings/ExtrudersModel.py +++ b/cura/Settings/ExtrudersModel.py @@ -71,13 +71,13 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): self._add_global = False self._simple_names = False - self._active_machine_extruders = [] # type: Iterable[ExtruderStack] + self._active_machine_extruders = [] # type: Iterable[ExtruderStack] self._add_optional_extruder = False - #Listen to changes. - Application.getInstance().globalContainerStackChanged.connect(self._extrudersChanged) #When the machine is swapped we must update the active machine extruders. - ExtruderManager.getInstance().extrudersChanged.connect(self._extrudersChanged) #When the extruders change we must link to the stack-changed signal of the new extruder. - self._extrudersChanged() #Also calls _updateExtruders. + # Listen to changes + Application.getInstance().globalContainerStackChanged.connect(self._extrudersChanged) # When the machine is swapped we must update the active machine extruders + ExtruderManager.getInstance().extrudersChanged.connect(self._extrudersChanged) # When the extruders change we must link to the stack-changed signal of the new extruder + self._extrudersChanged() # Also calls _updateExtruders def setAddGlobal(self, add): if add != self._add_global: @@ -128,21 +128,24 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): def _extrudersChanged(self, machine_id = None): if machine_id is not None: if Application.getInstance().getGlobalContainerStack() is None: - return #No machine, don't need to update the current machine's extruders. + # No machine, don't need to update the current machine's extruders + return if machine_id != Application.getInstance().getGlobalContainerStack().getId(): - return #Not the current machine. - #Unlink from old extruders. + # Not the current machine + return + + # Unlink from old extruders for extruder in self._active_machine_extruders: extruder.containersChanged.disconnect(self._onExtruderStackContainersChanged) - #Link to new extruders. + # Link to new extruders self._active_machine_extruders = [] extruder_manager = ExtruderManager.getInstance() for extruder in extruder_manager.getExtruderStacks(): extruder.containersChanged.connect(self._onExtruderStackContainersChanged) self._active_machine_extruders.append(extruder) - self._updateExtruders() #Since the new extruders may have different properties, update our own model. + self._updateExtruders() # Since the new extruders may have different properties, update our own model. def _onExtruderStackContainersChanged(self, container): # Update when there is an empty container or material change @@ -150,7 +153,6 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): # The ExtrudersModel needs to be updated when the material-name or -color changes, because the user identifies extruders by material-name self._updateExtruders() - modelChanged = pyqtSignal() def _updateExtruders(self): @@ -161,14 +163,17 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): # This should be called whenever the list of extruders changes. @UM.FlameProfiler.profile def __updateExtruders(self): - changed = False + extruders_changed = False if self.rowCount() != 0: - changed = True + extruders_changed = True items = [] + global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: + + # TODO: remove this - CURA-4482 if self._add_global: material = global_container_stack.material color = material.getMetaDataEntry("color_code", default = self.defaultColors[0]) if material else self.defaultColors[0] @@ -180,40 +185,44 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): "definition": "" } items.append(item) - changed = True + extruders_changed = True + # get machine extruder count for verification machine_extruder_count = global_container_stack.getProperty("machine_extruder_count", "value") - manager = ExtruderManager.getInstance() - for extruder in manager.getMachineExtruders(global_container_stack.getId()): + + for extruder in ExtruderManager.getInstance().getMachineExtruders(global_container_stack.getId()): position = extruder.getMetaDataEntry("position", default = "0") # Get the position try: position = int(position) - except ValueError: #Not a proper int. + except ValueError: + # Not a proper int. position = -1 if position >= machine_extruder_count: continue - extruder_name = extruder.getName() - material = extruder.material - variant = extruder.variant - default_color = self.defaultColors[position] if position >= 0 and position < len(self.defaultColors) else self.defaultColors[0] - color = material.getMetaDataEntry("color_code", default = default_color) if material else default_color - item = { #Construct an item with only the relevant information. + default_color = self.defaultColors[position] if 0 <= position < len(self.defaultColors) else self.defaultColors[0] + color = extruder.material.getMetaDataEntry("color_code", default = default_color) if extruder.material else default_color + + # construct an item with only the relevant information + item = { "id": extruder.getId(), - "name": extruder_name, + "name": extruder.getName(), "color": color, "index": position, "definition": extruder.getBottom().getId(), - "material": material.getName() if material else "", - "variant": variant.getName() if variant else "", + "material": extruder.material.getName() if extruder.material else "", + "variant": extruder.variant.getName() if extruder.variant else "", # e.g. print core } - items.append(item) - changed = True - if changed: + items.append(item) + extruders_changed = True + + if extruders_changed: + # sort by extruder index items.sort(key = lambda i: i["index"]) + # We need optional extruder to be last, so add it after we do sorting. - # This way we can simply intrepret the -1 of the index as the last item (which it now always is) + # This way we can simply interpret the -1 of the index as the last item (which it now always is) if self._add_optional_extruder: item = { "id": "", @@ -223,5 +232,6 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): "definition": "" } items.append(item) + self.setItems(items) self.modelChanged.emit() diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py index 88218c2f1e..2f27c4ab72 100755 --- a/cura/Settings/GlobalStack.py +++ b/cura/Settings/GlobalStack.py @@ -23,9 +23,9 @@ class GlobalStack(CuraContainerStack): def __init__(self, container_id: str, *args, **kwargs): super().__init__(container_id, *args, **kwargs) - self.addMetaDataEntry("type", "machine") # For backward compatibility + self.addMetaDataEntry("type", "machine") # For backward compatibility - self._extruders = {} + self._extruders = {} # type: Dict[str, "ExtruderStack"] # This property is used to track which settings we are calculating the "resolve" for # and if so, to bypass the resolve to prevent an infinite recursion that would occur @@ -61,13 +61,6 @@ class GlobalStack(CuraContainerStack): # \throws Exceptions.TooManyExtrudersError Raised when trying to add an extruder while we # already have the maximum number of extruders. def addExtruder(self, extruder: ContainerStack) -> None: - extruder_count = self.getProperty("machine_extruder_count", "value") - - if extruder_count <= 1: - Logger.log("i", "Not adding extruder[%s] to [%s] because it is a single-extrusion machine.", - extruder.id, self.id) - return - position = extruder.getMetaDataEntry("position") if position is None: Logger.log("w", "No position defined for extruder {extruder}, cannot add it to stack {stack}", extruder = extruder.id, stack = self.id) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index f85acc164d..0daf54c018 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -107,9 +107,8 @@ class MachineManager(QObject): if active_machine_id != "" and ContainerRegistry.getInstance().findContainerStacks(id = active_machine_id): # An active machine was saved, so restore it. self.setActiveMachine(active_machine_id) - if self._global_container_stack and self._global_container_stack.getProperty("machine_extruder_count", "value") > 1: - # Make sure _active_container_stack is properly initiated - ExtruderManager.getInstance().setActiveExtruderIndex(0) + # Make sure _active_container_stack is properly initiated + ExtruderManager.getInstance().setActiveExtruderIndex(0) self._auto_materials_changed = {} self._auto_hotends_changed = {} @@ -162,7 +161,7 @@ class MachineManager(QObject): @pyqtProperty(int, constant=True) def totalNumberOfSettings(self) -> int: - return len(ContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")[0].getAllKeys()) + return len(ContainerRegistry.getInstance().findDefinitionContainers(id = "fdmprinter")[0].getAllKeys()) def _onHotendIdChanged(self, index: Union[str, int], hotend_id: str) -> None: if not self._global_container_stack: @@ -258,13 +257,13 @@ class MachineManager(QObject): if old_index is not None: extruder_manager.setActiveExtruderIndex(old_index) - self._auto_hotends_changed = {} #Processed all of them now. + self._auto_hotends_changed = {} # Processed all of them now. def _onGlobalContainerChanged(self): if self._global_container_stack: try: self._global_container_stack.nameChanged.disconnect(self._onMachineNameChanged) - except TypeError: #pyQtSignal gives a TypeError when disconnecting from something that was already disconnected. + except TypeError: # pyQtSignal gives a TypeError when disconnecting from something that was already disconnected. pass try: self._global_container_stack.containersChanged.disconnect(self._onInstanceContainersChanged) @@ -274,52 +273,38 @@ class MachineManager(QObject): self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged) except TypeError: pass - material = self._global_container_stack.material - material.nameChanged.disconnect(self._onMaterialNameChanged) - quality = self._global_container_stack.quality - quality.nameChanged.disconnect(self._onQualityNameChanged) - - if self._global_container_stack.getProperty("machine_extruder_count", "value") > 1: - for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks(): - extruder_stack.propertyChanged.disconnect(self._onPropertyChanged) - extruder_stack.containersChanged.disconnect(self._onInstanceContainersChanged) + for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks(): + extruder_stack.propertyChanged.disconnect(self._onPropertyChanged) + extruder_stack.containersChanged.disconnect(self._onInstanceContainersChanged) + # update the local global container stack reference self._global_container_stack = Application.getInstance().getGlobalContainerStack() self.globalContainerChanged.emit() + # after switching the global stack we reconnect all the signals and set the variant and material references if self._global_container_stack: 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._onPropertyChanged) - if self._global_container_stack.getProperty("machine_extruder_count", "value") > 1: - # For multi-extrusion machines, we do not want variant or material profiles in the stack, - # because these are extruder specific and may cause wrong values to be used for extruders - # that did not specify a value in the extruder. - global_variant = self._global_container_stack.variant - if global_variant != self._empty_variant_container: - self._global_container_stack.setVariant(self._empty_variant_container) + # set the global variant to empty as we now use the extruder stack at all times - CURA-4482 + global_variant = self._global_container_stack.variant + if global_variant != self._empty_variant_container: + self._global_container_stack.setVariant(self._empty_variant_container) - global_material = self._global_container_stack.material - if global_material != self._empty_material_container: - self._global_container_stack.setMaterial(self._empty_material_container) + # set the global material to empty as we now use the extruder stack at all times - CURA-4482 + global_material = self._global_container_stack.material + if global_material != self._empty_material_container: + self._global_container_stack.setMaterial(self._empty_material_container) - for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks(): #Listen for changes on all extruder stacks. - extruder_stack.propertyChanged.connect(self._onPropertyChanged) - extruder_stack.containersChanged.connect(self._onInstanceContainersChanged) - - else: - material = self._global_container_stack.material - material.nameChanged.connect(self._onMaterialNameChanged) - - quality = self._global_container_stack.quality - quality.nameChanged.connect(self._onQualityNameChanged) - - self._active_container_stack = self._global_container_stack - self.activeStackChanged.emit() + # Listen for changes on all extruder stacks + for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks(): + extruder_stack.propertyChanged.connect(self._onPropertyChanged) + extruder_stack.containersChanged.connect(self._onInstanceContainersChanged) self._error_check_timer.start() @@ -336,8 +321,6 @@ class MachineManager(QObject): old_active_container_stack = self._active_container_stack self._active_container_stack = ExtruderManager.getInstance().getActiveExtruderStack() - if not self._active_container_stack: - self._active_container_stack = self._global_container_stack self._error_check_timer.start() @@ -384,15 +367,6 @@ class MachineManager(QObject): else: Logger.log("w", "Failed creating a new machine!") - ## Create a name that is not empty and unique - # \param container_type \type{string} Type of the container (machine, quality, ...) - # \param current_name \type{} Current name of the container, which may be an acceptable option - # \param new_name \type{string} Base name, which may not be unique - # \param fallback_name \type{string} Name to use when (stripped) new_name is empty - # \return \type{string} Name that is unique for the specified type and name/id - def _createUniqueName(self, container_type: str, current_name: str, new_name: str, fallback_name: str) -> str: - return ContainerRegistry.getInstance().createUniqueName(container_type, current_name, new_name, fallback_name) - def _checkStacksHaveErrors(self) -> bool: if self._global_container_stack is None: #No active machine. return False @@ -732,15 +706,13 @@ class MachineManager(QObject): ## Copy the value of the setting of the current extruder to all other extruders as well as the global container. @pyqtSlot(str) def copyValueToExtruders(self, key: str): - if not self._active_container_stack or self._global_container_stack.getProperty("machine_extruder_count", "value") <= 1: - return - new_value = self._active_container_stack.getProperty(key, "value") - stacks = [stack for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())] - stacks.append(self._global_container_stack) - for extruder_stack in stacks: + extruder_stacks = [stack for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())] + + # check in which stack the value has to be replaced + for extruder_stack in extruder_stacks: if extruder_stack != self._active_container_stack and extruder_stack.getProperty(key, "value") != new_value: - extruder_stack.getTop().setProperty(key, "value", new_value) + extruder_stack.userChanges.setProperty(key, "value", new_value) # TODO: nested property access, should be improved ## Set the active material by switching out a container # Depending on from/to material+current variant, a quality profile is chosen and set. @@ -947,35 +919,46 @@ class MachineManager(QObject): global_container_stack = self._global_container_stack if not global_container_stack: return [] + global_machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.getBottom()) - extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks() - if extruder_stacks: - stacks = extruder_stacks - else: - stacks = [global_container_stack] - for stack in stacks: - material = stack.material + # find qualities for extruders + for extruder_stack in extruder_stacks: + material = extruder_stack.material # TODO: fix this - if self._new_material_container and stack.getId() == self._active_container_stack.getId(): + if self._new_material_container and extruder_stack.getId() == self._active_container_stack.getId(): material = self._new_material_container quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material]) + if not quality: # No quality profile is found for this quality type. quality = self._empty_quality_container - result.append({"stack": stack, "quality": quality, "quality_changes": empty_quality_changes}) - if extruder_stacks: - # Add an extra entry for the global stack. - global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [], global_quality = "True") + result.append({ + "stack": extruder_stack, + "quality": quality, + "quality_changes": empty_quality_changes + }) - if not global_quality: - global_quality = self._empty_quality_container + # also find a global quality for the machine + global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [], global_quality = "True") - result.append({"stack": global_container_stack, "quality": global_quality, "quality_changes": empty_quality_changes}) + # if there is not global quality but we're using a single extrusion machine, copy the quality of the first extruder - CURA-4482 + if not global_quality and len(extruder_stacks) == 1: + global_quality = result[0]["quality"] + + # if there is still no global quality, set it to empty (not supported) + if not global_quality: + global_quality = self._empty_quality_container + + result.append({ + "stack": global_container_stack, + "quality": global_quality, + "quality_changes": empty_quality_changes + }) return result @@ -988,10 +971,8 @@ class MachineManager(QObject): quality_manager = QualityManager.getInstance() global_container_stack = self._global_container_stack - global_machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.getBottom()) - - quality_changes_profiles = quality_manager.findQualityChangesByName(quality_changes_name, - global_machine_definition) + global_machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.definition) + quality_changes_profiles = quality_manager.findQualityChangesByName(quality_changes_name, global_machine_definition) global_quality_changes = [qcp for qcp in quality_changes_profiles if qcp.getMetaDataEntry("extruder") is None] if global_quality_changes: @@ -1002,27 +983,22 @@ class MachineManager(QObject): material = global_container_stack.material + # find a quality type that matches both machine and materials if self._new_material_container and self._active_container_stack.getId() == global_container_stack.getId(): material = self._new_material_container # For the global stack, find a quality which matches the quality_type in # the quality changes profile and also satisfies any material constraints. quality_type = global_quality_changes.getMetaDataEntry("quality_type") - if global_container_stack.getProperty("machine_extruder_count", "value") > 1: - global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [], global_quality = True) - else: - global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material]) - if not global_quality: - global_quality = self._empty_quality_container - # Find the values for each extruder. extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks() - for stack in extruder_stacks: - extruder_definition = quality_manager.getParentMachineDefinition(stack.getBottom()) + # append the extruder quality changes + for extruder_stack in extruder_stacks: + extruder_definition = quality_manager.getParentMachineDefinition(extruder_stack.definition) + + quality_changes_list = [qcp for qcp in quality_changes_profiles if qcp.getMetaDataEntry("extruder") == extruder_definition.getId()] - quality_changes_list = [qcp for qcp in quality_changes_profiles - if qcp.getMetaDataEntry("extruder") == extruder_definition.getId()] if quality_changes_list: quality_changes = quality_changes_list[0] else: @@ -1030,24 +1006,39 @@ class MachineManager(QObject): if not quality_changes: quality_changes = self._empty_quality_changes_container - material = stack.material + material = extruder_stack.material - if self._new_material_container and self._active_container_stack.getId() == stack.getId(): + if self._new_material_container and self._active_container_stack.getId() == extruder_stack.getId(): material = self._new_material_container quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material]) - if not quality: #No quality profile found for this quality type. + + if not quality: + # No quality profile found for this quality type. quality = self._empty_quality_container - result.append({"stack": stack, "quality": quality, "quality_changes": quality_changes}) + result.append({ + "stack": extruder_stack, + "quality": quality, + "quality_changes": quality_changes + }) - if extruder_stacks: - global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material], global_quality = "True") - if not global_quality: - global_quality = self._empty_quality_container - result.append({"stack": global_container_stack, "quality": global_quality, "quality_changes": global_quality_changes}) - else: - result.append({"stack": global_container_stack, "quality": global_quality, "quality_changes": global_quality_changes}) + # append the global quality changes + global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material], global_quality = "True") + + # if there is not global quality but we're using a single extrusion machine, copy the quality of the first extruder - CURA-4482 + if not global_quality and len(extruder_stacks) == 1: + global_quality = result[0]["quality"] + + # if still no global quality changes are found we set it to empty (not supported) + if not global_quality: + global_quality = self._empty_quality_container + + result.append({ + "stack": global_container_stack, + "quality": global_quality, + "quality_changes": global_quality_changes + }) return result @@ -1156,10 +1147,11 @@ class MachineManager(QObject): @pyqtSlot(str, str) def renameMachine(self, machine_id: str, new_name: str): - containers = ContainerRegistry.getInstance().findContainerStacks(id = machine_id) - if containers: - new_name = self._createUniqueName("machine", containers[0].getName(), new_name, containers[0].getBottom().getName()) - containers[0].setName(new_name) + container_registry = ContainerRegistry.getInstance() + machine_stack = container_registry.findContainerStacks(id = machine_id) + if machine_stack: + new_name = container_registry.createUniqueName("machine", machine_stack[0].getName(), new_name, machine_stack[0].getBottom().getName()) + machine_stack[0].setName(new_name) self.globalContainerChanged.emit() @pyqtSlot(str) @@ -1183,15 +1175,14 @@ class MachineManager(QObject): @pyqtProperty(bool, notify = globalContainerChanged) def hasMaterials(self) -> bool: if self._global_container_stack: - return bool(self._global_container_stack.getMetaDataEntry("has_materials", False)) + return Util.parseBool(self._global_container_stack.getMetaDataEntry("has_materials", False)) return False @pyqtProperty(bool, notify = globalContainerChanged) def hasVariants(self) -> bool: if self._global_container_stack: - return bool(self._global_container_stack.getMetaDataEntry("has_variants", False)) - + return Util.parseBool(self._global_container_stack.getMetaDataEntry("has_variants", False)) return False ## Property to indicate if a machine has "specialized" material profiles. @@ -1199,8 +1190,7 @@ class MachineManager(QObject): @pyqtProperty(bool, notify = globalContainerChanged) def filterMaterialsByMachine(self) -> bool: if self._global_container_stack: - return bool(self._global_container_stack.getMetaDataEntry("has_machine_materials", False)) - + return Util.parseBool(self._global_container_stack.getMetaDataEntry("has_machine_materials", False)) return False ## Property to indicate if a machine has "specialized" quality profiles. @@ -1208,7 +1198,7 @@ class MachineManager(QObject): @pyqtProperty(bool, notify = globalContainerChanged) def filterQualityByMachine(self) -> bool: if self._global_container_stack: - return bool(self._global_container_stack.getMetaDataEntry("has_machine_quality", False)) + return Util.parseBool(self._global_container_stack.getMetaDataEntry("has_machine_quality", False)) return False ## Get the Definition ID of a machine (specified by ID) @@ -1221,7 +1211,7 @@ class MachineManager(QObject): return containers[0].getBottom().getId() @staticmethod - def createMachineManager(engine=None, script_engine=None): + def createMachineManager(): return MachineManager() @deprecated("Use ExtruderStack.material = ... and it won't be necessary", "2.7") diff --git a/cura/Settings/ProfilesModel.py b/cura/Settings/ProfilesModel.py index 6353d3ce84..2b75cf1bd2 100644 --- a/cura/Settings/ProfilesModel.py +++ b/cura/Settings/ProfilesModel.py @@ -12,6 +12,11 @@ from UM.Settings.Models.InstanceContainersModel import InstanceContainersModel from cura.QualityManager import QualityManager from cura.Settings.ExtruderManager import ExtruderManager +from typing import List, TYPE_CHECKING + +if TYPE_CHECKING: + from cura.Settings.ExtruderStack import ExtruderStack + ## QML Model for listing the current list of valid quality profiles. # @@ -27,7 +32,6 @@ class ProfilesModel(InstanceContainersModel): self.addRoleName(self.AvailableRole, "available") Application.getInstance().globalContainerStackChanged.connect(self._update) - Application.getInstance().getMachineManager().activeVariantChanged.connect(self._update) Application.getInstance().getMachineManager().activeStackChanged.connect(self._update) Application.getInstance().getMachineManager().activeMaterialChanged.connect(self._update) @@ -54,18 +58,12 @@ class ProfilesModel(InstanceContainersModel): global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack is None: return [] - global_stack_definition = global_container_stack.getBottom() + + global_stack_definition = global_container_stack.definition # Get the list of extruders and place the selected extruder at the front of the list. - extruder_manager = ExtruderManager.getInstance() - active_extruder = extruder_manager.getActiveExtruderStack() - extruder_stacks = extruder_manager.getActiveExtruderStacks() - materials = [global_container_stack.material] - - if active_extruder in extruder_stacks: - extruder_stacks.remove(active_extruder) - extruder_stacks = [active_extruder] + extruder_stacks - materials = [extruder.material for extruder in extruder_stacks] + extruder_stacks = self._getOrderedExtruderStacksList() + materials = [extruder.material for extruder in extruder_stacks] # Fetch the list of usable qualities across all extruders. # The actual list of quality profiles come from the first extruder in the extruder list. @@ -100,32 +98,12 @@ class ProfilesModel(InstanceContainersModel): if global_container_stack is None: return - # Detecting if the machine has multiple extrusion - multiple_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1 - - # Get the list of extruders and place the selected extruder at the front of the list. - extruder_manager = ExtruderManager.getInstance() - active_extruder = extruder_manager.getActiveExtruderStack() - extruder_stacks = extruder_manager.getActiveExtruderStacks() - - if multiple_extrusion: - # Place the active extruder at the front of the list. - # This is a workaround checking if there is an active_extruder or not before moving it to the front of the list. - # Actually, when a printer has multiple extruders, should exist always an active_extruder. However, in some - # cases the active_extruder is still None. - if active_extruder in extruder_stacks: - extruder_stacks.remove(active_extruder) - new_extruder_stacks = [] - if active_extruder is not None: - new_extruder_stacks = [active_extruder] - extruder_stacks = new_extruder_stacks + extruder_stacks + extruder_stacks = self._getOrderedExtruderStacksList() + container_registry = ContainerRegistry.getInstance() # Get a list of usable/available qualities for this machine and material qualities = QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks) - container_registry = ContainerRegistry.getInstance() - machine_manager = Application.getInstance().getMachineManager() - unit = global_container_stack.getBottom().getProperty("layer_height", "unit") if not unit: unit = "" @@ -190,6 +168,8 @@ class ProfilesModel(InstanceContainersModel): yield item continue + machine_manager = Application.getInstance().getMachineManager() + # Quality-changes profile that has no value for layer height. Get the corresponding quality profile and ask that profile. quality_type = profile.getMetaDataEntry("quality_type", None) if quality_type: @@ -201,7 +181,8 @@ class ProfilesModel(InstanceContainersModel): else: # No global container stack in the results: if quality_results: - quality = quality_results[0]["quality"] # Take any of the extruders. + # Take any of the extruders. + quality = quality_results[0]["quality"] else: quality = None if quality and quality.hasProperty("layer_height", "value"): @@ -211,13 +192,27 @@ class ProfilesModel(InstanceContainersModel): # Quality has no value for layer height either. Get the layer height from somewhere lower in the stack. skip_until_container = global_container_stack.material - if not skip_until_container or skip_until_container == ContainerRegistry.getInstance().getEmptyInstanceContainer(): #No material in stack. + if not skip_until_container or skip_until_container == ContainerRegistry.getInstance().getEmptyInstanceContainer(): # No material in stack. skip_until_container = global_container_stack.variant - if not skip_until_container or skip_until_container == ContainerRegistry.getInstance().getEmptyInstanceContainer(): #No variant in stack. + if not skip_until_container or skip_until_container == ContainerRegistry.getInstance().getEmptyInstanceContainer(): # No variant in stack. skip_until_container = global_container_stack.getBottom() self._setItemLayerHeight(item, global_container_stack.getRawProperty("layer_height", "value", skip_until_container = skip_until_container.getId()), unit) # Fall through to the currently loaded material. yield item - def _setItemLayerHeight(self, item, value, unit): + ## Get a list of extruder stacks with the active extruder at the front of the list. + @staticmethod + def _getOrderedExtruderStacksList() -> List["ExtruderStack"]: + extruder_manager = ExtruderManager.getInstance() + extruder_stacks = extruder_manager.getActiveExtruderStacks() + active_extruder = extruder_manager.getActiveExtruderStack() + + if active_extruder in extruder_stacks: + extruder_stacks.remove(active_extruder) + extruder_stacks = [active_extruder] + extruder_stacks + + return extruder_stacks + + @staticmethod + def _setItemLayerHeight(item, value, unit): item["layer_height"] = str(value) + unit item["layer_height_without_unit"] = str(value) diff --git a/cura/Settings/QualityAndUserProfilesModel.py b/cura/Settings/QualityAndUserProfilesModel.py index 9d7d913d5e..2e181c6031 100644 --- a/cura/Settings/QualityAndUserProfilesModel.py +++ b/cura/Settings/QualityAndUserProfilesModel.py @@ -22,47 +22,23 @@ class QualityAndUserProfilesModel(ProfilesModel): # Fetch the list of quality changes. quality_manager = QualityManager.getInstance() - machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.getBottom()) + machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.definition) quality_changes_list = quality_manager.findAllQualityChangesForMachine(machine_definition) - # Detecting if the machine has multiple extrusion - multiple_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1 - # Get the list of extruders extruder_manager = ExtruderManager.getInstance() active_extruder = extruder_manager.getActiveExtruderStack() - extruder_stacks = extruder_manager.getActiveExtruderStacks() - if multiple_extrusion: - # Place the active extruder at the front of the list. - # This is a workaround checking if there is an active_extruder or not before moving it to the front of the list. - # Actually, when a printer has multiple extruders, should exist always an active_extruder. However, in some - # cases the active_extruder is still None. - if active_extruder in extruder_stacks: - extruder_stacks.remove(active_extruder) - new_extruder_stacks = [] - if active_extruder is not None: - new_extruder_stacks = [active_extruder] - else: - # if there is no active extruder, use the first one in the active extruder stacks - active_extruder = extruder_stacks[0] - extruder_stacks = new_extruder_stacks + extruder_stacks + extruder_stacks = self._getOrderedExtruderStacksList() - # Fetch the list of useable qualities across all extruders. + # Fetch the list of usable qualities across all extruders. # The actual list of quality profiles come from the first extruder in the extruder list. - quality_list = quality_manager.findAllUsableQualitiesForMachineAndExtruders(global_container_stack, - extruder_stacks) + quality_list = quality_manager.findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks) # Filter the quality_change by the list of available quality_types quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list]) - - 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") 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 not None and + (qc.getMetaDataEntry("extruder") == active_extruder.definition.getMetaDataEntry("quality_definition") or + qc.getMetaDataEntry("extruder") == active_extruder.definition.getId())] return quality_list + filtered_quality_changes diff --git a/cura/Settings/QualitySettingsModel.py b/cura/Settings/QualitySettingsModel.py index 2ab4e2a9b5..d0379dc510 100644 --- a/cura/Settings/QualitySettingsModel.py +++ b/cura/Settings/QualitySettingsModel.py @@ -224,7 +224,6 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel): if self._extruder_id == "" and settable_per_extruder: continue - label = definition.label if self._i18n_catalog: label = self._i18n_catalog.i18nc(definition.key + " label", label) diff --git a/cura/Settings/SettingInheritanceManager.py b/cura/Settings/SettingInheritanceManager.py index 8c9f5b20d6..0d4cd02cdb 100644 --- a/cura/Settings/SettingInheritanceManager.py +++ b/cura/Settings/SettingInheritanceManager.py @@ -47,21 +47,20 @@ class SettingInheritanceManager(QObject): @pyqtSlot(str, str, result = "QStringList") def getOverridesForExtruder(self, key, extruder_index): - multi_extrusion = self._global_container_stack.getProperty("machine_extruder_count", "value") > 1 - if not multi_extrusion: - return self._settings_with_inheritance_warning - extruder = ExtruderManager.getInstance().getExtruderStack(extruder_index) - if not extruder: - Logger.log("w", "Unable to find extruder for current machine with index %s", extruder_index) - return [] + result = [] - definitions = self._global_container_stack.definition.findDefinitions(key=key) + extruder_stack = ExtruderManager.getInstance().getExtruderStack(extruder_index) + if not extruder_stack: + Logger.log("w", "Unable to find extruder for current machine with index %s", extruder_index) + return result + + definitions = self._global_container_stack.definition.findDefinitions(key = key) if not definitions: Logger.log("w", "Could not find definition for key [%s] (2)", key) - return [] - result = [] + return result + for key in definitions[0].getAllKeys(): - if self._settingIsOverwritingInheritance(key, extruder): + if self._settingIsOverwritingInheritance(key, extruder_stack): result.append(key) return result @@ -78,8 +77,8 @@ class SettingInheritanceManager(QObject): def _onActiveExtruderChanged(self): new_active_stack = ExtruderManager.getInstance().getActiveExtruderStack() - if not new_active_stack: - new_active_stack = self._global_container_stack + # if not new_active_stack: + # new_active_stack = self._global_container_stack if new_active_stack != self._active_container_stack: # Check if changed if self._active_container_stack: # Disconnect signal from old container (if any) diff --git a/cura/Settings/SettingOverrideDecorator.py b/cura/Settings/SettingOverrideDecorator.py index 4e0893a35f..5026f9760d 100644 --- a/cura/Settings/SettingOverrideDecorator.py +++ b/cura/Settings/SettingOverrideDecorator.py @@ -27,11 +27,7 @@ class SettingOverrideDecorator(SceneNodeDecorator): self._stack = PerObjectContainerStack(stack_id = id(self)) self._stack.setDirty(False) # This stack does not need to be saved. self._stack.addContainer(InstanceContainer(container_id = "SettingOverrideInstanceContainer")) - - if ExtruderManager.getInstance().extruderCount > 1: - self._extruder_stack = ExtruderManager.getInstance().getExtruderStack(0).getId() - else: - self._extruder_stack = None + self._extruder_stack = ExtruderManager.getInstance().getExtruderStack(0).getId() self._stack.propertyChanged.connect(self._onSettingChanged) diff --git a/cura/Settings/UserProfilesModel.py b/cura/Settings/UserProfilesModel.py index aa815ef4aa..5ae9055759 100644 --- a/cura/Settings/UserProfilesModel.py +++ b/cura/Settings/UserProfilesModel.py @@ -22,47 +22,23 @@ class UserProfilesModel(ProfilesModel): # Fetch the list of quality changes. quality_manager = QualityManager.getInstance() - machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.getBottom()) + machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.definition) quality_changes_list = quality_manager.findAllQualityChangesForMachine(machine_definition) - # Detecting if the machine has multiple extrusion - multiple_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1 - # Get the list of extruders and place the selected extruder at the front of the list. extruder_manager = ExtruderManager.getInstance() active_extruder = extruder_manager.getActiveExtruderStack() - extruder_stacks = extruder_manager.getActiveExtruderStacks() - if multiple_extrusion: - # Place the active extruder at the front of the list. - # This is a workaround checking if there is an active_extruder or not before moving it to the front of the list. - # Actually, when a printer has multiple extruders, should exist always an active_extruder. However, in some - # cases the active_extruder is still None. - if active_extruder in extruder_stacks: - extruder_stacks.remove(active_extruder) - new_extruder_stacks = [] - if active_extruder is not None: - new_extruder_stacks = [active_extruder] - else: - # if there is no active extruder, use the first one in the active extruder stacks - active_extruder = extruder_stacks[0] - extruder_stacks = new_extruder_stacks + extruder_stacks + extruder_stacks = self._getOrderedExtruderStacksList() - # Fetch the list of useable qualities across all extruders. + # Fetch the list of usable qualities across all extruders. # The actual list of quality profiles come from the first extruder in the extruder list. - quality_list = quality_manager.findAllUsableQualitiesForMachineAndExtruders(global_container_stack, - extruder_stacks) + quality_list = quality_manager.findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks) # Filter the quality_change by the list of available quality_types quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list]) - - 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") 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 not None and + (qc.getMetaDataEntry("extruder") == active_extruder.definition.getMetaDataEntry("quality_definition") or + qc.getMetaDataEntry("extruder") == active_extruder.definition.getId())] return filtered_quality_changes diff --git a/plugins/3MFReader/ThreeMFReader.py b/plugins/3MFReader/ThreeMFReader.py index a34bf771d7..786226ae61 100755 --- a/plugins/3MFReader/ThreeMFReader.py +++ b/plugins/3MFReader/ThreeMFReader.py @@ -107,20 +107,13 @@ class ThreeMFReader(MeshReader): um_node.addDecorator(SettingOverrideDecorator()) global_container_stack = Application.getInstance().getGlobalContainerStack() + # Ensure the correct next container for the SettingOverride decorator is set. if global_container_stack: - multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1 + default_stack = ExtruderManager.getInstance().getExtruderStack(0) - # Ensure that all extruder data is reset - if not multi_extrusion: - default_stack_id = global_container_stack.getId() - else: - default_stack = ExtruderManager.getInstance().getExtruderStack(0) - if default_stack: - default_stack_id = default_stack.getId() - else: - default_stack_id = global_container_stack.getId() - um_node.callDecoration("setActiveExtruder", default_stack_id) + if default_stack: + um_node.callDecoration("setActiveExtruder", default_stack.getId()) # Get the definition & set it definition = QualityManager.getInstance().getParentMachineDefinition(global_container_stack.getBottom()) @@ -139,7 +132,7 @@ class ThreeMFReader(MeshReader): else: Logger.log("w", "Unable to find extruder in position %s", setting_value) continue - setting_container.setProperty(key,"value", setting_value) + setting_container.setProperty(key, "value", setting_value) if len(um_node.getChildren()) > 0: group_decorator = GroupDecorator() diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 28d12bf2d5..a3aadc79b8 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -644,9 +644,10 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # Get the stack(s) saved in the workspace. Logger.log("d", "Workspace loading is checking stacks containers...") - # -- # load global stack file try: + stack = None + if self._resolve_strategies["machine"] == "override": container_stacks = self._container_registry.findContainerStacks(id = global_stack_id_original) stack = container_stacks[0] @@ -682,12 +683,11 @@ class ThreeMFWorkspaceReader(WorkspaceReader): self._container_registry.addContainer(stack) containers_added.append(stack) else: - Logger.log("e", "Resolve strategy of %s for machine is not supported", - self._resolve_strategies["machine"]) + Logger.log("e", "Resolve strategy of %s for machine is not supported", 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")) + stack.setDefinitionChanges(CuraStackBuilder.createDefinitionChangesContainer(stack, stack.getId() + "_settings")) global_stack = stack Job.yieldThread() except: @@ -697,16 +697,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader): self._container_registry.removeContainer(container.getId()) return - # - # Use the number of extruders from the global stack instead of the number of extruder stacks this project file - # contains. The Custom FDM Printer can have multiple extruders, but the actual number of extruders in used is - # defined in the global stack. - # Because for single-extrusion machines, there won't be an extruder stack, so relying on the the extruder count - # in the global stack can avoid problems in those cases. - # - extruder_count_from_global_stack = global_stack.getProperty("machine_extruder_count", "value") - - # -- # load extruder stack files try: for extruder_stack_file in extruder_stack_files: @@ -749,9 +739,15 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # 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")) - if global_stack.getProperty("machine_extruder_count", "value") > 1: - extruder_stacks.append(stack) + stack.setDefinitionChanges(CuraStackBuilder.createDefinitionChangesContainer(stack, stack.getId() + "_settings")) + + extruder_stacks.append(stack) + + # If not extruder stacks were saved in the project file (pre 3.1) create one manually + # We re-use the container registry's addExtruderStackForSingleExtrusionMachine method for this + if not extruder_stacks: + self._container_registry.addExtruderStackForSingleExtrusionMachine(global_stack, "fdmextruder") + 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. @@ -784,7 +780,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader): for stack in [global_stack] + extruder_stacks: stack.replaceContainer(_ContainerIndexes.Quality, empty_quality_container) - # # Replacing the old containers if resolve is "new". # When resolve is "new", some containers will get renamed, so all the other containers that reference to those # MUST get updated too. diff --git a/plugins/3MFWriter/ThreeMFWriter.py b/plugins/3MFWriter/ThreeMFWriter.py index a764d30fac..6254bef03a 100644 --- a/plugins/3MFWriter/ThreeMFWriter.py +++ b/plugins/3MFWriter/ThreeMFWriter.py @@ -87,7 +87,7 @@ class ThreeMFWriter(MeshWriter): if stack is not None: changed_setting_keys = set(stack.getTop().getAllKeys()) - # Ensure that we save the extruder used for this object. + # Ensure that we save the extruder used for this object in a multi-extrusion setup if stack.getProperty("machine_extruder_count", "value") > 1: changed_setting_keys.add("extruder_nr") diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index eb0337c4f2..b922baea52 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -159,13 +159,9 @@ class StartSliceJob(Job): self._buildGlobalSettingsMessage(stack) self._buildGlobalInheritsStackMessage(stack) - # Only add extruder stacks if there are multiple extruders - # Single extruder machines only use the global stack to store setting values - if stack.getProperty("machine_extruder_count", "value") > 1: - for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(stack.getId()): - self._buildExtruderMessage(extruder_stack) - else: - self._buildExtruderMessageFromGlobalStack(stack) + # Build messages for extruder stacks + for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(stack.getId()): + self._buildExtruderMessage(extruder_stack) for group in object_groups: group_message = self._slice_message.addRepeatedMessage("object_lists") @@ -251,19 +247,6 @@ class StartSliceJob(Job): setting.value = str(stack.getProperty(key, "value")).encode("utf-8") Job.yieldThread() - ## Create extruder message from global stack - def _buildExtruderMessageFromGlobalStack(self, stack): - message = self._slice_message.addRepeatedMessage("extruders") - - for key in stack.getAllKeys(): - # Do not send settings that are not settable_per_extruder. - if not stack.getProperty(key, "settable_per_extruder"): - continue - setting = message.getMessage("settings").addRepeatedMessage("settings") - setting.name = key - setting.value = str(stack.getProperty(key, "value")).encode("utf-8") - Job.yieldThread() - ## Sends all global settings to the engine. # # The settings are taken from the global stack. This does not include any diff --git a/plugins/MachineSettingsAction/MachineSettingsAction.py b/plugins/MachineSettingsAction/MachineSettingsAction.py index 360dae7a2c..2de5b6e9b8 100755 --- a/plugins/MachineSettingsAction/MachineSettingsAction.py +++ b/plugins/MachineSettingsAction/MachineSettingsAction.py @@ -116,8 +116,7 @@ class MachineSettingsAction(MachineAction): @pyqtSlot(int) def setMachineExtruderCount(self, extruder_count): - machine_manager = Application.getInstance().getMachineManager() - extruder_manager = ExtruderManager.getInstance() + extruder_manager = Application.getInstance().getExtruderManager() definition_changes_container = self._global_container_stack.definitionChanges if not self._global_container_stack or definition_changes_container == self._empty_container: @@ -127,34 +126,6 @@ class MachineSettingsAction(MachineAction): if extruder_count == previous_extruder_count: return - extruder_material_id = None - extruder_variant_id = None - if extruder_count == 1: - # Get the material and variant of the first extruder before setting the number extruders to 1 - if machine_manager.hasMaterials: - extruder_material_id = machine_manager.allActiveMaterialIds[extruder_manager.extruderIds["0"]] - if machine_manager.hasVariants: - extruder_variant_id = machine_manager.allActiveVariantIds[extruder_manager.extruderIds["0"]] - - # Copy any settable_per_extruder setting value from the extruders to the global stack - extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks() - extruder_stacks.reverse() # make sure the first extruder is done last, so its settings override any higher extruder settings - - global_user_container = self._global_container_stack.getTop() - for extruder_stack in extruder_stacks: - extruder_index = extruder_stack.getMetaDataEntry("position") - extruder_user_container = extruder_stack.getTop() - for setting_instance in extruder_user_container.findInstances(): - setting_key = setting_instance.definition.key - settable_per_extruder = self._global_container_stack.getProperty(setting_key, "settable_per_extruder") - - if settable_per_extruder: - limit_to_extruder = self._global_container_stack.getProperty(setting_key, "limit_to_extruder") - - if limit_to_extruder == "-1" or limit_to_extruder == extruder_index: - global_user_container.setProperty(setting_key, "value", extruder_user_container.getProperty(setting_key, "value")) - extruder_user_container.removeInstance(setting_key) - # reset all extruder number settings whose value is no longer valid for setting_instance in self._global_container_stack.userChanges.findInstances(): setting_key = setting_instance.definition.key @@ -177,52 +148,29 @@ class MachineSettingsAction(MachineAction): definition_changes_container.setProperty("machine_extruder_count", "value", extruder_count) - if extruder_count > 1: - # Multiextrusion + # Make sure one of the extruder stacks is active + extruder_manager.setActiveExtruderIndex(0) - # Make sure one of the extruder stacks is active - if extruder_manager.activeExtruderIndex == -1: - extruder_manager.setActiveExtruderIndex(0) + # Move settable_per_extruder values out of the global container + # After CURA-4482 this should not be the case anymore, but we still want to support older project files. + global_user_container = self._global_container_stack.getTop() - # Move settable_per_extruder values out of the global container - if previous_extruder_count == 1: - extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks() - global_user_container = self._global_container_stack.getTop() + if previous_extruder_count == 1: + extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks() + global_user_container = self._global_container_stack.getTop() - for setting_instance in global_user_container.findInstances(): - setting_key = setting_instance.definition.key - settable_per_extruder = self._global_container_stack.getProperty(setting_key, "settable_per_extruder") - if settable_per_extruder: - limit_to_extruder = int(self._global_container_stack.getProperty(setting_key, "limit_to_extruder")) - extruder_stack = extruder_stacks[max(0, limit_to_extruder)] - extruder_stack.getTop().setProperty(setting_key, "value", global_user_container.getProperty(setting_key, "value")) - global_user_container.removeInstance(setting_key) - else: - # Single extrusion + for setting_instance in global_user_container.findInstances(): + setting_key = setting_instance.definition.key + settable_per_extruder = self._global_container_stack.getProperty(setting_key, "settable_per_extruder") - # Make sure the machine stack is active - if extruder_manager.activeExtruderIndex > -1: - extruder_manager.setActiveExtruderIndex(-1) - - # Restore material and variant on global stack - # MachineManager._onGlobalContainerChanged removes the global material and variant of multiextruder machines - if extruder_material_id or extruder_variant_id: - # Prevent the DiscardOrKeepProfileChangesDialog from popping up (twice) if there are user changes - # The dialog is not relevant here, since we're restoring the previous situation as good as possible - preferences = Preferences.getInstance() - choice_on_profile_override = preferences.getValue("cura/choice_on_profile_override") - preferences.setValue("cura/choice_on_profile_override", "always_keep") - - if extruder_material_id: - machine_manager.setActiveMaterial(extruder_material_id) - if extruder_variant_id: - machine_manager.setActiveVariant(extruder_variant_id) - - preferences.setValue("cura/choice_on_profile_override", choice_on_profile_override) + if settable_per_extruder: + limit_to_extruder = int(self._global_container_stack.getProperty(setting_key, "limit_to_extruder")) + extruder_stack = extruder_stacks[max(0, limit_to_extruder)] + extruder_stack.getTop().setProperty(setting_key, "value", global_user_container.getProperty(setting_key, "value")) + global_user_container.removeInstance(setting_key) self.forceUpdate() - @pyqtSlot() def forceUpdate(self): # Force rebuilding the build volume by reloading the global container stack. @@ -275,16 +223,13 @@ class MachineSettingsAction(MachineAction): if not self._global_container_stack.getMetaDataEntry("has_materials", False): return - machine_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value") - if machine_extruder_count > 1: - material = ExtruderManager.getInstance().getActiveExtruderStack().material - else: - material = self._global_container_stack.material + material = ExtruderManager.getInstance().getActiveExtruderStack().material material_diameter = material.getProperty("material_diameter", "value") - if not material_diameter: # in case of "empty" material + if not material_diameter: + # in case of "empty" material material_diameter = 0 - material_approximate_diameter = str(round(material_diameter)) + material_approximate_diameter = str(round(material_diameter)) definition_changes = self._global_container_stack.definitionChanges machine_diameter = definition_changes.getProperty("material_diameter", "value") if not machine_diameter: @@ -294,10 +239,7 @@ class MachineSettingsAction(MachineAction): if material_approximate_diameter != machine_approximate_diameter: Logger.log("i", "The the currently active material(s) do not match the diameter set for the printer. Finding alternatives.") - if machine_extruder_count > 1: - stacks = ExtruderManager.getInstance().getExtruderStacks() - else: - stacks = [self._global_container_stack] + stacks = ExtruderManager.getInstance().getExtruderStacks() if self._global_container_stack.getMetaDataEntry("has_machine_materials", False): materials_definition = self._global_container_stack.definition.getId() @@ -338,7 +280,7 @@ class MachineSettingsAction(MachineAction): search_criteria["id"] = stack.getMetaDataEntry("preferred_material") materials = self._container_registry.findInstanceContainers(**search_criteria) if not materials: - # Preferrd material with new diameter is not found, search for any material + # Preferred material with new diameter is not found, search for any material search_criteria.pop("id", None) materials = self._container_registry.findInstanceContainers(**search_criteria) if not materials: diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py b/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py index d0cb53c4f8..dc6efafa13 100644 --- a/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py +++ b/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py @@ -78,31 +78,26 @@ class PerObjectSettingsTool(Tool): def _onGlobalContainerChanged(self): global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: + + # used for enabling or disabling per extruder settings per object self._multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1 - # Ensure that all extruder data is reset - if not self._multi_extrusion: - default_stack_id = global_container_stack.getId() - else: - default_stack = ExtruderManager.getInstance().getExtruderStack(0) - if default_stack: - default_stack_id = default_stack.getId() - else: - default_stack_id = global_container_stack.getId() + extruder_stack = ExtruderManager.getInstance().getExtruderStack(0) - root_node = Application.getInstance().getController().getScene().getRoot() - for node in DepthFirstIterator(root_node): - new_stack_id = default_stack_id - # Get position of old extruder stack for this node - old_extruder_pos = node.callDecoration("getActiveExtruderPosition") - if old_extruder_pos is not None: - # Fetch current (new) extruder stack at position - new_stack = ExtruderManager.getInstance().getExtruderStack(old_extruder_pos) - if new_stack: - new_stack_id = new_stack.getId() - node.callDecoration("setActiveExtruder", new_stack_id) + if extruder_stack: + root_node = Application.getInstance().getController().getScene().getRoot() + for node in DepthFirstIterator(root_node): + new_stack_id = extruder_stack.getId() + # Get position of old extruder stack for this node + old_extruder_pos = node.callDecoration("getActiveExtruderPosition") + if old_extruder_pos is not None: + # Fetch current (new) extruder stack at position + new_stack = ExtruderManager.getInstance().getExtruderStack(old_extruder_pos) + if new_stack: + new_stack_id = new_stack.getId() + node.callDecoration("setActiveExtruder", new_stack_id) - self._updateEnabled() + self._updateEnabled() def _updateEnabled(self): selected_objects = Selection.getAllSelectedObjects() diff --git a/plugins/SliceInfoPlugin/SliceInfo.py b/plugins/SliceInfoPlugin/SliceInfo.py index 0514c4dacf..79963a4740 100755 --- a/plugins/SliceInfoPlugin/SliceInfo.py +++ b/plugins/SliceInfoPlugin/SliceInfo.py @@ -87,15 +87,10 @@ class SliceInfo(Extension): data["active_machine"] = {"definition_id": global_container_stack.definition.getId(), "manufacturer": global_container_stack.definition.getMetaData().get("manufacturer","")} + # add extruder specific data to slice info data["extruders"] = [] - extruder_count = len(global_container_stack.extruders) - extruders = [] - if extruder_count > 1: - extruders = list(ExtruderManager.getInstance().getMachineExtruders(global_container_stack.getId())) - extruders = sorted(extruders, key = lambda extruder: extruder.getMetaDataEntry("position")) - - if not extruders: - extruders = [global_container_stack] + extruders = list(ExtruderManager.getInstance().getMachineExtruders(global_container_stack.getId())) + extruders = sorted(extruders, key = lambda extruder: extruder.getMetaDataEntry("position")) for extruder in extruders: extruder_dict = dict() diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py index 8f0c9a4dc1..bc1b08cc5e 100644 --- a/plugins/SolidView/SolidView.py +++ b/plugins/SolidView/SolidView.py @@ -46,19 +46,10 @@ class SolidView(View): self._disabled_shader.setUniformValue("u_diffuseColor2", Color(*theme.getColor("model_unslicable_alt").getRgb())) self._disabled_shader.setUniformValue("u_width", 50.0) - multi_extrusion = False - global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: - multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1 - - if multi_extrusion: - support_extruder_nr = global_container_stack.getProperty("support_extruder_nr", "value") - support_angle_stack = ExtruderManager.getInstance().getExtruderStack(support_extruder_nr) - if not support_angle_stack: - support_angle_stack = global_container_stack - else: - support_angle_stack = global_container_stack + support_extruder_nr = global_container_stack.getProperty("support_extruder_nr", "value") + support_angle_stack = ExtruderManager.getInstance().getExtruderStack(support_extruder_nr) if Preferences.getInstance().getValue("view/show_overhang"): angle = support_angle_stack.getProperty("support_angle", "value") @@ -71,33 +62,26 @@ class SolidView(View): else: self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0))) - for node in DepthFirstIterator(scene.getRoot()): if not node.render(renderer): if node.getMeshData() and node.isVisible(): uniforms = {} shade_factor = 1.0 - if not multi_extrusion: - if global_container_stack: - material = global_container_stack.findContainer({ "type": "material" }) - material_color = material.getMetaDataEntry("color_code", default = self._extruders_model.defaultColors[0]) if material else self._extruders_model.defaultColors[0] - else: - material_color = self._extruders_model.defaultColors[0] - else: - # Get color to render this mesh in from ExtrudersModel - extruder_index = 0 - extruder_id = node.callDecoration("getActiveExtruder") - if extruder_id: - extruder_index = max(0, self._extruders_model.find("id", extruder_id)) - try: - material_color = self._extruders_model.getItem(extruder_index)["color"] - except KeyError: - material_color = self._extruders_model.defaultColors[0] + # Get color to render this mesh in from ExtrudersModel + extruder_index = 0 + extruder_id = node.callDecoration("getActiveExtruder") + if extruder_id: + extruder_index = max(0, self._extruders_model.find("id", extruder_id)) + try: + material_color = self._extruders_model.getItem(extruder_index)["color"] + except KeyError: + material_color = self._extruders_model.defaultColors[0] + + if extruder_index != ExtruderManager.getInstance().activeExtruderIndex: + # Shade objects that are printed with the non-active extruder 25% darker + shade_factor = 0.6 - if extruder_index != ExtruderManager.getInstance().activeExtruderIndex: - # Shade objects that are printed with the non-active extruder 25% darker - shade_factor = 0.6 try: # Colors are passed as rgb hex strings (eg "#ffffff"), and the shader needs # an rgba list of floats (eg [1.0, 1.0, 1.0, 1.0]) diff --git a/plugins/UltimakerMachineActions/UM2UpgradeSelection.py b/plugins/UltimakerMachineActions/UM2UpgradeSelection.py index c84033a98e..db277bc485 100644 --- a/plugins/UltimakerMachineActions/UM2UpgradeSelection.py +++ b/plugins/UltimakerMachineActions/UM2UpgradeSelection.py @@ -37,7 +37,7 @@ class UM2UpgradeSelection(MachineAction): def setHasVariants(self, has_variants = True): global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: - variant_container = global_container_stack.variant + variant_container = global_container_stack.extruders["0"].variant variant_index = global_container_stack.getContainerIndex(variant_container) if has_variants: @@ -52,7 +52,7 @@ class UM2UpgradeSelection(MachineAction): search_criteria = { "type": "variant", "definition": "ultimaker2", "id": "*0.4*" } containers = self._container_registry.findInstanceContainers(**search_criteria) if containers: - global_container_stack.variant = containers[0] + global_container_stack.extruders["0"].variant = containers[0] else: # The metadata entry is stored in an ini, and ini files are parsed as strings only. # Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False. diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 86f90e8970..fa40819eeb 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -549,7 +549,7 @@ class XmlMaterialProfile(InstanceContainer): definitions = ContainerRegistry.getInstance().findDefinitionContainers(id = machine_id) if not definitions: - Logger.log("w", "No definition found for machine ID %s", machine_id) + # Logger.log("w", "No definition found for machine ID %s", machine_id) continue definition = definitions[0] diff --git a/resources/definitions/101Hero.def.json b/resources/definitions/101Hero.def.json index 3d19aef626..aaea743b47 100644 --- a/resources/definitions/101Hero.def.json +++ b/resources/definitions/101Hero.def.json @@ -7,10 +7,6 @@ "visible": true, "author": "rikky", "manufacturer": "101Hero", - "machine_extruder_trains": - { - "0": "fdmextruder" - }, "file_formats": "text/x-gcode", "platform": "101hero-platform.stl", "supports_usb_connection": true diff --git a/resources/definitions/3dator.def.json b/resources/definitions/3dator.def.json index b72a49a35b..513ee8f0e1 100644 --- a/resources/definitions/3dator.def.json +++ b/resources/definitions/3dator.def.json @@ -10,11 +10,7 @@ "file_formats": "text/x-gcode", "icon": "icon_ultimaker2", "supports_usb_connection": true, - "platform": "3dator_platform.stl", - "machine_extruder_trains": - { - "0": "fdmextruder" - } + "platform": "3dator_platform.stl" }, "overrides": { @@ -29,7 +25,6 @@ "layer_height": { "default_value": 0.2 }, "speed_print": { "default_value": 50 }, "speed_infill": { "default_value": 60 }, - "machine_extruder_count": { "default_value": 1 }, "machine_heated_bed": { "default_value": true }, "machine_center_is_zero": { "default_value": false }, "machine_height": { "default_value": 260 }, diff --git a/resources/definitions/fdmextruder.def.json b/resources/definitions/fdmextruder.def.json index 8ed194fc2d..ada35fafe0 100644 --- a/resources/definitions/fdmextruder.def.json +++ b/resources/definitions/fdmextruder.def.json @@ -8,7 +8,8 @@ "author": "Ultimaker", "manufacturer": "Unknown", "setting_version": 1, - "visible": false + "visible": false, + "position": "0" }, "settings": { diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index 2fd19a8a03..fb2bd3bff2 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -177,7 +177,7 @@ UM.MainWindow MenuSeparator { } - MenuItem { text: catalog.i18nc("@action:inmenu", "Set as Active Extruder"); onTriggered: ExtruderManager.setActiveExtruderIndex(model.index) } + 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) diff --git a/resources/qml/ExtruderButton.qml b/resources/qml/ExtruderButton.qml index 99196b0c9f..9212c705f7 100644 --- a/resources/qml/ExtruderButton.qml +++ b/resources/qml/ExtruderButton.qml @@ -18,7 +18,7 @@ Button style: UM.Theme.styles.tool_button; iconSource: UM.Theme.getIcon("extruder_button") - checked: ExtruderManager.selectedObjectExtruders.indexOf(extruder.id) != -1 + checked: Cura.ExtruderManager.selectedObjectExtruders.indexOf(extruder.id) != -1 enabled: UM.Selection.hasSelection property color customColor: base.hovered ? UM.Theme.getColor("button_hover") : UM.Theme.getColor("button"); diff --git a/resources/qml/Menus/ContextMenu.qml b/resources/qml/Menus/ContextMenu.qml index 39d497722f..b2c95ebcd8 100644 --- a/resources/qml/Menus/ContextMenu.qml +++ b/resources/qml/Menus/ContextMenu.qml @@ -31,7 +31,7 @@ Menu visible: base.shouldShowExtruders enabled: UM.Selection.hasSelection checkable: true - checked: ExtruderManager.selectedObjectExtruders.indexOf(model.id) != -1 + checked: Cura.ExtruderManager.selectedObjectExtruders.indexOf(model.id) != -1 onTriggered: CuraActions.setExtruderForSelection(model.id) shortcut: "Ctrl+" + (model.index + 1) } diff --git a/resources/qml/Menus/MaterialMenu.qml b/resources/qml/Menus/MaterialMenu.qml index d3ecfb69fe..a6666b67f4 100644 --- a/resources/qml/Menus/MaterialMenu.qml +++ b/resources/qml/Menus/MaterialMenu.qml @@ -78,16 +78,16 @@ Menu { text: model.name checkable: true - checked: model.id == Cura.MachineManager.allActiveMaterialIds[ExtruderManager.extruderIds[extruderIndex]] + checked: model.id == Cura.MachineManager.allActiveMaterialIds[Cura.ExtruderManager.extruderIds[extruderIndex]] 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); + var activeExtruderIndex = Cura.ExtruderManager.activeExtruderIndex; + Cura.ExtruderManager.setActiveExtruderIndex(extruderIndex); Cura.MachineManager.setActiveMaterial(model.id); - ExtruderManager.setActiveExtruderIndex(activeExtruderIndex); + Cura.ExtruderManager.setActiveExtruderIndex(activeExtruderIndex); } } onObjectAdded: menu.insertItem(index, object) @@ -121,16 +121,16 @@ Menu { text: model.name checkable: true - checked: model.id == Cura.MachineManager.allActiveMaterialIds[ExtruderManager.extruderIds[extruderIndex]] + checked: model.id == Cura.MachineManager.allActiveMaterialIds[Cura.ExtruderManager.extruderIds[extruderIndex]] 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); + var activeExtruderIndex = Cura.ExtruderManager.activeExtruderIndex; + Cura.ExtruderManager.setActiveExtruderIndex(extruderIndex); Cura.MachineManager.setActiveMaterial(model.id); - ExtruderManager.setActiveExtruderIndex(activeExtruderIndex); + Cura.ExtruderManager.setActiveExtruderIndex(activeExtruderIndex); } } onObjectAdded: brandMaterialsMenu.insertItem(index, object) diff --git a/resources/qml/Menus/NozzleMenu.qml b/resources/qml/Menus/NozzleMenu.qml index e9f2df1f38..f70e639872 100644 --- a/resources/qml/Menus/NozzleMenu.qml +++ b/resources/qml/Menus/NozzleMenu.qml @@ -44,15 +44,15 @@ Menu visible: printerConnected && Cura.MachineManager.printerOutputDevices[0].hotendIds.length > extruderIndex && !isClusterPrinter onTriggered: { - var activeExtruderIndex = ExtruderManager.activeExtruderIndex; - ExtruderManager.setActiveExtruderIndex(extruderIndex); + var activeExtruderIndex = Cura.ExtruderManager.activeExtruderIndex; + Cura.ExtruderManager.setActiveExtruderIndex(extruderIndex); var hotendId = Cura.MachineManager.printerOutputDevices[0].hotendIds[extruderIndex]; var itemIndex = nozzleInstantiator.model.find("name", hotendId); if(itemIndex > -1) { Cura.MachineManager.setActiveVariant(nozzleInstantiator.model.getItem(itemIndex).id); } - ExtruderManager.setActiveExtruderIndex(activeExtruderIndex); + Cura.ExtruderManager.setActiveExtruderIndex(activeExtruderIndex); } } @@ -75,14 +75,14 @@ Menu MenuItem { text: model.name checkable: true - checked: model.id == Cura.MachineManager.allActiveVariantIds[ExtruderManager.extruderIds[extruderIndex]] + checked: model.id == Cura.MachineManager.allActiveVariantIds[Cura.ExtruderManager.extruderIds[extruderIndex]] exclusiveGroup: group onTriggered: { - var activeExtruderIndex = ExtruderManager.activeExtruderIndex; - ExtruderManager.setActiveExtruderIndex(extruderIndex); + var activeExtruderIndex = Cura.ExtruderManager.activeExtruderIndex; + Cura.ExtruderManager.setActiveExtruderIndex(extruderIndex); Cura.MachineManager.setActiveVariant(model.id); - ExtruderManager.setActiveExtruderIndex(activeExtruderIndex); + Cura.ExtruderManager.setActiveExtruderIndex(activeExtruderIndex); } } onObjectAdded: menu.insertItem(index, object) diff --git a/resources/qml/Menus/ProfileMenu.qml b/resources/qml/Menus/ProfileMenu.qml index fecea5ef99..edce2641af 100644 --- a/resources/qml/Menus/ProfileMenu.qml +++ b/resources/qml/Menus/ProfileMenu.qml @@ -41,7 +41,7 @@ Menu MenuItem { - text: model.name + " - " + model.layer_height + text: model.name checkable: true checked: Cura.MachineManager.activeQualityChangesId == model.id exclusiveGroup: group diff --git a/resources/qml/Preferences/ProfilesPage.qml b/resources/qml/Preferences/ProfilesPage.qml index c7b6b3e933..e3ba9b23a4 100644 --- a/resources/qml/Preferences/ProfilesPage.qml +++ b/resources/qml/Preferences/ProfilesPage.qml @@ -208,7 +208,7 @@ UM.ManagementPage anchors.right: parent.right anchors.bottom: parent.bottom - currentIndex: ExtruderManager.extruderCount > 0 ? ExtruderManager.activeExtruderIndex + 1 : 0 + currentIndex: Cura.ExtruderManager.extruderCount > 0 ? Cura.ExtruderManager.activeExtruderIndex + 1 : 0 ProfileTab { diff --git a/resources/qml/PrintMonitor.qml b/resources/qml/PrintMonitor.qml index ce169ba714..e69f7cf4fd 100644 --- a/resources/qml/PrintMonitor.qml +++ b/resources/qml/PrintMonitor.qml @@ -87,7 +87,7 @@ Column Label //Extruder name. { - text: ExtruderManager.getExtruderName(index) != "" ? ExtruderManager.getExtruderName(index) : catalog.i18nc("@label", "Extruder") + text: Cura.ExtruderManager.getExtruderName(index) != "" ? Cura.ExtruderManager.getExtruderName(index) : catalog.i18nc("@label", "Extruder") color: UM.Theme.getColor("text") font: UM.Theme.getFont("default") anchors.left: parent.left diff --git a/resources/qml/Settings/SettingItem.qml b/resources/qml/Settings/SettingItem.qml index 2bf2c17273..6234e5f1f7 100644 --- a/resources/qml/Settings/SettingItem.qml +++ b/resources/qml/Settings/SettingItem.qml @@ -157,7 +157,7 @@ Item { var tooltipText = catalog.i18nc("@label", "This setting is always shared between all extruders. Changing it here will change the value for all extruders") + "."; if ((resolve != "None") && (stackLevel != 0)) { // We come here if a setting has a resolve and the setting is not manually edited. - tooltipText += " " + catalog.i18nc("@label", "The value is resolved from per-extruder values ") + "[" + ExtruderManager.getInstanceExtruderValues(definition.key) + "]."; + tooltipText += " " + catalog.i18nc("@label", "The value is resolved from per-extruder values ") + "[" + Cura.ExtruderManager.getInstanceExtruderValues(definition.key) + "]."; } base.showTooltip(tooltipText); } diff --git a/resources/qml/Settings/SettingView.qml b/resources/qml/Settings/SettingView.qml index c116fa933a..d1c8ae2c51 100644 --- a/resources/qml/Settings/SettingView.qml +++ b/resources/qml/Settings/SettingView.qml @@ -275,7 +275,7 @@ Item Behavior on opacity { NumberAnimation { duration: 100 } } enabled: { - if(!ExtruderManager.activeExtruderStackId && machineExtruderCount.properties.value > 1) + if (!Cura.ExtruderManager.activeExtruderStackId && machineExtruderCount.properties.value > 1) { // disable all controls on the global tab, except categories return model.type == "category" @@ -345,12 +345,12 @@ Item if(inheritStackProvider.properties.limit_to_extruder != null && inheritStackProvider.properties.limit_to_extruder >= 0) { //We have limit_to_extruder, so pick that stack. - return ExtruderManager.extruderIds[String(inheritStackProvider.properties.limit_to_extruder)]; + return Cura.ExtruderManager.extruderIds[String(inheritStackProvider.properties.limit_to_extruder)]; } - if(ExtruderManager.activeExtruderStackId) + if(Cura.ExtruderManager.activeExtruderStackId) { //We're on an extruder tab. Pick the current extruder. - return ExtruderManager.activeExtruderStackId; + return Cura.ExtruderManager.activeExtruderStackId; } //No extruder tab is selected. Pick the global stack. Shouldn't happen any more since we removed the global tab. return activeMachineId; diff --git a/resources/qml/SidebarHeader.qml b/resources/qml/SidebarHeader.qml index 78e21f3a68..8bba4cf6fd 100644 --- a/resources/qml/SidebarHeader.qml +++ b/resources/qml/SidebarHeader.qml @@ -14,7 +14,7 @@ Column { id: base; - property int currentExtruderIndex: ExtruderManager.activeExtruderIndex; + property int currentExtruderIndex: Cura.ExtruderManager.activeExtruderIndex; property bool currentExtruderVisible: extrudersList.visible; spacing: Math.floor(UM.Theme.getSize("sidebar_margin").width * 0.9) @@ -93,7 +93,7 @@ Column onClicked: { forceActiveFocus() // Changing focus applies the currently-being-typed values so it can change the displayed setting values. - ExtruderManager.setActiveExtruderIndex(index); + Cura.ExtruderManager.setActiveExtruderIndex(index); } style: ButtonStyle diff --git a/resources/qml/SidebarSimple.qml b/resources/qml/SidebarSimple.qml index 549d203c4d..020b75e3ce 100644 --- a/resources/qml/SidebarSimple.qml +++ b/resources/qml/SidebarSimple.qml @@ -19,7 +19,7 @@ Item property Action configureSettings; property variant minimumPrintTime: PrintInformation.minimumPrintTime; property variant maximumPrintTime: PrintInformation.maximumPrintTime; - property bool settingsEnabled: ExtruderManager.activeExtruderStackId || machineExtruderCount.properties.value == 1 + property bool settingsEnabled: Cura.ExtruderManager.activeExtruderStackId || machineExtruderCount.properties.value == 1 Component.onCompleted: PrintInformation.enabled = true Component.onDestruction: PrintInformation.enabled = false