Migrating from eppy¶
idfkit provides a compatibility layer so most eppy code works with only minor changes. You can migrate gradually -- all the eppy-style methods listed below are available alongside the newer idfkit API.
Loading a file¶
eppy requires you to locate and pass the IDD file yourself:
from eppy.modeleditor import IDF
IDF.setiddname("/path/to/Energy+.idd")
idf = IDF("/path/to/in.idf")
idfkit bundles schemas and detects the version automatically:
No IDD path is needed. If you want to target a specific EnergyPlus version:
Quick reference¶
The table below maps common eppy patterns to their idfkit equivalents. The eppy alias column shows that the old spelling still works in idfkit when one exists.
| Task | eppy | idfkit | eppy alias in idfkit? |
|---|---|---|---|
| Load file | IDF(idd, idf) |
load_idf(path) |
-- |
| Create object | idf.newidfobject("ZONE", Name=...) |
doc.add("Zone", "name", ...) |
doc.newidfobject(...) |
| Get collection | idf.idfobjects["ZONE"] |
doc["Zone"] or doc.zones |
doc.idfobjects[...] |
| Get object by name | idf.getobject("ZONE", "name") |
doc["Zone"]["name"] |
doc.getobject(...) |
| Remove object | idf.removeidfobject(obj) |
doc.remove(obj) |
doc.removeidfobject(obj) |
| Remove by index | idf.popidfobject("ZONE", 0) |
doc.popidfobject("Zone", 0) |
doc.popidfobject(...) |
| Copy object | idf.copyidfobject(obj) |
doc.copyidfobject(obj) |
doc.copyidfobject(obj) |
| Object type | obj.key |
obj.obj_type |
obj.key |
| Object name | obj.Name |
obj.name |
obj.Name |
| Parent document | obj.theidf |
obj._document |
obj.theidf |
| Field names | obj.fieldnames |
list(obj.data.keys()) |
obj.fieldnames |
| Field values | obj.fieldvalues |
list(obj.data.values()) |
obj.fieldvalues |
| Field IDD info | obj.getfieldidd(name) |
obj.get_field_idd(name) |
obj.getfieldidd(name) |
| Field range | obj.getrange(name) |
obj.getrange(name) |
obj.getrange(name) |
| Check range | obj.checkrange(name) |
obj.checkrange(name) |
obj.checkrange(name) |
| Follow reference | obj.get_referenced_object(name) |
obj.get_referenced_object(name) |
obj.get_referenced_object(name) |
| Find referrers | obj.getreferingobjs() |
obj.get_referring_objects() |
obj.getreferingobjs() |
| Group dict | idf.getiddgroupdict() |
doc.getiddgroupdict() |
doc.getiddgroupdict() |
| Get surfaces | idf.getsurfaces() |
doc.getsurfaces() |
doc.getsurfaces() |
| Save file | idf.save() |
doc.save() |
doc.save() |
| Save as | idf.saveas(path) |
doc.saveas(path) |
doc.saveas(path) |
| Save copy | idf.savecopy(path) |
doc.savecopy(path) |
doc.savecopy(path) |
| Output type | idf.outputtype = "compressed" |
doc.save(output_type="compressed") |
-- |
| Run simulation | idf.run(weather) |
simulate(doc, weather) |
doc.run(weather) |
| Batch update | json_functions.updateidf(idf, d) |
doc.update(d) |
doc.update(d) |
| HTML tables | readhtml.titletable(html) |
result.html.titletable() |
-- |
| Window-wall ratio | idf.set_wwr(0.4) |
set_wwr(doc, 0.4) |
-- |
| Match surfaces | idf.intersect_match() |
intersect_match(doc) |
-- |
Creating objects¶
eppy:
idfkit:
Or using the eppy-compatible method:
Accessing fields¶
eppy uses the capitalised IDD field names:
idfkit uses snake_case names:
Both styles resolve to the same underlying data.
Reference tracking (new in idfkit)¶
eppy has no built-in way to find which objects reference a given name. idfkit maintains a live reference graph:
# Find every object that points to the "Office" zone
for obj in doc.get_referencing("Office"):
print(obj.obj_type, obj.name)
# Find every name that the People object references
names = doc.get_references(people_obj)
Renaming with cascading updates (new in idfkit)¶
In eppy, renaming a zone requires you to manually update every surface, people, lights, and other object that references it. In idfkit the reference graph handles this automatically:
zone = doc["Zone"]["Office"]
zone.name = "Open_Office"
# All fields across the document that pointed to "Office" now say "Open_Office"
Validation (new in idfkit)¶
from idfkit import validate_document
result = validate_document(doc)
if not result.is_valid:
for error in result.errors:
print(error)
Saving files¶
eppy saves through methods on the IDF object:
idfkit supports the same methods:
doc.saveas("out.idf") # save and update doc.filepath
doc.savecopy("backup.idf") # save without changing doc.filepath
doc.save() # save to current doc.filepath
Or use the standalone writer for more control:
from idfkit import write_idf, write_epjson
write_idf(doc, "out.idf")
write_epjson(doc, "out.epJSON") # or convert to epJSON
Output formatting modes¶
eppy controls output formatting with idf.outputtype:
idfkit passes the mode to the writer:
from idfkit import write_idf
write_idf(doc, "out.idf", output_type="nocomment") # no field comments
write_idf(doc, "out.idf", output_type="compressed") # single-line objects
write_idf(doc, "out.idf", output_type="standard") # default, with comments
Following references¶
eppy lets you follow a reference field to get the target object:
surface = idf.idfobjects["BuildingSurface:Detailed"][0]
construction = surface.get_referenced_object("Construction_Name")
idfkit provides the same method:
surface = doc["BuildingSurface:Detailed"][0]
construction = surface.get_referenced_object("construction_name")
Finding referring objects¶
eppy finds all objects that reference a given object:
idfkit provides both the eppy spelling and a corrected alias:
zone = doc["Zone"]["Office"]
# eppy-compatible spelling
referrers = zone.getreferingobjs()
# Corrected spelling
referrers = zone.get_referring_objects()
# Optional filters -- by IDD group and/or field name
surfaces = zone.getreferingobjs(
iddgroups=["Thermal Zones and Surfaces"],
fields=["zone_name"],
)
Range checking¶
eppy provides range checking on numeric fields:
obj.getrange("Density")
# {'minimum': 0, 'type': 'real'}
obj.checkrange("Density") # raises RangeError if out of range
idfkit supports the same API:
obj.getrange("density")
# {'minimum': 0, 'type': 'real'}
obj.checkrange("density") # True, or raises RangeError
Removing objects by index¶
eppy removes objects by index with popidfobject:
idfkit:
Batch updates¶
eppy uses json_functions.updateidf() for parametric sweeps:
idfkit has this as a method on the document:
Geometry¶
eppy relies on geomeppy for
geometry operations. idfkit ships its own Vector3D and Polygon3D
classes with no external dependencies:
from idfkit.geometry import (
calculate_surface_area,
calculate_surface_tilt,
calculate_surface_azimuth,
calculate_zone_volume,
calculate_zone_height,
calculate_zone_ceiling_area,
)
for surface in doc["BuildingSurface:Detailed"]:
area = calculate_surface_area(surface)
tilt = calculate_surface_tilt(surface) # 0=up, 90=vertical, 180=down
azimuth = calculate_surface_azimuth(surface) # 0=north, 90=east, 180=south
print(f"{surface.name}: {area:.1f} m2, tilt={tilt:.0f}, azimuth={azimuth:.0f}")
print("Zone volume:", calculate_zone_volume(doc, "Office"))
print("Zone height:", calculate_zone_height(doc, "Office"))
print("Ceiling area:", calculate_zone_ceiling_area(doc, "Office"))
Building transforms¶
Translate or rotate all surfaces in the model:
from idfkit.geometry import translate_building, rotate_building, Vector3D
# Shift the entire building 10m east and 5m north
translate_building(doc, Vector3D(10.0, 5.0, 0.0))
# Rotate 45 degrees counter-clockwise around the origin
rotate_building(doc, 45.0)
# Rotate around a custom anchor point
rotate_building(doc, 90.0, anchor=Vector3D(5.0, 5.0, 0.0))
Window-wall ratio¶
geomeppy sets window-wall ratios as a method on the IDF:
# geomeppy (extends eppy)
idf.set_wwr(0.4)
idf.set_wwr(0.4, construction="SimpleGlazing")
idf.set_wwr(0.25, orientation="south")
idfkit uses a standalone function:
from idfkit.geometry import set_wwr
# Apply 40% WWR to all exterior walls
set_wwr(doc, 0.4)
# Specify a window construction
set_wwr(doc, 0.4, construction="SimpleGlazing")
# Target only south-facing walls
set_wwr(doc, 0.25, orientation="south")
Surface intersection and matching¶
geomeppy matches adjacent surfaces as a method on the IDF:
idfkit uses a standalone function:
from idfkit.geometry import intersect_match
# Match coincident walls between zones and set boundary conditions
intersect_match(doc)
Strict field access (new in idfkit)¶
eppy silently returns an empty string when you mistype a field name, making bugs hard to find. idfkit defaults to the same behaviour for compatibility, but you can opt in to strict mode to catch typos immediately:
from idfkit import new_document
# Enable strict mode to catch field-name typos during migration
doc = new_document()
doc.strict = True
zone = doc.add("Zone", "Office", x_origin=5.0)
print(zone.x_origin) # 5.0 — known field, works fine
zone.x_orgin # AttributeError: 'Zone' object has no field 'x_orgin'
Enable strict mode during migration to surface field-name mismatches early. Once your code is clean you can leave it on or turn it off.
Running a simulation¶
eppy runs simulations directly on the IDF object:
idfkit uses a standalone simulate() function (recommended):
from idfkit.simulation import simulate
result = simulate(doc, "weather.epw")
print(result.errors.summary())
Or use the eppy-compatible convenience method:
# eppy-compatible shortcut (calls simulate() internally)
result = doc.run("weather.epw", design_day=True)
HTML tabular output¶
eppy parses HTML tabular output using readhtml:
from eppy import readhtml
with open("eplustbl.htm") as f:
html = f.read()
tables = readhtml.titletable(html)
idfkit parses HTML output as part of SimulationResult:
result = simulate(doc, weather)
html = result.html # HTMLResult, lazily parsed
# eppy-compatible (title, rows) pairs
for title, rows in html.titletable():
print(title, len(rows), "rows")
# Lookup by name
table = html.tablebyname("Site and Source Energy")
print(table.to_dict()) # {row_key: {col_header: value}}
# Filter by report
annual = html.tablesbyreport("Annual Building Utility Performance Summary")
Or parse a standalone HTML file: