diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py index e298ef7389..3201f4cc25 100644 --- a/cura/Machines/MaterialManager.py +++ b/cura/Machines/MaterialManager.py @@ -302,7 +302,6 @@ class MaterialManager(QObject): # Create a new ID & container to hold the data. new_containers = [] - container_registry = CuraContainerRegistry.getInstance() if new_base_id is None: new_base_id = container_registry.uniqueName(base_container.getId()) new_base_container = copy.deepcopy(base_container) @@ -338,9 +337,10 @@ class MaterialManager(QObject): # if the duplicated material was favorite then the new material should also be added to favorite. if root_material_id in self.getFavorites(): - self.addFavorite(new_base_id) + 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. diff --git a/cura/Machines/Models/MaterialManagementModel.py b/cura/Machines/Models/MaterialManagementModel.py index 2c8617073d..90e63e6240 100644 --- a/cura/Machines/Models/MaterialManagementModel.py +++ b/cura/Machines/Models/MaterialManagementModel.py @@ -1,16 +1,23 @@ # Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +import copy # To duplicate materials. from PyQt5.QtCore import QObject, pyqtSlot # To allow the preference page proxy to be used from the actual preferences page. -from typing import TYPE_CHECKING +from typing import Any, Dict, Optional, TYPE_CHECKING +import uuid # To generate new GUIDs for new materials. +from UM.i18n import i18nCatalog from UM.Logger import Logger +import cura.CuraApplication # Imported like this to prevent circular imports. +from cura.Machines.ContainerTree import ContainerTree from cura.Settings.CuraContainerRegistry import CuraContainerRegistry # To find the sets of materials belonging to each other, and currently loaded extruder stacks. if TYPE_CHECKING: from cura.Machines.MaterialNode import MaterialNode +catalog = i18nCatalog("cura") + ## Proxy class to the materials page in the preferences. # # This class handles the actions in that page, such as creating new materials, @@ -62,4 +69,98 @@ class MaterialManagementModel(QObject): container_registry = CuraContainerRegistry.getInstance() materials_this_base_file = container_registry.findContainersMetadata(base_file = material_node.base_file) for material_metadata in materials_this_base_file: - container_registry.removeContainer(material_metadata["id"]) \ No newline at end of file + container_registry.removeContainer(material_metadata["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]: + container_registry = CuraContainerRegistry.getInstance() + + root_materials = container_registry.findContainers(id = material_node.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)) + return None + root_material = root_materials[0] + + # Ensure that all settings are saved. + application = cura.CuraApplication.CuraApplication.getInstance() + application.saveSettings() + + # Create a new ID and container to hold the data. + if new_base_id is None: + new_base_id = container_registry.uniqueName(root_material.getId()) + new_root_material = copy.deepcopy(root_material) + new_root_material.getMetaData()["id"] = new_base_id + new_root_material.getMetaData()["base_file"] = new_base_id + if new_metadata is not None: + new_root_material.getMetaData().update(new_metadata) + 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: + continue # We already have that one. Skip it. + new_id = new_base_id + definition = container_to_copy.getMetaDataEntry("definition") + if definition != "fdmprinter": + new_id += "_" + definition + variant_name = container_to_copy.getMetaDataEntry("variant_name") + if variant_name: + new_id += "_" + variant_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: + new_container.getMetaData().update(new_metadata) + 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 the favorites. + favorites_set = set(application.getPreferences().getValue("cura/favorite_materials").split(";")) + if material_node.base_file in favorites_set: + favorites_set.add(new_base_id) + application.getPreferences().setValue("cura/favorite_materials", ";".join(favorites_set)) + + return new_base_id + + ## 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: + # Ensure all settings are saved. + application = cura.CuraApplication.CuraApplication.getInstance() + application.saveSettings() + + # Find the preferred material. + extruder_stack = application.getMachineManager().activeStack + active_variant_name = extruder_stack.variant.getName() + approximate_diameter = int(extruder_stack.approximateMaterialDiameter) + machine_node = ContainerTree.getInstance().machines[application.getGlobalContainerStack().definition.getId()] + preferred_material_node = machine_node.variants[active_variant_name].preferredMaterial(approximate_diameter) + + # Create a new ID & new metadata for the new material. + 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.duplicateMaterial(preferred_material_node, new_base_id = new_id, new_metadata = new_metadata) + return new_id \ No newline at end of file diff --git a/cura/Machines/VariantNode.py b/cura/Machines/VariantNode.py index f5a1e3006e..abcd588c4a 100644 --- a/cura/Machines/VariantNode.py +++ b/cura/Machines/VariantNode.py @@ -79,7 +79,7 @@ class VariantNode(ContainerNode): # material. # \return The node for the preferred material, or any arbitrary material # if there is no match. - def preferredMaterial(self, approximate_diameter) -> MaterialNode: + def preferredMaterial(self, approximate_diameter: int) -> MaterialNode: for base_material, material_node in self.materials.items(): if self.machine.preferred_material in base_material and approximate_diameter == int(material_node.getMetaDataEntry("approximate_diameter")): return material_node diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index cbdec39054..5ec14fe60c 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -226,8 +226,6 @@ class XmlMaterialProfile(InstanceContainer): machine_container_map = {} # type: Dict[str, InstanceContainer] machine_variant_map = {} # type: Dict[str, Dict[str, Any]] - container_tree = ContainerTree.getInstance() - root_material_id = self.getMetaDataEntry("base_file") # if basefile is self.getId, this is a basefile. all_containers = registry.findInstanceContainers(base_file = root_material_id) @@ -247,7 +245,7 @@ class XmlMaterialProfile(InstanceContainer): machine_container_map[definition_id] = container continue - variant_dict = {"variant_type": container.getMetaDataEntry("hardware_type", str(VariantType.NOZZLE)), + variant_dict = {"variant_type": container.getMetaDataEntry("hardware_type", "nozzle"), "material_container": container} machine_variant_map[definition_id][variant_name] = variant_dict diff --git a/resources/qml/Preferences/Materials/MaterialsPage.qml b/resources/qml/Preferences/Materials/MaterialsPage.qml index 4a6ff75f0d..6e637a8d1f 100644 --- a/resources/qml/Preferences/Materials/MaterialsPage.qml +++ b/resources/qml/Preferences/Materials/MaterialsPage.qml @@ -122,7 +122,7 @@ Item onClicked: { forceActiveFocus(); - base.newRootMaterialIdToSwitchTo = base.materialManager.createMaterial(); + base.newRootMaterialIdToSwitchTo = base.materialManagementModel.createMaterial(); base.toActivateNewMaterial = true; } } @@ -137,7 +137,7 @@ Item onClicked: { forceActiveFocus(); - base.newRootMaterialIdToSwitchTo = base.materialManager.duplicateMaterial(base.currentItem.container_node); + base.newRootMaterialIdToSwitchTo = base.materialManagementModel.duplicateMaterial(base.currentItem.container_node); base.toActivateNewMaterial = true; } }