Source code for honeybee_radiance.properties.model

# coding=utf-8
"""Model Radiance Properties."""
from honeybee.extensionutil import model_extension_dicts
from honeybee.checkdup import check_duplicate_identifiers
from honeybee.boundarycondition import Surface
from honeybee.typing import invalid_dict_error, clean_rad_string, clean_and_id_rad_string
from honeybee.model import Model

from ..sensorgrid import SensorGrid
from ..view import View
from ..dynamic.group import DynamicShadeGroup, DynamicSubFaceGroup
from ..modifierset import ModifierSet
from ..mutil import dict_to_modifier  # imports all modifiers classes
from ..modifier.material import aBSDF, BSDF
from ..lib.modifiers import black, generic_context
from ..lib.modifiersets import generic_modifier_set_visible

try:
    from itertools import izip as zip  # python 2
except ImportError:
    pass   # python 3


[docs] class ModelRadianceProperties(object): """Radiance Properties for Honeybee Model. Args: host: A honeybee_core Model object that hosts these properties. Properties: * host * sensor_grids * views * modifiers * blk_modifiers * room_modifiers * face_modifiers * shade_modifiers * bsdf_modifiers * modifier_sets * global_modifier_set * dynamic_shade_groups * dynamic_subface_groups * shade_group_identifiers * subface_group_identifiers * has_sensor_grids * has_views """ def __init__(self, host, sensor_grids=None, views=None): """Initialize Model radiance properties.""" self._host = host self.sensor_grids = sensor_grids self.views = views @property def host(self): """Get the Model object hosting these properties.""" return self._host @property def sensor_grids(self): """Get or set an array of SensorGrids that are associated with the model.""" return tuple(self._sensor_grids) @sensor_grids.setter def sensor_grids(self, value): if value: try: self._sensor_grids = list(value) for obj in self._sensor_grids: assert isinstance(obj, SensorGrid), 'Expected SensorGrid for Model' \ ' sensor_grids. Got {}.'.format(type(value)) except (ValueError, TypeError): raise TypeError( 'Model sensor_grids must be an array. Got {}.'.format(type(value))) else: self._sensor_grids = [] @property def views(self): """Get or set an array of Views that are associated with the model.""" return tuple(self._views) @views.setter def views(self, value): if value: try: self._views = list(value) for obj in self._views: assert isinstance(obj, View), 'Expected View for Model' \ ' views. Got {}.'.format(type(value)) except (ValueError, TypeError): raise TypeError( 'Model views must be an array. Got {}.'.format(type(value))) else: self._views = [] @property def modifiers(self): """A list of all unique modifiers in the model. This includes modifiers across all Faces, Apertures, Doors, Shades, Room ModifierSets, and modifiers for any dynamic states assigned to these objects. However, it excludes modifiers in the default modifier set. It also excludes blk_modifiers and the modifier_direct of any states, which can be obtained separately from the blk_modifiers property. """ all_mods = self.room_modifiers + self.face_modifiers + self.shade_modifiers return list(set(all_mods)) @property def blk_modifiers(self): """A list of all unique modifier_blk in the model. This includes modifier_blk across all Faces, Apertures, Doors, and Shades. It also includes modifier_direct for any dynamic states assigned to these objects. """ modifiers = [black] for face in self.host.faces: # check all orphaned Face modifiers self._check_and_add_face_modifier_blk(face, modifiers) for ap in self.host.orphaned_apertures: # check all Aperture modifiers self._check_and_add_dynamic_obj_modifier_blk(ap, modifiers) for dr in self.host.orphaned_doors: # check all Door modifiers self._check_and_add_dynamic_obj_modifier_blk(dr, modifiers) for shade in self.host.shades: self._check_and_add_dynamic_obj_modifier_blk(shade, modifiers) for sm in self.host.shade_meshes: # check all ShadeMesh modifiers self._check_and_add_obj_modifier_blk(sm, modifiers) return list(set(modifiers)) @property def room_modifiers(self): """A list of all unique modifiers assigned to Room ModifierSets.""" room_mods = [] for cnstr_set in self.modifier_sets: room_mods.extend(cnstr_set.modified_modifiers_unique) return list(set(room_mods)) @property def face_modifiers(self): """A list of all unique modifiers assigned to Faces, Apertures and Doors. This includes both objects that are a part of Rooms as well as orphaned objects. It does not include the modifiers of any shades assigned to these objects. Nor does it include any blk modifiers. """ modifiers = [] for face in self.host.faces: # check all orphaned Face modifiers self._check_and_add_face_modifier(face, modifiers) for ap in self.host.orphaned_apertures: # check all Aperture modifiers self._check_and_add_dynamic_obj_modifier(ap, modifiers) for dr in self.host.orphaned_doors: # check all Door modifiers self._check_and_add_dynamic_obj_modifier(dr, modifiers) return list(set(modifiers)) @property def shade_modifiers(self): """A list of all unique modifiers assigned to Shade and ShadeMeshes in the model. """ modifiers = [] for room in self.host.rooms: self._check_and_add_room_modifier_shade(room, modifiers) for face in self.host.orphaned_faces: self._check_and_add_face_modifier_shade(face, modifiers) for ap in self.host.orphaned_apertures: self._check_and_add_obj_modifier_shade(ap, modifiers) for dr in self.host.orphaned_doors: self._check_and_add_obj_modifier_shade(dr, modifiers) for shade in self.host.orphaned_shades: self._check_and_add_orphaned_shade_modifier(shade, modifiers) for shade_mesh in self.host.shade_meshes: self._check_and_add_shade_mesh_modifier(shade_mesh, modifiers) return list(set(modifiers)) @property def bsdf_modifiers(self): """A list of all unique BSDF modifiers in the model. This includes any BSDF modifiers in both the Model.modifiers and the Model.blk_modifiers. """ all_mods = self.modifiers + self.blk_modifiers return list(set(mod for mod in all_mods if isinstance(mod, (aBSDF, BSDF)))) @property def modifier_sets(self): """A list of all unique Room-Assigned ModifierSets in the Model.""" modifier_sets = [] for room in self.host.rooms: if room.properties.radiance._modifier_set is not None: if not self._instance_in_array(room.properties.radiance._modifier_set, modifier_sets): modifier_sets.append(room.properties.radiance._modifier_set) return list(set(modifier_sets)) # catch equivalent modifier sets @property def global_modifier_set(self): """The global radiance modifier set. This is what is used whenever no modifier has been assigned to a given Face/Aperture/Door/Shade and there is no modifier_set assigned to the parent Room. """ return generic_modifier_set_visible @property def dynamic_shade_groups(self): """Get a list of DynamicShadeGroups in the model. These can be used to write dynamic shades into radiance files. """ # gather all of the shades with a common identifier into groups group_dict = {} for shade in self.host.shades: if shade.properties.radiance._dynamic_group_identifier: group_id = shade.properties.radiance._dynamic_group_identifier try: group_dict[group_id].append(shade) except KeyError: group_dict[group_id] = [shade] # return DynamicShadeGroup objects return [DynamicShadeGroup(ident, shades)for ident, shades in group_dict.items()] @property def dynamic_subface_groups(self): """Get a list of DynamicSubFaceGroups in the model. These can be used to write dynamic Apertures and Doors into radiance files. """ # gather all of the subfaces with a common identifier into groups group_dict = {} for subface in self.host.apertures + self.host.doors: if subface.properties.radiance._dynamic_group_identifier: group_id = subface.properties.radiance._dynamic_group_identifier try: group_dict[group_id].append(subface) except KeyError: group_dict[group_id] = [subface] # return DynamicSubFaceGroup objects return [DynamicSubFaceGroup(ident, subf)for ident, subf in group_dict.items()] @property def shade_group_identifiers(self): """Get a list of identifiers for all the DynamicShadeGroups in the model.""" group_ids = set() for shade in self.host.shades: if shade.properties.radiance._dynamic_group_identifier: group_ids.add(shade.properties.radiance._dynamic_group_identifier) return list(group_ids) @property def subface_group_identifiers(self): """Get a list of identifiers for all the DynamicSubFaceGroups in the model.""" group_ids = set() for subface in self.host.apertures + self.host.doors: if subface.properties.radiance._dynamic_group_identifier: group_ids.add(subface.properties.radiance._dynamic_group_identifier) return list(group_ids) @property def has_sensor_grids(self): """Get a boolean for whether there are sensor grids assigned to the model.""" return len(self._sensor_grids) != 0 @property def has_views(self): """Get a boolean for whether there are views assigned to the model.""" return len(self._views) != 0
[docs] def remove_sensor_grids(self): """Remove all sensor grids from the model.""" self._sensor_grids = []
[docs] def remove_views(self): """Remove all views from the model.""" self._views = []
[docs] def add_sensor_grid(self, sensor_grid): """Add a SensorGrid to this model. Args: sensor_grid: A SensorGrid to add to this model. """ assert isinstance(sensor_grid, SensorGrid), \ 'Expected SensorGrid. Got {}.'.format(type(sensor_grid)) self._sensor_grids.append(sensor_grid)
[docs] def add_view(self, view): """Add a View to this model. Args: view: A View to add to this model. """ assert isinstance(view, View), 'Expected View. Got {}.'.format(type(view)) self._views.append(view)
[docs] def add_sensor_grids(self, sensor_grids): """Add a list of SensorGrids to this model.""" for grid in sensor_grids: self.add_sensor_grid(grid)
[docs] def add_views(self, views): """Add a list of Views to this model.""" for view in views: self.add_view(view)
[docs] def faces_by_blk(self): """Get all Faces in the model separated by their blk property. This method will also ensure that any faces with Surface boundary condition are not duplicated in the result but are rather offset from the base face to ensure modifiers on opposite sides of interior Faces are accounted for. Furthermore, this method also ensures that any static interior sub-faces (with Surface boundary condition) only have one of such objects in the output lists. Returns: A tuple with 2 lists: - faces: A list of all faces without a unique modifier_blk. - faces_blk: A list of all opaque faces that have a unique modifier_blk. """ faces, faces_blk = [], [] interior_faces, offset = set(), self.host.tolerance * -2 for face in self.host.faces: if isinstance(face.boundary_condition, Surface): if face.identifier in interior_faces: face = face.duplicate() face.move(face.normal * offset) else: interior_faces.add(face.boundary_condition.boundary_condition_object) for subf in face.apertures + face.doors: if subf.properties.radiance.dynamic_group_identifier is None: if subf.properties.radiance._modifier_blk: faces_blk.append(subf) else: faces.append(subf) if face.properties.radiance._modifier_blk: faces_blk.append(face) else: faces.append(face) return faces, faces_blk
[docs] def subfaces_by_blk(self): """Get model exterior sub-faces (Apertures, Doors) grouped by their blk property. Dynamic sub-faces will be excluded from the output lists. Returns: A tuple with 2 lists: - subfaces: A list of all sub-faces without a unique modifier_blk (just using the default black). - subfaces_blk: A list of all sub-faces that have a unique modifier_blk. """ subfaces, subfaces_blk = [], [] for subf in self.host.apertures + self.host.doors: if subf.properties.radiance.dynamic_group_identifier: continue # sub-face will be accounted for in the dynamic objects if isinstance(subf.boundary_condition, Surface): continue # static interior apertures are part of the scene if subf.properties.radiance._modifier_blk: subfaces_blk.append(subf) else: subfaces.append(subf) return subfaces, subfaces_blk
[docs] def shades_by_blk(self): """Get all Shades in the model separated by their blk property. Dynamic shades will be excluded from the output lists. Returns: A tuple with 2 lists: - shades: A list of all opaque shades without a unique modifier_blk (just using the default black or transparent modifier). - shades_blk: A list of all opaque shades that have a unique modifier_blk. """ shades, shades_blk = [], [] for shade in self.host.shades: if shade.properties.radiance.dynamic_group_identifier: continue # shade will be accounted for in the dynamic objects if shade.properties.radiance._modifier_blk: shades_blk.append(shade) else: shades.append(shade) return shades, shades_blk
[docs] def shade_meshes_by_blk(self): """Get all ShadeMeshes in the model separated by their blk property. Returns: A tuple with 2 lists: - shade_meshes: A list of all shade meshes without a unique modifier_blk (just using the default black or transparent modifier). - shade_meshes_blk: A list of all shade meshes that have a unique modifier_blk. """ shade_meshes, shade_meshes_blk = [], [] for shade in self.host.shade_meshes: if shade.properties.radiance._modifier_blk: shade_meshes_blk.append(shade) else: shade_meshes.append(shade) return shade_meshes, shade_meshes_blk
[docs] def move(self, moving_vec): """Move all sensor_grid and view geometry along a vector. Args: moving_vec: A ladybug_geometry Vector3D with the direction and distance to move the objects. """ for grid in self._sensor_grids: grid.move(moving_vec) for view in self._views: view.move(moving_vec)
[docs] def rotate(self, axis, angle, origin): """Rotate all sensor_grid and view geometry. 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. """ for grid in self._sensor_grids: grid.rotate(axis, angle, origin) for view in self._views: view.rotate(axis, angle, origin)
[docs] def rotate_xy(self, angle, origin): """Rotate all sensor_grids and views counterclockwise in the world XY plane. Args: angle: An angle in degrees. origin: A ladybug_geometry Point3D for the origin around which the object will be rotated. """ for grid in self._sensor_grids: grid.rotate_xy(angle, origin) for view in self._views: view.rotate_xy(angle, origin)
[docs] def reflect(self, plane): """Reflect all sensor_grid and view geometry across a plane. Args: plane: A ladybug_geometry Plane across which the object will be reflected. """ for grid in self._sensor_grids: grid.reflect(plane) for view in self._views: view.reflect(plane)
[docs] def scale(self, factor, origin=None): """Scale all sensor_grid and view geometry by a factor. 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). """ for grid in self._sensor_grids: grid.scale(factor, origin) for view in self._views: view.scale(factor, origin)
[docs] def generate_exterior_face_sensor_grid( self, dimension, offset=0.1, face_type='Wall', punched_geometry=False): """Get a radiance SensorGrid generated from all exterior Faces of this Model. The Face geometry without windows punched into it will be used. This will be None if the Model has no 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 face. Positive numbers indicate an offset towards the exterior. (Default is 0.1, which will offset the grid to be 0.1 unit from the faces). 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). Returns: A honeybee_radiance SensorGrid generated from the exterior Faces of the model. Will be None if the Model has no exterior Faces. """ # generate the mesh grid from the exterior Faces face_grid = self.host.generate_exterior_face_grid( dimension, offset, face_type, punched_geometry) if face_grid is None: # no valid mesh could be generated return None # create the sensor grid from the mesh f_nm = 'Faces' if face_type.title() == 'All' else face_type.title() grid_name = '{}_Exterior{}'.format(self.host.display_name, f_nm) sensor_grid = SensorGrid.from_mesh3d(clean_rad_string(grid_name), face_grid) sensor_grid.display_name = grid_name return sensor_grid
[docs] def generate_exterior_aperture_sensor_grid( self, dimension, offset=0.1, aperture_type='All'): """Get a radiance SensorGrid generated from all exterior Apertures of this Model. This will be None if the Model has no exterior Apertures. Args: dimension: The dimension of the grid cells as a number. offset: A number for how far to offset the grid from the base aperture. Positive numbers indicate an offset towards the exterior while negative numbers indicate an offset towards the interior, essentially modeling the value of sun on the building interior. (Default is 0.1, which will offset the grid to be 0.1 unit from the aperture). aperture_type: Text to specify the type of Aperture that will be used to generate grids. Window indicates Apertures in Walls. Choose from the following. (Default: All). * Window * Skylight * All Returns: A honeybee_radiance SensorGrid generated from the exterior Apertures of the model. Will be None if the Model has no exterior Apertures. """ # generate the mesh grid from the exterior Apertures ap_grid = self.host.generate_exterior_aperture_grid( dimension, offset, aperture_type) if ap_grid is None: # no valid mesh could be generated return None # create the sensor grid from the mesh f_nm = 'Apertures' if aperture_type.title() == 'All' else aperture_type.title() grid_name = '{}_Exterior{}'.format(self.host.display_name, f_nm) sensor_grid = SensorGrid.from_mesh3d(clean_rad_string(grid_name), ap_grid) sensor_grid.display_name = grid_name return sensor_grid
[docs] def check_for_extension(self, raise_exception=True, detailed=False): """Check that the Model is valid for Radiance simulation. This process includes all relevant honeybee-core checks as well as checks that apply only for Radiance. Args: raise_exception: Boolean to note whether a ValueError should be raised if any errors are found. If False, this method will simply return a text string with all errors that were found. (Default: True). detailed: Boolean for whether the returned object is a detailed list of dicts with error info or a string with a message. (Default: False). Returns: A text string with all errors that were found or a list if detailed is True. This string (or list) will be empty if no errors were found. """ # set up defaults to ensure the method runs correctly detailed = False if raise_exception else detailed msgs = [] tol = self.host.tolerance ang_tol = self.host.angle_tolerance # perform checks for duplicate identifiers, which might mess with other checks msgs.append(self.host.check_all_duplicate_identifiers(False, detailed)) # perform several checks for the Honeybee schema geometry rules msgs.append(self.host.check_planar(tol, False, detailed)) msgs.append(self.host.check_self_intersecting(tol, False, detailed)) msgs.append(self.host.check_degenerate_rooms(tol, False, detailed)) # perform geometry checks related to parent-child relationships msgs.append(self.host.check_sub_faces_valid(tol, ang_tol, False, detailed)) msgs.append(self.host.check_sub_faces_overlapping(tol, False, detailed)) msgs.append(self.host.check_rooms_solid(tol, ang_tol, False, detailed)) # perform checks that are specific to Radiance msgs.append(self.check_duplicate_modifier_identifiers(False, detailed)) msgs.append(self.check_duplicate_modifier_set_identifiers(False, detailed)) msgs.append(self.check_duplicate_sensor_grid_identifiers(False, detailed)) msgs.append(self.check_duplicate_view_identifiers(False, detailed)) msgs.append(self.check_sensor_grid_rooms_in_model(False, detailed)) msgs.append(self.check_view_rooms_in_model(False, detailed)) # output a final report of errors or raise an exception full_msgs = [msg for msg in msgs if msg] if detailed: return [m for msg in full_msgs for m in msg] full_msg = '\n'.join(full_msgs) if raise_exception and len(full_msgs) != 0: raise ValueError(full_msg) return full_msg
[docs] def check_all(self, raise_exception=True, detailed=False): """Check all of the aspects of the Model radiance properties. Args: raise_exception: Boolean to note whether a ValueError should be raised if any errors are found. If False, this method will simply return a text string with all errors that were found. (Default: True). detailed: Boolean for whether the returned object is a detailed list of dicts with error info or a string with a message. (Default: False). Returns: A text string with all errors that were found or a list if detailed is True. This string (or list) will be empty if no errors were found. """ # set up defaults to ensure the method runs correctly detailed = False if raise_exception else detailed msgs = [] # perform checks for key honeybee model schema rules msgs.append(self.check_duplicate_modifier_identifiers(False, detailed)) msgs.append(self.check_duplicate_modifier_set_identifiers(False, detailed)) msgs.append(self.check_duplicate_sensor_grid_identifiers(False, detailed)) msgs.append(self.check_duplicate_view_identifiers(False, detailed)) msgs.append(self.check_sensor_grid_rooms_in_model(False, detailed)) msgs.append(self.check_view_rooms_in_model(False, detailed)) # output a final report of errors or raise an exception full_msgs = [msg for msg in msgs if msg] if detailed: return [m for msg in full_msgs for m in msg] full_msg = '\n'.join(full_msgs) if raise_exception and len(full_msgs) != 0: raise ValueError(full_msg) return full_msg
[docs] def check_duplicate_modifier_identifiers(self, raise_exception=True, detailed=False): """Check that there are no duplicate Modifier identifiers in the model. Args: raise_exception: Boolean to note whether a ValueError should be raised if duplicate identifiers are found. (Default: True). detailed: Boolean for whether the returned object is a detailed list of dicts with error info or a string with a message. (Default: False). Returns: A string with the message or a list with a dictionary if detailed is True. """ return check_duplicate_identifiers( self.modifiers, raise_exception, 'Radiance Modifier', detailed, '010001', 'Radiance', error_type='Duplicate Modifier Identifier')
[docs] def check_duplicate_modifier_set_identifiers( self, raise_exception=True, detailed=False): """Check that there are no duplicate ModifierSet identifiers in the model. Args: raise_exception: Boolean to note whether a ValueError should be raised if duplicate identifiers are found. (Default: True). detailed: Boolean for whether the returned object is a detailed list of dicts with error info or a string with a message. (Default: False). Returns: A string with the message or a list with a dictionary if detailed is True. """ return check_duplicate_identifiers( self.modifier_sets, raise_exception, 'ModifierSet', detailed, '010002', 'Radiance', error_type='Duplicate ModifierSet Identifier')
[docs] def check_duplicate_sensor_grid_identifiers( self, raise_exception=True, detailed=False): """Check that there are no duplicate SensorGrid identifiers in the model. Args: raise_exception: Boolean to note whether a ValueError should be raised if duplicate identifiers are found. (Default: True). detailed: Boolean for whether the returned object is a detailed list of dicts with error info or a string with a message. (Default: False). Returns: A string with the message or a list with a dictionary if detailed is True. """ return check_duplicate_identifiers( self.sensor_grids, raise_exception, 'SensorGrid', detailed, '010003', 'Radiance', error_type='Duplicate SensorGrid Identifier')
[docs] def check_duplicate_view_identifiers(self, raise_exception=True, detailed=False): """Check that there are no duplicate View identifiers in the model. Args: raise_exception: Boolean to note whether a ValueError should be raised if duplicate identifiers are found. (Default: True). detailed: Boolean for whether the returned object is a detailed list of dicts with error info or a string with a message. (Default: False). Returns: A string with the message or a list with a dictionary if detailed is True. """ return check_duplicate_identifiers( self.views, raise_exception, 'View', detailed, '010004', 'Radiance', error_type='Duplicate View Identifier')
[docs] def check_sensor_grid_rooms_in_model(self, raise_exception=True, detailed=False): """Check that the room_identifiers of SenorGrids are in the model. Args: raise_exception: Boolean to note whether a ValueError should be raised if SensorGrids reference Rooms that are not in the Model. (Default: True). detailed: Boolean for whether the returned object is a detailed list of dicts with error info or a string with a message. (Default: False). Returns: A string with the message or a list with a dictionary if detailed is True. """ detailed = False if raise_exception else detailed # gather a list of all the missing rooms grid_ids = [(grid, grid.room_identifier) for grid in self.sensor_grids if grid.room_identifier is not None] room_ids = set(room.identifier for room in self.host.rooms) missing_rooms = [] if detailed else set() for grid in grid_ids: if grid[1] not in room_ids: if detailed: missing_rooms.append(grid[0]) else: missing_rooms.add(grid[1]) # if missing rooms were found, then report the issue if len(missing_rooms) != 0: if detailed: all_err = [] for grid in missing_rooms: msg = 'SensorGrid "{}" has a room_identifier that is not in the ' \ 'Model: "{}"'.format(grid.identifier, grid.room_identifier) error_dict = { 'type': 'ValidationError', 'code': '010005', 'error_type': 'SensorGrid Room Not In Model', 'extension_type': 'Radiance', 'element_type': 'SensorGrid', 'element_id': [grid.identifier], 'element_name': [grid.display_name], 'message': msg } all_err.append(error_dict) return all_err else: msg = 'The model has the following missing rooms referenced by sensor ' \ 'grids:\n{}'.format('\n'.join(missing_rooms)) if raise_exception: raise ValueError(msg) return msg return [] if detailed else ''
[docs] def check_view_rooms_in_model(self, raise_exception=True, detailed=False): """Check that the room_identifiers of Views are in the model. Args: raise_exception: Boolean to note whether a ValueError should be raised if Views reference Rooms that are not in the Model. (Default: True). detailed: Boolean for whether the returned object is a detailed list of dicts with error info or a string with a message. (Default: False). Returns: A string with the message or a list with a dictionary if detailed is True. """ detailed = False if raise_exception else detailed # gather a list of all the missing rooms view_ids = [(view, view.room_identifier) for view in self.views if view.room_identifier is not None] room_ids = set(room.identifier for room in self.host.rooms) missing_rooms = [] if detailed else set() for view in view_ids: if view[1] not in room_ids: if detailed: missing_rooms.append(view[0]) else: missing_rooms.add(view[1]) if len(missing_rooms) != 0: if detailed: all_err = [] for view in missing_rooms: msg = 'View "{}" has a room_identifier that is not in the ' \ 'Model: "{}"'.format(view.identifier, view.room_identifier) error_dict = { 'type': 'ValidationError', 'code': '010006', 'error_type': 'View Room Not In Model', 'extension_type': 'Radiance', 'element_type': 'View', 'element_id': [view.identifier], 'element_name': [view.display_name], 'message': msg } all_err.append(error_dict) return all_err else: msg = 'The model has the following missing rooms referenced by ' \ 'views:\n{}'.format('\n'.join(missing_rooms)) if raise_exception: raise ValueError(msg) return msg return [] if detailed else ''
[docs] def apply_properties_from_dict(self, data): """Apply the radiance properties of a dictionary to the host Model of this object. Args: data: A dictionary representation of an entire honeybee-core Model. Note that this dictionary must have ModelRadianceProperties in order for this method to successfully apply the radiance properties. """ assert 'radiance' in data['properties'], \ 'Dictionary possesses no ModelRadianceProperties.' modifiers, modifier_sets = self.load_properties_from_dict(data) # collect lists of radiance property dictionaries room_e_dicts, face_e_dicts, shd_e_dicts, ap_e_dicts, dr_e_dicts = \ model_extension_dicts(data, 'radiance', [], [], [], [], []) # apply radiance properties to objects using the radiance property dictionaries for room, r_dict in zip(self.host.rooms, room_e_dicts): if r_dict is not None: room.properties.radiance.apply_properties_from_dict( r_dict, modifier_sets) for face, f_dict in zip(self.host.faces, face_e_dicts): if f_dict is not None: face.properties.radiance.apply_properties_from_dict( f_dict, modifiers) for aperture, a_dict in zip(self.host.apertures, ap_e_dicts): if a_dict is not None: aperture.properties.radiance.apply_properties_from_dict( a_dict, modifiers) for door, d_dict in zip(self.host.doors, dr_e_dicts): if d_dict is not None: door.properties.radiance.apply_properties_from_dict( d_dict, modifiers) # apply properties to the Shades with separation of Shades from ShadeMesh all_shades = self.host.shades + self.host._shade_meshes for shade, s_dict in zip(all_shades, shd_e_dicts): if s_dict is not None: shade.properties.radiance.apply_properties_from_dict( s_dict, modifiers) # apply the sensor grids and views if they are in the data. rad_data = data['properties']['radiance'] if 'sensor_grids' in rad_data and rad_data['sensor_grids'] is not None: self.sensor_grids = \ [SensorGrid.from_dict(grid) for grid in rad_data['sensor_grids']] if 'views' in rad_data and rad_data['views'] is not None: self.views = [View.from_dict(view) for view in rad_data['views']]
[docs] def to_dict(self): """Return Model radiance properties as a dictionary.""" base = {'radiance': {'type': 'ModelRadianceProperties'}} # add the global modifier set to the dictionary gs = self.global_modifier_set.to_dict(abridged=True, none_for_defaults=False) gs['type'] = 'GlobalModifierSet' del gs['identifier'] g_mods = self.global_modifier_set.modifiers_unique gs['modifiers'] = [mod.to_dict() for mod in g_mods] gs['context_modifier'] = generic_context.identifier gs['modifiers'].append(generic_context.to_dict()) base['radiance']['global_modifier_set'] = gs # add all ModifierSets to the dictionary base['radiance']['modifier_sets'] = [] modifier_sets = self.modifier_sets for mod_set in modifier_sets: base['radiance']['modifier_sets'].append(mod_set.to_dict(abridged=True)) # add all unique Modifiers to the dictionary room_mods = [] for mod_set in modifier_sets: room_mods.extend(mod_set.modified_modifiers_unique) all_mods = room_mods + self.face_modifiers + self.shade_modifiers modifiers = list(set(all_mods)) base['radiance']['modifiers'] = [] for mod in modifiers: base['radiance']['modifiers'].append(mod.to_dict()) # add the sensor grids and views to the dictionary if len(self._sensor_grids) != 0: base['radiance']['sensor_grids'] = \ [grid.to_dict() for grid in self._sensor_grids] if len(self._views) != 0: base['radiance']['views'] = [view.to_dict() for view in self._views] return base
[docs] def duplicate(self, new_host=None): """Get a copy of this object. new_host: A new Model object that hosts these properties. If None, the properties will be duplicated with the same host. """ _host = new_host or self._host new_grids = [sg.duplicate() for sg in self._sensor_grids] new_views = [vw.duplicate() for vw in self._views] return ModelRadianceProperties(_host, new_grids, new_views)
[docs] @staticmethod def load_properties_from_dict(data): """Load model radiance properties of a dictionary to Python objects. Loaded objects include Modifiers and ModifierSets. The function is called when re-serializing a Model object from a dictionary to load honeybee_radiance objects into their Python object form before applying them to the Model geometry. Args: data: A dictionary representation of an entire honeybee-core Model. Note that this dictionary must have ModelRadianceProperties in order for this method to successfully load the radiance properties. Returns: A tuple with two elements - modifiers: A dictionary with identifiers of modifiers as keys and Python modifier objects as values. - modifier_sets: A dictionary with identifiers of modifier sets as keys and Python modifier set objects as values. """ assert 'radiance' in data['properties'], \ 'Dictionary possesses no ModelRadianceProperties.' # process all modifiers in the ModelRadianceProperties dictionary modifiers = {} if 'modifiers' in data['properties']['radiance'] and \ data['properties']['radiance']['modifiers'] is not None: for mod in data['properties']['radiance']['modifiers']: try: modifiers[mod['identifier']] = dict_to_modifier(mod) except Exception as e: invalid_dict_error(mod, e) # process all modifier sets in the ModelRadianceProperties dictionary modifier_sets = {} if 'modifier_sets' in data['properties']['radiance'] and \ data['properties']['radiance']['modifier_sets'] is not None: if 'modifier_sets' in data['properties']['radiance'] and \ data['properties']['radiance']['modifier_sets'] is not None: for m_set in data['properties']['radiance']['modifier_sets']: try: if m_set['type'] == 'ModifierSet': modifier_sets[m_set['identifier']] = \ ModifierSet.from_dict(m_set) else: modifier_sets[m_set['identifier']] = \ ModifierSet.from_dict_abridged(m_set, modifiers) except Exception as e: invalid_dict_error(m_set, e) return modifiers, modifier_sets
[docs] @staticmethod def dump_properties_to_dict(modifiers=None, modifier_sets=None): """Get a ModelRadianceProperties dictionary from arrays of Python objects. Args: modifiers: A list or tuple of radiance modifier objects. modifier_sets: A list or tuple of modifier set objects. Returns: data: A dictionary representation of ModelRadianceProperties. Note that all objects in this dictionary will follow the abridged schema. """ # process the modifiers and modifier sets all_m = [] if modifiers is None else list(modifiers) all_mod_sets = [] if modifier_sets is None else list(modifier_sets) for mod_set in all_mod_sets: all_m.extend(mod_set.modified_modifiers) # get sets of unique objects all_mods = set(all_m) # add all object dictionaries into one object data = {'type': 'ModelRadianceProperties'} data['modifiers'] = [m.to_dict() for m in all_mods] data['modifier_sets'] = [ms.to_dict(abridged=True) for ms in all_mod_sets] return data
[docs] @staticmethod def reset_resource_ids_in_dict( data, add_uuid=False, reset_modifiers=True, reset_modifier_sets=True): """Reset the identifiers of radiance resource objects in a Model dictionary. This is useful when human-readable names are needed when the model is exported to other formats like Rad and the uniqueness of the identifiers is less of a concern. Args: data: A dictionary representation of an entire honeybee-core Model. Note that this dictionary must have ModelRadianceProperties in order for this method to successfully edit the radiance properties. add_uuid: Boolean to note whether newly-generated resource object IDs should be derived only from a cleaned display_name (False) or whether this new ID should also have a unique set of 8 characters appended to it to guarantee uniqueness. (Default: False). reset_modifiers: Boolean to note whether the IDs of all modifiers in the model should be reset or kept. (Default: True). reset_modifier_sets: Boolean to note whether the IDs of all modifier sets in the model should be reset or kept. (Default: True). Returns: A new Model dictionary with the resource identifiers reset. All references to the reset resources will be correct and valid in the resulting dictionary, assuming that the input is valid. """ model = Model.from_dict(data) modifiers, modifier_sets = \ model.properties.radiance.load_properties_from_dict(data) res_func = clean_and_id_rad_string if add_uuid else clean_rad_string # change the identifiers of the modifiers if reset_modifiers: model_mods = set() for mod in model.properties.radiance.modifiers: mod.unlock() old_id, new_id = mod.identifier, res_func(mod.display_name) mod.identifier = new_id modifiers[old_id].unlock() modifiers[old_id].identifier = new_id model_mods.add(old_id) for old_id, mod in modifiers.items(): if old_id not in model_mods: mod.unlock() mod.identifier = res_func(mod.display_name) # change the identifiers of the modifier_sets if reset_modifier_sets: model_ms = set() for ms in model.properties.radiance.modifier_sets: ms.unlock() old_id, new_id = ms.identifier, res_func(ms.display_name) ms.identifier = new_id modifier_sets[old_id].unlock() modifier_sets[old_id].identifier = new_id model_ms.add(old_id) for old_id, ms in modifier_sets.items(): if old_id not in model_ms: ms.unlock() ms.identifier = res_func(ms.display_name) # create the model dictionary and update any unreferenced resources model_dict = model.to_dict() mr_props = model_dict['properties']['radiance'] mr_props['modifiers'] = [mod.to_dict() for mod in modifiers.values()] mr_props['modifier_sets'] = \ [ms.to_dict(abridged=True) for ms in modifier_sets.values()] return model_dict
def _check_and_add_room_modifier_shade(self, room, modifiers): """Check if a modifier is assigned to a Room's shades and add it to a list.""" self._check_and_add_obj_modifier_shade(room, modifiers) for face in room.faces: # check all Face modifiers self._check_and_add_face_modifier_shade(face, modifiers) def _check_and_add_face_modifier_shade(self, face, modifiers): """Check if a modifier is assigned to a Face's shades and add it to a list.""" self._check_and_add_obj_modifier_shade(face, modifiers) for ap in face.apertures: # check all Aperture modifiers self._check_and_add_obj_modifier_shade(ap, modifiers) for dr in face.doors: # check all Door Shade modifiers self._check_and_add_obj_modifier_shade(dr, modifiers) def _check_and_add_obj_modifier_shade(self, subf, modifiers): """Check if a modifier is assigned to an object's shades and add it to a list.""" for shade in subf.shades: self._check_and_add_dynamic_obj_modifier(shade, modifiers) def _check_and_add_face_modifier(self, face, modifiers): """Check if a modifier is assigned to a face and add it to a list.""" self._check_and_add_obj_modifier(face, modifiers) for ap in face.apertures: # check all Aperture modifiers self._check_and_add_dynamic_obj_modifier(ap, modifiers) for dr in face.doors: # check all Door modifiers self._check_and_add_dynamic_obj_modifier(dr, modifiers) def _check_and_add_face_modifier_blk(self, face, modifiers): """Check if a modifier_blk is assigned to a face and add it to a list.""" self._check_and_add_obj_modifier_blk(face, modifiers) for ap in face.apertures: # check all Aperture modifiers self._check_and_add_dynamic_obj_modifier_blk(ap, modifiers) for dr in face.doors: # check all Door modifiers self._check_and_add_dynamic_obj_modifier_blk(dr, modifiers) def _check_and_add_obj_modifier(self, obj, modifiers): """Check if a modifier is assigned to an object and add it to a list.""" mod = obj.properties.radiance._modifier if mod is not None: if not self._instance_in_array(mod, modifiers): modifiers.append(mod) def _check_and_add_dynamic_obj_modifier(self, obj, modifiers): """Check if a modifier is assigned to a dynamic object and add it to a list.""" mod = obj.properties.radiance._modifier if mod is not None: if not self._instance_in_array(mod, modifiers): modifiers.append(mod) for st in obj.properties.radiance._states: stm = (st._modifier, st._modifier_direct) + \ tuple(s.modifier for s in st._shades) for mod in stm: if mod is not None: if not self._instance_in_array(mod, modifiers): modifiers.append(mod) def _check_and_add_obj_modifier_blk(self, obj, modifiers): """Check if a modifier_blk is assigned to an object and add it to a list.""" mod = obj.properties.radiance._modifier_blk if mod is not None: if not self._instance_in_array(mod, modifiers): modifiers.append(mod) def _check_and_add_dynamic_obj_modifier_blk(self, obj, modifiers): """Check if a modifier_blk is assigned to a dynamic object and add it to a list. """ mod = obj.properties.radiance._modifier_blk if mod is not None: if not self._instance_in_array(mod, modifiers): modifiers.append(mod) for st in obj.properties.radiance._states: for s in st._shades: mod = s.modifier if mod is not None: if not self._instance_in_array(mod, modifiers): modifiers.append(mod) def _check_and_add_orphaned_shade_modifier(self, obj, modifiers): """Check if a modifier is assigned to an object and add it to a list.""" mod = obj.properties.radiance._modifier if mod is not None: if not self._instance_in_array(mod, modifiers): modifiers.append(mod) else: def_mod = generic_context if obj.is_detached else \ generic_modifier_set_visible.shade_set.exterior_modifier if not self._instance_in_array(def_mod, modifiers): modifiers.append(def_mod) for st in obj.properties.radiance._states: stm = (st._modifier, st._modifier_direct) + \ tuple(s.modifier for s in st._shades) for mod in stm: if mod is not None: if not self._instance_in_array(mod, modifiers): modifiers.append(mod) def _check_and_add_shade_mesh_modifier(self, obj, modifiers): """Check if a modifier is assigned to an object and add it to a list.""" mod = obj.properties.radiance._modifier if mod is not None: if not self._instance_in_array(mod, modifiers): modifiers.append(mod) else: def_mod = generic_context if obj.is_detached else \ generic_modifier_set_visible.shade_set.exterior_modifier if not self._instance_in_array(def_mod, modifiers): modifiers.append(def_mod) @staticmethod def _instance_in_array(object_instance, object_array): """Check if a specific object instance is already in an array. This can be much faster than `if object_instance in object_array` when you expect to be testing a lot of the same instance of an object for inclusion in an array since the builtin method uses an == operator to test inclusion. """ for val in object_array: if val is object_instance: return True return False
[docs] def ToString(self): return self.__repr__()
def __repr__(self): return 'Model Radiance Properties: [host: {}]'.format(self.host.display_name)