# Copyright (c) 2016 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. from PyQt5.QtCore import pyqtProperty, pyqtSignal from UM.FlameProfiler import pyqtSlot from cura.MachineAction import MachineAction from UM.Application import Application from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.DefinitionContainer import DefinitionContainer from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Logger import Logger from cura.Settings.CuraContainerRegistry import CuraContainerRegistry from cura.Settings.ExtruderManager import ExtruderManager import UM.i18n catalog = UM.i18n.i18nCatalog("cura") ## This action allows for certain settings that are "machine only") to be modified. # It automatically detects machine definitions that it knows how to change and attaches itself to those. class MachineSettingsAction(MachineAction): def __init__(self, parent = None): super().__init__("MachineSettingsAction", catalog.i18nc("@action", "Machine Settings")) self._qml_url = "MachineSettingsAction.qml" self._global_container_stack = None self._container_index = 0 self._extruder_container_index = 0 self._container_registry = ContainerRegistry.getInstance() self._container_registry.containerAdded.connect(self._onContainerAdded) Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged) ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged) self._backend = Application.getInstance().getBackend() def _reset(self): if not self._global_container_stack: return # Make sure there is a definition_changes container to store the machine settings definition_changes_container = self._global_container_stack.findContainer({"type": "definition_changes"}) if not definition_changes_container: definition_changes_container = self._createDefinitionChangesContainer(self._global_container_stack, self._global_container_stack.getName() + "_settings") # Notify the UI in which container to store the machine settings data container_index = self._global_container_stack.getContainerIndex(definition_changes_container) if container_index != self._container_index: self._container_index = container_index self.containerIndexChanged.emit() # Disable autoslicing while the machineaction is showing self._backend.disableTimer() @pyqtSlot() def onFinishAction(self): # Restore autoslicing when the machineaction is dismissed if self._backend.determineAutoSlicing(): self._backend.tickle() def _onActiveExtruderStackChanged(self): extruder_container_stack = ExtruderManager.getInstance().getActiveExtruderStack() if not self._global_container_stack or not extruder_container_stack: return # Make sure there is a definition_changes container to store the machine settings definition_changes_container = extruder_container_stack.findContainer({"type": "definition_changes"}) if not definition_changes_container: definition_changes_container = self._createDefinitionChangesContainer(extruder_container_stack, extruder_container_stack.getId() + "_settings") # Notify the UI in which container to store the machine settings data container_index = extruder_container_stack.getContainerIndex(definition_changes_container) if container_index != self._extruder_container_index: self._extruder_container_index = container_index self.extruderContainerIndexChanged.emit() def _createDefinitionChangesContainer(self, container_stack, container_name, container_index = None): definition_changes_container = InstanceContainer(container_name) definition = container_stack.getBottom() definition_changes_container.setDefinition(definition) definition_changes_container.addMetaDataEntry("type", "definition_changes") self._container_registry.addContainer(definition_changes_container) # Insert definition_changes between the definition and the variant container_stack.insertContainer(-1, definition_changes_container) return definition_changes_container containerIndexChanged = pyqtSignal() @pyqtProperty(int, notify = containerIndexChanged) def containerIndex(self): return self._container_index extruderContainerIndexChanged = pyqtSignal() @pyqtProperty(int, notify = extruderContainerIndexChanged) def extruderContainerIndex(self): return self._extruder_container_index def _onContainerAdded(self, container): # Add this action as a supported action to all machine definitions if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine": Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey()) def _onGlobalContainerChanged(self): self._global_container_stack = Application.getInstance().getGlobalContainerStack() # This additional emit is needed because we cannot connect a UM.Signal directly to a pyqtSignal self.globalContainerChanged.emit() globalContainerChanged = pyqtSignal() @pyqtProperty(int, notify = globalContainerChanged) def definedExtruderCount(self): if not self._global_container_stack: return 0 return len(self._global_container_stack.getMetaDataEntry("machine_extruder_trains")) @pyqtSlot(int) def setMachineExtruderCount(self, extruder_count): machine_manager = Application.getInstance().getMachineManager() extruder_manager = ExtruderManager.getInstance() definition_changes_container = self._global_container_stack.findContainer({"type": "definition_changes"}) if not self._global_container_stack or not definition_changes_container: return if extruder_count == self._global_container_stack.getProperty("machine_extruder_count", "value"): return extruder_material_id = None extruder_variant_id = None if extruder_count == 1: # Get the material and variant of the first extruder before setting the number extruders to 1 if machine_manager.hasMaterials: extruder_material_id = machine_manager.allActiveMaterialIds[extruder_manager.extruderIds["0"]] if machine_manager.hasVariants: extruder_variant_id = machine_manager.activeVariantIds[0] # Check to see if any features are set to print with an extruder that will no longer exist for setting_name in ["adhesion_extruder_nr", "support_extruder_nr", "support_extruder_nr_layer_0", "support_infill_extruder_nr", "support_interface_extruder_nr"]: if int(self._global_container_stack.getProperty(setting_name, "value")) > extruder_count -1: Logger.log("i", "Lowering %s setting to match number of extruders", setting_name) self._global_container_stack.getTop().setProperty(setting_name, "value", extruder_count -1) # Check to see if any objects are set to print with an extruder that will no longer exist root_node = Application.getInstance().getController().getScene().getRoot() for node in DepthFirstIterator(root_node): if node.getMeshData(): extruder_nr = node.callDecoration("getActiveExtruderPosition") if extruder_nr is not None and extruder_nr > extruder_count - 1: node.callDecoration("setActiveExtruder", extruder_manager.getExtruderStack(extruder_count -1).getId()) definition_changes_container.setProperty("machine_extruder_count", "value", extruder_count) self.forceUpdate() if extruder_count > 1: # multiextrusion; make sure one of these extruder stacks is active if extruder_manager.activeExtruderIndex == -1: extruder_manager.setActiveExtruderIndex(0) else: # single extrusion; make sure the machine stack is active if extruder_manager.activeExtruderIndex > -1: extruder_manager.setActiveExtruderIndex(-1); # Restore material and variant on global stack # MachineManager._onGlobalContainerChanged removes the global material and vaiant of multiextruder machines if extruder_material_id: machine_manager.setActiveMaterial(extruder_material_id); if extruder_variant_id: machine_manager.setActiveVariant(extruder_variant_id); @pyqtSlot() def forceUpdate(self): # Force rebuilding the build volume by reloading the global container stack. # This is a bit of a hack, but it seems quick enough. Application.getInstance().globalContainerStackChanged.emit() @pyqtSlot() def updateHasMaterialsMetadata(self): # Updates the has_materials metadata flag after switching gcode flavor if not self._global_container_stack: return definition = self._global_container_stack.getBottom() if definition.getProperty("machine_gcode_flavor", "value") == "UltiGCode" and not definition.getMetaDataEntry("has_materials", False): has_materials = self._global_container_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode" material_container = self._global_container_stack.findContainer({"type": "material"}) material_index = self._global_container_stack.getContainerIndex(material_container) if has_materials: if "has_materials" in self._global_container_stack.getMetaData(): self._global_container_stack.setMetaDataEntry("has_materials", True) else: self._global_container_stack.addMetaDataEntry("has_materials", True) # Set the material container to a sane default if material_container.getId() == "empty_material": search_criteria = { "type": "material", "definition": "fdmprinter", "id": "*pla*" } containers = self._container_registry.findInstanceContainers(**search_criteria) if containers: self._global_container_stack.replaceContainer(material_index, containers[0]) else: # The metadata entry is stored in an ini, and ini files are parsed as strings only. # Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False. if "has_materials" in self._global_container_stack.getMetaData(): self._global_container_stack.removeMetaDataEntry("has_materials") empty_material = self._container_registry.findInstanceContainers(id = "empty_material")[0] self._global_container_stack.replaceContainer(material_index, empty_material) Application.getInstance().globalContainerStackChanged.emit()