# coding: utf-8
"""Dynamic geometry that can be assigned to individual states."""
from ..modifier import Modifier
from ..geometry import Polygon
from ..mutil import dict_to_modifier # imports all modifiers classes
from ..lib.modifiers import black, generic_exterior_shade
from honeybee.typing import valid_rad_string
from ladybug_geometry.geometry3d.pointvector import Point3D
from ladybug_geometry.geometry3d.face import Face3D
import math
[docs]
class StateGeometry(object):
"""A single planar geometry that can be assigned to Radiance states.
Args:
identifier: Text string for a unique geometry ID. Must not contain any
spaces or special characters.
geometry: A ladybug-geometry Face3D.
modifier: A Honeybee Radiance Modifier object for the geometry. If None,
it will be the Generic Exterior Shade modifier in the lib. (Default: None).
Properties:
* identifier
* display_name
* geometry
* modifier
* modifier_direct
* parent
* has_parent
* vertices
* normal
* center
* area
"""
__slots__ = ('_identifier', '_display_name', '_geometry', '_modifier',
'_modifier_direct', '_parent')
def __init__(self, identifier, geometry, modifier=None):
"""Initialize StateGeometry."""
# process the identifier
self._identifier = valid_rad_string(identifier, 'state geometry identifier')
self._display_name = self._identifier
# process the geometry
assert isinstance(geometry, Face3D), \
'Expected ladybug_geometry Face3D. Got {}'.format(type(geometry))
self._geometry = geometry
# process the modifier
self.modifier = modifier
self._modifier_direct = None
self._parent = None # _parent will be set when the Geo is added to a state
[docs]
@classmethod
def from_dict(cls, data):
"""Initialize a StateGeometry from a dictionary.
Note that the dictionary must be a non-abridged version for this
classmethod to work.
Args:
data: A dictionary representation of an StateGeometry with the
format below.
.. code-block:: python
{
'type': 'StateGeometry',
'identifier': str, # Text for the unique object identifier
'display_name': str, # Optional text for the display name
'geometry': {}, # A ladybug_geometry Face3D dictionary
'modifier': {}, # A Honeybee Radiance Modifier dictionary
'modifier_direct': {} # A Honeybee Radiance Modifier dictionary
}
"""
# check the type of dictionary
assert data['type'] == 'StateGeometry', 'Expected StateGeometry dictionary. ' \
'Got {}.'.format(data['type'])
geo = cls(data['identifier'], Face3D.from_dict(data['geometry']))
if 'modifier' in data and data['modifier'] is not None:
geo.modifier = dict_to_modifier(data['modifier'])
if 'modifier_direct' in data and data['modifier_direct'] is not None:
geo.modifier_direct = dict_to_modifier(data['modifier_direct'])
if 'display_name' in data and data['display_name'] is not None:
geo.display_name = data['display_name']
return geo
[docs]
@classmethod
def from_dict_abridged(cls, data, modifiers):
"""Create StateGeometry from an abridged dictionary.
Args:
data: A dictionary representation of StateGeometryAbridged with
the format below.
modifiers: A dictionary of modifiers with modifier identifiers as keys,
which will be used to re-assign modifiers.
.. code-block:: python
{
'type': 'StateGeometryAbridged',
'identifier': str, # Text for the unique object identifier
'display_name': str, # Optional text for the display name
'geometry': {}, # A ladybug_geometry Face3D dictionary
'modifier': str # A Honeybee Radiance Modifier identifier
'modifier_direct': str # A Honeybee Radiance Modifier identifier
}
"""
# check the type of dictionary
assert data['type'] == 'StateGeometryAbridged', \
'Expected StateGeometryAbridged dictionary. Got {}.'.format(data['type'])
geo = cls(data['identifier'], Face3D.from_dict(data['geometry']))
if 'modifier' in data and data['modifier'] is not None:
geo.modifier = modifiers[data['modifier']]
if 'modifier_direct' in data and data['modifier_direct'] is not None:
geo.modifier_direct = modifiers[data['modifier_direct']]
if 'display_name' in data and data['display_name'] is not None:
geo.display_name = data['display_name']
return geo
[docs]
@classmethod
def from_vertices(cls, identifier, vertices, modifier=None):
"""Create StateGeometry from vertices with each vertex as an iterable of 3 floats.
Note that this method is not recommended for a geometry with one or more holes
since the distinction between hole vertices and boundary vertices cannot
be derived from a single list of vertices.
Args:
identifier: Text string for a unique geometry ID. Must not contain any
spaces or special characters.
vertices: A flattened list of 3 or more vertices as (x, y, z).
modifier: A Honeybee Radiance Modifier object for the geometry. If None, it
will be the Generic Exterior Shade modifier in the lib. (Default: None).
"""
geometry = Face3D(tuple(Point3D(*v) for v in vertices))
return cls(identifier, geometry, modifier)
@property
def identifier(self):
"""Get a text string for the unique object identifer.
This identifier remains constant as the object is mutated, copied, and
serialized to different formats (eg. dict, idf, rad). As such, this
property is used to reference the object across a Model.
"""
return self._identifier
@property
def display_name(self):
"""Get or set a string for the object name without any character restrictions.
If not set, this will be equal to the identifier.
"""
return self._display_name
@display_name.setter
def display_name(self, value):
try:
self._display_name = str(value)
except UnicodeEncodeError: # Python 2 machine lacking the character set
self._display_name = value # keep it as unicode
@property
def geometry(self):
"""Get a ladybug_geometry Face3D object representing the Shade."""
return self._geometry
@property
def modifier(self):
"""Get or set the object modifier."""
if self._modifier: # set by user
return self._modifier
return generic_exterior_shade
@modifier.setter
def modifier(self, value):
if value is not None:
assert isinstance(value, Modifier), \
'Expected Radiance Modifier for shade. Got {}'.format(type(value))
value.lock() # lock editing in case modifier has multiple references
self._modifier = value
@property
def modifier_direct(self):
"""Get or set a modifier to be used in direct solar studies.
If None, this will be a completely black material if the object's modifier is
opaque and will be equal to the modifier if the object's modifier is non-opaque.
"""
if self._modifier_direct: # set by user
return self._modifier_direct
mod = self.modifier # assign a default based on whether the modifier is opaque
if mod.is_void or mod.is_opaque:
return black
else:
return mod
@modifier_direct.setter
def modifier_direct(self, value):
if value is not None:
assert isinstance(value, Modifier), \
'Expected Radiance Modifier. Got {}'.format(type(value))
value.lock() # lock editing in case modifier has multiple references
self._modifier_direct = value
@property
def is_opaque(self):
"""Boolean noting whether this geomtry has an opaque modifier."""
return True if self.modifier.is_void else self.modifier.is_opaque
@property
def parent(self):
"""Get the parent State if assigned. None if not assigned."""
return self._parent
@property
def has_parent(self):
"""Get a boolean noting whether this StateGeometry has a parent State."""
return self._parent is not None
@property
def vertices(self):
"""Get a list of vertices for the geometry (in counter-clockwise order)."""
return self._geometry.vertices
@property
def normal(self):
"""Get a ladybug_geometry Vector3D for the direction the geometry is pointing.
"""
return self._geometry.normal
@property
def center(self):
"""Get a ladybug_geometry Point3D for the center of the geometry.
Note that this is the center of the bounding rectangle around this geometry
and not the area centroid.
"""
return self._geometry.center
@property
def area(self):
"""Get the area of the geometry."""
return self._geometry.area
[docs]
def move(self, moving_vec):
"""Move this StateGeometry along a vector.
Args:
moving_vec: A ladybug_geometry Vector3D with the direction and distance
to move the face.
"""
self._geometry = self.geometry.move(moving_vec)
[docs]
def rotate(self, axis, angle, origin):
"""Rotate this StateGeometry by a certain angle around an axis and origin.
Args:
axis: A ladybug_geometry Vector3D axis representing the axis of rotation.
angle: An angle for rotation in degrees.
origin: A ladybug_geometry Point3D for the origin around which the
object will be rotated.
"""
self._geometry = self.geometry.rotate(axis, math.radians(angle), origin)
[docs]
def rotate_xy(self, angle, origin):
"""Rotate this StateGeometry counterclockwise in the XY plane by a certain angle.
Args:
angle: An angle in degrees.
origin: A ladybug_geometry Point3D for the origin around which the
object will be rotated.
"""
self._geometry = self.geometry.rotate_xy(math.radians(angle), origin)
[docs]
def reflect(self, plane):
"""Reflect this StateGeometry across a plane.
Args:
plane: A ladybug_geometry Plane across which the object will
be reflected.
"""
self._geometry = self.geometry.reflect(plane.n, plane.o)
[docs]
def scale(self, factor, origin=None):
"""Scale this StateGeometry by a factor from an origin point.
Args:
factor: A number representing how much the object should be scaled.
origin: A ladybug_geometry Point3D representing the origin from which
to scale. If None, it will be scaled from the World origin (0, 0, 0).
"""
self._geometry = self.geometry.scale(factor, origin)
[docs]
def to_dict(self, abridged=False):
"""Return StateGeometry as a dictionary.
Args:
abridged: Boolean to note whether the full dictionary describing the
object should be returned (False) or just an abridged version (True).
Default: False.
"""
# assign required properties
base = {'type': 'StateGeometryAbridged'} if abridged else \
{'type': 'StateGeometry'}
base['identifier'] = self.identifier
base['display_name'] = self.display_name
base['geometry'] = self.geometry.to_dict()
# assign optional properties
if self._modifier:
base['modifier'] = self._modifier.identifier if abridged else \
self._modifier.to_dict()
if self._modifier_direct is not None:
base['modifier_direct'] = self._modifier_direct.identifier if abridged \
else self._modifier.to_dict()
return base
[docs]
def to_radiance(self, direct=False, minimal=False):
"""Generate a RAD string representation of this StateGeometry.
Note that the resulting string lacks modifiers.
Args:
direct: Boolean to note whether to write the "direct" version of the
state, which will have the modifier_direct applied. (Default: False)
minimal: Boolean to note whether the radiance string should be written
in a minimal format (with spaces instead of line breaks). Default: False.
"""
modifier = self.modifier_direct if direct else self.modifier
base_poly = Polygon(self.identifier, self.vertices, modifier)
return base_poly.to_radiance(minimal, False, False)
[docs]
def duplicate(self):
"""Get a copy of this object."""
return self.__copy__()
def __copy__(self):
new_geo = StateGeometry(self.identifier, self.geometry, self._modifier)
new_geo._modifier_direct = self._modifier_direct
new_geo._display_name = self.display_name
return new_geo
[docs]
def ToString(self):
return self.__repr__()
def __repr__(self):
return 'StateGeometry: %s' % self.display_name