MaterialNode.py 8.8 KB

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