cerc_idf basic implementation
This commit is contained in:
parent
c4636c2c3c
commit
5401064905
422
hub/exports/building_energy/cerc_idf.py
Normal file
422
hub/exports/building_energy/cerc_idf.py
Normal file
|
@ -0,0 +1,422 @@
|
|||
"""
|
||||
Idf exports one building to idf format
|
||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||
Copyright © 2022 Concordia CERC group
|
||||
Project Coder Guille Guillermo.GutierrezMorote@concordia.ca
|
||||
Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
||||
Oriol Gavalda Torrellas oriol.gavalda@concordia.ca
|
||||
"""
|
||||
import copy
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
import hub.helpers.constants as cte
|
||||
import hub.exports.building_energy.idf_helper as idf_cte
|
||||
from hub.city_model_structure.attributes.schedule import Schedule
|
||||
from hub.city_model_structure.building_demand.layer import Layer
|
||||
|
||||
|
||||
class CercIdf:
|
||||
"""
|
||||
Exports city to IDF
|
||||
"""
|
||||
|
||||
_schedules_added_to_idf = {}
|
||||
_materials_added_to_idf = {}
|
||||
_windows_added_to_idf = {'glazing_index': 0}
|
||||
_constructions_added_to_idf = {}
|
||||
|
||||
def __init__(self, city, output_path, idf_file_path, idd_file_path, epw_file_path, export_type="Surfaces",
|
||||
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._output_schedules_path = str((output_path / 'schedules.idf').resolve())
|
||||
self._output_file_schedules_path = str((output_path / 'file_schedules.idf').resolve())
|
||||
self._output_solid_materials_path = str((output_path / 'solid_materials.idf').resolve())
|
||||
self._output_nomass_materials_path = str((output_path / 'nomass_materials.idf').resolve())
|
||||
self._output_window_materials_path = str((output_path / 'window_materials.idf').resolve())
|
||||
self._output_constructions_path = str((output_path / 'constructions.idf').resolve())
|
||||
self._output_surfaces_path = str((output_path / 'surfaces.idf').resolve())
|
||||
self._output_file_path = str((output_path / f'{city.name}.idf').resolve())
|
||||
|
||||
self._export_type = export_type
|
||||
self._idd_file_path = str(idd_file_path)
|
||||
self._idf_file_path = str(idf_file_path)
|
||||
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()
|
||||
|
||||
self._output_schedules = open(self._output_schedules_path, 'w')
|
||||
self._output_file_schedules = open(self._output_file_schedules_path, 'w')
|
||||
self._output_solid_materials = open(self._output_solid_materials_path, 'w')
|
||||
self._output_nomass_materials = open(self._output_nomass_materials_path, 'w')
|
||||
self._output_window_materials = open(self._output_window_materials_path, 'w')
|
||||
self._output_constructions = open(self._output_constructions_path, 'w')
|
||||
self._output_surfaces = open(self._output_surfaces_path, 'w')
|
||||
|
||||
with open(self._output_file_path, 'w') as self._idf_file:
|
||||
self._idf_file.writelines(lines)
|
||||
self._export()
|
||||
|
||||
def __del__(self):
|
||||
self._output_schedules.close()
|
||||
self._output_file_schedules.close()
|
||||
self._output_solid_materials.close()
|
||||
self._output_nomass_materials.close()
|
||||
|
||||
@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._output_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 = ','
|
||||
for i, coordinate in enumerate(coordinates):
|
||||
if i == len(coordinates) -1:
|
||||
eol = ';'
|
||||
self._write_to_idf_format(file, coordinate[0], f'Vertex {i} Xcoordinate')
|
||||
self._write_to_idf_format(file, coordinate[1], f'Vertex {i} Ycoordinate')
|
||||
self._write_to_idf_format(file, coordinate[2], f'Vertex {i} Zcoordinate', eol)
|
||||
|
||||
@staticmethod
|
||||
def _create_infiltration_schedules(thermal_zone):
|
||||
_infiltration_schedules = []
|
||||
if thermal_zone.thermal_control is None:
|
||||
return []
|
||||
for hvac_availability_schedule in thermal_zone.thermal_control.hvac_availability_schedules:
|
||||
_schedule = Schedule()
|
||||
_schedule.type = cte.INFILTRATION
|
||||
_schedule.data_type = cte.FRACTION
|
||||
_schedule.time_step = cte.HOUR
|
||||
_schedule.time_range = cte.DAY
|
||||
_schedule.day_types = copy.deepcopy(hvac_availability_schedule.day_types)
|
||||
_infiltration_values = []
|
||||
for hvac_value in hvac_availability_schedule.values:
|
||||
if hvac_value == 0:
|
||||
_infiltration_values.append(1.0)
|
||||
else:
|
||||
if thermal_zone.infiltration_rate_system_off == 0:
|
||||
_infiltration_values.append(0.0)
|
||||
else:
|
||||
_infiltration_values.append(
|
||||
thermal_zone.infiltration_rate_system_on / thermal_zone.infiltration_rate_system_off)
|
||||
_schedule.values = _infiltration_values
|
||||
_infiltration_schedules.append(_schedule)
|
||||
return _infiltration_schedules
|
||||
|
||||
@staticmethod
|
||||
def _create_ventilation_schedules(thermal_zone):
|
||||
_ventilation_schedules = []
|
||||
if thermal_zone.thermal_control is None:
|
||||
return []
|
||||
for hvac_availability_schedule in thermal_zone.thermal_control.hvac_availability_schedules:
|
||||
_schedule = Schedule()
|
||||
_schedule.type = cte.VENTILATION
|
||||
_schedule.data_type = cte.FRACTION
|
||||
_schedule.time_step = cte.HOUR
|
||||
_schedule.time_range = cte.DAY
|
||||
_schedule.day_types = copy.deepcopy(hvac_availability_schedule.day_types)
|
||||
_ventilation_schedules = thermal_zone.thermal_control.hvac_availability_schedules
|
||||
return _ventilation_schedules
|
||||
|
||||
@staticmethod
|
||||
def _create_constant_value_schedules(value, amount):
|
||||
_schedule = Schedule()
|
||||
_schedule.type = ''
|
||||
_schedule.data_type = cte.ANY_NUMBER
|
||||
_schedule.time_step = cte.HOUR
|
||||
_schedule.time_range = cte.DAY
|
||||
_schedule.day_types = ['monday',
|
||||
'tuesday',
|
||||
'wednesday',
|
||||
'thursday',
|
||||
'friday',
|
||||
'saturday',
|
||||
'sunday',
|
||||
'holiday',
|
||||
'winter_design_day',
|
||||
'summer_design_day']
|
||||
_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._output_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._output_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._output_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._output_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._output_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):
|
||||
"""
|
||||
CONSTRUCTION,
|
||||
1000_1900_6 Roof, !- Name
|
||||
Asphalt 1, !- Outside Layer
|
||||
virtual_no_mass_13, !- Layer 2
|
||||
MW Glass Wool (rolls); !- Layer 3
|
||||
"""
|
||||
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._output_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._output_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 _merge_files(self):
|
||||
self._output_schedules.flush()
|
||||
with open(self._output_schedules_path, 'r') as file:
|
||||
lines = file.readlines()
|
||||
self._idf_file.writelines(lines)
|
||||
self._output_file_schedules.flush()
|
||||
with open(self._output_file_schedules_path, 'r') as file:
|
||||
lines = file.readlines()
|
||||
self._idf_file.writelines(lines)
|
||||
self._output_solid_materials.flush()
|
||||
with open(self._output_solid_materials_path, 'r') as file:
|
||||
lines = file.readlines()
|
||||
self._idf_file.writelines(lines)
|
||||
self._output_nomass_materials.flush()
|
||||
with open(self._output_nomass_materials_path, 'r') as file:
|
||||
lines = file.readlines()
|
||||
self._idf_file.writelines(lines)
|
||||
self._output_window_materials.flush()
|
||||
with open(self._output_window_materials_path, 'r') as file:
|
||||
lines = file.readlines()
|
||||
self._idf_file.writelines(lines)
|
||||
self._output_constructions.flush()
|
||||
with open(self._output_constructions_path, 'r') as file:
|
||||
lines = file.readlines()
|
||||
self._idf_file.writelines(lines)
|
||||
self._output_surfaces.flush()
|
||||
with open(self._output_surfaces_path, 'r') as file:
|
||||
lines = file.readlines()
|
||||
self._idf_file.writelines(lines)
|
||||
|
||||
def _export(self):
|
||||
for building in self._city.buildings:
|
||||
is_target = building.name in self._target_buildings or building.name in self._adjacent_buildings
|
||||
for internal_zone in building.internal_zones:
|
||||
if internal_zone.thermal_zones_from_internal_zones is None:
|
||||
self._target_buildings.remove(building.name)
|
||||
is_target = False
|
||||
continue
|
||||
for thermal_zone in internal_zone.thermal_zones_from_internal_zones:
|
||||
if is_target:
|
||||
start = datetime.now()
|
||||
service_temperature = thermal_zone.domestic_hot_water.service_temperature
|
||||
usage = thermal_zone.usage_name
|
||||
occ = thermal_zone.occupancy
|
||||
if occ.occupancy_density == 0:
|
||||
total_heat = 0
|
||||
else:
|
||||
total_heat = (
|
||||
occ.sensible_convective_internal_gain +
|
||||
occ.sensible_radiative_internal_gain +
|
||||
occ.latent_internal_gain
|
||||
) / occ.occupancy_density
|
||||
self._add_idf_schedules(usage, 'Infiltration', self._create_infiltration_schedules(thermal_zone))
|
||||
self._add_idf_schedules(usage, 'Ventilation', self._create_ventilation_schedules(thermal_zone))
|
||||
self._add_idf_schedules(usage, 'Occupancy', thermal_zone.occupancy.occupancy_schedules)
|
||||
self._add_idf_schedules(usage, 'HVAC AVAIL', thermal_zone.thermal_control.hvac_availability_schedules)
|
||||
self._add_idf_schedules(usage, 'Heating thermostat', thermal_zone.thermal_control.heating_set_point_schedules)
|
||||
self._add_idf_schedules(usage, 'Cooling thermostat', thermal_zone.thermal_control.cooling_set_point_schedules)
|
||||
self._add_idf_schedules(usage, 'Lighting', thermal_zone.lighting.schedules)
|
||||
self._add_idf_schedules(usage, 'Appliance', thermal_zone.appliances.schedules)
|
||||
self._add_idf_schedules(usage, 'DHW_prof', thermal_zone.domestic_hot_water.schedules)
|
||||
self._add_idf_schedules(usage, 'DHW_temp', self._create_constant_value_schedules(service_temperature, 24))
|
||||
self._add_idf_schedules(usage, 'Activity Level', self._create_constant_value_schedules(total_heat, 24))
|
||||
self._add_file_schedules(usage, 'cold_temp', self._create_constant_value_schedules(building.cold_water_temperature[cte.HOUR], 24))
|
||||
for thermal_boundary in thermal_zone.thermal_boundaries:
|
||||
self._add_materials(thermal_boundary)
|
||||
self._add_constructions(thermal_boundary)
|
||||
for thermal_opening in thermal_boundary.thermal_openings:
|
||||
self._add_window_materials(thermal_opening)
|
||||
self._add_windows_constructions()
|
||||
if is_target:
|
||||
self._add_surfaces(building, building.name)
|
||||
|
||||
|
||||
# Merge files
|
||||
self._merge_files()
|
|
@ -107,6 +107,7 @@ class Idf:
|
|||
else:
|
||||
for building_name in target_buildings:
|
||||
building = city.city_object(building_name)
|
||||
print('Name: ', building_name)
|
||||
if building.neighbours is not None:
|
||||
self._adjacent_buildings += building.neighbours
|
||||
self._export()
|
||||
|
@ -520,7 +521,7 @@ class Idf:
|
|||
is_target = building.name in self._target_buildings or building.name in self._adjacent_buildings
|
||||
for internal_zone in building.internal_zones:
|
||||
if internal_zone.thermal_zones_from_internal_zones is None:
|
||||
self._target_buildings.remoidf_surface_typeve(building.name)
|
||||
self._target_buildings.remove(building.name)
|
||||
is_target = False
|
||||
continue
|
||||
for thermal_zone in internal_zone.thermal_zones_from_internal_zones:
|
||||
|
@ -571,9 +572,8 @@ class Idf:
|
|||
if self._export_type == "Surfaces":
|
||||
if is_target:
|
||||
if building.thermal_zones_from_internal_zones is not None:
|
||||
start = datetime.datetime.now()
|
||||
self._add_surfaces(building, building.name)
|
||||
print(f'add surfaces {datetime.datetime.now() - start}')
|
||||
pass
|
||||
# self._add_surfaces(building, building.name)
|
||||
else:
|
||||
self._add_pure_geometry(building, building.name)
|
||||
else:
|
||||
|
@ -624,6 +624,10 @@ class Idf:
|
|||
self._idf.removeidfobject(window)
|
||||
|
||||
self._idf.saveas(str(self._output_file))
|
||||
for building in self._city.buildings:
|
||||
if self._export_type == "Surfaces":
|
||||
if is_target and building.thermal_zones_from_internal_zones is not None:
|
||||
self._add_surfaces(building, building.name)
|
||||
return self._idf
|
||||
|
||||
def run(self):
|
||||
|
@ -703,8 +707,13 @@ class Idf:
|
|||
self._idf.set_wwr(wwr)
|
||||
|
||||
def _add_surfaces(self, building, zone_name):
|
||||
start = datetime.datetime.now()
|
||||
print(f'thermal_zones {len(building.thermal_zones_from_internal_zones)}')
|
||||
for thermal_zone in building.thermal_zones_from_internal_zones:
|
||||
print(f'thermal zone: {datetime.datetime.now() - start}')
|
||||
for index, boundary in enumerate(thermal_zone.thermal_boundaries):
|
||||
print(f'{index} boundary: {datetime.datetime.now() - start}')
|
||||
|
||||
idf_surface_type = self.idf_surfaces[boundary.parent_surface.type]
|
||||
outside_boundary_condition = 'Outdoors'
|
||||
sun_exposure = 'SunExposed'
|
||||
|
@ -731,12 +740,14 @@ class Idf:
|
|||
else:
|
||||
construction_name = f'{boundary.construction_name} {boundary.parent_surface.type}'
|
||||
_kwargs['Construction_Name'] = construction_name
|
||||
|
||||
start = datetime.datetime.now()
|
||||
surface = self._idf.newidfobject(self._SURFACE, **_kwargs)
|
||||
|
||||
print(f'create surface: {datetime.datetime.now() - start}')
|
||||
coordinates = self._matrix_to_list(boundary.parent_surface.solid_polygon.coordinates,
|
||||
self._city.lower_corner)
|
||||
|
||||
surface.setcoords(coordinates)
|
||||
print(f'set coords surface: {datetime.datetime.now() - start}')
|
||||
|
||||
if self._lod >= 3:
|
||||
for internal_zone in building.internal_zones:
|
||||
|
|
62
hub/exports/building_energy/idf_files/base.idf
Normal file
62
hub/exports/building_energy/idf_files/base.idf
Normal file
|
@ -0,0 +1,62 @@
|
|||
!- Linux Line endings
|
||||
|
||||
Version,
|
||||
24.1; !- Version Identifier
|
||||
|
||||
SimulationControl,
|
||||
No, !- Do Zone Sizing Calculation
|
||||
No, !- Do System Sizing Calculation
|
||||
No, !- Do Plant Sizing Calculation
|
||||
No, !- Run Simulation for Sizing Periods
|
||||
Yes, !- Run Simulation for Weather File Run Periods
|
||||
No, !- Do HVAC Sizing Simulation for Sizing Periods
|
||||
1; !- Maximum Number of HVAC Sizing Simulation Passes
|
||||
|
||||
Building,
|
||||
Buildings in b'Montreal', !- Name
|
||||
0, !- North Axis
|
||||
Suburbs, !- Terrain
|
||||
0.04, !- Loads Convergence Tolerance Value
|
||||
0.4, !- Temperature Convergence Tolerance Value
|
||||
FullExterior, !- Solar Distribution
|
||||
25, !- Maximum Number of Warmup Days
|
||||
6; !- Minimum Number of Warmup Days
|
||||
|
||||
Timestep,
|
||||
4; !- Number of Timesteps per Hour
|
||||
|
||||
RunPeriod,
|
||||
Run Period 1, !- Name
|
||||
1, !- Begin Month
|
||||
1, !- Begin Day of Month
|
||||
, !- Begin Year
|
||||
12, !- End Month
|
||||
31, !- End Day of Month
|
||||
, !- End Year
|
||||
Tuesday, !- Day of Week for Start Day
|
||||
Yes, !- Use Weather File Holidays and Special Days
|
||||
Yes, !- Use Weather File Daylight Saving Period
|
||||
No, !- Apply Weekend Holiday Rule
|
||||
Yes, !- Use Weather File Rain Indicators
|
||||
Yes; !- Use Weather File Snow Indicators
|
||||
|
||||
SCHEDULETYPELIMITS,
|
||||
Any Number, !- Name
|
||||
, !- Lower Limit Value
|
||||
, !- Upper Limit Value
|
||||
, !- Numeric Type
|
||||
Dimensionless; !- Unit Type
|
||||
|
||||
SCHEDULETYPELIMITS,
|
||||
Fraction, !- Name
|
||||
0, !- Lower Limit Value
|
||||
1, !- Upper Limit Value
|
||||
Continuous, !- Numeric Type
|
||||
Dimensionless; !- Unit Type
|
||||
|
||||
SCHEDULETYPELIMITS,
|
||||
On/Off, !- Name
|
||||
0, !- Lower Limit Value
|
||||
1, !- Upper Limit Value
|
||||
Discrete, !- Numeric Type
|
||||
Dimensionless; !- Unit Type
|
47
hub/exports/building_energy/idf_helper/__init__.py
Normal file
47
hub/exports/building_energy/idf_helper/__init__.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
import hub.helpers.constants as cte
|
||||
|
||||
BUILDING_SURFACE = '\nBUILDINGSURFACE:DETAILED,\n'
|
||||
COMPACT_SCHEDULE = '\nSCHEDULE:COMPACT,\n'
|
||||
FILE_SCHEDULE = '\nSCHEDULE:FILE,\n'
|
||||
NOMASS_MATERIAL = '\nMATERIAL:NOMASS,\n'
|
||||
SOLID_MATERIAL = '\nMATERIAL,\n'
|
||||
WINDOW_MATERIAL = '\nWINDOWMATERIAL:SIMPLEGLAZINGSYSTEM,\n'
|
||||
CONSTRUCTION = '\nCONSTRUCTION,\n'
|
||||
|
||||
AUTOCALCULATE = 'autocalculate'
|
||||
ROUGHNESS = 'MediumRough'
|
||||
OUTDOORS = 'Outdoors'
|
||||
GROUND = 'Ground'
|
||||
SURFACE = 'Surface'
|
||||
SUN_EXPOSED = 'SunExposed'
|
||||
WIND_EXPOSED = 'WindExposed'
|
||||
NON_SUN_EXPOSED = 'NoSun'
|
||||
NON_WIND_EXPOSED = 'NoWind'
|
||||
EMPTY = ''
|
||||
|
||||
idf_surfaces = {
|
||||
cte.WALL: 'wall',
|
||||
cte.GROUND: 'floor',
|
||||
cte.ROOF: 'roof'
|
||||
}
|
||||
|
||||
idf_type_limits = {
|
||||
cte.ON_OFF: 'on/off',
|
||||
cte.FRACTION: 'Fraction',
|
||||
cte.ANY_NUMBER: 'Any Number',
|
||||
cte.CONTINUOUS: 'Continuous',
|
||||
cte.DISCRETE: 'Discrete'
|
||||
}
|
||||
|
||||
idf_day_types = {
|
||||
cte.MONDAY: 'Monday',
|
||||
cte.TUESDAY: 'Tuesday',
|
||||
cte.WEDNESDAY: 'Wednesday',
|
||||
cte.THURSDAY: 'Thursday',
|
||||
cte.FRIDAY: 'Friday',
|
||||
cte.SATURDAY: 'Saturday',
|
||||
cte.SUNDAY: 'Sunday',
|
||||
cte.HOLIDAY: 'Holidays',
|
||||
cte.WINTER_DESIGN_DAY: 'WinterDesignDay',
|
||||
cte.SUMMER_DESIGN_DAY: 'SummerDesignDay'
|
||||
}
|
|
@ -11,6 +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.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
|
||||
|
@ -60,6 +61,18 @@ class EnergyBuildingsExportsFactory:
|
|||
return Idf(self._city, self._path, (idf_data_path / 'Minimal.idf'), (idf_data_path / 'Energy+.idd'), weather_path,
|
||||
target_buildings=self._target_buildings)
|
||||
|
||||
@property
|
||||
def _cerc_idf(self):
|
||||
idf_data_path = (Path(__file__).parent / './building_energy/idf_files/').resolve()
|
||||
url = wh().epw_file(self._city.region_code)
|
||||
weather_path = (Path(__file__).parent.parent / f'data/weather/epw/{url.rsplit("/", 1)[1]}').resolve()
|
||||
if not weather_path.exists():
|
||||
with open(weather_path, 'wb') as epw_file:
|
||||
epw_file.write(requests.get(url, allow_redirects=True).content)
|
||||
return CercIdf(self._city, self._path, (idf_data_path / 'base.idf'), (idf_data_path / 'Energy+.idd'), weather_path,
|
||||
target_buildings=self._target_buildings)
|
||||
|
||||
|
||||
@property
|
||||
def _insel_monthly_energy_balance(self):
|
||||
"""
|
||||
|
|
|
@ -144,6 +144,7 @@ class TestExports(TestCase):
|
|||
UsageFactory('nrcan', city).enrich()
|
||||
WeatherFactory('epw', city).enrich()
|
||||
try:
|
||||
EnergyBuildingsExportsFactory('idf', city, self._output_path, target_buildings=[1]).export()
|
||||
EnergyBuildingsExportsFactory('cerc_idf', city, self._output_path)._cerc_idf
|
||||
|
||||
except Exception:
|
||||
self.fail("Idf ExportsFactory raised ExceptionType unexpectedly!")
|
||||
|
|
Loading…
Reference in New Issue
Block a user