MachineManager.py 37 KB

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