Skip to content

Commit

Permalink
Merge branch '2.7_stack_context' of github.com:Ultimaker/Uranium into…
Browse files Browse the repository at this point in the history
… 2.7
  • Loading branch information
nallath committed Aug 28, 2017
2 parents e4d3336 + 5e4e87f commit f9e1fec
Show file tree
Hide file tree
Showing 16 changed files with 66 additions and 19 deletions.
5 changes: 3 additions & 2 deletions UM/Backend/Backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class BackendState(IntEnum):
Error = 4
Disabled = 5


## Base class for any backend communication (separate piece of software).
# It makes use of the Socket class from libArcus for the actual communication bits.
# The message_handlers dict should be filled with string (full name of proto message), function pairs.
Expand Down Expand Up @@ -71,7 +72,7 @@ def startEngine(self):
self._process = self._runEngineProcess(command)
if self._process is None: #Failed to start engine.
return
Logger.log("i", "Started engine process: %s" % (self.getEngineCommand()[0]))
Logger.log("i", "Started engine process: %s", self.getEngineCommand()[0])
self._backendLog(bytes("Calling engine with: %s\n" % self.getEngineCommand(), "utf-8"))
t = threading.Thread(target = self._storeOutputToLogThread, args = (self._process.stdout,))
t.daemon = True
Expand All @@ -80,7 +81,7 @@ def startEngine(self):
t.daemon = True
t.start()
except FileNotFoundError as e:
Logger.log("e", "Unable to find backend executable: %s" % (self.getEngineCommand()[0]))
Logger.log("e", "Unable to find backend executable: %s", self.getEngineCommand()[0])

def close(self):
if self._socket:
Expand Down
2 changes: 1 addition & 1 deletion UM/Settings/ContainerRegistry.py
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,7 @@ def isDirty(self) -> bool:
def isReadOnly(self) -> bool:
return True

def getProperty(self, key, property_name):
def getProperty(self, key, property_name, context = None):
return None

def setProperty(self, key, property_name, property_value, container = None, set_from_cache = False):
Expand Down
9 changes: 7 additions & 2 deletions UM/Settings/ContainerStack.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
from UM.Settings.DefinitionContainer import DefinitionContainer #For getting all definitions in this stack.
from UM.Settings.Interfaces import ContainerInterface, ContainerRegistryInterface
from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext
from UM.Settings.SettingFunction import SettingFunction
from UM.Settings.Validator import ValidatorState

Expand Down Expand Up @@ -194,10 +195,14 @@ def setDirty(self, dirty: bool) -> None:
# Note that if the property value is a function, this method will return the
# result of evaluating that property with the current stack. If you need the
# actual function, use getRawProperty()
def getProperty(self, key: str, property_name: str):
def getProperty(self, key: str, property_name: str, context: Optional[PropertyEvaluationContext] = None):
value = self.getRawProperty(key, property_name)
if isinstance(value, SettingFunction):
return value(self)
if context is not None:
context.pushContainer(self)
value = value(self, context)
if context is not None:
context.popContainer()

return value

Expand Down
2 changes: 1 addition & 1 deletion UM/Settings/DefinitionContainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ def getMetaDataEntry(self, entry, default = None):
## \copydoc ContainerInterface::getProperty
#
# Reimplemented from ContainerInterface.
def getProperty(self, key, property_name):
def getProperty(self, key, property_name, context = None):
definition = self._getDefinition(key)
if not definition:
return None
Expand Down
2 changes: 1 addition & 1 deletion UM/Settings/InstanceContainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ def setDirty(self, dirty):
## \copydoc ContainerInterface::getProperty
#
# Reimplemented from ContainerInterface
def getProperty(self, key, property_name):
def getProperty(self, key, property_name, context = None):
self._instantiateCachedValues()
if key in self._instances:
try:
Expand Down
3 changes: 2 additions & 1 deletion UM/Settings/Interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import UM.Decorators
from UM.Signal import Signal
from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext


## Shared interface between setting container types
Expand Down Expand Up @@ -62,7 +63,7 @@ def getMetaDataEntry(self, entry: str, default: Any = None) -> Any:
# \param name \type{string} The name of the property to retrieve.
#
# \return The specified property value of the container item corresponding to key, or None if not found.
def getProperty(self, key: str, property_name: str) -> Any:
def getProperty(self, key: str, property_name: str, context: Optional[PropertyEvaluationContext] = None) -> Any:
pass

## Get whether the container item has a specific property.
Expand Down
4 changes: 4 additions & 0 deletions UM/Settings/Models/SettingPropertyProvider.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,13 +275,17 @@ def _onPropertiesChanged(self, key, property_names):
self.isValueUsedChanged.emit()
return

has_values_changed = False
for property_name in property_names:
if property_name not in self._watched_properties:
continue

has_values_changed = True
self._property_map.insert(property_name, self._getPropertyValue(property_name))

self._updateStackLevels()
if has_values_changed:
self.propertiesChanged.emit()

def _update(self, container = None):
if not self._stack or not self._watched_properties or not self._key:
Expand Down
28 changes: 28 additions & 0 deletions UM/Settings/PropertyEvaluationContext.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Copyright (c) 2017 Ultimaker B.V.
# Uranium is released under the terms of the AGPLv3 or higher.

from collections import deque


## Context for evaluating a property value
# It contains:
# 1. a stack of containers during the evaluation in the function call stack fashion
# 2. a context dictionary which contains all the current context
#
class PropertyEvaluationContext:

def __init__(self, source_stack = None):
self.stack_of_containers = deque()
if source_stack is not None:
self.stack_of_containers.append(source_stack)
self.context = {}

def rootStack(self):
if self.stack_of_containers:
return self.stack_of_containers[0]

def pushContainer(self, container):
self.stack_of_containers.append(container)

def popContainer(self):
return self.stack_of_containers.pop()
10 changes: 7 additions & 3 deletions UM/Settings/SettingFunction.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@

import ast
import math # Imported here so it can be used easily by the setting functions.
from typing import Any, Dict, Callable, Set, FrozenSet, NamedTuple
from typing import Any, Dict, Callable, Set, FrozenSet, NamedTuple, Optional

from UM.Settings.Interfaces import ContainerInterface
from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext
from UM.Logger import Logger

MYPY = False
Expand Down Expand Up @@ -54,16 +55,19 @@ def __init__(self, code: str) -> None:
Logger.log("e", "Exception in function ({0}) for setting: {1}".format(str(e), self._code))

## Call the actual function to calculate the value.
def __call__(self, value_provider: ContainerInterface) -> Any:
def __call__(self, value_provider: ContainerInterface, context: Optional[PropertyEvaluationContext] = None) -> Any:
if not value_provider:
return None

if not self._valid:
return None

locals = {} # type: Dict[str, Any]
# if there is a context, evaluate the values from the perspective of the original caller
if context is not None:
value_provider = context.rootStack()
for name in self._used_values:
value = value_provider.getProperty(name, "value")
value = value_provider.getProperty(name, "value", context)
if value is None:
continue

Expand Down
8 changes: 6 additions & 2 deletions UM/Settings/Validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
# Uranium is released under the terms of the AGPLv3 or higher.

from enum import Enum
from typing import Any
from typing import Any, Optional

from UM.Settings.Interfaces import ContainerInterface
from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext
from UM.Logger import Logger

MYPY = False
Expand Down Expand Up @@ -39,7 +40,7 @@ def __init__(self, key: str) -> None:
self._key = key # type: str

## Perform the actual validation.
def __call__(self, value_provider: ContainerInterface) -> Any:
def __call__(self, value_provider: ContainerInterface, context: Optional[PropertyEvaluationContext] = None) -> Any:
if not value_provider:
return

Expand All @@ -53,6 +54,9 @@ def __call__(self, value_provider: ContainerInterface) -> Any:
if minimum is not None and maximum is not None and minimum > maximum:
raise ValueError("Cannot validate a state of setting {0} with minimum > maximum".format(self._key))

if context is not None:
value_provider = context.rootStack()

value = value_provider.getProperty(self._key, "value")
if value is None or value != value:
raise ValueError("Cannot validate None, NaN or similar values in setting {0}, actual value: {1}".format(self._key, value))
Expand Down
2 changes: 1 addition & 1 deletion tests/Settings/ContainerTestPlugin/ContainerTestPlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def isReadOnly(self):
# None.
#
# \return Always returns None.
def getProperty(self, key, property_name):
def getProperty(self, key, property_name, context = None):
pass

def hasProperty(self, key, property_name):
Expand Down
2 changes: 1 addition & 1 deletion tests/Settings/TestContainerRegistry.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def setPath(self, path):
## Gets the value of a property of a container item.
#
# This method is not implemented in the mock container.
def getProperty(self, key, property_name):
def getProperty(self, key, property_name, context = None):
raise NotImplementedError()

## Get the value of a container item.
Expand Down
2 changes: 1 addition & 1 deletion tests/Settings/TestContainerStack.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def getMetaDataEntry(self, entry, default = None):
# If the key doesn't exist, returns None.
#
# \param key The key of the item to get.
def getProperty(self, key, property_name):
def getProperty(self, key, property_name, context = None):
if key in self.items:
return self.items[key]
return None
Expand Down
2 changes: 1 addition & 1 deletion tests/Settings/TestSettingFunction.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def __init__(self):
## Provides a value.
#
# \param name The key of the value to provide.
def getProperty(self, key, property_name):
def getProperty(self, key, property_name, context = None):
if not (key in self._values):
return None
return self._values[key]
Expand Down
2 changes: 1 addition & 1 deletion tests/Settings/TestSettingInstance.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def __init__(self):

self._instances = []

def getProperty(self, key, property_name):
def getProperty(self, key, property_name, context = None):
for instance in self._instances:
if instance.definition.key == key:
try:
Expand Down
2 changes: 1 addition & 1 deletion tests/Settings/TestValidator.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def __init__(self, value):
self.maximum_value_warning = None
self.value = value

def getProperty(self, key, property_name):
def getProperty(self, key, property_name, context = None):
return getattr(self, property_name)

## Called before the first test function is executed.
Expand Down

0 comments on commit f9e1fec

Please sign in to comment.