MachineSettingsAction.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. # Copyright (c) 2016 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.Settings.InstanceContainer import InstanceContainer
  8. from UM.Settings.ContainerRegistry import ContainerRegistry
  9. from UM.Settings.DefinitionContainer import DefinitionContainer
  10. from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
  11. from UM.Logger import Logger
  12. from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
  13. from cura.Settings.ExtruderManager import ExtruderManager
  14. import UM.i18n
  15. catalog = UM.i18n.i18nCatalog("cura")
  16. ## This action allows for certain settings that are "machine only") to be modified.
  17. # It automatically detects machine definitions that it knows how to change and attaches itself to those.
  18. class MachineSettingsAction(MachineAction):
  19. def __init__(self, parent = None):
  20. super().__init__("MachineSettingsAction", catalog.i18nc("@action", "Machine Settings"))
  21. self._qml_url = "MachineSettingsAction.qml"
  22. self._global_container_stack = None
  23. self._container_index = 0
  24. self._extruder_container_index = 0
  25. self._container_registry = ContainerRegistry.getInstance()
  26. self._container_registry.containerAdded.connect(self._onContainerAdded)
  27. Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)
  28. ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged)
  29. self._backend = Application.getInstance().getBackend()
  30. def _reset(self):
  31. if not self._global_container_stack:
  32. return
  33. # Make sure there is a definition_changes container to store the machine settings
  34. definition_changes_container = self._global_container_stack.findContainer({"type": "definition_changes"})
  35. if not definition_changes_container:
  36. definition_changes_container = self._createDefinitionChangesContainer(self._global_container_stack, self._global_container_stack.getName() + "_settings")
  37. # Notify the UI in which container to store the machine settings data
  38. container_index = self._global_container_stack.getContainerIndex(definition_changes_container)
  39. if container_index != self._container_index:
  40. self._container_index = container_index
  41. self.containerIndexChanged.emit()
  42. # Disable autoslicing while the machineaction is showing
  43. self._backend.disableTimer()
  44. @pyqtSlot()
  45. def onFinishAction(self):
  46. # Restore autoslicing when the machineaction is dismissed
  47. if self._backend.determineAutoSlicing():
  48. self._backend.tickle()
  49. def _onActiveExtruderStackChanged(self):
  50. extruder_container_stack = ExtruderManager.getInstance().getActiveExtruderStack()
  51. if not self._global_container_stack or not extruder_container_stack:
  52. return
  53. # Make sure there is a definition_changes container to store the machine settings
  54. definition_changes_container = extruder_container_stack.findContainer({"type": "definition_changes"})
  55. if not definition_changes_container:
  56. definition_changes_container = self._createDefinitionChangesContainer(extruder_container_stack, extruder_container_stack.getId() + "_settings")
  57. # Notify the UI in which container to store the machine settings data
  58. container_index = extruder_container_stack.getContainerIndex(definition_changes_container)
  59. if container_index != self._extruder_container_index:
  60. self._extruder_container_index = container_index
  61. self.extruderContainerIndexChanged.emit()
  62. def _createDefinitionChangesContainer(self, container_stack, container_name, container_index = None):
  63. definition_changes_container = InstanceContainer(container_name)
  64. definition = container_stack.getBottom()
  65. definition_changes_container.setDefinition(definition)
  66. definition_changes_container.addMetaDataEntry("type", "definition_changes")
  67. self._container_registry.addContainer(definition_changes_container)
  68. # Insert definition_changes between the definition and the variant
  69. container_stack.insertContainer(-1, definition_changes_container)
  70. return definition_changes_container
  71. containerIndexChanged = pyqtSignal()
  72. @pyqtProperty(int, notify = containerIndexChanged)
  73. def containerIndex(self):
  74. return self._container_index
  75. extruderContainerIndexChanged = pyqtSignal()
  76. @pyqtProperty(int, notify = extruderContainerIndexChanged)
  77. def extruderContainerIndex(self):
  78. return self._extruder_container_index
  79. def _onContainerAdded(self, container):
  80. # Add this action as a supported action to all machine definitions
  81. if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine":
  82. Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey())
  83. def _onGlobalContainerChanged(self):
  84. self._global_container_stack = Application.getInstance().getGlobalContainerStack()
  85. # This additional emit is needed because we cannot connect a UM.Signal directly to a pyqtSignal
  86. self.globalContainerChanged.emit()
  87. globalContainerChanged = pyqtSignal()
  88. @pyqtProperty(int, notify = globalContainerChanged)
  89. def definedExtruderCount(self):
  90. if not self._global_container_stack:
  91. return 0
  92. return len(self._global_container_stack.getMetaDataEntry("machine_extruder_trains"))
  93. @pyqtSlot(int)
  94. def setMachineExtruderCount(self, extruder_count):
  95. machine_manager = Application.getInstance().getMachineManager()
  96. extruder_manager = ExtruderManager.getInstance()
  97. definition_changes_container = self._global_container_stack.findContainer({"type": "definition_changes"})
  98. if not self._global_container_stack or not definition_changes_container:
  99. return
  100. previous_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
  101. if extruder_count == previous_extruder_count:
  102. return
  103. extruder_material_id = None
  104. extruder_variant_id = None
  105. if extruder_count == 1:
  106. # Get the material and variant of the first extruder before setting the number extruders to 1
  107. if machine_manager.hasMaterials:
  108. extruder_material_id = machine_manager.allActiveMaterialIds[extruder_manager.extruderIds["0"]]
  109. if machine_manager.hasVariants:
  110. extruder_variant_id = machine_manager.activeVariantIds[0]
  111. # Copy any settable_per_extruder setting value from the extruders to the global stack
  112. extruder_stacks = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
  113. extruder_stacks.reverse() # make sure the first extruder is done last, so its settings override any higher extruder settings
  114. global_user_container = self._global_container_stack.getTop()
  115. for extruder_stack in extruder_stacks:
  116. extruder_index = extruder_stack.getMetaDataEntry("position")
  117. extruder_user_container = extruder_stack.getTop()
  118. for setting_instance in extruder_user_container.findInstances():
  119. setting_key = setting_instance.definition.key
  120. settable_per_extruder = self._global_container_stack.getProperty(setting_key, "settable_per_extruder")
  121. if settable_per_extruder:
  122. limit_to_extruder = self._global_container_stack.getProperty(setting_key, "limit_to_extruder")
  123. if limit_to_extruder == "-1" or limit_to_extruder == extruder_index:
  124. global_user_container.setProperty(setting_key, "value", extruder_user_container.getProperty(setting_key, "value"))
  125. extruder_user_container.removeInstance(setting_key)
  126. # Check to see if any features are set to print with an extruder that will no longer exist
  127. for setting_key in ["adhesion_extruder_nr", "support_extruder_nr", "support_extruder_nr_layer_0", "support_infill_extruder_nr", "support_interface_extruder_nr"]:
  128. if int(self._global_container_stack.getProperty(setting_key, "value")) > extruder_count -1:
  129. Logger.log("i", "Lowering %s setting to match number of extruders", setting_key)
  130. self._global_container_stack.getTop().setProperty(setting_key, "value", extruder_count -1)
  131. # Check to see if any objects are set to print with an extruder that will no longer exist
  132. root_node = Application.getInstance().getController().getScene().getRoot()
  133. for node in DepthFirstIterator(root_node):
  134. if node.getMeshData():
  135. extruder_nr = node.callDecoration("getActiveExtruderPosition")
  136. if extruder_nr is not None and extruder_nr > extruder_count - 1:
  137. node.callDecoration("setActiveExtruder", extruder_manager.getExtruderStack(extruder_count -1).getId())
  138. definition_changes_container.setProperty("machine_extruder_count", "value", extruder_count)
  139. self.forceUpdate()
  140. if extruder_count > 1:
  141. # Multiextrusion
  142. # Make sure one of the extruder stacks is active
  143. if extruder_manager.activeExtruderIndex == -1:
  144. extruder_manager.setActiveExtruderIndex(0)
  145. # Move settable_per_extruder values out of the global container
  146. if previous_extruder_count == 1:
  147. extruder_stacks = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
  148. global_user_container = self._global_container_stack.getTop()
  149. for setting_instance in global_user_container.findInstances():
  150. setting_key = setting_instance.definition.key
  151. settable_per_extruder = self._global_container_stack.getProperty(setting_key, "settable_per_extruder")
  152. if settable_per_extruder:
  153. limit_to_extruder = int(self._global_container_stack.getProperty(setting_key, "limit_to_extruder"))
  154. extruder_stack = extruder_stacks[max(0, limit_to_extruder)]
  155. extruder_stack.getTop().setProperty(setting_key, "value", global_user_container.getProperty(setting_key, "value"))
  156. global_user_container.removeInstance(setting_key)
  157. else:
  158. # Single extrusion
  159. # Make sure the machine stack is active
  160. if extruder_manager.activeExtruderIndex > -1:
  161. extruder_manager.setActiveExtruderIndex(-1);
  162. # Restore material and variant on global stack
  163. # MachineManager._onGlobalContainerChanged removes the global material and vaiant of multiextruder machines
  164. if extruder_material_id:
  165. machine_manager.setActiveMaterial(extruder_material_id);
  166. if extruder_variant_id:
  167. machine_manager.setActiveVariant(extruder_variant_id);
  168. @pyqtSlot()
  169. def forceUpdate(self):
  170. # Force rebuilding the build volume by reloading the global container stack.
  171. # This is a bit of a hack, but it seems quick enough.
  172. Application.getInstance().globalContainerStackChanged.emit()
  173. @pyqtSlot()
  174. def updateHasMaterialsMetadata(self):
  175. # Updates the has_materials metadata flag after switching gcode flavor
  176. if not self._global_container_stack:
  177. return
  178. definition = self._global_container_stack.getBottom()
  179. if definition.getProperty("machine_gcode_flavor", "value") == "UltiGCode" and not definition.getMetaDataEntry("has_materials", False):
  180. has_materials = self._global_container_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode"
  181. material_container = self._global_container_stack.findContainer({"type": "material"})
  182. material_index = self._global_container_stack.getContainerIndex(material_container)
  183. if has_materials:
  184. if "has_materials" in self._global_container_stack.getMetaData():
  185. self._global_container_stack.setMetaDataEntry("has_materials", True)
  186. else:
  187. self._global_container_stack.addMetaDataEntry("has_materials", True)
  188. # Set the material container to a sane default
  189. if material_container.getId() == "empty_material":
  190. search_criteria = { "type": "material", "definition": "fdmprinter", "id": "*pla*" }
  191. containers = self._container_registry.findInstanceContainers(**search_criteria)
  192. if containers:
  193. self._global_container_stack.replaceContainer(material_index, containers[0])
  194. else:
  195. # The metadata entry is stored in an ini, and ini files are parsed as strings only.
  196. # Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False.
  197. if "has_materials" in self._global_container_stack.getMetaData():
  198. self._global_container_stack.removeMetaDataEntry("has_materials")
  199. empty_material = self._container_registry.findInstanceContainers(id = "empty_material")[0]
  200. self._global_container_stack.replaceContainer(material_index, empty_material)
  201. Application.getInstance().globalContainerStackChanged.emit()