diff --git a/honeybee_energy/_extend_honeybee.py b/honeybee_energy/_extend_honeybee.py index 5a82da1d9..4fe51ebe0 100644 --- a/honeybee_energy/_extend_honeybee.py +++ b/honeybee_energy/_extend_honeybee.py @@ -1,7 +1,12 @@ # coding=utf-8 from honeybee.properties import ModelProperties, RoomProperties, FaceProperties, \ ShadeProperties, ApertureProperties, DoorProperties -import honeybee.writer as writer +import honeybee.writer.door as door_writer +import honeybee.writer.aperture as aperture_writer +import honeybee.writer.shade as shade_writer +import honeybee.writer.face as face_writer +import honeybee.writer.room as room_writer +import honeybee.writer.model as model_writer import honeybee.boundarycondition as hbc from .properties.model import ModelEnergyProperties @@ -10,11 +15,12 @@ from .properties.shade import ShadeEnergyProperties from .properties.aperture import ApertureEnergyProperties from .properties.door import DoorEnergyProperties -from .writer import face_to_idf +from .writer import model_to_idf, room_to_idf, face_to_idf, shade_to_idf, \ + aperture_to_idf, door_to_idf from .boundarycondition import Adiabatic # set a hidden energy attribute on each core geometry Property class to None -# define methods to produce energy property instances on each Proprety instance +# define methods to produce energy property instances on each Property instance ModelProperties._energy = None RoomProperties._energy = None FaceProperties._energy = None @@ -68,7 +74,12 @@ def door_energy_properties(self): DoorProperties.energy = property(door_energy_properties) # add energy writer to idf -setattr(writer, 'idf', face_to_idf) +model_writer.idf = model_to_idf +room_writer.idf = room_to_idf +face_writer.idf = face_to_idf +shade_writer.idf = shade_to_idf +aperture_writer.idf = aperture_to_idf +door_writer.idf = door_to_idf # extend boundary conditions setattr(hbc, 'Adiabatic', Adiabatic) diff --git a/honeybee_energy/properties/model.py b/honeybee_energy/properties/model.py index d5a8fdbb5..cd6d59b4c 100644 --- a/honeybee_energy/properties/model.py +++ b/honeybee_energy/properties/model.py @@ -18,6 +18,7 @@ from ..schedule.typelimit import ScheduleTypeLimit from ..schedule.ruleset import ScheduleRuleset from ..schedule.fixedinterval import ScheduleFixedInterval +from ..writer import generate_idf_string try: from itertools import izip as zip # python 2 @@ -261,6 +262,33 @@ def program_types(self): program_types.append(room.properties.energy._program_type) return list(set(program_types)) # catch equivalent program types + def building_idf(self, solar_distribution='FullInteriorAndExteriorWithReflections'): + """Get an IDF string for Building that this model represents. + + Args: + solar_distribution: Text desribing how EnergyPlus should treat beam solar + radiation reflected from surfaces. Default: + FullInteriorAndExteriorWithReflections. Choose from the following: + * MinimalShadowing + * FullExterior + * FullInteriorAndExterior + * FullExteriorWithReflections + * FullInteriorAndExteriorWithReflections + """ + values = (self.host.name, + self.host.north_angle, + self.terrain_type, + '', + '', + solar_distribution) + comments = ('name', + 'north axis', + 'terrain', + 'loads convergence tolerance', + 'temperature convergence tolerance', + 'solar distribution') + return generate_idf_string('Building', values, comments) + def check_duplicate_material_names(self, raise_exception=True): """Check that there are no duplicate Material names in the model.""" material_names = set() diff --git a/honeybee_energy/simulation/output.py b/honeybee_energy/simulation/output.py index b35e1f50f..353f3539b 100644 --- a/honeybee_energy/simulation/output.py +++ b/honeybee_energy/simulation/output.py @@ -478,7 +478,7 @@ def to_idf(self): 'Output:Table:SummaryReports', self.summary_reports, r_comments) if \ len(self._summary_reports) != 0 else None sqlite = generate_idf_string( - 'Output:SQLite', ('SimpleAndTabular,'), ('option type',)) if \ + 'Output:SQLite', ('SimpleAndTabular',), ('option type',)) if \ self.include_sqlite else None surfaces_list = generate_idf_string( 'Output:Surfaces:List', ('Details',), ('report type',)) diff --git a/honeybee_energy/simulation/sizing.py b/honeybee_energy/simulation/sizing.py index dedb769ce..ee79cd3b6 100644 --- a/honeybee_energy/simulation/sizing.py +++ b/honeybee_energy/simulation/sizing.py @@ -57,10 +57,10 @@ def from_idf(cls, idf_string): Args: idf_string: A text string fully describing an EnergyPlus - SizingParameters definition. + Sizing:Parameters definition. """ # check the inputs - ep_strs = parse_idf_string(idf_string, 'SizingParameters,') + ep_strs = parse_idf_string(idf_string, 'Sizing:Parameters,') # extract the properties from the string heating_factor = 1.25 @@ -99,7 +99,7 @@ def to_idf(self): """Get an EnergyPlus string representation of the SizingParameters.""" values = (self.heating_factor, self.cooling_factor) comments = ('heating factor', 'cooling factor') - return generate_idf_string('SizingParameters', values, comments) + return generate_idf_string('Sizing:Parameters', values, comments) def to_dict(self): """SizingParameter dictionary representation.""" diff --git a/honeybee_energy/simulationparameter.py b/honeybee_energy/simulationparameter.py index 91fbd94ab..8c5ef27fb 100644 --- a/honeybee_energy/simulationparameter.py +++ b/honeybee_energy/simulationparameter.py @@ -25,6 +25,7 @@ class SimulationParameter(object): * simulation_control * shadow_calculation * sizing_parameter + * global_geometry_rules """ __slots__ = ('_output', '_run_period', '_timestep', '_simulation_control', '_shadow_calculation', '_sizing_parameter') @@ -144,6 +145,17 @@ def sizing_parameter(self, value): else: self._sizing_parameter = SizingParameter() + @property + def global_geometry_rules(self): + """Get an IDF string for the official honeybee global geometry rules.""" + values = ('UpperLeftCorner', + 'Counterclockwise', + 'Relative') + comments = ('starting vertex position', + 'vertex entry direction', + 'coordinate system') + return generate_idf_string('GlobalGeometryRules', values, comments) + @classmethod def from_idf(cls, idf_string): """Create a SimulationParameter object from an EnergyPlus IDF text string. @@ -169,7 +181,7 @@ def from_idf(cls, idf_string): sh_calc_pattern = re.compile(r"(?i)(ShadowCalculation,[\s\S]*?;)") bldg_pattern = re.compile(r"(?i)(Building,[\s\S]*?;)") control_pattern = re.compile(r"(?i)(SimulationControl,[\s\S]*?;)") - sizing_pattern = re.compile(r"(?i)(SizingParameters,[\s\S]*?;)") + sizing_pattern = re.compile(r"(?i)(Sizing:Parameters,[\s\S]*?;)") # process the outputs within the idf_string try: @@ -286,22 +298,42 @@ def to_idf(self): objects that make up the SimulationParameter (ie. RunPeriod, SimulationControl, etc.), """ - header_str = '!- ============== SIMULATION PARAMETERS ==============\n' + sim_param_str = ['!- ==========================================\n' + '!- ========= SIMULATION PARAMETERS =========\n' + '!- ==========================================\n'] + + # add the outputs requested table_style, output_vars, reports, sqlite, surfaces = self.output.to_idf() - output_vars_str = '/n/n'.join(output_vars) if output_vars is not None else '' + sim_param_str.append(table_style) + if output_vars is not None: + sim_param_str.append('\n\n'.join(output_vars)) + if reports is not None: + sim_param_str.append(reports) + if sqlite is not None: + sim_param_str.append(sqlite) + sim_param_str.append(surfaces) + + # add simulation settings + sim_param_str.append(self.simulation_control.to_idf()) + sim_param_str.append(self.shadow_calculation.to_idf()) + sim_param_str.append(generate_idf_string( + 'Timestep', [self.timestep], ['timesteps per hour'])) + + # add the run period run_period_str, holidays, daylight_saving = self.run_period.to_idf() - holiday_str = '/n/n'.join(holidays) if holidays is not None else '' - daylight_saving_time_str = daylight_saving if daylight_saving is not None else '' - timestep_str = generate_idf_string( - 'Timestep', [self.timestep], ['timesteps per hour']) - sim_control_str = self.simulation_control.to_idf() - shadow_calc_str = self.shadow_calculation.to_idf() - sizing_par_str = self.sizing_parameter.to_idf() - - return '/n/n'.join([table_style, output_vars_str, reports, sqlite, surfaces, - header_str, sim_control_str, shadow_calc_str, timestep_str, - run_period_str, holiday_str, daylight_saving_time_str, - sizing_par_str]) + sim_param_str.append(run_period_str) + if holidays is not None: + sim_param_str.append('\n\n'.join(holidays)) + if daylight_saving is not None: + sim_param_str.append(daylight_saving) + + # write the sizing parameters + sim_param_str.append(self.sizing_parameter.to_idf()) + + # write the global geometry rules + sim_param_str.append(self.global_geometry_rules) + + return '\n\n'.join(sim_param_str) def to_dict(self): """SimulationParameter dictionary representation.""" @@ -319,6 +351,10 @@ def duplicate(self): """Get a copy of this object.""" return self.__copy__() + def ToString(self): + """Overwrite .NET ToString.""" + return self.__repr__() + def __copy__(self): return SimulationParameter( self.output.duplicate(), self.run_period.duplicate(), self.timestep, diff --git a/honeybee_energy/writer.py b/honeybee_energy/writer.py index 5572e27be..5e3d16d49 100644 --- a/honeybee_energy/writer.py +++ b/honeybee_energy/writer.py @@ -1,5 +1,11 @@ +# coding=utf-8 """Methods to write to idf.""" -from honeybee.boundarycondition import Surface +import os + +from honeybee.room import Room +from honeybee.face import Face +from honeybee.boundarycondition import Outdoors, Surface, Ground +from honeybee.facetype import RoofCeiling, AirWall def generate_idf_string(object_type, values, comments=None): @@ -22,44 +28,367 @@ def generate_idf_string(object_type, values, comments=None): ep_str = object_type + ',\n ' + '\n '.join( '{},{}!- {}'.format(val, space, com) for val, space, com in zip(values[:-1], spaces[:-1], comments[:-1])) - ep_str = ep_str + '\n {};{}!- {}'.format( - values[-1], spaces[-1], comments[-1]) + if len(values) == 1: + ep_str = ep_str + '{};{}!- {}'.format(values[-1], spaces[-1], comments[-1]) + else: + ep_str = ep_str + '\n {};{}!- {}'.format(values[-1], spaces[-1], comments[-1]) \ + if comments[-1] != '' else ep_str + '\n {};'.format(values[-1]) else: ep_str = object_type + ',\n ' + '\n '.join( '{},'.format(val) for val in values[:-1]) - ep_str = ep_str + '\n {};'.format(values[-1]) + ep_str = ep_str + '\n {};'.format(values[-1]) if len(values) == 1 else \ + ep_str + '{};'.format(values[-1]) return ep_str +def door_to_idf(door): + """Generate an IDF string representation of a Door. + + Note that the resulting string does not include full construction definitions. + + Args: + door: A honeyee Door for which an IDF representation will be returned. + """ + door_bc_obj = door.boundary_condition.boundary_condition_object if \ + isinstance(door.boundary_condition, Surface) else '' + values = (door.name, + 'Door' if not door.is_glass else 'GlassDoor', + door.properties.energy.construction.name, + door.parent.name if door.has_parent else 'unknown', + door_bc_obj, + door.boundary_condition.view_factor, + '', # TODO: Implement Frame and Divider objects on WindowConstructions + '1', + len(door.vertices), + ',\n '.join('%.3f, %.3f, %.3f' % (v[0], v[1], v[2]) + for v in door.upper_left_vertices)) + comments = ('name', + 'surface type', + 'construction name', + 'building surface name', + 'boundary condition object', + 'view factor to ground', + 'frame and divider name', + 'multiplier', + 'number of vertices', + '') + return generate_idf_string('FenestrationSurface:Detailed', values, comments) + + +def aperture_to_idf(aperture): + """Generate an IDF string representation of an Aperture. + + Note that the resulting string does not include full construction definitions. + + Also note that this does not include any of the shades assigned to the Aperture + in the resulting string. To write these objects into a final string, you must + loop through the Aperture.shades, and call the to.idf method on each one. + + Args: + aperture: A honeyee Aperture for which an IDF representation will be returned. + """ + ap_bc_obj = aperture.boundary_condition.boundary_condition_object if \ + isinstance(aperture.boundary_condition, Surface) else '' + values = (aperture.name, + 'Window', + aperture.properties.energy.construction.name, + aperture.parent.name if aperture.has_parent else 'unknown', + ap_bc_obj, + aperture.boundary_condition.view_factor, + '', # TODO: Implement Frame and Divider objects on WindowConstructions + '1', + len(aperture.vertices), + ',\n '.join('%.3f, %.3f, %.3f' % (v[0], v[1], v[2]) + for v in aperture.upper_left_vertices)) + comments = ('name', + 'surface type', + 'construction name', + 'building surface name', + 'boundary condition object', + 'view factor to ground', + 'frame and divider name', + 'multiplier', + 'number of vertices', + '') + return generate_idf_string('FenestrationSurface:Detailed', values, comments) + + +def shade_to_idf(shade): + """Generate an IDF string representation of a Shade. + + Note that the resulting string will possess both the Shading object + as well as a ShadingProperty:Reflectance if the Shade's construction + is not defaulted. + + Args: + shade: A honeyee Shade for which an IDF representation will be returned. + """ + # create the Shading:Detailed IDF string + trans_sched = shade.properties.energy.transmittance_schedule.name if \ + shade.properties.energy.transmittance_schedule is not None else '' + if shade.has_parent and not isinstance(shade.parent, Room): + if isinstance(shade.parent, Face): + base_srf = shade.parent.name + else: # Aperture for parent + try: + base_srf = shade.parent.parent.name + except AttributeError: + base_srf = 'unknown' # aperture without a parent + values = (shade.name, + base_srf, + trans_sched, + len(shade.vertices), + ',\n '.join('%.3f, %.3f, %.3f' % (v[0], v[1], v[2]) + for v in shade.upper_left_vertices)) + comments = ('name', + 'base surface', + 'transmittance schedule', + 'number of vertices', + '') + shade_str = generate_idf_string('Shading:Zone:Detailed', values, comments) + else: + values = (shade.name, + trans_sched, + len(shade.vertices), + ',\n '.join('%.3f, %.3f, %.3f' % (v[0], v[1], v[2]) + for v in shade.upper_left_vertices)) + comments = ('name', + 'transmittance schedule', + 'number of vertices', + '') + shade_str = generate_idf_string('Shading:Building:Detailed', values, comments) + + # create the ShadingProperty:Reflectance IDF string if construction is not default + construction = shade.properties.energy.construction + if construction.is_default: + return shade_str + else: + values = (shade.name, + construction.solar_reflectance, + construction.visible_reflectance) + comments = ('shading surface name', + 'diffuse solar reflectance', + 'diffuse visible reflectance') + if construction.is_specular: + values = values + (1, construction.name) + comments = comments + ('glazed fraction of surface', 'glazing construction') + constr_str = generate_idf_string('ShadingProperty:Reflectance', values, comments) + return '\n\n'.join((shade_str, constr_str)) + + def face_to_idf(face): """Generate an IDF string representation of a Face. + Note that the resulting string does not include full construction definitions. + + Also note that this does not include any of the shades assigned to the Face + in the resulting string. Nor does it include the strings for the + apertures or doors. To write these objects into a final string, you must + loop through the Face.shades, Face.apertures, and Face.doors and call the + to.idf method on each one. + Args: face: A honeyee Face for which an IDF representation will be returned. """ + if isinstance(face.type, RoofCeiling): + face_type = 'Roof' if isinstance(face.boundary_condition, (Outdoors, Ground)) \ + else 'Ceiling' # EnergyPlus distinguishes between Roof and Ceiling + elif isinstance(face.type, AirWall): + face_type = 'Wall' # air walls are not a Surface type in EnergyPlus + else: + face_type = face.type.name + face_bc_obj = face.boundary_condition.boundary_condition_object if \ + isinstance(face.boundary_condition, Surface) else '' + values = (face.name, + face_type, + face.properties.energy.construction.name, + face.parent.name if face.has_parent else 'unknown', + face.boundary_condition.name, + face_bc_obj, + face.boundary_condition.sun_exposure_idf, + face.boundary_condition.wind_exposure_idf, + face.boundary_condition.view_factor, + len(face.vertices), + ',\n '.join('%.3f, %.3f, %.3f' % (v[0], v[1], v[2]) + for v in face.upper_left_vertices)) + comments = ('name', + 'surface type', + 'construction name', + 'zone name', + 'boundary condition', + 'boundary condition object', + 'sun exposure', + 'wind exposure', + 'view factor to ground', + 'number of vertices', + '') + return generate_idf_string('BuildingSurface:Detailed', values, comments) + - return 'BuildingSurface:Detailed,' \ - '\n\t%s,\t!- Name' \ - '\n\t%s,\t!- Surface Type' \ - '\n\t%s,\t!- Construction Name' \ - '\n\t%s,\t!- Zone Name' \ - '\n\t%s,\t!- Outside Boundary Condition' \ - '\n\t%s,\t!- Outside Boundary Condition Object' \ - '\n\t%s,\t!- Sun Exposure' \ - '\n\t%s,\t!- Wind Exposure' \ - '\n\t%s,\t!- View Factor to Ground' \ - '\n\t%d,\t!- Number of Vertices' \ - '\n\t%s;' % ( - face.name, - face.type.name, - face.properties.energy.construction.name, - face.parent.name if face.parent else 'unknown', - face.boundary_condition.name, - face.boundary_condition.boundary_condition_object if - isinstance(face.boundary_condition, Surface) else '', - face.boundary_condition.sun_exposure_idf, - face.boundary_condition.wind_exposure_idf, - face.boundary_condition.view_factor, - len(face.vertices), - ',\n\t'.join('%f, %f, %f' % (v[0], v[1], v[2]) for v in face.upper_left_vertices) - ) +def room_to_idf(room): + """Generate an IDF string representation of a Room. + + The resulting string will include all internal gain defintiions for the Room + (people, lights, equipment), infiltration definitions, ventilation requirements, + thermostat objects, and ideal air load objects. However, complete schedule + defintions assigned to these objects are not included. + + Also note that this method does not write any of the geometry of the Room + into the resulting string. To represent the Room geometry, you must loop + through the Room.shades and Room.faces and call the to.idf method on + each one. Note that you will likely also need to call to.idf on the + apertures, doors and shades of each face as well as the shades on each + aperture. + + Args: + room: A honeyee Room for which an IDF representation will be returned. + """ + # list of zone strings that will eventually be joined + zone_str = ['!- ________ZONE:{}________\n'.format(room.display_name)] + + # write the zone defintiion + zone_values = (room.name,) + zone_comments = ('name',) + if room.multiplier != 1: + zone_values = zone_values + ('', '', '', '', '', room.multiplier) + zone_comments = zone_comments + ('north', 'x', 'y', 'z', 'type', 'multiplier') + zone_str.append(generate_idf_string('Zone', zone_values, zone_comments)) + + # write the load definitions + people = room.properties.energy.people + lighting = room.properties.energy.lighting + electric_equipment = room.properties.energy.electric_equipment + gas_equipment = room.properties.energy.gas_equipment + infiltration = room.properties.energy.infiltration + ventilation = room.properties.energy.ventilation + + if people is not None: + zone_str.append(people.to_idf(room.name)) + if lighting is not None: + zone_str.append(lighting.to_idf(room.name)) + if electric_equipment is not None: + zone_str.append(electric_equipment.to_idf(room.name)) + if gas_equipment is not None: + zone_str.append(gas_equipment.to_idf(room.name)) + if infiltration is not None: + zone_str.append(infiltration.to_idf(room.name)) + + # write the ventilation, thermostat, and ideal air system + if ventilation is not None: + zone_str.append(ventilation.to_idf()) + if room.properties.energy.is_conditioned: + zone_str.append(room.properties.energy.hvac.to_idf()) + zone_str.append(room.properties.energy.setpoint.to_idf()) + humidistat = room.properties.energy.setpoint.to_idf_humidistat(room.name) + if humidistat is not None: + zone_str.append(humidistat) + + return '\n\n'.join(zone_str) + + +def model_to_idf(model, schedule_directory=None, + solar_distribution='FullInteriorAndExteriorWithReflections'): + """Generate an IDF string representation of a Model. + + The resulting string will include all geometry (Rooms, Faces, Shades, Apertures, + Doors), all fully-detailed counstructions + materials, all fully-detailed + schedules, and the room properties (loads, setpoints, and HVAC). + + Essentially, the string includes everything needed to simulate the model + except the simulation parameters. So joining this string with the output of + SimulationParameter.to_idf() should create a simulate-able IDF. + + Args: + model: A honeyee Model for which an IDF representation will be returned. + schedule_directory: An optional file directory to which any file-based + schedules should be written to. If None, it will be written to the + user folder assuming the project is entitled 'unnamed'. + solar_distribution: Text desribing how EnergyPlus should treat beam solar + radiation reflected from surfaces. Default: + FullInteriorAndExteriorWithReflections. Choose from the following: + * MinimalShadowing + * FullExterior + * FullInteriorAndExterior + * FullExteriorWithReflections + * FullInteriorAndExteriorWithReflections + """ + # write the building object into the string + model_str = ['!- =======================================\n' + '!- ================ MODEL ================\n' + '!- =======================================\n'] + model_str.append(model.properties.energy.building_idf(solar_distribution)) + + # write all of the schedules and type limits + sched_strs = [] + type_limits = [] + sched_dir = None + for sched in model.properties.energy.schedules: + try: + day_scheds = [day.to_idf() for day in sched.day_schedules] + year_schedule, week_schedules = sched.to_idf() + sched_strs.extend([year_schedule] + week_schedules + day_scheds) + except AttributeError: # ScheduleFixedInterval + if sched_dir is None: + sched_dir = schedule_directory if schedule_directory is not None \ + else os.path.join(os.environ['USERPROFILE'], 'honeybee', + 'unnamed', 'schedules') + sched_strs.append(sched.to_idf(sched_dir)) + t_lim = sched.schedule_type_limit + if t_lim is not None and not _instance_in_array(t_lim, type_limits): + type_limits.append(t_lim) + model_str.append('!- ========= SCHEDULE TYPE LIMITS =========\n') + model_str.extend([type_limit.to_idf() for type_limit in set(type_limits)]) + model_str.append('!- ============== SCHEDULES ==============\n') + model_str.extend(sched_strs) + + # write all of the materials and constructions + materials = [] + construction_strs = [] + for constr in model.properties.energy.constructions: + try: + materials.extend(constr.materials) + construction_strs.append(constr.to_idf()) + except AttributeError: + pass # ShadeConstruction; No need to write to IDF + model_str.append('!- ============== MATERIALS ==============\n') + model_str.extend([mat.to_idf() for mat in set(materials)]) + model_str.append('!- ============ CONSTRUCTIONS ============\n') + model_str.extend(construction_strs) + + # write all of the zone geometry + model_str.append('!- ============ ZONE GEOMETRY ============\n') + for room in model.rooms: + model_str.append(room.to.idf(room)) + for face in room.faces: + model_str.append(face.to.idf(face)) + for ap in face.apertures: + model_str.append(ap.to.idf(ap)) + for shade in ap.outdoor_shades: + model_str.append(shade.to.idf(shade)) + for dr in face.doors: + model_str.append(dr.to.idf(dr)) + for shade in face.outdoor_shades: + model_str.append(shade.to.idf(shade)) + for shade in room.outdoor_shades: + model_str.append(shade.to.idf(shade)) + + # write all context shade geometry + model_str.append('!- ========== CONTEXT GEOMETRY ==========\n') + for shade in model.orphaned_shades: + model_str.append(shade.to.idf(shade)) + + return '\n\n'.join(model_str) + + +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_arrary` + 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 diff --git a/tests/aperture_extend_test.py b/tests/aperture_extend_test.py index 7cf469e52..c8a8af2e6 100644 --- a/tests/aperture_extend_test.py +++ b/tests/aperture_extend_test.py @@ -144,3 +144,22 @@ def test_from_dict(): new_aperture = Aperture.from_dict(ad) assert new_aperture.properties.energy.construction == triple_pane assert new_aperture.to_dict() == ad + + +def test_writer_to_idf(): + """Test the Aperture to_idf method.""" + aperture = Aperture.from_vertices( + 'wall_window', [[0, 0, 0], [10, 0, 0], [10, 0, 10], [0, 0, 10]]) + clear_glass = EnergyWindowMaterialGlazing( + 'Clear Glass', 0.005715, 0.770675, 0.07, 0.8836, 0.0804, + 0, 0.84, 0.84, 1.0) + gap = EnergyWindowMaterialGas('air gap', thickness=0.0127) + triple_pane = WindowConstruction( + 'TriplePane', [clear_glass, gap, clear_glass, gap, clear_glass]) + aperture.properties.energy.construction = triple_pane + + assert hasattr(aperture.to, 'idf') + idf_string = aperture.to.idf(aperture) + assert 'wall_window,' in idf_string + assert 'FenestrationSurface:Detailed,' in idf_string + assert 'TriplePane' in idf_string diff --git a/tests/door_extend_test.py b/tests/door_extend_test.py index a7c744893..42bb0687a 100644 --- a/tests/door_extend_test.py +++ b/tests/door_extend_test.py @@ -131,3 +131,19 @@ def test_from_dict(): new_door = Door.from_dict(drd) assert new_door.properties.energy.construction == mass_constr assert new_door.to_dict() == drd + + +def test_writer_to_idf(): + """Test the Door to_idf method.""" + door = Door.from_vertices( + 'front_door', [[0, 0, 0], [10, 0, 0], [10, 0, 10], [0, 0, 10]]) + concrete5 = EnergyMaterial('5cm Concrete', 0.05, 2.31, 2322, 832, + 'MediumRough', 0.95, 0.75, 0.8) + mass_constr = OpaqueConstruction('ConcreteDoor', [concrete5]) + door.properties.energy.construction = mass_constr + + assert hasattr(door.to, 'idf') + idf_string = door.to.idf(door) + assert 'front_door,' in idf_string + assert 'FenestrationSurface:Detailed,' in idf_string + assert 'ConcreteDoor' in idf_string diff --git a/tests/model_extend_test.py b/tests/model_extend_test.py index f7b2fd48c..f2aa31989 100644 --- a/tests/model_extend_test.py +++ b/tests/model_extend_test.py @@ -502,3 +502,56 @@ def test_to_dict_multizone_house(): with open(dest_file, 'w') as fp: json.dump(model_dict, fp, indent=4) """ + + +def test_writer_to_idf(): + """Test the Model to.idf method.""" + room = Room.from_box('Tiny House Zone', 5, 10, 3) + room.properties.energy.program_type = office_program + room.properties.energy.hvac = IdealAirSystem() + + stone = EnergyMaterial('Thick Stone', 0.3, 2.31, 2322, 832, 'Rough', + 0.95, 0.75, 0.8) + thermal_mass_constr = OpaqueConstruction('Thermal Mass Floor', [stone]) + room[0].properties.energy.construction = thermal_mass_constr + + south_face = room[3] + south_face.apertures_by_ratio(0.4, 0.01) + south_face.apertures[0].overhang(0.5, indoor=False) + south_face.apertures[0].overhang(0.5, indoor=True) + south_face.move_shades(Vector3D(0, 0, -0.5)) + light_shelf_out = ShadeConstruction('Outdoor Light Shelf', 0.5, 0.5) + light_shelf_in = ShadeConstruction('Indoor Light Shelf', 0.7, 0.7) + south_face.apertures[0].outdoor_shades[0].properties.energy.construction = light_shelf_out + south_face.apertures[0].indoor_shades[0].properties.energy.construction = light_shelf_in + + north_face = room[1] + north_face.overhang(0.25, indoor=False) + door_verts = [Point3D(2, 10, 0.1), Point3D(1, 10, 0.1), + Point3D(1, 10, 2.5), Point3D(2, 10, 2.5)] + door = Door('Front Door', Face3D(door_verts)) + north_face.add_door(door) + + aperture_verts = [Point3D(4.5, 10, 1), Point3D(2.5, 10, 1), + Point3D(2.5, 10, 2.5), Point3D(4.5, 10, 2.5)] + aperture = Aperture('Front Aperture', Face3D(aperture_verts)) + triple_pane = WindowConstruction( + 'Triple Pane Window', [clear_glass, air_gap, clear_glass, air_gap, clear_glass]) + aperture.properties.energy.construction = triple_pane + north_face.add_aperture(aperture) + + tree_canopy_geo = Face3D.from_regular_polygon( + 6, 2, Plane(Vector3D(0, 0, 1), Point3D(5, -3, 4))) + tree_canopy = Shade('Tree Canopy', tree_canopy_geo) + + table_geo = Face3D.from_rectangle(2, 2, Plane(o=Point3D(1.5, 4, 1))) + table = Shade('Table', table_geo) + room.add_indoor_shade(table) + + model = Model('TinyHouse', [room], orphaned_shades=[tree_canopy]) + model.north_angle = 15 + + assert hasattr(model.to, 'idf') + idf_string = model.to.idf(model, schedule_directory='./tests/idf/') + assert 'TinyHouse,' in idf_string + assert 'Building,' in idf_string diff --git a/tests/room_extend_test.py b/tests/room_extend_test.py index 4f4a951ee..0a57b1048 100644 --- a/tests/room_extend_test.py +++ b/tests/room_extend_test.py @@ -245,3 +245,23 @@ def test_from_dict(): assert new_room.properties.energy.construction_set.name == \ 'Thermal Mass Construction Set' assert new_room.to_dict() == rd + + +def test_writer_to_idf(): + """Test the Room to_idf method.""" + room = Room.from_box('ClosedOffice', 5, 10, 3) + room.properties.energy.program_type = office_program + room.properties.energy.hvac = IdealAirSystem() + + assert hasattr(room.to, 'idf') + idf_string = room.to.idf(room) + assert 'ClosedOffice,' in idf_string + assert 'Zone,' in idf_string + assert 'People' in idf_string + assert 'Lights' in idf_string + assert 'ElectricEquipment' in idf_string + assert 'GasEquipment' not in idf_string + assert 'ZoneInfiltration:DesignFlowRate' in idf_string + assert 'DesignSpecification:OutdoorAir' in idf_string + assert 'HVACTemplate:Thermostat' in idf_string + assert 'HVACTemplate:Zone:IdealLoadsAirSystem' in idf_string diff --git a/tests/shade_extend_test.py b/tests/shade_extend_test.py index 533898d89..a2726c5ea 100644 --- a/tests/shade_extend_test.py +++ b/tests/shade_extend_test.py @@ -159,3 +159,29 @@ def test_from_dict(): assert shade.properties.energy.construction.is_specular assert new_shade.properties.energy.transmittance_schedule == fritted_glass_trans assert new_shade.to_dict() == shade_dict + + +def test_writer_to_idf(): + """Test the Shade to_idf method.""" + verts = [Point3D(0, 0, 0), Point3D(1, 0, 0), Point3D(1, 0, 3), Point3D(0, 0, 3)] + shade = Shade('overhang', Face3D(verts)) + + assert hasattr(shade.to, 'idf') + idf_string = shade.to.idf(shade) + assert 'overhang,' in idf_string + assert 'Shading:Building:Detailed,' in idf_string + assert 'ShadingProperty:Reflectance' not in idf_string + + shade = Shade('overhang', Face3D(verts)) + light_shelf = ShadeConstruction('Light Shelf', 0.5, 0.5, True) + shade.properties.energy.construction = light_shelf + fritted_glass_trans = ScheduleRuleset.from_constant_value( + 'FrittedGlass', 0.5, schedule_types.fractional) + shade.properties.energy.transmittance_schedule = fritted_glass_trans + + assert hasattr(shade.to, 'idf') + idf_string = shade.to.idf(shade) + assert 'overhang,' in idf_string + assert 'Shading:Building:Detailed,' in idf_string + assert 'ShadingProperty:Reflectance' in idf_string + assert 'FrittedGlass' in idf_string