VariantManager.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. # Copyright (c) 2019 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. from collections import OrderedDict
  4. from typing import Optional, TYPE_CHECKING, Dict
  5. from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
  6. from UM.Decorators import deprecated
  7. from UM.Logger import Logger
  8. from UM.Util import parseBool
  9. from cura.Machines.ContainerNode import ContainerNode
  10. from cura.Machines.VariantType import VariantType, ALL_VARIANT_TYPES
  11. from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
  12. from cura.Settings.GlobalStack import GlobalStack
  13. if TYPE_CHECKING:
  14. from UM.Settings.DefinitionContainer import DefinitionContainer
  15. #
  16. # VariantManager is THE place to look for a specific variant. It maintains two variant lookup tables with the following
  17. # structure:
  18. #
  19. # [machine_definition_id] -> [variant_type] -> [variant_name] -> ContainerNode(metadata / container)
  20. # Example: "ultimaker3" -> "buildplate" -> "Glass" (if present) -> ContainerNode
  21. # -> ...
  22. # -> "nozzle" -> "AA 0.4"
  23. # -> "BB 0.8"
  24. # -> ...
  25. #
  26. # [machine_definition_id] -> [machine_buildplate_type] -> ContainerNode(metadata / container)
  27. # Example: "ultimaker3" -> "glass" (this is different from the variant name) -> ContainerNode
  28. #
  29. # Note that the "container" field is not loaded in the beginning because it would defeat the purpose of lazy-loading.
  30. # A container is loaded when getVariant() is called to load a variant InstanceContainer.
  31. #
  32. class VariantManager:
  33. __instance = None
  34. @classmethod
  35. @deprecated("Use the ContainerTree structure instead.", since = "4.3")
  36. def getInstance(cls) -> "VariantManager":
  37. if cls.__instance is None:
  38. cls.__instance = VariantManager()
  39. return cls.__instance
  40. def __init__(self) -> None:
  41. self._machine_to_variant_dict_map = dict() # type: Dict[str, Dict["VariantType", Dict[str, ContainerNode]]]
  42. self._machine_to_buildplate_dict_map = dict() # type: Dict[str, Dict[str, ContainerNode]]
  43. self._exclude_variant_id_list = ["empty_variant"]
  44. #
  45. # Initializes the VariantManager including:
  46. # - initializing the variant lookup table based on the metadata in ContainerRegistry.
  47. #
  48. def initialize(self) -> None:
  49. self._machine_to_variant_dict_map = OrderedDict()
  50. self._machine_to_buildplate_dict_map = OrderedDict()
  51. # Cache all variants from the container registry to a variant map for better searching and organization.
  52. container_registry = CuraContainerRegistry.getInstance
  53. variant_metadata_list = container_registry.findContainersMetadata(type = "variant")
  54. for variant_metadata in variant_metadata_list:
  55. if variant_metadata["id"] in self._exclude_variant_id_list:
  56. Logger.log("d", "Exclude variant [%s]", variant_metadata["id"])
  57. continue
  58. variant_name = variant_metadata["name"]
  59. variant_definition = variant_metadata["definition"]
  60. if variant_definition not in self._machine_to_variant_dict_map:
  61. self._machine_to_variant_dict_map[variant_definition] = OrderedDict()
  62. for variant_type in ALL_VARIANT_TYPES:
  63. self._machine_to_variant_dict_map[variant_definition][variant_type] = dict()
  64. try:
  65. variant_type = variant_metadata["hardware_type"]
  66. except KeyError:
  67. Logger.log("w", "Variant %s does not specify a hardware_type; assuming 'nozzle'", variant_metadata["id"])
  68. variant_type = VariantType.NOZZLE
  69. variant_type = VariantType(variant_type)
  70. variant_dict = self._machine_to_variant_dict_map[variant_definition][variant_type]
  71. if variant_name in variant_dict:
  72. # ERROR: duplicated variant name.
  73. ConfigurationErrorMessage.getInstance().addFaultyContainers(variant_metadata["id"])
  74. continue #Then ignore this variant. This now chooses one of the two variants arbitrarily and deletes the other one! No guarantees!
  75. variant_dict[variant_name] = ContainerNode(metadata = variant_metadata)
  76. # If the variant is a buildplate then fill also the buildplate map
  77. if variant_type == VariantType.BUILD_PLATE:
  78. if variant_definition not in self._machine_to_buildplate_dict_map:
  79. self._machine_to_buildplate_dict_map[variant_definition] = OrderedDict()
  80. try:
  81. variant_container = container_registry.findContainers(type = "variant", id = variant_metadata["id"])[0]
  82. except IndexError as e:
  83. # It still needs to break, but we want to know what variant ID made it break.
  84. msg = "Unable to find build plate variant with the id [%s]" % variant_metadata["id"]
  85. Logger.logException("e", msg)
  86. raise IndexError(msg)
  87. buildplate_type = variant_container.getProperty("machine_buildplate_type", "value")
  88. if buildplate_type not in self._machine_to_buildplate_dict_map[variant_definition]:
  89. self._machine_to_variant_dict_map[variant_definition][buildplate_type] = dict()
  90. self._machine_to_buildplate_dict_map[variant_definition][buildplate_type] = variant_dict[variant_name]
  91. #
  92. # Gets the variant InstanceContainer with the given information.
  93. # Almost the same as getVariantMetadata() except that this returns an InstanceContainer if present.
  94. #
  95. def getVariantNode(self, machine_definition_id: str, variant_name: str,
  96. variant_type: Optional["VariantType"] = None) -> Optional["ContainerNode"]:
  97. if variant_type is None:
  98. variant_node = None
  99. variant_type_dict = self._machine_to_variant_dict_map.get("machine_definition_id", {})
  100. for variant_dict in variant_type_dict.values():
  101. if variant_name in variant_dict:
  102. variant_node = variant_dict[variant_name]
  103. break
  104. return variant_node
  105. return self._machine_to_variant_dict_map.get(machine_definition_id, {}).get(variant_type, {}).get(variant_name)
  106. def getVariantNodes(self, machine: "GlobalStack", variant_type: "VariantType") -> Dict[str, ContainerNode]:
  107. machine_definition_id = machine.definition.getId()
  108. return self._machine_to_variant_dict_map.get(machine_definition_id, {}).get(variant_type, {})
  109. #
  110. # Gets the default variant for the given machine definition.
  111. # If the optional GlobalStack is given, the metadata information will be fetched from the GlobalStack instead of
  112. # the DefinitionContainer. Because for machines such as UM2, you can enable Olsson Block, which will set
  113. # "has_variants" to True in the GlobalStack. In those cases, we need to fetch metadata from the GlobalStack or
  114. # it may not be correct.
  115. #
  116. def getDefaultVariantNode(self, machine_definition: "DefinitionContainer",
  117. variant_type: "VariantType",
  118. global_stack: Optional["GlobalStack"] = None) -> Optional["ContainerNode"]:
  119. machine_definition_id = machine_definition.getId()
  120. container_for_metadata_fetching = global_stack if global_stack is not None else machine_definition
  121. preferred_variant_name = None
  122. if variant_type == VariantType.BUILD_PLATE:
  123. if parseBool(container_for_metadata_fetching.getMetaDataEntry("has_variant_buildplates", False)):
  124. preferred_variant_name = container_for_metadata_fetching.getMetaDataEntry("preferred_variant_buildplate_name")
  125. else:
  126. if parseBool(container_for_metadata_fetching.getMetaDataEntry("has_variants", False)):
  127. preferred_variant_name = container_for_metadata_fetching.getMetaDataEntry("preferred_variant_name")
  128. node = None
  129. if preferred_variant_name:
  130. node = self.getVariantNode(machine_definition_id, preferred_variant_name, variant_type)
  131. return node
  132. def getBuildplateVariantNode(self, machine_definition_id: str, buildplate_type: str) -> Optional["ContainerNode"]:
  133. if machine_definition_id in self._machine_to_buildplate_dict_map:
  134. return self._machine_to_buildplate_dict_map[machine_definition_id].get(buildplate_type)
  135. return None