Question-and-Answer Resource for the Building Energy Modeling Community
Get started with the Help page
Ask Your Question

Using Eppy to extract fenestration from IDF and replace with WWR-derived formulas

asked 2016-08-02 10:41:06 -0500

andrea.botti gravatar image

updated 2016-10-11 08:19:31 -0500

Hello, I am trying to write a piece of Eppy code to:

  1. open an IDF (created with designbuilder or openstudio)
  2. list all windows in the idf file (under FenestrationSurface:Detailed)
  3. extract the Building Surface Name parameter from these windows
  4. create “Window” objects for each surface in the list generated by the previous step
  5. apply fields Construction_Name and Building_Surface_Name to the newly created windows
  6. assign:

    • Starting_X_Coordinate (x0) = host wall's XCOORDINATE (X0) + WWR formula [could it be x0 = (X2-X0)/2*sqrt(WWR) ?]

    • Starting_Z_Coordinate (z0) = host wall's ZCOORDINATE (Z0) + WWR formula

    • window length (x2-x0) = sqrt(WWR)*(X2-X0)

    • window height (z2-z0) = sqrt(WWR)*(Z2-Z0)

There is some guidance on that on the jeplus manual

My code looks show a tentative attempt to create a loop but I could not get far. Can anyone help?

old_fen = idf1.idfobjects['FENESTRATIONSURFACE:DETAILED']
new_win = idf1.idfobjects['WINDOW']

#for w in new_win:
    #print w.objls      # useful to understand what IDF fields can be called

for w in old_fen:
    #print w.objls
    old_fen_name = w.Name
    new_win1 = idf1.newidfobject('window'.upper())      # the key for the object type has to be in upper case
    new_win1.Construction_Name = w.Construction_Name
    new_win1.Building_Surface_Name = w.Building_Surface_Name
    print new_win1.Building_Surface_Name
    # find "new_win1.Building_Surface_Name" (the surface hosting the window) among surfaces
    # gather XCOORDINATE and ZCOORDINATE of those host surfaces
    # new_win1.Starting_X_Coordinate = XCOORDINATE (host)
    # new_win1.Starting_Z_Coordinate = ZCOORDINATE (host)

EDIT: Combining the script suggested by Jamie Bull:

for f in old_fen:
    base_wall = idf1.getobject('BUILDINGSURFACE:DETAILED', f.Building_Surface_Name)
    w = idf1.newidfobject('WINDOW', f.Name,
        Starting_Z_Coordinate="#[%s * #[1 - #[@@wwr@@ / 2.0]]]" % base_wall.height,
        Height="#[%s * @@wwr@@]" % base_wall.height,

with another, aimed at replacing a given ceiling height with a jeplus-compatible label @@ch@@, (also suggested by Jamie Bull) :

ceiling_height = '@@ch@@'
walls_and_roofs = [s for s in surfaces if s.Surface_Type in ['Wall', 'Roof']]
for s in surfaces:
    max_z = max(pt[2] for pt in s.coords)
    for field in s.objls:  # or "for field in s.objls:" if using version <= 0.5.2
        if 'ZCOORDINATE' in field.upper() and s[field] == max_z:
            s[field] = ceiling_height

Then the Starting_Z_Coordinate and Height of the parametric windows refer to the original wall heigth - which does not account for the @@ch@@ parameter. This results in windows created with the wwr parameter being too big and going outsite the existing walls. Any idea on how to fix this Jamie? :P

edit retag flag offensive close merge delete


Just a note about a different option. The OpenStudio API (documentation) can be used like eppy to manipulate an IDF (workspace) using Ruby. The OpenStudio Measure Writing Guide has examples for interacting with the IDF through formal EnergyPlus Measures, which can also be run from a command line/terminal.

MatthewSteen gravatar imageMatthewSteen ( 2016-08-02 11:36:04 -0500 )edit

2 Answers

Sort by » oldest newest most voted

answered 2016-08-03 10:05:41 -0500

updated 2018-01-13 07:50:42 -0500

In your pseudocode it looks like you've missed the fact that WINDOW objects' X and Z coordinates are defined relative to the building surface they are based on. That makes the calculations for a specified WWR ratio simpler as you can set the window to the full width of the wall, and the height to WWR * wall height. Usefully, eppy defines a height and a width attribute for surfaces.

Putting it together for a new WINDOW based on wall, Starting X Coordinate is 0, Length is wall.width. Starting Z Coordinate is a little trickier. I use wall.height * 1-(wwr / 2.0), and finally, Height is wall.height * wwr.

Assuming you only have a single FENESTRATIONSURFACE:DETAILED on each surface, you can use this code to create a strip window vertically-centred on the wall:

wwr = 0.5
old_fen = idf.idfobjects['FENESTRATIONSURFACE:DETAILED']

for f in old_fen:
    base_wall = idf.getobject(
    w = idf.newidfobject(
        'WINDOW', f.Name,
        Starting_Z_Coordinate=base_wall.height * 1 - (wwr / 2.0),
        Height=base_wall.height * wwr,

Or if you'd like to have WWR as a jEPlus @@variable@@, you could change two lines when creating the new WINDOW to create EPMacro statements:

    w = idf.newidfobject(
        Starting_Z_Coordinate="#[%s * #[1 - #[@@wwr@@ / 2.0]]]" % base_wall.height,
        Height="#[%s * @@wwr@@]" % base_wall.height,

Using the 1ZoneUncontrolled_win_1.idf example file, this outputs:

    Zn001:Wall001:Win001,     !- Name
    DoubleClear,              !- Construction Name
    Zn001:Wall001,            !- Building Surface Name
    ,                         !- Shading Control Name
    ,                         !- Frame and Divider Name
    1.0,                      !- Multiplier
    0,                        !- Starting X Coordinate
    #[4.572 * #[1 - #[@@wwr@@ / 2.0]]],    !- Starting Z Coordinate
    15.24,                    !- Length
    #[4.572 * @@wwr@@];       !- Height

edited to add

I thought I'd update this to add another option. I've just released an alpha version of geomeppy, a package that builds on Eppy and adds functionality to manipulate EnergyPlus geometry in IDF files. (PyPI, GitHub)

To install geomeppy just do pip install geomeppy (Python 2.7 only at the moment but aiming to have Python 3.5 support very soon). This now works for both Python 2 and 3.

Using geomeppy to set window-to-wall ratio is then as easy as:

from geomeppy import IDF  # this keeps all the same functions as Eppy's IDF

# the usual code to set the IDD and read/create an IDF here

IDF.set_wwr() # set a WWR of 20% (the default) for all external walls

IDF.set_wwr(wwr=0.25) # set a WWR of 25% for all external walls

IDF.set_wwr(wwr_map={90: 0}) # set no windows on all external walls with azimuth of 90, and WWR of 20% on other walls

IDF.set_wwr(wwr=0, wwr_map={90: 0.3}) # set a WWR of 30% for all external walls with azimuth of 90, and no windows on other walls

If wwr_map is passed, it overrides any value passed to wwr, including the default of 0.2. However it only overrides it on walls which have an azimuth in the wwr_map. Any ... (more)

edit flag offensive delete link more


Jamie, once again thanks very much for your help.

I do get the following error though:

if abs(poly[num][2] - poly[0][2]) < abs(poly[1][2] - poly[0][2]):
TypeError: unsupported operand type(s) for -: 'str' and 'str

Any idea why?

andrea.botti gravatar imageandrea.botti ( 2016-08-03 11:23:29 -0500 )edit

Hi Jamie, excellent demonstration of Eppy/Python and how they can work with EP-Macro and jEPlus! Inspiring!

Yi Zhang gravatar imageYi Zhang ( 2016-08-03 11:47:19 -0500 )edit

@andrea.botti for some reason it looks like strings are being passed into the surface.height or surface.width fields. I can't reproduce that error though. Do you perhaps have some @@variables@@ already in the surfaces that you're then trying to do calculations on?

Jamie Bull gravatar imageJamie Bull ( 2016-08-03 13:12:18 -0500 )edit

@Yi Zhang thanks!

Jamie Bull gravatar imageJamie Bull ( 2016-08-03 13:12:34 -0500 )edit

Is Eppy for CPython or for Iron Python? Cannot install it for Iron Python 2.7...

Dinosaver gravatar imageDinosaver ( 2016-08-05 04:01:46 -0500 )edit

answered 2016-08-03 12:09:17 -0500

Yi Zhang gravatar image

Hi Andrea,

We have added Python support in the model preparation stage in jEPlus. Ivan has also made an example script using Eppy to apply WWR to each and every window in the external walls. Below is the script.

You can find further details on how to use Python pre-processing in jEPlus, and download the new version, here:

# python script for pre-processing: This script takes WWR value from the argument list and modifies all the windows in the IDF model
# This script is designed to work with jEPlus v1.6.4 and later, with the @python? syntax
# @author: Dr Ivan Korolija []
# Arguments:
#   sys.argv[1]  -  project's base folder where the project files are located
#   sys.argv[2]  -  folder of the current case where in.idf is located
#   sys.argv[3]  -  Other arguments specified in the parameter definition. They are passed in as a ',' delimited string
#   sys.argv[4]  -  folder of the binary files of the simulation program, e.g. the location of Energy+.idd

import os
from eppy.modeleditor import IDF
import sys
import math

# function for calculating wall width and height
def wall_width_height(coordinates):
    ulc = coordinates[0]  # upper left corner coordinates
    blc = coordinates[1]  # bottom left corner coordinates
    brc = coordinates[2]  # bottom right corner coordinates

    # calculate wall width and height by using the Euclidean distance
    w = math.sqrt(math.pow(
        (brc[0] - blc[0]), 2) + math.pow((brc[1] - blc[1]), 2) +
        math.pow((brc[2] - blc[2]), 2))
    h = math.sqrt(math.pow(
        (ulc[0] - blc[0]), 2) + math.pow((ulc[1] - blc[1]), 2) +
        math.pow((ulc[2] - blc[2]), 2))
    return w, h  # return wall width and height
# End 

# path to E+ idd file (required by eppy)
iddfile = os.path.join(sys.argv[4], 'Energy+.idd')

# path to energyplus input file within each simulated folder
idf = os.path.join(sys.argv[2], 'in.idf')

# glazing ratio convert to integer
gr = int(sys.argv[3])

idf = IDF(idf)  # read idf file to eppy
# extract window and wall objects
window_objects = idf.idfobjects['Window'.upper()]
wall_object = idf.idfobjects['BuildingSurface:Detailed'.upper()]

# loop through window objects
for window in window_objects:
    # find the base surface for the window
    win_base_surface = window.Building_Surface_Name

    # loop through the wall objects
    for wall in wall_object:
        # when wall name equals to the window base surface name extract coords
        if wall.Name == win_base_surface:
            coord = wall.coords
            # calculate wall width and height
            w, h = wall_width_height(coord)
            # calculate wall length and height as a function of glazing ratio
            wl = w * math.sqrt(gr / 100)  # window length
            wh = h * math.sqrt(gr / 100)  # window height
            # starting X/Z coordinates relative to the wall bottom left corner
            x = (w - wl) / 2
            z = (h - wh) / 2

    # coords and window H/L converted into strings and applied to window object
    window.Starting_X_Coordinate = '%.2f' % x
    window.Starting_Z_Coordinate = '%.2f' % z
    window.Length = '%.2f' % wl
    window.Height = '%.2f' % wh

# save the updated idf file

# Done
edit flag offensive delete link more

Your Answer

Please start posting anonymously - your entry will be published after you log in or create a new account.

Add Answer


Question Tools

1 follower


Asked: 2016-08-02 10:41:06 -0500

Seen: 517 times

Last updated: Jan 13