MaterialNode.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. # Copyright (c) 2019 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. from typing import Any, Optional, TYPE_CHECKING
  4. from UM.Logger import Logger
  5. from UM.Settings.ContainerRegistry import ContainerRegistry
  6. from UM.Settings.Interfaces import ContainerInterface
  7. from UM.Signal import Signal
  8. from cura.Machines.ContainerNode import ContainerNode
  9. from cura.Machines.QualityNode import QualityNode
  10. import UM.FlameProfiler
  11. if TYPE_CHECKING:
  12. from typing import Dict
  13. from cura.Machines.VariantNode import VariantNode
  14. class MaterialNode(ContainerNode):
  15. """Represents a material in the container tree.
  16. Its subcontainers are quality profiles.
  17. """
  18. def __init__(self, container_id: str, variant: "VariantNode") -> None:
  19. super().__init__(container_id)
  20. self.variant = variant
  21. self.qualities = {} # type: Dict[str, QualityNode] # Mapping container IDs to quality profiles.
  22. self.materialChanged = Signal() # Triggered when the material is removed or its metadata is updated.
  23. container_registry = ContainerRegistry.getInstance()
  24. my_metadata = container_registry.findContainersMetadata(id = container_id)[0]
  25. self.base_file = my_metadata["base_file"]
  26. self.material_type = my_metadata["material"]
  27. self.guid = my_metadata["GUID"]
  28. self._loadAll()
  29. container_registry.containerRemoved.connect(self._onRemoved)
  30. container_registry.containerMetaDataChanged.connect(self._onMetadataChanged)
  31. def preferredQuality(self) -> QualityNode:
  32. """Finds the preferred quality for this printer with this material and this variant loaded.
  33. If the preferred quality is not available, an arbitrary quality is returned. If there is a configuration
  34. mistake (like a typo in the preferred quality) this returns a random available quality. If there are no
  35. available qualities, this will return the empty quality node.
  36. :return: The node for the preferred quality, or any arbitrary quality if there is no match.
  37. """
  38. for quality_id, quality_node in self.qualities.items():
  39. if self.variant.machine.preferred_quality_type == quality_node.quality_type:
  40. return quality_node
  41. fallback = next(iter(self.qualities.values())) # Should only happen with empty quality node.
  42. Logger.log("w", "Could not find preferred quality type {preferred_quality_type} for material {material_id} and variant {variant_id}, falling back to {fallback}.".format(
  43. preferred_quality_type = self.variant.machine.preferred_quality_type,
  44. material_id = self.container_id,
  45. variant_id = self.variant.container_id,
  46. fallback = fallback.container_id
  47. ))
  48. return fallback
  49. @UM.FlameProfiler.profile
  50. def _loadAll(self) -> None:
  51. container_registry = ContainerRegistry.getInstance()
  52. # Find all quality profiles that fit on this material.
  53. if not self.variant.machine.has_machine_quality: # Need to find the global qualities.
  54. qualities = container_registry.findInstanceContainersMetadata(type = "quality",
  55. definition = "fdmprinter")
  56. elif not self.variant.machine.has_materials:
  57. qualities = container_registry.findInstanceContainersMetadata(type = "quality",
  58. definition = self.variant.machine.quality_definition)
  59. else:
  60. if self.variant.machine.has_variants:
  61. # Need to find the qualities that specify a material profile with the same material type.
  62. qualities = container_registry.findInstanceContainersMetadata(type = "quality",
  63. definition = self.variant.machine.quality_definition,
  64. variant = self.variant.variant_name,
  65. material = self.base_file) # First try by exact material ID.
  66. # CURA-7070
  67. # The quality profiles only reference a material with the material_root_id. They will never state something
  68. # such as "generic_pla_ultimaker_s5_AA_0.4". So we search with the "base_file" which is the material_root_id.
  69. else:
  70. qualities = container_registry.findInstanceContainersMetadata(type = "quality", definition = self.variant.machine.quality_definition, material = self.base_file)
  71. if not qualities:
  72. my_material_type = self.material_type
  73. if self.variant.machine.has_variants:
  74. qualities_any_material = container_registry.findInstanceContainersMetadata(type = "quality",
  75. definition = self.variant.machine.quality_definition,
  76. variant = self.variant.variant_name)
  77. else:
  78. qualities_any_material = container_registry.findInstanceContainersMetadata(type = "quality", definition = self.variant.machine.quality_definition)
  79. all_material_base_files = {material_metadata["base_file"] for material_metadata in container_registry.findInstanceContainersMetadata(type = "material", material = my_material_type)}
  80. qualities.extend((quality for quality in qualities_any_material if quality.get("material") in all_material_base_files))
  81. if not qualities: # No quality profiles found. Go by GUID then.
  82. my_guid = self.guid
  83. for material_metadata in container_registry.findInstanceContainersMetadata(type = "material", guid = my_guid):
  84. qualities.extend((quality for quality in qualities_any_material if quality["material"] == material_metadata["base_file"]))
  85. if not qualities:
  86. # There are still some machines that should use global profiles in the extruder, so do that now.
  87. # These are mostly older machines that haven't received updates (so single extruder machines without specific qualities
  88. # but that do have materials and profiles specific to that machine)
  89. qualities.extend([quality for quality in qualities_any_material if quality.get("global_quality", "False") != "False"])
  90. for quality in qualities:
  91. quality_id = quality["id"]
  92. if quality_id not in self.qualities:
  93. self.qualities[quality_id] = QualityNode(quality_id, parent = self)
  94. if not self.qualities:
  95. self.qualities["empty_quality"] = QualityNode("empty_quality", parent = self)
  96. def _onRemoved(self, container: ContainerInterface) -> None:
  97. """Triggered when any container is removed, but only handles it when the container is removed that this node
  98. represents.
  99. :param container: The container that was allegedly removed.
  100. """
  101. if container.getId() == self.container_id:
  102. # Remove myself from my parent.
  103. if self.base_file in self.variant.materials:
  104. del self.variant.materials[self.base_file]
  105. if not self.variant.materials:
  106. self.variant.materials["empty_material"] = MaterialNode("empty_material", variant = self.variant)
  107. self.materialChanged.emit(self)
  108. def _onMetadataChanged(self, container: ContainerInterface, **kwargs: Any) -> None:
  109. """Triggered when any metadata changed in any container, but only handles it when the metadata of this node is
  110. changed.
  111. :param container: The container whose metadata changed.
  112. :param kwargs: Key-word arguments provided when changing the metadata. These are ignored. As far as I know they
  113. are never provided to this call.
  114. """
  115. if container.getId() != self.container_id:
  116. return
  117. new_metadata = container.getMetaData()
  118. old_base_file = self.base_file
  119. if new_metadata["base_file"] != old_base_file:
  120. self.base_file = new_metadata["base_file"]
  121. if old_base_file in self.variant.materials: # Move in parent node.
  122. del self.variant.materials[old_base_file]
  123. self.variant.materials[self.base_file] = self
  124. old_material_type = self.material_type
  125. self.material_type = new_metadata["material"]
  126. old_guid = self.guid
  127. self.guid = new_metadata["GUID"]
  128. if self.base_file != old_base_file or self.material_type != old_material_type or self.guid != old_guid: # List of quality profiles could've changed.
  129. self.qualities = {}
  130. self._loadAll() # Re-load the quality profiles for this node.
  131. self.materialChanged.emit(self)