# coding: utf-8
"""Grid Parameters with instructions for generating SensorGrids."""
from __future__ import division
from ladybug_geometry.geometry3d import Vector3D
from honeybee.typing import float_in_range, float_positive, int_positive, valid_string
from honeybee.altnumber import autocalculate
class _GridParameterBase(object):
"""Base object for all GridParameters.
This object records all of the methods that must be overwritten on a grid
parameter object for it to be successfully be applied in dragonfly workflows.
Args:
dimension: The dimension of the grid cells as a number.
offset: A number for how far to offset the grid from the base
geometries. (Default: 0).
include_mesh: A boolean to note whether the resulting SensorGrid should
include the mesh. (Default: True).
"""
__slots__ = ('_dimension', '_offset', '_include_mesh')
def __init__(self, dimension, offset=0, include_mesh=True):
self._dimension = float_positive(dimension, 'grid dimension')
self._offset = float_in_range(offset, input_name='grid offset')
self._include_mesh = bool(include_mesh)
@property
def dimension(self):
"""Get a number for the dimension of the grid cells."""
return self._dimension
@property
def offset(self):
"""Get a number for how far to offset the grid from the base geometries."""
return self._offset
@property
def include_mesh(self):
"""Get a boolean for whether the resulting SensorGrid should include the mesh."""
return self._include_mesh
def generate_grid_from_room(self, honeybee_room):
"""Get a SensorGrid from a Honeybee Room using these GridParameter.
Args:
honeybee_room: A Honeybee Room to which these grid parameters
are applied.
Returns:
A honeybee-radiance SensorGrid generated from the Honeybee Room.
"""
pass
def scale(self, factor):
"""Get a scaled version of these GridParameters.
This method is called within the scale methods of the Room2D.
Args:
factor: A number representing how much the object should be scaled.
"""
return _GridParameterBase(
self.dimension * factor, self.offset * factor, self.include_mesh)
@classmethod
def from_dict(cls, data):
"""Create GridParameterBase from a dictionary.
.. code-block:: python
{
"type": "GridParameterBase",
"dimension": 0.5,
"offset": 1.0,
"include_mesh": True
}
"""
assert data['type'] == 'GridParameterBase', \
'Expected GridParameterBase dictionary. Got {}.'.format(data['type'])
offset = data['offset'] \
if 'offset' in data and data['offset'] is not None else 0
include_mesh = data['include_mesh'] \
if 'include_mesh' in data and data['include_mesh'] is not None else True
return cls(data['dimension'], offset, include_mesh)
def to_dict(self):
"""Get GridParameterBase as a dictionary."""
base = {
'type': 'GridParameterBase',
'dimension': self.dimension,
'offset': self.offset
}
if not self.include_mesh:
base['include_mesh'] = self.include_mesh
return base
def duplicate(self):
"""Get a copy of this object."""
return self.__copy__()
def ToString(self):
return self.__repr__()
def __copy__(self):
return _GridParameterBase(self.dimension, self.offset, self.include_mesh)
def __repr__(self):
return 'GridParameterBase'
[docs]
class RoomGridParameter(_GridParameterBase):
"""Instructions for a SensorGrid generated from a Room2D's floors.
The resulting grid will have the room referenced in its room_identifier
property. Note that the grid is generated within the XY coordinate system
of the Room2D's floor_geometry. So rotating the plane of this geometry will
will result in rotated grid cells.
Args:
dimension: The dimension of the grid cells as a number.
offset: A number for how far to offset the grid from the base
geometries. (Default: 1, suitable for Rooms in Meters).
wall_offset: A number for the distance at which sensors close to walls
should be removed. Note that this option has no effect unless the
value is more than half of the dimension. (Default: 0).
include_mesh: A boolean to note whether the resulting SensorGrid should
include the mesh. (Default: True).
"""
__slots__ = ('_wall_offset',)
def __init__(self, dimension, offset=1.0, wall_offset=0, include_mesh=True):
_GridParameterBase.__init__(self, dimension, offset, include_mesh)
self._wall_offset = float_positive(wall_offset, 'grid wall offset')
@property
def wall_offset(self):
"""Get a number for the distance at which sensors near walls should be removed.
"""
return self._wall_offset
[docs]
def generate_grid_from_room(self, honeybee_room):
"""Get a SensorGrid from a Honeybee Room using these GridParameter.
Args:
honeybee_room: A Honeybee Room to which these grid parameters are applied.
Returns:
A honeybee-radiance SensorGrid generated from the Honeybee Room. Will
be None if a valid Grid cannot be generated from the Room.
"""
ftc_h = honeybee_room.max.z - honeybee_room.min.z
if self.offset >= ftc_h:
return None
s_grid = honeybee_room.properties.radiance.generate_sensor_grid(
self.dimension, offset=self.offset, wall_offset=self.wall_offset)
if not self.include_mesh and s_grid is not None:
s_grid.mesh = None
return s_grid
[docs]
def scale(self, factor):
"""Get a scaled version of these GridParameters.
This method is called within the scale methods of the Room2D.
Args:
factor: A number representing how much the object should be scaled.
"""
return RoomGridParameter(
self.dimension * factor, self.offset * factor,
self.wall_offset * factor, self.include_mesh)
[docs]
@classmethod
def from_dict(cls, data):
"""Create RoomGridParameter from a dictionary.
.. code-block:: python
{
"type": "RoomGridParameter",
"dimension": 0.5,
"offset": 1.0,
"wall_offset": 0.5,
"include_mesh": True
}
"""
assert data['type'] == 'RoomGridParameter', \
'Expected RoomGridParameter dictionary. Got {}.'.format(data['type'])
offset = data['offset'] \
if 'offset' in data and data['offset'] is not None else 1.0
wall_offset = data['wall_offset'] \
if 'wall_offset' in data and data['wall_offset'] is not None else 0
include_mesh = data['include_mesh'] \
if 'include_mesh' in data and data['include_mesh'] is not None else True
return cls(data['dimension'], offset, wall_offset, include_mesh)
[docs]
def to_dict(self):
"""Get RoomGridParameter as a dictionary."""
base = {
'type': 'RoomGridParameter',
'dimension': self.dimension,
'offset': self.offset
}
if self.wall_offset != 0:
base['wall_offset'] = self.wall_offset
if not self.include_mesh:
base['include_mesh'] = self.include_mesh
return base
[docs]
def duplicate(self):
"""Get a copy of this object."""
return self.__copy__()
[docs]
def ToString(self):
return self.__repr__()
def __copy__(self):
return RoomGridParameter(
self.dimension, self.offset, self.wall_offset, self.include_mesh)
def __repr__(self):
return 'RoomGridParameter [dimension: {}] [offset: {}]'.format(
self.dimension, self.offset)
[docs]
class RoomRadialGridParameter(RoomGridParameter):
"""Instructions for a SensorGrid of radial directions around positions from floors.
This type of sensor grid is particularly helpful for studies of multiple
view directions, such as imageless glare studies.
The resulting grid will have the room referenced in its room_identifier
property. Note that the grid is generated within the XY coordinate system
of the Room2D's floor_geometry. So rotating the plane of this geometry will
will result in rotated grid cells.
Args:
dimension: The dimension of the grid cells as a number.
offset: A number for how far to offset the grid from the base
geometries. (Default: 1.2, suitable for Rooms in Meters).
wall_offset: A number for the distance at which sensors close to walls
should be removed. Note that this option has no effect unless the
value is more than half of the x_dim or y_dim. (Default: 0).
dir_count: A positive integer for the number of radial directions
to be generated around each position. (Default: 8).
start_vector: A Vector3D to set the start direction of the generated
directions. This can be used to orient the resulting sensors to
specific parts of the scene. It can also change the elevation of the
resulting directions since this start vector will always be rotated in
the XY plane to generate the resulting directions. (Default: (0, -1, 0)).
mesh_radius: An optional number to override the radius of the meshes
generated around each sensor. If None or autocalculate, it will be
equal to 45% of the grid dimension. (Default: None).
include_mesh: A boolean to note whether the resulting SensorGrid should
include the mesh. (Default: True).
"""
__slots__ = ('_dir_count', '_start_vector', '_mesh_radius')
def __init__(self, dimension, offset=1.2, wall_offset=0, dir_count=8,
start_vector=Vector3D(0, -1, 0), mesh_radius=None, include_mesh=True):
RoomGridParameter.__init__(self, dimension, offset, wall_offset, include_mesh)
self._dir_count = int_positive(dir_count, 'radial grid dir count')
assert self._dir_count != 0, 'Radial grid dir count must not be equal to 0.'
assert isinstance(start_vector, Vector3D), 'Expected Vector3D for radial ' \
'grid start_vector. Got {}.'.format(type(start_vector))
self._start_vector = start_vector
if mesh_radius == autocalculate:
mesh_radius = None
elif mesh_radius is not None:
mesh_radius = float_positive(mesh_radius, 'radial grid mesh_radius')
self._mesh_radius = mesh_radius
@property
def dir_count(self):
"""Get an integer for the number of radial directions around each position.
"""
return self._dir_count
@property
def start_vector(self):
"""Get a Vector3D that sets the start direction of the generated directions.
"""
return self._start_vector
@property
def mesh_radius(self):
"""Get a number that sets the radius of the meshes generated around each sensor.
If None or autocalculate, it will be equal to 45% of the grid dimension.
"""
return self._mesh_radius
[docs]
def generate_grid_from_room(self, honeybee_room):
"""Get a SensorGrid from a Honeybee Room using these GridParameter.
Args:
honeybee_room: A Honeybee Room to which these grid parameters are applied.
Returns:
A honeybee-radiance SensorGrid generated from the Honeybee Room. Will
be None if a valid Grid cannot be generated from the Room.
"""
ftc_h = honeybee_room.max.z - honeybee_room.min.z
if self.offset >= ftc_h:
return None
m_rad = self.mesh_radius if self.include_mesh else 0
s_grid = honeybee_room.properties.radiance.generate_sensor_grid_radial(
self.dimension, offset=self.offset, wall_offset=self.wall_offset,
dir_count=self.dir_count, start_vector=self.start_vector, mesh_radius=m_rad)
return s_grid
[docs]
def scale(self, factor):
"""Get a scaled version of these GridParameters.
This method is called within the scale methods of the Room2D.
Args:
factor: A number representing how much the object should be scaled.
"""
m_rad = self.mesh_radius * factor if self.mesh_radius is not None else None
return RoomRadialGridParameter(
self.dimension * factor, self.offset * factor,
self.wall_offset * factor, self.dir_count,
self.start_vector, m_rad, self.include_mesh)
[docs]
@classmethod
def from_dict(cls, data):
"""Create RoomRadialGridParameter from a dictionary.
.. code-block:: python
{
"type": "RoomRadialGridParameter",
"dimension": 1.0,
"offset": 1.2,
"wall_offset": 1.0,
"dir_count": 8,
"start_vector": (0, -1, 0),
"mesh_radius": 0.5,
"include_mesh": True
}
"""
assert data['type'] == 'RoomRadialGridParameter', \
'Expected RoomRadialGridParameter dictionary. Got {}.'.format(data['type'])
offset = data['offset'] \
if 'offset' in data and data['offset'] is not None else 1.2
wall_offset = data['wall_offset'] \
if 'wall_offset' in data and data['wall_offset'] is not None else 0
dir_count = data['dir_count'] \
if 'dir_count' in data and data['dir_count'] is not None else 8
start_vector = Vector3D.from_array(data['start_vector']) if 'start_vector' \
in data and data['start_vector'] is not None else Vector3D(0, -1, 0)
mesh_radius = data['mesh_radius'] if 'mesh_radius' in data and \
data['mesh_radius'] != autocalculate.to_dict() else None
include_mesh = data['include_mesh'] \
if 'include_mesh' in data and data['include_mesh'] is not None else True
return cls(data['dimension'], offset, wall_offset, dir_count, start_vector,
mesh_radius, include_mesh)
[docs]
def to_dict(self):
"""Get RoomRadialGridParameter as a dictionary."""
base = {
'type': 'RoomRadialGridParameter',
'dimension': self.dimension,
'offset': self.offset,
'dir_count': self.dir_count,
'start_vector': self.start_vector.to_array()
}
if self.mesh_radius is not None:
base['mesh_radius'] = self.mesh_radius
if self.wall_offset != 0:
base['wall_offset'] = self.wall_offset
if not self.include_mesh:
base['include_mesh'] = self.include_mesh
return base
[docs]
def duplicate(self):
"""Get a copy of this object."""
return self.__copy__()
[docs]
def ToString(self):
return self.__repr__()
def __copy__(self):
return RoomRadialGridParameter(
self.dimension, self.offset, self.wall_offset, self.dir_count,
self.start_vector, self.mesh_radius, self.include_mesh)
def __repr__(self):
return 'RoomRadialGridParameter [dimension: {}] [offset: {}]'.format(
self.dimension, self.offset)
[docs]
class ExteriorFaceGridParameter(_GridParameterBase):
"""Instructions for a SensorGrid generated from exterior Faces.
Args:
dimension: The dimension of the grid cells as a number.
offset: A number for how far to offset the grid from the base
geometries. (Default: 0.1, suitable for Rooms in Meters).
face_type: Text to specify the type of face that will be used to
generate grids. Note that only Faces with Outdoors boundary
conditions will be used, meaning that most Floors will typically
be excluded unless they represent the underside of a cantilever.
Choose from the following. (Default: Wall).
* Wall
* Roof
* Floor
* All
punched_geometry: Boolean to note whether the punched_geometry of the faces
should be used (True) with the areas of sub-faces removed from the grid
or the full geometry should be used (False). (Default:False).
include_mesh: A boolean to note whether the resulting SensorGrid should
include the mesh. (Default: True).
"""
__slots__ = ('_face_type', '_punched_geometry')
FACE_TYPES = ('Wall', 'Roof', 'Floor', 'All')
def __init__(self, dimension, offset=0.1, face_type='Wall', punched_geometry=False,
include_mesh=True):
_GridParameterBase.__init__(self, dimension, offset, include_mesh)
clean_face_type = valid_string(face_type).lower()
for key in self.FACE_TYPES:
if key.lower() == clean_face_type:
face_type = key
break
else:
raise ValueError(
'ExteriorFaceGrid face_type "{}" is not recognized.\nChoose from the '
'following:\n{}'.format(face_type, self.FACE_TYPES))
self._face_type = face_type
self._punched_geometry = bool(punched_geometry)
@property
def face_type(self):
"""Get text to specify the type of face that will be used to generate grids.
"""
return self._face_type
@property
def punched_geometry(self):
"""Get a boolean for whether the punched_geometry of the faces should be used.
"""
return self._punched_geometry
[docs]
def generate_grid_from_room(self, honeybee_room):
"""Get a SensorGrid from a Honeybee Room using these GridParameter.
Args:
honeybee_room: A Honeybee Room to which these grid parameters are applied.
Returns:
A honeybee-radiance SensorGrid generated from the Honeybee Room. Will
be None if the Room has no exterior Faces.
"""
s_grid = honeybee_room.properties.radiance.generate_exterior_face_sensor_grid(
self.dimension, offset=self.offset, face_type=self.face_type,
punched_geometry=self.punched_geometry)
if not self.include_mesh and s_grid is not None:
s_grid.mesh = None
return s_grid
[docs]
def scale(self, factor):
"""Get a scaled version of these GridParameters.
This method is called within the scale methods of the Room2D.
Args:
factor: A number representing how much the object should be scaled.
"""
return ExteriorFaceGridParameter(
self.dimension * factor, self.offset * factor,
self.face_type, self.punched_geometry, self.include_mesh)
[docs]
@classmethod
def from_dict(cls, data):
"""Create ExteriorFaceGridParameter from a dictionary.
.. code-block:: python
{
"type": "ExteriorFaceGridParameter",
"dimension": 0.5,
"offset": 0.15,
"face_type": "Roof",
"include_mesh": True
}
"""
assert data['type'] == 'ExteriorFaceGridParameter', \
'Expected ExteriorFaceGridParameter dictionary. Got {}.'.format(data['type'])
offset = data['offset'] \
if 'offset' in data and data['offset'] is not None else 0.1
face_type = data['face_type'] \
if 'face_type' in data and data['face_type'] is not None else 'Wall'
pg = data['punched_geometry'] if 'punched_geometry' in data \
and data['punched_geometry'] is not None else False
include_mesh = data['include_mesh'] \
if 'include_mesh' in data and data['include_mesh'] is not None else True
return cls(data['dimension'], offset, face_type, pg, include_mesh)
[docs]
def to_dict(self):
"""Get ExteriorFaceGridParameter as a dictionary."""
base = {
'type': 'ExteriorFaceGridParameter',
'dimension': self.dimension,
'offset': self.offset,
'face_type': self.face_type
}
if self.punched_geometry:
base['punched_geometry'] = self.punched_geometry
if not self.include_mesh:
base['include_mesh'] = self.include_mesh
return base
[docs]
def duplicate(self):
"""Get a copy of this object."""
return self.__copy__()
[docs]
def ToString(self):
return self.__repr__()
def __copy__(self):
return ExteriorFaceGridParameter(
self.dimension, self.offset, self.face_type,
self.punched_geometry, self.include_mesh)
def __repr__(self):
return 'ExteriorFaceGridParameter [dimension: {}] [type: {}]'.format(
self.dimension, self.face_type)
[docs]
class ExteriorApertureGridParameter(_GridParameterBase):
"""Instructions for a SensorGrid generated from exterior Aperture.
Args:
dimension: The dimension of the grid cells as a number.
offset: A number for how far to offset the grid from the base
geometries. (Default: 0.1, suitable for Rooms in Meters).
aperture_type: Text to specify the type of Aperture that will be used to
generate grids. Window indicates Apertures in Walls. Skylights
are in parent Roof faces. Choose from the following. (Default: All).
* Window
* Skylight
* All
include_mesh: A boolean to note whether the resulting SensorGrid should
include the mesh. (Default: True).
"""
__slots__ = ('_aperture_type',)
APERTURE_TYPES = ('Window', 'Skylight', 'All')
def __init__(self, dimension, offset=0.1, aperture_type='All', include_mesh=True):
_GridParameterBase.__init__(self, dimension, offset, include_mesh)
clean_ap_type = valid_string(aperture_type).lower()
for key in self.APERTURE_TYPES:
if key.lower() == clean_ap_type:
aperture_type = key
break
else:
raise ValueError(
'ExteriorApertureGrid aperture_type "{}" is not recognized.\nChoose '
'from the following:\n{}'.format(aperture_type, self.APERTURE_TYPES))
self._aperture_type = aperture_type
@property
def aperture_type(self):
"""Get text to specify the type of face that will be used to generate grids.
"""
return self._aperture_type
[docs]
def generate_grid_from_room(self, honeybee_room):
"""Get a SensorGrid from a Honeybee Room using these GridParameter.
Args:
honeybee_room: A Honeybee Room to which these grid parameters are applied.
Returns:
A honeybee-radiance SensorGrid generated from the Honeybee Room. Will
be None if the object has no exterior Apertures.
"""
s_g = honeybee_room.properties.radiance.generate_exterior_aperture_sensor_grid(
self.dimension, offset=self.offset, aperture_type=self.aperture_type)
if not self.include_mesh and s_g is not None:
s_g.mesh = None
return s_g
[docs]
def scale(self, factor):
"""Get a scaled version of these GridParameters.
This method is called within the scale methods of the Room2D.
Args:
factor: A number representing how much the object should be scaled.
"""
return ExteriorApertureGridParameter(
self.dimension * factor, self.offset * factor,
self.aperture_type, self.include_mesh)
[docs]
@classmethod
def from_dict(cls, data):
"""Create ExteriorApertureGridParameter from a dictionary.
.. code-block:: python
{
"type": "ExteriorApertureGridParameter",
"dimension": 0.5,
"offset": 0.15,
"aperture_type": "Window",
"include_mesh": True
}
"""
assert data['type'] == 'ExteriorApertureGridParameter', 'Expected ' \
'ExteriorApertureGridParameter dictionary. Got {}.'.format(data['type'])
offset = data['offset'] \
if 'offset' in data and data['offset'] is not None else 0.1
ap_type = data['aperture_type'] \
if 'aperture_type' in data and data['aperture_type'] is not None else 'All'
include_mesh = data['include_mesh'] \
if 'include_mesh' in data and data['include_mesh'] is not None else True
return cls(data['dimension'], offset, ap_type, include_mesh)
[docs]
def to_dict(self):
"""Get ExteriorApertureGridParameter as a dictionary."""
base = {
'type': 'ExteriorApertureGridParameter',
'dimension': self.dimension,
'offset': self.offset,
'aperture_type': self.aperture_type
}
if not self.include_mesh:
base['include_mesh'] = self.include_mesh
return base
[docs]
def duplicate(self):
"""Get a copy of this object."""
return self.__copy__()
[docs]
def ToString(self):
return self.__repr__()
def __copy__(self):
return ExteriorApertureGridParameter(
self.dimension, self.offset, self.aperture_type, self.include_mesh)
def __repr__(self):
return 'ExteriorApertureGridParameter [dimension: {}] [type: {}]'.format(
self.dimension, self.aperture_type)