MachineSettingsAction.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. # Copyright (c) 2017 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 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.ContainerRegistry import ContainerRegistry
  8. from UM.Settings.DefinitionContainer import DefinitionContainer
  9. from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
  10. from UM.Logger import Logger
  11. from cura.Settings.ExtruderManager import ExtruderManager
  12. from cura.Settings.CuraStackBuilder import CuraStackBuilder
  13. import UM.i18n
  14. catalog = UM.i18n.i18nCatalog("cura")
  15. ## This action allows for certain settings that are "machine only") to be modified.
  16. # It automatically detects machine definitions that it knows how to change and attaches itself to those.
  17. class MachineSettingsAction(MachineAction):
  18. def __init__(self, parent = None):
  19. super().__init__("MachineSettingsAction", catalog.i18nc("@action", "Machine Settings"))
  20. self._qml_url = "MachineSettingsAction.qml"
  21. self._global_container_stack = None
  22. from cura.Settings.CuraContainerStack import _ContainerIndexes
  23. self._container_index = _ContainerIndexes.DefinitionChanges
  24. self._container_registry = ContainerRegistry.getInstance()
  25. self._container_registry.containerAdded.connect(self._onContainerAdded)
  26. self._container_registry.containerRemoved.connect(self._onContainerRemoved)
  27. Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)
  28. self._empty_container = self._container_registry.getEmptyInstanceContainer()
  29. self._backend = Application.getInstance().getBackend()
  30. def _onContainerAdded(self, container):
  31. # Add this action as a supported action to all machine definitions
  32. if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine":
  33. Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey())
  34. def _onContainerRemoved(self, container):
  35. # Remove definition_changes containers when a stack is removed
  36. if container.getMetaDataEntry("type") in ["machine", "extruder_train"]:
  37. definition_changes_container = container.definitionChanges
  38. if definition_changes_container == self._empty_container:
  39. return
  40. self._container_registry.removeContainer(definition_changes_container.getId())
  41. def _reset(self):
  42. if not self._global_container_stack:
  43. return
  44. # Make sure there is a definition_changes container to store the machine settings
  45. definition_changes_container = self._global_container_stack.definitionChanges
  46. if definition_changes_container == self._empty_container:
  47. definition_changes_container = CuraStackBuilder.createDefinitionChangesContainer(
  48. self._global_container_stack, self._global_container_stack.getName() + "_settings")
  49. # Notify the UI in which container to store the machine settings data
  50. from cura.Settings.CuraContainerStack import CuraContainerStack, _ContainerIndexes
  51. container_index = _ContainerIndexes.DefinitionChanges
  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 and self._backend.determineAutoSlicing():
  62. self._backend.tickle()
  63. containerIndexChanged = pyqtSignal()
  64. @pyqtProperty(int, notify = containerIndexChanged)
  65. def containerIndex(self):
  66. return self._container_index
  67. def _onGlobalContainerChanged(self):
  68. self._global_container_stack = Application.getInstance().getGlobalContainerStack()
  69. # This additional emit is needed because we cannot connect a UM.Signal directly to a pyqtSignal
  70. self.globalContainerChanged.emit()
  71. globalContainerChanged = pyqtSignal()
  72. @pyqtProperty(int, notify = globalContainerChanged)
  73. def definedExtruderCount(self):
  74. if not self._global_container_stack:
  75. return 0
  76. return len(self._global_container_stack.getMetaDataEntry("machine_extruder_trains"))
  77. @pyqtSlot(int)
  78. def setMachineExtruderCount(self, extruder_count):
  79. extruder_manager = Application.getInstance().getExtruderManager()
  80. definition_changes_container = self._global_container_stack.definitionChanges
  81. if not self._global_container_stack or definition_changes_container == self._empty_container:
  82. return
  83. previous_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
  84. if extruder_count == previous_extruder_count:
  85. return
  86. # reset all extruder number settings whose value is no longer valid
  87. for setting_instance in self._global_container_stack.userChanges.findInstances():
  88. setting_key = setting_instance.definition.key
  89. if not self._global_container_stack.getProperty(setting_key, "type") in ("extruder", "optional_extruder"):
  90. continue
  91. old_value = int(self._global_container_stack.userChanges.getProperty(setting_key, "value"))
  92. if old_value >= extruder_count:
  93. self._global_container_stack.userChanges.removeInstance(setting_key)
  94. Logger.log("d", "Reset [%s] because its old value [%s] is no longer valid ", setting_key, old_value)
  95. # Check to see if any objects are set to print with an extruder that will no longer exist
  96. root_node = Application.getInstance().getController().getScene().getRoot()
  97. for node in DepthFirstIterator(root_node):
  98. if node.getMeshData():
  99. extruder_nr = node.callDecoration("getActiveExtruderPosition")
  100. if extruder_nr is not None and int(extruder_nr) > extruder_count - 1:
  101. node.callDecoration("setActiveExtruder", extruder_manager.getExtruderStack(extruder_count - 1).getId())
  102. definition_changes_container.setProperty("machine_extruder_count", "value", extruder_count)
  103. # Make sure one of the extruder stacks is active
  104. extruder_manager.setActiveExtruderIndex(0)
  105. # Move settable_per_extruder values out of the global container
  106. # After CURA-4482 this should not be the case anymore, but we still want to support older project files.
  107. global_user_container = self._global_container_stack.getTop()
  108. if previous_extruder_count == 1:
  109. extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
  110. global_user_container = self._global_container_stack.getTop()
  111. for setting_instance in global_user_container.findInstances():
  112. setting_key = setting_instance.definition.key
  113. settable_per_extruder = self._global_container_stack.getProperty(setting_key, "settable_per_extruder")
  114. if settable_per_extruder:
  115. limit_to_extruder = int(self._global_container_stack.getProperty(setting_key, "limit_to_extruder"))
  116. extruder_stack = extruder_stacks[max(0, limit_to_extruder)]
  117. extruder_stack.getTop().setProperty(setting_key, "value", global_user_container.getProperty(setting_key, "value"))
  118. global_user_container.removeInstance(setting_key)
  119. self.forceUpdate()
  120. @pyqtSlot()
  121. def forceUpdate(self):
  122. # Force rebuilding the build volume by reloading the global container stack.
  123. # This is a bit of a hack, but it seems quick enough.
  124. Application.getInstance().globalContainerStackChanged.emit()
  125. @pyqtSlot()
  126. def updateHasMaterialsMetadata(self):
  127. # Updates the has_materials metadata flag after switching gcode flavor
  128. if not self._global_container_stack:
  129. return
  130. definition = self._global_container_stack.getBottom()
  131. if definition.getProperty("machine_gcode_flavor", "value") != "UltiGCode" or definition.getMetaDataEntry("has_materials", False):
  132. # In other words: only continue for the UM2 (extended), but not for the UM2+
  133. return
  134. stacks = ExtruderManager.getInstance().getExtruderStacks()
  135. has_materials = self._global_container_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode"
  136. if has_materials:
  137. if "has_materials" in self._global_container_stack.getMetaData():
  138. self._global_container_stack.setMetaDataEntry("has_materials", True)
  139. else:
  140. self._global_container_stack.addMetaDataEntry("has_materials", True)
  141. # Set the material container for each extruder to a sane default
  142. for stack in stacks:
  143. material_container = stack.material
  144. if material_container == self._empty_container:
  145. machine_approximate_diameter = str(round(self._global_container_stack.getProperty("material_diameter", "value")))
  146. search_criteria = { "type": "material", "definition": "fdmprinter", "id": self._global_container_stack.getMetaDataEntry("preferred_material"), "approximate_diameter": machine_approximate_diameter}
  147. materials = self._container_registry.findInstanceContainers(**search_criteria)
  148. if materials:
  149. stack.material = materials[0]
  150. else:
  151. # The metadata entry is stored in an ini, and ini files are parsed as strings only.
  152. # Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False.
  153. if "has_materials" in self._global_container_stack.getMetaData():
  154. self._global_container_stack.removeMetaDataEntry("has_materials")
  155. for stack in stacks:
  156. stack.material = ContainerRegistry.getInstance().getEmptyInstanceContainer()
  157. Application.getInstance().globalContainerStackChanged.emit()
  158. @pyqtSlot(int)
  159. def updateMaterialForDiameter(self, extruder_position: int):
  160. # Updates the material container to a material that matches the material diameter set for the printer
  161. if not self._global_container_stack:
  162. return
  163. if not self._global_container_stack.getMetaDataEntry("has_materials", False):
  164. return
  165. extruder_stack = self._global_container_stack.extruders[str(extruder_position)]
  166. material_diameter = extruder_stack.material.getProperty("material_diameter", "value")
  167. if not material_diameter:
  168. # in case of "empty" material
  169. material_diameter = 0
  170. material_approximate_diameter = str(round(material_diameter))
  171. machine_diameter = extruder_stack.definitionChanges.getProperty("material_diameter", "value")
  172. if not machine_diameter:
  173. machine_diameter = extruder_stack.definition.getProperty("material_diameter", "value")
  174. machine_approximate_diameter = str(round(machine_diameter))
  175. if material_approximate_diameter != machine_approximate_diameter:
  176. Logger.log("i", "The the currently active material(s) do not match the diameter set for the printer. Finding alternatives.")
  177. if self._global_container_stack.getMetaDataEntry("has_machine_materials", False):
  178. materials_definition = self._global_container_stack.definition.getId()
  179. has_material_variants = self._global_container_stack.getMetaDataEntry("has_variants", False)
  180. else:
  181. materials_definition = "fdmprinter"
  182. has_material_variants = False
  183. old_material = extruder_stack.material
  184. search_criteria = {
  185. "type": "material",
  186. "approximate_diameter": machine_approximate_diameter,
  187. "material": old_material.getMetaDataEntry("material", "value"),
  188. "brand": old_material.getMetaDataEntry("brand", "value"),
  189. "supplier": old_material.getMetaDataEntry("supplier", "value"),
  190. "color_name": old_material.getMetaDataEntry("color_name", "value"),
  191. "definition": materials_definition
  192. }
  193. if has_material_variants:
  194. search_criteria["variant"] = extruder_stack.variant.getId()
  195. if old_material == self._empty_container:
  196. search_criteria.pop("material", None)
  197. search_criteria.pop("supplier", None)
  198. search_criteria.pop("brand", None)
  199. search_criteria.pop("definition", None)
  200. search_criteria["id"] = extruder_stack.getMetaDataEntry("preferred_material")
  201. materials = self._container_registry.findInstanceContainers(**search_criteria)
  202. if not materials:
  203. # Same material with new diameter is not found, search for generic version of the same material type
  204. search_criteria.pop("supplier", None)
  205. search_criteria.pop("brand", None)
  206. search_criteria["color_name"] = "Generic"
  207. materials = self._container_registry.findInstanceContainers(**search_criteria)
  208. if not materials:
  209. # Generic material with new diameter is not found, search for preferred material
  210. search_criteria.pop("color_name", None)
  211. search_criteria.pop("material", None)
  212. search_criteria["id"] = extruder_stack.getMetaDataEntry("preferred_material")
  213. materials = self._container_registry.findInstanceContainers(**search_criteria)
  214. if not materials:
  215. # Preferred material with new diameter is not found, search for any material
  216. search_criteria.pop("id", None)
  217. materials = self._container_registry.findInstanceContainers(**search_criteria)
  218. if not materials:
  219. # Just use empty material as a final fallback
  220. materials = [self._empty_container]
  221. Logger.log("i", "Selecting new material: %s", materials[0].getId())
  222. extruder_stack.material = materials[0]