MaterialNode.py 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  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.brand = my_metadata["brand"]
  28. self.guid = my_metadata["GUID"]
  29. self._loadAll()
  30. container_registry.containerRemoved.connect(self._onRemoved)
  31. container_registry.containerMetaDataChanged.connect(self._onMetadataChanged)
  32. def preferredQuality(self) -> QualityNode:
  33. """Finds the preferred quality for this printer with this material and this variant loaded.
  34. If the preferred quality is not available, an arbitrary quality is returned. If there is a configuration
  35. mistake (like a typo in the preferred quality) this returns a random available quality. If there are no
  36. available qualities, this will return the empty quality node.
  37. :return: The node for the preferred quality, or any arbitrary quality if there is no match.
  38. """
  39. for quality_id, quality_node in self.qualities.items():
  40. if self.variant.machine.preferred_quality_type == quality_node.quality_type:
  41. return quality_node
  42. fallback = next(iter(self.qualities.values())) # Should only happen with empty quality node.
  43. 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(
  44. preferred_quality_type = self.variant.machine.preferred_quality_type,
  45. material_id = self.container_id,
  46. variant_id = self.variant.container_id,
  47. fallback = fallback.container_id
  48. ))
  49. return fallback
  50. @UM.FlameProfiler.profile
  51. def _loadAll(self) -> None:
  52. container_registry = ContainerRegistry.getInstance()
  53. # Find all quality profiles that fit on this material.
  54. if not self.variant.machine.has_machine_quality: # Need to find the global qualities.
  55. qualities = container_registry.findInstanceContainersMetadata(type = "quality",
  56. definition = "fdmprinter")
  57. elif not self.variant.machine.has_materials:
  58. qualities = container_registry.findInstanceContainersMetadata(type = "quality",
  59. definition = self.variant.machine.quality_definition)
  60. else:
  61. if self.variant.machine.has_variants:
  62. # Need to find the qualities that specify a material profile with the same material type.
  63. qualities = container_registry.findInstanceContainersMetadata(type = "quality",
  64. definition = self.variant.machine.quality_definition,
  65. variant = self.variant.variant_name,
  66. material = self.base_file) # First try by exact material ID.
  67. # CURA-7070
  68. # The quality profiles only reference a material with the material_root_id. They will never state something
  69. # such as "generic_pla_ultimaker_s5_AA_0.4". So we search with the "base_file" which is the material_root_id.
  70. else:
  71. qualities = container_registry.findInstanceContainersMetadata(type = "quality", definition = self.variant.machine.quality_definition, material = self.base_file)
  72. if not qualities:
  73. my_material_type = self.material_type
  74. if self.variant.machine.has_variants:
  75. qualities_any_material = container_registry.findInstanceContainersMetadata(type = "quality",
  76. definition = self.variant.machine.quality_definition,
  77. variant = self.variant.variant_name)
  78. else:
  79. qualities_any_material = container_registry.findInstanceContainersMetadata(type = "quality", definition = self.variant.machine.quality_definition)
  80. # First we attempt to find materials that have the same brand but not the right color
  81. all_material_base_files_right_brand = {material_metadata["base_file"] for material_metadata in container_registry.findInstanceContainersMetadata(type = "material", material = my_material_type, brand = self.brand)}
  82. right_brand_no_color_qualities = [quality for quality in qualities_any_material if quality.get("material") in all_material_base_files_right_brand]
  83. if right_brand_no_color_qualities:
  84. # We found qualties for materials with the right brand but not with the right color. Use those.
  85. qualities.extend(right_brand_no_color_qualities)
  86. else:
  87. # Fall back to generic
  88. all_material_base_files = {material_metadata["base_file"] for material_metadata in
  89. container_registry.findInstanceContainersMetadata(type="material",
  90. material=my_material_type)}
  91. no_brand_no_color_qualities = (quality for quality in qualities_any_material if
  92. quality.get("material") in all_material_base_files)
  93. qualities.extend(no_brand_no_color_qualities)
  94. if not qualities: # No quality profiles found. Go by GUID then.
  95. my_guid = self.guid
  96. for material_metadata in container_registry.findInstanceContainersMetadata(type = "material", guid = my_guid):
  97. qualities.extend((quality for quality in qualities_any_material if quality["material"] == material_metadata["base_file"]))
  98. if not qualities:
  99. # There are still some machines that should use global profiles in the extruder, so do that now.
  100. # These are mostly older machines that haven't received updates (so single extruder machines without specific qualities
  101. # but that do have materials and profiles specific to that machine)
  102. qualities.extend([quality for quality in qualities_any_material if quality.get("global_quality", "False") != "False"])
  103. for quality in qualities:
  104. quality_id = quality["id"]
  105. if quality_id not in self.qualities:
  106. self.qualities[quality_id] = QualityNode(quality_id, parent = self)
  107. if not self.qualities:
  108. self.qualities["empty_quality"] = QualityNode("empty_quality", parent = self)
  109. def _onRemoved(self, container: ContainerInterface) -> None:
  110. """Triggered when any container is removed, but only handles it when the container is removed that this node
  111. represents.
  112. :param container: The container that was allegedly removed.
  113. """
  114. if container.getId() == self.container_id:
  115. # Remove myself from my parent.
  116. if self.base_file in self.variant.materials:
  117. del self.variant.materials[self.base_file]
  118. if not self.variant.materials:
  119. self.variant.materials["empty_material"] = MaterialNode("empty_material", variant = self.variant)
  120. self.materialChanged.emit(self)
  121. def _onMetadataChanged(self, container: ContainerInterface, **kwargs: Any) -> None:
  122. """Triggered when any metadata changed in any container, but only handles it when the metadata of this node is
  123. changed.
  124. :param container: The container whose metadata changed.
  125. :param kwargs: Key-word arguments provided when changing the metadata. These are ignored. As far as I know they
  126. are never provided to this call.
  127. """
  128. if container.getId() != self.container_id:
  129. return
  130. new_metadata = container.getMetaData()
  131. old_base_file = self.base_file
  132. if new_metadata["base_file"] != old_base_file:
  133. self.base_file = new_metadata["base_file"]
  134. if old_base_file in self.variant.materials: # Move in parent node.
  135. del self.variant.materials[old_base_file]
  136. self.variant.materials[self.base_file] = self
  137. old_material_type = self.material_type
  138. self.material_type = new_metadata["material"]
  139. old_guid = self.guid
  140. self.guid = new_metadata["GUID"]
  141. 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.
  142. self.qualities = {}
  143. self._loadAll() # Re-load the quality profiles for this node.
  144. self.materialChanged.emit(self)