How to migrate 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.
Python only, permanently
eppy compatibility surface belongs to Python alone. JavaScript is not waiting on a port and will not gain a counterpart: this is a permanent boundary, so no issue tracks it, and moving the capability out of that state takes a constitutional amendment rather than a ledger edit.
A Python ecosystem concern with no JavaScript counterpart. These methods exist solely so that code written against eppy keeps working after a move to idfkit; every one of them names an idfkit alternative in its own docstring. There is no eppy in JavaScript, so there is nothing to be compatible with. Listed explicitly so it is never ported by mistake.
The full entry, including the vocabulary this capability owns, is on the capability parity page.
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)1 |
| 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 save_idf, save_epjson
save_idf(doc, "out.idf")
save_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 save_idf
save_idf(doc, "out.idf", output_type="nocomment") # no field comments
save_idf(doc, "out.idf", output_type="compressed") # single-line objects
save_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")
Construction preservation
When construction is not specified, set_wwr preserves the construction
from any existing fenestration on each wall. Cross-references (e.g. from
output variables) are also re-pointed to the new windows.
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)¶
idfkit has two different strictness settings:
- Strict parsing (
load_idf(..., strict_parsing=True)) validates IDF input while reading and is enabled by default. - Strict field access (
load_idf(..., strict=True)) raises on mistyped attribute names when reading or writing object fields. This is on by default.
eppy silently returns an empty string when you mistype a field name, making bugs hard to find. idfkit enables strict field access by default to catch typos immediately:
from idfkit import new_document
# Strict field access is on by default
doc = new_document()
zone = doc.add("Zone", "Office", x_origin=5.0)
print(zone.x_origin) # 5.0 — known field, works fine
zone.x_orgin # InvalidFieldError: 'Zone' object has no field 'x_orgin'
Strict field access is on by default. Disable it with strict=False
when loading third-party IDF files that may contain non-standard fields.
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:
-
doc.update(d)runs, but a type checker cannot see it onIDFDocument. The document inherits a generatedTypedDictso thatdoc["Zone"]resolves to a typed collection without an overload stack, and pyright synthesisesupdatefor every class in aTypedDicthierarchy, outranking any declaration. Annotate the variable asidfkit._compat.EppyDocumentMixinwhere you need the call checked. ↩