ExtruderManager.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  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. import UM.Settings.SettingFunction
  8. ## Manages all existing extruder stacks.
  9. #
  10. # This keeps a list of extruder stacks for each machine.
  11. class ExtruderManager(QObject):
  12. ## Signal to notify other components when the list of extruders changes.
  13. extrudersChanged = pyqtSignal(QVariant)
  14. ## Notify when the user switches the currently active extruder.
  15. activeExtruderChanged = pyqtSignal()
  16. ## Registers listeners and such to listen to changes to the extruders.
  17. def __init__(self, parent = None):
  18. super().__init__(parent)
  19. self._extruder_trains = { } #Per machine, a dictionary of extruder container stack IDs.
  20. self._active_extruder_index = 0
  21. UM.Application.getInstance().globalContainerStackChanged.connect(self.__globalContainerStackChanged)
  22. self._addCurrentMachineExtruders()
  23. ## Gets the unique identifier of the currently active extruder stack.
  24. #
  25. # The currently active extruder stack is the stack that is currently being
  26. # edited.
  27. #
  28. # \return The unique ID of the currently active extruder stack.
  29. @pyqtProperty(str, notify = activeExtruderChanged)
  30. def activeExtruderStackId(self):
  31. if not UM.Application.getInstance().getGlobalContainerStack():
  32. return None # No active machine, so no active extruder.
  33. try:
  34. return self._extruder_trains[UM.Application.getInstance().getGlobalContainerStack().getId()][str(self._active_extruder_index)].getId()
  35. 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.
  36. return None
  37. @pyqtProperty(int, notify = extrudersChanged)
  38. def extruderCount(self):
  39. if not UM.Application.getInstance().getGlobalContainerStack():
  40. return 0 # No active machine, so no extruders.
  41. return len(self._extruder_trains[UM.Application.getInstance().getGlobalContainerStack().getId()])
  42. @pyqtProperty("QVariantMap", notify=extrudersChanged)
  43. def extruderIds(self):
  44. map = {}
  45. for position in self._extruder_trains[UM.Application.getInstance().getGlobalContainerStack().getId()]:
  46. map[position] = self._extruder_trains[UM.Application.getInstance().getGlobalContainerStack().getId()][position].getId()
  47. return map
  48. @pyqtSlot(str, result = str)
  49. def getQualityChangesIdByExtruderStackId(self, id):
  50. for position in self._extruder_trains[UM.Application.getInstance().getGlobalContainerStack().getId()]:
  51. extruder = self._extruder_trains[UM.Application.getInstance().getGlobalContainerStack().getId()][position]
  52. if extruder.getId() == id:
  53. return extruder.findContainer(type = "quality_changes").getId()
  54. ## The instance of the singleton pattern.
  55. #
  56. # It's None if the extruder manager hasn't been created yet.
  57. __instance = None
  58. ## Gets an instance of the extruder manager, or creates one if no instance
  59. # exists yet.
  60. #
  61. # This is an implementation of singleton. If an extruder manager already
  62. # exists, it is re-used.
  63. #
  64. # \return The extruder manager.
  65. @classmethod
  66. def getInstance(cls):
  67. if not cls.__instance:
  68. cls.__instance = ExtruderManager()
  69. return cls.__instance
  70. ## Changes the active extruder by index.
  71. #
  72. # \param index The index of the new active extruder.
  73. @pyqtSlot(int)
  74. def setActiveExtruderIndex(self, index):
  75. self._active_extruder_index = index
  76. self.activeExtruderChanged.emit()
  77. @pyqtProperty(int, notify = activeExtruderChanged)
  78. def activeExtruderIndex(self):
  79. return self._active_extruder_index
  80. def getActiveExtruderStack(self):
  81. global_container_stack = UM.Application.getInstance().getGlobalContainerStack()
  82. if global_container_stack:
  83. if global_container_stack.getId() in self._extruder_trains:
  84. if str(self._active_extruder_index) in self._extruder_trains[global_container_stack.getId()]:
  85. return self._extruder_trains[global_container_stack.getId()][str(self._active_extruder_index)]
  86. return None
  87. ## Get an extruder stack by index
  88. def getExtruderStack(self, index):
  89. global_container_stack = UM.Application.getInstance().getGlobalContainerStack()
  90. if global_container_stack:
  91. if global_container_stack.getId() in self._extruder_trains:
  92. if str(index) in self._extruder_trains[global_container_stack.getId()]:
  93. return self._extruder_trains[global_container_stack.getId()][str(index)]
  94. return None
  95. ## Adds all extruders of a specific machine definition to the extruder
  96. # manager.
  97. #
  98. # \param machine_definition The machine definition to add the extruders for.
  99. # \param machine_id The machine_id to add the extruders for.
  100. def addMachineExtruders(self, machine_definition, machine_id):
  101. changed = False
  102. machine_definition_id = machine_definition.getId()
  103. if machine_id not in self._extruder_trains:
  104. self._extruder_trains[machine_id] = { }
  105. changed = True
  106. container_registry = UM.Settings.ContainerRegistry.getInstance()
  107. if container_registry:
  108. # Add the extruder trains that don't exist yet.
  109. for extruder_definition in container_registry.findDefinitionContainers(machine = machine_definition_id):
  110. position = extruder_definition.getMetaDataEntry("position", None)
  111. if not position:
  112. UM.Logger.log("w", "Extruder definition %s specifies no position metadata entry.", extruder_definition.getId())
  113. if not container_registry.findContainerStacks(machine = machine_id, position = position): # Doesn't exist yet.
  114. self.createExtruderTrain(extruder_definition, machine_definition, position, machine_id)
  115. changed = True
  116. # Gets the extruder trains that we just created as well as any that still existed.
  117. extruder_trains = container_registry.findContainerStacks(type = "extruder_train", machine = machine_id)
  118. for extruder_train in extruder_trains:
  119. self._extruder_trains[machine_id][extruder_train.getMetaDataEntry("position")] = extruder_train
  120. # Make sure the next stack is a stack that contains only the machine definition
  121. if not extruder_train.getNextStack():
  122. shallow_stack = UM.Settings.ContainerStack(machine_id + "_shallow")
  123. shallow_stack.addContainer(machine_definition)
  124. extruder_train.setNextStack(shallow_stack)
  125. changed = True
  126. if changed:
  127. self.extrudersChanged.emit(machine_id)
  128. ## Creates a container stack for an extruder train.
  129. #
  130. # The container stack has an extruder definition at the bottom, which is
  131. # linked to a machine definition. Then it has a variant profile, a material
  132. # profile, a quality profile and a user profile, in that order.
  133. #
  134. # The resulting container stack is added to the registry.
  135. #
  136. # \param extruder_definition The extruder to create the extruder train for.
  137. # \param machine_definition The machine that the extruder train belongs to.
  138. # \param position The position of this extruder train in the extruder slots of the machine.
  139. # \param machine_id The id of the "global" stack this extruder is linked to.
  140. def createExtruderTrain(self, extruder_definition, machine_definition, position, machine_id):
  141. # Cache some things.
  142. container_registry = UM.Settings.ContainerRegistry.getInstance()
  143. machine_definition_id = machine_definition.getId()
  144. # Create a container stack for this extruder.
  145. extruder_stack_id = container_registry.uniqueName(extruder_definition.getId())
  146. container_stack = UM.Settings.ContainerStack(extruder_stack_id)
  147. container_stack.setName(extruder_definition.getName()) # Take over the display name to display the stack with.
  148. container_stack.addMetaDataEntry("type", "extruder_train")
  149. container_stack.addMetaDataEntry("machine", machine_id)
  150. container_stack.addMetaDataEntry("position", position)
  151. container_stack.addContainer(extruder_definition)
  152. # Find the variant to use for this extruder.
  153. variant = container_registry.findInstanceContainers(id = "empty_variant")[0]
  154. if machine_definition.getMetaDataEntry("has_variants"):
  155. # First add any variant. Later, overwrite with preference if the preference is valid.
  156. variants = container_registry.findInstanceContainers(definition = machine_definition_id, type = "variant")
  157. if len(variants) >= 1:
  158. variant = variants[0]
  159. preferred_variant_id = machine_definition.getMetaDataEntry("preferred_variant")
  160. if preferred_variant_id:
  161. preferred_variants = container_registry.findInstanceContainers(id = preferred_variant_id, type = "variant")
  162. if len(preferred_variants) >= 1:
  163. variant = preferred_variants[0]
  164. else:
  165. 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)
  166. # And leave it at the default variant.
  167. container_stack.addContainer(variant)
  168. # Find a material to use for this variant.
  169. material = container_registry.findInstanceContainers(id = "empty_material")[0]
  170. if machine_definition.getMetaDataEntry("has_materials"):
  171. # First add any material. Later, overwrite with preference if the preference is valid.
  172. if machine_definition.getMetaDataEntry("has_variant_materials", default = "False") == "True":
  173. materials = container_registry.findInstanceContainers(type = "material", definition = machine_definition_id, variant = variant.getId())
  174. else:
  175. materials = container_registry.findInstanceContainers(type = "material", definition = machine_definition_id)
  176. if len(materials) >= 1:
  177. material = materials[0]
  178. preferred_material_id = machine_definition.getMetaDataEntry("preferred_material")
  179. if preferred_material_id:
  180. search_criteria = { "type": "material", "id": preferred_material_id}
  181. if machine_definition.getMetaDataEntry("has_machine_materials"):
  182. search_criteria["definition"] = machine_definition.id
  183. if machine_definition.getMetaDataEntry("has_variants") and variant:
  184. search_criteria["variant"] = variant.id
  185. else:
  186. search_criteria["definition"] = "fdmprinter"
  187. preferred_materials = container_registry.findInstanceContainers(**search_criteria)
  188. if len(preferred_materials) >= 1:
  189. material = preferred_materials[0]
  190. else:
  191. 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)
  192. # And leave it at the default material.
  193. container_stack.addContainer(material)
  194. # Find a quality to use for this extruder.
  195. quality = container_registry.getEmptyInstanceContainer()
  196. search_criteria = { "type": "quality" }
  197. if machine_definition.getMetaDataEntry("has_machine_quality"):
  198. search_criteria["definition"] = machine_definition.id
  199. if machine_definition.getMetaDataEntry("has_materials") and material:
  200. search_criteria["material"] = material.id
  201. else:
  202. search_criteria["definition"] = "fdmprinter"
  203. preferred_quality = machine_definition.getMetaDataEntry("preferred_quality")
  204. if preferred_quality:
  205. search_criteria["id"] = preferred_quality
  206. containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
  207. if not containers and preferred_quality:
  208. UM.Logger.log("w", "The preferred quality \"%s\" of machine %s doesn't exist or is not a quality profile.", preferred_quality, machine_id)
  209. search_criteria.pop("id", None)
  210. containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
  211. if containers:
  212. quality = containers[0]
  213. container_stack.addContainer(quality)
  214. empty_quality_changes = container_registry.findInstanceContainers(id = "empty_quality_changes")[0]
  215. container_stack.addContainer(empty_quality_changes)
  216. user_profile = container_registry.findInstanceContainers(type = "user", extruder = extruder_stack_id)
  217. if user_profile: # There was already a user profile, loaded from settings.
  218. user_profile = user_profile[0]
  219. else:
  220. user_profile = UM.Settings.InstanceContainer(extruder_stack_id + "_current_settings") # Add an empty user profile.
  221. user_profile.addMetaDataEntry("type", "user")
  222. user_profile.addMetaDataEntry("extruder", extruder_stack_id)
  223. user_profile.setDefinition(machine_definition)
  224. container_registry.addContainer(user_profile)
  225. container_stack.addContainer(user_profile)
  226. # Make sure the next stack is a stack that contains only the machine definition
  227. if not container_stack.getNextStack():
  228. shallow_stack = UM.Settings.ContainerStack(machine_id + "_shallow")
  229. shallow_stack.addContainer(machine_definition)
  230. container_stack.setNextStack(shallow_stack)
  231. container_registry.addContainer(container_stack)
  232. ## Removes the container stack and user profile for the extruders for a specific machine.
  233. #
  234. # \param machine_id The machine to remove the extruders for.
  235. def removeMachineExtruders(self, machine_id):
  236. for extruder in self.getMachineExtruders(machine_id):
  237. containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "user", extruder = extruder.getId())
  238. for container in containers:
  239. UM.Settings.ContainerRegistry.getInstance().removeContainer(container.getId())
  240. UM.Settings.ContainerRegistry.getInstance().removeContainer(extruder.getId())
  241. ## Returns extruders for a specific machine.
  242. #
  243. # \param machine_id The machine to get the extruders of.
  244. def getMachineExtruders(self, machine_id):
  245. if machine_id not in self._extruder_trains:
  246. UM.Logger.log("w", "Tried to get the extruder trains for machine %s, which doesn't exist.", machine_id)
  247. return
  248. for name in self._extruder_trains[machine_id]:
  249. yield self._extruder_trains[machine_id][name]
  250. ## Returns a generator that will iterate over the global stack and per-extruder stacks.
  251. #
  252. # The first generated element is the global container stack. After that any extruder stacks are generated.
  253. def getActiveGlobalAndExtruderStacks(self):
  254. global_stack = UM.Application.getInstance().getGlobalContainerStack()
  255. if not global_stack:
  256. return
  257. yield global_stack
  258. global_id = global_stack.getId()
  259. for name in self._extruder_trains[global_id]:
  260. yield self._extruder_trains[global_id][name]
  261. def __globalContainerStackChanged(self):
  262. self._addCurrentMachineExtruders()
  263. self.activeExtruderChanged.emit()
  264. ## Adds the extruders of the currently active machine.
  265. def _addCurrentMachineExtruders(self):
  266. global_stack = UM.Application.getInstance().getGlobalContainerStack()
  267. if global_stack and global_stack.getBottom():
  268. self.addMachineExtruders(global_stack.getBottom(), global_stack.getId())
  269. ## Get all extruder values for a certain setting.
  270. #
  271. # This is exposed to SettingFunction so it can be used in value functions.
  272. #
  273. # \param key The key of the setting to retieve values for.
  274. #
  275. # \return A list of values for all extruders. If an extruder does not have a value, it will not be in the list.
  276. # If no extruder has the value, the list will contain the global value.
  277. @staticmethod
  278. def getExtruderValues(key):
  279. global_stack = UM.Application.getInstance().getGlobalContainerStack()
  280. result = []
  281. for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()):
  282. value = extruder.getRawProperty(key, "value")
  283. if not value:
  284. continue
  285. if isinstance(value, UM.Settings.SettingFunction):
  286. value = value(extruder)
  287. result.append(value)
  288. if not result:
  289. result.append(global_stack.getProperty(key, "value"))
  290. return result
  291. ## Get all extruder values for a certain setting.
  292. #
  293. # This is exposed to qml for display purposes
  294. #
  295. # \param key The key of the setting to retieve values for.
  296. #
  297. # \return String representing the extruder values
  298. @pyqtSlot(str, result="QList<int>")
  299. def getInstanceExtruderValues(self, key):
  300. return ExtruderManager.getExtruderValues(key)
  301. ## Get the value for a setting from a specific extruder.
  302. #
  303. # This is exposed to SettingFunction to use in value functions.
  304. #
  305. # \param extruder_index The index of the extruder to get the value from.
  306. # \param key The key of the setting to get the value of.
  307. #
  308. # \return The value of the setting for the specified extruder or for the
  309. # global stack if not found.
  310. @staticmethod
  311. def getExtruderValue(extruder_index, key):
  312. extruder = ExtruderManager.getInstance().getExtruderStack(extruder_index)
  313. if extruder:
  314. value = extruder.getRawProperty(key, "value")
  315. if isinstance(value, UM.Settings.SettingFunction):
  316. value = value(extruder)
  317. else: #Just a value from global.
  318. value = UM.Application.getInstance().getGlobalContainerStack().getProperty(key, "value")
  319. return value