# Copyright (c) 2016 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal import UM.Settings from UM.Application import Application import cura.Settings ## The settingInheritance manager is responsible for checking each setting in order to see if one of the "deeper" # containers has a setting function and the topmost one with a value has a value. We need to have this check # because some profiles tend to have 'hardcoded' values that break our inheritance. A good example of that are the # speed settings. If all the children of print_speed have a single value override, changing the speed won't # actually do anything, as only the 'leaf' settings are used by the engine. class SettingInheritanceManager(QObject): def __init__(self, parent = None): super().__init__(parent) Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged) self._global_container_stack = None self._settings_with_inheritance_warning = [] self._active_container_stack = None self._onGlobalContainerChanged() cura.Settings.ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderChanged) self._onActiveExtruderChanged() settingsWithIntheritanceChanged = pyqtSignal() ## Get the keys of all children settings with an override. @pyqtSlot(str, result = "QStringList") def getChildrenKeysWithOverride(self, key): definitions = self._global_container_stack.getBottom().findDefinitions(key=key) if not definitions: return result = [] for key in definitions[0].getAllKeys(): if key in self._settings_with_inheritance_warning: result.append(key) return result @pyqtSlot(str) def manualRemoveOverride(self, key): if key in self._settings_with_inheritance_warning: self._settings_with_inheritance_warning.remove(key) self.settingsWithIntheritanceChanged.emit() @pyqtSlot() def forceUpdate(self): self._update() def _onActiveExtruderChanged(self): new_active_stack = cura.Settings.ExtruderManager.getInstance().getActiveExtruderStack() if not new_active_stack: new_active_stack = self._global_container_stack if new_active_stack != self._active_container_stack: # Check if changed if self._active_container_stack: # Disconnect signal from old container (if any) self._active_container_stack.propertyChanged.disconnect(self._onPropertyChanged) self._active_container_stack = new_active_stack self._active_container_stack.propertyChanged.connect(self._onPropertyChanged) self._update() # Ensure that the settings_with_inheritance_warning list is populated. def _onPropertyChanged(self, key, property_name): if property_name == "value" and self._global_container_stack: definitions = self._global_container_stack.getBottom().findDefinitions(key = key) if not definitions: return has_overwritten_inheritance = self._settingIsOverwritingInheritance(key) settings_with_inheritance_warning_changed = False # Check if the setting needs to be in the list. if key not in self._settings_with_inheritance_warning and has_overwritten_inheritance: self._settings_with_inheritance_warning.append(key) settings_with_inheritance_warning_changed = True elif key in self._settings_with_inheritance_warning and not has_overwritten_inheritance: self._settings_with_inheritance_warning.remove(key) settings_with_inheritance_warning_changed = True # Find the topmost parent (Assumed to be a category) parent = definitions[0].parent while parent.parent is not None: parent = parent.parent if parent.key not in self._settings_with_inheritance_warning and has_overwritten_inheritance: # Category was not in the list yet, so needs to be added now. self._settings_with_inheritance_warning.append(parent.key) settings_with_inheritance_warning_changed = True elif parent.key in self._settings_with_inheritance_warning and not has_overwritten_inheritance: # Category was in the list and one of it's settings is not overwritten. if not self._recursiveCheck(parent): # Check if any of it's children have overwritten inheritance. self._settings_with_inheritance_warning.remove(parent.key) settings_with_inheritance_warning_changed = True # Emit the signal if there was any change to the list. if settings_with_inheritance_warning_changed: self.settingsWithIntheritanceChanged.emit() def _recursiveCheck(self, definition): for child in definition.children: if child.key in self._settings_with_inheritance_warning: return True if child.children: if self._recursiveCheck(child): return True return False @pyqtProperty("QVariantList", notify = settingsWithIntheritanceChanged) def settingsWithInheritanceWarning(self): return self._settings_with_inheritance_warning ## Check if a setting has an inheritance function that is overwritten def _settingIsOverwritingInheritance(self, key): has_setting_function = False stack = self._active_container_stack containers = [] ## Check if the setting has a user state. If not, it is never overwritten. has_user_state = self._active_container_stack.getProperty(key, "state") == UM.Settings.InstanceState.User if not has_user_state: return False ## If a setting is not enabled, don't label it as overwritten (It's never visible anyway). if not self._active_container_stack.getProperty(key, "enabled"): return False ## Also check if the top container is not a setting function (this happens if the inheritance is restored). if isinstance(self._active_container_stack.getTop().getProperty(key, "value"), UM.Settings.SettingFunction): return False ## Mash all containers for all the stacks together. while stack: containers.extend(stack.getContainers()) stack = stack.getNextStack() has_non_function_value = False for container in containers: try: value = container.getProperty(key, "value") if value is not None: has_setting_function = isinstance(value, UM.Settings.SettingFunction) if has_setting_function is False: has_non_function_value = True continue except AttributeError: continue if has_setting_function: break # There is a setting function somewhere, stop looking deeper. return has_setting_function and has_non_function_value def _update(self): self._settings_with_inheritance_warning = [] # Reset previous data. # Check all setting keys that we know of and see if they are overridden. for setting_key in self._global_container_stack.getAllKeys(): override = self._settingIsOverwritingInheritance(setting_key) if override: self._settings_with_inheritance_warning.append(setting_key) # Check all the categories if any of their children have their inheritance overwritten. for category in self._global_container_stack.getBottom().findDefinitions(type = "category"): if self._recursiveCheck(category): self._settings_with_inheritance_warning.append(category.key) # Notify others that things have changed. self.settingsWithIntheritanceChanged.emit() def _onGlobalContainerChanged(self): if self._global_container_stack: self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged) self._global_container_stack = Application.getInstance().getGlobalContainerStack() if self._global_container_stack: self._global_container_stack.propertyChanged.connect(self._onPropertyChanged) self._onActiveExtruderChanged() @staticmethod def createSettingInheritanceManager(engine=None, script_engine=None): return SettingInheritanceManager()