VariantNode.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. # Copyright (c) 2019 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. from typing import 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.MaterialNode import MaterialNode
  10. if TYPE_CHECKING:
  11. from typing import Dict
  12. from cura.Machines.MachineNode import MachineNode
  13. ## This class represents an extruder variant in the container tree.
  14. #
  15. # The subnodes of these nodes are materials.
  16. #
  17. # This node contains materials with ALL filament diameters underneath it. The
  18. # tree of this variant is not specific to one global stack, so because the
  19. # list of materials can be different per stack depending on the compatible
  20. # material diameter setting, we cannot filter them here. Filtering must be
  21. # done in the model.
  22. class VariantNode(ContainerNode):
  23. def __init__(self, container_id: str, machine: "MachineNode") -> None:
  24. super().__init__(container_id)
  25. self.machine = machine
  26. self.materials = {} # type: Dict[str, MaterialNode] # Mapping material base files to their nodes.
  27. self.materialsChanged = Signal()
  28. container_registry = ContainerRegistry.getInstance()
  29. self.variant_name = container_registry.findContainersMetadata(id = container_id)[0]["name"] # Store our own name so that we can filter more easily.
  30. container_registry.containerAdded.connect(self._materialAdded)
  31. self._loadAll()
  32. ## (Re)loads all materials under this variant.
  33. def _loadAll(self):
  34. container_registry = ContainerRegistry.getInstance()
  35. if not self.machine.has_materials:
  36. self.materials["empty_material"] = MaterialNode("empty_material", variant = self)
  37. return # There should not be any materials loaded for this printer.
  38. # Find all the materials for this variant's name.
  39. if not self.machine.has_machine_materials: # Printer has no specific materials. Look for all fdmprinter materials.
  40. materials = container_registry.findInstanceContainersMetadata(type = "material", definition = "fdmprinter") # These are ONLY the base materials.
  41. else: # Printer has its own material profiles. Look for material profiles with this printer's definition.
  42. all_materials = container_registry.findInstanceContainersMetadata(type = "material", definition = "fdmprinter")
  43. printer_specific_materials = container_registry.findInstanceContainersMetadata(type = "material", definition = self.machine.container_id)
  44. variant_specific_materials = container_registry.findInstanceContainersMetadata(type = "material", definition = self.machine.container_id, variant = self.variant_name) # If empty_variant, this won't return anything.
  45. materials_per_base_file = {material["base_file"]: material for material in all_materials}
  46. materials_per_base_file.update({material["base_file"]: material for material in printer_specific_materials}) # Printer-specific profiles override global ones.
  47. materials_per_base_file.update({material["base_file"]: material for material in variant_specific_materials}) # Variant-specific profiles override all of those.
  48. materials = materials_per_base_file.values()
  49. filtered_materials = []
  50. for material in materials:
  51. if material["id"] not in self.machine.exclude_materials:
  52. filtered_materials.append(material)
  53. for material in filtered_materials:
  54. base_file = material["base_file"]
  55. if base_file not in self.materials:
  56. self.materials[base_file] = MaterialNode(material["id"], variant = self)
  57. self.materials[base_file].materialChanged.connect(self.materialsChanged)
  58. if not self.materials:
  59. self.materials["empty_material"] = MaterialNode("empty_material", variant = self)
  60. ## Finds the preferred material for this printer with this nozzle in one of
  61. # the extruders.
  62. #
  63. # If there is no material here (because the printer has no materials or
  64. # because there are no matching material profiles), None is returned.
  65. # \param approximate_diameter The desired approximate diameter of the
  66. # material.
  67. # \return The node for the preferred material, or None if there is no
  68. # match.
  69. def preferredMaterial(self, approximate_diameter) -> MaterialNode:
  70. for base_material, material_node in self.materials.items():
  71. if self.machine.preferred_material in base_material and approximate_diameter == int(material_node.getMetaDataEntry("approximate_diameter")):
  72. return material_node
  73. fallback = next(iter(self.materials.values())) # Should only happen with empty material node.
  74. Logger.log("w", "Could not find preferred material {preferred_material} with diameter {diameter} for variant {variant_id}, falling back to {fallback}.".format(
  75. preferred_material = self.machine.preferred_material,
  76. diameter = approximate_diameter,
  77. variant_id = self.container_id,
  78. fallback = fallback.container_id
  79. ))
  80. return fallback
  81. ## When a material gets added to the set of profiles, we need to update our
  82. # tree here.
  83. def _materialAdded(self, container: ContainerInterface):
  84. if container.getMetaDataEntry("type") != "material":
  85. return # Not interested.
  86. if not self.machine.has_materials:
  87. return # We won't add any materials.
  88. material_definition = container.getMetaDataEntry("definition")
  89. if not self.machine.has_machine_materials:
  90. if material_definition != "fdmprinter":
  91. return
  92. base_file = container.getMetaDataEntry("base_file")
  93. if base_file in self.machine.exclude_materials:
  94. return # Material is forbidden for this printer.
  95. if base_file not in self.materials: # Completely new base file. Always better than not having a file as long as it matches our set-up.
  96. if material_definition != "fdmprinter" and material_definition != self.machine.container_id:
  97. return
  98. material_variant = container.getMetaDataEntry("variant", "empty")
  99. if material_variant != "empty" and material_variant != self.variant_name:
  100. return
  101. else: # We already have this base profile. Replace the base profile if the new one is more specific.
  102. new_definition = container.getMetaDataEntry("definition")
  103. if new_definition == "fdmprinter":
  104. return # Just as unspecific or worse.
  105. if new_definition != self.machine.container_id:
  106. return # Doesn't match this set-up.
  107. original_metadata = ContainerRegistry.getInstance().findContainersMetadata(id = self.materials[base_file].container_id)[0]
  108. original_variant = original_metadata.get("variant", "empty")
  109. if original_variant != "empty" or container.getMetaDataEntry("variant", "empty") == "empty":
  110. return # Original was already specific or just as unspecific as the new one.
  111. if "empty_material" in self.materials:
  112. del self.materials["empty_material"]
  113. self.materials[base_file] = MaterialNode(container.getId(), variant = self)
  114. self.materials[base_file].materialChanged.connect(self.materialsChanged)