# coding: utf-8
"""Dragonfly Context Shade."""
from __future__ import division
import math
from ladybug_geometry.geometry3d import Face3D, Mesh3D
from honeybee.shade import Shade
from honeybee.shademesh import ShadeMesh
from honeybee.typing import clean_string
from ._base import _BaseGeometry
from .properties import ContextShadeProperties
import dragonfly.writer.context as writer
[docs]class ContextShade(_BaseGeometry):
"""A Context Shade object defined by an array of Face3Ds and/or Mesh3Ds.
Args:
identifier: Text string for a unique ContextShade ID. Must be < 100 characters
and not contain any spaces or special characters.
geometry: An array of ladybug_geometry Face3D and/or Mesh3D objects
that together represent the context shade.
is_detached: Boolean to note whether this object is detached from other
geometry. Cases where this should be True include shade representing
surrounding buildings or context. (Default: True).
Properties:
* identifier
* display_name
* geometry
* is_detached
* area
* min
* max
* user_data
"""
__slots__ = ('_geometry', '_is_detached')
def __init__(self, identifier, geometry, is_detached=True):
"""Initialize ContextShade."""
_BaseGeometry.__init__(self, identifier) # process the identifier
# process the geometry
if not isinstance(geometry, tuple):
geometry = tuple(geometry)
assert len(geometry) > 0, 'ContextShade must have at least one geometry.'
for shd_geo in geometry:
assert isinstance(shd_geo, (Face3D, Mesh3D)), 'Expected ladybug_geometry ' \
'Face3D or Mesh3D. Got {}'.format(type(shd_geo))
self._geometry = geometry
self.is_detached = is_detached
self._properties = ContextShadeProperties(self) # properties for extensions
[docs] @classmethod
def from_dict(cls, data):
"""Initialize an ContextShade from a dictionary.
Args:
data: A dictionary representation of an ContextShade object.
"""
# check the type of dictionary
assert data['type'] == 'ContextShade', 'Expected ContextShade dictionary. ' \
'Got {}.'.format(data['type'])
is_detached = data['is_detached'] if 'is_detached' in data else True
geometry = []
for shd_geo in data['geometry']:
if shd_geo['type'] == 'Face3D':
geometry.append(Face3D.from_dict(shd_geo))
else:
geometry.append(Mesh3D.from_dict(shd_geo))
shade = cls(data['identifier'], geometry, is_detached)
if 'display_name' in data and data['display_name'] is not None:
shade.display_name = data['display_name']
if 'user_data' in data and data['user_data'] is not None:
shade.user_data = data['user_data']
if data['properties']['type'] == 'ContextShadeProperties':
shade.properties._load_extension_attr_from_dict(data['properties'])
return shade
[docs] @classmethod
def from_honeybee(cls, shade):
"""Initialize an ContextShade from a Honeybee Shade or ShadeMesh.
Args:
shade: A Honeybee Shade or ShadeMesh object.
"""
con_shade = cls(shade.identifier, [shade.geometry], shade.is_detached)
con_shade._display_name = shade.display_name
con_shade._user_data = None if shade.user_data is None \
else shade.user_data.copy()
con_shade.properties.from_honeybee(shade.properties)
return con_shade
@property
def geometry(self):
"""Get a tuple of Face3D and/or Mesh3D objects that represent the context shade.
"""
return self._geometry
@property
def is_detached(self):
"""Get or set a boolean for whether this object is detached from other geometry.
"""
return self._is_detached
@is_detached.setter
def is_detached(self, value):
try:
self._is_detached = bool(value)
except TypeError:
raise TypeError(
'Expected boolean for ContextShade.is_detached. Got {}.'.format(value))
@property
def area(self):
"""Get a number for the total surface area of the ContextShade."""
return sum([geo.area for geo in self._geometry])
@property
def min(self):
"""Get a Point2D for the min bounding rectangle vertex in the XY plane.
This is useful in calculations to determine if this ContextShade is in
proximity to other objects.
"""
return self._calculate_min(self._geometry)
@property
def max(self):
"""Get a Point2D for the max bounding rectangle vertex in the XY plane.
This is useful in calculations to determine if this ContextShade is in
proximity to other objects.
"""
return self._calculate_max(self._geometry)
[docs] def add_prefix(self, prefix):
"""Change the identifier of this object by inserting a prefix.
This is particularly useful in workflows where you duplicate and edit
a starting object and then want to combine it with the original object
into one Model (like making a model of repeated shades) since all objects
within a Model must have unique identifiers.
Args:
prefix: Text that will be inserted at the start of this object's identifier
and display_name. It is recommended that this prefix be short to
avoid maxing out the 100 allowable characters for dragonfly identifiers.
"""
self._identifier = clean_string('{}_{}'.format(prefix, self.identifier))
self.display_name = '{}_{}'.format(prefix, self.display_name)
self.properties.add_prefix(prefix)
[docs] def move(self, moving_vec):
"""Move this ContextShade along a vector.
Args:
moving_vec: A ladybug_geometry Vector3D with the direction and distance
to move the object.
"""
self._geometry = tuple(shd_geo.move(moving_vec) for shd_geo in self._geometry)
self.properties.move(moving_vec)
[docs] def rotate_xy(self, angle, origin):
"""Rotate this ContextShade 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 = tuple(shd_geo.rotate_xy(math.radians(angle), origin)
for shd_geo in self._geometry)
self.properties.rotate_xy(angle, origin)
[docs] def reflect(self, plane):
"""Reflect this ContextShade across a plane.
Args:
plane: A ladybug_geometry Plane across which the object will be reflected.
"""
self._geometry = tuple(shd_geo.reflect(plane.n, plane.o)
for shd_geo in self._geometry)
self.properties.reflect(plane)
[docs] def scale(self, factor, origin=None):
"""Scale this ContextShade 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 = tuple(shd_geo.scale(factor, origin)
for shd_geo in self._geometry)
self.properties.scale(factor, origin)
[docs] def to_honeybee(self):
"""Convert Dragonfly ContextShade to a list of Honeybee Shades and ShadeMeshes.
"""
shades = []
for i, shd_geo in enumerate(self._geometry):
if isinstance(shd_geo, Face3D):
shade = Shade('{}_{}'.format(self.identifier, i), shd_geo,
is_detached=self.is_detached)
shade._properties = self.properties.to_honeybee(shade, False)
else:
shade = ShadeMesh('{}_{}'.format(self.identifier, i), shd_geo,
is_detached=self.is_detached)
shade._properties = self.properties.to_honeybee(shade, True)
shade.display_name = self.display_name
shade.user_data = None if self.user_data is None else self.user_data.copy()
shades.append(shade)
return shades
[docs] def to_dict(self, abridged=False, included_prop=None):
"""Return ContextShade as a dictionary.
Args:
abridged: Boolean to note whether the extension properties of the
object (ie. materials, transmittance schedule) should be included in
detail (False) or just referenced by identifier (True). Default: False.
included_prop: List of properties to filter keys that must be included in
output dictionary. For example ['energy'] will include 'energy' key if
available in properties to_dict. By default all the keys will be
included. To exclude all the keys from extensions use an empty list.
"""
base = {'type': 'ContextShade'}
base['identifier'] = self.identifier
base['display_name'] = self.display_name
base['properties'] = self.properties.to_dict(abridged, included_prop)
base['geometry'] = [shd_geo.to_dict() for shd_geo in self._geometry]
if not self.is_detached:
base['is_detached'] = self.is_detached
if self.user_data is not None:
base['user_data'] = self.user_data
return base
@property
def to(self):
"""ContextShade writer object.
Use this method to access Writer class to write the context in other formats.
"""
return writer
def __copy__(self):
new_shd = ContextShade(self.identifier, self._geometry, self.is_detached)
new_shd._display_name = self.display_name
new_shd._user_data = None if self.user_data is None else self.user_data.copy()
new_shd._properties._duplicate_extension_attr(self._properties)
return new_shd
def __len__(self):
return len(self._geometry)
def __getitem__(self, key):
return self._geometry[key]
def __iter__(self):
return iter(self._geometry)
def __repr__(self):
return 'ContextShade: %s' % self.display_name