Merge branch 'idf_export' into 'master'
Idf export See merge request Guille/libs!16
This commit is contained in:
commit
6414ceedaf
|
@ -1,3 +1,4 @@
|
|||
#
|
||||
# Prepare your environment
|
||||
|
||||
Download the latest version of python and Microsoft c++ redistributable
|
||||
|
|
|
@ -129,10 +129,10 @@ class ThermalZone:
|
|||
self._indirectly_heated_area_ratio = float(value)
|
||||
|
||||
@property
|
||||
def infiltration_rate_system_on(self) -> Union[None, Schedule]:
|
||||
def infiltration_rate_system_on(self):
|
||||
"""
|
||||
Get thermal zone infiltration rate system on in air changes per hour (ACH)
|
||||
:return: None or Schedule
|
||||
:return: None or float
|
||||
"""
|
||||
return self._infiltration_rate_system_on
|
||||
|
||||
|
@ -140,15 +140,15 @@ class ThermalZone:
|
|||
def infiltration_rate_system_on(self, value):
|
||||
"""
|
||||
Set thermal zone infiltration rate system on in air changes per hour (ACH)
|
||||
:param value: Schedule
|
||||
:param value: float
|
||||
"""
|
||||
self._infiltration_rate_system_on = value
|
||||
|
||||
@property
|
||||
def infiltration_rate_system_off(self) -> Union[None, Schedule]:
|
||||
def infiltration_rate_system_off(self):
|
||||
"""
|
||||
Get thermal zone infiltration rate system off in air changes per hour (ACH)
|
||||
:return: None or Schedule
|
||||
:return: None or float
|
||||
"""
|
||||
return self._infiltration_rate_system_off
|
||||
|
||||
|
@ -156,7 +156,7 @@ class ThermalZone:
|
|||
def infiltration_rate_system_off(self, value):
|
||||
"""
|
||||
Set thermal zone infiltration rate system on in air changes per hour (ACH)
|
||||
:param value: Schedule
|
||||
:param value: float
|
||||
"""
|
||||
self._infiltration_rate_system_off = value
|
||||
|
||||
|
|
|
@ -1,20 +1,22 @@
|
|||
"""
|
||||
TestOccupancyFactory test and validate the city model structure schedules parameters
|
||||
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
|
||||
Soroush Samareh Abolhassani soroush.samarehabolhassani@mail.concordia.ca
|
||||
"""
|
||||
|
||||
import copy
|
||||
import math
|
||||
from pathlib import Path
|
||||
from geomeppy import IDF
|
||||
import helpers.constants as cte
|
||||
from city_model_structure.attributes.schedule import Schedule
|
||||
|
||||
|
||||
class Idf:
|
||||
"""
|
||||
Export city to IDF
|
||||
Exports city to IDF
|
||||
"""
|
||||
_THERMOSTAT = 'HVACTEMPLATE:THERMOSTAT'
|
||||
_IDEAL_LOAD_AIR_SYSTEM = 'HVACTEMPLATE:ZONE:IDEALLOADSAIRSYSTEM'
|
||||
|
@ -29,7 +31,6 @@ class Idf:
|
|||
_ZONE = 'ZONE'
|
||||
_LIGHTS = 'LIGHTS'
|
||||
_PEOPLE = 'PEOPLE'
|
||||
_ELECTRIC_EQUIPMENT = 'ELECTRICEQUIPMENT'
|
||||
_INFILTRATION = 'ZONEINFILTRATION:DESIGNFLOWRATE'
|
||||
_BUILDING_SURFACE = 'BuildingSurfaceDetailed'
|
||||
_SCHEDULE_LIMIT = 'SCHEDULETYPELIMITS'
|
||||
|
@ -38,6 +39,11 @@ class Idf:
|
|||
_ANY_NUMBER = 'Any Number'
|
||||
_CONTINUOUS = 'Continuous'
|
||||
_DISCRETE = 'Discrete'
|
||||
_BUILDING = 'BUILDING'
|
||||
_SIZING_PERIODS = 'SIZINGPERIOD:DESIGNDAY'
|
||||
_LOCATION = 'SITE:LOCATION'
|
||||
_WINDOW_MATERIAL_SIMPLE = 'WINDOWMATERIAL:SIMPLEGLAZINGSYSTEM'
|
||||
_WINDOW = 'WINDOW'
|
||||
|
||||
idf_surfaces = {
|
||||
# todo: make an enum for all the surface types
|
||||
|
@ -162,9 +168,6 @@ class Idf:
|
|||
_kwargs[f'Field_{j * 25 + 3 + i}'] = f'Until: {i + 1:02d}:00,{_val[i]}'
|
||||
self._idf.newidfobject(self._COMPACT_SCHEDULE, **_kwargs)
|
||||
|
||||
def _add_non_hourly_schedule(self, usage, schedules):
|
||||
raise NotImplementedError
|
||||
|
||||
def _write_schedules_file(self, usage, schedule):
|
||||
file_name = str((Path(self._output_path) / f'{schedule.type} schedules {usage}.dat').resolve())
|
||||
with open(file_name, 'w') as file:
|
||||
|
@ -173,7 +176,6 @@ class Idf:
|
|||
return file_name
|
||||
|
||||
def _add_file_schedule(self, usage, schedule, file_name):
|
||||
print(file_name)
|
||||
_schedule = self._idf.newidfobject(self._FILE_SCHEDULE, Name=f'{schedule.type} schedules {usage}')
|
||||
_schedule.Schedule_Type_Limits_Name = self.idf_type_limits[schedule.data_type]
|
||||
_schedule.File_Name = file_name
|
||||
|
@ -184,6 +186,28 @@ class Idf:
|
|||
_schedule.Interpolate_to_Timestep = 'No'
|
||||
_schedule.Minutes_per_Item = 60
|
||||
|
||||
def _add_infiltration_schedules(self, thermal_zone):
|
||||
_infiltration_schedules = []
|
||||
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(thermal_zone.infiltration_rate_system_off)
|
||||
else:
|
||||
_infiltration_values.append(thermal_zone.infiltration_rate_system_on)
|
||||
_schedule.values = _infiltration_values
|
||||
_infiltration_schedules.append(_schedule)
|
||||
for schedule in self._idf.idfobjects[self._HOURLY_SCHEDULE]:
|
||||
if schedule.Name == f'{_infiltration_schedules[0].type} schedules {thermal_zone.usage}':
|
||||
return
|
||||
return self._add_standard_compact_hourly_schedule(thermal_zone.usage, _infiltration_schedules)
|
||||
|
||||
def _add_schedules(self, usage, new_schedules, schedule_from_file=False):
|
||||
if schedule_from_file:
|
||||
new_schedule = new_schedules[0]
|
||||
|
@ -218,6 +242,22 @@ class Idf:
|
|||
_kwargs[f'Layer_{i + 1}'] = layers[1].material.name
|
||||
self._idf.newidfobject(self._CONSTRUCTION, **_kwargs)
|
||||
|
||||
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:
|
||||
return
|
||||
|
||||
order = str(len(self._idf.idfobjects[self._WINDOW_MATERIAL_SIMPLE]) + 1)
|
||||
material_name = 'glazing_' + order
|
||||
_kwargs = {'Name': material_name, 'UFactor': thermal_opening.overall_u_value,
|
||||
'Solar_Heat_Gain_Coefficient': thermal_opening.g_value}
|
||||
self._idf.newidfobject(self._WINDOW_MATERIAL_SIMPLE, **_kwargs)
|
||||
|
||||
window_construction_name = 'window_construction_' + order
|
||||
_kwargs = {'Name': window_construction_name, 'Outside_Layer': material_name}
|
||||
self._idf.newidfobject(self._CONSTRUCTION, **_kwargs)
|
||||
|
||||
def _add_zone(self, thermal_zone):
|
||||
for zone in self._idf.idfobjects['ZONE']:
|
||||
if zone.Name == thermal_zone.id:
|
||||
|
@ -264,52 +304,56 @@ class Idf:
|
|||
Activity_Level_Schedule_Name=f'Occupancy schedules {thermal_zone.usage}'
|
||||
)
|
||||
|
||||
def _add_equipment(self, usage_zone):
|
||||
self._idf.newidfobject(self._ELECTRIC_EQUIPMENT,
|
||||
Name=f'{usage_zone.id}_electricload',
|
||||
Zone_or_ZoneList_Name=usage_zone.id,
|
||||
Schedule_Name=f'Electrical schedules {usage_zone.usage}', # todo: add electrical schedules
|
||||
Design_Level_Calculation_Method='EquipmentLevel',
|
||||
Design_Level=566000 # todo: change it from usage catalog
|
||||
)
|
||||
|
||||
def _add_infiltration(self, usage_zone):
|
||||
def _add_infiltration(self, thermal_zone):
|
||||
for zone in self._idf.idfobjects["ZONE"]:
|
||||
if zone.Name == f'{usage_zone.id}_infiltration':
|
||||
if zone.Name == f'{thermal_zone.id}_infiltration':
|
||||
return
|
||||
self._idf.newidfobject(self._INFILTRATION,
|
||||
Name=f'{usage_zone.id}_infiltration',
|
||||
Zone_or_ZoneList_Name=usage_zone.id,
|
||||
Schedule_Name=f'Infiltration schedules {usage_zone.usage}',
|
||||
Name=f'{thermal_zone.id}_infiltration',
|
||||
Zone_or_ZoneList_Name=thermal_zone.id,
|
||||
Schedule_Name=f'Infiltration schedules {thermal_zone.usage}',
|
||||
Design_Flow_Rate_Calculation_Method='AirChanges/Hour',
|
||||
Air_Changes_per_Hour=usage_zone.mechanical_air_change,
|
||||
Constant_Term_Coefficient=0.606, # todo: change it from usage catalog
|
||||
Temperature_Term_Coefficient=3.6359996E-02, # todo: change it from usage catalog
|
||||
Velocity_Term_Coefficient=0.1177165, # todo: change it from usage catalog
|
||||
Velocity_Squared_Term_Coefficient=0.0000000E+00 # todo: change it from usage catalog
|
||||
Air_Changes_per_Hour=thermal_zone.mechanical_air_change
|
||||
)
|
||||
|
||||
def _rename_building(self, b):
|
||||
for building in self._idf.idfobjects[self._BUILDING]:
|
||||
building.Name = b.name
|
||||
building['Solar_Distribution'] = 'FullExterior'
|
||||
|
||||
def _remove_sizing_periods(self):
|
||||
while len(self._idf.idfobjects[self._SIZING_PERIODS]) > 0:
|
||||
self._idf.popidfobject(self._SIZING_PERIODS, 0)
|
||||
|
||||
def _remove_location(self):
|
||||
self._idf.popidfobject(self._LOCATION, 0)
|
||||
|
||||
def _export(self):
|
||||
"""
|
||||
Export the idf file into the given path
|
||||
export type = "Surfaces|Block"
|
||||
"""
|
||||
|
||||
self._remove_location()
|
||||
self._remove_sizing_periods()
|
||||
for building in self._city.buildings:
|
||||
self._rename_building(building)
|
||||
for internal_zone in building.internal_zones:
|
||||
for thermal_zone in internal_zone.thermal_zones:
|
||||
for thermal_boundary in thermal_zone.thermal_boundaries:
|
||||
self._add_construction(thermal_boundary)
|
||||
for thermal_opening in thermal_boundary.thermal_openings:
|
||||
self._add_window_construction_and_material(thermal_opening)
|
||||
usage = thermal_zone.usage
|
||||
# todo: infiltration can be written with two values (system on and system off) in E+? Or just as schedule?
|
||||
# self._add_schedule(usage, "Infiltration")
|
||||
self._add_infiltration_schedules(thermal_zone)
|
||||
# todo: why is this schedule unused?
|
||||
self._add_schedules(usage, thermal_zone.lighting.schedules)
|
||||
self._add_schedules(usage, thermal_zone.occupancy.occupancy_schedules, schedule_from_file=True)
|
||||
self._add_schedules(usage, thermal_zone.thermal_control.hvac_availability_schedules)
|
||||
|
||||
self._add_zone(thermal_zone)
|
||||
self._add_heating_system(thermal_zone)
|
||||
# self._add_infiltration(usage_zone)
|
||||
self._add_infiltration(thermal_zone)
|
||||
self._add_occupancy(thermal_zone)
|
||||
|
||||
if self._export_type == "Surfaces":
|
||||
|
@ -356,14 +400,36 @@ class Idf:
|
|||
self._idf.intersect_match()
|
||||
|
||||
def _add_surfaces(self, building):
|
||||
|
||||
for internal_zone in building.internal_zones:
|
||||
for thermal_zone in internal_zone.thermal_zones:
|
||||
for boundary in thermal_zone.thermal_boundaries:
|
||||
idf_surface_type = self.idf_surfaces[boundary.parent_surface.type]
|
||||
# todo: thermal boundary vs. surfaces??
|
||||
surface = self._idf.newidfobject(self._SURFACE, Name=f'{boundary.parent_surface.name}',
|
||||
Surface_Type=idf_surface_type, Zone_Name=thermal_zone.id,
|
||||
Construction_Name=boundary.construction_name)
|
||||
coordinates = self._matrix_to_list(boundary.parent_surface.solid_polygon.coordinates,
|
||||
self._city.lower_corner)
|
||||
surface.setcoords(coordinates)
|
||||
self._add_windows(boundary)
|
||||
|
||||
def _add_windows(self, boundary):
|
||||
for opening in boundary.thermal_openings:
|
||||
for construction in self._idf.idfobjects[self._CONSTRUCTION]:
|
||||
if construction['Outside_Layer'].split('_')[0] == 'glazing':
|
||||
window_construction = construction
|
||||
if self._compare_window_constructions(window_construction, opening):
|
||||
opening_name = 'window_' + str(len(self._idf.idfobjects[self._WINDOW]) + 1)
|
||||
opening_length = math.sqrt(opening.area)
|
||||
self._idf.newidfobject(self._WINDOW, Name=f'{opening_name}', Construction_Name=window_construction['Name'],
|
||||
Building_Surface_Name=boundary.parent_surface.name, Multiplier='1',
|
||||
Length=opening_length, Height=opening_length)
|
||||
|
||||
def _compare_window_constructions(self, window_construction, opening):
|
||||
glazing = window_construction['Outside_Layer']
|
||||
for material in self._idf.idfobjects[self._WINDOW_MATERIAL_SIMPLE]:
|
||||
if material['Name'] == glazing:
|
||||
if material['UFactor'] == opening.overall_u_value and \
|
||||
material['Solar_Heat_Gain_Coefficient'] == opening.g_value:
|
||||
return True
|
||||
return False
|
||||
|
|
|
@ -36,7 +36,8 @@ class UsPhysicsParameters(NrelPhysicsInterface):
|
|||
archetype = self._search_archetype(building.function, building.year_of_construction, self._climate_zone)
|
||||
except KeyError:
|
||||
sys.stderr.write(f'Building {building.name} has unknown archetype for building function: {building.function} '
|
||||
f'and building year of construction: {building.year_of_construction}\n')
|
||||
f'and building year of construction: {building.year_of_construction} '
|
||||
f'and climate zone reference norm {self._climate_zone}\n')
|
||||
return
|
||||
|
||||
# if building has no thermal zones defined from geometry, one thermal zone per storey is assigned
|
||||
|
|
Loading…
Reference in New Issue
Block a user