# 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) 


"""
Explanation: New functions
These are recently written functions that have not made it into the main documentation
Python Lesson: Errors and Exceptions
End of explanation
"""


from eppy import modeleditor 
from eppy.modeleditor import IDF
fname1 = "../eppy/resources/idffiles/V_7_2/smallfile.idf"


"""
Explanation: When things go wrong in your eppy script, you get "Errors and Exceptions". 
To know more about how this works in python and eppy, take a look at Python: Errors and Exceptions
Setting IDD name
When you work with Energyplus you are working with idf files (files that have the extension *.idf). There is another file that is very important, called the idd file. This is the file that defines all the objects in Energyplus. Esch version of Energyplus has a different idd file. 
So eppy needs to know which idd file to use. Only one idd file can be used in a script or program. This means that you cannot change the idd file once you have selected it. Of course you have to first select an idd file before eppy can work.
If you use eppy and break the above rules, eppy will raise an exception. So let us use eppy incorrectly and make eppy raise the exception, just see how that happens.
First let us try to open an idf file without setting an idd file.
End of explanation
"""


try:
    idf1 = IDF(fname1)
except Exception, e:
    raise e
    

"""
Explanation: Now let us open file fname1 without setting the idd file
End of explanation
"""


iddfile = "../eppy/resources/iddfiles/Energy+V7_2_0.idd"
IDF.setiddname(iddfile)
idf1 = IDF(fname1)


"""
Explanation: OK. It does not let you do that and it raises an exception
So let us set the idd file and then open the idf file
End of explanation
"""


try:
    IDF.setiddname("anotheridd.idd")
except Exception, e:
    raise e   
    

"""
Explanation: That worked without raising an exception
Now let us try to change the idd file. Eppy should not let you do this and should raise an exception.
End of explanation
"""


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"


# IDF.setiddname(iddfile)# idd ws set further up in this page
idf1 = IDF(fname1)


building = idf1.idfobjects['building'.upper()][0]
print building


print building.getrange("Loads_Convergence_Tolerance_Value")


print building.checkrange("Loads_Convergence_Tolerance_Value")


"""
Explanation: Excellent!! It raised the exception we were expecting.
Check range for fields
The fields of idf objects often have a range of legal values. The following functions will let you discover what that range is and test if your value lies within that range
demonstrate two new functions:

EpBunch.getrange(fieldname) # will return the ranges for that field
EpBunch.checkrange(fieldname) # will throw an exception if the value is outside the range
End of explanation
"""


building.Loads_Convergence_Tolerance_Value = 0.6
from eppy.bunch_subclass import RangeError
try:
    print building.checkrange("Loads_Convergence_Tolerance_Value")
except RangeError, e:
    raise e
    

"""
Explanation: Let us set these values outside the range and see what happens
End of explanation
"""


print building.fieldnames


"""
Explanation: So the Range Check works
Looping through all the fields in an idf object
We have seen how to check the range of field in the idf object. What if you want to do a range check on all the fields in an idf object ? To do this we will need a list of all the fields in the idf object. We can do this easily by the following line
End of explanation
"""


for fieldname in building.fieldnames:
    print "%s = %s" % (fieldname, building[fieldname])
    

"""
Explanation: So let us use this
End of explanation
"""


from eppy.bunch_subclass import RangeError
for fieldname in building.fieldnames:
    try:
        building.checkrange(fieldname)
        print "%s = %s #-in range" % (fieldname, building[fieldname],)
    except RangeError as e:
        print "%s = %s #-****OUT OF RANGE****" % (fieldname, building[fieldname],)
        

"""
Explanation: Now let us test if the values are in the legal range. We know that "Loads_Convergence_Tolerance_Value" is out of range
End of explanation
"""


# some initial steps
from eppy.modeleditor import IDF
iddfile = "../eppy/resources/iddfiles/Energy+V7_2_0.idd"
# IDF.setiddname(iddfile) # Has already been set 

# - Let us first open a file from the disk
fname1 = "../eppy/resources/idffiles/V_7_2/smallfile.idf"
idf_fromfilename = IDF(fname1) # initialize the IDF object with the file name

idf_fromfilename.printidf()


# - now let us open a file from the disk differently
fname1 = "../eppy/resources/idffiles/V_7_2/smallfile.idf"
fhandle = open(fname1, 'r') # open the file for reading and assign it a file handle
idf_fromfilehandle = IDF(fhandle) # initialize the IDF object with the file handle

idf_fromfilehandle.printidf()


# So IDF object can be initialized with either a file name or a file handle

# - How do I create a blank new idf file  
idftxt = "" # empty string
from StringIO import StringIO
fhandle = StringIO(idftxt) # we can make a file handle of a string
idf_emptyfile = IDF(fhandle) # initialize the IDF object with the file handle

idf_emptyfile.printidf()


"""
Explanation: You see, we caught the out of range value
Blank idf file
Until now in all our examples, we have been reading an idf file from disk:

How do I create a blank new idf file  
give it a file name
Save it to the disk

Here are the steps to do that
End of explanation
"""


# - The string does not have to be blank
idftxt = "VERSION, 7.3;" # Not an emplty string. has just the version number
fhandle = StringIO(idftxt) # we can make a file handle of a string
idf_notemptyfile = IDF(fhandle) # initialize the IDF object with the file handle

idf_notemptyfile.printidf()


"""
Explanation: It did not print anything. Why should it. It was empty. 
What if we give it a string that was not blank
End of explanation
"""


# - give it a file name
idf_notemptyfile.idfname = "notemptyfile.idf"
# - Save it to the disk
idf_notemptyfile.save()


"""
Explanation: Aha !
Now let us give it a file name
End of explanation
"""


txt = open("notemptyfile.idf", 'r').read()# read the file from the disk
print txt


"""
Explanation: Let us confirm that the file was saved to disk
End of explanation
"""


import os
os.remove("notemptyfile.idf")


"""
Explanation: Yup ! that file was saved. Let us delete it since we were just playing
End of explanation
"""


# making a blank idf object
blankstr = ""
from StringIO import StringIO
idf = IDF(StringIO(blankstr))


"""
Explanation: Deleting, copying/adding and making new idfobjects
Making a new idf object
Let us start with a blank idf file and make some new "MATERIAL" objects in it
End of explanation
"""


newobject = idf.newidfobject("material".upper()) # the key for the object type has to be in upper case
                                     # .upper() makes it upper case
    

print newobject


"""
Explanation: To make and add a new idfobject object, we use the function IDF.newidfobject(). We want to make an object of type "MATERIAL"
End of explanation
"""


newobject.Name = "Shiny new material object"
print newobject


anothermaterial = idf.newidfobject("material".upper())
anothermaterial.Name = "Lousy material"
thirdmaterial = idf.newidfobject("material".upper())
thirdmaterial.Name = "third material"
print thirdmaterial


"""
Explanation: Let us give this a name, say "Shiny new material object"
End of explanation
"""


print idf.idfobjects["MATERIAL"]


"""
Explanation: Let us look at all the "MATERIAL" objects
End of explanation
"""


idf.popidfobject('MATERIAL', 1) # first material is '0', second is '1'


print idf.idfobjects['MATERIAL']


"""
Explanation: As we can see there are three MATERIAL idfobjects. They are:

Shiny new material object
Lousy material
third material

Deleting an idf object
Let us remove 2. Lousy material. It is the second material in the list. So let us remove the second material
End of explanation
"""


firstmaterial = idf.idfobjects['MATERIAL'][-1]


idf.removeidfobject(firstmaterial)


print idf.idfobjects['MATERIAL']


"""
Explanation: You can see that the second material is gone ! Now let us remove the first material, but do it using a different function
End of explanation
"""


onlymaterial = idf.idfobjects["MATERIAL"][0]

idf.copyidfobject(onlymaterial)

print idf.idfobjects["MATERIAL"]

"""
Explanation: So we have two ways of deleting an idf object:

popidfobject -> give it the idf key: "MATERIAL", and the index number
removeidfobject -> give it the idf object to be deleted

Copying/Adding an idf object
Having deleted two "MATERIAL" objects, we have only one left. Let us make a copy of this object and add it to our idf file
End of explanation
"""


gypboard = idf.newidfobject('MATERIAL', Name="G01a 19mm gypsum board",
                            Roughness="MediumSmooth",
                            Thickness=0.019,
                            Conductivity=0.16,
                            Density=800,
                            Specific_Heat=1090)

print gypboard

"""
Explanation: So now we have a copy of the material. You can use this method to copy idf objects from other idf files too.
Making an idf object with named arguments
What if we wanted to make an idf object with values for it's fields? We can do that too.
End of explanation
"""


print idf.idfobjects["MATERIAL"]

"""
Explanation: newidfobject() also fills in the default values like "Thermal Absorptance", "Solar Absorptance", etc.
End of explanation
"""


interiorwall = idf.newidfobject("CONSTRUCTION", Name="Interior Wall",
                 Outside_Layer="G01a 19mm gypsum board",
                 Layer_2="Shiny new material object",
                 Layer_3="G01a 19mm gypsum board")
print interiorwall


"""
Explanation: Renaming an idf object
It is easy to rename an idf object. If we want to rename the gypboard object that we created above, we simply say:
But this could create a problem. What if this gypboard is part of a "CONSTRUCTION" object. The construction object will refer to the gypboard by name. If we change the name of the gypboard, we should change it in the construction object. 
But there may be many constructions objects using the gypboard. Now we will have to change it in all those construction objects. Sounds painfull. 
Let us try this with an example:
End of explanation
"""


modeleditor.rename(idf, "MATERIAL", "G01a 19mm gypsum board", "peanut butter")

print interiorwall

"""
Explanation: to rename gypboard and have that name change in all the places we call modeleditor.rename(idf, key, oldname, newname)
End of explanation
"""


idf.printidf()

"""
Explanation: Now we have "peanut butter" everywhere. At least where we need it. Let us look at the entir idf file, just to be sure
End of explanation
"""


defaultmaterial = idf.newidfobject("MATERIAL", 
                                     Name='with default')
print defaultmaterial
nodefaultmaterial = idf.newidfobject("MATERIAL", 
                                     Name='Without default',
                                  defaultvalues=False)
print nodefaultmaterial

"""
Explanation: Turn off default values
Can I turn off the defautl values. Yes you can:
End of explanation
"""


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/box.idf"
# IDF.setiddname(iddfile)

idf = IDF(fname1)

surfaces = idf.idfobjects["BuildingSurface:Detailed".upper()]
surface = surfaces[0]
print "area = %s" % (surface.area, )
print "tilt = %s" % (surface.tilt, )
print "azimuth = %s" % (surface.azimuth, )

"""
Explanation: But why would you want to turn it off. 
Well .... sometimes you have to 
Try it with the object DAYLIGHTING:CONTROLS, and you will see the need for defaultvalues=False

Of course, internally EnergyPlus will still use the default values it it is left blank. If just won't turn up in the IDF file.
Zone area and volume
The idf file has zones with surfaces and windows. It is easy to get the attributes of the surfaces and windows as we have seen in the tutorial. Let us review this once more:
End of explanation
"""


zones = idf.idfobjects["ZONE"]
zone = zones[0]
area = modeleditor.zonearea(idf, zone.Name)
volume = modeleditor.zonevolume(idf, zone.Name)
print "zone area = %s" % (area, )
print "zone volume = %s" % (volume, )

"""
Explanation: Can we do the same for zones ? 
Not yet .. not yet. Not in this version on eppy
But we can still get the area and volume of the zone
End of explanation
"""


idf1.printidf()

import eppy.json_functions as json_functions
json_str = {"idf.VERSION..Version_Identifier":8.5,
            "idf.SIMULATIONCONTROL..Do_Zone_Sizing_Calculation": "No",
            "idf.SIMULATIONCONTROL..Do_System_Sizing_Calculation": "No",
            "idf.SIMULATIONCONTROL..Do_Plant_Sizing_Calculation": "No",
            "idf.BUILDING.Empire State Building.North_Axis": 52,
            "idf.BUILDING.Empire State Building.Terrain": "Rural",
            }
json_functions.updateidf(idf1, json_str)

idf1.printidf()

"""
Explanation: Not as slick, but still pretty easy
Some notes on the zone area calculation:

area is calculated by summing up all the areas of the floor surfaces
if there are no floors, then the sum of ceilings and roof is taken as zone area
if there are no floors, ceilings or roof, we are out of luck. The function returns 0

Using JSON to update idf
we are going to update idf1 using json. First let us print the idf1 before changing it, so we can see what has changed once we make an update
End of explanation
"""


json_str = {"idf.BUILDING.Taj.Terrain": "Rural",}
json_functions.updateidf(idf1, json_str)
idf1.idfobjects['building'.upper()]
# of course, you are creating an invalid E+ file. But we are just playing here.

"""
Explanation: Compare the first printidf() and the second printidf(). 
The syntax of the json string is described below::
You can also create a new object using JSON, using the same syntax. Take a look at this:
End of explanation
"""


# first way
json_str = {"idf.BUILDING.Taj.with.dot.Terrain": "Rural",}
json_functions.updateidf(idf1, json_str)
# second way (put the name in single quotes)
json_str = {"idf.BUILDING.'Another.Taj.with.dot'.Terrain": "Rural",}
json_functions.updateidf(idf1, json_str)

idf1.idfobjects['building'.upper()]

"""
Explanation: What if you object name had a dot . in it? Will the json_function get confused?
If the name has a dot in it, there are two ways of doing this.
End of explanation
"""


from eppy import modeleditor
from eppy.modeleditor import IDF

iddfile = "../eppy/resources/iddfiles/Energy+V7_2_0.idd"
fname = "../eppy/resources/idffiles/V_7_2/smallfile.idf"
IDF.setiddname(iddfile)
idf = IDF(fname)

"""
Explanation: Note When you us the json update function:

The json function expects the Name field to have a value.
If you try to update an object with a blank Name field, the results may be unexpected (undefined ? :-). So don't do this.
If the object has no Name field (some don't), changes are made to the first object in the list. Which should be fine, since usually there is only one item in the list
In any case, if the object does not exist, it is created with the default values

Use Case for JSON update
If you have an eppy running on a remote server somewhere on the internet, you can change an idf file by sending it a JSON over the internet. This is very useful if you ever need it. If you don't need it, you shouldn't care :-)
Open a file quickly¶
It is rather cumbersome to open an IDF file in eppy. From the tutorial, the steps look like this:
End of explanation
"""


from eppy.easyopen import easyopen

fname = './eppy/resources/idffiles/V8_8/smallfile.idf'
idf = easyopen(fname)

"""
Explanation: You have to find the IDD file on your hard disk.
Then set the IDD using setiddname(iddfile).
Now you can open the IDF file

Why can’t you just open the IDF file without jumping thru all those hoops. Why do you have to find the IDD file. What is the point of having a computer, if it does not do the grunt work for you.
The function easyopen will do the grunt work for you. It will automatically read the version number from the IDF file, locate the correct IDD file and set it in eppy and then open your file. It works like this:
End of explanation
"""


thefans = idf.idfobjects['Fan:VariableVolume'.upper()]
thefan = thefans[0]
bhp = thefan.fanpower_bhp
watts = thefan.fanpower_watts
cfm = thefan.fan_maxcfm

"""
Explanation: For this to work,

the IDF file should have the VERSION object. You may not have this if you are just working on a file snippet.
you need to have the version of EnergyPlus installed that matches your IDF version.
Energyplus should be installed in the default location.

If easyopen does not work, use the long winded steps shown in the tutorial. That is guaranteed to work
Other miscellaneous functions¶
Fan power in Watts, BHP and fan cfm¶
We normally think of fan power in terms of Brake Horsepower (BHP), Watts. Also when working with IP units it is useful to think of fan flow volume in terms of cubic feet per minute (cfm).
Energyplus does not have fields for those values. With eppy we have functions that will calculate the values

fan power in BHP
fan power in Watts
fan flow in CFM

It will work for the following objects:

FAN:CONSTANTVOLUME
FAN:VARIABLEVOLUME
FAN:ONOFF
FAN:ZONEEXHAUST
FANPERFORMANCE:NIGHTVENTILATION

The sample code would look like this:
End of explanation
"""


