Source code for being.params

"""Parameter blocks can be used to control values inside the block network.
Their value are mirrored in a dedicated config file. All Parameter blocks appear
in the web UI and can be tweaked by the end user.

Example:
    >>> # !This will create / add to the config file in your current working directory!
    >>> slider = Slider('some/value', default=0.0, minValue=0.0, maxValue=10.0)
    ... slider.change(15.0)  # Will get clipped to `maxValue`
    ... print(slider.output.value)
    10.0
"""
from typing import Any, Optional, Iterable, List

from being.block import Block
from being.configs import ConfigFile, split_name
from being.content import Content
from being.configuration import CONFIG
from being.math import clip
from being.utils import SingleInstanceCache, unique


PARAMETER_CONFIG_FILEPATH = CONFIG['General']['PARAMETER_CONFIG_FILEPATH']


[docs]class ParamsConfigFile(ConfigFile, SingleInstanceCache): """Slim :class:`being.configs.ConfigFile` subclass so that we can use it with :class:`being.utils.SingleInstanceCache`. """ def __init__(self, filepath=PARAMETER_CONFIG_FILEPATH): super().__init__(filepath)
[docs]class Parameter(Block): """Parameter block base class.""" def __init__(self, fullname: str, configFile: Optional[ConfigFile] = None): """ Args: fullname: Full name of config entry. configFile: Configuration file instance (DI). Default is :attr:`being.params.ParamsConfigFile` instance. """ _, name = split_name(fullname) if configFile is None: configFile = ParamsConfigFile.single_instance_setdefault() super().__init__(name=name) self.add_value_output() self.fullname: str = fullname """Full name of config entry.""" self.configFile: ConfigFile = configFile """Configuration file instance."""
[docs] def validate(self, value: Any) -> Any: """Validate value. Pass-through / no validation by default. Args: value: Value to validate. Returns: Validated value. """ return value
[docs] def load(self): """Load value from config file.""" value = self.configFile.retrieve(self.fullname) validated = self.validate(value) if validated != value: self.configFile.store(self.fullname, validated) self.configFile.save() self.output.value = validated
[docs] def loaddefault(self, default): """Load value from Configuration file with default value (similar :meth:`dict.setdefault`). Args: default: Default value. """ validated = self.validate(default) self.configFile.storedefault(self.fullname, validated) self.load()
[docs] def change(self, value: Any): """Change value and store it to the config file. Args: value: New value to set. """ validated = self.validate(value) self.configFile.store(self.fullname, validated) self.configFile.save() self.output.value = validated
[docs] def to_dict(self): dct = super().to_dict() dct['fullname'] = self.fullname dct['value'] = self.output.value return dct
def __str__(self): return f'{type(self).__name__}({self.fullname!r}, value: {self.output.value}, {self.configFile})'
[docs]class Slider(Parameter): """Scalar value slider.""" def __init__(self, fullname: str, default: Any = 0.0, minValue: float = 0., maxValue: float = 1., **kwargs, ): """ Args: fullname: Full name of config entry. default (optional): Default value. minValue (optional): Minimum value. maxValue (optional): Maximum value. **kwargs: Arbitrary Parameter block keyword arguments. """ if maxValue < minValue: raise ValueError super().__init__(fullname, **kwargs) self.minValue = min(minValue, default) self.maxValue = max(maxValue, default) self.loaddefault(default)
[docs] def validate(self, value): return clip(value, self.minValue, self.maxValue)
[docs] def to_dict(self): dct = super().to_dict() dct['minValue'] = self.minValue dct['maxValue'] = self.maxValue return dct
[docs]class SingleSelection(Parameter): """Single selection out of multiple possibilities.""" def __init__(self, fullname: str, possibilities: Iterable, default: Optional[Any] = None, **kwargs, ): """ Args: fullname: Full name of config entry. possibilities: All possibilities to choose from. default (optional): Default value. First entry of `possibilities` by default. **kwargs: Arbitrary Parameter block keyword arguments. """ super().__init__(fullname, **kwargs) self.possibilities = list(unique(possibilities)) if default is None: default = self.possibilities[0] self.loaddefault(default)
[docs] def validate(self, value): assert value in self.possibilities, f'{value} not in {self.possibilities}' return value
[docs] def to_dict(self): dct = super().to_dict() dct['possibilities'] = self.possibilities return dct
[docs]class MultiSelection(Parameter): """Multiple selection out of multiple possibilities.""" def __init__(self, fullname: str, possibilities: Iterable, default: Optional[List[Any]] = None, **kwargs, ): """ Args: fullname: Full name of config entry. possibilities: All possibilities to choose from. default (optional): Default value(s). List of elements from `possibilities`. Nothing selected by default. **kwargs: Arbitrary Parameter block keyword arguments. """ super().__init__(fullname, **kwargs) if default is None: default = [] self.possibilities = list(unique(possibilities)) self.loaddefault(default)
[docs] def validate(self, value): return list(set(self.possibilities).intersection(value))
[docs] def to_dict(self): dct = super().to_dict() dct['possibilities'] = self.possibilities return dct
[docs]class MotionSelection(MultiSelection): """Multiple motion selection. Similar to :class:`being.params.MultiSelection` but works with motions from :class:`being.content.Content` and can be updated. """ def __init__(self, fullname: str, default: Optional[List[str]] = None, content: Optional[Content] = None, **kwargs, ): """ Args: fullname: Full name of config entry. default (optional): Default motions. List of motion names. Nothing selected by default. content (optional): Content instance (DI). **kwargs: Arbitrary Parameter block keyword arguments. """ if default is None: default = [] if content is None: content = Content.single_instance_setdefault() possibilities = content.list_curve_names() super().__init__(fullname, possibilities, **kwargs) self.content = content self.loaddefault(default)
[docs] def on_content_changed(self): """Callback function on content changed. Motion cleanup. Will reload possibilities from current motions. """ self.possibilities = self.content.list_curve_names() self.load()