diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py index 264f7bbd8b..dc9fb55902 100644 --- a/cura/Machines/MaterialManager.py +++ b/cura/Machines/MaterialManager.py @@ -131,8 +131,8 @@ class MaterialManager(QObject): # Fetch the available materials (ContainerNode) for the current active machine and extruder setup. materials = self.getAvailableMaterials(machine.definition.getId(), nozzle_name) - compatible_material_diameter = str(round(extruder_stack.getCompatibleMaterialDiameter())) - result = {key: material for key, material in materials.items() if material.container and material.container.getMetaDataEntry("approximate_diameter") == compatible_material_diameter} + compatible_material_diameter = extruder_stack.getApproximateMaterialDiameter() + result = {key: material for key, material in materials.items() if material.container and float(material.container.getMetaDataEntry("approximate_diameter")) == compatible_material_diameter} return result # @@ -171,9 +171,11 @@ class MaterialManager(QObject): def getMaterialNodeByType(self, global_stack: "GlobalStack", position: str, nozzle_name: str, buildplate_name: Optional[str], material_guid: str) -> Optional["MaterialNode"]: machine_definition = global_stack.definition - variant_name = global_stack.extruders[position].variant.getName() + extruder = global_stack.extruderList[int(position)] + variant_name = extruder.variant.getName() + approximate_diameter = extruder.getApproximateMaterialDiameter() - return self.getMaterialNode(machine_definition.getId(), variant_name, buildplate_name, 3, material_guid) + return self.getMaterialNode(machine_definition.getId(), variant_name, buildplate_name, approximate_diameter, material_guid) # There are 2 ways to get fallback materials; # - A fallback by type (@sa getFallbackMaterialIdByMaterialType), which adds the generic version of this material @@ -266,128 +268,59 @@ class MaterialManager(QObject): return False return True + ## Change the user-visible name of a material. + # \param material_node The ContainerTree node of the material to rename. + # \param name The new name for the material. @pyqtSlot("QVariant", str) def setMaterialName(self, material_node: "MaterialNode", name: str) -> None: - if material_node.container is None: - return - root_material_id = material_node.container.getMetaDataEntry("base_file") - if root_material_id is None: - return - if CuraContainerRegistry.getInstance().isReadOnly(root_material_id): - Logger.log("w", "Cannot set name of read-only container %s.", root_material_id) - return - containers = CuraContainerRegistry.getInstance().findInstanceContainers(id = root_material_id) - containers[0].setName(name) + return cura.CuraApplication.CuraApplication.getMaterialManagementModel().setMaterialName(material_node, name) + ## Deletes a material from Cura. + # + # This function does not do any safety checking any more. Please call this + # function only if: + # - The material is not read-only. + # - The material is not used in any stacks. + # If the material was not lazy-loaded yet, this will fully load the + # container. When removing this material node, all other materials with + # the same base fill will also be removed. + # \param material_node The material to remove. @pyqtSlot("QVariant") def removeMaterial(self, material_node: "MaterialNode") -> None: - if material_node.container is None: - return - root_material_id = material_node.container.getMetaDataEntry("base_file") - if root_material_id is not None: - self.removeMaterialByRootId(root_material_id) + return cura.CuraApplication.CuraApplication.getMaterialManagementModel().setMaterialName(material_node) - def duplicateMaterialByRootId(self, root_material_id: str, new_base_id: Optional[str] = None, - new_metadata: Optional[Dict[str, Any]] = None) -> Optional[str]: - container_registry = CuraContainerRegistry.getInstance() - results = container_registry.findContainers(id=root_material_id) - - if not results: - Logger.log("i", "Unable to duplicate the material with id %s, because it doesn't exist.", root_material_id) - return None - - base_container = results[0] - - # Ensure all settings are saved. - cura.CuraApplication.CuraApplication.getInstance().saveSettings() - - # Create a new ID & container to hold the data. - new_containers = [] - if new_base_id is None: - new_base_id = container_registry.uniqueName(base_container.getId()) - new_base_container = copy.deepcopy(base_container) - new_base_container.getMetaData()["id"] = new_base_id - new_base_container.getMetaData()["base_file"] = new_base_id - if new_metadata is not None: - for key, value in new_metadata.items(): - new_base_container.getMetaData()[key] = value - new_containers.append(new_base_container) - - # Clone all of them. - for container_to_copy in container_registry.findContainers(base_file=root_material_id): - if container_to_copy.getId() == root_material_id: - continue # We already have that one, skip it - new_id = new_base_id - if container_to_copy.getMetaDataEntry("definition") != "fdmprinter": - new_id += "_" + container_to_copy.getMetaDataEntry("definition") - if container_to_copy.getMetaDataEntry("variant_name"): - nozzle_name = container_to_copy.getMetaDataEntry("variant_name") - new_id += "_" + nozzle_name.replace(" ", "_") - - new_container = copy.deepcopy(container_to_copy) - new_container.getMetaData()["id"] = new_id - new_container.getMetaData()["base_file"] = new_base_id - if new_metadata is not None: - for key, value in new_metadata.items(): - new_container.getMetaData()[key] = value - new_containers.append(new_container) - - for container_to_add in new_containers: - container_to_add.setDirty(True) - container_registry.addContainer(container_to_add) - - # if the duplicated material was favorite then the new material should also be added to favorite. - if root_material_id in self.getFavorites(): - cura.CuraApplication.CuraApplication.getInstance().getMaterialManagementModel().addFavorite(new_base_id) - - return new_base_id - - # - # Creates a duplicate of a material, which has the same GUID and base_file metadata. - # Returns the root material ID of the duplicated material if successful. - # - @pyqtSlot("QVariant", result = str) - def duplicateMaterial(self, material_node: MaterialNode, new_base_id: Optional[str] = None, - new_metadata: Optional[Dict[str, Any]] = None) -> str: - if material_node.container is None: - Logger.log("e", "Material node {0} doesn't have container.".format(material_node.container_id)) + def duplicateMaterialByRootId(self, root_material_id: str, new_base_id: Optional[str] = None, new_metadata: Optional[Dict[str, Any]] = None) -> Optional[str]: + result = cura.CuraApplication.CuraApplication.getInstance().getMaterialManagementModel().duplicateMaterialByBaseFile(root_material_id, new_base_id, new_metadata) + if result is None: return "ERROR" - root_material_id = cast(str, material_node.container.getMetaDataEntry("base_file", "")) - new_material_id = self.duplicateMaterialByRootId(root_material_id, new_base_id, new_metadata) - return new_material_id if new_material_id is not None else "ERROR" + return result - # Create a new material by cloning Generic PLA for the current material diameter and generate a new GUID. - # Returns the ID of the newly created material. + ## Creates a duplicate of a material with the same GUID and base_file + # metadata. + # \param material_node The node representing the material to duplicate. + # \param new_base_id A new material ID for the base material. The IDs of + # the submaterials will be based off this one. If not provided, a material + # ID will be generated automatically. + # \param new_metadata Metadata for the new material. If not provided, this + # will be duplicated from the original material. + # \return The root material ID of the duplicate material. + @pyqtSlot("QVariant", result = str) + def duplicateMaterial(self, material_node: MaterialNode, new_base_id: Optional[str] = None, new_metadata: Optional[Dict[str, Any]] = None) -> str: + result = cura.CuraApplication.CuraApplication.getInstance().getMaterialManagementModel().duplicateMaterial(material_node, new_base_id, new_metadata) + if result is None: + return "ERROR" + return result + + ## Create a new material by cloning the preferred material for the current + # material diameter and generate a new GUID. + # + # The material type is explicitly left to be the one from the preferred + # material, since this allows the user to still have SOME profiles to work + # with. + # \return The ID of the newly created material. @pyqtSlot(result = str) def createMaterial(self) -> str: - from UM.i18n import i18nCatalog - catalog = i18nCatalog("cura") - # Ensure all settings are saved. - application = cura.CuraApplication.CuraApplication.getInstance() - application.saveSettings() - - machine_manager = application.getMachineManager() - extruder_stack = machine_manager.activeStack - - global_stack = application.getGlobalContainerStack() - if global_stack is None: - Logger.log("e", "Global stack not present!") - return "ERROR" - machine_definition = global_stack.definition - root_material_id = machine_definition.getMetaDataEntry("preferred_material", default = "generic_pla") - - approximate_diameter = str(extruder_stack.approximateMaterialDiameter) - root_material_id = self.getRootMaterialIDForDiameter(root_material_id, approximate_diameter) - - # Create a new ID & container to hold the data. - new_id = CuraContainerRegistry.getInstance().uniqueName("custom_material") - new_metadata = {"name": catalog.i18nc("@label", "Custom Material"), - "brand": catalog.i18nc("@label", "Custom"), - "GUID": str(uuid.uuid4()), - } - - self.duplicateMaterialByRootId(root_material_id, new_base_id = new_id, new_metadata = new_metadata) - return new_id + return cura.CuraApplication.CuraApplication.getMaterialManagementModel().createMaterial() @pyqtSlot(str) def addFavorite(self, root_material_id: str) -> None: diff --git a/cura/Machines/Models/BaseMaterialsModel.py b/cura/Machines/Models/BaseMaterialsModel.py index 66718e8e4f..e2c43ed883 100644 --- a/cura/Machines/Models/BaseMaterialsModel.py +++ b/cura/Machines/Models/BaseMaterialsModel.py @@ -127,8 +127,8 @@ class BaseMaterialsModel(ListModel): return nozzle_name = extruder_stack.variant.getName() materials = ContainerTree.getInstance().machines[global_stack.definition.getId()].variants[nozzle_name].materials - compatible_material_diameter = str(round(extruder_stack.getCompatibleMaterialDiameter())) - self._available_materials = {key: material for key, material in materials.items() if material.container.getMetaDataEntry("approximate_diameter") == compatible_material_diameter} + approximate_material_diameter = extruder_stack.getApproximateMaterialDiameter() + self._available_materials = {key: material for key, material in materials.items() if float(material.container.getMetaDataEntry("approximate_diameter")) == approximate_material_diameter} ## This method is used by all material models in the beginning of the # _update() method in order to prevent errors. It's the same in all models diff --git a/cura/Machines/Models/IntentCategoryModel.py b/cura/Machines/Models/IntentCategoryModel.py index 5211889801..c436f94421 100644 --- a/cura/Machines/Models/IntentCategoryModel.py +++ b/cura/Machines/Models/IntentCategoryModel.py @@ -10,7 +10,7 @@ from cura.Settings.IntentManager import IntentManager from UM.Qt.ListModel import ListModel from UM.Settings.ContainerRegistry import ContainerRegistry #To update the list if anything changes. from PyQt5.QtCore import pyqtProperty, pyqtSignal - +import cura.CuraApplication if TYPE_CHECKING: from UM.Settings.ContainerRegistry import ContainerInterface @@ -48,7 +48,7 @@ class IntentCategoryModel(ListModel): ContainerRegistry.getInstance().containerAdded.connect(self._onContainerChange) ContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChange) - IntentManager.getInstance().configurationChanged.connect(self.update) + cura.CuraApplication.CuraApplication.getInstance().getMachineManager().activeStackChanged.connect(self.update) self.update() diff --git a/cura/Machines/Models/IntentModel.py b/cura/Machines/Models/IntentModel.py index 8773a12fd3..79d26db8d3 100644 --- a/cura/Machines/Models/IntentModel.py +++ b/cura/Machines/Models/IntentModel.py @@ -66,12 +66,26 @@ class IntentModel(ListModel): container_tree = ContainerTree.getInstance() machine_node = container_tree.machines[global_stack.definition.getId()] - active_extruder = ExtruderManager.getInstance().getActiveExtruderStack() + + # We can't just look at the active extruder, since it is possible for only one extruder to have an intent + # and the other extruders have no intent (eg, default). It is not possible for one extruder to have intent A and + # the other to have B. + # So we will use the first extruder that we find that has an intent that is not default as the "active" extruder + + active_extruder = None + for extruder in global_stack.extruderList: + if extruder.intent.getMetaDataEntry("intent_category", "default") == "default": + if active_extruder is None: + active_extruder = extruder # If there is no extruder found and the intent is default, use that. + else: # We found an intent, use that extruder as "active" + active_extruder = extruder + if not active_extruder: return active_variant_name = active_extruder.variant.getMetaDataEntry("name") active_variant_node = machine_node.variants[active_variant_name] active_material_node = active_variant_node.materials[active_extruder.material.getMetaDataEntry("base_file")] + layer_heights_added = [] for quality_id, quality_node in active_material_node.qualities.items(): if quality_node.quality_type not in quality_groups: # Don't add the empty quality type (or anything else that would crash, defensively). diff --git a/cura/Machines/Models/MaterialManagementModel.py b/cura/Machines/Models/MaterialManagementModel.py index f9af587293..a0b61e0b9b 100644 --- a/cura/Machines/Models/MaterialManagementModel.py +++ b/cura/Machines/Models/MaterialManagementModel.py @@ -73,20 +73,19 @@ class MaterialManagementModel(QObject): ## Creates a duplicate of a material with the same GUID and base_file # metadata. - # \param material_node The node representing the material to duplicate. + # \param base_file: The base file of the material to duplicate. # \param new_base_id A new material ID for the base material. The IDs of # the submaterials will be based off this one. If not provided, a material # ID will be generated automatically. # \param new_metadata Metadata for the new material. If not provided, this # will be duplicated from the original material. # \return The root material ID of the duplicate material. - @pyqtSlot("QVariant", result = str) - def duplicateMaterial(self, material_node: "MaterialNode", new_base_id: Optional[str] = None, new_metadata: Dict[str, Any] = None) -> Optional[str]: + def duplicateMaterialByBaseFile(self, base_file: str, new_base_id: Optional[str] = None, new_metadata: Dict[str, Any] = None) -> Optional[str]: container_registry = CuraContainerRegistry.getInstance() - root_materials = container_registry.findContainers(id = material_node.base_file) + root_materials = container_registry.findContainers(id = base_file) if not root_materials: - Logger.log("i", "Unable to duplicate the root material with ID {root_id}, because it doesn't exist.".format(root_id = material_node.base_file)) + Logger.log("i", "Unable to duplicate the root material with ID {root_id}, because it doesn't exist.".format(root_id = base_file)) return None root_material = root_materials[0] @@ -105,8 +104,8 @@ class MaterialManagementModel(QObject): new_containers = [new_root_material] # Clone all submaterials. - for container_to_copy in container_registry.findInstanceContainers(base_file = material_node.base_file): - if container_to_copy.getId() == material_node.base_file: + for container_to_copy in container_registry.findInstanceContainers(base_file = base_file): + if container_to_copy.getId() == base_file: continue # We already have that one. Skip it. new_id = new_base_id definition = container_to_copy.getMetaDataEntry("definition") @@ -129,12 +128,25 @@ class MaterialManagementModel(QObject): # If the duplicated material was favorite then the new material should also be added to the favorites. favorites_set = set(application.getPreferences().getValue("cura/favorite_materials").split(";")) - if material_node.base_file in favorites_set: + if base_file in favorites_set: favorites_set.add(new_base_id) application.getPreferences().setValue("cura/favorite_materials", ";".join(favorites_set)) return new_base_id + ## Creates a duplicate of a material with the same GUID and base_file + # metadata. + # \param material_node The node representing the material to duplicate. + # \param new_base_id A new material ID for the base material. The IDs of + # the submaterials will be based off this one. If not provided, a material + # ID will be generated automatically. + # \param new_metadata Metadata for the new material. If not provided, this + # will be duplicated from the original material. + # \return The root material ID of the duplicate material. + @pyqtSlot("QVariant", result = str) + def duplicateMaterial(self, material_node: "MaterialNode", new_base_id: Optional[str] = None, new_metadata: Dict[str, Any] = None) -> Optional[str]: + return self.duplicateMaterialByBaseFile(material_node.base_file, new_base_id, new_metadata) + ## Create a new material by cloning the preferred material for the current # material diameter and generate a new GUID. # diff --git a/cura/Machines/Models/QualityProfilesDropDownMenuModel.py b/cura/Machines/Models/QualityProfilesDropDownMenuModel.py index 2311e80689..8b3999275d 100644 --- a/cura/Machines/Models/QualityProfilesDropDownMenuModel.py +++ b/cura/Machines/Models/QualityProfilesDropDownMenuModel.py @@ -78,9 +78,7 @@ class QualityProfilesDropDownMenuModel(ListModel): quality_group_dict = ContainerTree.getInstance().getCurrentQualityGroups() item_list = [] - for key in quality_group_dict: - quality_group = quality_group_dict[key] - + for quality_group in quality_group_dict.values(): layer_height = self._fetchLayerHeight(quality_group) item = {"name": quality_group.name, diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py index 4a5a4c0885..6c54a6890d 100644 --- a/cura/Machines/QualityManager.py +++ b/cura/Machines/QualityManager.py @@ -5,7 +5,6 @@ from typing import Any, Dict, List, Optional, TYPE_CHECKING from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot -from UM.Logger import Logger from UM.Util import parseBool from UM.Settings.InstanceContainer import InstanceContainer from UM.Decorators import deprecated diff --git a/cura/Settings/IntentManager.py b/cura/Settings/IntentManager.py index 251cf0aed8..1a73fb818c 100644 --- a/cura/Settings/IntentManager.py +++ b/cura/Settings/IntentManager.py @@ -4,6 +4,7 @@ from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot from typing import Any, Dict, List, Optional, Set, Tuple, TYPE_CHECKING import cura.CuraApplication +from UM.Logger import Logger from cura.Machines.ContainerTree import ContainerTree from cura.Settings.cura_empty_instance_containers import empty_intent_container from UM.Settings.InstanceContainer import InstanceContainer @@ -11,20 +12,12 @@ from UM.Settings.InstanceContainer import InstanceContainer if TYPE_CHECKING: from UM.Settings.InstanceContainer import InstanceContainer + ## Front-end for querying which intents are available for a certain # configuration. -# -# CURRENTLY THIS CLASS CONTAINS ONLY SOME PSEUDOCODE OF WHAT WE ARE SUPPOSED -# TO IMPLEMENT. class IntentManager(QObject): __instance = None - def __init__(self) -> None: - super().__init__() - cura.CuraApplication.CuraApplication.getInstance().getMachineManager().activeStackChanged.connect(self.configurationChanged) - self.configurationChanged.connect(self.selectDefaultIntent) - pass - ## This class is a singleton. @classmethod def getInstance(cls): @@ -32,7 +25,6 @@ class IntentManager(QObject): cls.__instance = IntentManager() return cls.__instance - configurationChanged = pyqtSignal() #Triggered when something changed in the rest of the stack. intentCategoryChanged = pyqtSignal() #Triggered when we switch categories. ## Gets the metadata dictionaries of all intent profiles for a given @@ -40,12 +32,16 @@ class IntentManager(QObject): # # \param definition_id ID of the printer. # \param nozzle_name Name of the nozzle. - # \param material_id ID of the material. + # \param material_base_file The base_file of the material. # \return A list of metadata dictionaries matching the search criteria, or # an empty list if nothing was found. - def intentMetadatas(self, definition_id: str, nozzle_name: str, material_id: str) -> List[Dict[str, Any]]: - registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry() - return registry.findContainersMetadata(type = "intent", definition = definition_id, variant = nozzle_name, material = material_id) + def intentMetadatas(self, definition_id: str, nozzle_name: str, material_base_file: str) -> List[Dict[str, Any]]: + material_node = ContainerTree.getInstance().machines[definition_id].variants[nozzle_name].materials[material_base_file] + intent_metadatas = [] + for quality_node in material_node.qualities.values(): + for intent_node in quality_node.intents.values(): + intent_metadatas.append(intent_node.getMetadata()) + return intent_metadatas ## Collects and returns all intent categories available for the given # parameters. Note that the 'default' category is always available. @@ -77,7 +73,6 @@ class IntentManager(QObject): # even though there should always be defaults. The problem then is what to do with the quality_types. # Currently _also_ inconsistent with 'currentAvailableIntentCategories', which _does_ return default. quality_groups = ContainerTree.getInstance().getCurrentQualityGroups() - # TODO: These quality nodes in that tree already contain the intent nodes. We can optimise this. available_quality_types = {quality_group.quality_type for quality_group in quality_groups.values() if quality_group.node_for_global is not None} final_intent_ids = set() # type: Set[str] @@ -131,6 +126,7 @@ class IntentManager(QObject): ## Apply intent on the stacks. @pyqtSlot(str, str) def selectIntent(self, intent_category: str, quality_type: str) -> None: + Logger.log("i", "Attempting to set intent_category to [%s] and quality type to [%s]", intent_category, quality_type) old_intent_category = self.currentIntentCategory application = cura.CuraApplication.CuraApplication.getInstance() global_stack = application.getGlobalContainerStack() @@ -147,13 +143,4 @@ class IntentManager(QObject): extruder_stack.intent = self.getDefaultIntent() application.getMachineManager().setQualityGroupByQualityType(quality_type) if old_intent_category != intent_category: - self.intentCategoryChanged.emit() - - ## Selects the default intents on every extruder. - def selectDefaultIntent(self) -> None: - application = cura.CuraApplication.CuraApplication.getInstance() - global_stack = application.getGlobalContainerStack() - if global_stack is None: - return - for extruder_stack in global_stack.extruderList: - extruder_stack.intent = self.getDefaultIntent() + self.intentCategoryChanged.emit() \ No newline at end of file diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index a8d7379cf7..4ff5ad95cd 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -26,7 +26,6 @@ import cura.CuraApplication # Imported like this to prevent circular references from cura.Machines.ContainerNode import ContainerNode from cura.Machines.ContainerTree import ContainerTree -from cura.Machines.MaterialManager import MaterialManager from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice, ConnectionType from cura.PrinterOutput.Models.PrinterConfigurationModel import PrinterConfigurationModel @@ -616,10 +615,15 @@ class MachineManager(QObject): @pyqtProperty(str, notify=activeIntentChanged) def activeIntentCategory(self): + global_container_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() - if not self._active_container_stack: + if not global_container_stack: return "" - intent_category = self._active_container_stack.intent.getMetaDataEntry("intent_category") + intent_category = "default" + for extruder in global_container_stack.extruderList: + category = extruder.intent.getMetaDataEntry("intent_category", "default") + if category != "default" and category != intent_category: + intent_category = category return intent_category ## Returns whether there is anything unsupported in the current set-up. @@ -1301,8 +1305,7 @@ class MachineManager(QObject): self._setMaterial(position_item, new_material) else: # The current material is not available, find the preferred one. - material_diameter = self._global_container_stack.extruders[position].getCompatibleMaterialDiameter() - approximate_material_diameter = round(material_diameter) + approximate_material_diameter = int(self._global_container_stack.extruderList[int(position)].getApproximateMaterialDiameter()) material_node = nozzle_node.preferredMaterial(approximate_material_diameter) self._setMaterial(position_item, material_node) @@ -1343,6 +1346,7 @@ class MachineManager(QObject): if self._global_container_stack is None: return self.blurSettings.emit() + container_registry = CuraContainerRegistry.getInstance() with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): self.switchPrinterType(configuration.printerType) @@ -1379,20 +1383,18 @@ class MachineManager(QObject): else: machine_node = ContainerTree.getInstance().machines.get(self._global_container_stack.definition.getId()) variant_node = machine_node.variants.get(extruder_configuration.hotendID) - if variant_node: - self._setVariantNode(position, variant_node) - else: - self._global_container_stack.extruders[position].variant = empty_variant_container + self._setVariantNode(position, variant_node) - material_container_node = MaterialManager.getInstance().getMaterialNodeByType(self._global_container_stack, - position, - extruder_configuration.hotendID, - configuration.buildplateConfiguration, - extruder_configuration.material.guid) - if material_container_node: - self._setMaterial(position, material_container_node) - else: - self._global_container_stack.extruders[position].material = empty_material_container + # Find the material profile that the printer has stored. + # This might find one of the duplicates if the user duplicated the material to sync with. But that's okay; both have this GUID so both are correct. + approximate_diameter = int(self._global_container_stack.extruderList[int(position)].getApproximateMaterialDiameter()) + materials_with_guid = container_registry.findInstanceContainersMetadata(guid = extruder_configuration.material.guid, approximate_diameter = approximate_diameter) + material_container_node = variant_node.preferredMaterial(approximate_diameter) + if materials_with_guid: # We also have the material profile that the printer wants to share. + base_file = materials_with_guid[0]["base_file"] + material_container_node = variant_node.materials.get(base_file, default = material_container_node) # If Cura thinks that the selected material is not available for this printer, revert to the preferred material. + + self._setMaterial(position, material_container_node) self._global_container_stack.extruders[position].setEnabled(True) self.updateMaterialWithVariant(position) @@ -1431,17 +1433,12 @@ class MachineManager(QObject): def setMaterialById(self, position: str, root_material_id: str) -> None: if self._global_container_stack is None: return - buildplate_name = None - if self._global_container_stack.variant.getId() != "empty_variant": - buildplate_name = self._global_container_stack.variant.getName() machine_definition_id = self._global_container_stack.definition.id position = str(position) extruder_stack = self._global_container_stack.extruders[position] nozzle_name = extruder_stack.variant.getName() - material_diameter = extruder_stack.getApproximateMaterialDiameter() - material_node = MaterialManager.getInstance().getMaterialNode(machine_definition_id, nozzle_name, buildplate_name, - material_diameter, root_material_id) + material_node = ContainerTree.getInstance().machines[machine_definition_id].variants[nozzle_name].materials[root_material_id] self.setMaterial(position, material_node) ## Global_stack: if you want to provide your own global_stack instead of the current active one @@ -1624,7 +1621,3 @@ class MachineManager(QObject): abbr_machine += stripped_word return abbr_machine - - # Gets all machines that belong to the given group_id. - def getMachinesInGroup(self, group_id: str) -> List["GlobalStack"]: - return self._container_registry.findContainerStacks(type = "machine", group_id = group_id) diff --git a/plugins/MachineSettingsAction/MachineSettingsAction.py b/plugins/MachineSettingsAction/MachineSettingsAction.py index d48475b1e6..25586ddeb0 100755 --- a/plugins/MachineSettingsAction/MachineSettingsAction.py +++ b/plugins/MachineSettingsAction/MachineSettingsAction.py @@ -11,7 +11,9 @@ from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.DefinitionContainer import DefinitionContainer from UM.Util import parseBool +import cura.CuraApplication # Imported like this to prevent circular dependencies. from cura.MachineAction import MachineAction +from cura.Machines.ContainerTree import ContainerTree # To re-build the machine node when hasMaterials changes. from cura.Settings.CuraStackBuilder import CuraStackBuilder from cura.Settings.cura_empty_instance_containers import isEmptyContainer @@ -41,6 +43,9 @@ class MachineSettingsAction(MachineAction): self._backend = self._application.getBackend() self.onFinished.connect(self._onFinished) + # If the g-code flavour changes between UltiGCode and another flavour, we need to update the container tree. + self._application.globalContainerStackChanged.connect(self._updateHasMaterialsInContainerTree) + # Which container index in a stack to store machine setting changes. @pyqtProperty(int, constant = True) def storeContainerIndex(self) -> int: @@ -51,6 +56,16 @@ class MachineSettingsAction(MachineAction): if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine": self._application.getMachineActionManager().addSupportedAction(container.getId(), self.getKey()) + ## Triggered when the global container stack changes or when the g-code + # flavour setting is changed. + def _updateHasMaterialsInContainerTree(self) -> None: + global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() + machine_node = ContainerTree.getInstance().machines[global_stack.definition.getId()] + + if machine_node.has_materials != parseBool(global_stack.getMetaDataEntry("has_materials")): # May have changed due to the g-code flavour. + machine_node.has_materials = parseBool(global_stack.getMetaDataEntry("has_materials")) + machine_node._loadAll() + def _reset(self): global_stack = self._application.getMachineManager().activeMachine if not global_stack: @@ -98,11 +113,8 @@ class MachineSettingsAction(MachineAction): return machine_manager = self._application.getMachineManager() - material_manager = self._application.getMaterialManager() - extruder_positions = list(global_stack.extruders.keys()) has_materials = global_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode" - material_node = None if has_materials: global_stack.setMetaDataEntry("has_materials", True) else: @@ -111,11 +123,15 @@ class MachineSettingsAction(MachineAction): if "has_materials" in global_stack.getMetaData(): global_stack.removeMetaDataEntry("has_materials") + self._updateHasMaterialsInContainerTree() + # set materials - for position in extruder_positions: - if has_materials: - material_node = material_manager.getDefaultMaterial(global_stack, position, None) - machine_manager.setMaterial(position, material_node) + machine_node = ContainerTree.getInstance().machines[global_stack.definition.getId()] + for position, extruder in enumerate(global_stack.extruderList): + #Find out what material we need to default to. + approximate_diameter = round(extruder.getProperty("material_diameter", "value")) + material_node = machine_node.variants[extruder.variant.getName()].preferredMaterial(approximate_diameter) + machine_manager.setMaterial(str(position), material_node) self._application.globalContainerStackChanged.emit() diff --git a/plugins/UM3NetworkPrinting/src/Network/SendMaterialJob.py b/plugins/UM3NetworkPrinting/src/Network/SendMaterialJob.py index 5637f388c1..09949ed37e 100644 --- a/plugins/UM3NetworkPrinting/src/Network/SendMaterialJob.py +++ b/plugins/UM3NetworkPrinting/src/Network/SendMaterialJob.py @@ -69,9 +69,9 @@ class SendMaterialJob(Job): def _sendMaterials(self, materials_to_send: Set[str]) -> None: container_registry = CuraApplication.getInstance().getContainerRegistry() all_materials = container_registry.findInstanceContainersMetadata(type = "material") - all_root_materials = {material["base_file"] for material in all_materials if "base_file" in material} # Filters out uniques by making it a set. Don't include files without base file (i.e. empty material). + all_base_files = {material["base_file"] for material in all_materials if "base_file" in material} # Filters out uniques by making it a set. Don't include files without base file (i.e. empty material). - for root_material_id in all_root_materials: + for root_material_id in all_base_files: if root_material_id not in materials_to_send: # If the material does not have to be sent we skip it. continue @@ -129,10 +129,10 @@ class SendMaterialJob(Job): def _getLocalMaterials() -> Dict[str, LocalMaterial]: result = {} # type: Dict[str, LocalMaterial] all_materials = CuraApplication.getInstance().getContainerRegistry().findInstanceContainersMetadata(type = "material") - all_root_materials = [material for material in all_materials if material["id"] == material.get("base_file")] # Don't send materials without base_file: The empty material doesn't need to be sent. + all_base_files = [material for material in all_materials if material["id"] == material.get("base_file")] # Don't send materials without base_file: The empty material doesn't need to be sent. # Find the latest version of all material containers in the registry. - for material_metadata in all_root_materials: + for material_metadata in all_base_files: try: # material version must be an int material_metadata["version"] = int(material_metadata["version"]) diff --git a/resources/qml/PrintSetupSelector/Custom/MenuButton.qml b/resources/qml/PrintSetupSelector/Custom/MenuButton.qml index 29436da9cf..cebe130a20 100644 --- a/resources/qml/PrintSetupSelector/Custom/MenuButton.qml +++ b/resources/qml/PrintSetupSelector/Custom/MenuButton.qml @@ -22,7 +22,7 @@ Button background: Rectangle { id: backgroundRectangle - border.width: 1 + border.width: UM.Theme.getSize("default_lining").width border.color: button.checked ? UM.Theme.getColor("setting_control_border_highlight") : "transparent" color: button.hovered ? UM.Theme.getColor("action_button_hovered") : "transparent" radius: UM.Theme.getSize("action_button_radius").width diff --git a/resources/qml/PrintSetupSelector/Custom/QualitiesWithIntentMenu.qml b/resources/qml/PrintSetupSelector/Custom/QualitiesWithIntentMenu.qml index 94f9acf3b3..cff4e3e7a6 100644 --- a/resources/qml/PrintSetupSelector/Custom/QualitiesWithIntentMenu.qml +++ b/resources/qml/PrintSetupSelector/Custom/QualitiesWithIntentMenu.qml @@ -182,11 +182,12 @@ Popup Rectangle { - height: 1 + height: UM.Theme.getSize("default_lining").height anchors.left: parent.left anchors.right: parent.right color: borderColor } + MenuButton { labelText: Cura.Actions.addProfile.text diff --git a/resources/qml/RadioCheckbar.qml b/resources/qml/RadioCheckbar.qml index 3c767a6201..59dba59910 100644 --- a/resources/qml/RadioCheckbar.qml +++ b/resources/qml/RadioCheckbar.qml @@ -14,7 +14,7 @@ Item property color activeColor: UM.Theme.getColor("primary") property color inactiveColor: UM.Theme.getColor("slider_groove") property color defaultItemColor: UM.Theme.getColor("small_button_active") - property int checkboxSize: UM.Theme.getSize("radio_button").height * 0.75 + property int checkboxSize: Math.round(UM.Theme.getSize("radio_button").height * 0.75) property int inactiveMarkerSize: 2 * barSize property int barSize: UM.Theme.getSize("slider_groove_radius").height property var isCheckedFunction // Function that accepts the modelItem and returns if the item should be active. @@ -36,8 +36,8 @@ Item { left: buttonBar.left right: buttonBar.right - leftMargin: (checkboxSize - inactiveMarkerSize) / 2 - rightMargin: (checkboxSize - inactiveMarkerSize) / 2 + leftMargin: Math.round((checkboxSize - inactiveMarkerSize) / 2) + rightMargin: Math.round((checkboxSize - inactiveMarkerSize) / 2) verticalCenter: parent.verticalCenter } } @@ -72,7 +72,7 @@ Item property Item previousItem: repeater.itemAt(index - 1) height: barSize - width: buttonBar.width / (repeater.count - 1) - activeComponent.width - 2 + width: Math.round(buttonBar.width / (repeater.count - 1) - activeComponent.width - 2) color: defaultItemColor anchors @@ -110,7 +110,7 @@ Item anchors.horizontalCenter: parent.horizontalCenter height: inactiveMarkerSize width: inactiveMarkerSize - radius: width / 2 + radius: Math.round(width / 2) color: inactiveColor } } @@ -132,7 +132,7 @@ Item { height: checkboxSize width: checkboxSize - radius: width / 2 + radius: Math.round(width / 2) border.color: defaultItemColor @@ -143,7 +143,7 @@ Item margins: 3 fill: parent } - radius: width / 2 + radius: Math.round(width / 2) color: activeColor visible: checkbox.checked }