Skip to content

Sections and floor plans

Cut the tessellated model with any plane and get closed 2D rings back, in the plane's own UV coordinates and metres.

p := geometry.HorizontalPlane(3.0) // cut at z = 3 m
for _, e := range a.Scene.Elements {
    for _, loop := range e.SectionOn(p) {
        _ = loop.Points // [][2]float64, outer rings CCW, holes CW
    }
}

For a plane that is not horizontal, PlaneFromNormal builds one from an origin and a normal:

p, ok := geometry.PlaneFromNormal([3]float64{0, 0, 3}, [3]float64{1, 0, 0})
if !ok {
    panic("degenerate normal")
}

PlaneFromNormal does not promise a particular UV orientation

Its U is deterministic for a given normal, but which in-plane orientation it picks is unspecified and may change between versions — including for a +Z normal, where it is not guaranteed to match HorizontalPlane. If you persist Points, or need to match an external convention, build the Plane yourself rather than rely on this choice.

SectionOn versus FootprintOn

These two differ in exactly one way: what they do when the plane does not cross the solid.

e.SectionOn(p) geometry.FootprintOn(e, p)
Plane crosses the solid cut rings, tagged LoopCut cut rings, tagged LoopCut
Plane misses nil silhouette of faces opposing p.N, tagged LoopSilhouette
Mesh degenerate / no silhouette nil bounding-box rectangle, tagged LoopSilhouette

That fallback is a plan-view feature and a section-view bug. A floor plan wants context geometry for everything on the storey, whether or not the cut height happens to pass through it. A building section wants to know the plane missed — a fabricated rectangle in a section drawing is a lie that renders convincingly.

Loop roles

const (
    LoopCut        LoopRole = "cut"   // section poché — the plane crosses the solid
    LoopSilhouette LoopRole = "below" // light context
)

The string values are a serialization contract

LoopSilhouette's value is "below", not "silhouette". The constant was renamed in v0.2.0; the wire value deliberately was not, so drawing data already on disk stayed valid. Renderers matching the literal "below" need no change. Do not change these values without a coordinated consumer migration.

Winding and holes

Outer rings are wound CCW. Hole rings — the inner boundary of a hollow or annular section — are wound CW and share the outer ring's Role. An even-odd or nonzero polygon fill therefore renders them as cutouts with no extra field and no nesting metadata to interpret.

Coordinates come out in the IFC-native orientation. A renderer whose Y axis points down applies its own flip.

A plane that lies along a solid's edges emits nothing

A triangle only contributes a crossing segment when it has a vertex strictly above and one strictly below the plane. A face that merely touches the plane along an edge contributes nothing, so the cut ring cannot close.

This is a known gap with a test pinning it, not an undiagnosed bug. In practice it bites when you cut exactly at a slab's top face — nudge the plane by a millimetre if you hit it.