1# Copyright (c) 2019 Ultimaker B.V. 2# Cura is released under the terms of the LGPLv3 or higher. 3 4from typing import Optional, TYPE_CHECKING 5 6from PyQt5.QtCore import pyqtProperty 7 8import UM.i18n 9from UM.FlameProfiler import pyqtSlot 10from UM.Settings.ContainerRegistry import ContainerRegistry 11from UM.Settings.DefinitionContainer import DefinitionContainer 12from UM.Util import parseBool 13 14import cura.CuraApplication # Imported like this to prevent circular dependencies. 15from cura.MachineAction import MachineAction 16from cura.Machines.ContainerTree import ContainerTree # To re-build the machine node when hasMaterials changes. 17from cura.Settings.CuraStackBuilder import CuraStackBuilder 18from cura.Settings.cura_empty_instance_containers import isEmptyContainer 19 20if TYPE_CHECKING: 21 from PyQt5.QtCore import QObject 22 23catalog = UM.i18n.i18nCatalog("cura") 24 25 26class MachineSettingsAction(MachineAction): 27 """This action allows for certain settings that are "machine only") to be modified. 28 29 It automatically detects machine definitions that it knows how to change and attaches itself to those. 30 """ 31 def __init__(self, parent: Optional["QObject"] = None) -> None: 32 super().__init__("MachineSettingsAction", catalog.i18nc("@action", "Machine Settings")) 33 self._qml_url = "MachineSettingsAction.qml" 34 35 from cura.CuraApplication import CuraApplication 36 self._application = CuraApplication.getInstance() 37 38 from cura.Settings.CuraContainerStack import _ContainerIndexes 39 self._store_container_index = _ContainerIndexes.DefinitionChanges 40 41 self._container_registry = ContainerRegistry.getInstance() 42 self._container_registry.containerAdded.connect(self._onContainerAdded) 43 44 # The machine settings dialog blocks auto-slicing when it's shown, and re-enables it when it's finished. 45 self._backend = self._application.getBackend() 46 self.onFinished.connect(self._onFinished) 47 48 # If the g-code flavour changes between UltiGCode and another flavour, we need to update the container tree. 49 self._application.globalContainerStackChanged.connect(self._updateHasMaterialsInContainerTree) 50 51 # Which container index in a stack to store machine setting changes. 52 @pyqtProperty(int, constant = True) 53 def storeContainerIndex(self) -> int: 54 return self._store_container_index 55 56 def _onContainerAdded(self, container): 57 # Add this action as a supported action to all machine definitions 58 if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine": 59 self._application.getMachineActionManager().addSupportedAction(container.getId(), self.getKey()) 60 61 def _updateHasMaterialsInContainerTree(self) -> None: 62 """Triggered when the global container stack changes or when the g-code 63 64 flavour setting is changed. 65 """ 66 global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() 67 if global_stack is None: 68 return 69 machine_node = ContainerTree.getInstance().machines[global_stack.definition.getId()] 70 71 if machine_node.has_materials != parseBool(global_stack.getMetaDataEntry("has_materials")): # May have changed due to the g-code flavour. 72 machine_node.has_materials = parseBool(global_stack.getMetaDataEntry("has_materials")) 73 machine_node._loadAll() 74 75 def _reset(self): 76 global_stack = self._application.getMachineManager().activeMachine 77 if not global_stack: 78 return 79 80 # Make sure there is a definition_changes container to store the machine settings 81 definition_changes_id = global_stack.definitionChanges.getId() 82 if isEmptyContainer(definition_changes_id): 83 CuraStackBuilder.createDefinitionChangesContainer(global_stack, 84 global_stack.getName() + "_settings") 85 86 # Disable auto-slicing while the MachineAction is showing 87 if self._backend: # This sometimes triggers before backend is loaded. 88 self._backend.disableTimer() 89 90 def _onFinished(self): 91 # Restore auto-slicing when the machine action is dismissed 92 if self._backend and self._backend.determineAutoSlicing(): 93 self._backend.enableTimer() 94 self._backend.tickle() 95 96 @pyqtSlot(int) 97 def setMachineExtruderCount(self, extruder_count: int) -> None: 98 # Note: this method was in this class before, but since it's quite generic and other plugins also need it 99 # it was moved to the machine manager instead. Now this method just calls the machine manager. 100 self._application.getMachineManager().setActiveMachineExtruderCount(extruder_count) 101 102 @pyqtSlot() 103 def forceUpdate(self) -> None: 104 # Force rebuilding the build volume by reloading the global container stack. 105 # This is a bit of a hack, but it seems quick enough. 106 self._application.getMachineManager().globalContainerChanged.emit() 107 self._application.getMachineManager().forceUpdateAllSettings() 108 109 @pyqtSlot() 110 def updateHasMaterialsMetadata(self) -> None: 111 global_stack = self._application.getMachineManager().activeMachine 112 113 # Updates the has_materials metadata flag after switching gcode flavor 114 if not global_stack: 115 return 116 117 definition = global_stack.getDefinition() 118 if definition.getProperty("machine_gcode_flavor", "value") != "UltiGCode" or parseBool(definition.getMetaDataEntry("has_materials", False)): 119 # In other words: only continue for the UM2 (extended), but not for the UM2+ 120 return 121 122 machine_manager = self._application.getMachineManager() 123 has_materials = global_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode" 124 125 if has_materials: 126 global_stack.setMetaDataEntry("has_materials", True) 127 else: 128 # The metadata entry is stored in an ini, and ini files are parsed as strings only. 129 # Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False. 130 if "has_materials" in global_stack.getMetaData(): 131 global_stack.removeMetaDataEntry("has_materials") 132 133 self._updateHasMaterialsInContainerTree() 134 135 # set materials 136 machine_node = ContainerTree.getInstance().machines[global_stack.definition.getId()] 137 for position, extruder in enumerate(global_stack.extruderList): 138 #Find out what material we need to default to. 139 approximate_diameter = round(extruder.getProperty("material_diameter", "value")) 140 material_node = machine_node.variants[extruder.variant.getName()].preferredMaterial(approximate_diameter) 141 machine_manager.setMaterial(str(position), material_node) 142 143 self._application.globalContainerStackChanged.emit() 144 145 @pyqtSlot(int) 146 def updateMaterialForDiameter(self, extruder_position: int) -> None: 147 # Updates the material container to a material that matches the material diameter set for the printer 148 self._application.getMachineManager().updateMaterialWithVariant(str(extruder_position)) 149