MachineSettingsAction.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. # Copyright (c) 2017 Ultimaker B.V.
  2. # Cura is released under the terms of the AGPLv3 or higher.
  3. from PyQt5.QtCore import pyqtProperty, pyqtSignal
  4. from UM.FlameProfiler import pyqtSlot
  5. from cura.MachineAction import MachineAction
  6. from UM.Application import Application
  7. from UM.Preferences import Preferences
  8. from UM.Settings.InstanceContainer import InstanceContainer
  9. from UM.Settings.ContainerRegistry import ContainerRegistry
  10. from UM.Settings.DefinitionContainer import DefinitionContainer
  11. from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
  12. from UM.Logger import Logger
  13. from cura.CuraApplication import CuraApplication
  14. from cura.Settings.ExtruderManager import ExtruderManager
  15. import UM.i18n
  16. catalog = UM.i18n.i18nCatalog("cura")
  17. ## This action allows for certain settings that are "machine only") to be modified.
  18. # It automatically detects machine definitions that it knows how to change and attaches itself to those.
  19. class MachineSettingsAction(MachineAction):
  20. def __init__(self, parent = None):
  21. super().__init__("MachineSettingsAction", catalog.i18nc("@action", "Machine Settings"))
  22. self._qml_url = "MachineSettingsAction.qml"
  23. self._global_container_stack = None
  24. self._container_index = 0
  25. self._extruder_container_index = 0
  26. self._container_registry = ContainerRegistry.getInstance()
  27. self._container_registry.containerAdded.connect(self._onContainerAdded)
  28. self._container_registry.containerRemoved.connect(self._onContainerRemoved)
  29. Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)
  30. ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged)
  31. self._backend = Application.getInstance().getBackend()
  32. def _onContainerAdded(self, container):
  33. # Add this action as a supported action to all machine definitions
  34. if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine":
  35. Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey())
  36. def _onContainerRemoved(self, container):
  37. # Remove definition_changes containers when a stack is removed
  38. if container.getMetaDataEntry("type") in ["machine", "extruder_train"]:
  39. definition_changes_container = container.definitionChanges
  40. if definition_changes_container.id == "empty":
  41. return
  42. self._container_registry.removeContainer(definition_changes_container.getId())
  43. def _reset(self):
  44. if not self._global_container_stack:
  45. return
  46. # Make sure there is a definition_changes container to store the machine settings
  47. definition_changes_container = self._global_container_stack.definitionChanges
  48. if definition_changes_container.id == "empty":
  49. definition_changes_container = self._createDefinitionChangesContainer(self._global_container_stack, self._global_container_stack.getName() + "_settings")
  50. # Notify the UI in which container to store the machine settings data
  51. container_index = self._global_container_stack.getContainerIndex(definition_changes_container)
  52. if container_index != self._container_index:
  53. self._container_index = container_index
  54. self.containerIndexChanged.emit()
  55. # Disable auto-slicing while the MachineAction is showing
  56. if self._backend: # This sometimes triggers before backend is loaded.
  57. self._backend.disableTimer()
  58. @pyqtSlot()
  59. def onFinishAction(self):
  60. # Restore autoslicing when the machineaction is dismissed
  61. if self._backend.determineAutoSlicing():
  62. self._backend.tickle()
  63. def _onActiveExtruderStackChanged(self):
  64. extruder_container_stack = ExtruderManager.getInstance().getActiveExtruderStack()
  65. if not self._global_container_stack or not extruder_container_stack:
  66. return
  67. # Make sure there is a definition_changes container to store the machine settings
  68. definition_changes_container = extruder_container_stack.definitionChanges
  69. if definition_changes_container.id == "empty":
  70. definition_changes_container = self._createDefinitionChangesContainer(extruder_container_stack, extruder_container_stack.getId() + "_settings")
  71. # Notify the UI in which container to store the machine settings data
  72. container_index = extruder_container_stack.getContainerIndex(definition_changes_container)
  73. if container_index != self._extruder_container_index:
  74. self._extruder_container_index = container_index
  75. self.extruderContainerIndexChanged.emit()
  76. def _createDefinitionChangesContainer(self, container_stack, container_name, container_index = None):
  77. definition_changes_container = InstanceContainer(container_name)
  78. definition = container_stack.getBottom()
  79. definition_changes_container.setDefinition(definition)
  80. definition_changes_container.addMetaDataEntry("type", "definition_changes")
  81. definition_changes_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
  82. self._container_registry.addContainer(definition_changes_container)
  83. container_stack.definitionChanges = definition_changes_container
  84. return definition_changes_container
  85. containerIndexChanged = pyqtSignal()
  86. @pyqtProperty(int, notify = containerIndexChanged)
  87. def containerIndex(self):
  88. return self._container_index
  89. extruderContainerIndexChanged = pyqtSignal()
  90. @pyqtProperty(int, notify = extruderContainerIndexChanged)
  91. def extruderContainerIndex(self):
  92. return self._extruder_container_index
  93. def _onGlobalContainerChanged(self):
  94. self._global_container_stack = Application.getInstance().getGlobalContainerStack()
  95. # This additional emit is needed because we cannot connect a UM.Signal directly to a pyqtSignal
  96. self.globalContainerChanged.emit()
  97. globalContainerChanged = pyqtSignal()
  98. @pyqtProperty(int, notify = globalContainerChanged)
  99. def definedExtruderCount(self):
  100. if not self._global_container_stack:
  101. return 0
  102. return len(self._global_container_stack.getMetaDataEntry("machine_extruder_trains"))
  103. @pyqtSlot(int)
  104. def setMachineExtruderCount(self, extruder_count):
  105. machine_manager = Application.getInstance().getMachineManager()
  106. extruder_manager = ExtruderManager.getInstance()
  107. definition_changes_container = self._global_container_stack.definitionChanges
  108. if not self._global_container_stack or definition_changes_container.id == "empty":
  109. return
  110. previous_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
  111. if extruder_count == previous_extruder_count:
  112. return
  113. extruder_material_id = None
  114. extruder_variant_id = None
  115. if extruder_count == 1:
  116. # Get the material and variant of the first extruder before setting the number extruders to 1
  117. if machine_manager.hasMaterials:
  118. extruder_material_id = machine_manager.allActiveMaterialIds[extruder_manager.extruderIds["0"]]
  119. if machine_manager.hasVariants:
  120. extruder_variant_id = machine_manager.allActiveVariantIds[extruder_manager.extruderIds["0"]]
  121. # Copy any settable_per_extruder setting value from the extruders to the global stack
  122. extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
  123. extruder_stacks.reverse() # make sure the first extruder is done last, so its settings override any higher extruder settings
  124. global_user_container = self._global_container_stack.getTop()
  125. for extruder_stack in extruder_stacks:
  126. extruder_index = extruder_stack.getMetaDataEntry("position")
  127. extruder_user_container = extruder_stack.getTop()
  128. for setting_instance in extruder_user_container.findInstances():
  129. setting_key = setting_instance.definition.key
  130. settable_per_extruder = self._global_container_stack.getProperty(setting_key, "settable_per_extruder")
  131. if settable_per_extruder:
  132. limit_to_extruder = self._global_container_stack.getProperty(setting_key, "limit_to_extruder")
  133. if limit_to_extruder == "-1" or limit_to_extruder == extruder_index:
  134. global_user_container.setProperty(setting_key, "value", extruder_user_container.getProperty(setting_key, "value"))
  135. extruder_user_container.removeInstance(setting_key)
  136. # Check to see if any features are set to print with an extruder that will no longer exist
  137. for setting_key in ["adhesion_extruder_nr", "support_extruder_nr", "support_extruder_nr_layer_0", "support_infill_extruder_nr", "support_interface_extruder_nr"]:
  138. if int(self._global_container_stack.getProperty(setting_key, "value")) > extruder_count - 1:
  139. Logger.log("i", "Lowering %s setting to match number of extruders", setting_key)
  140. self._global_container_stack.getTop().setProperty(setting_key, "value", extruder_count - 1)
  141. # Check to see if any objects are set to print with an extruder that will no longer exist
  142. root_node = Application.getInstance().getController().getScene().getRoot()
  143. for node in DepthFirstIterator(root_node):
  144. if node.getMeshData():
  145. extruder_nr = node.callDecoration("getActiveExtruderPosition")
  146. if extruder_nr is not None and int(extruder_nr) > extruder_count - 1:
  147. node.callDecoration("setActiveExtruder", extruder_manager.getExtruderStack(extruder_count - 1).getId())
  148. definition_changes_container.setProperty("machine_extruder_count", "value", extruder_count)
  149. self.forceUpdate()
  150. if extruder_count > 1:
  151. # Multiextrusion
  152. # Make sure one of the extruder stacks is active
  153. if extruder_manager.activeExtruderIndex == -1:
  154. extruder_manager.setActiveExtruderIndex(0)
  155. # Move settable_per_extruder values out of the global container
  156. if previous_extruder_count == 1:
  157. extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
  158. global_user_container = self._global_container_stack.getTop()
  159. for setting_instance in global_user_container.findInstances():
  160. setting_key = setting_instance.definition.key
  161. settable_per_extruder = self._global_container_stack.getProperty(setting_key, "settable_per_extruder")
  162. if settable_per_extruder:
  163. limit_to_extruder = int(self._global_container_stack.getProperty(setting_key, "limit_to_extruder"))
  164. extruder_stack = extruder_stacks[max(0, limit_to_extruder)]
  165. extruder_stack.getTop().setProperty(setting_key, "value", global_user_container.getProperty(setting_key, "value"))
  166. global_user_container.removeInstance(setting_key)
  167. else:
  168. # Single extrusion
  169. # Make sure the machine stack is active
  170. if extruder_manager.activeExtruderIndex > -1:
  171. extruder_manager.setActiveExtruderIndex(-1)
  172. # Restore material and variant on global stack
  173. # MachineManager._onGlobalContainerChanged removes the global material and variant of multiextruder machines
  174. if extruder_material_id or extruder_variant_id:
  175. # Prevent the DiscardOrKeepProfileChangesDialog from popping up (twice) if there are user changes
  176. # The dialog is not relevant here, since we're restoring the previous situation as good as possible
  177. preferences = Preferences.getInstance()
  178. choice_on_profile_override = preferences.getValue("cura/choice_on_profile_override")
  179. preferences.setValue("cura/choice_on_profile_override", "always_keep")
  180. if extruder_material_id:
  181. machine_manager.setActiveMaterial(extruder_material_id)
  182. if extruder_variant_id:
  183. machine_manager.setActiveVariant(extruder_variant_id)
  184. preferences.setValue("cura/choice_on_profile_override", choice_on_profile_override)
  185. @pyqtSlot()
  186. def forceUpdate(self):
  187. # Force rebuilding the build volume by reloading the global container stack.
  188. # This is a bit of a hack, but it seems quick enough.
  189. Application.getInstance().globalContainerStackChanged.emit()
  190. @pyqtSlot()
  191. def updateHasMaterialsMetadata(self):
  192. # Updates the has_materials metadata flag after switching gcode flavor
  193. if not self._global_container_stack:
  194. return
  195. definition = self._global_container_stack.getBottom()
  196. if definition.getProperty("machine_gcode_flavor", "value") != "UltiGCode" or definition.getMetaDataEntry("has_materials", False):
  197. # In other words: only continue for the UM2 (extended), but not for the UM2+
  198. return
  199. has_materials = self._global_container_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode"
  200. material_container = self._global_container_stack.material
  201. if has_materials:
  202. if "has_materials" in self._global_container_stack.getMetaData():
  203. self._global_container_stack.setMetaDataEntry("has_materials", True)
  204. else:
  205. self._global_container_stack.addMetaDataEntry("has_materials", True)
  206. # Set the material container to a sane default
  207. if material_container.getId() == "empty":
  208. search_criteria = { "type": "material", "definition": "fdmprinter", "id": self._global_container_stack.getMetaDataEntry("preferred_material")}
  209. materials = self._container_registry.findInstanceContainers(**search_criteria)
  210. if materials:
  211. self._global_container_stack.material = materials[0]
  212. else:
  213. # The metadata entry is stored in an ini, and ini files are parsed as strings only.
  214. # Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False.
  215. if "has_materials" in self._global_container_stack.getMetaData():
  216. self._global_container_stack.removeMetaDataEntry("has_materials")
  217. self._global_container_stack.material = ContainerRegistry.getInstance().getEmptyInstanceContainer()
  218. Application.getInstance().globalContainerStackChanged.emit()
  219. @pyqtSlot()
  220. def updateMaterialForDiameter(self):
  221. # Updates the material container to a material that matches the material diameter set for the printer
  222. if not self._global_container_stack:
  223. return
  224. if not self._global_container_stack.getMetaDataEntry("has_materials", False):
  225. return
  226. machine_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
  227. if machine_extruder_count > 1:
  228. material = ExtruderManager.getInstance().getActiveExtruderStack().material
  229. else:
  230. material = self._global_container_stack.material
  231. material_diameter = material.getProperty("material_diameter", "value")
  232. if not material_diameter: # in case of "empty" material
  233. material_diameter = 0
  234. material_approximate_diameter = str(round(material_diameter))
  235. definition_changes = self._global_container_stack.definitionChanges
  236. machine_diameter = definition_changes.getProperty("material_diameter", "value")
  237. if not machine_diameter:
  238. machine_diameter = self._global_container_stack.definition.getProperty("material_diameter", "value")
  239. machine_approximate_diameter = str(round(machine_diameter))
  240. if material_approximate_diameter != machine_approximate_diameter:
  241. Logger.log("i", "The the currently active material(s) do not match the diameter set for the printer. Finding alternatives.")
  242. if machine_extruder_count > 1:
  243. stacks = ExtruderManager.getInstance().getExtruderStacks()
  244. else:
  245. stacks = [self._global_container_stack]
  246. if self._global_container_stack.getMetaDataEntry("has_machine_materials", False):
  247. materials_definition = self._global_container_stack.definition.getId()
  248. has_material_variants = self._global_container_stack.getMetaDataEntry("has_variants", False)
  249. else:
  250. materials_definition = "fdmprinter"
  251. has_material_variants = False
  252. for stack in stacks:
  253. old_material = stack.material
  254. search_criteria = {
  255. "type": "material",
  256. "approximate_diameter": machine_approximate_diameter,
  257. "material": old_material.getMetaDataEntry("material", "value"),
  258. "supplier": old_material.getMetaDataEntry("supplier", "value"),
  259. "color_name": old_material.getMetaDataEntry("color_name", "value"),
  260. "definition": materials_definition
  261. }
  262. if has_material_variants:
  263. search_criteria["variant"] = stack.variant.getId()
  264. if old_material.getId() == "empty":
  265. search_criteria.pop("material", None)
  266. search_criteria.pop("supplier", None)
  267. search_criteria.pop("definition", None)
  268. search_criteria["id"] = stack.getMetaDataEntry("preferred_material")
  269. materials = self._container_registry.findInstanceContainers(**search_criteria)
  270. if not materials:
  271. # Same material with new diameter is not found, search for generic version of the same material type
  272. search_criteria.pop("supplier", None)
  273. search_criteria["color_name"] = "Generic"
  274. materials = self._container_registry.findInstanceContainers(**search_criteria)
  275. if not materials:
  276. # Generic material with new diameter is not found, search for preferred material
  277. search_criteria.pop("color_name", None)
  278. search_criteria.pop("material", None)
  279. search_criteria["id"] = stack.getMetaDataEntry("preferred_material")
  280. materials = self._container_registry.findInstanceContainers(**search_criteria)
  281. if not materials:
  282. # Preferrd material with new diameter is not found, search for any material
  283. search_criteria.pop("id", None)
  284. materials = self._container_registry.findInstanceContainers(**search_criteria)
  285. if not materials:
  286. # Just use empty material as a final fallback
  287. materials = [ContainerRegistry.getInstance().getEmptyInstanceContainer()]
  288. Logger.log("i", "Selecting new material: %s" % materials[0].getId())
  289. stack.material = materials[0]