MachineManager.py 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735
  1. # Copyright (c) 2016 Ultimaker B.V.
  2. # Cura is released under the terms of the AGPLv3 or higher.
  3. from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
  4. from PyQt5.QtWidgets import QMessageBox
  5. from UM.Application import Application
  6. from UM.Preferences import Preferences
  7. from UM.Logger import Logger
  8. import UM.Settings
  9. from cura.PrinterOutputDevice import PrinterOutputDevice
  10. from . import ExtruderManager
  11. from UM.i18n import i18nCatalog
  12. catalog = i18nCatalog("cura")
  13. import time
  14. class MachineManager(QObject):
  15. def __init__(self, parent = None):
  16. super().__init__(parent)
  17. self._active_container_stack = None
  18. self._global_container_stack = None
  19. Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)
  20. self._global_stack_valid = None
  21. self._onGlobalContainerChanged()
  22. ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged)
  23. self.globalContainerChanged.connect(self._onActiveExtruderStackChanged)
  24. self._onActiveExtruderStackChanged()
  25. ## When the global container is changed, active material probably needs to be updated.
  26. self.globalContainerChanged.connect(self.activeMaterialChanged)
  27. self.globalContainerChanged.connect(self.activeVariantChanged)
  28. self.globalContainerChanged.connect(self.activeQualityChanged)
  29. ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeMaterialChanged)
  30. ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeVariantChanged)
  31. ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeQualityChanged)
  32. self.globalContainerChanged.connect(self.activeStackChanged)
  33. self.globalValueChanged.connect(self.activeStackChanged)
  34. ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeStackChanged)
  35. self._empty_variant_container = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = "empty_variant")[0]
  36. self._empty_material_container = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = "empty_material")[0]
  37. self._empty_quality_container = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = "empty_quality")[0]
  38. Preferences.getInstance().addPreference("cura/active_machine", "")
  39. active_machine_id = Preferences.getInstance().getValue("cura/active_machine")
  40. self._printer_output_devices = []
  41. Application.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged)
  42. if active_machine_id != "":
  43. # An active machine was saved, so restore it.
  44. self.setActiveMachine(active_machine_id)
  45. pass
  46. self._auto_change_material_hotend_flood_window = 10
  47. self._auto_change_material_hotend_flood_time = 0
  48. self._auto_change_material_hotend_flood_last_choice = None
  49. globalContainerChanged = pyqtSignal()
  50. activeMaterialChanged = pyqtSignal()
  51. activeVariantChanged = pyqtSignal()
  52. activeQualityChanged = pyqtSignal()
  53. activeStackChanged = pyqtSignal()
  54. globalValueChanged = pyqtSignal() # Emitted whenever a value inside global container is changed.
  55. globalValidationChanged = pyqtSignal() # Emitted whenever a validation inside global container is changed
  56. blurSettings = pyqtSignal() # Emitted to force fields in the advanced sidebar to un-focus, so they update properly
  57. outputDevicesChanged = pyqtSignal()
  58. def _onOutputDevicesChanged(self):
  59. for printer_output_device in self._printer_output_devices:
  60. printer_output_device.hotendIdChanged.disconnect(self._onHotendIdChanged)
  61. printer_output_device.materialIdChanged.disconnect(self._onMaterialIdChanged)
  62. self._printer_output_devices.clear()
  63. for printer_output_device in Application.getInstance().getOutputDeviceManager().getOutputDevices():
  64. if isinstance(printer_output_device, PrinterOutputDevice):
  65. self._printer_output_devices.append(printer_output_device)
  66. printer_output_device.hotendIdChanged.connect(self._onHotendIdChanged)
  67. printer_output_device.materialIdChanged.connect(self._onMaterialIdChanged)
  68. self.outputDevicesChanged.emit()
  69. @pyqtProperty("QVariantList", notify = outputDevicesChanged)
  70. def printerOutputDevices(self):
  71. return self._printer_output_devices
  72. def _onHotendIdChanged(self, index, hotend_id):
  73. if not self._global_container_stack:
  74. return
  75. containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "variant", definition = self._global_container_stack.getBottom().getId(), name = hotend_id)
  76. if containers:
  77. extruder_manager = ExtruderManager.ExtruderManager.getInstance()
  78. old_index = extruder_manager.activeExtruderIndex
  79. if old_index != index:
  80. extruder_manager.setActiveExtruderIndex(index)
  81. else:
  82. old_index = None
  83. if self.activeVariantId != containers[0].getId():
  84. if time.time() - self._auto_change_material_hotend_flood_time > self._auto_change_material_hotend_flood_window:
  85. Application.getInstance().messageBox(catalog.i18nc("@window:title", "Changes on the Printer"), catalog.i18nc("@label", "Do you want to change the hotend to match the hotend in your printer?"),
  86. catalog.i18nc("@label", "The hotend on your printer was changed. For best results always slice for the hotend that is inserted in your printer."),
  87. buttons = QMessageBox.Yes + QMessageBox.No, icon = QMessageBox.Question, callback = self._hotendChangedDialogCallback, callback_arguments = [index, containers[0].getId()])
  88. else:
  89. self._hotendChangedDialogCallback(self._auto_change_material_hotend_flood_last_choice, index, containers[0].getId())
  90. if old_index is not None:
  91. extruder_manager.setActiveExtruderIndex(old_index)
  92. else:
  93. Logger.log("w", "No variant found for printer definition %s with id %s" % (definition_id, variant_id))
  94. def _hotendChangedDialogCallback(self, button, index, hotend_id):
  95. self._auto_change_material_hotend_flood_time = time.time()
  96. self._auto_change_material_hotend_flood_last_choice = button
  97. Logger.log("d", "Setting hotend variant of hotend %d to %s" % (index, containers[0].getId()))
  98. extruder_manager = ExtruderManager.ExtruderManager.getInstance()
  99. old_index = extruder_manager.activeExtruderIndex
  100. if old_index != index:
  101. extruder_manager.setActiveExtruderIndex(index)
  102. else:
  103. old_index = None
  104. self.setActiveVariant(containers[0].getId())
  105. if old_index is not None:
  106. extruder_manager.setActiveExtruderIndex(old_index)
  107. def _onMaterialIdChanged(self, index, material_id):
  108. if not self._global_container_stack:
  109. return
  110. definition_id = "fdmprinter"
  111. if self._global_container_stack.getMetaDataEntry("has_machine_materials", False):
  112. definition_id = self._global_container_stack.getBottom().getId()
  113. containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "material", definition = definition_id, GUID = material_id)
  114. if containers:
  115. extruder_manager = ExtruderManager.ExtruderManager.getInstance()
  116. old_index = extruder_manager.activeExtruderIndex
  117. if old_index != index:
  118. extruder_manager.setActiveExtruderIndex(index)
  119. else:
  120. old_index = None
  121. if self.activeMaterialId != containers[0].getId():
  122. if time.time() - self._auto_change_material_hotend_flood_time > self._auto_change_material_hotend_flood_window:
  123. Application.getInstance().messageBox(catalog.i18nc("@window:title", "Changes on the Printer"), catalog.i18nc("@label", "Do you want to change the material to match the material in your printer?"),
  124. catalog.i18nc("@label", "The material on your printer was changed. For best results always slice for the material that is inserted in your printer."),
  125. buttons = QMessageBox.Yes + QMessageBox.No, icon = QMessageBox.Question, callback = self._materialIdChangedDialogCallback, callback_arguments = [index, containers[0].getId()])
  126. else:
  127. self._materialIdChangedDialogCallback(self._auto_change_material_hotend_flood_last_choice, index, containers[0].getId())
  128. if old_index is not None:
  129. extruder_manager.setActiveExtruderIndex(old_index)
  130. else:
  131. Logger.log("w", "No material definition found for printer definition %s and GUID %s" % (definition_id, material_id))
  132. def _materialIdChangedDialogCallback(self, button, index, material_id):
  133. self._auto_change_material_hotend_flood_time = time.time()
  134. self._auto_change_material_hotend_flood_last_choice = button
  135. Logger.log("d", "Setting material of hotend %d to %s" % (index, material_id))
  136. extruder_manager = ExtruderManager.ExtruderManager.getInstance()
  137. old_index = extruder_manager.activeExtruderIndex
  138. if old_index != index:
  139. extruder_manager.setActiveExtruderIndex(index)
  140. else:
  141. old_index = None
  142. self.setActiveMaterial(material_id)
  143. if old_index is not None:
  144. extruder_manager.setActiveExtruderIndex(old_index)
  145. def _onGlobalPropertyChanged(self, key, property_name):
  146. if property_name == "value":
  147. self.globalValueChanged.emit()
  148. if property_name == "validationState":
  149. if self._global_stack_valid:
  150. changed_validation_state = self._active_container_stack.getProperty(key, property_name)
  151. if changed_validation_state in (UM.Settings.ValidatorState.Exception, UM.Settings.ValidatorState.MaximumError, UM.Settings.ValidatorState.MinimumError):
  152. self._global_stack_valid = False
  153. self.globalValidationChanged.emit()
  154. else:
  155. has_errors = self._checkStackForErrors(self._active_container_stack)
  156. if not has_errors:
  157. self._global_stack_valid = True
  158. self.globalValidationChanged.emit()
  159. def _onGlobalContainerChanged(self):
  160. if self._global_container_stack:
  161. self._global_container_stack.containersChanged.disconnect(self._onInstanceContainersChanged)
  162. self._global_container_stack.propertyChanged.disconnect(self._onGlobalPropertyChanged)
  163. self._global_container_stack = Application.getInstance().getGlobalContainerStack()
  164. self.globalContainerChanged.emit()
  165. if self._global_container_stack:
  166. Preferences.getInstance().setValue("cura/active_machine", self._global_container_stack.getId())
  167. self._global_container_stack.containersChanged.connect(self._onInstanceContainersChanged)
  168. self._global_container_stack.propertyChanged.connect(self._onGlobalPropertyChanged)
  169. self._global_stack_valid = not self._checkStackForErrors(self._global_container_stack)
  170. def _onActiveExtruderStackChanged(self):
  171. self.blurSettings.emit() # Ensure no-one has focus.
  172. if self._active_container_stack and self._active_container_stack != self._global_container_stack:
  173. self._active_container_stack.containersChanged.disconnect(self._onInstanceContainersChanged)
  174. self._active_container_stack.propertyChanged.disconnect(self._onGlobalPropertyChanged)
  175. self._active_container_stack = ExtruderManager.getInstance().getActiveExtruderStack()
  176. if self._active_container_stack:
  177. self._active_container_stack.containersChanged.connect(self._onInstanceContainersChanged)
  178. self._active_container_stack.propertyChanged.connect(self._onGlobalPropertyChanged)
  179. else:
  180. self._active_container_stack = self._global_container_stack
  181. def _onInstanceContainersChanged(self, container):
  182. container_type = container.getMetaDataEntry("type")
  183. if container_type == "material":
  184. self.activeMaterialChanged.emit()
  185. elif container_type == "variant":
  186. self.activeVariantChanged.emit()
  187. elif container_type == "quality":
  188. self.activeQualityChanged.emit()
  189. @pyqtSlot(str)
  190. def setActiveMachine(self, stack_id):
  191. containers = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(id = stack_id)
  192. if containers:
  193. Application.getInstance().setGlobalContainerStack(containers[0])
  194. @pyqtSlot(str, str)
  195. def addMachine(self, name, definition_id):
  196. definitions = UM.Settings.ContainerRegistry.getInstance().findDefinitionContainers(id = definition_id)
  197. if definitions:
  198. definition = definitions[0]
  199. name = self._createUniqueName("machine", "", name, definition.getName())
  200. new_global_stack = UM.Settings.ContainerStack(name)
  201. new_global_stack.addMetaDataEntry("type", "machine")
  202. UM.Settings.ContainerRegistry.getInstance().addContainer(new_global_stack)
  203. variant_instance_container = self._updateVariantContainer(definition)
  204. material_instance_container = self._updateMaterialContainer(definition, variant_instance_container)
  205. quality_instance_container = self._updateQualityContainer(definition, material_instance_container)
  206. current_settings_instance_container = UM.Settings.InstanceContainer(name + "_current_settings")
  207. current_settings_instance_container.addMetaDataEntry("machine", name)
  208. current_settings_instance_container.addMetaDataEntry("type", "user")
  209. current_settings_instance_container.setDefinition(definitions[0])
  210. UM.Settings.ContainerRegistry.getInstance().addContainer(current_settings_instance_container)
  211. # If a definition is found, its a list. Should only have one item.
  212. new_global_stack.addContainer(definition)
  213. if variant_instance_container:
  214. new_global_stack.addContainer(variant_instance_container)
  215. if material_instance_container:
  216. new_global_stack.addContainer(material_instance_container)
  217. if quality_instance_container:
  218. new_global_stack.addContainer(quality_instance_container)
  219. new_global_stack.addContainer(current_settings_instance_container)
  220. ExtruderManager.getInstance().addMachineExtruders(definition)
  221. Application.getInstance().setGlobalContainerStack(new_global_stack)
  222. ## Create a name that is not empty and unique
  223. # \param container_type \type{string} Type of the container (machine, quality, ...)
  224. # \param current_name \type{} Current name of the container, which may be an acceptable option
  225. # \param new_name \type{string} Base name, which may not be unique
  226. # \param fallback_name \type{string} Name to use when (stripped) new_name is empty
  227. # \return \type{string} Name that is unique for the specified type and name/id
  228. def _createUniqueName(self, container_type, current_name, new_name, fallback_name):
  229. return UM.Settings.ContainerRegistry.getInstance().createUniqueName(container_type, current_name, new_name, fallback_name)
  230. ## Convenience function to check if a stack has errors.
  231. def _checkStackForErrors(self, stack):
  232. if stack is None:
  233. return False
  234. for key in stack.getAllKeys():
  235. validation_state = stack.getProperty(key, "validationState")
  236. if validation_state in (UM.Settings.ValidatorState.Exception, UM.Settings.ValidatorState.MaximumError, UM.Settings.ValidatorState.MinimumError):
  237. return True
  238. return False
  239. ## Remove all instances from the top instanceContainer (effectively removing all user-changed settings)
  240. @pyqtSlot()
  241. def clearUserSettings(self):
  242. if not self._active_container_stack:
  243. return
  244. self.blurSettings.emit()
  245. user_settings = self._active_container_stack.getTop()
  246. user_settings.clear()
  247. ## Check if the global_container has instances in the user container
  248. @pyqtProperty(bool, notify = activeStackChanged)
  249. def hasUserSettings(self):
  250. if not self._active_container_stack:
  251. return False
  252. user_settings = self._active_container_stack.getTop().findInstances(**{})
  253. return len(user_settings) != 0
  254. ## Check if the global profile does not contain error states
  255. # Note that the _global_stack_valid is cached due to performance issues
  256. # Calling _checkStackForErrors on every change is simply too expensive
  257. @pyqtProperty(bool, notify = globalValidationChanged)
  258. def isGlobalStackValid(self):
  259. return bool(self._global_stack_valid)
  260. @pyqtProperty(str, notify = activeStackChanged)
  261. def activeUserProfileId(self):
  262. if self._active_container_stack:
  263. return self._active_container_stack.getTop().getId()
  264. return ""
  265. @pyqtProperty(str, notify = globalContainerChanged)
  266. def activeMachineName(self):
  267. if self._global_container_stack:
  268. return self._global_container_stack.getName()
  269. return ""
  270. @pyqtProperty(str, notify = globalContainerChanged)
  271. def activeMachineId(self):
  272. if self._global_container_stack:
  273. return self._global_container_stack.getId()
  274. return ""
  275. @pyqtProperty(str, notify = activeStackChanged)
  276. def activeStackId(self):
  277. if self._active_container_stack:
  278. return self._active_container_stack.getId()
  279. return ""
  280. @pyqtProperty(str, notify = activeMaterialChanged)
  281. def activeMaterialName(self):
  282. if self._active_container_stack:
  283. material = self._active_container_stack.findContainer({"type":"material"})
  284. if material:
  285. return material.getName()
  286. return ""
  287. @pyqtProperty(str, notify=activeMaterialChanged)
  288. def activeMaterialId(self):
  289. if self._active_container_stack:
  290. material = self._active_container_stack.findContainer({"type": "material"})
  291. if material:
  292. return material.getId()
  293. return ""
  294. @pyqtProperty(str, notify=activeQualityChanged)
  295. def activeQualityName(self):
  296. if self._active_container_stack:
  297. quality = self._active_container_stack.findContainer({"type": "quality"})
  298. if quality:
  299. return quality.getName()
  300. return ""
  301. @pyqtProperty(str, notify=activeQualityChanged)
  302. def activeQualityId(self):
  303. if self._active_container_stack:
  304. quality = self._active_container_stack.findContainer({"type": "quality"})
  305. if quality:
  306. return quality.getId()
  307. return ""
  308. ## Check if a container is read_only
  309. @pyqtSlot(str, result = bool)
  310. def isReadOnly(self, container_id):
  311. containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = container_id)
  312. if not containers or not self._active_container_stack:
  313. return True
  314. return containers[0].isReadOnly()
  315. @pyqtSlot(result = str)
  316. def newQualityContainerFromQualityAndUser(self):
  317. new_container_id = self.duplicateContainer(self.activeQualityId)
  318. if new_container_id == "":
  319. return
  320. self.blurSettings.emit()
  321. self.setActiveQuality(new_container_id)
  322. self.updateQualityContainerFromUserContainer()
  323. return new_container_id
  324. @pyqtSlot(str, result=str)
  325. def duplicateContainer(self, container_id):
  326. if not self._active_container_stack:
  327. return ""
  328. containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = container_id)
  329. if containers:
  330. new_name = self._createUniqueName("quality", "", containers[0].getName(), catalog.i18nc("@label", "Custom profile"))
  331. new_container = InstanceContainer("")
  332. ## Copy all values
  333. new_container.deserialize(containers[0].serialize())
  334. new_container.setReadOnly(False)
  335. new_container.setName(new_name)
  336. new_container._id = new_name
  337. UM.Settings.ContainerRegistry.getInstance().addContainer(new_container)
  338. return new_name
  339. return ""
  340. @pyqtSlot(str, str)
  341. def renameQualityContainer(self, container_id, new_name):
  342. containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = container_id, type = "quality")
  343. if containers:
  344. new_name = self._createUniqueName("quality", containers[0].getName(), new_name,
  345. catalog.i18nc("@label", "Custom profile"))
  346. if containers[0].getName() == new_name:
  347. # Nothing to do.
  348. return
  349. # As we also want the id of the container to be changed (so that profile name is the name of the file
  350. # on disk. We need to create a new instance and remove it (so the old file of the container is removed)
  351. # If we don't do that, we might get duplicates & other weird issues.
  352. new_container = InstanceContainer("")
  353. new_container.deserialize(containers[0].serialize())
  354. # Actually set the name
  355. new_container.setName(new_name)
  356. new_container._id = new_name # Todo: Fix proper id change function for this.
  357. # Add the "new" container.
  358. UM.Settings.ContainerRegistry.getInstance().addContainer(new_container)
  359. # Ensure that the renamed profile is saved -before- we remove the old profile.
  360. Application.getInstance().saveSettings()
  361. # Actually set & remove new / old quality.
  362. self.setActiveQuality(new_name)
  363. self.removeQualityContainer(containers[0].getId())
  364. @pyqtSlot(str)
  365. def removeQualityContainer(self, container_id):
  366. containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = container_id)
  367. if not containers or not self._active_container_stack:
  368. return
  369. # If the container that is being removed is the currently active container, set another machine as the active container
  370. activate_new_container = container_id == self.activeQualityId
  371. UM.Settings.ContainerRegistry.getInstance().removeContainer(container_id)
  372. if activate_new_container:
  373. definition_id = "fdmprinter" if not self.filterQualityByMachine else self.activeDefinitionId
  374. containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "quality", definition = definition_id)
  375. if containers:
  376. self.setActiveQuality(containers[0].getId())
  377. self.activeQualityChanged.emit()
  378. @pyqtSlot()
  379. def updateQualityContainerFromUserContainer(self):
  380. if not self._active_container_stack:
  381. return
  382. user_settings = self._active_container_stack.getTop()
  383. quality = self._active_container_stack.findContainer({"type": "quality"})
  384. for key in user_settings.getAllKeys():
  385. quality.setProperty(key, "value", user_settings.getProperty(key, "value"))
  386. self.clearUserSettings() # As all users settings are noq a quality, remove them.
  387. @pyqtSlot(str)
  388. def setActiveMaterial(self, material_id):
  389. containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = material_id)
  390. if not containers or not self._active_container_stack:
  391. return
  392. old_material = self._active_container_stack.findContainer({"type":"material"})
  393. old_quality = self._active_container_stack.findContainer({"type": "quality"})
  394. if old_material:
  395. material_index = self._active_container_stack.getContainerIndex(old_material)
  396. self._active_container_stack.replaceContainer(material_index, containers[0])
  397. preferred_quality_name = None
  398. if old_quality:
  399. preferred_quality_name = old_quality.getName()
  400. self.setActiveQuality(self._updateQualityContainer(self._global_container_stack.getBottom(), containers[0], preferred_quality_name).id)
  401. @pyqtSlot(str)
  402. def setActiveVariant(self, variant_id):
  403. containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = variant_id)
  404. if not containers or not self._active_container_stack:
  405. return
  406. old_variant = self._active_container_stack.findContainer({"type": "variant"})
  407. old_material = self._active_container_stack.findContainer({"type": "material"})
  408. if old_variant:
  409. variant_index = self._active_container_stack.getContainerIndex(old_variant)
  410. self._active_container_stack.replaceContainer(variant_index, containers[0])
  411. preferred_material = None
  412. if old_material:
  413. preferred_material_name = old_material.getName()
  414. self.setActiveMaterial(self._updateMaterialContainer(self._global_container_stack.getBottom(), containers[0], preferred_material_name).id)
  415. @pyqtSlot(str)
  416. def setActiveQuality(self, quality_id):
  417. containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = quality_id)
  418. if not containers or not self._active_container_stack:
  419. return
  420. old_quality = self._active_container_stack.findContainer({"type": "quality"})
  421. if old_quality and old_quality != containers[0]:
  422. quality_index = self._active_container_stack.getContainerIndex(old_quality)
  423. self._active_container_stack.replaceContainer(quality_index, containers[0])
  424. if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1:
  425. # Ask the user if the user profile should be cleared or not (discarding the current settings)
  426. # In Simple Mode we assume the user always wants to keep the (limited) current settings
  427. details = catalog.i18nc("@label", "You made changes to the following setting(s):")
  428. user_settings = self._active_container_stack.getTop().findInstances(**{})
  429. for setting in user_settings:
  430. details = details + "\n " + setting.definition.label
  431. Application.getInstance().messageBox(catalog.i18nc("@window:title", "Switched profiles"), catalog.i18nc("@label", "Do you want to transfer your changed settings to this profile?"),
  432. catalog.i18nc("@label", "If you transfer your settings they will override settings in the profile."), details,
  433. buttons = QMessageBox.Yes + QMessageBox.No, icon = QMessageBox.Question, callback = self._keepUserSettingsDialogCallback)
  434. def _keepUserSettingsDialogCallback(self, button):
  435. if button == QMessageBox.Yes:
  436. # Yes, keep the settings in the user profile with this profile
  437. pass
  438. elif button == QMessageBox.No:
  439. # No, discard the settings in the user profile
  440. self.clearUserSettings()
  441. @pyqtProperty(str, notify = activeVariantChanged)
  442. def activeVariantName(self):
  443. if self._active_container_stack:
  444. variant = self._active_container_stack.findContainer({"type": "variant"})
  445. if variant:
  446. return variant.getName()
  447. return ""
  448. @pyqtProperty(str, notify = activeVariantChanged)
  449. def activeVariantId(self):
  450. if self._active_container_stack:
  451. variant = self._active_container_stack.findContainer({"type": "variant"})
  452. if variant:
  453. return variant.getId()
  454. return ""
  455. @pyqtProperty(str, notify = globalContainerChanged)
  456. def activeDefinitionId(self):
  457. if self._global_container_stack:
  458. definition = self._global_container_stack.getBottom()
  459. if definition:
  460. return definition.id
  461. return ""
  462. @pyqtSlot(str, str)
  463. def renameMachine(self, machine_id, new_name):
  464. containers = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(id = machine_id)
  465. if containers:
  466. new_name = self._createUniqueName("machine", containers[0].getName(), new_name, containers[0].getBottom().getName())
  467. containers[0].setName(new_name)
  468. self.globalContainerChanged.emit()
  469. @pyqtSlot(str)
  470. def removeMachine(self, machine_id):
  471. # If the machine that is being removed is the currently active machine, set another machine as the active machine
  472. activate_new_machine = (self._global_container_stack and self._global_container_stack.getId() == machine_id)
  473. current_settings_id = machine_id + "_current_settings"
  474. containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = current_settings_id)
  475. for container in containers:
  476. UM.Settings.ContainerRegistry.getInstance().removeContainer(container.getId())
  477. UM.Settings.ContainerRegistry.getInstance().removeContainer(machine_id)
  478. if activate_new_machine:
  479. stacks = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(type = "machine")
  480. if stacks:
  481. Application.getInstance().setGlobalContainerStack(stacks[0])
  482. @pyqtProperty(bool, notify = globalContainerChanged)
  483. def hasMaterials(self):
  484. if self._global_container_stack:
  485. return bool(self._global_container_stack.getMetaDataEntry("has_materials", False))
  486. return False
  487. @pyqtProperty(bool, notify = globalContainerChanged)
  488. def hasVariants(self):
  489. if self._global_container_stack:
  490. return bool(self._global_container_stack.getMetaDataEntry("has_variants", False))
  491. return False
  492. @pyqtProperty(bool, notify = globalContainerChanged)
  493. def filterMaterialsByMachine(self):
  494. if self._global_container_stack:
  495. return bool(self._global_container_stack.getMetaDataEntry("has_machine_materials", False))
  496. return False
  497. @pyqtProperty(bool, notify = globalContainerChanged)
  498. def filterQualityByMachine(self):
  499. if self._global_container_stack:
  500. return bool(self._global_container_stack.getMetaDataEntry("has_machine_quality", False))
  501. return False
  502. @pyqtSlot(str, result = str)
  503. def getDefinitionByMachineId(self, machine_id):
  504. containers = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(id=machine_id)
  505. if containers:
  506. return containers[0].getBottom().getId()
  507. @staticmethod
  508. def createMachineManager(engine, script_engine):
  509. return MachineManager()
  510. def _updateVariantContainer(self, definition):
  511. if not definition.getMetaDataEntry("has_variants"):
  512. return self._empty_variant_container
  513. containers = []
  514. preferred_variant = definition.getMetaDataEntry("preferred_variant")
  515. if preferred_variant:
  516. containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "variant", definition = definition.id, id = preferred_variant)
  517. if not containers:
  518. containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "variant", definition = definition.id)
  519. if containers:
  520. return containers[0]
  521. return self._empty_variant_container
  522. def _updateMaterialContainer(self, definition, variant_container = None, preferred_material_name = None):
  523. if not definition.getMetaDataEntry("has_materials"):
  524. return self._empty_material_container
  525. search_criteria = { "type": "material" }
  526. if definition.getMetaDataEntry("has_machine_materials"):
  527. search_criteria["definition"] = definition.id
  528. if definition.getMetaDataEntry("has_variants") and variant_container:
  529. search_criteria["variant"] = variant_container.id
  530. else:
  531. search_criteria["definition"] = "fdmprinter"
  532. if preferred_material_name:
  533. search_criteria["name"] = preferred_material_name
  534. else:
  535. preferred_material = definition.getMetaDataEntry("preferred_material")
  536. if preferred_material:
  537. search_criteria["id"] = preferred_material
  538. containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
  539. if containers:
  540. return containers[0]
  541. if "name" in search_criteria or "id" in search_criteria:
  542. # If a material by this name can not be found, try a wider set of search criteria
  543. search_criteria.pop("name", None)
  544. search_criteria.pop("id", None)
  545. containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
  546. if containers:
  547. return containers[0]
  548. return self._empty_material_container
  549. def _updateQualityContainer(self, definition, material_container = None, preferred_quality_name = None):
  550. search_criteria = { "type": "quality" }
  551. if definition.getMetaDataEntry("has_machine_quality"):
  552. search_criteria["definition"] = definition.id
  553. if definition.getMetaDataEntry("has_materials") and material_container:
  554. search_criteria["material"] = material_container.id
  555. else:
  556. search_criteria["definition"] = "fdmprinter"
  557. if preferred_quality_name:
  558. search_criteria["name"] = preferred_quality_name
  559. else:
  560. preferred_quality = definition.getMetaDataEntry("preferred_quality")
  561. if preferred_quality:
  562. search_criteria["id"] = preferred_quality
  563. containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
  564. if containers:
  565. return containers[0]
  566. if "name" in search_criteria or "id" in search_criteria:
  567. # If a quality by this name can not be found, try a wider set of search criteria
  568. search_criteria.pop("name", None)
  569. search_criteria.pop("id", None)
  570. containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
  571. if containers:
  572. return containers[0]
  573. return self._empty_quality_container