Source code for honeybee_radiance.lightpath

"""Utilities to determine the path of light taken through interior spaces of a model."""
from honeybee.boundarycondition import Surface, Outdoors
from honeybee.facetype import AirBoundary
from honeybee.aperture import Aperture
from honeybee.door import Door


[docs] def light_path_from_room(model, room_identifier, static_name='__static_apertures__'): """Get the dynamic aperture groups that need to be simulated for a room in a model. Args: model: A honeybee Model object which will be used to identify the aperture groups that are needed to simulate a single room. room_identifier: Text string for the identifier of the Room in the model for which the light path will be computed. static_name: An optional name to be used to refer to static apertures found within the model. (Default: '__static_apertures__'). Returns: A list of lists where each sub-list contains the identifiers of the aperture groups through which light is passing Usage: .. code-block:: python from honeybee_radiance.lightpath import light_path_from_room from honeybee.room import Room from honeybee.model import Model from ladybug_geometry.geometry3d.pointvector import Point3D room1 = Room.from_box('Tiny_House_Room1', 5, 10, 3) room1[2].apertures_by_ratio(0.4, 0.01) # east interior window room1[3].apertures_by_ratio(0.4, 0.01) # outdoor south window south_ap1 = room1[3].apertures[0] south_ap1.properties.radiance.dynamic_group_identifier = 'SouthWindow1' room2 = Room.from_box('Tiny_House_Room2', 5, 10, 3, origin=Point3D(5, 0, 0)) room2[4].apertures_by_ratio(0.4, 0.01) # west interior window room2[1].apertures_by_ratio(0.4, 0.01) # outdoor north window north_ap2 = room2[1].apertures[0] north_ap2.properties.radiance.dynamic_group_identifier = 'NorthWindow2' Room.solve_adjacency([room1, room2], 0.01) model = Model('TinyHouse', [room1, room2]) print(light_path_from_room(model, room1.identifier)) >> [['SouthWindow1'], ['__static_apertures__', 'NorthWindow2']] """ #TODO: Consider to modify light path if there are two consecutive # items, such as ['__static_apertures__', '__static_apertures__']. def get_adjacent_room(face): """Get the adjacent Room of a Face. Args: face: A Face object for which to find the adjacent room. Returns: A Room object. """ adj_room_identifier = \ face.boundary_condition.boundary_condition_objects[-1] adj_room = model.rooms_by_identifier([adj_room_identifier])[0] return adj_room def trace_light_path( s_face, s_light_path, parent_room, passed_rooms = set(), static_name='__static_apertures__'): """Trace light path recursively. This function will return the light path for a single face (Aperture, Door, or AirBoundary). The function will trace the light path recursively meaning that it will enter rooms adjacent to interior Apertures and Doors as well as AirBoundaries. Args: s_face: The Aperture, Door, or AirBoundary for which to trace the light path. s_light_path: The base light path of the s_face. This is a single item list which contains the dynamic group identifier or the static name, however, if s_face is an AirBoundary the list will be empty. parent_room: A Room object. This Room is the parent Room of the s_face. In this function the parent room is used to check that we do not go back into the parent room, and enter a infinite recursive loop. passed_rooms: A set of Room objects. This set contains the rooms that have already been processed. This is used to not enter an infinite recursive loop, e.g., if multiple are connected by interior apertures. static_name: An optional name to be used to refer to static apertures found within the model. (Default: '__static_apertures__'). Returns: A list of lists where each sub-list contains the identifiers of the ApertureGroups through which light is passing. """ passed_rooms.add(parent_room) s_face_light_path = [] room = get_adjacent_room(s_face) for face in room.faces: adj_s_faces = face.apertures + face.doors if isinstance(face.type, AirBoundary): adj_s_faces = adj_s_faces + (face,) for adj_s_face in adj_s_faces: s_light_path_duplicate = list(s_light_path) if isinstance(adj_s_face, Aperture): if adj_s_face.properties.radiance._dynamic_group_identifier: light_path_id = \ adj_s_face.properties.radiance._dynamic_group_identifier else: light_path_id = static_name elif isinstance(adj_s_face, Door): if adj_s_face.is_glass: if adj_s_face.properties.radiance._dynamic_group_identifier: light_path_id = \ [adj_s_face.properties.radiance._dynamic_group_identifier] else: light_path_id = static_name else: break if isinstance(adj_s_face.boundary_condition, Surface): adj_room = get_adjacent_room(adj_s_face) # check that adjacent room is not in passed_rooms if not adj_room in passed_rooms: if isinstance(adj_s_face, (Aperture, Door)): # do not append if face is an AirBoundary if not s_light_path_duplicate: s_light_path_duplicate.append(light_path_id) elif not light_path_id == s_light_path_duplicate[-1]: s_light_path_duplicate.append(light_path_id) _s_face_light_path = trace_light_path( adj_s_face, s_light_path_duplicate, room, passed_rooms=passed_rooms ) s_face_light_path.extend(_s_face_light_path) elif not isinstance(adj_s_face, (Aperture, Door)) and \ isinstance(adj_s_face.boundary_condition, Outdoors): # if not Aperture or Door (i.e. AirBoundary), we just pass # if the boundary condition is Outdoors pass else: if not s_light_path_duplicate: s_light_path_duplicate.append(light_path_id) elif not light_path_id == s_light_path_duplicate[-1]: s_light_path_duplicate.append(light_path_id) if s_light_path_duplicate not in s_face_light_path: s_face_light_path.append(s_light_path_duplicate) return s_face_light_path _light_path = [] room = model.rooms_by_identifier([room_identifier])[0] for face in room.faces: s_faces = face.apertures + face.doors if isinstance(face.type, AirBoundary): s_faces = s_faces + (face,) for s_face in s_faces: if isinstance(s_face, Aperture): # create base light path if Aperture or Door if s_face.properties.radiance._dynamic_group_identifier: s_light_path = \ [s_face.properties.radiance._dynamic_group_identifier] else: s_light_path = [static_name] elif isinstance(s_face, Door): if s_face.is_glass: if s_face.properties.radiance._dynamic_group_identifier: s_light_path = \ [s_face.properties.radiance._dynamic_group_identifier] else: s_light_path = [static_name] else: s_light_path = [] else: # else AirBoundary, no light path id s_light_path = [] if isinstance(s_face.boundary_condition, Surface): if isinstance(s_face, Door): if not s_face.is_glass: break # boundary condition, trace light path recursively for s_face s_face_light_path = \ trace_light_path(s_face, s_light_path, room) _light_path.extend(s_face_light_path) elif not isinstance(s_face, (Aperture, Door)): # if it is AirBoundary but without Surface boundary condition pass else: if isinstance(s_face, Door): break # no boundary condition, tracing ends here if not s_light_path in _light_path: _light_path.append(s_light_path) # remove any duplicates light_path = [] for lp in _light_path: if not lp in light_path: light_path.append(lp) return light_path