CuraFormulaFunctions.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. # Copyright (c) 2018 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. from typing import Any, List, Optional, Union, TYPE_CHECKING
  4. from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext
  5. from UM.Settings.SettingFunction import SettingFunction
  6. from UM.Logger import Logger
  7. if TYPE_CHECKING:
  8. from cura.CuraApplication import CuraApplication
  9. from cura.Settings.CuraContainerStack import CuraContainerStack
  10. #
  11. # This class contains all Cura-related custom functions that can be used in formulas. Some functions requires
  12. # information such as the currently active machine, so this is made into a class instead of standalone functions.
  13. #
  14. class CuraFormulaFunctions:
  15. def __init__(self, application: "CuraApplication") -> None:
  16. self._application = application
  17. # ================
  18. # Custom Functions
  19. # ================
  20. # Gets the default extruder position of the currently active machine.
  21. def getDefaultExtruderPosition(self) -> str:
  22. machine_manager = self._application.getMachineManager()
  23. return machine_manager.defaultExtruderPosition
  24. # Gets the given setting key from the given extruder position.
  25. def getValueInExtruder(self, extruder_position: int, property_key: str,
  26. context: Optional["PropertyEvaluationContext"] = None) -> Any:
  27. machine_manager = self._application.getMachineManager()
  28. if extruder_position == -1:
  29. extruder_position = int(machine_manager.defaultExtruderPosition)
  30. global_stack = machine_manager.activeMachine
  31. try:
  32. extruder_stack = global_stack.extruderList[int(extruder_position)]
  33. except IndexError:
  34. if extruder_position != 0:
  35. Logger.log("w", "Value for %s of extruder %s was requested, but that extruder is not available. Returning the result from extruder 0 instead" % (property_key, extruder_position))
  36. # This fixes a very specific fringe case; If a profile was created for a custom printer and one of the
  37. # extruder settings has been set to non zero and the profile is loaded for a machine that has only a single extruder
  38. # it would cause all kinds of issues (and eventually a crash).
  39. # See https://github.com/Ultimaker/Cura/issues/5535
  40. return self.getValueInExtruder(0, property_key, context)
  41. Logger.log("w", "Value for %s of extruder %s was requested, but that extruder is not available. " % (property_key, extruder_position))
  42. return None
  43. value = extruder_stack.getRawProperty(property_key, "value", context = context)
  44. if isinstance(value, SettingFunction):
  45. value = value(extruder_stack, context = context)
  46. if isinstance(value, str):
  47. value = value.lower()
  48. return value
  49. def _getActiveExtruders(self, context: Optional["PropertyEvaluationContext"] = None,
  50. where: Union[str, List[str]] = None, where_not: Union[str, List[str]] = None) -> List[str]:
  51. machine_manager = self._application.getMachineManager()
  52. extruder_manager = self._application.getExtruderManager()
  53. global_stack = machine_manager.activeMachine
  54. if isinstance(where, str):
  55. where = [where]
  56. if isinstance(where_not, str):
  57. where_not = [where_not]
  58. enabled_extruders = []
  59. filtered_extruders = []
  60. for extruder in extruder_manager.getActiveExtruderStacks():
  61. if not extruder.isEnabled:
  62. continue
  63. # only include values from extruders that are "active" for the current machine instance
  64. if int(extruder.getMetaDataEntry("position")) >= global_stack.getProperty("machine_extruder_count", "value", context = context):
  65. continue
  66. enabled_extruders.append(extruder)
  67. if where and not all(extruder.getProperty(key, "value", context=context) for key in where):
  68. continue
  69. if where_not and any(extruder.getProperty(key, "value", context=context) for key in where_not):
  70. continue
  71. filtered_extruders.append(extruder)
  72. return filtered_extruders if filtered_extruders else enabled_extruders
  73. # Gets all extruder values as a list for the given property.
  74. def getValuesInAllExtruders(self, property_key: str,
  75. context: Optional["PropertyEvaluationContext"] = None,
  76. *, where: str = None, where_not: str = None) -> List[Any]:
  77. global_stack = self._application.getMachineManager().activeMachine
  78. result = []
  79. for extruder in self._getActiveExtruders(context, where=where, where_not=where_not):
  80. value = extruder.getRawProperty(property_key, "value", context = context)
  81. if value is None:
  82. continue
  83. if isinstance(value, SettingFunction):
  84. value = value(extruder, context = context)
  85. result.append(value)
  86. if not result:
  87. result.append(global_stack.getProperty(property_key, "value", context = context))
  88. return result
  89. # Get the first extruder that adheres to a specific (boolean) property, like 'material_is_support_material'.
  90. def getAnyExtruderPositionWithOrDefault(self, filter_key: str,
  91. context: Optional["PropertyEvaluationContext"] = None) -> str:
  92. for extruder in self._getActiveExtruders(context):
  93. value = extruder.getRawProperty(filter_key, "value", context=context)
  94. if value is None or not value:
  95. continue
  96. return str(extruder.position)
  97. # Get the first extruder with material that adheres to a specific (boolean) property, like 'material_is_support_material'.
  98. def getExtruderPositionWithMaterial(self, filter_key: str,
  99. context: Optional["PropertyEvaluationContext"] = None) -> str:
  100. for extruder in self._getActiveExtruders(context):
  101. material_container = extruder.material
  102. value = material_container.getProperty(filter_key, "value", context)
  103. if value is not None:
  104. return str(extruder.position)
  105. return self.getDefaultExtruderPosition()
  106. # Get the resolve value or value for a given key.
  107. def getResolveOrValue(self, property_key: str, context: Optional["PropertyEvaluationContext"] = None) -> Any:
  108. machine_manager = self._application.getMachineManager()
  109. global_stack = machine_manager.activeMachine
  110. resolved_value = global_stack.getProperty(property_key, "value", context = context)
  111. return resolved_value
  112. # Gets the default setting value from given extruder position. The default value is what excludes the values in
  113. # the user_changes container.
  114. def getDefaultValueInExtruder(self, extruder_position: int, property_key: str) -> Any:
  115. machine_manager = self._application.getMachineManager()
  116. global_stack = machine_manager.activeMachine
  117. try:
  118. extruder_stack = global_stack.extruderList[extruder_position]
  119. except IndexError:
  120. Logger.log("w", "Unable to find extruder on in index %s", extruder_position)
  121. else:
  122. context = self.createContextForDefaultValueEvaluation(extruder_stack)
  123. return self.getValueInExtruder(extruder_position, property_key, context = context)
  124. # Gets all default setting values as a list from all extruders of the currently active machine.
  125. # The default values are those excluding the values in the user_changes container.
  126. def getDefaultValuesInAllExtruders(self, property_key: str) -> List[Any]:
  127. machine_manager = self._application.getMachineManager()
  128. global_stack = machine_manager.activeMachine
  129. context = self.createContextForDefaultValueEvaluation(global_stack)
  130. return self.getValuesInAllExtruders(property_key, context = context)
  131. # Gets the resolve value or value for a given key without looking the first container (user container).
  132. def getDefaultResolveOrValue(self, property_key: str) -> Any:
  133. machine_manager = self._application.getMachineManager()
  134. global_stack = machine_manager.activeMachine
  135. context = self.createContextForDefaultValueEvaluation(global_stack)
  136. return self.getResolveOrValue(property_key, context = context)
  137. # Gets the value for the given setting key starting from the given container index.
  138. def getValueFromContainerAtIndex(self, property_key: str, container_index: int,
  139. context: Optional["PropertyEvaluationContext"] = None) -> Any:
  140. machine_manager = self._application.getMachineManager()
  141. global_stack = machine_manager.activeMachine
  142. context = self.createContextForDefaultValueEvaluation(global_stack)
  143. context.context["evaluate_from_container_index"] = container_index
  144. return global_stack.getProperty(property_key, "value", context = context)
  145. # Gets the extruder value for the given setting key starting from the given container index.
  146. def getValueFromContainerAtIndexInExtruder(self, extruder_position: int, property_key: str, container_index: int,
  147. context: Optional["PropertyEvaluationContext"] = None) -> Any:
  148. machine_manager = self._application.getMachineManager()
  149. global_stack = machine_manager.activeMachine
  150. if extruder_position == -1:
  151. extruder_position = int(machine_manager.defaultExtruderPosition)
  152. global_stack = machine_manager.activeMachine
  153. try:
  154. extruder_stack = global_stack.extruderList[int(extruder_position)]
  155. except IndexError:
  156. Logger.log("w", "Value for %s of extruder %s was requested, but that extruder is not available. " % (property_key, extruder_position))
  157. return None
  158. context = self.createContextForDefaultValueEvaluation(extruder_stack)
  159. context.context["evaluate_from_container_index"] = container_index
  160. return self.getValueInExtruder(extruder_position, property_key, context)
  161. # Creates a context for evaluating default values (skip the user_changes container).
  162. def createContextForDefaultValueEvaluation(self, source_stack: "CuraContainerStack") -> "PropertyEvaluationContext":
  163. context = PropertyEvaluationContext(source_stack)
  164. context.context["evaluate_from_container_index"] = 1 # skip the user settings container
  165. context.context["override_operators"] = {
  166. "extruderValue": self.getDefaultValueInExtruder,
  167. "extruderValues": self.getDefaultValuesInAllExtruders,
  168. "resolveOrValue": self.getDefaultResolveOrValue,
  169. }
  170. return context