mirror of
https://github.com/louisleroy5/trnslator.git
synced 2024-12-23 08:05:54 -05:00
2874 lines
102 KiB
Python
2874 lines
102 KiB
Python
################################################################################
|
|
# Module: trnsys.py
|
|
# Description: Convert EnergyPlus models to TrnBuild models
|
|
# License: MIT, see full license in LICENSE.txt
|
|
# Web: https://github.com/louisleroy5/translater
|
|
################################################################################
|
|
|
|
import io
|
|
import logging as lg
|
|
import os
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
from copy import deepcopy
|
|
|
|
import numpy as np
|
|
import pandas as pd
|
|
from geomeppy.geom.polygons import Polygon3D
|
|
from path import Path
|
|
from tqdm import tqdm
|
|
|
|
from translater import (
|
|
log,
|
|
settings,
|
|
Schedule,
|
|
checkStr,
|
|
check_unique_name,
|
|
angle,
|
|
load_idf,
|
|
load_idf_object_from_cache,
|
|
hash_file,
|
|
run_eplus,
|
|
recursive_len,
|
|
ReportData,
|
|
)
|
|
|
|
|
|
def convert_idf_to_trnbuild(
|
|
idf_file,
|
|
weather_file,
|
|
window_lib=None,
|
|
return_idf=False,
|
|
return_b18=True,
|
|
return_t3d=False,
|
|
return_dck=False,
|
|
output_folder=None,
|
|
trnsidf_exe=None,
|
|
template=None,
|
|
log_clear_names=False,
|
|
schedule_as_input=True,
|
|
**kwargs
|
|
):
|
|
"""Convert regular IDF file (EnergyPlus) to TRNBuild file (TRNSYS)
|
|
|
|
There are three optional outputs:
|
|
|
|
* the path to the modified IDF with the new names, coordinates, etc. of
|
|
the IDF objects. It is an input file for EnergyPlus (.idf)
|
|
* the path to the TRNBuild file (.b18)
|
|
* the path to the TRNBuild input file (.idf)
|
|
* the path to the TRNSYS dck file (.dck)
|
|
|
|
Example:
|
|
>>> # Exemple of setting kwargs to be unwrapped in the function
|
|
>>> kwargs_dict = {'u_value': 2.5, 'shgc': 0.6, 't_vis': 0.78,
|
|
>>> 'tolerance': 0.05, "fframe": 0.0, "uframe": 0.5, 'ordered': True}
|
|
>>> # Exemple how to call the function
|
|
>>> idf_file = "/file.idf"
|
|
>>> window_filepath = "/W74-lib.dat"
|
|
>>> convert_idf_to_trnbuild(idf_file=idf_file, weather_file=weather_file,
|
|
>>> window_lib=window_filepath,
|
|
>>> **kwargs_dict)
|
|
|
|
Args:
|
|
idf_file (str): path to the idf file to convert
|
|
weather_file (str): To run EnergyPlus simulation and be able to get some
|
|
values (e.g. internal gain, infiltration, etc.)
|
|
window_lib (str): File path of the window library (from Berkeley Lab)
|
|
return_idf (bool, optional): If True, also return the path to the
|
|
modified IDF with the new names, coordinates, etc. of the IDF
|
|
objects. It is an input file for EnergyPlus (.idf)
|
|
return_b18 (bool, optional): If True, also return the path to the
|
|
TRNBuild file (.b18).
|
|
return_t3d (bool, optional): If True, also return the path to the
|
|
return_dck (bool, optional): If True, also return the path to the TRNSYS
|
|
dck file (.dck).
|
|
output_folder (str, optional): location where output files will be
|
|
trnsidf_exe (str): Path to *trnsidf.exe*.
|
|
template (str): Path to d18 template file.
|
|
log_clear_names (bool): If True, DOES NOT log the equivalence between
|
|
the old and new names in the console.
|
|
schedule_as_input (bool): If True, writes the schedules as INPUTS in the BUI
|
|
file. Then, the user would have to link in TRNSYS studio the csv file
|
|
with the schedules to those INPUTS. If False, the schedules are written as
|
|
SCHEDULES in the BUI file. Be aware that this last option (False) can make
|
|
crash TRNBuild because the schedules are too long are there is too many
|
|
schedules.
|
|
kwargs: keyword arguments sent to
|
|
:func:`convert_idf_to_trnbuild()` or :func:`trnbuild_idf()` or
|
|
:func:`choose_window`. "ordered=True" to have the name of idf
|
|
objects in the outputfile in ascendant order. See
|
|
:func:`trnbuild_idf` or :func:`choose_window()` for other parameter
|
|
definition
|
|
|
|
Returns:
|
|
(tuple): A tuple containing:
|
|
|
|
* return_b18 (str): the path to the TRNBuild file (.b18). Only
|
|
provided if *return_b18* is True.
|
|
* return_trn (str): the path to the TRNBuild input file (.idf). Only
|
|
provided if *return_t3d* is True.
|
|
* retrun_dck (str): the path to the TRNSYS dck file (.dck). Only
|
|
provided if *return_dck* is True.
|
|
"""
|
|
|
|
# Assert all path needed exist
|
|
(
|
|
idf_file,
|
|
weather_file,
|
|
window_lib,
|
|
output_folder,
|
|
trnsidf_exe,
|
|
template,
|
|
) = _assert_files(
|
|
idf_file, weather_file, window_lib, output_folder, trnsidf_exe, template
|
|
)
|
|
|
|
# Run EnergyPlus Simulation
|
|
ep_version = kwargs.pop("ep_version", None)
|
|
outputs = [
|
|
{
|
|
"ep_object": "Output:Variable".upper(),
|
|
"kwargs": dict(
|
|
Variable_Name="Zone Thermostat Heating Setpoint Temperature",
|
|
Reporting_Frequency="hourly",
|
|
save=True,
|
|
),
|
|
},
|
|
{
|
|
"ep_object": "Output:Variable".upper(),
|
|
"kwargs": dict(
|
|
Variable_Name="Zone Thermostat Cooling Setpoint Temperature",
|
|
Reporting_Frequency="hourly",
|
|
save=True,
|
|
),
|
|
},
|
|
]
|
|
_, idf = run_eplus(
|
|
idf_file,
|
|
weather_file,
|
|
output_directory=None,
|
|
ep_version=ep_version,
|
|
output_report=None,
|
|
prep_outputs=outputs,
|
|
design_day=False,
|
|
annual=True,
|
|
expandobjects=True,
|
|
return_idf=True,
|
|
)
|
|
|
|
# Check if cache exists
|
|
# idf = _load_idf_file_and_clean_names(idf_file, log_clear_names)
|
|
# Outpout reports
|
|
htm = idf.htm
|
|
sql = idf.sql
|
|
sql_file = idf.sql_file
|
|
|
|
# Clean names of idf objects (e.g. 'MATERIAL')
|
|
idf_2 = deepcopy(idf)
|
|
log("Cleaning names of the IDF objects...", lg.INFO)
|
|
start_time = time.time()
|
|
clear_name_idf_objects(idf_2, log_clear_names)
|
|
log(
|
|
"Cleaned IDF object names in {:,.2f} seconds".format(time.time() - start_time),
|
|
lg.INFO,
|
|
)
|
|
|
|
# Get old:new names equivalence
|
|
old_new_names = pd.read_csv(
|
|
os.path.join(
|
|
settings.data_folder,
|
|
Path(idf_file).basename().stripext() + "_old_new_names_equivalence.csv",
|
|
)
|
|
).to_dict()
|
|
|
|
# Read IDF_T3D template and write lines in variable
|
|
lines = io.TextIOWrapper(io.BytesIO(settings.template_BUI)).readlines()
|
|
|
|
# Get objects from IDF file
|
|
(
|
|
buildingSurfs,
|
|
buildings,
|
|
constructions,
|
|
equipments,
|
|
fenestrationSurfs,
|
|
globGeomRules,
|
|
lights,
|
|
locations,
|
|
materialAirGap,
|
|
materialNoMass,
|
|
materials,
|
|
peoples,
|
|
versions,
|
|
zones,
|
|
zonelists,
|
|
) = get_idf_objects(idf_2)
|
|
|
|
# Get all construction EXCEPT fenestration ones
|
|
constr_list = _get_constr_list(buildingSurfs)
|
|
|
|
# If ordered=True, ordering idf objects
|
|
ordered = kwargs.get("ordered", False)
|
|
(
|
|
buildingSurfs,
|
|
buildings,
|
|
constr_list,
|
|
constructions,
|
|
equipments,
|
|
fenestrationSurfs,
|
|
globGeomRules,
|
|
lights,
|
|
locations,
|
|
materialAirGap,
|
|
materialNoMass,
|
|
materials,
|
|
peoples,
|
|
zones,
|
|
zonelists,
|
|
) = _order_objects(
|
|
buildingSurfs,
|
|
buildings,
|
|
constr_list,
|
|
constructions,
|
|
equipments,
|
|
fenestrationSurfs,
|
|
globGeomRules,
|
|
lights,
|
|
locations,
|
|
materialAirGap,
|
|
materialNoMass,
|
|
materials,
|
|
peoples,
|
|
zones,
|
|
zonelists,
|
|
ordered,
|
|
)
|
|
|
|
# region Get schedules from IDF
|
|
schedule_names, schedules = _get_schedules(idf_2)
|
|
|
|
# Adds ground temperature to schedules
|
|
adds_sch_ground(htm, schedule_names, schedules)
|
|
|
|
# Adds "sch_setpoint_ZONES" to schedules
|
|
df_heating_setpoint = ReportData.from_sqlite(
|
|
sql_file, table_name="Zone Thermostat Heating Setpoint Temperature"
|
|
)
|
|
df_cooling_setpoint = ReportData.from_sqlite(
|
|
sql_file, table_name="Zone Thermostat Cooling Setpoint Temperature"
|
|
)
|
|
# Heating
|
|
adds_sch_setpoint(
|
|
zones, df_heating_setpoint, old_new_names, schedule_names, schedules, "h"
|
|
)
|
|
# Cooling
|
|
adds_sch_setpoint(
|
|
zones, df_cooling_setpoint, old_new_names, schedule_names, schedules, "c"
|
|
)
|
|
|
|
# Save schedules to csv file
|
|
_yearlySched_to_csv(idf_file, output_folder, schedule_names, schedules)
|
|
# endregion
|
|
|
|
# Gets and removes from IDF materials with resistance lower than 0.0007
|
|
mat_name = _remove_low_conductivity(constructions, idf_2, materials)
|
|
|
|
# Write data from IDF file to T3D file
|
|
start_time = time.time()
|
|
# Write VERSION from IDF to lines (T3D)
|
|
_write_version(lines, versions)
|
|
|
|
# Write BUILDING from IDF to lines (T3D)
|
|
_write_building(buildings, lines)
|
|
|
|
# Write LOCATION and GLOBALGEOMETRYRULES from IDF to lines (T3D) and
|
|
# define if coordinate system is "Relative"
|
|
coordSys = _write_location_geomrules(globGeomRules, lines, locations)
|
|
|
|
# Determine if coordsSystem is "World" (all zones at (0,0,0))
|
|
coordSys = _is_coordSys_world(coordSys, zones)
|
|
|
|
# Change coordinates from relative to absolute for building surfaces
|
|
_change_relative_coords(buildingSurfs, coordSys, idf_2)
|
|
|
|
# Adds or changes adjacent surface if needed
|
|
_add_change_adj_surf(buildingSurfs, idf_2)
|
|
buildingSurfs = idf_2.idfobjects["BUILDINGSURFACE:DETAILED"]
|
|
|
|
# region Write VARIABLEDICTONARY (Zone, BuildingSurf, FenestrationSurf)
|
|
# from IDF to lines (T3D)
|
|
|
|
# Get all surfaces having Outside boundary condition with the ground.
|
|
# To be used to find the window's slopes
|
|
n_ground = _get_ground_vertex(buildingSurfs)
|
|
|
|
# Writing zones in lines
|
|
win_slope_dict = _write_zone_buildingSurf_fenestrationSurf(
|
|
buildingSurfs,
|
|
coordSys,
|
|
fenestrationSurfs,
|
|
idf_2,
|
|
lines,
|
|
n_ground,
|
|
zones,
|
|
schedule_as_input,
|
|
)
|
|
# endregion
|
|
|
|
# region Write CONSTRUCTION from IDF to lines (T3D)
|
|
_write_constructions(constr_list, idf_2, lines, mat_name, materials)
|
|
# endregion
|
|
|
|
# Write CONSTRUCTION from IDF to lines, at the end of the T3D file
|
|
_write_constructions_end(constr_list, idf_2, lines)
|
|
|
|
# region Write LAYER from IDF to lines (T3D)
|
|
_write_materials(lines, materialAirGap, materialNoMass, materials)
|
|
# endregion
|
|
|
|
# region Write GAINS (People, Lights, Equipment) from IDF to lines (T3D)
|
|
_write_gains(equipments, lights, lines, peoples, htm, old_new_names)
|
|
# endregion
|
|
|
|
# region Write basic conditioning systems (HEATING and COOLING) from IDF to lines (T3D)
|
|
heat_dict, cool_dict = _write_conditioning(
|
|
htm, lines, schedules, old_new_names, schedule_as_input
|
|
)
|
|
# endregion
|
|
|
|
# region Write SCHEDULES from IDF to lines (T3D)
|
|
schedules_not_written = _write_schedules(
|
|
lines, schedule_names, schedules, schedule_as_input, idf_file
|
|
)
|
|
# endregion
|
|
|
|
# region Write WINDOWS chosen by the user (from Berkeley lab library) in
|
|
# lines (T3D)
|
|
# Get window from library
|
|
# window = (win_id, description, design, u_win, shgc_win, t_sol_win, rf_sol,
|
|
# t_vis_win, lay_win, width, window_bunches[win_id],
|
|
# and maybe tolerance)
|
|
log("Get windows info from window library...")
|
|
win_u_value = kwargs.get("u_value", 2.2)
|
|
win_shgc = kwargs.get("shgc", 0.64)
|
|
win_tvis = kwargs.get("t_vis", 0.8)
|
|
win_tolerance = kwargs.get("tolerance", 0.05)
|
|
win_fframe = kwargs.get("fframe", 0.15)
|
|
win_uframe = kwargs.get("uframe", 8.17)
|
|
window = choose_window(win_u_value, win_shgc, win_tvis, win_tolerance, window_lib)
|
|
|
|
# Write windows in lines
|
|
_write_window(lines, win_slope_dict, window, win_fframe, win_uframe)
|
|
|
|
# Write window pool in lines
|
|
_write_winPool(lines, window)
|
|
# endregion
|
|
|
|
# Save T3D file at output_folder
|
|
output_folder, t3d_path = _save_t3d(idf_file, lines, output_folder)
|
|
|
|
log(
|
|
"Write data from IDF to T3D in {:,.2f} seconds".format(
|
|
time.time() - start_time
|
|
),
|
|
lg.INFO,
|
|
)
|
|
|
|
# If asked by the user, save IDF file with modification done on the names,
|
|
# coordinates, etc. at
|
|
# output_folder
|
|
new_idf_path = os.path.join(output_folder, "MODIFIED_" + os.path.basename(idf_file))
|
|
if return_idf:
|
|
idf_2.saveas(filename=new_idf_path)
|
|
|
|
# Run trnsidf to convert T3D to BUI
|
|
log("Converting t3d file to bui file. Running trnsidf.exe...")
|
|
dck = return_dck
|
|
nonum = kwargs.pop("nonum", False)
|
|
N = kwargs.pop("N", False)
|
|
geo_floor = kwargs.pop("geo_floor", 0.6)
|
|
refarea = kwargs.pop("refarea", False)
|
|
volume = kwargs.pop("volume", False)
|
|
capacitance = kwargs.pop("capacitance", False)
|
|
trnbuild_idf(
|
|
t3d_path,
|
|
output_folder=output_folder,
|
|
template=template,
|
|
dck=dck,
|
|
nonum=nonum,
|
|
N=N,
|
|
geo_floor=geo_floor,
|
|
refarea=refarea,
|
|
volume=volume,
|
|
capacitance=capacitance,
|
|
trnsidf_exe=trnsidf_exe,
|
|
)
|
|
|
|
# Prepare return arguments
|
|
pre, ext = os.path.splitext(t3d_path)
|
|
b18_path = pre + ".b18"
|
|
dck_path = pre + ".dck"
|
|
|
|
from itertools import compress
|
|
|
|
return_path = tuple(
|
|
compress(
|
|
[new_idf_path, b18_path, t3d_path, dck_path],
|
|
[return_idf, return_b18, return_t3d, return_dck],
|
|
)
|
|
)
|
|
|
|
# region Modify B18 file
|
|
with open(b18_path) as b18_file:
|
|
b18_lines = b18_file.readlines()
|
|
|
|
# Adds conditionning to B18 file
|
|
conditioning_to_b18(b18_lines, heat_dict, cool_dict, zones, old_new_names)
|
|
|
|
# Adds infiltration to b18 file
|
|
infilt_to_b18(b18_lines, zones, htm)
|
|
|
|
# Adds internal gain to b18 file
|
|
gains_to_b18(
|
|
b18_lines,
|
|
zones,
|
|
zonelists,
|
|
peoples,
|
|
lights,
|
|
equipments,
|
|
schedules_not_written,
|
|
htm,
|
|
old_new_names,
|
|
schedule_as_input,
|
|
)
|
|
|
|
# T initial to b18
|
|
t_initial_to_b18(b18_lines, zones, schedules)
|
|
|
|
if schedule_as_input:
|
|
# Reduce number of schedules written as Inputs
|
|
reduce_schedule(b18_lines, lines, schedule_names)
|
|
|
|
# Save B18 file at output_folder
|
|
if output_folder is None:
|
|
# User did not provide an output folder path. We use the default setting
|
|
output_folder = os.path.relpath(settings.data_folder)
|
|
if not os.path.isdir(output_folder):
|
|
os.makedirs(output_folder)
|
|
with open(b18_path, "w") as converted_file:
|
|
for line in b18_lines:
|
|
converted_file.writelines(str(line))
|
|
# endregion
|
|
|
|
return return_path
|
|
|
|
|
|
def reduce_schedule(b18_lines, lines, schedule_names):
|
|
name_to_delete = []
|
|
count = checkStr(b18_lines, "INPUTS_DESCRIPTION")
|
|
|
|
# Search schedule names that are not used in the b18 file
|
|
for name in schedule_names:
|
|
bool_list = [name in line for line in b18_lines[count:]]
|
|
if not any(bool_list):
|
|
name_to_delete.append(name)
|
|
|
|
# Delete the unused schedule names from the "INPUTS" line (in the T3D lines)
|
|
input_count = checkStr(lines, "I n p u t s")
|
|
input_descr_count = checkStr(lines, "INPUTS_DESCRIPTION")
|
|
input_lines = lines[input_count + 1 : input_descr_count-1]
|
|
for idx, line in enumerate(input_lines):
|
|
removed = []
|
|
for name in name_to_delete:
|
|
if name in line:
|
|
input_lines[idx] = line.replace(name, "")
|
|
removed.append(name)
|
|
for rmv_name in removed:
|
|
name_to_delete.remove(rmv_name)
|
|
|
|
# Remove "INPUTS" line from b18_lines and insert the "input_lines" from T3D file
|
|
# From where we just deleted the unused schedule names
|
|
del b18_lines[count - 2]
|
|
input_lines.reverse()
|
|
for line in input_lines:
|
|
line = line.replace(" ", " ")
|
|
line = line.replace(" ;", ";")
|
|
line = line.replace("!-", "")
|
|
b18_lines.insert(count - 2, line)
|
|
|
|
|
|
def t_initial_to_b18(b18_lines, zones, schedules):
|
|
for zone in zones:
|
|
t_ini = schedules["sch_h_setpoint_" + zone.Name]["all values"][0]
|
|
# Get line number where to write TINITIAL
|
|
f_count = checkStr(b18_lines, "Z o n e " + zone.Name)
|
|
tIniNum = checkStr(b18_lines, "TINITIAL", f_count)
|
|
ind_tini = b18_lines[tIniNum - 1].find("TINITIAL")
|
|
ind_phini = b18_lines[tIniNum - 1].find("PHINITIAL")
|
|
b18_lines[tIniNum - 1] = (
|
|
b18_lines[tIniNum - 1][: ind_tini + len("TINITIAL=")]
|
|
+ " "
|
|
+ str(t_ini)
|
|
+ " : "
|
|
+ b18_lines[tIniNum - 1][ind_phini:]
|
|
+ "\n"
|
|
)
|
|
|
|
|
|
def adds_sch_setpoint(
|
|
zones, report_sqlite, old_new_names, schedule_names, schedules, string
|
|
):
|
|
if string == "h":
|
|
description = "get_heating_setpoints"
|
|
if string == "c":
|
|
description = "get_cooling_setpoints"
|
|
|
|
for zone in tqdm(zones, desc=description):
|
|
all_values = report_sqlite[
|
|
report_sqlite.loc[:, "KeyValue"]
|
|
== old_new_names[zone.Name.upper()][0].upper()
|
|
].Value.values
|
|
schedule_name = "sch_" + string + "_setpoint_" + zone.Name
|
|
schedule_names.append(schedule_name)
|
|
schedules[schedule_name] = {"all values": all_values}
|
|
|
|
|
|
def adds_sch_ground(htm, schedule_names, schedules):
|
|
# Get the monthly values from htm output file from EP simulation
|
|
values = np.append(
|
|
htm["Site:GroundTemperature:BuildingSurface"].values[0][1:],
|
|
htm["Site:GroundTemperature:BuildingSurface"].values[0][-1],
|
|
)
|
|
# Create array of 8760 values from monthly values
|
|
all_values = (
|
|
pd.DataFrame(
|
|
values, index=pd.date_range(freq="MS", start="01/01/2019", periods=13)
|
|
)
|
|
.resample("H")
|
|
.ffill()[:-1]
|
|
.T.values[0]
|
|
)
|
|
schedule_names.append("sch_ground")
|
|
# Adds "sch_ground" to schedules dict
|
|
schedules["sch_ground"] = {"all values": all_values}
|
|
|
|
|
|
def infilt_to_b18(b18_lines, zones, htm):
|
|
try:
|
|
mean_infilt = round(
|
|
np.average(
|
|
htm["ZoneInfiltration Airflow Stats Nominal"][
|
|
"ACH - Air Changes per Hour"
|
|
].values,
|
|
weights=htm["ZoneInfiltration Airflow Stats Nominal"][
|
|
"Zone Floor Area {m2}"
|
|
].values,
|
|
),
|
|
3,
|
|
)
|
|
except KeyError:
|
|
mean_infilt = 0
|
|
|
|
log("Writing infiltration info from idf file to b18 file...")
|
|
# Get line number where to write
|
|
infiltNum = checkStr(b18_lines, "I n f i l t r a t i o n")
|
|
# Write in infiltration section
|
|
b18_lines.insert(infiltNum + 1, "INFILTRATION Constant" + "\n")
|
|
b18_lines.insert(infiltNum + 2, "AIRCHANGE=" + str(mean_infilt) + "\n")
|
|
# Write in zone section
|
|
for zone in tqdm(zones, desc="writing infiltration in BUI"):
|
|
f_count = checkStr(b18_lines, "Z o n e " + zone.Name)
|
|
regimeInfiltNum = checkStr(b18_lines, "REGIME", f_count)
|
|
b18_lines.insert(regimeInfiltNum, " INFILTRATION = Constant" + "\n")
|
|
|
|
|
|
def gains_to_b18(
|
|
b18_lines,
|
|
zones,
|
|
zonelists,
|
|
peoples,
|
|
lights,
|
|
equipments,
|
|
schedules_not_written,
|
|
htm,
|
|
old_new_names,
|
|
schedule_as_input,
|
|
):
|
|
peoples_in_zone = zone_where_gain_is(peoples, zones, zonelists)
|
|
lights_in_zone = zone_where_gain_is(lights, zones, zonelists)
|
|
equipments_in_zone = zone_where_gain_is(equipments, zones, zonelists)
|
|
|
|
for zone in tqdm(zones, desc="writing internal gains in BUI"):
|
|
# Write people gains
|
|
_write_gain_to_b18(
|
|
b18_lines,
|
|
zone,
|
|
peoples,
|
|
peoples_in_zone,
|
|
schedules_not_written,
|
|
htm,
|
|
old_new_names,
|
|
"People",
|
|
schedule_as_input,
|
|
)
|
|
# Write light gains
|
|
_write_gain_to_b18(
|
|
b18_lines,
|
|
zone,
|
|
lights,
|
|
lights_in_zone,
|
|
schedules_not_written,
|
|
htm,
|
|
old_new_names,
|
|
"Lights",
|
|
schedule_as_input,
|
|
)
|
|
# Write equipment gains
|
|
_write_gain_to_b18(
|
|
b18_lines,
|
|
zone,
|
|
equipments,
|
|
equipments_in_zone,
|
|
schedules_not_written,
|
|
htm,
|
|
old_new_names,
|
|
"ElectricEquipment",
|
|
schedule_as_input,
|
|
)
|
|
|
|
|
|
def _write_gain_to_b18(
|
|
b18_lines,
|
|
zone,
|
|
gains,
|
|
gains_in_zone,
|
|
schedules_not_written,
|
|
htm,
|
|
old_new_names,
|
|
string,
|
|
schedule_as_input,
|
|
):
|
|
for gain in gains:
|
|
if zone.Name in gains_in_zone[gain.Name]:
|
|
f_count = checkStr(b18_lines, "Z o n e " + zone.Name)
|
|
regimeNum = checkStr(b18_lines, "REGIME", f_count)
|
|
schedule = htm[string + " Internal Gains Nominal"][
|
|
htm[string + " Internal Gains Nominal"]["Name"].str.contains(
|
|
old_new_names[gain.Name.upper()][0]
|
|
)
|
|
]["Schedule Name"].values[0]
|
|
schedule = [
|
|
key for (key, value) in old_new_names.items() if value[0] == schedule
|
|
][0].lower()
|
|
if schedule in schedules_not_written:
|
|
continue
|
|
# Write
|
|
if schedule_as_input:
|
|
b18_lines.insert(
|
|
regimeNum,
|
|
" GAIN= "
|
|
+ gain.Name
|
|
+ " : SCALE= INPUT 1*"
|
|
+ schedule
|
|
+ " : GEOPOS=0 : SCALE2= 1 : FRAC_REFAREA= 1"
|
|
+ "\n",
|
|
)
|
|
else:
|
|
b18_lines.insert(
|
|
regimeNum,
|
|
" GAIN= "
|
|
+ gain.Name
|
|
+ " : SCALE= SCHEDULE 1*"
|
|
+ schedule
|
|
+ " : GEOPOS=0 : SCALE2= 1 : FRAC_REFAREA= 1"
|
|
+ "\n",
|
|
)
|
|
|
|
|
|
def conditioning_to_b18(b18_lines, heat_dict, cool_dict, zones, old_new_names):
|
|
for zone in tqdm(zones, desc="writing conditioning in BUI"):
|
|
# Heating
|
|
_write_heat_cool_to_b18(heat_dict, old_new_names, zone, b18_lines, " HEATING")
|
|
# Cooling
|
|
_write_heat_cool_to_b18(cool_dict, old_new_names, zone, b18_lines, " COOLING")
|
|
|
|
|
|
def _write_heat_cool_to_b18(list_dict, old_new_names, zone, b18_lines, string):
|
|
for key in list_dict.keys():
|
|
if old_new_names[zone.Name.upper()][0] in key:
|
|
f_count = checkStr(b18_lines, "Z o n e " + zone.Name)
|
|
regimeNum = checkStr(b18_lines, "REGIME", f_count)
|
|
# Write
|
|
if not isinstance(list_dict[key], list):
|
|
value = list_dict[key]
|
|
else:
|
|
value = list_dict[key][0]
|
|
b18_lines.insert(regimeNum, string + " = " + value + "\n")
|
|
|
|
|
|
def zone_where_gain_is(gains, zones, zonelists):
|
|
gain_in_zone = {}
|
|
for gain in gains:
|
|
list_zone = []
|
|
for zone in zones:
|
|
if zone.Name == gain.Zone_or_ZoneList_Name:
|
|
list_zone.append([zone.Name])
|
|
for zonelist in zonelists:
|
|
if zonelist.Name == gain.Zone_or_ZoneList_Name:
|
|
list_zone.append(zonelist.fieldvalues[2:])
|
|
|
|
flat_list = [item for sublist in list_zone for item in sublist]
|
|
gain_in_zone[gain.Name] = flat_list
|
|
|
|
return gain_in_zone
|
|
|
|
|
|
def _change_relative_coords(buildingSurfs, coordSys, idf):
|
|
if coordSys == "Relative":
|
|
# Add zone coordinates to X, Y, Z vectors
|
|
for buildingSurf in buildingSurfs:
|
|
surf_zone = buildingSurf.Zone_Name
|
|
incrX, incrY, incrZ = zone_origin(idf.getobject("ZONE", surf_zone))
|
|
_relative_to_absolute(buildingSurf, incrX, incrY, incrZ)
|
|
|
|
|
|
def _yearlySched_to_csv(idf_file, output_folder, schedule_names, schedules):
|
|
log("Saving yearly schedules in CSV file...")
|
|
idf_file = Path(idf_file)
|
|
df_sched = pd.DataFrame()
|
|
schedule_names.sort()
|
|
for schedule_name in tqdm(schedule_names, desc="writing schedules in csv"):
|
|
df_sched[schedule_name] = schedules[schedule_name]["all values"]
|
|
sched_file_name = "yearly_schedules_" + idf_file.basename().stripext() + ".csv"
|
|
output_folder = Path(output_folder)
|
|
if not output_folder.exists():
|
|
output_folder.mkdir_p()
|
|
df_sched.to_csv(path_or_buf=os.path.join(output_folder, sched_file_name))
|
|
|
|
|
|
def _get_constr_list(buildingSurfs):
|
|
constr_list = []
|
|
for buildingSurf in buildingSurfs:
|
|
constr_list.append(buildingSurf.Construction_Name)
|
|
constr_list = list(set(constr_list))
|
|
constr_list.sort()
|
|
return constr_list
|
|
|
|
|
|
def _save_t3d(idf_file, lines, output_folder):
|
|
"""Saves T3D file
|
|
|
|
Args:
|
|
idf_file (str): path to the idf file to convert
|
|
lines (list): lines to copy in the T3D file
|
|
output_folder (str): path to the output folder (can be None)
|
|
|
|
Returns:
|
|
output_folder (str): path to the output folder
|
|
t3d_path (str): path to the T3D file
|
|
|
|
"""
|
|
if output_folder is None:
|
|
# User did not provide an output folder path. We use the default setting
|
|
output_folder = os.path.relpath(settings.data_folder)
|
|
if not os.path.isdir(output_folder):
|
|
os.makedirs(output_folder)
|
|
t3d_path = os.path.join(output_folder, "T3D_" + os.path.basename(idf_file))
|
|
with open(t3d_path, "w") as converted_file:
|
|
for line in lines:
|
|
converted_file.writelines(str(line))
|
|
return output_folder, t3d_path
|
|
|
|
|
|
def _remove_low_conductivity(constructions, idf, materials):
|
|
"""Removes materials form idf with conductivity too low (0.0007 kJ/h-m-K)
|
|
|
|
Args:
|
|
constructions (Idf_MSequence): CONSTRUCTION object from the IDF
|
|
idf (translater.idfclass.IDF object at 0x11e3d3208): the IDf object
|
|
materials (Idf_MSequence): MATERIAL object from the IDF
|
|
|
|
Returns:
|
|
mat_name (list): list of name of the removed materials
|
|
|
|
"""
|
|
material_low_res = []
|
|
for material in materials:
|
|
if material.Thickness / (material.Conductivity * 3.6) < 0.0007:
|
|
material_low_res.append(material)
|
|
# Remove materials with resistance lower than 0.0007 from IDF
|
|
mat_name = []
|
|
for mat in material_low_res:
|
|
mat_name.append(mat.Name)
|
|
idf.removeidfobject(mat)
|
|
# Get constructions with only materials with resistance lower than 0.0007
|
|
construct_low_res = []
|
|
for i in range(0, len(constructions)):
|
|
if (
|
|
len(constructions[i].fieldvalues) == 3
|
|
and constructions[i].fieldvalues[2] in mat_name
|
|
):
|
|
construct_low_res.append(constructions[i])
|
|
# Remove constructions with only materials with resistance lower than
|
|
# 0.0007 from IDF
|
|
for construct in construct_low_res:
|
|
idf.removeidfobject(construct)
|
|
return mat_name
|
|
|
|
|
|
def _order_objects(
|
|
buildingSurfs,
|
|
buildings,
|
|
constr_list,
|
|
constructions,
|
|
equipments,
|
|
fenestrationSurfs,
|
|
globGeomRules,
|
|
lights,
|
|
locations,
|
|
materialAirGap,
|
|
materialNoMass,
|
|
materials,
|
|
peoples,
|
|
zones,
|
|
zonelists,
|
|
ordered=True,
|
|
):
|
|
"""
|
|
|
|
Args:
|
|
ordered:
|
|
materials (Idf_MSequence): MATERIAL object from the IDF
|
|
materialNoMass (Idf_MSequence): MATERIAL:NOMASS object from the IDF
|
|
materialAirGap (Idf_MSequence): MATERIAL:AIRGAP object from the IDF
|
|
versions (Idf_MSequence): VERSION object from the IDF
|
|
buildings (Idf_MSequence): BUILDING object from the IDF
|
|
locations (Idf_MSequence): SITE:LOCATION object from the IDF
|
|
globGeomRules (Idf_MSequence): GLOBALGEOMETRYRULES object from the IDF
|
|
constructions (Idf_MSequence): CONSTRUCTION object from the IDF
|
|
buildingSurfs (Idf_MSequence): BUILDINGSURFACE:DETAILED object
|
|
from the IDF
|
|
fenestrationSurfs (Idf_MSequence): FENESTRATIONSURFACE:DETAILED object
|
|
from the IDF
|
|
zones (Idf_MSequence): ZONE object from the IDF
|
|
peoples (Idf_MSequence): PEOPLE object from the IDF
|
|
lights (Idf_MSequence): LIGHTs object from the IDF
|
|
equipments (Idf_MSequence): EQUIPMENT object from the IDF
|
|
|
|
Returns:
|
|
IDF objects (see Args) with their order reversed
|
|
|
|
"""
|
|
if ordered:
|
|
materials = list(reversed(materials))
|
|
materialNoMass = list(reversed(materialNoMass))
|
|
materialAirGap = list(reversed(materialAirGap))
|
|
buildings = list(reversed(buildings))
|
|
locations = list(reversed(locations))
|
|
globGeomRules = list(reversed(globGeomRules))
|
|
constructions = list(reversed(constructions))
|
|
fenestrationSurfs = list(reversed(fenestrationSurfs))
|
|
buildingSurfs = list(reversed(buildingSurfs))
|
|
zones = list(reversed(zones))
|
|
zonelists = list(reversed(zonelists))
|
|
peoples = list(reversed(peoples))
|
|
lights = list(reversed(lights))
|
|
equipments = list(reversed(equipments))
|
|
constr_list = list(reversed(constr_list))
|
|
return (
|
|
buildingSurfs,
|
|
buildings,
|
|
constr_list,
|
|
constructions,
|
|
equipments,
|
|
fenestrationSurfs,
|
|
globGeomRules,
|
|
lights,
|
|
locations,
|
|
materialAirGap,
|
|
materialNoMass,
|
|
materials,
|
|
peoples,
|
|
zones,
|
|
zonelists,
|
|
)
|
|
|
|
|
|
def get_idf_objects(idf):
|
|
"""Gets idf objects
|
|
|
|
Args:
|
|
idf (translater.idfclass.IDF object at 0x11e3d3208): the IDf object
|
|
|
|
Returns:
|
|
materials (Idf_MSequence): MATERIAL object from the IDF
|
|
materialNoMass (Idf_MSequence): MATERIAL:NOMASS object from the IDF
|
|
materialAirGap (Idf_MSequence): MATERIAL:AIRGAP object from the IDF
|
|
versions (Idf_MSequence): VERSION object from the IDF
|
|
buildings (Idf_MSequence): BUILDING object from the IDF
|
|
locations (Idf_MSequence): SITE:LOCATION object from the IDF
|
|
globGeomRules (Idf_MSequence): GLOBALGEOMETRYRULES object from the IDF
|
|
constructions (Idf_MSequence): CONSTRUCTION object from the IDF
|
|
buildingSurfs (Idf_MSequence): BUILDINGSURFACE:DETAILED object
|
|
from the IDF
|
|
fenestrationSurfs (Idf_MSequence): FENESTRATIONSURFACE:DETAILED object
|
|
from the IDF
|
|
zones (Idf_MSequence): ZONE object from the IDF
|
|
peoples (Idf_MSequence): PEOPLE object from the IDF
|
|
lights (Idf_MSequence): LIGHTs object from the IDF
|
|
equipments (Idf_MSequence): EQUIPMENT object from the IDF
|
|
|
|
"""
|
|
materials = idf.idfobjects["MATERIAL"]
|
|
materialNoMass = idf.idfobjects["MATERIAL:NOMASS"]
|
|
materialAirGap = idf.idfobjects["MATERIAL:AIRGAP"]
|
|
versions = idf.idfobjects["VERSION"]
|
|
buildings = idf.idfobjects["BUILDING"]
|
|
locations = idf.idfobjects["SITE:LOCATION"]
|
|
globGeomRules = idf.idfobjects["GLOBALGEOMETRYRULES"]
|
|
constructions = idf.idfobjects["CONSTRUCTION"]
|
|
fenestrationSurfs = idf.idfobjects["FENESTRATIONSURFACE:DETAILED"]
|
|
buildingSurfs = idf.idfobjects["BUILDINGSURFACE:DETAILED"]
|
|
zones = idf.idfobjects["ZONE"]
|
|
peoples = idf.idfobjects["PEOPLE"]
|
|
lights = idf.idfobjects["LIGHTS"]
|
|
equipments = idf.idfobjects["ELECTRICEQUIPMENT"]
|
|
zonelists = idf.idfobjects["ZONELIST"]
|
|
return (
|
|
buildingSurfs,
|
|
buildings,
|
|
constructions,
|
|
equipments,
|
|
fenestrationSurfs,
|
|
globGeomRules,
|
|
lights,
|
|
locations,
|
|
materialAirGap,
|
|
materialNoMass,
|
|
materials,
|
|
peoples,
|
|
versions,
|
|
zones,
|
|
zonelists,
|
|
)
|
|
|
|
|
|
def load_idf_file_and_clean_names(idf_file, log_clear_names):
|
|
"""Load idf file from cache if cache exist and user ask for use_cache=True.
|
|
Moreover cleans idf object names and log in the console the equivalence
|
|
between the old and new names if log_clear_names=False
|
|
|
|
Args:
|
|
idf_file (str): Path to the idf file
|
|
log_clear_names (bool): If True, DOES NOT log the equivalence between
|
|
the old and new names in the console.
|
|
|
|
Returns:
|
|
idf (translater.idfclass.IDF object at 0x11e3d3208): the IDf object
|
|
|
|
"""
|
|
log("Loading IDF file...", lg.INFO)
|
|
start_time = time.time()
|
|
cache_filename = hash_file(idf_file)
|
|
idf = load_idf_object_from_cache(idf_file, how="idf")
|
|
if not idf:
|
|
# Load IDF file(s)
|
|
idf = load_idf(idf_file)
|
|
log(
|
|
"IDF files loaded in {:,.2f} seconds".format(time.time() - start_time),
|
|
lg.INFO,
|
|
)
|
|
# Clean names of idf objects (e.g. 'MATERIAL')
|
|
log("Cleaning names of the IDF objects...", lg.INFO)
|
|
start_time = time.time()
|
|
clear_name_idf_objects(idf, log_clear_names)
|
|
path = os.path.join(
|
|
settings.cache_folder, cache_filename, cache_filename + ".idf"
|
|
)
|
|
if not os.path.exists(os.path.dirname(path)):
|
|
os.makedirs(os.path.dirname(path))
|
|
idf.saveas(filename=path)
|
|
# save_idf_object_to_cache(idf, idf_file, cache_filename, 'pickle')
|
|
log(
|
|
"Cleaned IDF object names in {:,.2f} seconds".format(
|
|
time.time() - start_time
|
|
),
|
|
lg.INFO,
|
|
)
|
|
return idf
|
|
|
|
|
|
def _assert_files(
|
|
idf_file, weather_file, window_lib, output_folder, trnsidf_exe, template
|
|
):
|
|
"""Ensure the files and directory are here
|
|
|
|
Args:
|
|
idf_file (str): path to the idf file to convert
|
|
window_lib (str): File path of the window library (from Berkeley Lab)
|
|
output_folder (str): path to the output folder (can be None)
|
|
trnsidf_exe (str): Path to *trnsidf.exe*.
|
|
template (str): Path to d18 template file.
|
|
"""
|
|
if isinstance(idf_file, str):
|
|
if not os.path.isfile(idf_file):
|
|
raise IOError("idf_file file not found")
|
|
else:
|
|
raise IOError("idf_file file is not a string (path)")
|
|
|
|
if isinstance(weather_file, str):
|
|
if not os.path.isfile(weather_file):
|
|
raise IOError("weather file not found")
|
|
else:
|
|
raise IOError("weather file is not a string (path)")
|
|
|
|
if window_lib:
|
|
if isinstance(window_lib, str):
|
|
if not os.path.isfile(window_lib):
|
|
raise IOError("window_lib file not found")
|
|
else:
|
|
raise IOError("window_lib file is not a string (path)")
|
|
|
|
if not output_folder:
|
|
output_folder = os.path.relpath(settings.data_folder)
|
|
if not os.path.exists(output_folder):
|
|
os.mkdir(output_folder)
|
|
|
|
if not template:
|
|
template = settings.path_template_d18
|
|
|
|
if not os.path.isfile(template):
|
|
raise IOError("template file not found")
|
|
|
|
if not trnsidf_exe:
|
|
trnsidf_exe = settings.trnsys_default_folder / Path(
|
|
r"Building\trnsIDF\trnsidf.exe"
|
|
)
|
|
|
|
if not os.path.isfile(trnsidf_exe):
|
|
raise IOError("trnsidf.exe not found")
|
|
|
|
return idf_file, weather_file, window_lib, output_folder, trnsidf_exe, template
|
|
|
|
|
|
def _add_change_adj_surf(buildingSurfs, idf):
|
|
"""Adds or changes adjacent surfaces if needed
|
|
|
|
Args:
|
|
buildingSurfs (idf_MSequence): IDF object from idf.idfobjects(). List of
|
|
building surfaces ("BUILDINGSURFACE:DETAILED" in the IDF). Building
|
|
surfaces to iterate over and determine if either a change on an
|
|
adjacent surface is needed or the creation of a new one
|
|
idf (translater.idfclass.IDF): IDF object
|
|
"""
|
|
adj_surfs_to_change = {}
|
|
adj_surfs_to_make = []
|
|
for buildingSurf in buildingSurfs:
|
|
if "zone" in buildingSurf.Outside_Boundary_Condition.lower():
|
|
# Get the surface EpBunch that is adjacent to the building surface
|
|
outside_bound_zone = buildingSurf.Outside_Boundary_Condition_Object
|
|
surfs_in_bound_zone = [
|
|
surf for surf in buildingSurfs if surf.Zone_Name == outside_bound_zone
|
|
]
|
|
poly_buildingSurf = Polygon3D(buildingSurf.coords)
|
|
n_buildingSurf = poly_buildingSurf.normal_vector
|
|
area_build = poly_buildingSurf.area
|
|
centroid_build = poly_buildingSurf.centroid
|
|
# Check if buildingSurf has an adjacent surface
|
|
for surf in surfs_in_bound_zone:
|
|
if surf.Outside_Boundary_Condition.lower() == "outdoors":
|
|
poly_surf_bound = Polygon3D(surf.coords)
|
|
n_surf_bound = poly_surf_bound.normal_vector
|
|
area_bound = poly_surf_bound.area
|
|
centroid_bound = poly_surf_bound.centroid
|
|
# Check if boundary surface already exist: sum of normal
|
|
# vectors must be equal to 0 AND surfaces must have the
|
|
# same centroid AND surfaces must have the same area
|
|
if (
|
|
round(n_surf_bound.x + n_buildingSurf.x, 3) == 0
|
|
and round(n_surf_bound.y + n_buildingSurf.y, 3) == 0
|
|
and round(n_surf_bound.z + n_buildingSurf.z, 3) == 0
|
|
and round(centroid_bound.x, 3) == round(centroid_build.x, 3)
|
|
and round(centroid_bound.y, 3) == round(centroid_build.y, 3)
|
|
and round(centroid_bound.z, 3) == round(centroid_build.z, 3)
|
|
and round(area_bound, 3) == round(area_build, 3)
|
|
):
|
|
# If boundary surface exists, append the list of surface
|
|
# to change
|
|
if not surf.Name in adj_surfs_to_change:
|
|
adj_surfs_to_change[buildingSurf.Name] = surf.Name
|
|
break
|
|
# If boundary surface does not exist, append the list of surface
|
|
# to create
|
|
if not buildingSurf.Name in adj_surfs_to_change:
|
|
if not buildingSurf.Name in adj_surfs_to_make:
|
|
adj_surfs_to_make.append(buildingSurf.Name)
|
|
# If adjacent surface found, check if Outside boundary
|
|
# condition is a Zone and not "Outdoors"
|
|
for key, value in adj_surfs_to_change.items():
|
|
idf.getobject(
|
|
"BUILDINGSURFACE:DETAILED", value
|
|
).Outside_Boundary_Condition = "Zone"
|
|
idf.getobject(
|
|
"BUILDINGSURFACE:DETAILED", value
|
|
).Outside_Boundary_Condition_Object = idf.getobject(
|
|
"BUILDINGSURFACE:DETAILED", key
|
|
).Zone_Name
|
|
idf.getobject(
|
|
"BUILDINGSURFACE:DETAILED", value
|
|
).Construction_Name = idf.getobject(
|
|
"BUILDINGSURFACE:DETAILED", key
|
|
).Construction_Name
|
|
# If did not find any adjacent surface
|
|
for adj_surf_to_make in adj_surfs_to_make:
|
|
buildSurf = idf.getobject("BUILDINGSURFACE:DETAILED", adj_surf_to_make)
|
|
surf_type = buildSurf.Surface_Type
|
|
if surf_type.lower() == "wall":
|
|
surf_type_bound = "Wall"
|
|
if surf_type.lower() == "floor":
|
|
surf_type_bound = "Ceiling"
|
|
if surf_type.lower() == "ceiling":
|
|
surf_type_bound = "Floor"
|
|
if surf_type.lower() == "roof":
|
|
surf_type_bound = "Floor"
|
|
# Create a new surface
|
|
idf.newidfobject(
|
|
"BUILDINGSURFACE:DETAILED",
|
|
Name=buildSurf.Name + "_adj",
|
|
Surface_Type=surf_type_bound,
|
|
Construction_Name=buildSurf.Construction_Name,
|
|
Zone_Name=buildSurf.Outside_Boundary_Condition_Object,
|
|
Outside_Boundary_Condition="Zone",
|
|
Outside_Boundary_Condition_Object=buildSurf.Zone_Name,
|
|
Sun_Exposure="NoSun",
|
|
Wind_Exposure="NoWind",
|
|
View_Factor_to_Ground="autocalculate",
|
|
Number_of_Vertices=buildSurf.Number_of_Vertices,
|
|
Vertex_1_Xcoordinate=buildSurf.Vertex_4_Xcoordinate,
|
|
Vertex_1_Ycoordinate=buildSurf.Vertex_4_Ycoordinate,
|
|
Vertex_1_Zcoordinate=buildSurf.Vertex_4_Zcoordinate,
|
|
Vertex_2_Xcoordinate=buildSurf.Vertex_3_Xcoordinate,
|
|
Vertex_2_Ycoordinate=buildSurf.Vertex_3_Ycoordinate,
|
|
Vertex_2_Zcoordinate=buildSurf.Vertex_3_Zcoordinate,
|
|
Vertex_3_Xcoordinate=buildSurf.Vertex_2_Xcoordinate,
|
|
Vertex_3_Ycoordinate=buildSurf.Vertex_2_Ycoordinate,
|
|
Vertex_3_Zcoordinate=buildSurf.Vertex_2_Zcoordinate,
|
|
Vertex_4_Xcoordinate=buildSurf.Vertex_1_Xcoordinate,
|
|
Vertex_4_Ycoordinate=buildSurf.Vertex_1_Ycoordinate,
|
|
Vertex_4_Zcoordinate=buildSurf.Vertex_1_Zcoordinate,
|
|
)
|
|
|
|
|
|
def _get_schedules(idf):
|
|
"""Get schedules from IDF
|
|
|
|
Args:
|
|
idf (translater.idfclass.IDF): IDF object
|
|
"""
|
|
start_time = time.time()
|
|
log("Reading schedules from the IDF file...")
|
|
schedule_names = []
|
|
used_schedules = idf.get_used_schedules(yearly_only=True)
|
|
schedules = {}
|
|
for schedule_name in tqdm(used_schedules, desc="get_schedules"):
|
|
s = Schedule(
|
|
schedule_name, idf, start_day_of_the_week=idf.day_of_week_for_start_day
|
|
)
|
|
|
|
schedule_names.append(schedule_name)
|
|
schedules[schedule_name] = {}
|
|
year, weeks, days = s.to_year_week_day()
|
|
schedules[schedule_name]["all values"] = s.all_values
|
|
schedules[schedule_name]["year"] = year
|
|
# schedules[schedule_name]["weeks"] = weeks
|
|
# schedules[schedule_name]["days"] = days
|
|
|
|
log(
|
|
"Got yearly, weekly and daily schedules in {:,.2f} seconds".format(
|
|
time.time() - start_time
|
|
),
|
|
lg.INFO,
|
|
)
|
|
return schedule_names, schedules
|
|
|
|
|
|
def clear_name_idf_objects(idfFile, log_clear_names=False):
|
|
"""Clean names of IDF objects.
|
|
|
|
Replaces variable names with a unique name, easy to refer to the original
|
|
object. For example : if object is the n-th "Schedule Type Limit", then the
|
|
new name will be "stl_00000n" - limits length to 10 characters
|
|
|
|
Args:
|
|
idfFile (translater.idfclass.IDF): IDF object where to clean names
|
|
log_clear_names:
|
|
"""
|
|
|
|
uniqueList = []
|
|
old_name_list = []
|
|
old_new_eq = {}
|
|
|
|
# For all categories of objects in the IDF file
|
|
for obj in tqdm(idfFile.idfobjects, desc="cleaning_names"):
|
|
epObjects = idfFile.idfobjects[obj]
|
|
|
|
# For all objects in Category
|
|
count_name = 0
|
|
for epObject in epObjects:
|
|
# Do not take fenestration, to be treated later
|
|
try:
|
|
fenestration = [
|
|
s
|
|
for s in ["fenestration", "shgc", "window", "glazing"]
|
|
if s in epObject.Name.lower() or s in epObject.key.lower()
|
|
]
|
|
except:
|
|
fenestration = []
|
|
if not fenestration:
|
|
try:
|
|
old_name = epObject.Name
|
|
# For TRNBuild compatibility we oblige the new name to
|
|
# begin by a lowercase letter and the new name is max 10
|
|
# characters. The new name is done with the uppercase of
|
|
# the epObject type and an increment depending on the number
|
|
# of this epObject type. Making sure we
|
|
# have an unique new name
|
|
list_word_epObject_type = re.sub(
|
|
r"([A-Z])", r" \1", epObject.fieldvalues[0]
|
|
).split()
|
|
# Making sure new name will be max 10 characters
|
|
if len(list_word_epObject_type) > 4:
|
|
list_word_epObject_type = list_word_epObject_type[:4]
|
|
|
|
first_letters = "".join(
|
|
word[0].lower() for word in list_word_epObject_type
|
|
)
|
|
end_count = "%06d" % count_name
|
|
new_name = first_letters + "_" + end_count
|
|
|
|
# Make sure new name does not already exist
|
|
new_name, count_name = check_unique_name(
|
|
first_letters, count_name, new_name, uniqueList
|
|
)
|
|
|
|
uniqueList.append(new_name)
|
|
old_name_list.append(old_name)
|
|
old_new_eq[new_name.upper()] = old_name.upper()
|
|
|
|
# Changing the name in the IDF object
|
|
idfFile.rename(obj, old_name, new_name)
|
|
except:
|
|
pass
|
|
else:
|
|
continue
|
|
|
|
# Save equivalence between old and new names
|
|
df = pd.DataFrame([old_new_eq])
|
|
if not os.path.isdir(settings.data_folder):
|
|
os.makedirs(settings.data_folder)
|
|
df.to_csv(
|
|
os.path.join(
|
|
settings.data_folder, idfFile.name[:-4] + "_old_new_names_equivalence.csv"
|
|
)
|
|
)
|
|
|
|
d = {"Old names": old_name_list, "New names": uniqueList}
|
|
from tabulate import tabulate
|
|
|
|
log_name = os.path.basename(idfFile.idfname) + "_clear_names.log"
|
|
log_msg = (
|
|
"Here is the equivalence between the old names and the new "
|
|
"ones." + "\n\n" + tabulate(d, headers="keys")
|
|
)
|
|
log(log_msg, name=log_name, level=lg.INFO, avoid_console=log_clear_names)
|
|
|
|
|
|
def zone_origin(zone_object):
|
|
"""Return coordinates of a zone
|
|
|
|
Args:
|
|
zone_object (EpBunch): zone element in zone list.
|
|
|
|
Returns:
|
|
Coordinates [X, Y, Z] of the zone in a list.
|
|
"""
|
|
x = zone_object.X_Origin
|
|
if x == "":
|
|
x = 0
|
|
y = zone_object.Y_Origin
|
|
if y == "":
|
|
y = 0
|
|
z = zone_object.Z_Origin
|
|
if z == "":
|
|
z = 0
|
|
return [x, y, z]
|
|
|
|
|
|
def closest_coords(surfList, to=[0, 0, 0]):
|
|
"""Find closest coordinates to given ones
|
|
|
|
Args:
|
|
surfList (idf_MSequence): list of surfaces with coordinates of each one.
|
|
to (list): list of coordinates we want to calculate the distance from.
|
|
|
|
Returns:
|
|
the closest point (its coordinates x, y, z) to the point chosen (input
|
|
"to")
|
|
"""
|
|
from scipy.spatial import cKDTree
|
|
|
|
size = recursive_len([buildingSurf.coords for buildingSurf in surfList])
|
|
tuple_list = []
|
|
for surf in surfList:
|
|
for i in range(0, len(surf.coords)):
|
|
tuple_list.append(surf.coords[i])
|
|
|
|
nbdata = np.array(tuple_list)
|
|
btree = cKDTree(data=nbdata, compact_nodes=True, balanced_tree=True)
|
|
dist, idx = btree.query(np.array(to).T, k=1)
|
|
x, y, z = nbdata[idx]
|
|
return x, y, z
|
|
|
|
|
|
def parse_window_lib(window_file_path):
|
|
"""Function that parse window library from Berkeley Lab in two parts. First
|
|
part is a dataframe with the window characteristics. Second part is a
|
|
dictionary with the description/properties of each window.
|
|
|
|
Args:
|
|
window_file_path (str): Path to the window library
|
|
|
|
Returns:
|
|
tuple: a tuple of:
|
|
|
|
* dataframe: df_windows, a dataframe with the window characteristics
|
|
in the columns and the window id as rows
|
|
* dict: bunches, a dict with the window id as key and
|
|
description/properties of each window as value
|
|
"""
|
|
|
|
# Read window library and write lines in variable
|
|
if window_file_path is None:
|
|
all_lines = io.TextIOWrapper(io.BytesIO(settings.template_winLib)).readlines()
|
|
else:
|
|
all_lines = open(window_file_path).readlines()
|
|
|
|
# Select list of windows at the end of the file
|
|
end = "*** END OF LIBRARY ***"
|
|
indice_end = [k for k, s in enumerate(all_lines) if end in s]
|
|
|
|
window_list = all_lines[indice_end[0] + 1 :]
|
|
|
|
# Delete asterisk lines
|
|
asterisk = "*"
|
|
indices_asterisk = [k for k, line in enumerate(window_list) if asterisk in line]
|
|
window_list = [
|
|
",".join(line.split())
|
|
for i, line in enumerate(window_list)
|
|
if i not in indices_asterisk
|
|
]
|
|
|
|
# Save lines_for_df in text file
|
|
# User did not provide an output folder path. We use the default setting
|
|
data_dir = os.path.relpath(settings.data_folder)
|
|
|
|
if not os.path.isdir(data_dir):
|
|
os.mkdir(data_dir)
|
|
|
|
with open(os.path.join(data_dir, "winPOOL.txt"), "w") as converted_file:
|
|
for line in window_list:
|
|
converted_file.write(str(line) + "\n")
|
|
|
|
df_windows = pd.read_csv(os.path.join(data_dir, "winPOOL.txt"), header=None)
|
|
columns = [
|
|
"WinID",
|
|
"Description",
|
|
"Design",
|
|
"u_value",
|
|
"g_value",
|
|
"T_sol",
|
|
"Rf_sol",
|
|
"t_vis",
|
|
"Lay",
|
|
"Width",
|
|
]
|
|
df_windows.columns = columns
|
|
|
|
# Select list of windows with all their characteristics (bunch)
|
|
bunch_delimiter = (
|
|
"BERKELEY LAB WINDOW v7.4.6.0 DOE-2 Data File : Multi "
|
|
"Band Calculation : generated with Trnsys18.std\n"
|
|
)
|
|
detailed_windows = all_lines[0 : indice_end[0]]
|
|
|
|
# 1 window = 55 lines
|
|
bunches_list = list(chunks(detailed_windows, 55))
|
|
|
|
bunches = dict(get_window_id(bunches_list))
|
|
|
|
return df_windows, bunches
|
|
|
|
|
|
def get_window_id(bunches):
|
|
"""Return bunch of window properties with their window id
|
|
|
|
Args:
|
|
bunches (dict): dict with the window id as key and
|
|
description/properties of each window as value
|
|
"""
|
|
id_line = "Window ID :"
|
|
for bunch in bunches:
|
|
for line in bunch:
|
|
if id_line in line:
|
|
_, value = line.split(":")
|
|
value = int(value.strip())
|
|
yield value, bunch
|
|
|
|
|
|
def chunks(l, n):
|
|
"""Yield successive n-sized chunks from l
|
|
|
|
Args:
|
|
l (list): list to divide in chunks
|
|
n (int): number of chunks we want
|
|
"""
|
|
for i in range(0, len(l), n):
|
|
yield l[i : i + n]
|
|
|
|
|
|
def choose_window(u_value, shgc, t_vis, tolerance, window_lib_path):
|
|
"""Return window object from TRNBuild library
|
|
|
|
Returns
|
|
(tuple): A tuple of:
|
|
|
|
* window_ID
|
|
* window's description (label)
|
|
* window's design (width of layers)
|
|
* window u-value
|
|
* window shgc
|
|
* window solar transmittance
|
|
* window solar refraction
|
|
* window visible transmittance
|
|
* number of layers of the window
|
|
* window width
|
|
* the "bunch" of description/properties from Berkeley lab
|
|
|
|
If tolerance not respected return new tolerance used to find a
|
|
window.
|
|
|
|
Args:
|
|
u_value (float): U_value of the glazing given by the user
|
|
shgc (float): SHGC of the glazing given by the user
|
|
t_vis (float): Visible transmittance of the glazing given by the user
|
|
tolerance (float): Maximum tolerance on u_value, shgc and tvis wanted by
|
|
the user
|
|
window_lib_path (.dat file): window library from Berkeley lab
|
|
"""
|
|
# Init "warn" variable (0 or 1) to log a warning if tolerance not respected
|
|
warn = 0
|
|
|
|
# Making sure u_value, shgc and tvis are float
|
|
if not isinstance(u_value, float):
|
|
u_value = float(u_value)
|
|
if not isinstance(shgc, float):
|
|
shgc = float(shgc)
|
|
if not isinstance(t_vis, float):
|
|
t_vis = float(t_vis)
|
|
if not isinstance(tolerance, float):
|
|
tolerance = float(tolerance)
|
|
|
|
# Parse window library
|
|
df_windows, window_bunches = parse_window_lib(window_lib_path)
|
|
|
|
# Find window(s) in the tolerance limit
|
|
cond1 = (df_windows["u_value"] <= u_value * (1 + tolerance)) & (
|
|
df_windows["u_value"] >= u_value * (1 - tolerance)
|
|
)
|
|
cond2 = (df_windows["g_value"] <= shgc * (1 + tolerance)) & (
|
|
df_windows["g_value"] >= shgc * (1 - tolerance)
|
|
)
|
|
cond3 = (df_windows["t_vis"] <= t_vis * (1 + tolerance)) & (
|
|
df_windows["t_vis"] >= t_vis * (1 - tolerance)
|
|
)
|
|
|
|
# Every window's IDs satisfying the tolerance
|
|
win_ids = df_windows.loc[(cond1 & cond2 & cond3), "WinID"]
|
|
|
|
# If nothing found, increase the tolerance
|
|
while win_ids.empty:
|
|
warn = 1
|
|
tolerance += 0.01
|
|
cond1 = (df_windows["u_value"] <= u_value * (1 + tolerance)) & (
|
|
df_windows["u_value"] >= u_value * (1 - tolerance)
|
|
)
|
|
cond2 = (df_windows["g_value"] <= shgc * (1 + tolerance)) & (
|
|
df_windows["g_value"] >= shgc * (1 - tolerance)
|
|
)
|
|
cond3 = (df_windows["t_vis"] <= t_vis * (1 + tolerance)) & (
|
|
df_windows["t_vis"] >= t_vis * (1 - tolerance)
|
|
)
|
|
win_ids = df_windows.loc[(cond1 & cond2 & cond3), "WinID"]
|
|
|
|
# If several windows found, get the one with the minimal square error sum.
|
|
best_window_index = (
|
|
df_windows.loc[win_ids.index, :]
|
|
.apply(
|
|
lambda x: (x.u_value - u_value) ** 2
|
|
+ (x.g_value - shgc) ** 2
|
|
+ (x.t_vis - t_vis) ** 2,
|
|
axis=1,
|
|
)
|
|
.idxmin()
|
|
)
|
|
(
|
|
win_id,
|
|
description,
|
|
design,
|
|
u_win,
|
|
shgc_win,
|
|
t_sol_win,
|
|
rf_sol_win,
|
|
t_vis_win,
|
|
lay_win,
|
|
width,
|
|
) = df_windows.loc[
|
|
best_window_index,
|
|
[
|
|
"WinID",
|
|
"Description",
|
|
"Design",
|
|
"u_value",
|
|
"g_value",
|
|
"T_sol",
|
|
"Rf_sol",
|
|
"t_vis",
|
|
"Lay",
|
|
"Width",
|
|
],
|
|
]
|
|
|
|
# If tolerance was not respected to find a window, write in log a warning
|
|
if warn:
|
|
log(
|
|
"Window tolerance was not respected. Final tolerance = "
|
|
"{:,.2f}".format(tolerance),
|
|
lg.WARNING,
|
|
)
|
|
# Write in log (info) the characteristics of the window
|
|
log(
|
|
"Characterisitics of the chosen window are: u_value = {:,.2f}, "
|
|
"SHGC= {:,.2f}, t_vis= {:,.2f}".format(u_win, shgc_win, t_vis_win),
|
|
lg.INFO,
|
|
)
|
|
|
|
# If warn = 1 (tolerance not respected) return tolerance
|
|
if warn:
|
|
return (
|
|
win_id,
|
|
description,
|
|
design,
|
|
u_win,
|
|
shgc_win,
|
|
t_sol_win,
|
|
rf_sol_win,
|
|
t_vis_win,
|
|
lay_win,
|
|
width,
|
|
window_bunches[win_id],
|
|
tolerance,
|
|
)
|
|
else:
|
|
return (
|
|
win_id,
|
|
description,
|
|
design,
|
|
u_win,
|
|
shgc_win,
|
|
t_sol_win,
|
|
rf_sol_win,
|
|
t_vis_win,
|
|
lay_win,
|
|
width,
|
|
window_bunches[win_id],
|
|
)
|
|
|
|
|
|
def trnbuild_idf(
|
|
idf_file,
|
|
output_folder=None,
|
|
template=None,
|
|
dck=False,
|
|
nonum=False,
|
|
N=False,
|
|
geo_floor=0.6,
|
|
refarea=False,
|
|
volume=False,
|
|
capacitance=False,
|
|
trnsidf_exe=None,
|
|
):
|
|
"""This program sorts and renumbers the IDF file and writes a B18 file based
|
|
on the geometric information of the IDF file and the template D18 file. In
|
|
addition, an template DCK file can be generated.
|
|
|
|
Important:
|
|
Where settings.trnsys_default_folder must be defined inside the
|
|
configuration file of the package
|
|
|
|
Example:
|
|
>>> # Exemple of setting kwargs to be unwrapped in the function
|
|
>>> kwargs_dict = {'dck': True, 'geo_floor': 0.57}
|
|
>>> # Exemple how to call the function
|
|
>>> trnbuild_idf(idf_file,template=os.path.join(
|
|
>>> settings.trnsys_default_folder,
|
|
>>> r"Building\\trnsIDF\\NewFileTemplate.d18"
|
|
|
|
Args:
|
|
idf_file (str): path/filename.idf to the T3D file "a SketchUp idf file"
|
|
output_folder (str, optional): location where output files will be
|
|
template (str): path/NewFileTemplate.d18
|
|
dck (bool): If True, create a template DCK
|
|
nonum (bool, optional): If True, no renumeration of surfaces
|
|
N (optional): BatchJob Modus
|
|
geo_floor (float, optional): generates GEOSURF values for distributing
|
|
direct solar radiation where `geo_floor` % is directed to the floor,
|
|
the rest to walls/windows. Default = 0.6
|
|
refarea (bool, optional): If True, floor reference area of airnodes is
|
|
updated
|
|
volume (bool, True): If True, volume of airnodes is updated
|
|
capacitance (bool, True): If True, capacitance of airnodes is updated
|
|
trnsidf_exe (str): Path of the trnsidf.exe executable
|
|
|
|
Returns:
|
|
str: status
|
|
|
|
Raises:
|
|
CalledProcessError: When could not run command with trnsidf.exe (to
|
|
create BUI file from IDF (T3D) file
|
|
"""
|
|
# assert files
|
|
if not trnsidf_exe:
|
|
trnsidf_exe = os.path.join(
|
|
settings.trnsys_default_folder, r"Building\trnsIDF\trnsidf.exe"
|
|
)
|
|
|
|
if not os.path.isfile(trnsidf_exe):
|
|
raise IOError("trnsidf.exe not found")
|
|
|
|
if not template:
|
|
template = settings.path_template_d18
|
|
|
|
if not os.path.isfile(template):
|
|
raise IOError("template file not found")
|
|
|
|
# first copy idf_file into output folder
|
|
if not output_folder:
|
|
output_folder = settings.data_folder
|
|
if not os.path.isdir(output_folder):
|
|
os.mkdir(output_folder)
|
|
head, tail = os.path.split(idf_file)
|
|
new_idf_file = os.path.abspath(os.path.join(output_folder, tail))
|
|
if new_idf_file != os.path.abspath(idf_file):
|
|
shutil.copy(idf_file, new_idf_file)
|
|
idf_file = os.path.abspath(new_idf_file) # back to idf_file
|
|
del new_idf_file, head, tail
|
|
|
|
# Continue
|
|
args = locals().copy()
|
|
idf = os.path.abspath(args.pop("idf_file"))
|
|
template = os.path.abspath(args.pop("template"))
|
|
trnsysidf_exe = os.path.abspath(args.pop("trnsidf_exe"))
|
|
|
|
if not os.path.isfile(idf) or not os.path.isfile(template):
|
|
raise FileNotFoundError()
|
|
|
|
if sys.platform == "win32":
|
|
cmd = [trnsysidf_exe]
|
|
else:
|
|
cmd = ["wine", trnsysidf_exe]
|
|
cmd.extend([idf])
|
|
cmd.extend([template])
|
|
for arg in args:
|
|
if args[arg]:
|
|
if isinstance(args[arg], bool):
|
|
args[arg] = ""
|
|
if args[arg] != "":
|
|
cmd.extend(["/{}={}".format(arg, args[arg])])
|
|
else:
|
|
cmd.extend(["/{}".format(arg)])
|
|
|
|
try:
|
|
# execute the command
|
|
log("Running cmd: {}".format(cmd), lg.DEBUG)
|
|
command_line_process = subprocess.Popen(
|
|
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
|
|
)
|
|
process_output, _ = command_line_process.communicate()
|
|
# process_output is now a string, not a file
|
|
log(process_output.decode("utf-8"), lg.DEBUG)
|
|
except subprocess.CalledProcessError as exception:
|
|
log("Exception occured: " + str(exception), lg.ERROR)
|
|
log("Trnsidf.exe failed", lg.ERROR)
|
|
return False
|
|
else:
|
|
# Send trnsidf log to logger
|
|
pre, ext = os.path.splitext(idf)
|
|
log_file = pre + ".log"
|
|
if os.path.isfile(log_file):
|
|
with open(log_file, "r") as f:
|
|
log(f.read(), lg.DEBUG)
|
|
|
|
return True
|
|
|
|
|
|
def _write_zone_buildingSurf_fenestrationSurf(
|
|
buildingSurfs,
|
|
coordSys,
|
|
fenestrationSurfs,
|
|
idf,
|
|
lines,
|
|
n_ground,
|
|
zones,
|
|
schedule_as_input,
|
|
):
|
|
"""Does several actions on the zones, fenestration and building surfaces.
|
|
Then, writes zone, fenestration and building surfaces information in lines.
|
|
|
|
Zones:
|
|
1. If the geometry global rule is 'World', convert zone's coordinates to
|
|
absolute.
|
|
2. Rounds zone's coordinates to 4 decimal.
|
|
3. Write zones in lines (T3D file).
|
|
|
|
Fenestration surfaces:
|
|
1. If the geometry global rule is 'Relative', convert fenestration's
|
|
coordinates to absolute.
|
|
2. Find the window slope and create a new window object (to write in T3D
|
|
file) for each different slope.
|
|
3. Rounds fenestration surface's coordinates to 4 decimal.
|
|
4. Write fenestration surfaces in lines (T3D file).
|
|
|
|
Building surfaces:
|
|
1. If the geometry global rule is 'Relative', convert building surface's
|
|
coordinates to absolute.
|
|
2. Determine the outside boundary condition (eg. 'ground') of each
|
|
surface. If boundary is 'surface' or 'zone', modify the surface to
|
|
make sure adjancency is well done between surfaces (see
|
|
_modify_adj_surface()). If boundary is 'ground', apply ground
|
|
temperature to the Outside_Boundary_Condition_Object. If the boundary
|
|
is 'adiabatic', apply an IDENTICAL boundary to the
|
|
Outside_Boundary_Condition_Object.
|
|
3. Rounds building surface's coordinates to 4 decimal
|
|
4. Write building surfaces in lines (T3D file)
|
|
|
|
Args:
|
|
buildingSurfs (idf_MSequence): IDF object from idf.idfobjects(). List of
|
|
building surfaces ("BUILDINGSURFACE:DETAILED" in the IDF).
|
|
coordSys (str): Coordinate system of the IDF file. Can be 'Absolute'
|
|
fenestrationSurfs (idf_MSequence): IDF object from idf.idfobjects().
|
|
List of fenestration surfaces ("FENESTRATIONSURFACE:DETAILED" in the
|
|
IDF).
|
|
idf (translater.idfclass.IDF): IDF object
|
|
lines (list): Text to create the T3D file (IDF file to import in
|
|
TRNBuild). To be appended (insert) here
|
|
n_ground (Vector 3D): Normal vector of the ground surface
|
|
zones (idf_MSequence): IDF object from idf.idfobjects(). List of zones
|
|
("ZONES" in the IDF).
|
|
"""
|
|
# Get line number where to write
|
|
variableDictNum = checkStr(
|
|
lines, "ALL OBJECTS IN CLASS: " "OUTPUT:VARIABLEDICTIONARY"
|
|
)
|
|
# Initialize list of window's slopes
|
|
count_slope = 0
|
|
win_slope_dict = {}
|
|
log(
|
|
"Writing geometry (zones, building and fenestration surfaces info from "
|
|
"idf file to t3d file..."
|
|
)
|
|
count_fs = 0
|
|
for zone in tqdm(zones, desc="writing building zone/surface coordinates "):
|
|
zone.Direction_of_Relative_North = 0.0
|
|
if zone.Multiplier == "":
|
|
zone.Multiplier = 1
|
|
# Coords of zone
|
|
incrX, incrY, incrZ = zone_origin(zone)
|
|
|
|
# Writing fenestrationSurface:Detailed in lines
|
|
for fenestrationSurf in fenestrationSurfs:
|
|
surfName = fenestrationSurf.Building_Surface_Name
|
|
if (
|
|
idf.getobject("BUILDINGSURFACE:DETAILED", surfName).Zone_Name
|
|
== zone.Name
|
|
):
|
|
count_fs += 1
|
|
# Clear fenestrationSurface:Detailed name
|
|
fenestrationSurf.Name = "fsd_" + "%06d" % count_fs
|
|
# Insure right number of vertices
|
|
fenestrationSurf.Number_of_Vertices = len(fenestrationSurf.coords)
|
|
|
|
# Change coordinates from relative to absolute
|
|
if coordSys == "Relative":
|
|
# Add zone coordinates to X, Y, Z vectors to fenestration
|
|
# surface
|
|
_relative_to_absolute(fenestrationSurf, incrX, incrY, incrZ)
|
|
|
|
# Round vertex to 2 decimal digit max
|
|
_round_vertex(fenestrationSurf)
|
|
|
|
# Polygon from vector's window surface
|
|
poly_window = Polygon3D(fenestrationSurf.coords)
|
|
# Normal vectors of the polygon
|
|
n_window = poly_window.normal_vector
|
|
|
|
# Calculate the slope between window and the ground (with
|
|
# normal vectors)
|
|
win_slope = 180 * angle(n_ground, n_window) / np.pi
|
|
if win_slope > 90:
|
|
win_slope -= 180
|
|
|
|
# Add a construction name if slope does not already exist
|
|
if win_slope not in win_slope_dict.values():
|
|
count_slope += 1
|
|
# Insure right construction name
|
|
fenestrationSurf.Construction_Name = "EXT_WINDOW{}".format(
|
|
count_slope
|
|
)
|
|
# Append win_slope_dict
|
|
win_slope_dict[fenestrationSurf.Construction_Name] = win_slope
|
|
|
|
else:
|
|
fenestrationSurf.Construction_Name = [
|
|
key
|
|
for key in win_slope_dict.keys()
|
|
if win_slope == win_slope_dict[key]
|
|
][0]
|
|
|
|
lines.insert(variableDictNum + 2, fenestrationSurf)
|
|
|
|
# Writing buildingSurface: Detailed in lines
|
|
surfList = []
|
|
for buildingSurf in buildingSurfs:
|
|
# Change Outside Boundary Condition and Objects
|
|
if buildingSurf.Zone_Name == zone.Name:
|
|
buildingSurf.Number_of_Vertices = len(buildingSurf.coords)
|
|
surfList.append(buildingSurf)
|
|
# Verify if surface is adjacent. If yes, modifies it
|
|
if "surface" in buildingSurf.Outside_Boundary_Condition.lower():
|
|
_modify_adj_surface(buildingSurf, idf)
|
|
|
|
if "ground" in buildingSurf.Outside_Boundary_Condition.lower():
|
|
if schedule_as_input:
|
|
buildingSurf.Outside_Boundary_Condition_Object = (
|
|
"BOUNDARY=INPUT 1*sch_ground"
|
|
)
|
|
else:
|
|
buildingSurf.Outside_Boundary_Condition_Object = (
|
|
"BOUNDARY=SCHEDULE 1*sch_ground"
|
|
)
|
|
|
|
if "adiabatic" in buildingSurf.Outside_Boundary_Condition.lower():
|
|
buildingSurf.Outside_Boundary_Condition = "OtherSideCoefficients"
|
|
buildingSurf.Outside_Boundary_Condition_Object = (
|
|
"BOUNDARY=IDENTICAL"
|
|
)
|
|
|
|
if (
|
|
"othersidecoefficients"
|
|
in buildingSurf.Outside_Boundary_Condition.lower()
|
|
):
|
|
buildingSurf.Outside_Boundary_Condition = "OtherSideCoefficients"
|
|
buildingSurf.Outside_Boundary_Condition_Object = (
|
|
"BOUNDARY=INPUT 1*TBOUNDARY"
|
|
)
|
|
|
|
if (
|
|
"othersideconditionsmodel"
|
|
in buildingSurf.Outside_Boundary_Condition.lower()
|
|
):
|
|
msg = (
|
|
'Surface "{}" has '
|
|
'"OtherSideConditionsModel" as an outside '
|
|
"boundary condition, this method is not implemented".format(
|
|
buildingSurf.Name
|
|
)
|
|
)
|
|
raise NotImplementedError(msg)
|
|
|
|
# Round vertex to 2 decimal digit max
|
|
_round_vertex(buildingSurf)
|
|
|
|
# Makes sure idf object key is not all upper string
|
|
buildingSurf.key = "BuildingSurface:Detailed"
|
|
|
|
lines.insert(variableDictNum + 2, buildingSurf)
|
|
|
|
# Change coordinates from world (all zones to 0) to absolute
|
|
if coordSys == "World":
|
|
zone.X_Origin, zone.Y_Origin, zone.Z_Origin = closest_coords(
|
|
surfList, to=zone_origin(zone)
|
|
)
|
|
|
|
# Round vertex to 4 decimal digit max
|
|
zone.X_Origin = round(zone_origin(zone)[0], 4)
|
|
zone.Y_Origin = round(zone_origin(zone)[1], 4)
|
|
zone.Z_Origin = round(zone_origin(zone)[2], 4)
|
|
|
|
lines.insert(variableDictNum + 2, zone)
|
|
return win_slope_dict
|
|
|
|
|
|
def _modify_adj_surface(buildingSurf, idf):
|
|
"""If necessary, modify outside boundary conditions and vertices of the
|
|
adjacent building surface
|
|
|
|
Args:
|
|
buildingSurf (EpBunch): Building surface object to modify
|
|
idf (translater.idfclass.IDF): IDF object
|
|
"""
|
|
# Force outside boundary condition to "Zone"
|
|
buildingSurf.Outside_Boundary_Condition = "Zone"
|
|
# Get the surface EpBunch that is adjacent to the building surface
|
|
outside_bound_surf = buildingSurf.Outside_Boundary_Condition_Object
|
|
# If outside_bound_surf is the same surface as buildingSurf, raises error
|
|
if outside_bound_surf == buildingSurf.Name:
|
|
buildingSurf.Outside_Boundary_Condition = "OtherSideCoefficients"
|
|
buildingSurf.Outside_Boundary_Condition_Object = "BOUNDARY=IDENTICAL"
|
|
# Prevents the user in the log of the change of the Boumdary Conditions
|
|
msg = (
|
|
'Surface "{surfname}" has "{outside_bound}" as Outside '
|
|
"Boundary Condition Object (adjacent to itself). To solve this "
|
|
"problem, we forced the Boundary Condition of this surface to "
|
|
'be "IDENTICAL".'.format(
|
|
surfname=buildingSurf.Name, outside_bound=outside_bound_surf
|
|
)
|
|
)
|
|
log(msg, lg.WARNING)
|
|
else:
|
|
# Replace the Outside_Boundary_Condition_Object that was the
|
|
# outside_bound_surf, by the adjacent zone name
|
|
buildingSurf.Outside_Boundary_Condition_Object = idf.getobject(
|
|
"ZONE",
|
|
idf.getobject("BUILDINGSURFACE:DETAILED", outside_bound_surf).Zone_Name,
|
|
).Name
|
|
# Force same construction for adjacent surfaces
|
|
buildingSurf.Construction_Name = idf.getobject(
|
|
"BUILDINGSURFACE:DETAILED", outside_bound_surf
|
|
).Construction_Name
|
|
# Polygon from vector's adjacent surfaces
|
|
poly1 = Polygon3D(buildingSurf.coords)
|
|
poly2 = Polygon3D(
|
|
idf.getobject("BUILDINGSURFACE:DETAILED", outside_bound_surf).coords
|
|
)
|
|
# Normal vectors of each polygon
|
|
n1 = poly1.normal_vector
|
|
n2 = poly2.normal_vector
|
|
# Verify if normal vectors of adjacent surfaces have
|
|
# opposite directions
|
|
if (
|
|
round((n1 + n2).x, 2) != 0
|
|
or round((n1 + n2).y, 2) != 0
|
|
or round((n1 + n2).z, 2) != 0
|
|
):
|
|
# If not, inverse vertice of buildingSurf
|
|
# (Vertex4 become Vertex1, Vertex2 become Vertex3, etc.)
|
|
_inverse_vertices_surf(
|
|
buildingSurf, idf, outside_bound_surf, "BUILDINGSURFACE:DETAILED"
|
|
)
|
|
|
|
|
|
def _inverse_vertices_surf(buildingSurf, idf, outside_bound_surf, idfobject_key):
|
|
"""Inverses the vertices of a surface (last vertex becomes the first one,
|
|
etc.)
|
|
|
|
Args:
|
|
buildingSurf (EpBunch): Building surface object to modify
|
|
idf (translater.idfclass.IDF): IDF object
|
|
outside_bound_surf (str): Name of the adjacent surface to the
|
|
buildingSurf
|
|
idfobject_key (str): Section name of the IDF where to find the
|
|
outside_bound_surf
|
|
"""
|
|
for j, k in zip(
|
|
range(1, len(buildingSurf.coords) + 1), range(len(buildingSurf.coords), 0, -1)
|
|
):
|
|
idf.getobject(idfobject_key, outside_bound_surf)[
|
|
"Vertex_" + str(j) + "_Xcoordinate"
|
|
] = buildingSurf["Vertex_" + str(k) + "_Xcoordinate"]
|
|
idf.getobject(idfobject_key, outside_bound_surf)[
|
|
"Vertex_" + str(j) + "_Ycoordinate"
|
|
] = buildingSurf["Vertex_" + str(k) + "_Ycoordinate"]
|
|
idf.getobject(idfobject_key, outside_bound_surf)[
|
|
"Vertex_" + str(j) + "_Zcoordinate"
|
|
] = buildingSurf["Vertex_" + str(k) + "_Zcoordinate"]
|
|
|
|
|
|
def _round_vertex(surface, nbr_decimal=2):
|
|
"""Round vertex to the number of decimal (nbr_decimal) wanted
|
|
|
|
Args:
|
|
surface (EpBunch): Surface object to which we want to round its vertices
|
|
nbr_decimal (int): Number of decimal to round
|
|
"""
|
|
for j in range(1, len(surface.coords) + 1):
|
|
surface["Vertex_" + str(j) + "_Xcoordinate"] = round(
|
|
surface["Vertex_" + str(j) + "_Xcoordinate"], nbr_decimal
|
|
)
|
|
surface["Vertex_" + str(j) + "_Ycoordinate"] = round(
|
|
surface["Vertex_" + str(j) + "_Ycoordinate"], nbr_decimal
|
|
)
|
|
surface["Vertex_" + str(j) + "_Zcoordinate"] = round(
|
|
surface["Vertex_" + str(j) + "_Zcoordinate"], nbr_decimal
|
|
)
|
|
|
|
|
|
def _relative_to_absolute(surface, incrX, incrY, incrZ):
|
|
"""Convert relative coordinates to absolute ones
|
|
|
|
Args:
|
|
surface (EpBunch): Surface object to which we want to convert its
|
|
vertices
|
|
incrX (str): X coordinate of the surface's zone
|
|
incrY (str): Y coordinate of the surface's zone
|
|
incrZ (str): Z coordinate of the surface's zone
|
|
"""
|
|
for j in range(1, len(surface.coords) + 1):
|
|
surface["Vertex_" + str(j) + "_Xcoordinate"] = (
|
|
surface["Vertex_" + str(j) + "_Xcoordinate"] + incrX
|
|
)
|
|
surface["Vertex_" + str(j) + "_Ycoordinate"] = (
|
|
surface["Vertex_" + str(j) + "_Ycoordinate"] + incrY
|
|
)
|
|
surface["Vertex_" + str(j) + "_Zcoordinate"] = (
|
|
surface["Vertex_" + str(j) + "_Zcoordinate"] + incrZ
|
|
)
|
|
|
|
|
|
def _write_winPool(lines, window):
|
|
"""Write the window pool (from Berkeley Lab window library) in lines
|
|
|
|
Args:
|
|
lines (list): Text to create the T3D file (IDF file to import in
|
|
TRNBuild). To be appended (insert) here
|
|
window (tuple): Information to write in the window pool extension (
|
|
"""
|
|
# Get line number to write the EXTENSION_WINPOOL
|
|
extWinpoolNum = checkStr(lines, "!-_EXTENSION_WINPOOL_START_")
|
|
count = 0
|
|
for line in window[10]:
|
|
lines.insert(extWinpoolNum + count, "!-" + line)
|
|
count += 1
|
|
# Get line number to write the Window description
|
|
winDescriptionNum = checkStr(lines, "WinID Description")
|
|
lines.insert(
|
|
winDescriptionNum + 1,
|
|
"!-"
|
|
+ str(window[0])
|
|
+ " "
|
|
+ str(window[1])
|
|
+ " "
|
|
+ str(window[2])
|
|
+ " "
|
|
+ str(window[3])
|
|
+ " "
|
|
+ str(window[4])
|
|
+ " "
|
|
+ str(window[5])
|
|
+ " "
|
|
+ str(window[6])
|
|
+ " "
|
|
+ str(window[7])
|
|
+ " "
|
|
+ str(window[8])
|
|
+ " "
|
|
+ str(window[9])
|
|
+ "\n",
|
|
)
|
|
|
|
|
|
def _write_window(lines, win_slope_dict, window, fframe=0.15, uframe=8.17):
|
|
"""Write window information in lines
|
|
|
|
Args:
|
|
lines (list): Text to create the T3D file (IDF file to import in
|
|
TRNBuild). To be appended (insert) here
|
|
win_slope_dict (dict): Dictionary with window's names as key and
|
|
window's slope as value
|
|
window (tuple): Information to write in the window pool extension
|
|
fframe (float): fraction of the window frame (between 0 and 1)
|
|
uframe (float): u-value of the window frame
|
|
"""
|
|
log("Writing windows info from idf file to t3d file...")
|
|
# Get line number where to write
|
|
windowNum = checkStr(lines, "W i n d o w s")
|
|
# Write
|
|
for key in win_slope_dict.keys():
|
|
lines.insert(windowNum + 1, "WINDOW " + str(key) + "\n")
|
|
lines.insert(
|
|
windowNum + 2,
|
|
"!- WINID = " + str(window[0]) + ": HINSIDE = 11:"
|
|
" HOUTSIDE = 64: SLOPE "
|
|
"= " + str(win_slope_dict[key]) + ": "
|
|
"SPACID = 4: WWID = 0.77: "
|
|
"WHEIG = 1.08: "
|
|
"FFRAME = "
|
|
+ str(fframe)
|
|
+ ": UFRAME = "
|
|
+ str(uframe)
|
|
+ ": ABSFRAME = 0.6: "
|
|
"RISHADE = 0: RESHADE = 0: "
|
|
"REFLISHADE = 0.5: "
|
|
"REFLOSHADE = 0.5: CCISHADE "
|
|
"= 0.5: "
|
|
"EPSFRAME = 0.9: EPSISHADE "
|
|
"= 0.9: "
|
|
"ITSHADECLOSE = INPUT 1 * "
|
|
"SHADE_CLOSE: "
|
|
"ITSHADEOPEN = INPUT 1 * "
|
|
"SHADE_OPEN: "
|
|
"FLOWTOAIRNODE = 1: PERT = "
|
|
"0: PENRT = 0: "
|
|
"RADMATERIAL = undefined: "
|
|
"RADMATERIAL_SHD1 = "
|
|
"undefined" + "\n",
|
|
)
|
|
|
|
|
|
def _write_schedules(lines, schedule_names, schedules, schedule_as_input, idf_file):
|
|
"""Write schedules information in lines
|
|
|
|
Args:
|
|
lines (list): Text to create the T3D file (IDF file to import in
|
|
TRNBuild). To be appended (insert) here
|
|
schedule_names (list): Names of all the schedules to be written in lines
|
|
schedules (dict): Dictionary with the schedule names as key and with
|
|
"""
|
|
log("Writing schedules info from idf file to t3d file...")
|
|
schedules_not_written = []
|
|
# Writes schedules as INPUTS
|
|
if schedule_as_input:
|
|
# Get line number where to write INPUTS
|
|
inputNum = checkStr(lines, "I n p u t s")
|
|
ind = lines[inputNum + 1].find("\n")
|
|
count = 0
|
|
while count * 13 < len(schedule_names):
|
|
begin = count * 13
|
|
end = begin + 13
|
|
if begin == 0 and len(schedule_names) == 13:
|
|
lines[inputNum + 1] = (
|
|
lines[inputNum + 1][:ind]
|
|
+ " "
|
|
+ " ".join(str(item) for item in schedule_names[begin:end])
|
|
+ "\n"
|
|
)
|
|
count += 1
|
|
continue
|
|
if begin == 0 and len(schedule_names) != 13:
|
|
lines[inputNum + 1] = (
|
|
lines[inputNum + 1][:ind]
|
|
+ " "
|
|
+ " ".join(str(item) for item in schedule_names[begin:end])
|
|
+ ";"
|
|
+ "\n"
|
|
)
|
|
count += 1
|
|
continue
|
|
if end >= len(schedule_names):
|
|
end = len(schedule_names)
|
|
lines.insert(
|
|
inputNum + count + 1,
|
|
" "
|
|
+ " ".join(str(item) for item in schedule_names[begin:end])
|
|
+ "\n",
|
|
)
|
|
else:
|
|
lines.insert(
|
|
inputNum + count + 1,
|
|
" "
|
|
+ " ".join(str(item) for item in schedule_names[begin:end])
|
|
+ ";"
|
|
+ "\n",
|
|
)
|
|
count += 1
|
|
# Writes INPUTS DESCRIPTION
|
|
idf_file = Path(idf_file)
|
|
inputDescrNum = checkStr(lines, "INPUTS_DESCRIPTION")
|
|
lines.insert(
|
|
inputDescrNum,
|
|
" sy_XXXXXX : any : yearly schedules for internal gains. "
|
|
"Should be found in the yearly_schedules_"
|
|
+ idf_file.basename().stripext()
|
|
+ ".csv file"
|
|
+ "\n",
|
|
)
|
|
# Writes schedules as SCHEDULES
|
|
else:
|
|
# Get line number where to write
|
|
scheduleNum = checkStr(lines, "S c h e d u l e s")
|
|
# Write schedules YEAR in lines
|
|
for schedule_name in schedule_names:
|
|
|
|
first_hour_month = [
|
|
0,
|
|
744,
|
|
1416,
|
|
2160,
|
|
2880,
|
|
3624,
|
|
4344,
|
|
5088,
|
|
5832,
|
|
6552,
|
|
7296,
|
|
8016,
|
|
8760,
|
|
]
|
|
|
|
# Get annual hourly values of schedules
|
|
arr = schedules[schedule_name]["all values"]
|
|
# Find the hours where hourly values change
|
|
(hours_list,) = np.where(np.roll(arr, 1) != arr)
|
|
# if hours_list is empty, give it hour 0
|
|
if hours_list.size == 0:
|
|
hours_list = np.array([0])
|
|
# Get schedule values where values change and add first schedule value
|
|
values = arr[hours_list]
|
|
# Add hour 0 and first value if not in array
|
|
if 0 not in hours_list:
|
|
hours_list = np.insert(hours_list, 0, np.array([0]))
|
|
values = np.insert(values, 0, arr[0])
|
|
# Add hour 8760 and if not in array
|
|
if 8760 not in hours_list:
|
|
hours_list = np.append(hours_list, 8760)
|
|
values = np.append(values, arr[len(arr) - 1])
|
|
|
|
# Makes sure fisrt hour of every month in hour and value lists
|
|
for hour in first_hour_month:
|
|
if hour not in hours_list:
|
|
temp = hours_list > hour
|
|
count = 0
|
|
for t in temp:
|
|
if t:
|
|
hours_list = np.insert(hours_list, count, hour)
|
|
values = np.insert(values, count, values[count - 1])
|
|
break
|
|
count += 1
|
|
|
|
# Round values to 1 decimal
|
|
values = np.round(values.astype("float64"), decimals=1)
|
|
|
|
# Writes schedule in lines
|
|
# Write values
|
|
_write_schedule_values(values, lines, scheduleNum, "VALUES")
|
|
# Write hours
|
|
_write_schedule_values(hours_list, lines, scheduleNum, "HOURS")
|
|
|
|
# Write schedule name
|
|
lines.insert(scheduleNum + 1, "!-SCHEDULE " + schedule_name + "\n")
|
|
|
|
# if (
|
|
# len(hours_list) <= 1500
|
|
# ): # Todo: Now, only writes "short" schedules. Make method that write them all
|
|
# lines.insert(
|
|
# scheduleNum + 1,
|
|
# "!-SCHEDULE " + schedules[schedule_name]["year"].Name + "\n",
|
|
# )
|
|
# lines.insert(
|
|
# scheduleNum + 2,
|
|
# "!- HOURS= " + " ".join(str(item) for item in hours_list) + "\n",
|
|
# )
|
|
# lines.insert(
|
|
# scheduleNum + 3,
|
|
# "!- VALUES= " + " ".join(str(item) for item in values) + "\n",
|
|
# )
|
|
# else:
|
|
# schedules_not_written.append(schedule_name)
|
|
|
|
return schedules_not_written
|
|
|
|
|
|
def _write_schedule_values(liste, lines, scheduleNum, string):
|
|
count = 0
|
|
while count * 13 < len(liste):
|
|
begin = count * 13
|
|
end = begin + 13
|
|
if begin == 0 and len(liste) == 13:
|
|
lines.insert(
|
|
scheduleNum + 1,
|
|
"!- "
|
|
+ string
|
|
+ "= "
|
|
+ " ".join(str(item) for item in liste[begin:end])
|
|
+ "\n",
|
|
)
|
|
count += 1
|
|
continue
|
|
if begin == 0 and len(liste) != 13:
|
|
lines.insert(
|
|
scheduleNum + 1,
|
|
"!- "
|
|
+ string
|
|
+ "= "
|
|
+ " ".join(str(item) for item in liste[begin:end])
|
|
+ ";"
|
|
+ "\n",
|
|
)
|
|
count += 1
|
|
continue
|
|
if end >= len(liste):
|
|
end = len(liste)
|
|
lines.insert(
|
|
scheduleNum + count + 1,
|
|
" ".join(str(item) for item in liste[begin:end]) + "\n",
|
|
)
|
|
else:
|
|
lines.insert(
|
|
scheduleNum + count + 1,
|
|
" ".join(str(item) for item in liste[begin:end]) + ";" + "\n",
|
|
)
|
|
count += 1
|
|
|
|
|
|
def _write_conditioning(htm, lines, schedules, old_new_names, schedule_as_input):
|
|
# Heating
|
|
heat_dict = {}
|
|
schedule = None
|
|
if htm["Zone Sensible Heating"].iloc[0, 0] != "None":
|
|
for i in tqdm(range(0, len(htm["Zone Sensible Heating"])), desc="writing conditioning (heating)"):
|
|
key = htm["Zone Sensible Heating"].iloc[i, 0]
|
|
for key_2 in schedules:
|
|
try:
|
|
if "_h_" in key_2 and old_new_names[key_2[-8:].upper()][0] == key:
|
|
schedule = key_2
|
|
break
|
|
except:
|
|
pass
|
|
name = "HEAT_z" + str(htm["Zone Sensible Heating"].iloc[i].name)
|
|
heat_dict[key] = [name, schedule]
|
|
size_factor = htm["Heating Sizing Factor Information"][
|
|
htm["Heating Sizing Factor Information"]["Sizing Factor ID"] == "Global"
|
|
]["Value"].max()
|
|
power = size_factor * (
|
|
float(
|
|
htm["Zone Sensible Heating"].iloc[i, :][
|
|
"User Design Load per Area [W/m2]"
|
|
]
|
|
)
|
|
/ 1000
|
|
* 3600
|
|
) # kJ/h-m2
|
|
# Writes in lines
|
|
heatingNum = checkStr(lines, "H e a t i n g")
|
|
lines.insert(heatingNum + 1, " AREA_RELATED_POWER=1" + "\n")
|
|
lines.insert(heatingNum + 1, " ELPOWERFRAC=0" + "\n")
|
|
lines.insert(heatingNum + 1, " RRAD=0" + "\n")
|
|
lines.insert(heatingNum + 1, " HUMIDITY=0" + "\n")
|
|
lines.insert(heatingNum + 1, "POWER=" + str(power) + "\n")
|
|
if schedule_as_input:
|
|
lines.insert(heatingNum + 1, " ON= INPUT 1*" + schedule + "\n")
|
|
else:
|
|
lines.insert(heatingNum + 1, " ON= SCHEDULE 1*" + schedule + "\n")
|
|
lines.insert(heatingNum + 1, "HEATING " + name + "\n")
|
|
# Cooling
|
|
cool_dict = {}
|
|
schedule = None
|
|
if htm["Zone Sensible Cooling"].iloc[0, 0] != "None":
|
|
for i in tqdm(range(0, len(htm["Zone Sensible Cooling"])), desc="writing conditioning (cooling)"):
|
|
key = htm["Zone Sensible Cooling"].iloc[i, 0]
|
|
for key_2 in schedules:
|
|
try:
|
|
if "_c_" in key_2 and old_new_names[key_2[-8:].upper()][0] == key:
|
|
schedule = key_2
|
|
break
|
|
except:
|
|
pass
|
|
name = "COOL_z" + str(htm["Zone Sensible Cooling"].iloc[i].name)
|
|
cool_dict[key] = [name, schedule]
|
|
size_factor = htm["Cooling Sizing Factor Information"][
|
|
htm["Cooling Sizing Factor Information"]["Sizing Factor ID"] == "Global"
|
|
]["Value"].max()
|
|
power = size_factor * (
|
|
float(
|
|
htm["Zone Sensible Cooling"].iloc[i, :][
|
|
"User Design Load per Area [W/m2]"
|
|
]
|
|
)
|
|
/ 1000
|
|
* 3600
|
|
) # kJ/h-m2
|
|
# Writes in lines
|
|
coolingNum = checkStr(lines, "C o o l i n g")
|
|
lines.insert(coolingNum + 1, " AREA_RELATED_POWER=1" + "\n")
|
|
lines.insert(coolingNum + 1, " ELPOWERFRAC=0" + "\n")
|
|
lines.insert(coolingNum + 1, " HUMIDITY=0" + "\n")
|
|
lines.insert(coolingNum + 1, "POWER=" + str(power) + "\n")
|
|
if schedule_as_input:
|
|
lines.insert(coolingNum + 1, " ON= INPUT 1*" + schedule + "\n")
|
|
else:
|
|
lines.insert(coolingNum + 1, " ON= SCHEDULE 1*" + schedule + "\n")
|
|
lines.insert(coolingNum + 1, "COOLING " + name + "\n")
|
|
|
|
return heat_dict, cool_dict
|
|
|
|
|
|
def _write_gains(equipments, lights, lines, peoples, htm, old_new_names):
|
|
"""Write gains in lines
|
|
|
|
Args:
|
|
equipments (idf_MSequence): IDF object from idf.idfobjects(). List of
|
|
equipments ("ELECTRICEQUIPMENT" in the IDF).
|
|
idf (translater.idfclass.IDF): IDF object
|
|
lights (idf_MSequence): IDF object from idf.idfobjects(). List of lights
|
|
("LIGHTS" in the IDF).
|
|
lines (list): Text to create the T3D file (IDF file to import in
|
|
TRNBuild). To be appended (insert) here
|
|
peoples (idf_MSequence): IDF object from idf.idfobjects()
|
|
"""
|
|
log("Writing gains info from idf file to t3d file...")
|
|
# Get line number where to write
|
|
gainNum = checkStr(lines, "G a i n s")
|
|
# Writing PEOPLE gains infos to lines
|
|
_write_people_gain(gainNum, lines, peoples, htm, old_new_names)
|
|
# Writing LIGHT gains infos to lines
|
|
_write_light_gain(gainNum, lights, lines, htm, old_new_names)
|
|
# Writing EQUIPMENT gains infos to lines
|
|
_write_equipment_gain(equipments, gainNum, lines, htm, old_new_names)
|
|
|
|
|
|
def _write_equipment_gain(equipments, gainNum, lines, htm, old_new_names):
|
|
"""Write equipment gains in lines
|
|
|
|
Args:
|
|
equipments (idf_MSequence): IDF object from idf.idfobjects(). List of
|
|
equipments ("ELECTRICEQUIPMENT" in the IDF).
|
|
gainNum (int): Line number where to write the equipment gains
|
|
lines (list): Text to create the T3D file (IDF file to import in
|
|
TRNBuild). To be appended (insert) here
|
|
"""
|
|
for equipment in tqdm(equipments, desc="writing electric equipment gains"):
|
|
gain = htm["ElectricEquipment Internal Gains Nominal"][
|
|
htm["ElectricEquipment Internal Gains Nominal"]["Name"].str.contains(
|
|
old_new_names[equipment.Name.upper()][0]
|
|
)
|
|
]
|
|
# Write gain name in lines
|
|
lines.insert(gainNum + 1, "GAIN " + equipment.Name + "\n")
|
|
areaMethod = "AREA_RELATED"
|
|
power = gain["Equipment/Floor Area {W/m2}"].values[0] / 1000 * 3600 # kJ/h-m2
|
|
radFract = gain["Fraction Radiant"].values[0]
|
|
lines.insert(
|
|
gainNum + 2,
|
|
" CONVECTIVE="
|
|
+ str(round(power * (1 - radFract), 3))
|
|
+ " : RADIATIVE="
|
|
+ str(round(power * radFract, 3))
|
|
+ " : HUMIDITY=0 : ELPOWERFRAC=1 "
|
|
": " + areaMethod + " : "
|
|
"CATEGORY=EQUIPMENT\n",
|
|
)
|
|
|
|
|
|
def _write_light_gain(gainNum, lights, lines, htm, old_new_names):
|
|
"""Write gain from lights in lines
|
|
|
|
Args:
|
|
gainNum (int): Line number where to write the equipment gains
|
|
lights (idf_MSequence): IDF object from idf.idfobjects(). List of lights
|
|
("LIGHTS" in the IDF).
|
|
lines (list): Text to create the T3D file (IDF file to import in
|
|
TRNBuild). To be appended (insert) here
|
|
"""
|
|
for light in tqdm(lights, desc="writing lighting gains"):
|
|
gain = htm["Lights Internal Gains Nominal"][
|
|
htm["Lights Internal Gains Nominal"]["Name"].str.contains(
|
|
old_new_names[light.Name.upper()][0]
|
|
)
|
|
]
|
|
# Write gain name in lines
|
|
lines.insert(gainNum + 1, "GAIN " + light.Name + "\n")
|
|
areaMethod = "AREA_RELATED"
|
|
power = gain["Lights/Floor Area {W/m2}"].values[0] / 1000 * 3600 # kJ/h-m2
|
|
radFract = gain["Fraction Radiant"].values[0]
|
|
lines.insert(
|
|
gainNum + 2,
|
|
" CONVECTIVE="
|
|
+ str(round(power * (1 - radFract), 3))
|
|
+ " : RADIATIVE="
|
|
+ str(round(power * radFract, 3))
|
|
+ " : HUMIDITY=0 : ELPOWERFRAC=1 "
|
|
": " + areaMethod + " : "
|
|
"CATEGORY=LIGHTS\n",
|
|
)
|
|
|
|
|
|
def _write_people_gain(gainNum, lines, peoples, htm, old_new_names):
|
|
"""
|
|
Args:
|
|
gainNum (int): Line number where to write the equipment gains
|
|
lines (list): Text to create the T3D file (IDF file to import in
|
|
TRNBuild). To be appended (insert) here
|
|
peoples (idf_MSequence): IDF object from idf.idfobjects()
|
|
"""
|
|
for people in tqdm(peoples, desc="writing people gains"):
|
|
gain = htm["People Internal Gains Nominal"][
|
|
htm["People Internal Gains Nominal"]["Name"].str.contains(
|
|
old_new_names[people.Name.upper()][0]
|
|
)
|
|
]
|
|
# Write gain name in lines
|
|
lines.insert(gainNum + 1, "GAIN " + people.Name + "\n")
|
|
areaMethod = "AREA_RELATED"
|
|
power = gain["People/Floor Area {person/m2}"].values[0] * 270 # kJ/h-m2
|
|
radFract = gain["Fraction Radiant"].values[0]
|
|
lines.insert(
|
|
gainNum + 2,
|
|
" CONVECTIVE="
|
|
+ str(round(power * (1 - radFract), 3))
|
|
+ " : RADIATIVE="
|
|
+ str(round(power * radFract, 3))
|
|
+ " : HUMIDITY=0.066 : ELPOWERFRAC=0 "
|
|
": " + areaMethod + " : "
|
|
"CATEGORY=PEOPLE\n",
|
|
)
|
|
|
|
|
|
def _write_materials(lines, materialAirGap, materialNoMass, materials):
|
|
"""Write materials (LAYER in TRNBuild) in lines
|
|
|
|
Args:
|
|
lines (list): Text to create the T3D file (IDF file to import in
|
|
TRNBuild). To be appended (insert) here
|
|
materialAirGap (idf_MSequence): IDF object from idf.idfobjects().
|
|
materialNoMass (idf_MSequence): IDF object from idf.idfobjects().
|
|
materials (idf_MSequence): IDF object from idf.idfobjects(). List of
|
|
materials ("MATERIAL" in the IDF)
|
|
"""
|
|
log("Writing materials (layers) info from idf file to t3d file...")
|
|
# Get line number where to write
|
|
layerNum = checkStr(lines, "L a y e r s")
|
|
listLayerName = []
|
|
# Writing MATERIAL infos to lines
|
|
_write_material(layerNum, lines, listLayerName, materials)
|
|
# Writing MATERIAL:NOMASS infos to lines
|
|
_write_material_nomass(layerNum, lines, listLayerName, materialNoMass)
|
|
# Writing MATERIAL:AIRGAP infos to lines
|
|
_write_material_airgap(layerNum, lines, listLayerName, materialAirGap)
|
|
|
|
|
|
def _write_material_airgap(layerNum, lines, listLayerName, materialAirGap):
|
|
"""
|
|
Args:
|
|
layerNum (int): Line number where to write the material
|
|
lines (list): Text to create the T3D file (IDF file to import in
|
|
TRNBuild). To be appended (insert) here
|
|
listLayerName (list): list of material's names. To be appended when
|
|
materialAirGap (materialAirGap): IDF object from
|
|
"""
|
|
for i in tqdm(range(0, len(materialAirGap)), desc="writing airgap materials"):
|
|
|
|
duplicate = [s for s in listLayerName if s == materialAirGap[i].Name]
|
|
if not duplicate:
|
|
lines.insert(layerNum + 1, "!-LAYER " + materialAirGap[i].Name + "\n")
|
|
listLayerName.append(materialAirGap[i].Name)
|
|
|
|
lines.insert(
|
|
layerNum + 2,
|
|
"!- RESISTANCE="
|
|
+ str(round(materialAirGap[i].Thermal_Resistance / 3.6, 4))
|
|
+ " : PERT= 0 : PENRT= 0\n",
|
|
)
|
|
else:
|
|
continue
|
|
|
|
|
|
def _write_material_nomass(layerNum, lines, listLayerName, materialNoMass):
|
|
"""
|
|
Args:
|
|
layerNum (int): Line number where to write the material
|
|
lines (list): Text to create the T3D file (IDF file to import in
|
|
TRNBuild). To be appended (insert) here
|
|
listLayerName (list): list of material's names. To be appended when
|
|
materialNoMass (idf_MSequence): IDF object from idf.idfobjects().
|
|
"""
|
|
for i in tqdm(range(0, len(materialNoMass)), desc="writing nomass materials"):
|
|
|
|
duplicate = [s for s in listLayerName if s == materialNoMass[i].Name]
|
|
if not duplicate:
|
|
lines.insert(layerNum + 1, "!-LAYER " + materialNoMass[i].Name + "\n")
|
|
listLayerName.append(materialNoMass[i].Name)
|
|
|
|
lines.insert(
|
|
layerNum + 2,
|
|
"!- RESISTANCE="
|
|
+ str(round(materialNoMass[i].Thermal_Resistance / 3.6, 4))
|
|
+ " : PERT= 0 : PENRT= 0\n",
|
|
)
|
|
else:
|
|
continue
|
|
|
|
|
|
def _write_material(layerNum, lines, listLayerName, materials):
|
|
"""
|
|
Args:
|
|
layerNum (int): Line number where to write the material
|
|
lines (list): Text to create the T3D file (IDF file to import in
|
|
TRNBuild). To be appended (insert) here
|
|
listLayerName (list): list of material's names. To be appended when
|
|
materials (idf_MSequence): IDF object from idf.idfobjects(). List of
|
|
materials ("MATERIAL" in the IDF)
|
|
"""
|
|
for i in tqdm(range(0, len(materials)), desc="writing materials"):
|
|
lines.insert(layerNum + 1, "!-LAYER " + materials[i].Name + "\n")
|
|
listLayerName.append(materials[i].Name)
|
|
|
|
lines.insert(
|
|
layerNum + 2,
|
|
"!- CONDUCTIVITY="
|
|
+ str(round(materials[i].Conductivity * 3.6, 4))
|
|
+ " : CAPACITY= "
|
|
+ str(round(materials[i].Specific_Heat / 1000, 4))
|
|
+ " : DENSITY= "
|
|
+ str(round(materials[i].Density, 4))
|
|
+ " : PERT= 0 : PENRT= 0\n",
|
|
)
|
|
|
|
|
|
def _write_constructions_end(constr_list, idf, lines):
|
|
"""Write constructions at the end of lines (IDF format)
|
|
|
|
Args:
|
|
constr_list (list): list of construction names to be written
|
|
idf (translater.idfclass.IDF): IDF object
|
|
lines (list): Text to create the T3D file (IDF file to import in
|
|
TRNBuild). To be appended (insert) here
|
|
"""
|
|
# Get line number where to write
|
|
constructionEndNum = checkStr(lines, "ALL OBJECTS IN CLASS: CONSTRUCTION")
|
|
# Writing CONSTRUCTION infos to lines
|
|
for constr in tqdm(constr_list, desc="writing constructions at the end of file"):
|
|
construction = idf.getobject("CONSTRUCTION", constr)
|
|
lines.insert(constructionEndNum, construction)
|
|
|
|
|
|
def _write_constructions(constr_list, idf, lines, mat_name, materials):
|
|
"""Write constructions in lines (TRNBuild format)
|
|
|
|
Args:
|
|
constr_list (list): list of construction names to be written
|
|
idf (translater.idfclass.IDF): IDF object
|
|
lines (list): Text to create the T3D file (IDF file to import in
|
|
TRNBuild). To be appended (insert) here
|
|
mat_name (list): list of material names to be written
|
|
materials (idf_MSequence): IDF object from idf.idfobjects(). List of
|
|
materials ("MATERIAL" in the IDF)
|
|
"""
|
|
log("Writing constructions info from idf file to t3d file...")
|
|
# Get line number where to write
|
|
constructionNum = checkStr(lines, "C O N S T R U C T I O N")
|
|
# Writing CONSTRUCTION in lines
|
|
for constr in tqdm(constr_list, desc="writing constructions"):
|
|
construction = idf.getobject("CONSTRUCTION", constr)
|
|
lines.insert(constructionNum + 1, "!-CONSTRUCTION " + construction.Name + "\n")
|
|
|
|
# Create lists to append with layers and thickness of construction
|
|
layerList = []
|
|
thickList = []
|
|
|
|
for j in range(2, len(construction.fieldvalues)):
|
|
|
|
if construction.fieldvalues[j] not in mat_name:
|
|
|
|
indiceMat = [
|
|
k
|
|
for k, s in enumerate(materials)
|
|
if construction.fieldvalues[j] == s.Name
|
|
]
|
|
|
|
if not indiceMat:
|
|
thickList.append(0.0)
|
|
else:
|
|
thickList.append(round(materials[indiceMat[0]].Thickness, 4))
|
|
|
|
layerList.append(construction.fieldvalues[j])
|
|
|
|
else:
|
|
continue
|
|
|
|
# Writes layers and thicknesses
|
|
lines.insert(
|
|
constructionNum + 2,
|
|
"!- LAYERS = " + " ".join(str(item) for item in layerList[::-1]) + "\n",
|
|
)
|
|
lines.insert(
|
|
constructionNum + 3,
|
|
"!- THICKNESS= " + " ".join(str(item) for item in thickList[::-1]) + "\n",
|
|
)
|
|
|
|
# Writes ABS-FRONT and ABS-BACK
|
|
sol_abs_front = get_sol_abs(idf, layerList[0])
|
|
sol_abs_back = get_sol_abs(idf, layerList[-1])
|
|
lines.insert(
|
|
constructionNum + 4,
|
|
"!- ABS-FRONT= "
|
|
+ str(sol_abs_front)
|
|
+ " : ABS-BACK= "
|
|
+ str(sol_abs_back)
|
|
+ "\n",
|
|
)
|
|
lines.insert(constructionNum + 5, "!- EPS-FRONT= 0.9 : EPS-BACK= 0.9\n")
|
|
|
|
# Writes HBACK
|
|
try:
|
|
condition = (
|
|
construction.getreferingobjs()[0].Outside_Boundary_Condition.lower()
|
|
== "ground"
|
|
)
|
|
except:
|
|
condition = False
|
|
if condition:
|
|
lines.insert(constructionNum + 6, "!- HFRONT = 11 : HBACK= 0.0005\n")
|
|
else:
|
|
lines.insert(constructionNum + 6, "!- HFRONT = 11 : HBACK= 64\n")
|
|
|
|
|
|
def get_sol_abs(idf, layer):
|
|
mat_ = idf.getobject("MATERIAL", layer)
|
|
if mat_:
|
|
sol_abs = mat_.Solar_Absorptance
|
|
else:
|
|
mat_ = idf.getobject("MATERIAL:NOMASS", layer)
|
|
if mat_:
|
|
sol_abs = mat_.Solar_Absorptance
|
|
else:
|
|
mat_ = idf.getobject("MATERIAL:AIRGAP", layer)
|
|
sol_abs = mat_.Solar_Absorptance
|
|
return sol_abs
|
|
|
|
|
|
def _get_ground_vertex(buildingSurfs):
|
|
"""Find the normal vertex of ground surface
|
|
|
|
Args:
|
|
buildingSurfs (idf_MSequence): IDF object from idf.idfobjects(). List of
|
|
building surfaces ("BUILDINGSURFACE:DETAILED" in the IDF).
|
|
"""
|
|
ground_surfs = [
|
|
buildingSurf
|
|
for buildingSurf in buildingSurfs
|
|
if buildingSurf.Outside_Boundary_Condition.lower() == "ground"
|
|
]
|
|
if ground_surfs:
|
|
ground = ground_surfs[0].coords
|
|
else:
|
|
ground = [(45, 28, 0), (45, 4, 0), (4, 4, 0), (4, 28, 0)]
|
|
# Polygon from vector's ground surface
|
|
poly_ground = Polygon3D(ground)
|
|
# Normal vectors of the polygon
|
|
n_ground = poly_ground.normal_vector
|
|
return n_ground
|
|
|
|
|
|
def _is_coordSys_world(coordSys, zones):
|
|
"""
|
|
Args:
|
|
coordSys (str): If already assigned ('Relative' or 'Absolute), function
|
|
returns the value
|
|
zones (idf_MSequence): IDF object from idf.idfobjects(). List of zones
|
|
("ZONES" in the IDF). Zones object to iterate over, to determine if
|
|
the coordinate system is 'World'
|
|
"""
|
|
X_zones = []
|
|
Y_zones = []
|
|
Z_zones = []
|
|
# Store all zones coordinates in lists
|
|
for zone in zones:
|
|
x, y, z = zone_origin(zone)
|
|
X_zones.append(x)
|
|
Y_zones.append(y)
|
|
Z_zones.append(z)
|
|
# If 2 zones have same coords and are equal to 0 -> coordSys = "World"
|
|
if (
|
|
X_zones == Y_zones
|
|
and X_zones == Z_zones
|
|
and Y_zones == Z_zones
|
|
and X_zones[0] == 0
|
|
and Y_zones[0] == 0
|
|
and Z_zones[0] == 0
|
|
):
|
|
coordSys = "World"
|
|
return coordSys
|
|
|
|
|
|
def _write_location_geomrules(globGeomRules, lines, locations):
|
|
"""
|
|
Args:
|
|
globGeomRules (idf_MSequence): IDF object from idf.idfobjects(). List of
|
|
global geometry rules ("GLOBALGEOMETRYRULES" in the IDF). Normally
|
|
there should be only one global geometry rules.
|
|
lines (list): Text to create the T3D file (IDF file to import in
|
|
TRNBuild). To be appended (insert) here
|
|
locations (idf_MSequence): IDF object from idf.idfobjects(). List of the
|
|
building locations ("SITE:LOCATION" in the IDF). Normally there
|
|
should be only one location.
|
|
"""
|
|
# Get line number where to write
|
|
log("Writing location info from idf file to t3d file...")
|
|
locationNum = checkStr(lines, "ALL OBJECTS IN CLASS: LOCATION")
|
|
# Writing GLOBALGEOMETRYRULES infos to lines
|
|
for globGeomRule in globGeomRules:
|
|
# Change Geometric rules from Relative to Absolute
|
|
coordSys = "Absolute"
|
|
if globGeomRule.Coordinate_System == "Relative":
|
|
coordSys = "Relative"
|
|
globGeomRule.Coordinate_System = "Absolute"
|
|
|
|
if globGeomRule.Daylighting_Reference_Point_Coordinate_System == "Relative":
|
|
globGeomRule.Daylighting_Reference_Point_Coordinate_System = "Absolute"
|
|
|
|
if globGeomRule.Rectangular_Surface_Coordinate_System == "Relative":
|
|
globGeomRule.Rectangular_Surface_Coordinate_System = "Absolute"
|
|
|
|
lines.insert(locationNum, globGeomRule)
|
|
# Writing LOCATION infos to lines
|
|
for location in locations:
|
|
lines.insert(locationNum, location)
|
|
return coordSys
|
|
|
|
|
|
def _write_building(buildings, lines):
|
|
"""
|
|
Args:
|
|
buildings (idf_MSequence): IDF object from idf.idfobjects()
|
|
lines (list): Text to create the T3D file (IDF file to import in
|
|
TRNBuild). To be appended (insert) here
|
|
"""
|
|
# Get line number where to write
|
|
log("Writing building info from idf file to t3d file...")
|
|
buildingNum = checkStr(lines, "ALL OBJECTS IN CLASS: BUILDING")
|
|
# Writing BUILDING infos to lines
|
|
for building in buildings:
|
|
lines.insert(buildingNum, building)
|
|
|
|
|
|
def _write_version(lines, versions):
|
|
"""
|
|
Args:
|
|
lines (list): Text to create the T3D file (IDF file to import in
|
|
TRNBuild). To be appended (insert) here
|
|
versions (idf_MSequence): IDF object from idf.idfobjects(). List of the
|
|
IDF file versions ("VERSION" in the IDF). Normally there should be
|
|
only one version.
|
|
"""
|
|
# Get line number where to write
|
|
log("Writing data from idf file to t3d file...")
|
|
versionNum = checkStr(lines, "ALL OBJECTS IN CLASS: VERSION")
|
|
# Writing VERSION infos to lines
|
|
for i in range(0, len(versions)):
|
|
lines.insert(
|
|
versionNum,
|
|
",".join(str(item) for item in versions[i].fieldvalues) + ";" + "\n",
|
|
)
|