Source code for dragonfly_trace.properties.model

# coding=utf-8
"""Model TACE Properties."""
from __future__ import division

from ladybug_geometry.geometry2d import Point2D, Polygon2D
from ladybug_geometry.geometry3d import Point3D, Face3D
from honeybee.units import parse_distance_string


[docs] class ModelTraceProperties(object): """TRACE Properties for Dragonfly Model. Args: host: A dragonfly_core Model object that hosts these properties. Properties: * host """ # dictionary mapping validation error codes to a corresponding check function ERROR_MAP = { '040101': 'check_no_room_2d_floor_plate_holes', '040102': 'check_story_floor_plates', '040103': 'check_no_skylights', '040104': 'check_windows_above_origin' } def __init__(self, host): """Initialize ModelTraceProperties.""" self._host = host @property def host(self): """Get the Model object hosting these properties.""" return self._host
[docs] def check_for_extension(self, raise_exception=True, detailed=False): """Check that the Model is valid for TRACE 3D Plus simulation. This process includes all relevant dragonfly-core checks as well as checks that apply only for TRACE 3D Plus. 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 checks for key dragonfly model schema rules msgs.append(self.host.check_degenerate_room_2ds(tol, False, detailed)) msgs.append(self.host.check_self_intersecting_room_2ds(tol, False, detailed)) msgs.append(self.host.check_plenum_depths(tol, False, detailed)) msgs.append(self.host.check_window_parameters_valid(tol, False, detailed)) msgs.append(self.host.check_no_room2d_overlaps(tol, False, detailed)) msgs.append(self.host.check_collisions_between_stories(tol, False, detailed, None)) msgs.append(self.host.check_roofs_above_rooms(tol, False, detailed)) msgs.append(self.host.check_room2d_floor_heights_valid(False, detailed)) msgs.append(self.host.check_missing_adjacencies(False, detailed)) msgs.append(self.host.check_all_room3d(tol, ang_tol, False, detailed)) # perform checks that are specific to TRACE 3D Plus msgs.append(self.check_no_room_2d_floor_plate_holes(False, detailed)) msgs.append(self.check_story_floor_plates(tol, False, detailed)) msgs.append(self.check_no_skylights(False, detailed)) msgs.append(self.check_windows_above_origin(tol, 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_generic(self, raise_exception=True, detailed=False): """Check generic of the aspects of the Model TRACE 3D Plus properties. This includes checks for everything except holes in floor plates and courtyard stories. 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. 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 = [] # 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 TRACE 3D Plus 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. 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 # perform checks for specific TRACE 3D Plus simulation rules msgs.append(self.check_no_room_2d_floor_plate_holes(False, detailed)) msgs.append(self.check_story_floor_plates(tol, False, detailed)) msgs.append(self.check_no_skylights(False, detailed)) msgs.append(self.check_windows_above_origin(tol, 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_no_room_2d_floor_plate_holes(self, raise_exception=True, detailed=False): """Check whether any Room2D floor geometry has holes. TRACE 3D Plus currently has no way to represent holes. Args: raise_exception: If True, a ValueError will be raised if the Room2D floor plate has one or more holes. (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 msgs = [] for room in self.host.room_2ds: msg = room.properties.trace.check_no_floor_plate_holes(False, detailed) if detailed: msgs.extend(msg) elif msg != '': msgs.append(msg) if detailed: return msgs full_msg = '\n'.join(msgs) if raise_exception and len(msgs) != 0: raise ValueError(full_msg) return full_msg
[docs] def check_story_floor_plates(self, tolerance=None, raise_exception=True, detailed=False): """Check Story floor plates for courtyards. Args: tolerance: The tolerance to be used when joining the Room2D floor plates together into a Story floor plate. If None, the Model tolerance will be used. (Default: None). raise_exception: Boolean to note whether a ValueError should be raised if the story contains a courtyard. (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. """ # establish the tolerance and gap width at which point it is clearly a courtyard tolerance = self.host.tolerance if tolerance is None else tolerance court_width = parse_distance_string('1ft', self.host.units) # loop through the stories and identify any courtyards story_msgs = [] for bldg in self.host.buildings: for story in bldg.unique_stories: floor_geos = [room.floor_geometry for room in story.room_2ds] joined_geos = self._grouped_floor_boundary(floor_geos, tolerance) c_count, help_geo = 0, [] for geo in joined_geos: if geo.has_holes: for hole in geo.holes: try: h_geo = Face3D(hole) h_geo = h_geo.remove_colinear_vertices(court_width) max_len = max(s.length for s in h_geo.boundary_segments) tol_area = max_len * court_width if h_geo.area > tol_area: c_count += 1 help_geo.append(h_geo) except (AssertionError, ValueError): pass # gap is too small to be a true courtyard if c_count != 0: hole_msg = 'a courtyard' if c_count == 1 \ else '{} courtyards'.format(c_count) msg = 'The geometry of Story "{}" contains {}, which TRACE 3D Plus ' \ 'cannot represent.'.format(story.display_name, hole_msg) if detailed: msg = { 'type': 'ValidationError', 'code': '040102', 'error_type': 'Story Floor Plate Contains Courtyards', 'extension_type': 'TRACE3D', 'element_type': 'Room2D', 'element_id': [r.identifier for r in story.room_2ds], 'element_name': [r.display_name for r in story.room_2ds], 'message': msg, 'helper_geometry': [f.to_dict() for f in help_geo] } story_msgs.append(msg) if detailed: return story_msgs if story_msgs != []: msg = 'The following Stories have issues with their floor plates' \ ':\n{}'.format('\n'.join(story_msgs)) if raise_exception: raise ValueError(msg) return msg return ''
[docs] def check_no_skylights(self, raise_exception=True, detailed=False): """Check whether any Room2D floor geometry has skylights. Args: raise_exception: If True, a ValueError will be raised if a Room2D has skylights. (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 msgs = [] for room in self.host.room_2ds: msg = room.properties.trace.check_no_skylights(False, detailed) if detailed: msgs.extend(msg) elif msg != '': msgs.append(msg) if detailed: return msgs full_msg = '\n'.join(msgs) if raise_exception and len(msgs) != 0: raise ValueError(full_msg) return full_msg
[docs] def check_windows_above_origin( self, tolerance=None, raise_exception=True, detailed=False ): """Check whether the Room2D has windows below the scene origin. TRACE 3D Plus currently fails to import these windows since it believes that they are below ground. Args: tolerance: The maximum difference between coordinate values of two vertices at which they can be considered equivalent. (Default: 0.01, suitable for objects in meters). raise_exception: Boolean to note whether a ValueError should be raised if the story contains a courtyard. (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 msgs = [] for room in self.host.room_2ds: msg = room.properties.trace.check_windows_above_origin(tolerance, False, detailed) if detailed: msgs.extend(msg) elif msg != '': msgs.append(msg) if detailed: return msgs full_msg = '\n'.join(msgs) if raise_exception and len(msgs) != 0: raise ValueError(full_msg) return full_msg
@staticmethod def _grouped_floor_boundary(floor_geos, tolerance=0.01): """Get a list of Face3D for the boundary around several horizontal Face3Ds. Args: floor_geos: A list of Honeybee Rooms for which the horizontal boundary will be computed. tolerance: The maximum difference between coordinate values of two vertices at which they can be considered equivalent. (Default: 0.01, suitable for objects in meters). """ # remove colinear vertices and degenerate faces clean_floor_geos = [] for geo in floor_geos: try: clean_floor_geos.append(geo.remove_colinear_vertices(tolerance)) except AssertionError: # degenerate geometry to ignore pass if len(clean_floor_geos) == 0: return [] # no Room boundary to be found # convert the floor Face3Ds into counterclockwise Polygon2Ds floor_polys, z_vals = [], [] for flr_geo in clean_floor_geos: z_vals.append(flr_geo.min.z) b_poly = Polygon2D([Point2D(pt.x, pt.y) for pt in flr_geo.boundary]) floor_polys.append(b_poly) if flr_geo.has_holes: for hole in flr_geo.holes: h_poly = Polygon2D([Point2D(pt.x, pt.y) for pt in hole]) floor_polys.append(h_poly) z_min = min(z_vals) # find the joined intersected boundary closed_polys = Polygon2D.joined_intersected_boundary(floor_polys, tolerance) # remove colinear vertices from the resulting polygons clean_polys = [] for poly in closed_polys: try: clean_polys.append(poly.remove_colinear_vertices(tolerance)) except AssertionError: pass # degenerate polygon to ignore # figure out if polygons represent holes in the others and make Face3D if len(clean_polys) == 0: return [] elif len(clean_polys) == 1: # can be represented with a single Face3D pts3d = [Point3D(pt.x, pt.y, z_min) for pt in clean_polys[0]] return [Face3D(pts3d)] else: # need to separate holes from distinct Face3Ds bound_faces = [] for poly in clean_polys: pts3d = tuple(Point3D(pt.x, pt.y, z_min) for pt in poly) bound_faces.append(Face3D(pts3d)) return Face3D.merge_faces_to_holes(bound_faces, tolerance)
[docs] def ToString(self): return self.__repr__()
def __repr__(self): return 'Model TRACE Properties: [host: {}]'.format(self.host.display_name)