diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 67bdd5805e..0b2940044f 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -4,7 +4,7 @@ import os import sys import time -from typing import cast, TYPE_CHECKING +from typing import cast, TYPE_CHECKING, Optional import numpy @@ -109,6 +109,7 @@ from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisi from cura.Settings.ContainerManager import ContainerManager from cura.Settings.SidebarCustomMenuItemsModel import SidebarCustomMenuItemsModel import cura.Settings.cura_empty_instance_containers +from cura.Settings.CuraFormulaFunctions import CuraFormulaFunctions from cura.ObjectsModel import ObjectsModel @@ -176,6 +177,8 @@ class CuraApplication(QtApplication): self._single_instance = None + self._cura_formula_functions = None # type: Optional[CuraFormulaFunctions] + self._cura_package_manager = None self._machine_action_manager = None @@ -325,6 +328,8 @@ class CuraApplication(QtApplication): # Adds custom property types, settings types, and extra operators (functions) that need to be registered in # SettingDefinition and SettingFunction. def __initializeSettingDefinitionsAndFunctions(self): + self._cura_formula_functions = CuraFormulaFunctions(self) + # Need to do this before ContainerRegistry tries to load the machines SettingDefinition.addSupportedProperty("settable_per_mesh", DefinitionPropertyType.Any, default = True, read_only = True) SettingDefinition.addSupportedProperty("settable_per_extruder", DefinitionPropertyType.Any, default = True, read_only = True) @@ -345,10 +350,10 @@ class CuraApplication(QtApplication): SettingDefinition.addSettingType("optional_extruder", None, str, None) SettingDefinition.addSettingType("[int]", None, str, None) - SettingFunction.registerOperator("extruderValues", ExtruderManager.getExtruderValues) - SettingFunction.registerOperator("extruderValue", ExtruderManager.getExtruderValue) - SettingFunction.registerOperator("resolveOrValue", ExtruderManager.getResolveOrValue) - SettingFunction.registerOperator("defaultExtruderPosition", ExtruderManager.getDefaultExtruderPosition) + SettingFunction.registerOperator("extruderValue", self._cura_formula_functions.getValueInExtruder) + SettingFunction.registerOperator("extruderValues", self._cura_formula_functions.getValuesInAllExtruders) + SettingFunction.registerOperator("resolveOrValue", self._cura_formula_functions.getResolveOrValue) + SettingFunction.registerOperator("defaultExtruderPosition", self._cura_formula_functions.getDefaultExtruderPosition) # Adds all resources and container related resources. def __addAllResourcesAndContainerResources(self) -> None: @@ -815,6 +820,11 @@ class CuraApplication(QtApplication): def getSettingVisibilityPresetsModel(self, *args) -> SettingVisibilityPresetsModel: return self._setting_visibility_presets_model + def getCuraFormulaFunctions(self, *args) -> "CuraFormulaFunctions": + if self._cura_formula_functions is None: + self._cura_formula_functions = CuraFormulaFunctions(self) + return self._cura_formula_functions + def getMachineErrorChecker(self, *args) -> MachineErrorChecker: return self._machine_error_checker diff --git a/cura/Settings/CuraFormulaFunctions.py b/cura/Settings/CuraFormulaFunctions.py new file mode 100644 index 0000000000..1db01857f8 --- /dev/null +++ b/cura/Settings/CuraFormulaFunctions.py @@ -0,0 +1,130 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from typing import Any, List, Optional, TYPE_CHECKING + +from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext +from UM.Settings.SettingFunction import SettingFunction + +if TYPE_CHECKING: + from cura.CuraApplication import CuraApplication + from cura.Settings.CuraContainerStack import CuraContainerStack + + +# +# This class contains all Cura-related custom functions that can be used in formulas. Some functions requires +# information such as the currently active machine, so this is made into a class instead of standalone functions. +# +class CuraFormulaFunctions: + + def __init__(self, application: "CuraApplication") -> None: + self._application = application + + # ================ + # Custom Functions + # ================ + + # Gets the default extruder position of the currently active machine. + def getDefaultExtruderPosition(self) -> str: + machine_manager = self._application.getMachineManager() + return machine_manager.defaultExtruderPosition + + # Gets the given setting key from the given extruder position. + def getValueInExtruder(self, extruder_position: int, property_key: str, + context: Optional["PropertyEvaluationContext"] = None) -> Any: + machine_manager = self._application.getMachineManager() + + if extruder_position == -1: + extruder_position = int(machine_manager.defaultExtruderPosition) + + global_stack = machine_manager.activeMachine + extruder_stack = global_stack.extruders[str(extruder_position)] + + value = extruder_stack.getRawProperty(property_key, "value", context = context) + if isinstance(value, SettingFunction): + value = value(extruder_stack, context = context) + + return value + + # Gets all extruder values as a list for the given property. + def getValuesInAllExtruders(self, property_key: str, + context: Optional["PropertyEvaluationContext"] = None) -> List[Any]: + machine_manager = self._application.getMachineManager() + extruder_manager = self._application.getExtruderManager() + + global_stack = machine_manager.activeMachine + + result = [] + for extruder in extruder_manager.getActiveExtruderStacks(): + if not extruder.isEnabled: + continue + # only include values from extruders that are "active" for the current machine instance + if int(extruder.getMetaDataEntry("position")) >= global_stack.getProperty("machine_extruder_count", "value", context = context): + continue + + value = extruder.getRawProperty(property_key, "value", context = context) + + if value is None: + continue + + if isinstance(value, SettingFunction): + value = value(extruder, context = context) + + result.append(value) + + if not result: + result.append(global_stack.getProperty(property_key, "value", context = context)) + + return result + + # Get the resolve value or value for a given key. + def getResolveOrValue(self, property_key: str, context: Optional["PropertyEvaluationContext"] = None) -> Any: + machine_manager = self._application.getMachineManager() + + global_stack = machine_manager.activeMachine + resolved_value = global_stack.getProperty(property_key, "value", context = context) + + return resolved_value + + # Gets the default setting value from given extruder position. The default value is what excludes the values in + # the user_changes container. + def getDefaultValueInExtruder(self, extruder_position: int, property_key: str) -> Any: + machine_manager = self._application.getMachineManager() + + global_stack = machine_manager.activeMachine + extruder_stack = global_stack.extruders[str(extruder_position)] + + context = self.createContextForDefaultValueEvaluation(extruder_stack) + + return self.getValueInExtruder(extruder_position, property_key, context = context) + + # Gets all default setting values as a list from all extruders of the currently active machine. + # The default values are those excluding the values in the user_changes container. + def getDefaultValuesInAllExtruders(self, property_key: str) -> List[Any]: + machine_manager = self._application.getMachineManager() + + global_stack = machine_manager.activeMachine + + context = self.createContextForDefaultValueEvaluation(global_stack) + + return self.getValuesInAllExtruders(property_key, context = context) + + # Gets the resolve value or value for a given key without looking the first container (user container). + def getDefaultResolveOrValue(self, property_key: str) -> Any: + machine_manager = self._application.getMachineManager() + + global_stack = machine_manager.activeMachine + + context = self.createContextForDefaultValueEvaluation(global_stack) + return self.getResolveOrValue(property_key, context = context) + + # Creates a context for evaluating default values (skip the user_changes container). + def createContextForDefaultValueEvaluation(self, source_stack: "CuraContainerStack") -> "PropertyEvaluationContext": + context = PropertyEvaluationContext(source_stack) + context.context["evaluate_from_container_index"] = 1 # skip the user settings container + context.context["override_operators"] = { + "extruderValue": self.getDefaultValueInExtruder, + "extruderValues": self.getDefaultValuesInAllExtruders, + "resolveOrValue": self.getDefaultResolveOrValue, + } + return context diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index c24dd83c54..2514e17075 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -12,9 +12,7 @@ from UM.Scene.SceneNode import SceneNode from UM.Scene.Selection import Selection from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator from UM.Settings.ContainerRegistry import ContainerRegistry # Finding containers by ID. -from UM.Settings.SettingFunction import SettingFunction from UM.Settings.ContainerStack import ContainerStack -from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext from typing import Any, cast, Dict, List, Optional, TYPE_CHECKING, Union @@ -69,16 +67,6 @@ class ExtruderManager(QObject): except KeyError: # Extruder index could be -1 if the global tab is selected, or the entry doesn't exist if the machine definition is wrong. return None - ## Return extruder count according to extruder trains. - @pyqtProperty(int, notify = extrudersChanged) - def extruderCount(self) -> int: - if not self._application.getGlobalContainerStack(): - return 0 # No active machine, so no extruders. - try: - return len(self._extruder_trains[self._application.getGlobalContainerStack().getId()]) - except KeyError: - return 0 - ## Gets a dict with the extruder stack ids with the extruder number as the key. @pyqtProperty("QVariantMap", notify = extrudersChanged) def extruderIds(self) -> Dict[str, str]: @@ -386,85 +374,7 @@ class ExtruderManager(QObject): extruder_definition = container_registry.findDefinitionContainers(id = expected_extruder_definition_0_id)[0] extruder_stack_0.definition = extruder_definition - ## Get all extruder values for a certain setting. - # - # This is exposed to SettingFunction so it can be used in value functions. - # - # \param key The key of the setting to retrieve values for. - # - # \return A list of values for all extruders. If an extruder does not have a value, it will not be in the list. - # If no extruder has the value, the list will contain the global value. - @staticmethod - def getExtruderValues(key: str) -> List[Any]: - global_stack = cast(GlobalStack, cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()) #We know that there must be a global stack by the time you're requesting setting values. - - result = [] - for extruder in ExtruderManager.getInstance().getActiveExtruderStacks(): - if not extruder.isEnabled: - continue - # only include values from extruders that are "active" for the current machine instance - if int(extruder.getMetaDataEntry("position")) >= global_stack.getProperty("machine_extruder_count", "value"): - continue - - value = extruder.getRawProperty(key, "value") - - if value is None: - continue - - if isinstance(value, SettingFunction): - value = value(extruder) - - result.append(value) - - if not result: - result.append(global_stack.getProperty(key, "value")) - - return result - - ## Get all extruder values for a certain setting. This function will skip the user settings container. - # - # This is exposed to SettingFunction so it can be used in value functions. - # - # \param key The key of the setting to retrieve values for. - # - # \return A list of values for all extruders. If an extruder does not have a value, it will not be in the list. - # If no extruder has the value, the list will contain the global value. - @staticmethod - def getDefaultExtruderValues(key: str) -> List[Any]: - global_stack = cast(GlobalStack, cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()) #We know that there must be a global stack by the time you're requesting setting values. - context = PropertyEvaluationContext(global_stack) - context.context["evaluate_from_container_index"] = 1 # skip the user settings container - context.context["override_operators"] = { - "extruderValue": ExtruderManager.getDefaultExtruderValue, - "extruderValues": ExtruderManager.getDefaultExtruderValues, - "resolveOrValue": ExtruderManager.getDefaultResolveOrValue - } - - result = [] - for extruder in ExtruderManager.getInstance().getActiveExtruderStacks(): - # only include values from extruders that are "active" for the current machine instance - if int(extruder.getMetaDataEntry("position")) >= global_stack.getProperty("machine_extruder_count", "value", context = context): - continue - - value = extruder.getRawProperty(key, "value", context = context) - - if value is None: - continue - - if isinstance(value, SettingFunction): - value = value(extruder, context = context) - - result.append(value) - - if not result: - result.append(global_stack.getProperty(key, "value", context = context)) - - return result - - ## Return the default extruder position from the machine manager - @staticmethod - def getDefaultExtruderPosition() -> str: - return cura.CuraApplication.CuraApplication.getInstance().getMachineManager().defaultExtruderPosition + extruder_stack_0.setNextStack(global_stack) ## Get all extruder values for a certain setting. # @@ -474,62 +384,8 @@ class ExtruderManager(QObject): # # \return String representing the extruder values @pyqtSlot(str, result="QVariant") - def getInstanceExtruderValues(self, key) -> List: - return ExtruderManager.getExtruderValues(key) - - ## Get the value for a setting from a specific extruder. - # - # This is exposed to SettingFunction to use in value functions. - # - # \param extruder_index The index of the extruder to get the value from. - # \param key The key of the setting to get the value of. - # - # \return The value of the setting for the specified extruder or for the - # global stack if not found. - @staticmethod - def getExtruderValue(extruder_index: int, key: str) -> Any: - if extruder_index == -1: - extruder_index = int(cura.CuraApplication.CuraApplication.getInstance().getMachineManager().defaultExtruderPosition) - extruder = ExtruderManager.getInstance().getExtruderStack(extruder_index) - - if extruder: - value = extruder.getRawProperty(key, "value") - if isinstance(value, SettingFunction): - value = value(extruder) - else: - # Just a value from global. - value = cast(GlobalStack, cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()).getProperty(key, "value") - - return value - - ## Get the default value from the given extruder. This function will skip the user settings container. - # - # This is exposed to SettingFunction to use in value functions. - # - # \param extruder_index The index of the extruder to get the value from. - # \param key The key of the setting to get the value of. - # - # \return The value of the setting for the specified extruder or for the - # global stack if not found. - @staticmethod - def getDefaultExtruderValue(extruder_index: int, key: str) -> Any: - extruder = ExtruderManager.getInstance().getExtruderStack(extruder_index) - context = PropertyEvaluationContext(extruder) - context.context["evaluate_from_container_index"] = 1 # skip the user settings container - context.context["override_operators"] = { - "extruderValue": ExtruderManager.getDefaultExtruderValue, - "extruderValues": ExtruderManager.getDefaultExtruderValues, - "resolveOrValue": ExtruderManager.getDefaultResolveOrValue - } - - if extruder: - value = extruder.getRawProperty(key, "value", context = context) - if isinstance(value, SettingFunction): - value = value(extruder, context = context) - else: # Just a value from global. - value = cast(GlobalStack, cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()).getProperty(key, "value", context = context) - - return value + def getInstanceExtruderValues(self, key: str) -> List: + return self._application.getCuraFormulaFunctions().getValuesInAllExtruders(key) ## Get the resolve value or value for a given key # @@ -545,28 +401,6 @@ class ExtruderManager(QObject): return resolved_value - ## Get the resolve value or value for a given key without looking the first container (user container) - # - # This is the effective value for a given key, it is used for values in the global stack. - # This is exposed to SettingFunction to use in value functions. - # \param key The key of the setting to get the value of. - # - # \return The effective value - @staticmethod - def getDefaultResolveOrValue(key: str) -> Any: - global_stack = cast(GlobalStack, cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()) - context = PropertyEvaluationContext(global_stack) - context.context["evaluate_from_container_index"] = 1 # skip the user settings container - context.context["override_operators"] = { - "extruderValue": ExtruderManager.getDefaultExtruderValue, - "extruderValues": ExtruderManager.getDefaultExtruderValues, - "resolveOrValue": ExtruderManager.getDefaultResolveOrValue - } - - resolved_value = global_stack.getProperty(key, "value", context = context) - - return resolved_value - __instance = None # type: ExtruderManager @classmethod diff --git a/cura/Settings/UserChangesModel.py b/cura/Settings/UserChangesModel.py index 95674e5ecd..9a26e5607e 100644 --- a/cura/Settings/UserChangesModel.py +++ b/cura/Settings/UserChangesModel.py @@ -1,15 +1,17 @@ -from UM.Qt.ListModel import ListModel +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +import os +from collections import OrderedDict from PyQt5.QtCore import pyqtSlot, Qt + from UM.Application import Application -from cura.Settings.ExtruderManager import ExtruderManager from UM.Settings.ContainerRegistry import ContainerRegistry from UM.i18n import i18nCatalog from UM.Settings.SettingFunction import SettingFunction -from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext -from collections import OrderedDict -import os +from UM.Qt.ListModel import ListModel class UserChangesModel(ListModel): @@ -38,9 +40,13 @@ class UserChangesModel(ListModel): self._update() def _update(self): + application = Application.getInstance() + machine_manager = application.getMachineManager() + cura_formula_functions = application.getCuraFormulaFunctions() + item_dict = OrderedDict() item_list = [] - global_stack = Application.getInstance().getGlobalContainerStack() + global_stack = machine_manager.activeMachine if not global_stack: return @@ -71,13 +77,7 @@ class UserChangesModel(ListModel): # Override "getExtruderValue" with "getDefaultExtruderValue" so we can get the default values user_changes = containers.pop(0) - default_value_resolve_context = PropertyEvaluationContext(stack) - default_value_resolve_context.context["evaluate_from_container_index"] = 1 # skip the user settings container - default_value_resolve_context.context["override_operators"] = { - "extruderValue": ExtruderManager.getDefaultExtruderValue, - "extruderValues": ExtruderManager.getDefaultExtruderValues, - "resolveOrValue": ExtruderManager.getDefaultResolveOrValue - } + default_value_resolve_context = cura_formula_functions.createContextForDefaultValueEvaluation(stack) for setting_key in user_changes.getAllKeys(): original_value = None