MachineNode.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. # Copyright (c) 2019 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. from typing import Dict, List
  4. from UM.Logger import Logger
  5. from UM.Signal import Signal
  6. from UM.Util import parseBool
  7. from UM.Settings.ContainerRegistry import ContainerRegistry # To find all the variants for this machine.
  8. from cura.Machines.ContainerNode import ContainerNode
  9. from cura.Machines.QualityChangesGroup import QualityChangesGroup # To construct groups of quality changes profiles that belong together.
  10. from cura.Machines.QualityGroup import QualityGroup # To construct groups of quality profiles that belong together.
  11. from cura.Machines.QualityNode import QualityNode
  12. from cura.Machines.VariantNode import VariantNode
  13. import UM.FlameProfiler
  14. ## This class represents a machine in the container tree.
  15. #
  16. # The subnodes of these nodes are variants.
  17. class MachineNode(ContainerNode):
  18. def __init__(self, container_id: str) -> None:
  19. super().__init__(container_id)
  20. self.variants = {} # type: Dict[str, VariantNode] # Mapping variant names to their nodes.
  21. self.global_qualities = {} # type: Dict[str, QualityNode] # Mapping quality types to the global quality for those types.
  22. self.materialsChanged = Signal() # Emitted when one of the materials underneath this machine has been changed.
  23. container_registry = ContainerRegistry.getInstance()
  24. try:
  25. my_metadata = container_registry.findContainersMetadata(id = container_id)[0]
  26. except IndexError:
  27. Logger.log("Unable to find metadata for container %s", container_id)
  28. my_metadata = {}
  29. # Some of the metadata is cached upon construction here.
  30. # ONLY DO THAT FOR METADATA THAT DOESN'T CHANGE DURING RUNTIME!
  31. # Otherwise you need to keep it up-to-date during runtime.
  32. self.has_materials = parseBool(my_metadata.get("has_materials", "true"))
  33. self.has_variants = parseBool(my_metadata.get("has_variants", "false"))
  34. self.has_machine_quality = parseBool(my_metadata.get("has_machine_quality", "false"))
  35. self.quality_definition = my_metadata.get("quality_definition", container_id) if self.has_machine_quality else "fdmprinter"
  36. self.exclude_materials = my_metadata.get("exclude_materials", [])
  37. self.preferred_variant_name = my_metadata.get("preferred_variant_name", "")
  38. self.preferred_material = my_metadata.get("preferred_material", "")
  39. self.preferred_quality_type = my_metadata.get("preferred_quality_type", "")
  40. self._loadAll()
  41. ## Get the available quality groups for this machine.
  42. #
  43. # This returns all quality groups, regardless of whether they are
  44. # available to the combination of extruders or not. On the resulting
  45. # quality groups, the is_available property is set to indicate whether the
  46. # quality group can be selected according to the combination of extruders
  47. # in the parameters.
  48. # \param variant_names The names of the variants loaded in each extruder.
  49. # \param material_bases The base file names of the materials loaded in
  50. # each extruder.
  51. # \param extruder_enabled Whether or not the extruders are enabled. This
  52. # allows the function to set the is_available properly.
  53. # \return For each available quality type, a QualityGroup instance.
  54. def getQualityGroups(self, variant_names: List[str], material_bases: List[str], extruder_enabled: List[bool]) -> Dict[str, QualityGroup]:
  55. if len(variant_names) != len(material_bases) or len(variant_names) != len(extruder_enabled):
  56. Logger.log("e", "The number of extruders in the list of variants (" + str(len(variant_names)) + ") is not equal to the number of extruders in the list of materials (" + str(len(material_bases)) + ") or the list of enabled extruders (" + str(len(extruder_enabled)) + ").")
  57. return {}
  58. # For each extruder, find which quality profiles are available. Later we'll intersect the quality types.
  59. qualities_per_type_per_extruder = [{}] * len(variant_names) # type: List[Dict[str, QualityNode]]
  60. for extruder_nr, variant_name in enumerate(variant_names):
  61. if not extruder_enabled[extruder_nr]:
  62. continue # No qualities are available in this extruder. It'll get skipped when calculating the available quality types.
  63. material_base = material_bases[extruder_nr]
  64. if variant_name not in self.variants or material_base not in self.variants[variant_name].materials:
  65. # The printer has no variant/material-specific quality profiles. Use the global quality profiles.
  66. qualities_per_type_per_extruder[extruder_nr] = self.global_qualities
  67. else:
  68. # Use the actually specialised quality profiles.
  69. qualities_per_type_per_extruder[extruder_nr] = {node.getMetaDataEntry("quality_type"): node for node in self.variants[variant_name].materials[material_base].qualities.values()}
  70. # Create the quality group for each available type.
  71. quality_groups = {}
  72. for quality_type, global_quality_node in self.global_qualities.items():
  73. if not global_quality_node.container:
  74. Logger.log("w", "Node {0} doesn't have a container.".format(global_quality_node.container_id))
  75. continue
  76. # CURA-6599
  77. # Same as QualityChangesGroup.
  78. # For some reason, QML will get null or fail to convert type for MachineManager.activeQualityChangesGroup() to
  79. # a QObject. Setting the object ownership to QQmlEngine.CppOwnership doesn't work, but setting the object
  80. # parent to application seems to work.
  81. from cura.CuraApplication import CuraApplication
  82. quality_groups[quality_type] = QualityGroup(name = global_quality_node.container.getMetaDataEntry("name", "Unnamed profile"),
  83. quality_type = quality_type,
  84. parent = CuraApplication.getInstance())
  85. quality_groups[quality_type].node_for_global = global_quality_node
  86. for extruder, qualities_per_type in enumerate(qualities_per_type_per_extruder):
  87. if quality_type in qualities_per_type:
  88. quality_groups[quality_type].nodes_for_extruders[extruder] = qualities_per_type[quality_type]
  89. available_quality_types = set(quality_groups.keys())
  90. for extruder_nr, qualities_per_type in enumerate(qualities_per_type_per_extruder):
  91. if not extruder_enabled[extruder_nr]:
  92. continue
  93. available_quality_types.intersection_update(qualities_per_type.keys())
  94. for quality_type in available_quality_types:
  95. quality_groups[quality_type].is_available = True
  96. return quality_groups
  97. ## Returns all of the quality changes groups available to this printer.
  98. #
  99. # The quality changes groups store which quality type and intent category
  100. # they were made for, but not which material and nozzle. Instead for the
  101. # quality type and intent category, the quality changes will always be
  102. # available but change the quality type and intent category when
  103. # activated.
  104. #
  105. # The quality changes group does depend on the printer: Which quality
  106. # definition is used.
  107. #
  108. # The quality changes groups that are available do depend on the quality
  109. # types that are available, so it must still be known which extruders are
  110. # enabled and which materials and variants are loaded in them. This allows
  111. # setting the correct is_available flag.
  112. # \param variant_names The names of the variants loaded in each extruder.
  113. # \param material_bases The base file names of the materials loaded in
  114. # each extruder.
  115. # \param extruder_enabled For each extruder whether or not they are
  116. # enabled.
  117. # \return List of all quality changes groups for the printer.
  118. def getQualityChangesGroups(self, variant_names: List[str], material_bases: List[str], extruder_enabled: List[bool]) -> List[QualityChangesGroup]:
  119. machine_quality_changes = ContainerRegistry.getInstance().findContainersMetadata(type = "quality_changes", definition = self.quality_definition) # All quality changes for each extruder.
  120. groups_by_name = {} #type: Dict[str, QualityChangesGroup] # Group quality changes profiles by their display name. The display name must be unique for quality changes. This finds profiles that belong together in a group.
  121. for quality_changes in machine_quality_changes:
  122. name = quality_changes["name"]
  123. if name not in groups_by_name:
  124. # CURA-6599
  125. # For some reason, QML will get null or fail to convert type for MachineManager.activeQualityChangesGroup() to
  126. # a QObject. Setting the object ownership to QQmlEngine.CppOwnership doesn't work, but setting the object
  127. # parent to application seems to work.
  128. from cura.CuraApplication import CuraApplication
  129. groups_by_name[name] = QualityChangesGroup(name, quality_type = quality_changes["quality_type"],
  130. intent_category = quality_changes.get("intent_category", "default"),
  131. parent = CuraApplication.getInstance())
  132. elif groups_by_name[name].intent_category == "default": # Intent category should be stored as "default" if everything is default or as the intent if any of the extruder have an actual intent.
  133. groups_by_name[name].intent_category = quality_changes.get("intent_category", "default")
  134. if "position" in quality_changes: # An extruder profile.
  135. groups_by_name[name].metadata_per_extruder[int(quality_changes["position"])] = quality_changes
  136. else: # Global profile.
  137. groups_by_name[name].metadata_for_global = quality_changes
  138. quality_groups = self.getQualityGroups(variant_names, material_bases, extruder_enabled)
  139. for quality_changes_group in groups_by_name.values():
  140. if quality_changes_group.quality_type not in quality_groups:
  141. quality_changes_group.is_available = False
  142. else:
  143. # Quality changes group is available iff the quality group it depends on is available. Irrespective of whether the intent category is available.
  144. quality_changes_group.is_available = quality_groups[quality_changes_group.quality_type].is_available
  145. return list(groups_by_name.values())
  146. ## Gets the preferred global quality node, going by the preferred quality
  147. # type.
  148. #
  149. # If the preferred global quality is not in there, an arbitrary global
  150. # quality is taken.
  151. # If there are no global qualities, an empty quality is returned.
  152. def preferredGlobalQuality(self) -> "QualityNode":
  153. return self.global_qualities.get(self.preferred_quality_type, next(iter(self.global_qualities.values())))
  154. ## (Re)loads all variants under this printer.
  155. @UM.FlameProfiler.profile
  156. def _loadAll(self):
  157. container_registry = ContainerRegistry.getInstance()
  158. if not self.has_variants:
  159. self.variants["empty"] = VariantNode("empty_variant", machine = self)
  160. else:
  161. # Find all the variants for this definition ID.
  162. variants = container_registry.findInstanceContainersMetadata(type = "variant", definition = self.container_id, hardware_type = "nozzle")
  163. for variant in variants:
  164. variant_name = variant["name"]
  165. if variant_name not in self.variants:
  166. self.variants[variant_name] = VariantNode(variant["id"], machine = self)
  167. self.variants[variant_name].materialsChanged.connect(self.materialsChanged)
  168. if not self.variants:
  169. self.variants["empty"] = VariantNode("empty_variant", machine = self)
  170. # Find the global qualities for this printer.
  171. global_qualities = container_registry.findInstanceContainersMetadata(type = "quality", definition = self.quality_definition, global_quality = "True") # First try specific to this printer.
  172. if len(global_qualities) == 0: # This printer doesn't override the global qualities.
  173. global_qualities = container_registry.findInstanceContainersMetadata(type = "quality", definition = "fdmprinter", global_quality = "True") # Otherwise pick the global global qualities.
  174. for global_quality in global_qualities:
  175. self.global_qualities[global_quality["quality_type"]] = QualityNode(global_quality["id"], parent = self)