GlobalStack.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. # Copyright (c) 2017 Ultimaker B.V.
  2. # Cura is released under the terms of the AGPLv3 or higher.
  3. from typing import Any, Dict, Optional
  4. from PyQt5.QtCore import pyqtProperty
  5. from UM.Decorators import override
  6. from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
  7. from UM.Settings.ContainerStack import ContainerStack
  8. from UM.Settings.SettingInstance import InstanceState
  9. from UM.Settings.ContainerRegistry import ContainerRegistry
  10. from UM.Logger import Logger
  11. from . import Exceptions
  12. from .CuraContainerStack import CuraContainerStack
  13. ## Represents the Global or Machine stack and its related containers.
  14. #
  15. class GlobalStack(CuraContainerStack):
  16. def __init__(self, container_id: str, *args, **kwargs):
  17. super().__init__(container_id, *args, **kwargs)
  18. self.addMetaDataEntry("type", "machine") # For backward compatibility
  19. self._extruders = {}
  20. # This property is used to track which settings we are calculating the "resolve" for
  21. # and if so, to bypass the resolve to prevent an infinite recursion that would occur
  22. # if the resolve function tried to access the same property it is a resolve for.
  23. self._resolving_settings = set()
  24. ## Get the list of extruders of this stack.
  25. #
  26. # \return The extruders registered with this stack.
  27. @pyqtProperty("QVariantMap")
  28. def extruders(self) -> Dict[str, "ExtruderStack"]:
  29. return self._extruders
  30. @classmethod
  31. def getLoadingPriority(cls) -> int:
  32. return 2
  33. def getConfigurationTypeFromSerialized(self, serialized: str) -> Optional[str]:
  34. configuration_type = None
  35. try:
  36. parser = self._readAndValidateSerialized(serialized)
  37. configuration_type = parser["metadata"].get("type")
  38. if configuration_type == "machine":
  39. configuration_type = "machine_stack"
  40. except Exception as e:
  41. Logger.log("e", "Could not get configuration type: %s", e)
  42. return configuration_type
  43. ## Add an extruder to the list of extruders of this stack.
  44. #
  45. # \param extruder The extruder to add.
  46. #
  47. # \throws Exceptions.TooManyExtrudersError Raised when trying to add an extruder while we
  48. # already have the maximum number of extruders.
  49. def addExtruder(self, extruder: ContainerStack) -> None:
  50. extruder_count = self.getProperty("machine_extruder_count", "value")
  51. if extruder_count <= 1:
  52. Logger.log("i", "Not adding extruder[%s] to [%s] because it is a single-extrusion machine.",
  53. extruder.id, self.id)
  54. return
  55. if extruder_count and len(self._extruders) + 1 > extruder_count:
  56. Logger.log("w", "Adding extruder {meta} to {id} but its extruder count is {count}".format(id = self.id, count = extruder_count, meta = str(extruder.getMetaData())))
  57. return
  58. position = extruder.getMetaDataEntry("position")
  59. if position is None:
  60. Logger.log("w", "No position defined for extruder {extruder}, cannot add it to stack {stack}", extruder = extruder.id, stack = self.id)
  61. return
  62. if any(item.getId() == extruder.id for item in self._extruders.values()):
  63. Logger.log("w", "Extruder [%s] has already been added to this stack [%s]", extruder.id, self._id)
  64. return
  65. self._extruders[position] = extruder
  66. Logger.log("i", "Extruder[%s] added to [%s] at position [%s]", extruder.id, self.id, position)
  67. ## Overridden from ContainerStack
  68. #
  69. # This will return the value of the specified property for the specified setting,
  70. # unless the property is "value" and that setting has a "resolve" function set.
  71. # When a resolve is set, it will instead try and execute the resolve first and
  72. # then fall back to the normal "value" property.
  73. #
  74. # \param key The setting key to get the property of.
  75. # \param property_name The property to get the value of.
  76. #
  77. # \return The value of the property for the specified setting, or None if not found.
  78. @override(ContainerStack)
  79. def getProperty(self, key: str, property_name: str) -> Any:
  80. if not self.definition.findDefinitions(key = key):
  81. return None
  82. # Handle the "resolve" property.
  83. if self._shouldResolve(key, property_name):
  84. self._resolving_settings.add(key)
  85. resolve = super().getProperty(key, "resolve")
  86. self._resolving_settings.remove(key)
  87. if resolve is not None:
  88. return resolve
  89. # Handle the "limit_to_extruder" property.
  90. limit_to_extruder = super().getProperty(key, "limit_to_extruder")
  91. if limit_to_extruder is not None and limit_to_extruder != "-1" and limit_to_extruder in self._extruders:
  92. if super().getProperty(key, "settable_per_extruder"):
  93. result = self._extruders[str(limit_to_extruder)].getProperty(key, property_name)
  94. if result is not None:
  95. return result
  96. else:
  97. Logger.log("e", "Setting {setting} has limit_to_extruder but is not settable per extruder!", setting = key)
  98. return super().getProperty(key, property_name)
  99. ## Overridden from ContainerStack
  100. #
  101. # This will simply raise an exception since the Global stack cannot have a next stack.
  102. @override(ContainerStack)
  103. def setNextStack(self, next_stack: ContainerStack) -> None:
  104. raise Exceptions.InvalidOperationError("Global stack cannot have a next stack!")
  105. ## Gets the approximate filament diameter that the machine requires.
  106. #
  107. # The approximate material diameter is the material diameter rounded to
  108. # the nearest millimetre.
  109. #
  110. # If the machine has no requirement for the diameter, -1 is returned.
  111. #
  112. # \return The approximate filament diameter for the printer, as a string.
  113. @pyqtProperty(str)
  114. def approximateMaterialDiameter(self) -> str:
  115. material_diameter = self.definition.getProperty("material_diameter", "value")
  116. if material_diameter is None:
  117. return "-1"
  118. return str(round(float(material_diameter))) #Round, then convert back to string.
  119. # protected:
  120. # Determine whether or not we should try to get the "resolve" property instead of the
  121. # requested property.
  122. def _shouldResolve(self, key: str, property_name: str) -> bool:
  123. if property_name is not "value":
  124. # Do not try to resolve anything but the "value" property
  125. return False
  126. if key in self._resolving_settings:
  127. # To prevent infinite recursion, if getProperty is called with the same key as
  128. # we are already trying to resolve, we should not try to resolve again. Since
  129. # this can happen multiple times when trying to resolve a value, we need to
  130. # track all settings that are being resolved.
  131. return False
  132. setting_state = super().getProperty(key, "state")
  133. if setting_state is not None and setting_state != InstanceState.Default:
  134. # When the user has explicitly set a value, we should ignore any resolve and
  135. # just return that value.
  136. return False
  137. return True
  138. ## private:
  139. global_stack_mime = MimeType(
  140. name = "application/x-cura-globalstack",
  141. comment = "Cura Global Stack",
  142. suffixes = ["global.cfg"]
  143. )
  144. MimeTypeDatabase.addMimeType(global_stack_mime)
  145. ContainerRegistry.addContainerTypeByName(GlobalStack, "global_stack", global_stack_mime.name)