ExtruderManager.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. # Copyright (c) 2016 Ultimaker B.V.
  2. # Cura is released under the terms of the AGPLv3 or higher.
  3. from PyQt5.QtCore import pyqtSignal, pyqtProperty, pyqtSlot, QObject, QVariant #For communicating data and events to Qt.
  4. import UM.Application #To get the global container stack to find the current machine.
  5. import UM.Logger
  6. import UM.Settings.ContainerRegistry #Finding containers by ID.
  7. ## Manages all existing extruder stacks.
  8. #
  9. # This keeps a list of extruder stacks for each machine.
  10. class ExtruderManager(QObject):
  11. ## Signal to notify other components when the list of extruders changes.
  12. extrudersChanged = pyqtSignal(QVariant)
  13. ## Notify when the user switches the currently active extruder.
  14. activeExtruderChanged = pyqtSignal()
  15. ## Registers listeners and such to listen to changes to the extruders.
  16. def __init__(self, parent = None):
  17. super().__init__(parent)
  18. self._extruder_trains = { } #Per machine, a dictionary of extruder container stack IDs.
  19. self._active_extruder_index = -1
  20. UM.Application.getInstance().globalContainerStackChanged.connect(self.__globalContainerStackChanged)
  21. self._addCurrentMachineExtruders()
  22. ## Gets the unique identifier of the currently active extruder stack.
  23. #
  24. # The currently active extruder stack is the stack that is currently being
  25. # edited.
  26. #
  27. # \return The unique ID of the currently active extruder stack.
  28. @pyqtProperty(str, notify = activeExtruderChanged)
  29. def activeExtruderStackId(self):
  30. if not UM.Application.getInstance().getGlobalContainerStack():
  31. return None # No active machine, so no active extruder.
  32. try:
  33. return self._extruder_trains[UM.Application.getInstance().getGlobalContainerStack().getId()][str(self._active_extruder_index)].getId()
  34. except KeyError: # Extruder index could be -1 if the global tab is selected, or the entry doesn't exist if the machine definition is wrong.
  35. return None
  36. ## The instance of the singleton pattern.
  37. #
  38. # It's None if the extruder manager hasn't been created yet.
  39. __instance = None
  40. ## Gets an instance of the extruder manager, or creates one if no instance
  41. # exists yet.
  42. #
  43. # This is an implementation of singleton. If an extruder manager already
  44. # exists, it is re-used.
  45. #
  46. # \return The extruder manager.
  47. @classmethod
  48. def getInstance(cls):
  49. if not cls.__instance:
  50. cls.__instance = ExtruderManager()
  51. return cls.__instance
  52. ## Changes the active extruder by index.
  53. #
  54. # \param index The index of the new active extruder.
  55. @pyqtSlot(int)
  56. def setActiveExtruderIndex(self, index):
  57. self._active_extruder_index = index
  58. self.activeExtruderChanged.emit()
  59. @pyqtProperty(int, notify = activeExtruderChanged)
  60. def activeExtruderIndex(self):
  61. return self._active_extruder_index
  62. def getActiveExtruderStack(self):
  63. global_container_stack = UM.Application.getInstance().getGlobalContainerStack()
  64. if global_container_stack:
  65. if global_container_stack.getId() in self._extruder_trains:
  66. if str(self._active_extruder_index) in self._extruder_trains[global_container_stack.getId()]:
  67. return self._extruder_trains[global_container_stack.getId()][str(self._active_extruder_index)]
  68. return None
  69. ## Adds all extruders of a specific machine definition to the extruder
  70. # manager.
  71. #
  72. # \param machine_definition The machine definition to add the extruders for.
  73. # \param machine_id The machine_id to add the extruders for.
  74. def addMachineExtruders(self, machine_definition, machine_id):
  75. changed = False
  76. machine_definition_id = machine_definition.getId()
  77. if machine_id not in self._extruder_trains:
  78. self._extruder_trains[machine_id] = { }
  79. changed = True
  80. container_registry = UM.Settings.ContainerRegistry.getInstance()
  81. if container_registry:
  82. # Add the extruder trains that don't exist yet.
  83. for extruder_definition in container_registry.findDefinitionContainers(machine = machine_definition_id):
  84. position = extruder_definition.getMetaDataEntry("position", None)
  85. if not position:
  86. UM.Logger.log("w", "Extruder definition %s specifies no position metadata entry.", extruder_definition.getId())
  87. if not container_registry.findContainerStacks(machine = machine_id, position = position): # Doesn't exist yet.
  88. self.createExtruderTrain(extruder_definition, machine_definition, position, machine_id)
  89. changed = True
  90. # Gets the extruder trains that we just created as well as any that still existed.
  91. extruder_trains = container_registry.findContainerStacks(type = "extruder_train", machine = machine_id)
  92. for extruder_train in extruder_trains:
  93. self._extruder_trains[machine_id][extruder_train.getMetaDataEntry("position")] = extruder_train
  94. # Ensure that the extruder train stacks are linked to global stack.
  95. extruder_train.setNextStack(UM.Application.getInstance().getGlobalContainerStack())
  96. changed = True
  97. if changed:
  98. self.extrudersChanged.emit(machine_id)
  99. ## Creates a container stack for an extruder train.
  100. #
  101. # The container stack has an extruder definition at the bottom, which is
  102. # linked to a machine definition. Then it has a variant profile, a material
  103. # profile, a quality profile and a user profile, in that order.
  104. #
  105. # The resulting container stack is added to the registry.
  106. #
  107. # \param extruder_definition The extruder to create the extruder train for.
  108. # \param machine_definition The machine that the extruder train belongs to.
  109. # \param position The position of this extruder train in the extruder slots of the machine.
  110. # \param machine_id The id of the "global" stack this extruder is linked to.
  111. def createExtruderTrain(self, extruder_definition, machine_definition, position, machine_id):
  112. # Cache some things.
  113. container_registry = UM.Settings.ContainerRegistry.getInstance()
  114. machine_definition_id = machine_definition.getId()
  115. # Create a container stack for this extruder.
  116. extruder_stack_id = container_registry.uniqueName(extruder_definition.getId())
  117. container_stack = UM.Settings.ContainerStack(extruder_stack_id)
  118. container_stack.setName(extruder_definition.getName()) # Take over the display name to display the stack with.
  119. container_stack.addMetaDataEntry("type", "extruder_train")
  120. container_stack.addMetaDataEntry("machine", machine_id)
  121. container_stack.addMetaDataEntry("position", position)
  122. container_stack.addContainer(extruder_definition)
  123. # Find the variant to use for this extruder.
  124. variant = container_registry.findInstanceContainers(id = "empty_variant")[0]
  125. if machine_definition.getMetaDataEntry("has_variants"):
  126. # First add any variant. Later, overwrite with preference if the preference is valid.
  127. variants = container_registry.findInstanceContainers(definition = machine_definition_id, type = "variant")
  128. if len(variants) >= 1:
  129. variant = variants[0]
  130. preferred_variant_id = machine_definition.getMetaDataEntry("preferred_variant")
  131. if preferred_variant_id:
  132. preferred_variants = container_registry.findInstanceContainers(id = preferred_variant_id, type = "variant")
  133. if len(preferred_variants) >= 1:
  134. variant = preferred_variants[0]
  135. else:
  136. UM.Logger.log("w", "The preferred variant \"%s\" of machine %s doesn't exist or is not a variant profile.", preferred_variant_id, machine_id)
  137. # And leave it at the default variant.
  138. container_stack.addContainer(variant)
  139. # Find a material to use for this variant.
  140. material = container_registry.findInstanceContainers(id = "empty_material")[0]
  141. if machine_definition.getMetaDataEntry("has_materials"):
  142. # First add any material. Later, overwrite with preference if the preference is valid.
  143. if machine_definition.getMetaDataEntry("has_variant_materials", default = "False") == "True":
  144. materials = container_registry.findInstanceContainers(type = "material", definition = machine_definition_id, variant = variant.getId())
  145. else:
  146. materials = container_registry.findInstanceContainers(type = "material", definition = machine_definition_id)
  147. if len(materials) >= 1:
  148. material = materials[0]
  149. preferred_material_id = machine_definition.getMetaDataEntry("preferred_material")
  150. if preferred_material_id:
  151. search_criteria = { "type": "material", "id": preferred_material_id}
  152. if machine_definition.getMetaDataEntry("has_machine_materials"):
  153. search_criteria["definition"] = machine_definition.id
  154. if machine_definition.getMetaDataEntry("has_variants") and variant:
  155. search_criteria["variant"] = variant.id
  156. else:
  157. search_criteria["definition"] = "fdmprinter"
  158. preferred_materials = container_registry.findInstanceContainers(**search_criteria)
  159. if len(preferred_materials) >= 1:
  160. material = preferred_materials[0]
  161. else:
  162. UM.Logger.log("w", "The preferred material \"%s\" of machine %s doesn't exist or is not a material profile.", preferred_material_id, machine_id)
  163. # And leave it at the default material.
  164. container_stack.addContainer(material)
  165. # Find a quality to use for this extruder.
  166. quality = container_registry.getEmptyInstanceContainer()
  167. search_criteria = { "type": "quality" }
  168. if machine_definition.getMetaDataEntry("has_machine_quality"):
  169. search_criteria["definition"] = machine_definition.id
  170. if machine_definition.getMetaDataEntry("has_materials") and material:
  171. search_criteria["material"] = material.id
  172. else:
  173. search_criteria["definition"] = "fdmprinter"
  174. preferred_quality = machine_definition.getMetaDataEntry("preferred_quality")
  175. if preferred_quality:
  176. search_criteria["id"] = preferred_quality
  177. containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
  178. if not containers and preferred_quality:
  179. UM.Logger.log("w", "The preferred quality \"%s\" of machine %s doesn't exist or is not a quality profile.", preferred_quality, machine_id)
  180. search_criteria.pop("id", None)
  181. containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
  182. if containers:
  183. quality = containers[0]
  184. container_stack.addContainer(quality)
  185. user_profile = container_registry.findInstanceContainers(type = "user", extruder = extruder_stack_id)
  186. if user_profile: # There was already a user profile, loaded from settings.
  187. user_profile = user_profile[0]
  188. else:
  189. user_profile = UM.Settings.InstanceContainer(extruder_stack_id + "_current_settings") # Add an empty user profile.
  190. user_profile.addMetaDataEntry("type", "user")
  191. user_profile.addMetaDataEntry("extruder", extruder_stack_id)
  192. user_profile.setDefinition(machine_definition)
  193. container_registry.addContainer(user_profile)
  194. container_stack.addContainer(user_profile)
  195. container_stack.setNextStack(UM.Application.getInstance().getGlobalContainerStack())
  196. container_registry.addContainer(container_stack)
  197. ## Removes the container stack and user profile for the extruders for a specific machine.
  198. #
  199. # \param machine_id The machine to remove the extruders for.
  200. def removeMachineExtruders(self, machine_id):
  201. for extruder in self.getMachineExtruders(machine_id):
  202. containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "user", extruder = extruder.getId())
  203. for container in containers:
  204. UM.Settings.ContainerRegistry.getInstance().removeContainer(container.getId())
  205. UM.Settings.ContainerRegistry.getInstance().removeContainer(extruder.getId())
  206. ## Returns extruders for a specific machine.
  207. #
  208. # \param machine_id The machine to get the extruders of.
  209. def getMachineExtruders(self, machine_id):
  210. if machine_id not in self._extruder_trains:
  211. UM.Logger.log("w", "Tried to get the extruder trains for machine %s, which doesn't exist.", machine_id)
  212. return
  213. for name in self._extruder_trains[machine_id]:
  214. yield self._extruder_trains[machine_id][name]
  215. def __globalContainerStackChanged(self):
  216. self._addCurrentMachineExtruders()
  217. self.activeExtruderChanged.emit()
  218. ## Adds the extruders of the currently active machine.
  219. def _addCurrentMachineExtruders(self):
  220. global_stack = UM.Application.getInstance().getGlobalContainerStack()
  221. if global_stack and global_stack.getBottom():
  222. self.addMachineExtruders(global_stack.getBottom(), global_stack.getId())