import contextlib
import numpy as np
import config
from eppy.modeleditor import IDF
import json
from functools import partial
import warnings
from errors import ModeError
"""This file is a collection of functions to interact with eppy.
They could do with some sorting and refactoring."""
def get_mode(building):
if isinstance(building, IDF):
return 'idf'
if isinstance(building, dict):
return 'json'
raise ModeError(message=f'Cannot find a valid mode for {building}')
[docs]def get_idf(idf_file: str = config.files.get('idf'),
idd_file: str = config.files.get('idd'),
output_directory=config.out_dir) -> IDF:
"""get_idf(idf_file, idd_file, output_directory) -> IDF
Uses eppy to read an idf file and generate the corresponding idf object
:param str idf_file: Input IDF file name
:param str idd_file: Input IDD file name
:param str output_directory: Directory for the IDF object
:return: The specified IDF object
"""
# Trying to change the idd file inside a program will cause an error
IDF.setiddname(idd_file)
# TODO: Fix this rather than hiding it.
# calling IDF causes a warning to appear, currently redirect_stdout hides this.
with contextlib.redirect_stdout(None):
idf = IDF(idf_file)
# override the output location so I stop messing up
idf.run = partial(idf.run, output_directory=output_directory)
return idf
def get_building(building=None,
data_dict=None,
output_directory=config.out_dir,
mode=None):
if mode is None and building is None:
building = config.files.get('building')
mode = config.energy_plus_mode
if mode is None:
ending = building.split('.')[-1]
if ending == 'idf':
mode = 'idf'
elif ending == 'epJSON':
mode = 'json'
else:
warnings.warn(f'expected "idf" or "epJSON" file. building has extension {ending}')
if building is None:
building = config.files[mode]
if mode == 'idf':
data_dict = data_dict or config.files['idd']
return get_idf(idf_file=building, idd_file=data_dict, output_directory=output_directory)
elif mode == 'json':
with open(building) as f:
return json.load(f)
ModeError(mode)
def get_windows(building):
mode = get_mode(building)
if mode == 'idf':
return ((window.Name, window) for window in building.idfobjects['FENESTRATIONSURFACE:DETAILED'])
elif mode == 'json':
return building['FenestrationSurface:Detailed'].items()
else:
raise ModeError(mode)
[docs]def wwr_all(building, wwr: float) -> None:
"""wwr_all(building, wwr)
Sets the wwr for all walls that have a window.
Will malfunction if there are multiple windows on one wall
:param building: The building to apply the changes to
:param float wwr: The desired wwr
"""
mode = get_mode(building)
windows = get_windows(building)
for window_name, window in windows:
wwr_single(window, wwr, building, mode)
[docs]def set_vertex(idfObj, vertexNum: int, x: float = 0, y: float = 0, z: float = 0):
"""set_vertex(idfObj, vertexNum, x = 0, y = 0, z = 0)
Sets a single vertex of the passed idfObject (idfObj) to the specified x,y and z coordinates.
:param idfObj: The idfObject to be used
:param float x, y, z: The position of the vertex
"""
for val, name in zip((x, y, z), 'XYZ'):
idfObj['Vertex_{}_{}coordinate'.format(vertexNum, name)] = round(val, 2)
[docs]def one_window(building):
"""Removes some windows so that each wall has no more than one
:param building: The building to apply the changes to
"""
mode = get_mode(building)
walls = set()
toRemove = []
windows = get_windows(building)
for window_name, window in windows:
if mode == 'idf':
wall_name = window.Building_Surface_Name
elif mode == 'json':
wall_name = window['building_surface_name']
else:
raise ModeError(mode)
if wall_name in walls:
toRemove.append((window_name, window))
else:
walls.add(wall_name)
if mode == 'idf':
for window_name, window in toRemove:
building.idfobjects['FENESTRATIONSURFACE:DETAILED'].remove(window)
elif mode == 'json':
for window_name, window in toRemove:
building['FenestrationSurface:Detailed'].pop(window_name)
[docs]def wwr_single(window, wwr: float, building, mode):
"""wwr_single(window, wwr, building, mode)
Sets `window` to have the window to wall ratio specified by `wwr`
:param window: The window to apply the changes to
:param float wwr: The desired wwr
:param building: The building to apply the changes to
:param str mode: Whether to convert to idf or json formating
"""
# will not work for some orientations
# multiple windows on a single wall will break this
if mode == 'idf':
def coordinates(ax):
return [window[f'Vertex_{n}_{ax.upper()}coordinate'] for n in range(1, 5)]
wall = building.getobject('BUILDINGSURFACE:DETAILED', window.Building_Surface_Name)
elif mode == 'json':
def coordinates(ax):
return [window[f'vertex_{n}_{ax.lower()}_coordinate'] for n in range(1, 5)]
wall = building['BuildingSurface:Detailed'][window['building_surface_name']]
else:
raise ModeError(mode)
xs = coordinates('X')
ys = coordinates('Y')
zs = coordinates('Z')
if max(ys) == min(ys):
axis = 'x'
axs = xs
elif max(xs) == min(xs):
axis = 'y'
axs = ys
else:
raise ValueError('The window is not aligned with the x or y axes')
width = max(axs) - min(axs)
scale_factor = np.sqrt(wwr)
new_width = width * scale_factor
height = max(zs) - min(zs)
new_height = height * scale_factor
startW = (width - new_width) / 2
endW = startW + new_width
startH = (height - new_height) / 2
endH = startH + new_height
# Maintains vertex order by mimicking the current order
s = [0] * 4
for vertex in range(0, 4):
if zs[vertex] == max(zs):
# vertex on the top
if axs[vertex] == max(axs):
# TOP RIGHT
s[0] += 1
set_vertex(window, vertex + 1, z=endH, **{axis: endW})
else:
# TOP LEFT
s[1] += 1
set_vertex(window, vertex + 1, z=endH, **{axis: startW})
else:
if axs[vertex] == max(axs):
# BOTTOM RIGHT
s[2] += 1
set_vertex(window, vertex + 1, z=startH, **{axis: endW})
else:
# BOTTOM LEFT
s[3] += 1
set_vertex(window, vertex + 1, z=startH, **{axis: startW})
assert s == [1] * 4, ('verticesS are wrong:', s)