Surface Matching¶
Robust intersect-and-split matching for coplanar, oppositely-facing interzone
surfaces -- splits partially-overlapping surfaces into congruent matched
fragments (interior Surface boundary conditions) plus exterior remainders.
Robust intersect-and-match for interzone surfaces.
Splits coplanar, oppositely-facing building surfaces that overlap into
congruent matched fragments (interior Surface boundary conditions) plus
remainder fragments (which keep their original, usually exterior, boundary).
Unlike a simple congruent-pair matcher, this handles the one-to-many case: a
single long wall shared with several smaller neighbouring zones is split into
one matched fragment per neighbour, with any leftover left exterior.
The algorithm is dependency-free and convex-preserving:
- Cluster eligible surfaces by plane (anti-parallel normals share a cluster; tolerances absorb wall thickness).
- Project every surface (and its detailed fenestration) into a shared 2-D frame for the plane, snap-rounding coordinates so shared edges become exactly shared.
- For each host surface
Pfacing neighboursM1..Mk, the matched fragments are the intersectionsP & Mjand the remainder isPminus the union of allMj. Because intersection is symmetric, the fragment computed fromP's side is congruent to the one fromMj's side, so both zones get matching surfaces with no ordering effects. - Re-lift each 2-D fragment onto its own source surface's plane, so each
zone's surface stays exactly where the modeller drew it (wall-thickness
offsets are preserved). Winding falls out: the plus-side lifts to
+Nand its minus-side partner to-N-- the reversed-vertex pair EnergyPlus wants. - Rewrite the document: the first fragment keeps the original surface's name (so inbound references survive untouched); the rest become new surfaces; detailed windows are re-homed onto the fragment that contains them.
The remainder is computed with a convex-cutter onion difference built entirely on half-plane clipping, so every output piece stays convex (EnergyPlus-friendly and window-safe). A concave cutter or subject (rare) is ear-clipped into triangles first.
MatchOptions
dataclass
¶
Tunable tolerances and scope for :func:intersect_and_match.
Attributes:
| Name | Type | Description |
|---|---|---|
angle_tol_deg |
float
|
Two surfaces are treated as coplanar when the angle
between their normals (modulo sign) is within this tolerance.
Defaults to ~8.1°, matching the |
max_thickness |
float
|
Maximum plane-offset difference (metres) tolerated when clustering surfaces onto one plane — absorbs construction thickness. |
snap_tol |
float
|
2-D snap-rounding grid (metres). Projected coordinates are quantised to this grid so shared edges coincide exactly. |
min_area |
float
|
Fragments below this area (m²) are dropped as slivers. |
min_edge |
float
|
Fragments with any edge shorter than this (metres) are dropped as slivers. |
surface_classes |
tuple[str, ...]
|
Uppercase |
match_same_zone |
bool
|
When |
Source code in src/idfkit/surface_matching.py
MatchReport
dataclass
¶
Summary of what :func:intersect_and_match changed.
Attributes:
| Name | Type | Description |
|---|---|---|
pairs_matched |
int
|
Number of matched interior fragment pairs created. |
surfaces_split |
int
|
Number of original surfaces broken into >1 fragment. |
fragments_created |
int
|
Number of new surfaces added (kept originals are not counted). |
slivers_dropped |
int
|
Number of below-tolerance fragments discarded. |
fenestration_conflicts |
list[str]
|
Names of surfaces left unsplit because a window/door straddled a cut line (never silently clipped). |
unmatched_exteriors |
int
|
Number of eligible surfaces that ended up with no matched fragment (remained fully exterior). |
Source code in src/idfkit/surface_matching.py
intersect_and_match(doc, options=None)
¶
Intersect and match coplanar interzone surfaces, splitting as needed.
Coplanar, oppositely-facing surfaces that overlap are split into congruent
matched fragments (interior Surface boundary) plus exterior remainders.
A single surface abutting several neighbours is split into one matched
fragment per neighbour. The document is modified in place.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
doc
|
IDFDocument
|
The document to modify. |
required |
options
|
MatchOptions | None
|
Tolerances and scope; defaults to :class: |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
MatchReport
|
class: |
Examples:
>>> from idfkit import new_document
>>> doc = new_document(version=(24, 1, 0))
>>> report = intersect_and_match(doc)
>>> report.pairs_matched
0