VariantManager.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  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. variant_container = self._container_registry.findContainers(type = "variant", id = variant_metadata["id"])[0]
  73. buildplate_type = variant_container.getProperty("machine_buildplate_type", "value")
  74. if buildplate_type not in self._machine_to_buildplate_dict_map[variant_definition]:
  75. self._machine_to_variant_dict_map[variant_definition][buildplate_type] = dict()
  76. self._machine_to_buildplate_dict_map[variant_definition][buildplate_type] = variant_dict[variant_name]
  77. #
  78. # Gets the variant InstanceContainer with the given information.
  79. # Almost the same as getVariantMetadata() except that this returns an InstanceContainer if present.
  80. #
  81. def getVariantNode(self, machine_definition_id: str, variant_name: str,
  82. variant_type: Optional["VariantType"] = None) -> Optional["ContainerNode"]:
  83. if variant_type is None:
  84. variant_node = None
  85. variant_type_dict = self._machine_to_variant_dict_map[machine_definition_id]
  86. for variant_dict in variant_type_dict.values():
  87. if variant_name in variant_dict:
  88. variant_node = variant_dict[variant_name]
  89. break
  90. return variant_node
  91. return self._machine_to_variant_dict_map.get(machine_definition_id, {}).get(variant_type, {}).get(variant_name)
  92. def getVariantNodes(self, machine: "GlobalStack", variant_type: "VariantType") -> Dict[str, ContainerNode]:
  93. machine_definition_id = machine.definition.getId()
  94. return self._machine_to_variant_dict_map.get(machine_definition_id, {}).get(variant_type, {})
  95. #
  96. # Gets the default variant for the given machine definition.
  97. # If the optional GlobalStack is given, the metadata information will be fetched from the GlobalStack instead of
  98. # the DefinitionContainer. Because for machines such as UM2, you can enable Olsson Block, which will set
  99. # "has_variants" to True in the GlobalStack. In those cases, we need to fetch metadata from the GlobalStack or
  100. # it may not be correct.
  101. #
  102. def getDefaultVariantNode(self, machine_definition: "DefinitionContainer",
  103. variant_type: "VariantType",
  104. global_stack: Optional["GlobalStack"] = None) -> Optional["ContainerNode"]:
  105. machine_definition_id = machine_definition.getId()
  106. container_for_metadata_fetching = global_stack if global_stack is not None else machine_definition
  107. preferred_variant_name = None
  108. if variant_type == VariantType.BUILD_PLATE:
  109. if parseBool(container_for_metadata_fetching.getMetaDataEntry("has_variant_buildplates", False)):
  110. preferred_variant_name = container_for_metadata_fetching.getMetaDataEntry("preferred_variant_buildplate_name")
  111. else:
  112. if parseBool(container_for_metadata_fetching.getMetaDataEntry("has_variants", False)):
  113. preferred_variant_name = container_for_metadata_fetching.getMetaDataEntry("preferred_variant_name")
  114. node = None
  115. if preferred_variant_name:
  116. node = self.getVariantNode(machine_definition_id, preferred_variant_name, variant_type)
  117. return node
  118. def getBuildplateVariantNode(self, machine_definition_id: str, buildplate_type: str) -> Optional["ContainerNode"]:
  119. if machine_definition_id in self._machine_to_buildplate_dict_map:
  120. return self._machine_to_buildplate_dict_map[machine_definition_id].get(buildplate_type)
  121. return None