SettingInheritanceManager.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. # Copyright (c) 2016 Ultimaker B.V.
  2. # Cura is released under the terms of the AGPLv3 or higher.
  3. from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
  4. import UM.Settings
  5. from UM.Application import Application
  6. import cura.Settings
  7. ## The settingInheritance manager is responsible for checking each setting in order to see if one of the "deeper"
  8. # containers has a setting function and the topmost one with a value has a value. We need to have this check
  9. # because some profiles tend to have 'hardcoded' values that break our inheritance. A good example of that are the
  10. # speed settings. If all the children of print_speed have a single value override, changing the speed won't
  11. # actually do anything, as only the 'leaf' settings are used by the engine.
  12. class SettingInheritanceManager(QObject):
  13. def __init__(self, parent = None):
  14. super().__init__(parent)
  15. Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)
  16. self._global_container_stack = None
  17. self._settings_with_inheritance_warning = []
  18. self._active_container_stack = None
  19. self._onGlobalContainerChanged()
  20. cura.Settings.ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderChanged)
  21. self._onActiveExtruderChanged()
  22. settingsWithIntheritanceChanged = pyqtSignal()
  23. ## Get the keys of all children settings with an override.
  24. @pyqtSlot(str, result = "QStringList")
  25. def getChildrenKeysWithOverride(self, key):
  26. definitions = self._global_container_stack.getBottom().findDefinitions(key=key)
  27. if not definitions:
  28. return
  29. result = []
  30. for key in definitions[0].getAllKeys():
  31. if key in self._settings_with_inheritance_warning:
  32. result.append(key)
  33. return result
  34. @pyqtSlot(str)
  35. def manualRemoveOverride(self, key):
  36. if key in self._settings_with_inheritance_warning:
  37. self._settings_with_inheritance_warning.remove(key)
  38. self.settingsWithIntheritanceChanged.emit()
  39. @pyqtSlot()
  40. def forceUpdate(self):
  41. self._update()
  42. def _onActiveExtruderChanged(self):
  43. new_active_stack = cura.Settings.ExtruderManager.getInstance().getActiveExtruderStack()
  44. if not new_active_stack:
  45. new_active_stack = self._global_container_stack
  46. if new_active_stack != self._active_container_stack: # Check if changed
  47. if self._active_container_stack: # Disconnect signal from old container (if any)
  48. self._active_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
  49. self._active_container_stack = new_active_stack
  50. self._active_container_stack.propertyChanged.connect(self._onPropertyChanged)
  51. self._update() # Ensure that the settings_with_inheritance_warning list is populated.
  52. def _onPropertyChanged(self, key, property_name):
  53. if property_name == "value" and self._global_container_stack:
  54. definitions = self._global_container_stack.getBottom().findDefinitions(key = key)
  55. if not definitions:
  56. return
  57. has_overwritten_inheritance = self._settingIsOverwritingInheritance(key)
  58. settings_with_inheritance_warning_changed = False
  59. # Check if the setting needs to be in the list.
  60. if key not in self._settings_with_inheritance_warning and has_overwritten_inheritance:
  61. self._settings_with_inheritance_warning.append(key)
  62. settings_with_inheritance_warning_changed = True
  63. elif key in self._settings_with_inheritance_warning and not has_overwritten_inheritance:
  64. self._settings_with_inheritance_warning.remove(key)
  65. settings_with_inheritance_warning_changed = True
  66. # Find the topmost parent (Assumed to be a category)
  67. parent = definitions[0].parent
  68. while parent.parent is not None:
  69. parent = parent.parent
  70. if parent.key not in self._settings_with_inheritance_warning and has_overwritten_inheritance:
  71. # Category was not in the list yet, so needs to be added now.
  72. self._settings_with_inheritance_warning.append(parent.key)
  73. settings_with_inheritance_warning_changed = True
  74. elif parent.key in self._settings_with_inheritance_warning and not has_overwritten_inheritance:
  75. # Category was in the list and one of it's settings is not overwritten.
  76. if not self._recursiveCheck(parent): # Check if any of it's children have overwritten inheritance.
  77. self._settings_with_inheritance_warning.remove(parent.key)
  78. settings_with_inheritance_warning_changed = True
  79. # Emit the signal if there was any change to the list.
  80. if settings_with_inheritance_warning_changed:
  81. self.settingsWithIntheritanceChanged.emit()
  82. def _recursiveCheck(self, definition):
  83. for child in definition.children:
  84. if child.key in self._settings_with_inheritance_warning:
  85. return True
  86. if child.children:
  87. if self._recursiveCheck(child):
  88. return True
  89. return False
  90. @pyqtProperty("QVariantList", notify = settingsWithIntheritanceChanged)
  91. def settingsWithInheritanceWarning(self):
  92. return self._settings_with_inheritance_warning
  93. ## Check if a setting has an inheritance function that is overwritten
  94. def _settingIsOverwritingInheritance(self, key):
  95. has_setting_function = False
  96. stack = self._active_container_stack
  97. containers = []
  98. ## Check if the setting has a user state. If not, it is never overwritten.
  99. has_user_state = self._active_container_stack.getProperty(key, "state") == UM.Settings.InstanceState.User
  100. if not has_user_state:
  101. return False
  102. ## If a setting is not enabled, don't label it as overwritten (It's never visible anyway).
  103. if not self._active_container_stack.getProperty(key, "enabled"):
  104. return False
  105. ## Also check if the top container is not a setting function (this happens if the inheritance is restored).
  106. if isinstance(self._active_container_stack.getTop().getProperty(key, "value"), UM.Settings.SettingFunction):
  107. return False
  108. ## Mash all containers for all the stacks together.
  109. while stack:
  110. containers.extend(stack.getContainers())
  111. stack = stack.getNextStack()
  112. has_non_function_value = False
  113. for container in containers:
  114. try:
  115. value = container.getProperty(key, "value")
  116. if value is not None:
  117. has_setting_function = isinstance(value, UM.Settings.SettingFunction)
  118. if has_setting_function is False:
  119. has_non_function_value = True
  120. continue
  121. except AttributeError:
  122. continue
  123. if has_setting_function:
  124. break # There is a setting function somewhere, stop looking deeper.
  125. return has_setting_function and has_non_function_value
  126. def _update(self):
  127. self._settings_with_inheritance_warning = [] # Reset previous data.
  128. # Check all setting keys that we know of and see if they are overridden.
  129. for setting_key in self._global_container_stack.getAllKeys():
  130. override = self._settingIsOverwritingInheritance(setting_key)
  131. if override:
  132. self._settings_with_inheritance_warning.append(setting_key)
  133. # Check all the categories if any of their children have their inheritance overwritten.
  134. for category in self._global_container_stack.getBottom().findDefinitions(type = "category"):
  135. if self._recursiveCheck(category):
  136. self._settings_with_inheritance_warning.append(category.key)
  137. # Notify others that things have changed.
  138. self.settingsWithIntheritanceChanged.emit()
  139. def _onGlobalContainerChanged(self):
  140. if self._global_container_stack:
  141. self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
  142. self._global_container_stack.containersChanged.disconnect(self._onContainersChanged)
  143. self._global_container_stack = Application.getInstance().getGlobalContainerStack()
  144. if self._global_container_stack:
  145. self._global_container_stack.containersChanged.connect(self._onContainersChanged)
  146. self._global_container_stack.propertyChanged.connect(self._onPropertyChanged)
  147. self._onActiveExtruderChanged()
  148. def _onContainersChanged(self, container):
  149. self._onActiveExtruderChanged()
  150. @staticmethod
  151. def createSettingInheritanceManager(engine=None, script_engine=None):
  152. return SettingInheritanceManager()