ExtruderManager.py 21 KB

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