Geometry and surfaces¶
idfkit ships a small, dependency-free 3D geometry layer for the calculations you do most often on EnergyPlus models: surface area, azimuth, tilt, zone volume, and 2D polygon ops. Vector3D and Polygon3D are the two building-block types; everything else is a free function that takes them or operates on IDFObject surfaces directly.
When to use¶
- You need to compute area/azimuth/tilt/volume from a model on disk.
- You want to translate or rotate a building.
- You're doing area-weighted aggregations (e.g. WWR per orientation, U-value × area).
- You're cleaning up a model with
intersect_match()to detect adjacency.
For building synthesis (footprints, zoning, fenestration), see geometry-builders-and-zoning.md.
Quick start¶
from idfkit import (
load_idf,
calculate_surface_area,
calculate_surface_azimuth,
calculate_zone_volume,
set_wwr,
intersect_match,
Vector3D,
Polygon3D,
)
doc = load_idf("building.idf")
# Per-surface
for surface in doc["BuildingSurface:Detailed"]:
print(surface.name, calculate_surface_area(surface), calculate_surface_azimuth(surface))
# Per-zone
print(calculate_zone_volume(doc, "Office"))
# Bulk
set_wwr(doc, wwr=0.4, orientation="South")
intersect_match(doc)
Core types¶
Vector3D¶
v = Vector3D(1.0, 0.0, 0.0)
v.x, v.y, v.z
v + Vector3D(0, 1, 0) # vector addition
v - Vector3D(0, 1, 0)
v.dot(other)
v.cross(other)
v.length()
v.normalize()
v.rotate_z(angle_deg=45) # rotate about Z (vertical), degrees
Polygon3D¶
poly = Polygon3D([Vector3D(0, 0, 0), Vector3D(10, 0, 0), Vector3D(10, 5, 0), Vector3D(0, 5, 0)])
poly.area # 50.0
poly.centroid # Vector3D
poly.normal # outward normal as Vector3D
poly.azimuth # 0-360, degrees clockwise from North
poly.tilt # 0-180, degrees from horizontal
Polygon3D does not expose a "reverse winding" helper — for floor↔ceiling matching, build a new polygon with the vertices in the opposite order (Polygon3D(list(reversed(poly.vertices)))).
Surface calculations¶
from idfkit import (
calculate_surface_area,
calculate_surface_azimuth,
calculate_surface_tilt,
)
surface = doc["BuildingSurface:Detailed"]["South Wall"]
calculate_surface_area(surface) # m²
calculate_surface_azimuth(surface) # 180 for a south wall (relative to North)
calculate_surface_tilt(surface) # 90 for a vertical wall, 0 for a roof
Internally these read the vertex list off the surface object and build a Polygon3D. To work with the polygon directly:
from idfkit.geometry import get_surface_coords, set_surface_coords
poly = get_surface_coords(surface) # Polygon3D | None
set_surface_coords(surface, new_poly) # writes vertices back, normal recomputed
Zone calculations¶
from idfkit import (
calculate_zone_floor_area,
calculate_zone_ceiling_area,
calculate_zone_height,
calculate_zone_volume,
)
zone_name = "Office"
calculate_zone_floor_area(doc, zone_name)
calculate_zone_ceiling_area(doc, zone_name)
calculate_zone_height(doc, zone_name)
calculate_zone_volume(doc, zone_name)
Zone volume is computed from the bounding surfaces — it works for any prismatic and most non-prismatic geometries.
Building-wide transforms¶
from idfkit import translate_building, rotate_building, Vector3D
translate_building(doc, Vector3D(10.0, 5.0, 0.0)) # shift all vertices
rotate_building(doc, angle_deg=15.0) # rotate about Z, anchor=origin
rotate_building(doc, angle_deg=15.0, anchor=Vector3D(50, 50, 0))
These walk every BuildingSurface:Detailed, Shading:*, and FenestrationSurface:Detailed and transform vertices in place. Zone origins update too.
Window-to-wall ratio (WWR)¶
set_wwr rewrites fenestration on exterior walls to match a target WWR:
from idfkit import set_wwr
set_wwr(doc, wwr=0.4) # all exterior walls
set_wwr(doc, wwr=0.4, orientation="South") # only south walls
# Per-orientation ratios: call once per orientation.
for orientation, ratio in [("North", 0.3), ("South", 0.5), ("East", 0.4), ("West", 0.4)]:
set_wwr(doc, wwr=ratio, orientation=orientation)
wwr is a single float in the open interval (0, 1). The existing windows on matched walls are removed; new FenestrationSurface:Detailed objects are inserted with the target ratio, centred on each wall.
Surface matching¶
intersect_match() finds pairs of surfaces that overlap (e.g. interior walls shared between two zones) and sets their outside_boundary_condition to Surface and outside_boundary_condition_object to point at each other:
from idfkit import intersect_match
intersect_match(doc) # mutates surfaces in place
# Every shared interior wall now has its boundary linked to the matching surface in the neighbour zone.
For ceiling↔floor matching specifically (often what you want for multi-story models), see geometry-builders-and-zoning.md for link_horizontal_surfaces and detect_horizontal_adjacencies.
2D polygon utilities¶
For computing footprint overlaps or differences (e.g. courtyard cut-outs):
from idfkit import (
polygon_area_2d,
polygon_contains_2d,
polygon_intersection_2d,
polygon_difference_2d,
)
a = [(0, 0), (10, 0), (10, 5), (0, 5)]
b = [(5, 0), (15, 0), (15, 5), (5, 5)]
inner = [(1, 1), (4, 1), (4, 4), (1, 4)]
polygon_area_2d(a) # 50.0
polygon_contains_2d(a, inner) # True (every vertex of inner is inside a)
polygon_intersection_2d(a, b) # overlapping rectangle
polygon_difference_2d(a, b) # a minus b
These take 2D (x, y) tuples (not Vector3D) because the typical use is on building footprints in plan view.
Common mistakes¶
manually summing vertex coordinates
rotating only Zone objects
passing a dict to set_wwr
call once per orientation
Related¶
- geometry-builders-and-zoning.md — building footprints and zoning.
- visualization.md — render the geometry to confirm what you just edited.
- thermal-properties.md — combine geometry with material properties for whole-wall U-values.
- API docs: py.idfkit.com/api/geometry/