Authors: Santosh Philip, Leora Tanjuatco
Eppy is a scripting language for E+ idf files, and E+ output files. Eppy is written in the programming language Python. As a result it takes full advantage of the rich data structure and idioms that are avaliable in python. You can programmatically navigate, search, and modify E+ idf files using eppy. The power of using a scripting language allows you to do the following:
So what does this matter? Here are some of the things you can do with eppy:
Here is a short IDF file that I’ll be using as an example to start us off ::
To use eppy to look at this model, we have to run a little code first:
In [1]:
# you would normaly install eppy by doing
# python setup.py install
# or
# pip install eppy
# or
# easy_install eppy
# if you have not done so, uncomment the following three lines
import sys
# pathnameto_eppy = 'c:/eppy'
pathnameto_eppy = '../'
sys.path.append(pathnameto_eppy)
from eppy import modeleditor
from eppy.modeleditor import IDF
iddfile = "../eppy/resources/iddfiles/Energy+V7_2_0.idd"
fname1 = "../eppy/resources/idffiles/V_7_2/smallfile.idf"
In [2]:
IDF.setiddname(iddfile)
idf1 = IDF(fname1)
idf1 now holds all the data to your in you idf file.
Now that the behind-the-scenes work is done, we can print this file.
In [3]:
idf1.printidf()
Looks like the same file as before, except that all the comments are slightly different.
As you can see, this file has four objects:
So, let us look take a closer look at the BUILDING object. We can do this using this command::
In [4]:
print idf1.idfobjects['BUILDING'] # put the name of the object you'd like to look at in brackets
We can also zoom in on the object and look just at its individual parts.
For example, let us look at the name of the building.
To do this, we have to do some more behind-the-scenes work, which we'll explain later.
In [5]:
building = idf1.idfobjects['BUILDING'][0]
Now we can do this:
In [6]:
print building.Name
Now that we've isolated the building name, we can change it.
In [7]:
building.Name = "Empire State Building"
In [8]:
print building.Name
Did this actually change the name in the model ? Let us print the entire model and see.
In [9]:
idf1.printidf()
Yes! It did. So now you have a taste of what eppy can do. Let's get started!
That was just a quick example -- we were showing off. Let's look a little closer.
As you might have guessed, changing an IDF field follows this structure::
Plugging the object name (building), the field name (Name) and our new field name ("Empire State Building") into this command gave us this:
In [10]:
building.Name = "Empire State Building"
In [11]:
import eppy
# import eppy.ex_inits
# reload(eppy.ex_inits)
import eppy.ex_inits
But how did we know that "Name" is one of the fields in the object "building"?
Are there other fields?
What are they called?
Let's take a look at the IDF editor:
In [12]:
from eppy import ex_inits #no need to know this code, it just shows the image below
for_images = ex_inits
for_images.display_png(for_images.idfeditor)
In the IDF Editor, the building object is selected.
We can see all the fields of the object "BUILDING".
They are:
Let us try to access the other fields.
In [13]:
print building.Terrain
How about the field "North Axis" ?
It is not a single word, but two words.
In a programming language, a variable has to be a single word without any spaces.
To solve this problem, put an underscore where there is a space.
So "North Axis" becomes "North_Axis".
In [14]:
print building.North_Axis
Now we can do:
In [15]:
print building.Name
print building.North_Axis
print building.Terrain
print building.Loads_Convergence_Tolerance_Value
print building.Temperature_Convergence_Tolerance_Value
print building.Solar_Distribution
print building.Maximum_Number_of_Warmup_Days
print building.Minimum_Number_of_Warmup_Days
Where else can we find the field names?
The IDF Editor saves the idf file with the field name commented next to field.
Eppy also does this.
Let us take a look at the "BUILDING" object in the text file that the IDF Editor saves ::
This a good place to find the field names too.
It is easy to copy and paste from here. You can't do that from the IDF Editor.
We know that in an E+ model, there will be only ONE "BUILDING" object. This will be the first and only item in the list "buildings".
But E+ models are made up of objects such as "BUILDING", "SITE:LOCATION", "ZONE", "PEOPLE", "LIGHTS". There can be a number of "ZONE" objects, a number of "PEOPLE" objects and a number of "LIGHTS" objects.
So how do you know if you're looking at the first "ZONE" object or the second one? Or the tenth one? To answer this, we need to learn about how lists work in python.
Eppy holds these objects in a python structure called list. Let us take a look at how lists work in python.
In [16]:
fruits = ["apple", "orange", "bannana"]
# fruits is a list with three items in it.
To get the first item in fruits we say:
In [17]:
fruits[0]
Out[17]:
Why "0" ?
Because, unlike us, python starts counting from zero in a list. So, to get the third item in the list we'd need to input 2, like this:
In [18]:
print fruits[2]
But calling the first fruit "fruit[0]" is rather cumbersome. Why don't we call it firstfruit?
In [19]:
firstfruit = fruits[0]
print firstfruit
We also can say
In [20]:
goodfruit = fruits[0]
redfruit = fruits[0]
print firstfruit
print goodfruit
print redfruit
print fruits[0]
As you see, we can call that item in the list whatever we want.
To know how many items are in a list, we ask for the length of the list.
The function 'len' will do this for us.
In [21]:
print len(fruits)
There are 3 fruits in the list.
This is easy:
In [22]:
idf1.save()
If you'd like to do a "Save as..." use this:
In [23]:
idf1.saveas('something.idf')
Let us open a small idf file that has only "CONSTRUCTION" and "MATERIAL" objects in it. You can go into "../idffiles/V_7_2/constructions.idf" and take a look at the file. We are not printing it here because it is too big.
So let us open it using the idfreader -
In [24]:
from eppy import modeleditor
from eppy.modeleditor import IDF
iddfile = "../eppy/resources/iddfiles/Energy+V7_2_0.idd"
try:
IDF.setiddname(iddfile)
except modeleditor.IDDAlreadySetError as e:
pass
fname1 = "../eppy/resources/idffiles/V_7_2/constructions.idf"
idf1 = IDF(fname1)
Let us print all the "MATERIAL" objects in this model.
In [25]:
materials = idf1.idfobjects["MATERIAL"]
print materials
As you can see, there are many material objects in this idf file.
The variable "materials" now contains a list of "MATERIAL" objects.
You already know a little about lists, so let us take a look at the items in this list.
In [26]:
firstmaterial = materials[0]
secondmaterial = materials[1]
In [27]:
print firstmaterial
Let us print secondmaterial
In [28]:
print secondmaterial
This is awesome!! Why?
To understand what you can do with your objects organized as lists, you'll have to learn a little more about lists.
You should remember that you can access any item in a list by passing in its index.
The tricky part is that python starts counting at 0, so you need to input 0 in order to get the first item in a list.
Following the same logic, you need to input 3 in order to get the fourth item on the list. Like so:
In [29]:
bad_architects = ["Donald Trump", "Mick Jagger",
"Steve Jobs", "Lady Gaga", "Santa Clause"]
print bad_architects[3]
But there's another way to access items in a list. If you input -1, it will return the last item. -2 will give you the second-to-last item, etc.
In [30]:
print bad_architects[-1]
print bad_architects[-2]
You can also get more than one item in a list:
In [31]:
print bad_architects[1:3] # slices at 1 and 3
How do I make sense of this?
To understand this you need to see the list in the following manner::
The slice operation bad_architects[1:3] slices right where the numbers are.
Does that make sense?
Let us try a few other slices:
In [32]:
print bad_architects[2:-1] # slices at 2 and -1
print bad_architects[-3:-1] # slices at -3 and -1
You can also slice in the following way:
In [33]:
print bad_architects[3:]
print bad_architects[:2]
print bad_architects[-3:]
print bad_architects[:-2]
I'll let you figure that out on your own.
This is simple: the append function adds an item to the end of the list.
The following command will add 'something' to the end of the list called listname::
In [34]:
bad_architects.append("First-year students")
print bad_architects
There are two ways to do this, based on the information you have. If you have the value of the object, you'll want to use the remove function. It looks like this:
An example:
In [35]:
bad_architects.remove("First-year students")
print bad_architects
What if you know the index of the item you want to remove?
What if you appended an item by mistake and just want to remove the last item in the list?
You should use the pop function. It looks like this:
In [36]:
what_i_ate_today = ["coffee", "bacon", "eggs"]
print what_i_ate_today
In [37]:
what_i_ate_today.append("vegetables") # adds vegetables to the end of the list
# but I don't like vegetables
print what_i_ate_today
In [38]:
# since I don't like vegetables
what_i_ate_today.pop(-1) # use index of -1, since vegetables is the last item in the list
print what_i_ate_today
You can also remove the second item.
In [39]:
what_i_ate_today.pop(1)
Out[39]:
Notice the 'bacon' in the line above.
pop actually 'pops' the value (the one you just removed from the list) back to you.
Let us pop the first item.
In [40]:
was_first_item = what_i_ate_today.pop(0)
print 'was_first_item =', was_first_item
print 'what_i_ate_today = ', what_i_ate_today
what_i_ate_today is just 'eggs'?
That is not much of a breakfast!
Let us get back to eppy.
Let us get those "MATERIAL" objects again
In [41]:
materials = idf1.idfobjects["MATERIAL"]
With our newfound knowledge of lists, we can do a lot of things.
Let us get the last material:
In [42]:
print materials[-1]
How about the last two?
In [43]:
print materials[-2:]
Pretty good.
How many materials are in this model ?
In [44]:
print len(materials)
Let us remove the last material in the list
In [45]:
was_last_material = materials.pop(-1)
In [46]:
print len(materials)
Success! We have only 9 materials now.
The last material used to be:
'G05 25mm wood'
In [47]:
print materials[-1]
Now the last material in the list is:
'M15 200mm heavyweight concrete'
We still have the old last material
In [48]:
print was_last_material
Let us add it back to the list
In [49]:
materials.append(was_last_material)
In [50]:
print len(materials)
Once again we have 10 materials and the last material is:
In [51]:
print materials[-1]
So far we have been working only with materials that were already in the list.
What if we want to make new material?
Obviously we would use the function 'newidfobject'.
In [52]:
idf1.newidfobject("MATERIAL")
Out[52]:
In [53]:
len(materials)
Out[53]:
We have 11 items in the materials list.
Let us take a look at the last material in the list, where this fancy new material was added
In [54]:
print materials[-1]
Looks a little different from the other materials. It does have the name we gave it.
Why do some fields have values and others are blank ?
"addobject" puts in all the default values, and leaves the others blank. It is up to us to put values in the the new fields.
Let's do it now.
In [55]:
materials[-1].Name = 'Peanut Butter'
materials[-1].Roughness = 'MediumSmooth'
materials[-1].Thickness = 0.03
materials[-1].Conductivity = 0.16
materials[-1].Density = 600
materials[-1].Specific_Heat = 1500
In [56]:
print materials[-1]
In [57]:
Peanutbuttermaterial = materials[-1]
idf1.copyidfobject(Peanutbuttermaterial)
materials = idf1.idfobjects["MATERIAL"]
len(materials)
materials[-1]
Out[57]:
I'm tired of doing all this work, it's time to make python do some heavy lifting for us!
Python can go through each item in a list and perform an operation on any (or every) item in the list.
This is called looping through the list.
Here's how to tell python to step through each item in a list, and then do something to every single item.
We'll use a 'for' loop to do this. ::
A quick note about the second line. Notice that it's indented? There are 4 blank spaces before the code starts::
It's elegant, but it means that the indentation of the code holds meaning.
So make sure to indent the second (and third and forth) lines of your loops!
Now let's make some fruit loops.
In [58]:
fruits = ["apple", "orange", "bannana"]
Given the syntax I gave you before I started rambling about indentation, we can easily print every item in the fruits list by using a 'for' loop.
In [59]:
for fruit in fruits:
print fruit
That was easy! But it can get complicated pretty quickly...
Let's make it do something more complicated than just print the fruits.
Let's have python add some words to each fruit.
In [60]:
for fruit in fruits:
print "I am a fruit said the", fruit
Now we'll try to confuse you:
In [61]:
rottenfruits = [] # makes a blank list called rottenfruits
for fruit in fruits: # steps through every item in fruits
rottenfruit = "rotten " + fruit # changes each item to "rotten _____"
rottenfruits.append(rottenfruit) # adds each changed item to the formerly empty list
In [62]:
print rottenfruits
In [63]:
# here's a shorter way of writing it
rottenfruits = ["rotten " + fruit for fruit in fruits]
Did you follow all that??
Just in case you didn't, let's review that last one::
In [64]:
print rottenfruits
But what if you don't want to change every item in a list?
We can use an 'if' statement to operate on only some items in the list.
Indentation is also important in 'if' statements, as you'll see::
In [65]:
fruits = ["apple", "orange", "pear", "berry", "mango", "plum", "peach", "melon", "bannana"]
In [66]:
for fruit in fruits: # steps through every fruit in fruits
if len(fruit) > 5: # checks to see if the length of the word is more than 5
print fruit # if true, print the fruit
# if false, python goes back to the 'for' loop
# and checks the next item in the list
Let's say we want to pick only the fruits that start with the letter 'p'.
In [67]:
p_fruits = [] # creates an empty list called p_fruits
for fruit in fruits: # steps through every fruit in fruits
if fruit.startswith("p"): # checks to see if the first letter is 'p', using a built-in function
p_fruits.append(fruit) # if the first letter is 'p', the item is added to p_fruits
# if the first letter is not 'p', python goes back to the 'for' loop
# and checks the next item in the list
In [68]:
print p_fruits
In [69]:
# here's a shorter way to write it
p_fruits = [fruit for fruit in fruits if fruit.startswith("p")]
::
In [70]:
print p_fruits
This is not really needed, but it is nice to know. You can safely skip this.
Python's built-in function range() makes a list of numbers within a range that you specify.
This is useful because you can use these lists inside of loops.
In [71]:
range(4) # makes a list
Out[71]:
In [72]:
for i in range(4):
print i
In [73]:
len(p_fruits)
Out[73]:
In [74]:
for i in range(len(p_fruits)):
print i
In [75]:
for i in range(len(p_fruits)):
print p_fruits[i]
In [76]:
for i in range(len(p_fruits)):
print i, p_fruits[i]
In [77]:
for item_from_enumerate in enumerate(p_fruits):
print item_from_enumerate
In [78]:
for i, fruit in enumerate(p_fruits):
print i, fruit
If you have read the python explanation of loops, you are now masters of using loops.
Let us use the loops with E+ objects.
We'll continue to work with the materials list.
In [79]:
for material in materials:
print material.Name
In [80]:
[material.Name for material in materials]
Out[80]:
In [81]:
[material.Roughness for material in materials]
Out[81]:
In [82]:
[material.Thickness for material in materials]
Out[82]:
In [83]:
[material.Thickness for material in materials if material.Thickness > 0.1]
Out[83]:
In [84]:
[material.Name for material in materials if material.Thickness > 0.1]
Out[84]:
In [85]:
thick_materials = [material for material in materials if material.Thickness > 0.1]
In [86]:
thick_materials
Out[86]:
In [87]:
# change the names of the thick materials
for material in thick_materials:
material.Name = "THICK " + material.Name
In [88]:
thick_materials
Out[88]:
So now we're working with two different lists: materials and thick_materials.
But even though the items can be separated into two lists, we're still working with the same items.
Here's a helpful illustration:
In [89]:
for_images.display_png(for_images.material_lists) # display the image below
In [90]:
# here's the same concept, demonstrated with code
# remember, we changed the names of the items in the list thick_materials
# these changes are visible when we print the materials list; the thick materials are also in the materials list
[material.Name for material in materials]
Out[90]:
Sometimes, we want information about the E+ object that is not in the fields. For example, it would be useful to know the areas and orientations of the surfaces. These attributes of the surfaces are not in the fields of surfaces, but surface objects do have fields that have the coordinates of the surface. The areas and orientations can be calculated from these coordinates.
Pyeplus has some functions that will do the calculations.
In the present version, pyeplus will calculate:
Let us explore these functions
In [91]:
# OLD CODE, SHOULD BE DELETED
# from idfreader import idfreader
# iddfile = "../iddfiles/Energy+V7_0_0_036.idd"
# fname = "../idffiles/V_7_0/5ZoneSupRetPlenRAB.idf"
# model, to_print, idd_info = idfreader(fname, iddfile)
# surfaces = model['BUILDINGSURFACE:DETAILED'] # all the surfaces
In [92]:
from eppy import modeleditor
from eppy.modeleditor import IDF
iddfile = "../eppy/resources/iddfiles/Energy+V7_2_0.idd"
try:
IDF.setiddname(iddfile)
except modeleditor.IDDAlreadySetError as e:
pass
fname1 = "../eppy/resources/idffiles/V_7_0/5ZoneSupRetPlenRAB.idf"
idf1 = IDF(fname1)
surfaces = idf1.idfobjects['BUILDINGSURFACE:DETAILED']
In [93]:
# Let us look at the first surface
asurface = surfaces[0]
print "surface azimuth =", asurface.azimuth, "degrees"
print "surface tilt =", asurface.tilt, "degrees"
print "surface area =", asurface.area, "m2"
In [94]:
# all the surface names
s_names = [surface.Name for surface in surfaces]
print s_names[:5] # print five of them
In [95]:
# surface names and azimuths
s_names_azm = [(sf.Name, sf.azimuth) for sf in surfaces]
print s_names_azm[:5] # print five of them
In [96]:
# or to do that in pretty printing
for name, azimuth in s_names_azm[:5]: # just five of them
print name, azimuth
In [97]:
# surface names and tilt
s_names_tilt = [(sf.Name, sf.tilt) for sf in surfaces]
for name, tilt in s_names_tilt[:5]: # just five of them
print name, tilt
In [98]:
# surface names and areas
s_names_area = [(sf.Name, sf.area) for sf in surfaces]
for name, area in s_names_area[:5]: # just five of them
print name, area, "m2"
Let us try to isolate the exterior north facing walls and change their construnctions
In [99]:
# just vertical walls
vertical_walls = [sf for sf in surfaces if sf.tilt == 90.0]
print [sf.Name for sf in vertical_walls]
In [100]:
# north facing walls
north_walls = [sf for sf in vertical_walls if sf.azimuth == 0.0]
print [sf.Name for sf in north_walls]
In [101]:
# north facing exterior walls
exterior_nwall = [sf for sf in north_walls if sf.Outside_Boundary_Condition == "Outdoors"]
print [sf.Name for sf in exterior_nwall]
In [102]:
# print out some more details of the north wall
north_wall_info = [(sf.Name, sf.azimuth, sf.Construction_Name) for sf in exterior_nwall]
for name, azimuth, construction in north_wall_info:
print name, azimuth, construction
In [103]:
# change the construction in the exterior north walls
for wall in exterior_nwall:
wall.Construction_Name = "NORTHERN-WALL" # make sure such a construction exists in the model
In [104]:
# see the change
north_wall_info = [(sf.Name, sf.azimuth, sf.Construction_Name) for sf in exterior_nwall]
for name, azimuth, construction in north_wall_info:
print name, azimuth, construction
In [105]:
# see this in all surfaces
for sf in surfaces:
print sf.Name, sf.azimuth, sf.Construction_Name
You can see the "NORTHERN-WALL" in the print out above.
This shows that very sophisticated modification can be made to the model rather quickly.