From 78aa84c33896b64c766befa00b966aa3194e42e8 Mon Sep 17 00:00:00 2001 From: guille Date: Mon, 30 Sep 2024 15:15:57 +0200 Subject: [PATCH] Partial refactor --- hub/exports/building_energy/cerc_idf.py | 383 ++---------------- .../building_energy/idf_helper/idf_base.py | 75 ++++ .../idf_helper/idf_constructions.py | 52 +++ .../idf_helper/idf_file_schedules.py | 29 ++ .../idf_helper/idf_lighting.py | 27 ++ .../idf_helper/idf_materials.py | 36 ++ .../idf_helper/idf_occupancy.py | 46 +++ .../idf_helper/idf_schedules.py | 29 ++ .../idf_helper/idf_surfaces.py | 52 +++ .../idf_helper/idf_windows_constructions.py | 16 + .../idf_helper/idf_windows_materials.py | 16 + .../building_energy/idf_helper/idf_zones.py | 21 + .../energy_building_exports_factory.py | 2 +- tests/test_exports.py | 2 +- 14 files changed, 436 insertions(+), 350 deletions(-) create mode 100644 hub/exports/building_energy/idf_helper/idf_base.py create mode 100644 hub/exports/building_energy/idf_helper/idf_constructions.py create mode 100644 hub/exports/building_energy/idf_helper/idf_file_schedules.py create mode 100644 hub/exports/building_energy/idf_helper/idf_lighting.py create mode 100644 hub/exports/building_energy/idf_helper/idf_materials.py create mode 100644 hub/exports/building_energy/idf_helper/idf_occupancy.py create mode 100644 hub/exports/building_energy/idf_helper/idf_schedules.py create mode 100644 hub/exports/building_energy/idf_helper/idf_surfaces.py create mode 100644 hub/exports/building_energy/idf_helper/idf_windows_constructions.py create mode 100644 hub/exports/building_energy/idf_helper/idf_windows_materials.py create mode 100644 hub/exports/building_energy/idf_helper/idf_zones.py diff --git a/hub/exports/building_energy/cerc_idf.py b/hub/exports/building_energy/cerc_idf.py index bae48076..ff3d9df6 100644 --- a/hub/exports/building_energy/cerc_idf.py +++ b/hub/exports/building_energy/cerc_idf.py @@ -1,5 +1,5 @@ """ -Idf exports one building to idf format +Cerc Idf exports one city or some buildings to idf format SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2022 Concordia CERC group Project Coder Guille Guillermo.GutierrezMorote@concordia.ca @@ -9,15 +9,23 @@ Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concord import copy import os from datetime import datetime -from pathlib import Path import hub.exports.building_energy.idf_helper as idf_cte import hub.helpers.constants as cte from hub.city_model_structure.attributes.schedule import Schedule -from hub.city_model_structure.building_demand.layer import Layer +from hub.exports.building_energy.idf_helper.idf_base import IdfBase +from hub.exports.building_energy.idf_helper.idf_constructions import IdfConstructions +from hub.exports.building_energy.idf_helper.idf_file_schedules import IdfFileSchedules +from hub.exports.building_energy.idf_helper.idf_lighting import IdfLighting +from hub.exports.building_energy.idf_helper.idf_materials import IdfMaterials +from hub.exports.building_energy.idf_helper.idf_occupancy import IdfOccupancy +from hub.exports.building_energy.idf_helper.idf_schedules import IdfSchedules +from hub.exports.building_energy.idf_helper.idf_surfaces import IdfSurfaces +from hub.exports.building_energy.idf_helper.idf_windows_constructions import IdfWindowsConstructions +from hub.exports.building_energy.idf_helper.idf_windows_materials import IdfWindowsMaterials -class CercIdf: +class CercIdf(IdfBase): """ Exports city to IDF """ @@ -28,60 +36,36 @@ class CercIdf: _constructions_added_to_idf = {} _thermostat_added_to_idf = {} - def __init__(self, city, output_path, idf_file_path, idd_file_path, epw_file_path, - target_buildings=None): - self._city = city - self._output_path = str(output_path.resolve()) - self._output_file_path = str((output_path / f'{city.name}.idf').resolve()) + def __init__(self, city, output_path, idf_file_path, idd_file_path, epw_file_path, target_buildings=None): + super().__init__(city, output_path, idf_file_path, idd_file_path, epw_file_path, target_buildings) + self._add_surfaces = IdfSurfaces.add + self._add_schedules = IdfSchedules.add + self._add_file_schedules = IdfFileSchedules.add + self._add_idf_schedules = IdfSchedules.add + self._add_constructions = IdfConstructions.add + self._add_materials = IdfMaterials.add + self._add_windows_materials = IdfWindowsMaterials.add + self._add_windows_constructions = IdfWindowsConstructions.add + self._add_occupancy = IdfOccupancy.add + self._add_lighting = IdfLighting.add - self._file_paths = { - 'schedules': str((output_path / 'schedules.idf').resolve()), - 'file_schedules': str((output_path / 'file_schedules.idf').resolve()), - 'solid_materials': str((output_path / 'solid_materials.idf').resolve()), - 'nomass_materials': str((output_path / 'nomass_materials.idf').resolve()), - 'window_materials': str((output_path / 'window_materials.idf').resolve()), - 'constructions': str((output_path / 'constructions.idf').resolve()), - 'zones': str((output_path / 'zones.idf').resolve()), - 'occupancy': str((output_path / 'occupancy.idf').resolve()), - 'lighting': str((output_path / 'lights.idf').resolve()), - 'appliances': str((output_path / 'appliances.idf').resolve()), - 'surfaces': str((output_path / 'surfaces.idf').resolve()), - 'shading': str((output_path / 'shading.idf').resolve()), - 'infiltration': str((output_path / 'infiltration.idf').resolve()), - 'ventilation': str((output_path / 'ventilation.idf').resolve()), - 'thermostat': str((output_path / 'thermostat.idf').resolve()), - 'ideal_load_system': str((output_path / 'ideal_load_system.idf').resolve()), - 'dhw': str((output_path / 'dhw.idf').resolve()), - } - self._files = {} - for key, value in self._file_paths.items(): - self._files[key] = open(value, 'w') - - self._idd_file_path = str(idd_file_path) - self._idf_file_path = str(idf_file_path) - self._outputs_file_path = str(Path(idf_file_path).parent / 'outputs.idf') - self._epw_file_path = str(epw_file_path) - - self._target_buildings = target_buildings - self._adjacent_buildings = [] - - if target_buildings is None: - self._target_buildings = [building.name for building in self._city.buildings] - else: - for building_name in target_buildings: - building = city.city_object(building_name) - if building.neighbours is not None: - self._adjacent_buildings += building.neighbours with open(self._idf_file_path, 'r') as base_idf: lines = base_idf.readlines() - # Create base values - self._create_geometry_rules() - with open(self._output_file_path, 'w') as self._idf_file: self._idf_file.writelines(lines) self._export() + # Create base values + self._create_geometry_rules() + + def _create_geometry_rules(self): + file = self._files['zones'] + self._write_to_idf_format(file, idf_cte.GLOBAL_GEOMETRY_RULES) + self._write_to_idf_format(file, 'UpperLeftCorner', 'Starting Vertex Position') + self._write_to_idf_format(file, 'CounterClockWise', 'Vertex Entry Direction') + self._write_to_idf_format(file, 'World', 'Coordinate System', ';') + def _merge_files(self): for file in self._files.values(): file.close() @@ -97,85 +81,6 @@ class CercIdf: lines = base_idf.readlines() self._idf_file.writelines(lines) - def _create_geometry_rules(self): - file = self._files['zones'] - self._write_to_idf_format(file, idf_cte.GLOBAL_GEOMETRY_RULES) - self._write_to_idf_format(file, 'UpperLeftCorner', 'Starting Vertex Position') - self._write_to_idf_format(file, 'CounterClockWise', 'Vertex Entry Direction') - self._write_to_idf_format(file, 'World', 'Coordinate System', ';') - - def _create_output_control_lighting(self): - file = self._files['appliances'] - self._write_to_idf_format(file, idf_cte.OUTPUT_CONTROL) - self._write_to_idf_format(file, 'Comma', 'Column Separator', ';') - - @staticmethod - def _write_to_idf_format(file, field, comment='', eol=','): - if comment != '': - comment = f' !- {comment}' - field = f' {field}{eol}'.ljust(26, ' ') - file.write(f'{field}{comment}\n') - else: - file.write(f'{field}{comment}') - - @staticmethod - def _matrix_to_list(points, lower_corner): - lower_x = lower_corner[0] - lower_y = lower_corner[1] - lower_z = lower_corner[2] - points_list = [] - for point in points: - point_tuple = (point[0] - lower_x, point[1] - lower_y, point[2] - lower_z) - points_list.append(point_tuple) - return points_list - - def _add_surfaces(self, building, zone_name): - # Verify if create building surfaces "by hand" it's faster wwr it's missing - zone_name = f'{zone_name}' - file = self._files['surfaces'] - for thermal_zone in building.thermal_zones_from_internal_zones: - for index, boundary in enumerate(thermal_zone.thermal_boundaries): - surface_type = idf_cte.idf_surfaces[boundary.parent_surface.type] - outside_boundary_condition = idf_cte.OUTDOORS - sun_exposure = idf_cte.SUN_EXPOSED - wind_exposure = idf_cte.WIND_EXPOSED - outside_boundary_condition_object = idf_cte.EMPTY - name = f'Building_{building.name}_surface_{index}' - construction_name = f'{boundary.construction_name} {boundary.parent_surface.type}' - space_name = idf_cte.EMPTY - if boundary.parent_surface.type == cte.GROUND: - outside_boundary_condition = idf_cte.GROUND - sun_exposure = idf_cte.NON_SUN_EXPOSED - wind_exposure = idf_cte.NON_WIND_EXPOSED - if boundary.parent_surface.percentage_shared is not None and boundary.parent_surface.percentage_shared > 0.5: - outside_boundary_condition_object = f'Building_{building.name}_surface_{index}' - outside_boundary_condition = idf_cte.SURFACE - sun_exposure = idf_cte.NON_SUN_EXPOSED - wind_exposure = idf_cte.NON_WIND_EXPOSED - self._write_to_idf_format(file, idf_cte.BUILDING_SURFACE) - self._write_to_idf_format(file, name, 'Name') - self._write_to_idf_format(file, surface_type, 'Surface Type') - self._write_to_idf_format(file, construction_name, 'Construction Name') - self._write_to_idf_format(file, zone_name, 'Zone Name') - self._write_to_idf_format(file, space_name, 'Space Name') - self._write_to_idf_format(file, outside_boundary_condition, 'Outside Boundary Condition') - self._write_to_idf_format(file, outside_boundary_condition_object, 'Outside Boundary Condition Object') - self._write_to_idf_format(file, sun_exposure, 'Sun Exposure') - self._write_to_idf_format(file, wind_exposure, 'Wind Exposure') - self._write_to_idf_format(file, idf_cte.AUTOCALCULATE, 'View Factor to Ground') - self._write_to_idf_format(file, idf_cte.AUTOCALCULATE, 'Number of Vertices') - coordinates = self._matrix_to_list(boundary.parent_surface.solid_polygon.coordinates, - self._city.lower_corner) - eol = ',' - coordinates_length = len(coordinates) - for i, coordinate in enumerate(coordinates): - vertex = i + 1 - if vertex == coordinates_length: - eol = ';' - self._write_to_idf_format(file, coordinate[0], f'Vertex {vertex} Xcoordinate') - self._write_to_idf_format(file, coordinate[1], f'Vertex {vertex} Ycoordinate') - self._write_to_idf_format(file, coordinate[2], f'Vertex {vertex} Zcoordinate', eol) - @staticmethod def _create_infiltration_schedules(thermal_zone): _infiltration_schedules = [] @@ -237,225 +142,7 @@ class CercIdf: _schedule.values = [value for _ in range(0, amount)] return [_schedule] - def _add_file_schedules(self, usage, schedule_type, schedules): - schedule_name = f'{schedule_type} schedules {usage}' - for schedule in schedules: - if schedule_name not in self._schedules_added_to_idf: - self._schedules_added_to_idf[schedule_name] = True - file_name = str( - (Path(self._output_path) / f'{schedule_type} schedules {usage.replace("/", "_")}.csv').resolve()) - with open(file_name, 'w', encoding='utf8') as file: - for value in schedule.values: - file.write(f'{str(value)},\n') - file = self._files['file_schedules'] - self._write_to_idf_format(file, idf_cte.FILE_SCHEDULE) - self._write_to_idf_format(file, schedule_name, 'Name') - self._write_to_idf_format(file, idf_cte.idf_type_limits[schedule.data_type], 'Schedule Type Limits Name') - self._write_to_idf_format(file, Path(file_name).name, 'File Name') - self._write_to_idf_format(file, 1, 'Column Number') - self._write_to_idf_format(file, 0, 'Rows to Skip at Top') - self._write_to_idf_format(file, 8760, 'Number of Hours of Data') - self._write_to_idf_format(file, 'Comma', 'Column Separator') - self._write_to_idf_format(file, 'No', 'Interpolate to Timestep') - self._write_to_idf_format(file, '60', 'Minutes per Item') - self._write_to_idf_format(file, 'Yes', 'Adjust Schedule for Daylight Savings', ';') - def _add_idf_schedules(self, usage, schedule_type, schedules): - if len(schedules) < 1: - return - schedule_name = f'{schedule_type} schedules {usage}' - if schedule_name not in self._schedules_added_to_idf: - self._schedules_added_to_idf[schedule_name] = True - file = self._files['schedules'] - self._write_to_idf_format(file, idf_cte.COMPACT_SCHEDULE) - self._write_to_idf_format(file, schedule_name, 'Name') - self._write_to_idf_format(file, idf_cte.idf_type_limits[schedules[0].data_type], 'Schedule Type Limits Name') - self._write_to_idf_format(file, 'Through: 12/31', 'Field 1') - counter = 1 - for j, schedule in enumerate(schedules): - _val = schedule.values - _new_field = '' - for day_type in schedule.day_types: - _new_field += f' {idf_cte.idf_day_types[day_type]}' - self._write_to_idf_format(file, f'For:{_new_field}', f'Field {j * 25 + 2}') - counter += 1 - for i, _ in enumerate(_val): - self._write_to_idf_format(file, f'Until: {i + 1:02d}:00,{_val[i]}', f'Field {j * 25 + 3 + i}') - counter += 1 - self._write_to_idf_format(file, 'For AllOtherDays', f'Field {counter + 1}') - self._write_to_idf_format(file, 'Until: 24:00,0.0', f'Field {counter + 2}', ';') - - def _add_solid_material(self, layer): - file = self._files['solid_materials'] - self._write_to_idf_format(file, idf_cte.SOLID_MATERIAL) - self._write_to_idf_format(file, layer.material_name, 'Name') - self._write_to_idf_format(file, idf_cte.ROUGHNESS, 'Roughness') - self._write_to_idf_format(file, layer.thickness, 'Thickness') - self._write_to_idf_format(file, layer.conductivity, 'Conductivity') - self._write_to_idf_format(file, layer.density, 'Density') - self._write_to_idf_format(file, layer.specific_heat, 'Specific Heat') - self._write_to_idf_format(file, layer.thermal_absorptance, 'Thermal Absorptance') - self._write_to_idf_format(file, layer.solar_absorptance, 'Solar Absorptance') - self._write_to_idf_format(file, layer.visible_absorptance, 'Visible Absorptance', ';') - - def _add_nomass_material(self, layer): - file = self._files['nomass_materials'] - self._write_to_idf_format(file, idf_cte.NOMASS_MATERIAL) - self._write_to_idf_format(file, layer.material_name, 'Name') - self._write_to_idf_format(file, idf_cte.ROUGHNESS, 'Roughness') - self._write_to_idf_format(file, layer.thermal_resistance, 'Thermal Resistance') - self._write_to_idf_format(file, 0.9, 'Thermal Absorptance') - self._write_to_idf_format(file, 0.7, 'Solar Absorptance') - self._write_to_idf_format(file, 0.7, 'Visible Absorptance', ';') - - def _add_materials(self, thermal_boundary): - for layer in thermal_boundary.layers: - if layer.material_name not in self._materials_added_to_idf: - self._materials_added_to_idf[layer.material_name] = True - if layer.no_mass: - self._add_nomass_material(layer) - else: - self._add_solid_material(layer) - - def _add_default_material(self): - layer = Layer() - layer.material_name = 'DefaultMaterial' - layer.thickness = 0.1 - layer.conductivity = 0.1 - layer.density = 1000 - layer.specific_heat = 1000 - layer.thermal_absorptance = 0.9 - layer.solar_absorptance = 0.9 - layer.visible_absorptance = 0.7 - self._add_solid_material(layer) - return layer - - def _add_window_materials(self, thermal_opening): - name = f'{thermal_opening.overall_u_value}_{thermal_opening.g_value}' - if name not in self._windows_added_to_idf: - glazing_index = self._windows_added_to_idf['glazing_index'] + 1 - self._windows_added_to_idf[name] = True - self._windows_added_to_idf['glazing_index'] = glazing_index # increase the count - file = self._files['window_materials'] - self._write_to_idf_format(file, idf_cte.WINDOW_MATERIAL) - self._write_to_idf_format(file, f'glazing_{glazing_index}', 'Name') - self._write_to_idf_format(file, thermal_opening.overall_u_value, 'UFactor') - self._write_to_idf_format(file, thermal_opening.g_value, 'Solar Heat Gain Coefficient', ';') - - def _add_constructions(self, thermal_boundary): - if thermal_boundary.layers is None: - thermal_boundary.layers = [self._add_default_material()] - name = f'{thermal_boundary.construction_name} {thermal_boundary.parent_surface.type}' - - if name not in self._constructions_added_to_idf: - self._constructions_added_to_idf[name] = True - file = self._files['constructions'] - self._write_to_idf_format(file, idf_cte.CONSTRUCTION) - self._write_to_idf_format(file, name, 'Name') - eol = ',' - if len(thermal_boundary.layers) == 1: - eol = ';' - self._write_to_idf_format(file, thermal_boundary.layers[0].material_name, 'Outside Layer', eol) - for i in range(1, len(thermal_boundary.layers) - 1): - comment = f'Layer {i + 1}' - material_name = thermal_boundary.layers[i].material_name - if i == len(thermal_boundary.layers) - 2: - eol = ';' - self._write_to_idf_format(file, material_name, comment, eol) - - def _add_windows_constructions(self): - glazing_index = self._windows_added_to_idf['glazing_index'] + 1 - for i in range(1, glazing_index): - construction_name = f'window_construction_{i}' - material_name = f'glazing_{i}' - if construction_name not in self._constructions_added_to_idf: - self._constructions_added_to_idf[construction_name] = True - file = self._files['constructions'] - self._write_to_idf_format(file, idf_cte.CONSTRUCTION) - self._write_to_idf_format(file, construction_name, 'Name') - self._write_to_idf_format(file, material_name, 'Outside Layer', ';') - - def _add_zone(self, thermal_zone, zone_name): - file = self._files['zones'] - self._write_to_idf_format(file, idf_cte.ZONE) - self._write_to_idf_format(file, zone_name, 'Name') - self._write_to_idf_format(file, 0, 'Direction of Relative North') - self._write_to_idf_format(file, 0, 'X Origin') - self._write_to_idf_format(file, 0, 'Y Origin') - self._write_to_idf_format(file, 0, 'Z Origin') - self._write_to_idf_format(file, 1, 'Type') - self._write_to_idf_format(file, 1, 'Multiplier') - self._write_to_idf_format(file, idf_cte.AUTOCALCULATE, 'Ceiling Height') - self._write_to_idf_format(file, thermal_zone.volume, 'Volume') - self._write_to_idf_format(file, idf_cte.AUTOCALCULATE, 'Floor Area') - self._write_to_idf_format(file, idf_cte.EMPTY, 'Zone Inside Convection Algorithm') - self._write_to_idf_format(file, idf_cte.EMPTY, 'Zone Outside Convection Algorithm') - self._write_to_idf_format(file, 'Yes', 'Part of Total Floor Area', ';') - - def _add_occupancy(self, thermal_zone, zone_name): - number_of_people = thermal_zone.occupancy.occupancy_density * thermal_zone.total_floor_area - fraction_radiant = 0 - total_sensible = ( - thermal_zone.occupancy.sensible_radiative_internal_gain + thermal_zone.occupancy.sensible_convective_internal_gain - ) - if total_sensible != 0: - fraction_radiant = thermal_zone.occupancy.sensible_radiative_internal_gain / total_sensible - occupancy_schedule = f'Occupancy schedules {thermal_zone.usage_name}' - activity_level_schedule = f'Activity Level schedules {thermal_zone.usage_name}' - file = self._files['occupancy'] - self._write_to_idf_format(file, idf_cte.PEOPLE) - self._write_to_idf_format(file, f'{zone_name}_occupancy', 'Name') - self._write_to_idf_format(file, zone_name, 'Zone or ZoneList or Space or SpaceList Name') - self._write_to_idf_format(file, occupancy_schedule, 'Number of People Schedule Name') - self._write_to_idf_format(file, 'People', 'Number of People Calculation Method') - self._write_to_idf_format(file, number_of_people, 'Number of People') - self._write_to_idf_format(file, idf_cte.EMPTY, 'People per Floor Area') - self._write_to_idf_format(file, idf_cte.EMPTY, 'Floor Area per Person') - self._write_to_idf_format(file, fraction_radiant, 'Fraction Radiant') - self._write_to_idf_format(file, idf_cte.AUTOCALCULATE, 'Sensible Heat Fraction') - self._write_to_idf_format(file, activity_level_schedule, 'Activity Level Schedule Name') - self._write_to_idf_format(file, '3.82e-08', 'Carbon Dioxide Generation Rate') - self._write_to_idf_format(file, 'No', 'Enable ASHRAE 55 Comfort Warnings') - self._write_to_idf_format(file, 'EnclosureAveraged', 'Mean Radiant Temperature Calculation Type') - self._write_to_idf_format(file, idf_cte.EMPTY, 'Surface NameAngle Factor List Name') - self._write_to_idf_format(file, idf_cte.EMPTY, 'Work Efficiency Schedule Name') - self._write_to_idf_format(file, 'ClothingInsulationSchedule', 'Clothing Insulation Calculation Method') - self._write_to_idf_format(file, idf_cte.EMPTY, 'Clothing Insulation Calculation Method Schedule Name') - self._write_to_idf_format(file, idf_cte.EMPTY, 'Clothing Insulation Schedule Name') - self._write_to_idf_format(file, idf_cte.EMPTY, 'Air Velocity Schedule Name') - self._write_to_idf_format(file, idf_cte.EMPTY, 'Thermal Comfort Model 1 Type') - self._write_to_idf_format(file, idf_cte.EMPTY, 'Thermal Comfort Model 2 Type') - self._write_to_idf_format(file, idf_cte.EMPTY, 'Thermal Comfort Model 3 Type') - self._write_to_idf_format(file, idf_cte.EMPTY, 'Thermal Comfort Model 4 Type') - self._write_to_idf_format(file, idf_cte.EMPTY, 'Thermal Comfort Model 5 Type') - self._write_to_idf_format(file, idf_cte.EMPTY, 'Thermal Comfort Model 6 Type') - self._write_to_idf_format(file, idf_cte.EMPTY, 'Thermal Comfort Model 7 Type') - self._write_to_idf_format(file, idf_cte.EMPTY, 'Ankle Level Air Velocity Schedule Name') - self._write_to_idf_format(file, '15.56', 'Cold Stress Temperature Threshold') - self._write_to_idf_format(file, '30', 'Heat Stress Temperature Threshold', ';') - - def _add_lighting(self, thermal_zone, zone_name): - storeys_number = int(thermal_zone.total_floor_area / thermal_zone.footprint_area) - watts_per_zone_floor_area = thermal_zone.lighting.density * storeys_number - subcategory = f'ELECTRIC EQUIPMENT#{zone_name}#GeneralLights' - schedule_name = f'Lighting schedules {thermal_zone.usage_name}' - file = self._files['lighting'] - self._write_to_idf_format(file, idf_cte.LIGHTS) - self._write_to_idf_format(file, f'{zone_name}_lights', 'Name') - self._write_to_idf_format(file, zone_name, 'Zone or ZoneList or Space or SpaceList Name') - self._write_to_idf_format(file, schedule_name, 'Schedule Name') - self._write_to_idf_format(file, 'Watts/Area', 'Design Level Calculation Method') - self._write_to_idf_format(file, idf_cte.EMPTY, 'Lighting Level') - self._write_to_idf_format(file, watts_per_zone_floor_area, 'Watts per Zone Floor Area') - self._write_to_idf_format(file, idf_cte.EMPTY, 'Watts per Person') - self._write_to_idf_format(file, 0, 'Return Air Fraction') - self._write_to_idf_format(file, thermal_zone.lighting.radiative_fraction, 'Fraction Radiant') - self._write_to_idf_format(file, 0, 'Fraction Visible') - self._write_to_idf_format(file, 1, 'Fraction Replaceable') - self._write_to_idf_format(file, subcategory, 'EndUse Subcategory') - self._write_to_idf_format(file, 'No', 'Return Air Fraction Calculated from Plenum Temperature') - self._write_to_idf_format(file, 0, 'Return Air Fraction Function of Plenum Temperature Coefficient 1') - self._write_to_idf_format(file, 0, 'Return Air Fraction Function of Plenum Temperature Coefficient 2', ';') def _add_appliances(self, thermal_zone, zone_name): schedule_name = f'Appliance schedules {thermal_zone.usage_name}' @@ -617,7 +304,7 @@ class CercIdf: def _add_window_construction_and_material(self, thermal_opening): for window_material in self._idf.idfobjects[self._WINDOW_MATERIAL_SIMPLE]: if window_material['UFactor'] == thermal_opening.overall_u_value and \ - window_material['Solar_Heat_Gain_Coefficient'] == thermal_opening.g_value: + window_material['Solar_Heat_Gain_Coefficient'] == thermal_opening.g_value: return order = str(len(self._idf.idfobjects[self._WINDOW_MATERIAL_SIMPLE]) + 1) diff --git a/hub/exports/building_energy/idf_helper/idf_base.py b/hub/exports/building_energy/idf_helper/idf_base.py new file mode 100644 index 00000000..cbd0e4ae --- /dev/null +++ b/hub/exports/building_energy/idf_helper/idf_base.py @@ -0,0 +1,75 @@ +import os +from pathlib import Path + +import hub.exports.building_energy.idf_helper as idf_cte + + +class IdfBase: + def __init__(self, city, output_path, idf_file_path, idd_file_path, epw_file_path, target_buildings=None): + self._city = city + self._output_path = str(output_path.resolve()) + self._output_file_path = str((output_path / f'{city.name}.idf').resolve()) + + self._file_paths = { + 'schedules': str((output_path / 'schedules.idf').resolve()), + 'file_schedules': str((output_path / 'file_schedules.idf').resolve()), + 'solid_materials': str((output_path / 'solid_materials.idf').resolve()), + 'nomass_materials': str((output_path / 'nomass_materials.idf').resolve()), + 'window_materials': str((output_path / 'window_materials.idf').resolve()), + 'constructions': str((output_path / 'constructions.idf').resolve()), + 'zones': str((output_path / 'zones.idf').resolve()), + 'occupancy': str((output_path / 'occupancy.idf').resolve()), + 'lighting': str((output_path / 'lights.idf').resolve()), + 'appliances': str((output_path / 'appliances.idf').resolve()), + 'surfaces': str((output_path / 'surfaces.idf').resolve()), + 'shading': str((output_path / 'shading.idf').resolve()), + 'infiltration': str((output_path / 'infiltration.idf').resolve()), + 'ventilation': str((output_path / 'ventilation.idf').resolve()), + 'thermostat': str((output_path / 'thermostat.idf').resolve()), + 'ideal_load_system': str((output_path / 'ideal_load_system.idf').resolve()), + 'dhw': str((output_path / 'dhw.idf').resolve()), + } + self._files = {} + for key, value in self._file_paths.items(): + self._files[key] = open(value, 'w') + + self._idd_file_path = str(idd_file_path) + self._idf_file_path = str(idf_file_path) + self._outputs_file_path = str(Path(idf_file_path).parent / 'outputs.idf') + self._epw_file_path = str(epw_file_path) + + self._target_buildings = target_buildings + self._adjacent_buildings = [] + + if target_buildings is None: + self._target_buildings = [building.name for building in self._city.buildings] + else: + for building_name in target_buildings: + building = city.city_object(building_name) + if building.neighbours is not None: + self._adjacent_buildings += building.neighbours + + def _create_output_control_lighting(self): + file = self._files['appliances'] + self._write_to_idf_format(file, idf_cte.OUTPUT_CONTROL) + self._write_to_idf_format(file, 'Comma', 'Column Separator', ';') + + @staticmethod + def _write_to_idf_format(file, field, comment='', eol=','): + if comment != '': + comment = f' !- {comment}' + field = f' {field}{eol}'.ljust(26, ' ') + file.write(f'{field}{comment}\n') + else: + file.write(f'{field}{comment}') + + @staticmethod + def _matrix_to_list(points, lower_corner): + lower_x = lower_corner[0] + lower_y = lower_corner[1] + lower_z = lower_corner[2] + points_list = [] + for point in points: + point_tuple = (point[0] - lower_x, point[1] - lower_y, point[2] - lower_z) + points_list.append(point_tuple) + return points_list diff --git a/hub/exports/building_energy/idf_helper/idf_constructions.py b/hub/exports/building_energy/idf_helper/idf_constructions.py new file mode 100644 index 00000000..f9ebbafd --- /dev/null +++ b/hub/exports/building_energy/idf_helper/idf_constructions.py @@ -0,0 +1,52 @@ +from hub.city_model_structure.building_demand.layer import Layer +from hub.exports.building_energy.cerc_idf import CercIdf +import hub.exports.building_energy.idf_helper as idf_cte + + +class IdfConstructions(CercIdf): + def _add_solid_material(self, layer): + file = self._files['solid_materials'] + self._write_to_idf_format(file, idf_cte.SOLID_MATERIAL) + self._write_to_idf_format(file, layer.material_name, 'Name') + self._write_to_idf_format(file, idf_cte.ROUGHNESS, 'Roughness') + self._write_to_idf_format(file, layer.thickness, 'Thickness') + self._write_to_idf_format(file, layer.conductivity, 'Conductivity') + self._write_to_idf_format(file, layer.density, 'Density') + self._write_to_idf_format(file, layer.specific_heat, 'Specific Heat') + self._write_to_idf_format(file, layer.thermal_absorptance, 'Thermal Absorptance') + self._write_to_idf_format(file, layer.solar_absorptance, 'Solar Absorptance') + self._write_to_idf_format(file, layer.visible_absorptance, 'Visible Absorptance', ';') + + def _add_default_material(self): + layer = Layer() + layer.material_name = 'DefaultMaterial' + layer.thickness = 0.1 + layer.conductivity = 0.1 + layer.density = 1000 + layer.specific_heat = 1000 + layer.thermal_absorptance = 0.9 + layer.solar_absorptance = 0.9 + layer.visible_absorptance = 0.7 + self._add_solid_material(layer) + return layer + + def add(self, thermal_boundary): + if thermal_boundary.layers is None: + thermal_boundary.layers = [self._add_default_material()] + name = f'{thermal_boundary.construction_name} {thermal_boundary.parent_surface.type}' + + if name not in self._constructions_added_to_idf: + self._constructions_added_to_idf[name] = True + file = self._files['constructions'] + self._write_to_idf_format(file, idf_cte.CONSTRUCTION) + self._write_to_idf_format(file, name, 'Name') + eol = ',' + if len(thermal_boundary.layers) == 1: + eol = ';' + self._write_to_idf_format(file, thermal_boundary.layers[0].material_name, 'Outside Layer', eol) + for i in range(1, len(thermal_boundary.layers) - 1): + comment = f'Layer {i + 1}' + material_name = thermal_boundary.layers[i].material_name + if i == len(thermal_boundary.layers) - 2: + eol = ';' + self._write_to_idf_format(file, material_name, comment, eol) diff --git a/hub/exports/building_energy/idf_helper/idf_file_schedules.py b/hub/exports/building_energy/idf_helper/idf_file_schedules.py new file mode 100644 index 00000000..573f98fc --- /dev/null +++ b/hub/exports/building_energy/idf_helper/idf_file_schedules.py @@ -0,0 +1,29 @@ +from pathlib import Path +import hub.exports.building_energy.idf_helper as idf_cte + +from hub.exports.building_energy.cerc_idf import CercIdf + + +class IdfFileSchedules(CercIdf): + def add(self, usage, schedule_type, schedules): + schedule_name = f'{schedule_type} schedules {usage}' + for schedule in schedules: + if schedule_name not in self._schedules_added_to_idf: + self._schedules_added_to_idf[schedule_name] = True + file_name = str( + (Path(self._output_path) / f'{schedule_type} schedules {usage.replace("/", "_")}.csv').resolve()) + with open(file_name, 'w', encoding='utf8') as file: + for value in schedule.values: + file.write(f'{str(value)},\n') + file = self._files['file_schedules'] + self._write_to_idf_format(file, idf_cte.FILE_SCHEDULE) + self._write_to_idf_format(file, schedule_name, 'Name') + self._write_to_idf_format(file, idf_cte.idf_type_limits[schedule.data_type], 'Schedule Type Limits Name') + self._write_to_idf_format(file, Path(file_name).name, 'File Name') + self._write_to_idf_format(file, 1, 'Column Number') + self._write_to_idf_format(file, 0, 'Rows to Skip at Top') + self._write_to_idf_format(file, 8760, 'Number of Hours of Data') + self._write_to_idf_format(file, 'Comma', 'Column Separator') + self._write_to_idf_format(file, 'No', 'Interpolate to Timestep') + self._write_to_idf_format(file, '60', 'Minutes per Item') + self._write_to_idf_format(file, 'Yes', 'Adjust Schedule for Daylight Savings', ';') diff --git a/hub/exports/building_energy/idf_helper/idf_lighting.py b/hub/exports/building_energy/idf_helper/idf_lighting.py new file mode 100644 index 00000000..57730e83 --- /dev/null +++ b/hub/exports/building_energy/idf_helper/idf_lighting.py @@ -0,0 +1,27 @@ +from hub.exports.building_energy.cerc_idf import CercIdf +import hub.exports.building_energy.idf_helper as idf_cte + + +class IdfLighting(CercIdf): + def add(self, thermal_zone, zone_name): + storeys_number = int(thermal_zone.total_floor_area / thermal_zone.footprint_area) + watts_per_zone_floor_area = thermal_zone.lighting.density * storeys_number + subcategory = f'ELECTRIC EQUIPMENT#{zone_name}#GeneralLights' + schedule_name = f'Lighting schedules {thermal_zone.usage_name}' + file = self._files['lighting'] + self._write_to_idf_format(file, idf_cte.LIGHTS) + self._write_to_idf_format(file, f'{zone_name}_lights', 'Name') + self._write_to_idf_format(file, zone_name, 'Zone or ZoneList or Space or SpaceList Name') + self._write_to_idf_format(file, schedule_name, 'Schedule Name') + self._write_to_idf_format(file, 'Watts/Area', 'Design Level Calculation Method') + self._write_to_idf_format(file, idf_cte.EMPTY, 'Lighting Level') + self._write_to_idf_format(file, watts_per_zone_floor_area, 'Watts per Zone Floor Area') + self._write_to_idf_format(file, idf_cte.EMPTY, 'Watts per Person') + self._write_to_idf_format(file, 0, 'Return Air Fraction') + self._write_to_idf_format(file, thermal_zone.lighting.radiative_fraction, 'Fraction Radiant') + self._write_to_idf_format(file, 0, 'Fraction Visible') + self._write_to_idf_format(file, 1, 'Fraction Replaceable') + self._write_to_idf_format(file, subcategory, 'EndUse Subcategory') + self._write_to_idf_format(file, 'No', 'Return Air Fraction Calculated from Plenum Temperature') + self._write_to_idf_format(file, 0, 'Return Air Fraction Function of Plenum Temperature Coefficient 1') + self._write_to_idf_format(file, 0, 'Return Air Fraction Function of Plenum Temperature Coefficient 2', ';') \ No newline at end of file diff --git a/hub/exports/building_energy/idf_helper/idf_materials.py b/hub/exports/building_energy/idf_helper/idf_materials.py new file mode 100644 index 00000000..17fe3517 --- /dev/null +++ b/hub/exports/building_energy/idf_helper/idf_materials.py @@ -0,0 +1,36 @@ +from hub.exports.building_energy.cerc_idf import CercIdf +import hub.exports.building_energy.idf_helper as idf_cte + + +class IdfMaterials(CercIdf): + def _add_solid_material(self, layer): + file = self._files['solid_materials'] + self._write_to_idf_format(file, idf_cte.SOLID_MATERIAL) + self._write_to_idf_format(file, layer.material_name, 'Name') + self._write_to_idf_format(file, idf_cte.ROUGHNESS, 'Roughness') + self._write_to_idf_format(file, layer.thickness, 'Thickness') + self._write_to_idf_format(file, layer.conductivity, 'Conductivity') + self._write_to_idf_format(file, layer.density, 'Density') + self._write_to_idf_format(file, layer.specific_heat, 'Specific Heat') + self._write_to_idf_format(file, layer.thermal_absorptance, 'Thermal Absorptance') + self._write_to_idf_format(file, layer.solar_absorptance, 'Solar Absorptance') + self._write_to_idf_format(file, layer.visible_absorptance, 'Visible Absorptance', ';') + + def _add_nomass_material(self, layer): + file = self._files['nomass_materials'] + self._write_to_idf_format(file, idf_cte.NOMASS_MATERIAL) + self._write_to_idf_format(file, layer.material_name, 'Name') + self._write_to_idf_format(file, idf_cte.ROUGHNESS, 'Roughness') + self._write_to_idf_format(file, layer.thermal_resistance, 'Thermal Resistance') + self._write_to_idf_format(file, 0.9, 'Thermal Absorptance') + self._write_to_idf_format(file, 0.7, 'Solar Absorptance') + self._write_to_idf_format(file, 0.7, 'Visible Absorptance', ';') + + def add(self, thermal_boundary): + for layer in thermal_boundary.layers: + if layer.material_name not in self._materials_added_to_idf: + self._materials_added_to_idf[layer.material_name] = True + if layer.no_mass: + self._add_nomass_material(layer) + else: + self._add_solid_material(layer) diff --git a/hub/exports/building_energy/idf_helper/idf_occupancy.py b/hub/exports/building_energy/idf_helper/idf_occupancy.py new file mode 100644 index 00000000..6890678e --- /dev/null +++ b/hub/exports/building_energy/idf_helper/idf_occupancy.py @@ -0,0 +1,46 @@ +from hub.exports.building_energy.cerc_idf import CercIdf +import hub.exports.building_energy.idf_helper as idf_cte + + +class IdfOccupancy(CercIdf): + def add(self, thermal_zone, zone_name): + number_of_people = thermal_zone.occupancy.occupancy_density * thermal_zone.total_floor_area + fraction_radiant = 0 + total_sensible = ( + thermal_zone.occupancy.sensible_radiative_internal_gain + thermal_zone.occupancy.sensible_convective_internal_gain + ) + if total_sensible != 0: + fraction_radiant = thermal_zone.occupancy.sensible_radiative_internal_gain / total_sensible + occupancy_schedule = f'Occupancy schedules {thermal_zone.usage_name}' + activity_level_schedule = f'Activity Level schedules {thermal_zone.usage_name}' + file = self._files['occupancy'] + self._write_to_idf_format(file, idf_cte.PEOPLE) + self._write_to_idf_format(file, f'{zone_name}_occupancy', 'Name') + self._write_to_idf_format(file, zone_name, 'Zone or ZoneList or Space or SpaceList Name') + self._write_to_idf_format(file, occupancy_schedule, 'Number of People Schedule Name') + self._write_to_idf_format(file, 'People', 'Number of People Calculation Method') + self._write_to_idf_format(file, number_of_people, 'Number of People') + self._write_to_idf_format(file, idf_cte.EMPTY, 'People per Floor Area') + self._write_to_idf_format(file, idf_cte.EMPTY, 'Floor Area per Person') + self._write_to_idf_format(file, fraction_radiant, 'Fraction Radiant') + self._write_to_idf_format(file, idf_cte.AUTOCALCULATE, 'Sensible Heat Fraction') + self._write_to_idf_format(file, activity_level_schedule, 'Activity Level Schedule Name') + self._write_to_idf_format(file, '3.82e-08', 'Carbon Dioxide Generation Rate') + self._write_to_idf_format(file, 'No', 'Enable ASHRAE 55 Comfort Warnings') + self._write_to_idf_format(file, 'EnclosureAveraged', 'Mean Radiant Temperature Calculation Type') + self._write_to_idf_format(file, idf_cte.EMPTY, 'Surface NameAngle Factor List Name') + self._write_to_idf_format(file, idf_cte.EMPTY, 'Work Efficiency Schedule Name') + self._write_to_idf_format(file, 'ClothingInsulationSchedule', 'Clothing Insulation Calculation Method') + self._write_to_idf_format(file, idf_cte.EMPTY, 'Clothing Insulation Calculation Method Schedule Name') + self._write_to_idf_format(file, idf_cte.EMPTY, 'Clothing Insulation Schedule Name') + self._write_to_idf_format(file, idf_cte.EMPTY, 'Air Velocity Schedule Name') + self._write_to_idf_format(file, idf_cte.EMPTY, 'Thermal Comfort Model 1 Type') + self._write_to_idf_format(file, idf_cte.EMPTY, 'Thermal Comfort Model 2 Type') + self._write_to_idf_format(file, idf_cte.EMPTY, 'Thermal Comfort Model 3 Type') + self._write_to_idf_format(file, idf_cte.EMPTY, 'Thermal Comfort Model 4 Type') + self._write_to_idf_format(file, idf_cte.EMPTY, 'Thermal Comfort Model 5 Type') + self._write_to_idf_format(file, idf_cte.EMPTY, 'Thermal Comfort Model 6 Type') + self._write_to_idf_format(file, idf_cte.EMPTY, 'Thermal Comfort Model 7 Type') + self._write_to_idf_format(file, idf_cte.EMPTY, 'Ankle Level Air Velocity Schedule Name') + self._write_to_idf_format(file, '15.56', 'Cold Stress Temperature Threshold') + self._write_to_idf_format(file, '30', 'Heat Stress Temperature Threshold', ';') diff --git a/hub/exports/building_energy/idf_helper/idf_schedules.py b/hub/exports/building_energy/idf_helper/idf_schedules.py new file mode 100644 index 00000000..4abca7c3 --- /dev/null +++ b/hub/exports/building_energy/idf_helper/idf_schedules.py @@ -0,0 +1,29 @@ +from hub.exports.building_energy.cerc_idf import CercIdf +import hub.exports.building_energy.idf_helper as idf_cte + + +class IdfSchedules(CercIdf): + def add(self, usage, schedule_type, schedules): + if len(schedules) < 1: + return + schedule_name = f'{schedule_type} schedules {usage}' + if schedule_name not in self._schedules_added_to_idf: + self._schedules_added_to_idf[schedule_name] = True + file = self._files['schedules'] + self._write_to_idf_format(file, idf_cte.COMPACT_SCHEDULE) + self._write_to_idf_format(file, schedule_name, 'Name') + self._write_to_idf_format(file, idf_cte.idf_type_limits[schedules[0].data_type], 'Schedule Type Limits Name') + self._write_to_idf_format(file, 'Through: 12/31', 'Field 1') + counter = 1 + for j, schedule in enumerate(schedules): + _val = schedule.values + _new_field = '' + for day_type in schedule.day_types: + _new_field += f' {idf_cte.idf_day_types[day_type]}' + self._write_to_idf_format(file, f'For:{_new_field}', f'Field {j * 25 + 2}') + counter += 1 + for i, _ in enumerate(_val): + self._write_to_idf_format(file, f'Until: {i + 1:02d}:00,{_val[i]}', f'Field {j * 25 + 3 + i}') + counter += 1 + self._write_to_idf_format(file, 'For AllOtherDays', f'Field {counter + 1}') + self._write_to_idf_format(file, 'Until: 24:00,0.0', f'Field {counter + 2}', ';') diff --git a/hub/exports/building_energy/idf_helper/idf_surfaces.py b/hub/exports/building_energy/idf_helper/idf_surfaces.py new file mode 100644 index 00000000..3a0bcabe --- /dev/null +++ b/hub/exports/building_energy/idf_helper/idf_surfaces.py @@ -0,0 +1,52 @@ +import hub.exports.building_energy.idf_helper as idf_cte +import hub.helpers.constants as cte +from hub.exports.building_energy.cerc_idf import CercIdf + + +class IdfSurfaces(CercIdf): + def add(self, building, zone_name): + # Verify if create building surfaces "by hand" it's faster wwr it's missing + zone_name = f'{zone_name}' + file = self._files['surfaces'] + for thermal_zone in building.thermal_zones_from_internal_zones: + for index, boundary in enumerate(thermal_zone.thermal_boundaries): + surface_type = idf_cte.idf_surfaces[boundary.parent_surface.type] + outside_boundary_condition = idf_cte.OUTDOORS + sun_exposure = idf_cte.SUN_EXPOSED + wind_exposure = idf_cte.WIND_EXPOSED + outside_boundary_condition_object = idf_cte.EMPTY + name = f'Building_{building.name}_surface_{index}' + construction_name = f'{boundary.construction_name} {boundary.parent_surface.type}' + space_name = idf_cte.EMPTY + if boundary.parent_surface.type == cte.GROUND: + outside_boundary_condition = idf_cte.GROUND + sun_exposure = idf_cte.NON_SUN_EXPOSED + wind_exposure = idf_cte.NON_WIND_EXPOSED + if boundary.parent_surface.percentage_shared is not None and boundary.parent_surface.percentage_shared > 0.5: + outside_boundary_condition_object = f'Building_{building.name}_surface_{index}' + outside_boundary_condition = idf_cte.SURFACE + sun_exposure = idf_cte.NON_SUN_EXPOSED + wind_exposure = idf_cte.NON_WIND_EXPOSED + self._write_to_idf_format(file, idf_cte.BUILDING_SURFACE) + self._write_to_idf_format(file, name, 'Name') + self._write_to_idf_format(file, surface_type, 'Surface Type') + self._write_to_idf_format(file, construction_name, 'Construction Name') + self._write_to_idf_format(file, zone_name, 'Zone Name') + self._write_to_idf_format(file, space_name, 'Space Name') + self._write_to_idf_format(file, outside_boundary_condition, 'Outside Boundary Condition') + self._write_to_idf_format(file, outside_boundary_condition_object, 'Outside Boundary Condition Object') + self._write_to_idf_format(file, sun_exposure, 'Sun Exposure') + self._write_to_idf_format(file, wind_exposure, 'Wind Exposure') + self._write_to_idf_format(file, idf_cte.AUTOCALCULATE, 'View Factor to Ground') + self._write_to_idf_format(file, idf_cte.AUTOCALCULATE, 'Number of Vertices') + coordinates = self._matrix_to_list(boundary.parent_surface.solid_polygon.coordinates, + self._city.lower_corner) + eol = ',' + coordinates_length = len(coordinates) + for i, coordinate in enumerate(coordinates): + vertex = i + 1 + if vertex == coordinates_length: + eol = ';' + self._write_to_idf_format(file, coordinate[0], f'Vertex {vertex} Xcoordinate') + self._write_to_idf_format(file, coordinate[1], f'Vertex {vertex} Ycoordinate') + self._write_to_idf_format(file, coordinate[2], f'Vertex {vertex} Zcoordinate', eol) diff --git a/hub/exports/building_energy/idf_helper/idf_windows_constructions.py b/hub/exports/building_energy/idf_helper/idf_windows_constructions.py new file mode 100644 index 00000000..9dba5ade --- /dev/null +++ b/hub/exports/building_energy/idf_helper/idf_windows_constructions.py @@ -0,0 +1,16 @@ +from hub.exports.building_energy.cerc_idf import CercIdf +import hub.exports.building_energy.idf_helper as idf_cte + + +class IdfWindowsConstructions(CercIdf): + def add(self): + glazing_index = self._windows_added_to_idf['glazing_index'] + 1 + for i in range(1, glazing_index): + construction_name = f'window_construction_{i}' + material_name = f'glazing_{i}' + if construction_name not in self._constructions_added_to_idf: + self._constructions_added_to_idf[construction_name] = True + file = self._files['constructions'] + self._write_to_idf_format(file, idf_cte.CONSTRUCTION) + self._write_to_idf_format(file, construction_name, 'Name') + self._write_to_idf_format(file, material_name, 'Outside Layer', ';') diff --git a/hub/exports/building_energy/idf_helper/idf_windows_materials.py b/hub/exports/building_energy/idf_helper/idf_windows_materials.py new file mode 100644 index 00000000..3ba01144 --- /dev/null +++ b/hub/exports/building_energy/idf_helper/idf_windows_materials.py @@ -0,0 +1,16 @@ +from hub.exports.building_energy.cerc_idf import CercIdf +import hub.exports.building_energy.idf_helper as idf_cte + + +class IdfWindowsMaterials(CercIdf): + def add(self, thermal_opening): + name = f'{thermal_opening.overall_u_value}_{thermal_opening.g_value}' + if name not in self._windows_added_to_idf: + glazing_index = self._windows_added_to_idf['glazing_index'] + 1 + self._windows_added_to_idf[name] = True + self._windows_added_to_idf['glazing_index'] = glazing_index # increase the count + file = self._files['window_materials'] + self._write_to_idf_format(file, idf_cte.WINDOW_MATERIAL) + self._write_to_idf_format(file, f'glazing_{glazing_index}', 'Name') + self._write_to_idf_format(file, thermal_opening.overall_u_value, 'UFactor') + self._write_to_idf_format(file, thermal_opening.g_value, 'Solar Heat Gain Coefficient', ';') \ No newline at end of file diff --git a/hub/exports/building_energy/idf_helper/idf_zones.py b/hub/exports/building_energy/idf_helper/idf_zones.py new file mode 100644 index 00000000..91e06921 --- /dev/null +++ b/hub/exports/building_energy/idf_helper/idf_zones.py @@ -0,0 +1,21 @@ +from hub.exports.building_energy.cerc_idf import CercIdf +import hub.exports.building_energy.idf_helper as idf_cte + + +class IdfZones(CercIdf): + def add(self, thermal_zone, zone_name): + file = self._files['zones'] + self._write_to_idf_format(file, idf_cte.ZONE) + self._write_to_idf_format(file, zone_name, 'Name') + self._write_to_idf_format(file, 0, 'Direction of Relative North') + self._write_to_idf_format(file, 0, 'X Origin') + self._write_to_idf_format(file, 0, 'Y Origin') + self._write_to_idf_format(file, 0, 'Z Origin') + self._write_to_idf_format(file, 1, 'Type') + self._write_to_idf_format(file, 1, 'Multiplier') + self._write_to_idf_format(file, idf_cte.AUTOCALCULATE, 'Ceiling Height') + self._write_to_idf_format(file, thermal_zone.volume, 'Volume') + self._write_to_idf_format(file, idf_cte.AUTOCALCULATE, 'Floor Area') + self._write_to_idf_format(file, idf_cte.EMPTY, 'Zone Inside Convection Algorithm') + self._write_to_idf_format(file, idf_cte.EMPTY, 'Zone Outside Convection Algorithm') + self._write_to_idf_format(file, 'Yes', 'Part of Total Floor Area', ';') \ No newline at end of file diff --git a/hub/exports/energy_building_exports_factory.py b/hub/exports/energy_building_exports_factory.py index e474d775..fd2dc7a9 100644 --- a/hub/exports/energy_building_exports_factory.py +++ b/hub/exports/energy_building_exports_factory.py @@ -11,7 +11,7 @@ import requests from hub.exports.building_energy.energy_ade import EnergyAde from hub.exports.building_energy.idf import Idf -from hub.exports.building_energy.cerc_idf import CercIdf +from hub.exports.building_energy.idf_helper import CercIdf from hub.exports.building_energy.insel.insel_monthly_energy_balance import InselMonthlyEnergyBalance from hub.helpers.utils import validate_import_export_type from hub.imports.weather.helpers.weather import Weather as wh diff --git a/tests/test_exports.py b/tests/test_exports.py index ac881960..3e435953 100644 --- a/tests/test_exports.py +++ b/tests/test_exports.py @@ -144,7 +144,7 @@ class TestExports(TestCase): UsageFactory('nrcan', city).enrich() WeatherFactory('epw', city).enrich() try: - EnergyBuildingsExportsFactory('cerc_idf', city, self._output_path)._cerc_idf + EnergyBuildingsExportsFactory('idf_helper', city, self._output_path)._cerc_idf except Exception: self.fail("Idf ExportsFactory raised ExceptionType unexpectedly!")