ExtruderManager.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  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 = 0
  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. @pyqtProperty(int, notify = extrudersChanged)
  37. def extruderCount(self):
  38. if not UM.Application.getInstance().getGlobalContainerStack():
  39. return 0 # No active machine, so no extruders.
  40. return len(self._extruder_trains[UM.Application.getInstance().getGlobalContainerStack().getId()])
  41. @pyqtProperty("QVariantMap", notify=extrudersChanged)
  42. def extruderIds(self):
  43. map = {}
  44. for position in self._extruder_trains[UM.Application.getInstance().getGlobalContainerStack().getId()]:
  45. map[position] = self._extruder_trains[UM.Application.getInstance().getGlobalContainerStack().getId()][position].getId()
  46. return map
  47. ## The instance of the singleton pattern.
  48. #
  49. # It's None if the extruder manager hasn't been created yet.
  50. __instance = None
  51. ## Gets an instance of the extruder manager, or creates one if no instance
  52. # exists yet.
  53. #
  54. # This is an implementation of singleton. If an extruder manager already
  55. # exists, it is re-used.
  56. #
  57. # \return The extruder manager.
  58. @classmethod
  59. def getInstance(cls):
  60. if not cls.__instance:
  61. cls.__instance = ExtruderManager()
  62. return cls.__instance
  63. ## Changes the active extruder by index.
  64. #
  65. # \param index The index of the new active extruder.
  66. @pyqtSlot(int)
  67. def setActiveExtruderIndex(self, index):
  68. self._active_extruder_index = index
  69. self.activeExtruderChanged.emit()
  70. @pyqtProperty(int, notify = activeExtruderChanged)
  71. def activeExtruderIndex(self):
  72. return self._active_extruder_index
  73. def getActiveExtruderStack(self):
  74. global_container_stack = UM.Application.getInstance().getGlobalContainerStack()
  75. if global_container_stack:
  76. if global_container_stack.getId() in self._extruder_trains:
  77. if str(self._active_extruder_index) in self._extruder_trains[global_container_stack.getId()]:
  78. return self._extruder_trains[global_container_stack.getId()][str(self._active_extruder_index)]
  79. return None
  80. ## Adds all extruders of a specific machine definition to the extruder
  81. # manager.
  82. #
  83. # \param machine_definition The machine definition to add the extruders for.
  84. # \param machine_id The machine_id to add the extruders for.
  85. def addMachineExtruders(self, machine_definition, machine_id):
  86. changed = False
  87. machine_definition_id = machine_definition.getId()
  88. if machine_id not in self._extruder_trains:
  89. self._extruder_trains[machine_id] = { }
  90. changed = True
  91. container_registry = UM.Settings.ContainerRegistry.getInstance()
  92. if container_registry:
  93. # Add the extruder trains that don't exist yet.
  94. for extruder_definition in container_registry.findDefinitionContainers(machine = machine_definition_id):
  95. position = extruder_definition.getMetaDataEntry("position", None)
  96. if not position:
  97. UM.Logger.log("w", "Extruder definition %s specifies no position metadata entry.", extruder_definition.getId())
  98. if not container_registry.findContainerStacks(machine = machine_id, position = position): # Doesn't exist yet.
  99. self.createExtruderTrain(extruder_definition, machine_definition, position, machine_id)
  100. changed = True
  101. # Gets the extruder trains that we just created as well as any that still existed.
  102. extruder_trains = container_registry.findContainerStacks(type = "extruder_train", machine = machine_id)
  103. for extruder_train in extruder_trains:
  104. self._extruder_trains[machine_id][extruder_train.getMetaDataEntry("position")] = extruder_train
  105. # Make sure the next stack is a stack that contains only the machine definition
  106. if not extruder_train.getNextStack():
  107. shallowStack = UM.Settings.ContainerStack(machine_id + "_shallow")
  108. shallowStack.addContainer(machine_definition)
  109. extruder_train.setNextStack(shallowStack)
  110. changed = True
  111. if changed:
  112. self.extrudersChanged.emit(machine_id)
  113. ## Creates a container stack for an extruder train.
  114. #
  115. # The container stack has an extruder definition at the bottom, which is
  116. # linked to a machine definition. Then it has a variant profile, a material
  117. # profile, a quality profile and a user profile, in that order.
  118. #
  119. # The resulting container stack is added to the registry.
  120. #
  121. # \param extruder_definition The extruder to create the extruder train for.
  122. # \param machine_definition The machine that the extruder train belongs to.
  123. # \param position The position of this extruder train in the extruder slots of the machine.
  124. # \param machine_id The id of the "global" stack this extruder is linked to.
  125. def createExtruderTrain(self, extruder_definition, machine_definition, position, machine_id):
  126. # Cache some things.
  127. container_registry = UM.Settings.ContainerRegistry.getInstance()
  128. machine_definition_id = machine_definition.getId()
  129. # Create a container stack for this extruder.
  130. extruder_stack_id = container_registry.uniqueName(extruder_definition.getId())
  131. container_stack = UM.Settings.ContainerStack(extruder_stack_id)
  132. container_stack.setName(extruder_definition.getName()) # Take over the display name to display the stack with.
  133. container_stack.addMetaDataEntry("type", "extruder_train")
  134. container_stack.addMetaDataEntry("machine", machine_id)
  135. container_stack.addMetaDataEntry("position", position)
  136. container_stack.addContainer(extruder_definition)
  137. # Find the variant to use for this extruder.
  138. variant = container_registry.findInstanceContainers(id = "empty_variant")[0]
  139. if machine_definition.getMetaDataEntry("has_variants"):
  140. # First add any variant. Later, overwrite with preference if the preference is valid.
  141. variants = container_registry.findInstanceContainers(definition = machine_definition_id, type = "variant")
  142. if len(variants) >= 1:
  143. variant = variants[0]
  144. preferred_variant_id = machine_definition.getMetaDataEntry("preferred_variant")
  145. if preferred_variant_id:
  146. preferred_variants = container_registry.findInstanceContainers(id = preferred_variant_id, type = "variant")
  147. if len(preferred_variants) >= 1:
  148. variant = preferred_variants[0]
  149. else:
  150. 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)
  151. # And leave it at the default variant.
  152. container_stack.addContainer(variant)
  153. # Find a material to use for this variant.
  154. material = container_registry.findInstanceContainers(id = "empty_material")[0]
  155. if machine_definition.getMetaDataEntry("has_materials"):
  156. # First add any material. Later, overwrite with preference if the preference is valid.
  157. if machine_definition.getMetaDataEntry("has_variant_materials", default = "False") == "True":
  158. materials = container_registry.findInstanceContainers(type = "material", definition = machine_definition_id, variant = variant.getId())
  159. else:
  160. materials = container_registry.findInstanceContainers(type = "material", definition = machine_definition_id)
  161. if len(materials) >= 1:
  162. material = materials[0]
  163. preferred_material_id = machine_definition.getMetaDataEntry("preferred_material")
  164. if preferred_material_id:
  165. search_criteria = { "type": "material", "id": preferred_material_id}
  166. if machine_definition.getMetaDataEntry("has_machine_materials"):
  167. search_criteria["definition"] = machine_definition.id
  168. if machine_definition.getMetaDataEntry("has_variants") and variant:
  169. search_criteria["variant"] = variant.id
  170. else:
  171. search_criteria["definition"] = "fdmprinter"
  172. preferred_materials = container_registry.findInstanceContainers(**search_criteria)
  173. if len(preferred_materials) >= 1:
  174. material = preferred_materials[0]
  175. else:
  176. 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)
  177. # And leave it at the default material.
  178. container_stack.addContainer(material)
  179. # Find a quality to use for this extruder.
  180. quality = container_registry.getEmptyInstanceContainer()
  181. search_criteria = { "type": "quality" }
  182. if machine_definition.getMetaDataEntry("has_machine_quality"):
  183. search_criteria["definition"] = machine_definition.id
  184. if machine_definition.getMetaDataEntry("has_materials") and material:
  185. search_criteria["material"] = material.id
  186. else:
  187. search_criteria["definition"] = "fdmprinter"
  188. preferred_quality = machine_definition.getMetaDataEntry("preferred_quality")
  189. if preferred_quality:
  190. search_criteria["id"] = preferred_quality
  191. containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
  192. if not containers and preferred_quality:
  193. UM.Logger.log("w", "The preferred quality \"%s\" of machine %s doesn't exist or is not a quality profile.", preferred_quality, machine_id)
  194. search_criteria.pop("id", None)
  195. containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
  196. if containers:
  197. quality = containers[0]
  198. container_stack.addContainer(quality)
  199. user_profile = container_registry.findInstanceContainers(type = "user", extruder = extruder_stack_id)
  200. if user_profile: # There was already a user profile, loaded from settings.
  201. user_profile = user_profile[0]
  202. else:
  203. user_profile = UM.Settings.InstanceContainer(extruder_stack_id + "_current_settings") # Add an empty user profile.
  204. user_profile.addMetaDataEntry("type", "user")
  205. user_profile.addMetaDataEntry("extruder", extruder_stack_id)
  206. user_profile.setDefinition(machine_definition)
  207. container_registry.addContainer(user_profile)
  208. container_stack.addContainer(user_profile)
  209. # Make sure the next stack is a stack that contains only the machine definition
  210. if not container_stack.getNextStack():
  211. shallowStack = UM.Settings.ContainerStack(machine_id + "_shallow")
  212. shallowStack.addContainer(machine_definition)
  213. container_stack.setNextStack(shallowStack)
  214. container_registry.addContainer(container_stack)
  215. ## Removes the container stack and user profile for the extruders for a specific machine.
  216. #
  217. # \param machine_id The machine to remove the extruders for.
  218. def removeMachineExtruders(self, machine_id):
  219. for extruder in self.getMachineExtruders(machine_id):
  220. containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "user", extruder = extruder.getId())
  221. for container in containers:
  222. UM.Settings.ContainerRegistry.getInstance().removeContainer(container.getId())
  223. UM.Settings.ContainerRegistry.getInstance().removeContainer(extruder.getId())
  224. ## Returns extruders for a specific machine.
  225. #
  226. # \param machine_id The machine to get the extruders of.
  227. def getMachineExtruders(self, machine_id):
  228. if machine_id not in self._extruder_trains:
  229. UM.Logger.log("w", "Tried to get the extruder trains for machine %s, which doesn't exist.", machine_id)
  230. return
  231. for name in self._extruder_trains[machine_id]:
  232. yield self._extruder_trains[machine_id][name]
  233. def __globalContainerStackChanged(self):
  234. self._addCurrentMachineExtruders()
  235. self.activeExtruderChanged.emit()
  236. ## Adds the extruders of the currently active machine.
  237. def _addCurrentMachineExtruders(self):
  238. global_stack = UM.Application.getInstance().getGlobalContainerStack()
  239. if global_stack and global_stack.getBottom():
  240. self.addMachineExtruders(global_stack.getBottom(), global_stack.getId())