VariantManager.py 8.1 KB

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