Source code for honeybee_radiance_command.options.optionbase

"""Base classes for Radiance Options."""
import honeybee_radiance_command._typing as typing
import honeybee_radiance_command.cutil as cutil
import honeybee_radiance_command._exception as exceptions
import honeybee_radiance_command.cutil as futil
import warnings
import re


[docs]class Option(object): """Radiance Option base class.""" __slots__ = ('_name', '_value', '_description') def __init__(self, name, description, value=None): """Create Radiance option. Args: name: Short name for Radiance option (e.g. ab). description: Longer description for Radiance option (e.g. ambient bounces) value: Optional value for option (Defult: None). """ self.name = name self.description = description self.value = value @property def name(self): return self._name.replace('_', '') @name.setter def name(self, name): self._name = name @property def description(self): return self._description @description.setter def description(self, description): self._description = description @property def value(self): """Option value.""" return self._value @value.setter def value(self, value): self._value = value @property def is_set(self): """Return True if the value is set by user.""" return self.value is not None
[docs] def to_radiance(self): """Translate option to Radiance format.""" if self.is_set: return '-%s %s' % (self.name, self.value) else: return ''
[docs] def ToString(self): return self.__repr__()
def __repr__(self): if self.is_set: return '%s\t\t# %s' % (self.to_radiance(), self.description) else: return '-%s <unset>\t# %s' % (self.name, self.description) def __eq__(self, other): return self._value == other def __ne__(self, other): return self._value != other def __add__(self, other): return self._value + other def __nonzero__(self): return bool(self._value) def __bool__(self): # python 3 return bool(self._value) def __iter__(self): return iter(self.value) def __contains__(self, value): return value in self._value
[docs]class FileOption(Option): __slots__ = () @property def value(self): """Option value.""" return self._value @value.setter def value(self, value): if value is None: self._value = value else: self._value = typing.normpath(value)
[docs] def to_radiance(self): """Translate option to Radiance format.""" if self.is_set: return '-%s %s' % ( self.name, self.value.replace('"', typing.wrapper).replace("'", typing.wrapper) ) else: return ''
def __len__(self): if self.value is not None: return len(self.value) else: return 0
[docs]class StringOption(FileOption): __slots__ = ('valid_values', 'whole', 'pattern_in', 'pattern_out') def __init__(self, name, description, value=None, valid_values=None, whole=True, pattern_in=None, pattern_out=None): """A string Radiance option. Args: name: Option name (e.g.: aa) description: Longer description for Radiance option (e.g. ambient accuracy). value: Optional value for the option (Default: None). valid_values: An optional list of valid values. By default all the string values are valid. whole: Set to true if the whole input string should be compared against valid values. If set to False the validator will run for each character in input string. pattern_in: A regex pattern that input values should match (Default: None). pattern_out: A format pattern to be applied to value for output e.g. "'%s'" (Default: None). """ self.valid_values = valid_values self.whole = whole FileOption.__init__(self, name, description) self.pattern_in = pattern_in self.pattern_out = pattern_out self.value = value @property def value(self): """Option value.""" return self._value @value.setter def value(self, value): if value is None: self._value = value else: if self.pattern_in is not None: if not re.match(self.pattern_in, value): raise ValueError( 'Input values for {} must match "{}" pattern.' ' Invalid input value: "{}".'.format( self.name, self.pattern_in, value) ) if self.valid_values is not None: if self.whole: if value not in self.valid_values: raise exceptions.InvalidValueError(self.name, value, self.valid_values) else: for v in value: if v not in self.valid_values: raise exceptions.InvalidValueError(self.name, v, self.valid_values) self._value = value if not self.pattern_out else self.pattern_out % value
[docs]class StringOptionJoined(StringOption): """Joined String Radiance option (e.g.: vtv, fa, etc.).""" __slots__ = ()
[docs] def to_radiance(self): """Translate option to Radiance format.""" if self.value is not None: return '-%s%s' % (self.name, self.value) else: return ''
[docs]class NumericOption(Option): """Numerical Radiance option.""" __slots__ = ('min_value', 'max_value') def __init__(self, name, description, value=None, min_value=float('-inf'), max_value=float('inf')): """A numerical Radiance option. For integer type use IntOption class. Args: name: Option name (e.g.: aa) description: Longer description for Radiance option (e.g. ambient accuracy). value: Optional value for aa (Default: None). min_value: Minimum valid value for this option. max_value: Maximum valid value for this option. """ Option.__init__(self, name, description) self.min_value = min_value self.max_value = max_value self.value = value @property def value(self): """Option value.""" return self._value @value.setter def value(self, value): if value is not None: self._value = typing.float_in_range( value, self.min_value, self.max_value, self.name) else: self._value = None def __int__(self): return int(self._value) def __float__(self): return float(self._value) def __sub__(self, other): return self._value - other def __lt__(self, other): return self._value < other def __gt__(self, other): return self._value > other def __le__(self, other): return self._value <= other def __ge__(self, other): return self._value >= other def __mul__(self, other): return self._value * other def __floordiv__(self, other): return self._value // other def __div__(self, other): return self._value / other def __mod__(self, other): return self._value % other def __pow__(self, other): return self._value ** other def __radd__(self, other): return self.__add__(other) def __rsub__(self, other): return other - self._value def __rmul__(self, other): return self.__mul__(other) def __rfloordiv__(self, other): return other // self._value def __rdiv__(self, other): return other / self._value def __rmod__(self, other): return other % self._value def __rpow__(self, other): return other ** self._value
[docs]class IntegerOption(NumericOption): """Integer Radiance option.""" __slots__ = () @property def value(self): """Option value.""" return self._value @value.setter def value(self, value): if value is not None: self._value = typing.int_in_range( value, self.min_value, self.max_value, self.name) else: self._value = None
[docs]class BoolOption(Option): """Boolean Radiance option.""" __slots__ = () @property def value(self): """Option value.""" return self._value @value.setter def value(self, value): if value is not None: # this is a special case to handle read from string when + is not used # in Radiance -I means -I+ and -h means -h+ and so on. value = True if value == '' else value self._value = False if value == '-' else bool(value) else: self._value = None
[docs] def to_radiance(self): """Translate option to Radiance format.""" if self.value is not None: return '-%s%s' % (self.name, '' if self.value else '-') else: return ''
[docs]class TupleOption(Option): """Tuple Radiance option.""" __slots__ = ('length', 'numtype') def __init__(self, name, description, value=None, length=3, numtype=float): """A numerical tuple Radiance option. Args: name: Option name (e.g.: aa) description: Longer description for Radiance option (e.g. ambient accuracy). value: Optional value for aa (Default: None). length: Number of items in tuple (Default: 3). numtype: Numerical type (Default: int). """ Option.__init__(self, name, description) self.length = length self.numtype = numtype self.value = value @property def value(self): """Option value.""" return self._value @value.setter def value(self, value): if value is not None: self._value = typing.tuple_with_length( value, self.length, self.numtype, self.name) else: self._value = None
[docs] def to_radiance(self): """Translate option to Radiance format.""" if self.value is not None: return '-%s %s' % (self.name, ' '.join(str(s) for s in self.value)) else: return ''
def __getitem__(self, key): return self._value[key] def __setitem__(self, key, val): self._value[key] = val
[docs]class OptionCollection(object): """Collection of Radiance Options. This is base class for difference Radiance command options. """ __slots__ = ('additional_options', '_on_setattr_check', '_protected') def __init__(self): # run on_setattr method on every attribute assignment # set to False if you are assigning several attributes all together when # initiating a new instance. object.__setattr__(self, '_on_setattr_check', False) self.additional_options = {} # collection of protected options that cannot be set by user. This is necessary # for cases like rfluxmtx. Even though rfluxmtx options subclasses from rcontrib # a handful number of options are controlled by rfluxmtx and may not be set by # user. self._protected = () @property def command(self): """Command name.""" return self.__class__.__name__.replace('Options', '').lower() @property def slots(self): """Return slots including the ones from the baseclass if any.""" slots = set(self.__slots__) for cls in self.__class__.__mro__[1:-2]: for s in getattr(cls, '__slots__', tuple()): if s in slots: continue slots.add(s) slots = [s for s in slots if s not in self._protected] slots.sort() return slots @property def options(self): """Print out list of options.""" options = [] for opt in self.slots: option = getattr(self, opt) if not isinstance(option, Option): continue options.append(str(option)) for k, v in self.additional_options.items(): options.append('-%s %s\t\t# additional option with no description' % (k, v)) return '\n'.join(options)
[docs] def update_from_string(self, string): """Update options from a standard radiance string. If the option is not currently part of the collection, it will be added to additional_options. """ slots = self.slots opt_dict = cutil.parse_radiance_options(string) for p, v in opt_dict.items(): if '_%s' % p in slots: setattr(self, p, v) else: if len(p) > 1: # joined string # catch special case -fio try: if p.startswith('f') and '_fio' in slots: setattr(self, 'fio', p[1:]) else: setattr(self, p[0], p[1:]) except AttributeError: # fall back to unknown item pass else: # it is assigned - go for the next one continue warnings.warn( '"%s" is a non-standard option for %s.' % ( p, self.__class__.__name__ ) ) # add to additional options self.additional_options[p] = v
[docs] def to_radiance(self): """Translate options to Radiance format.""" options = \ ' '.join(getattr(self, opt).to_radiance() for opt in self.slots) additional_options = \ ' '.join('-%s %s' % (k, v) for k, v in self.additional_options.items()) # handle replace %% with % to handle % in both Window and Unix return ' '.join( ' '.join((options, additional_options)).split()).replace('%%', '%')
[docs] def to_file(self, folder, file_name, mkdir=False): """Write options to a file.""" name = file_name or self.__class__.__name__ + '.opt' return futil.write_to_file_by_name(folder, name, self.to_radiance(), mkdir)
def __repr__(self): return self.options def __setattr__(self, name, value): try: object.__setattr__(self, name, value) except (AttributeError, SystemError): try: object.__setattr__(self, name + '_', value) except (AttributeError, SystemError): raise AttributeError( '"{1}" object has no attribute "{0}".' '\nYou can still try to use `update_from_string` method to add or' ' update the value for "{0}" from a string. Note that there will be' ' no checks for the input value from string inputs'.format( name, self.__class__.__name__) ) else: if self._on_setattr_check: self._on_setattr() def _on_setattr(self): """This method executes after setting each new attribute. Use this method to add checks that are necessary for OptionCollection. For instance in rtrace option collection -ti and -te are exclusive. You can include a check to ensure this is always correct. """ pass
[docs]class ToggleOption(Option): """Toggle radiance option.""" __slots__ = () @property def value(self): """Toggle value.""" return self._value @value.setter def value(self, val): if not val: self._value = None elif val in ('+', '-'): if val == '+': self._value = '+' else: self._value = '-' else: raise ValueError("The value needs to be either '+' or '-' only.")
[docs] def to_radiance(self): """Translate option to Radiance format.""" if self.value is not None: return '%s%s' % (self.value, self.name) else: return ''