MaterialManagementModel.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. # Copyright (c) 2019 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. import copy # To duplicate materials.
  4. from PyQt5.QtCore import QObject, pyqtSlot # To allow the preference page proxy to be used from the actual preferences page.
  5. from typing import Any, Dict, Optional, TYPE_CHECKING
  6. import uuid # To generate new GUIDs for new materials.
  7. from UM.i18n import i18nCatalog
  8. from UM.Logger import Logger
  9. import cura.CuraApplication # Imported like this to prevent circular imports.
  10. from cura.Machines.ContainerTree import ContainerTree
  11. from cura.Settings.CuraContainerRegistry import CuraContainerRegistry # To find the sets of materials belonging to each other, and currently loaded extruder stacks.
  12. if TYPE_CHECKING:
  13. from cura.Machines.MaterialNode import MaterialNode
  14. catalog = i18nCatalog("cura")
  15. ## Proxy class to the materials page in the preferences.
  16. #
  17. # This class handles the actions in that page, such as creating new materials,
  18. # renaming them, etc.
  19. class MaterialManagementModel(QObject):
  20. ## Can a certain material be deleted, or is it still in use in one of the
  21. # container stacks anywhere?
  22. #
  23. # We forbid the user from deleting a material if it's in use in any stack.
  24. # Deleting it while it's in use can lead to corrupted stacks. In the
  25. # future we might enable this functionality again (deleting the material
  26. # from those stacks) but for now it is easier to prevent the user from
  27. # doing this.
  28. # \param material_node The ContainerTree node of the material to check.
  29. # \return Whether or not the material can be removed.
  30. @pyqtSlot("QVariant", result = bool)
  31. def canMaterialBeRemoved(self, material_node: "MaterialNode"):
  32. container_registry = CuraContainerRegistry.getInstance()
  33. ids_to_remove = {metadata.get("id", "") for metadata in container_registry.findInstanceContainersMetadata(base_file = material_node.base_file)}
  34. for extruder_stack in container_registry.findContainerStacks(type = "extruder_train"):
  35. if extruder_stack.material.getId() in ids_to_remove:
  36. return False
  37. return True
  38. ## Change the user-visible name of a material.
  39. # \param material_node The ContainerTree node of the material to rename.
  40. # \param name The new name for the material.
  41. @pyqtSlot("QVariant", str)
  42. def setMaterialName(self, material_node: "MaterialNode", name: str) -> None:
  43. container_registry = CuraContainerRegistry.getInstance()
  44. root_material_id = material_node.base_file
  45. if container_registry.isReadOnly(root_material_id):
  46. Logger.log("w", "Cannot set name of read-only container %s.", root_material_id)
  47. return
  48. return container_registry.findContainers(id = root_material_id)[0].setName(name)
  49. ## Deletes a material from Cura.
  50. #
  51. # This function does not do any safety checking any more. Please call this
  52. # function only if:
  53. # - The material is not read-only.
  54. # - The material is not used in any stacks.
  55. # If the material was not lazy-loaded yet, this will fully load the
  56. # container. When removing this material node, all other materials with
  57. # the same base fill will also be removed.
  58. # \param material_node The material to remove.
  59. @pyqtSlot("QVariant")
  60. def removeMaterial(self, material_node: "MaterialNode") -> None:
  61. container_registry = CuraContainerRegistry.getInstance()
  62. materials_this_base_file = container_registry.findContainersMetadata(base_file = material_node.base_file)
  63. for material_metadata in materials_this_base_file:
  64. container_registry.removeContainer(material_metadata["id"])
  65. ## Creates a duplicate of a material with the same GUID and base_file
  66. # metadata.
  67. # \param material_node The node representing the material to duplicate.
  68. # \param new_base_id A new material ID for the base material. The IDs of
  69. # the submaterials will be based off this one. If not provided, a material
  70. # ID will be generated automatically.
  71. # \param new_metadata Metadata for the new material. If not provided, this
  72. # will be duplicated from the original material.
  73. # \return The root material ID of the duplicate material.
  74. @pyqtSlot("QVariant", result = str)
  75. def duplicateMaterial(self, material_node: "MaterialNode", new_base_id: Optional[str] = None, new_metadata: Dict[str, Any] = None) -> Optional[str]:
  76. container_registry = CuraContainerRegistry.getInstance()
  77. root_materials = container_registry.findContainers(id = material_node.base_file)
  78. if not root_materials:
  79. 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))
  80. return None
  81. root_material = root_materials[0]
  82. # Ensure that all settings are saved.
  83. application = cura.CuraApplication.CuraApplication.getInstance()
  84. application.saveSettings()
  85. # Create a new ID and container to hold the data.
  86. if new_base_id is None:
  87. new_base_id = container_registry.uniqueName(root_material.getId())
  88. new_root_material = copy.deepcopy(root_material)
  89. new_root_material.getMetaData()["id"] = new_base_id
  90. new_root_material.getMetaData()["base_file"] = new_base_id
  91. if new_metadata is not None:
  92. new_root_material.getMetaData().update(new_metadata)
  93. new_containers = [new_root_material]
  94. # Clone all submaterials.
  95. for container_to_copy in container_registry.findInstanceContainers(base_file = material_node.base_file):
  96. if container_to_copy.getId() == material_node.base_file:
  97. continue # We already have that one. Skip it.
  98. new_id = new_base_id
  99. definition = container_to_copy.getMetaDataEntry("definition")
  100. if definition != "fdmprinter":
  101. new_id += "_" + definition
  102. variant_name = container_to_copy.getMetaDataEntry("variant_name")
  103. if variant_name:
  104. new_id += "_" + variant_name.replace(" ", "_")
  105. new_container = copy.deepcopy(container_to_copy)
  106. new_container.getMetaData()["id"] = new_id
  107. new_container.getMetaData()["base_file"] = new_base_id
  108. if new_metadata is not None:
  109. new_container.getMetaData().update(new_metadata)
  110. new_containers.append(new_container)
  111. for container_to_add in new_containers:
  112. container_to_add.setDirty(True)
  113. container_registry.addContainer(container_to_add)
  114. # If the duplicated material was favorite then the new material should also be added to the favorites.
  115. favorites_set = set(application.getPreferences().getValue("cura/favorite_materials").split(";"))
  116. if material_node.base_file in favorites_set:
  117. favorites_set.add(new_base_id)
  118. application.getPreferences().setValue("cura/favorite_materials", ";".join(favorites_set))
  119. return new_base_id
  120. ## Create a new material by cloning the preferred material for the current
  121. # material diameter and generate a new GUID.
  122. #
  123. # The material type is explicitly left to be the one from the preferred
  124. # material, since this allows the user to still have SOME profiles to work
  125. # with.
  126. # \return The ID of the newly created material.
  127. @pyqtSlot(result = str)
  128. def createMaterial(self) -> str:
  129. # Ensure all settings are saved.
  130. application = cura.CuraApplication.CuraApplication.getInstance()
  131. application.saveSettings()
  132. # Find the preferred material.
  133. extruder_stack = application.getMachineManager().activeStack
  134. active_variant_name = extruder_stack.variant.getName()
  135. approximate_diameter = int(extruder_stack.approximateMaterialDiameter)
  136. global_container_stack = application.getGlobalContainerStack()
  137. if not global_container_stack:
  138. return ""
  139. machine_node = ContainerTree.getInstance().machines[global_container_stack.definition.getId()]
  140. preferred_material_node = machine_node.variants[active_variant_name].preferredMaterial(approximate_diameter)
  141. # Create a new ID & new metadata for the new material.
  142. new_id = CuraContainerRegistry.getInstance().uniqueName("custom_material")
  143. new_metadata = {"name": catalog.i18nc("@label", "Custom Material"),
  144. "brand": catalog.i18nc("@label", "Custom"),
  145. "GUID": str(uuid.uuid4()),
  146. }
  147. self.duplicateMaterial(preferred_material_node, new_base_id = new_id, new_metadata = new_metadata)
  148. return new_id