ExtruderStack.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. # Copyright (c) 2017 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. from typing import Any, TYPE_CHECKING, Optional
  4. from UM.Application import Application
  5. from UM.Decorators import override
  6. from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
  7. from UM.Settings.ContainerStack import ContainerStack
  8. from UM.Settings.ContainerRegistry import ContainerRegistry
  9. from UM.Settings.Interfaces import ContainerInterface, PropertyEvaluationContext
  10. from UM.Settings.SettingInstance import SettingInstance
  11. from . import Exceptions
  12. from .CuraContainerStack import CuraContainerStack
  13. from .ExtruderManager import ExtruderManager
  14. if TYPE_CHECKING:
  15. from cura.Settings.GlobalStack import GlobalStack
  16. ## Represents an Extruder and its related containers.
  17. #
  18. #
  19. class ExtruderStack(CuraContainerStack):
  20. def __init__(self, container_id: str, *args, **kwargs):
  21. super().__init__(container_id, *args, **kwargs)
  22. self.addMetaDataEntry("type", "extruder_train") # For backward compatibility
  23. self.propertiesChanged.connect(self._onPropertiesChanged)
  24. ## Overridden from ContainerStack
  25. #
  26. # This will set the next stack and ensure that we register this stack as an extruder.
  27. @override(ContainerStack)
  28. def setNextStack(self, stack: ContainerStack) -> None:
  29. super().setNextStack(stack)
  30. stack.addExtruder(self)
  31. self.addMetaDataEntry("machine", stack.id)
  32. # For backward compatibility: Register the extruder with the Extruder Manager
  33. ExtruderManager.getInstance().registerExtruder(self, stack.id)
  34. # Now each machine will have at least one extruder stack. If this is the first extruder, the extruder-specific
  35. # settings such as nozzle size and material diameter should be moved from the machine's definition_changes to
  36. # the this extruder's definition_changes.
  37. #
  38. # We do this here because it is tooooo expansive to do it in the version upgrade: During the version upgrade,
  39. # when we are upgrading a definition_changes container file, there is NO guarantee that other files such as
  40. # machine an extruder stack files are upgraded before this, so we cannot read those files assuming they are in
  41. # the latest format.
  42. #
  43. # MORE:
  44. # For single-extrusion machines, nozzle size is saved in the global stack, so the nozzle size value should be
  45. # carried to the first extruder.
  46. # For material diameter, it was supposed to be applied to all extruders, so its value should be copied to all
  47. # extruders.
  48. keys_to_copy = ["material_diameter", "machine_nozzle_size"] # these will be copied over to all extruders
  49. for key in keys_to_copy:
  50. # Only copy the value when this extruder doesn't have the value.
  51. if self.definitionChanges.hasProperty(key, "value"):
  52. continue
  53. # WARNING: this might be very dangerous and should be refactored ASAP!
  54. #
  55. # We cannot add a setting definition of "material_diameter" into the extruder's definition at runtime
  56. # because all other machines which uses "fdmextruder" as the extruder definition will be affected.
  57. #
  58. # The problem is that single extrusion machines have their default material diameter defined in the global
  59. # definitions. Now we automatically create an extruder stack for those machines using "fdmextruder"
  60. # definition, which doesn't have the specific "material_diameter" and "machine_nozzle_size" defined for
  61. # each machine. This results in wrong values which can be found in the MachineSettings dialog.
  62. #
  63. # To solve this, we put "material_diameter" back into the "fdmextruder" definition because modifying it in
  64. # the extruder definition will affect all machines which uses the "fdmextruder" definition. Moreover, now
  65. # we also check the value defined in the machine definition. If present, the value defined in the global
  66. # stack's definition changes container will be copied. Otherwise, we will check if the default values in the
  67. # machine definition and the extruder definition are the same, and if not, the default value in the machine
  68. # definition will be copied to the extruder stack's definition changes.
  69. #
  70. setting_value_in_global_def_changes = stack.definitionChanges.getProperty(key, "value")
  71. setting_value_in_global_def = stack.definition.getProperty(key, "value")
  72. setting_value = setting_value_in_global_def
  73. if setting_value_in_global_def_changes is not None:
  74. setting_value = setting_value_in_global_def_changes
  75. if setting_value == self.definition.getProperty(key, "value"):
  76. continue
  77. setting_definition = stack.getSettingDefinition(key)
  78. new_instance = SettingInstance(setting_definition, self.definitionChanges)
  79. new_instance.setProperty("value", setting_value)
  80. new_instance.resetState() # Ensure that the state is not seen as a user state.
  81. self.definitionChanges.addInstance(new_instance)
  82. self.definitionChanges.setDirty(True)
  83. # Make sure the material diameter is up to date for the extruder stack.
  84. if key == "material_diameter":
  85. from cura.CuraApplication import CuraApplication
  86. machine_manager = CuraApplication.getInstance().getMachineManager()
  87. position = self.getMetaDataEntry("position", "0")
  88. func = lambda p = position: CuraApplication.getInstance().getExtruderManager().updateMaterialForDiameter(p)
  89. machine_manager.machine_extruder_material_update_dict[stack.getId()].append(func)
  90. # NOTE: We cannot remove the setting from the global stack's definition changes container because for
  91. # material diameter, it needs to be applied to all extruders, but here we don't know how many extruders
  92. # a machine actually has and how many extruders has already been loaded for that machine, so we have to
  93. # keep this setting for any remaining extruders that haven't been loaded yet.
  94. #
  95. # Those settings will be removed in ExtruderManager which knows all those info.
  96. @override(ContainerStack)
  97. def getNextStack(self) -> Optional["GlobalStack"]:
  98. return super().getNextStack()
  99. @classmethod
  100. def getLoadingPriority(cls) -> int:
  101. return 3
  102. ## Overridden from ContainerStack
  103. #
  104. # It will perform a few extra checks when trying to get properties.
  105. #
  106. # The two extra checks it currently does is to ensure a next stack is set and to bypass
  107. # the extruder when the property is not settable per extruder.
  108. #
  109. # \throws Exceptions.NoGlobalStackError Raised when trying to get a property from an extruder without
  110. # having a next stack set.
  111. @override(ContainerStack)
  112. def getProperty(self, key: str, property_name: str, context: Optional[PropertyEvaluationContext] = None) -> Any:
  113. if not self._next_stack:
  114. raise Exceptions.NoGlobalStackError("Extruder {id} is missing the next stack!".format(id = self.id))
  115. if context is None:
  116. context = PropertyEvaluationContext()
  117. context.pushContainer(self)
  118. if not super().getProperty(key, "settable_per_extruder", context):
  119. result = self.getNextStack().getProperty(key, property_name, context)
  120. context.popContainer()
  121. return result
  122. limit_to_extruder = super().getProperty(key, "limit_to_extruder", context)
  123. if limit_to_extruder is not None:
  124. limit_to_extruder = str(limit_to_extruder)
  125. if (limit_to_extruder is not None and limit_to_extruder != "-1") and self.getMetaDataEntry("position") != str(limit_to_extruder):
  126. if str(limit_to_extruder) in self.getNextStack().extruders:
  127. result = self.getNextStack().extruders[str(limit_to_extruder)].getProperty(key, property_name, context)
  128. if result is not None:
  129. context.popContainer()
  130. return result
  131. result = super().getProperty(key, property_name, context)
  132. context.popContainer()
  133. return result
  134. @override(CuraContainerStack)
  135. def _getMachineDefinition(self) -> ContainerInterface:
  136. if not self.getNextStack():
  137. raise Exceptions.NoGlobalStackError("Extruder {id} is missing the next stack!".format(id = self.id))
  138. return self.getNextStack()._getMachineDefinition()
  139. @override(CuraContainerStack)
  140. def deserialize(self, contents: str, file_name: Optional[str] = None) -> None:
  141. super().deserialize(contents, file_name)
  142. stacks = ContainerRegistry.getInstance().findContainerStacks(id=self.getMetaDataEntry("machine", ""))
  143. if stacks:
  144. self.setNextStack(stacks[0])
  145. def _onPropertiesChanged(self, key, properties):
  146. # When there is a setting that is not settable per extruder that depends on a value from a setting that is,
  147. # we do not always get properly informed that we should re-evaluate the setting. So make sure to indicate
  148. # something changed for those settings.
  149. if not self.getNextStack():
  150. return #There are no global settings to depend on.
  151. definitions = self.getNextStack().definition.findDefinitions(key = key)
  152. if definitions:
  153. has_global_dependencies = False
  154. for relation in definitions[0].relations:
  155. if not getattr(relation.target, "settable_per_extruder", True):
  156. has_global_dependencies = True
  157. break
  158. if has_global_dependencies:
  159. self.getNextStack().propertiesChanged.emit(key, properties)
  160. def findDefaultVariant(self):
  161. # The default variant is defined in the machine stack and/or definition, so use the machine stack to find
  162. # the default variant.
  163. return self.getNextStack().findDefaultVariant()
  164. extruder_stack_mime = MimeType(
  165. name = "application/x-cura-extruderstack",
  166. comment = "Cura Extruder Stack",
  167. suffixes = ["extruder.cfg"]
  168. )
  169. MimeTypeDatabase.addMimeType(extruder_stack_mime)
  170. ContainerRegistry.addContainerTypeByName(ExtruderStack, "extruder_stack", extruder_stack_mime.name)