Documentation
¶
Overview ¶
Package ifc is the top-level entry to the goifc Go-native IFC engine. It wires the per-stage packages — step (STEP/EXPRESS parse) -> model (semantic extraction) -> geometry (proxy tessellation + derived quantities) — into a single Assemble call that turns a parsed STEP file into quantity-back-filled semantic elements plus their proxy geometry:
f, _ := step.ParseBytes(src)
a, _ := ifc.Assemble(f)
for i := range a.Result.Elements {
e := a.Result.Elements[i] // e.Qto, e.QuantitySource ("qto"|"geometry"|"none")
}
a.Scene.WriteGLB(w) // proxy geometry for a viewer
KNOWN LIMITATION: the geometry-derived Volume tier is GROSS. For walls with openings the extrude path reports the SOLID (un-subtracted) volume, so those elements over-report versus ifcopenshell's NET figure. The quantity_source="geometry" tag already flags these as bounding estimates, so no consumer mistakes one for an authored net Qto — netting openings out of the extrude volume is deliberately out of scope here.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Assembled ¶
type Assembled struct {
Result *model.Result
Scene *geometry.Scene
// contains filtered or unexported fields
}
Assembled is the end-to-end output of Assemble: the quantity-back-filled semantic Result — the []model.Element that downstream consumers golden-diff and import — paired with the proxy-geometry Scene it was derived from, for WriteGLB and mesh Stats. Elements in the two share identity via GlobalID.
func Assemble ¶
Assemble runs the full ifc pipeline over a parsed STEP file, in order:
model.Extract semantic elements ([]model.Element) geometry.Build proxy geometry per element Scene.DerivedQuantities tier-2 GROSS quantities from the meshes Result.ApplyDerivedQuantities back-fills them onto un-authored elements
It is the single production entry point for the engine: it chains the four stages so callers never have to reinvent the chain.
Quantity tiering follows model.ApplyDerivedQuantities: authored Qto (tier-1, NET) always wins; where absent, the GROSS geometry-derived quantities back-fill and the element is tagged quantity_source="geometry"; an element with neither stays "none" — never a fabricated 0.0.
Example ¶
ExampleAssemble shows the end-to-end entry: raw IFC bytes -> parse -> Assemble -> per-element quantities + a GLB export, all from one orchestration call.
f, err := step.ParseBytes([]byte(boxIFC))
if err != nil {
fmt.Println("parse:", err)
return
}
a, err := ifc.Assemble(f)
if err != nil {
fmt.Println("assemble:", err)
return
}
fmt.Println("elements:", len(a.Result.Elements))
for i := range a.Result.Elements {
e := a.Result.Elements[i]
h := 0.0
if e.Qto.Height != nil {
h = *e.Qto.Height
}
fmt.Printf("%s source=%s height=%.2fm\n", e.Name, e.QuantitySource, h)
}
var glb bytes.Buffer
fmt.Println("glb written:", a.Scene.WriteGLB(&glb) == nil && glb.Len() > 0)
Output: elements: 1 Box source=geometry height=1.00m glb written: true
type ImportModel ¶
type ImportModel struct {
Nodes []ImportNode
Scene *geometry.Scene
StoreyPlans []StoreyPlan
// TypeLayers is the build-up per distinct IfcTypeObject, keyed by the type's
// GlobalId — the same key ImportNode.TypeGlobalID carries. Keyed by TYPE, not
// by occurrence: a real model has ~70 types and ~1,400 typed occurrences, and
// this whole struct is typically serialized into a workflow payload with a
// low-megabyte size cap.
//
// A type that resolved but carries no ordered build-up is PRESENT with an
// empty Layers slice — that is a positive claim ("this type has no layers"),
// distinct from absence ("no such type in this model"), and consumers
// reconciling previously-imported layers need the difference: the first must
// retire every stale row, the second must touch nothing.
TypeLayers map[string]TypeLayerSet
}
ImportModel is the assembled import contract: the parents-first node tree plus the proxy-geometry Scene the physical nodes' meshes are baked from (Scene.WriteGLB).
func BuildImport ¶
func BuildImport(f *step.File) (*ImportModel, error)
BuildImport turns a parsed STEP file into the import contract, assembling the model first. It is BuildImportFrom over a fresh Assemble — use that directly when you also need the Result or Scene the import was derived from, so the model is tessellated once rather than twice.
func BuildImportFrom ¶ added in v0.7.0
func BuildImportFrom(f *step.File, a *Assembled) (*ImportModel, error)
BuildImportFrom turns a parsed STEP file plus its assembly into the import contract: spatial containers (SpatialNodes) + physical elements in ONE parents-first ordered tree.
Assemble is the expensive stage, and its Result and Scene are worth more than the import contract alone: an elevation, a net-area reconciliation or a GLB all read them. Taking the assembly as an argument is what lets one caller have both without paying for the tessellation twice.
f must be the same *step.File the assembly was built from. Below this point everything joins physical elements to spatial containers and geometry by bare ExpressID (forwardParentMap, f.ByID, Scene.NetAreas), and ExpressIDs are small sequential integers that restart per file — a mismatched pair would very plausibly collide rather than miss, handing an element another model's parent, material or opening data with no error at all. An Assembled stamped by Assemble is checked by pointer identity against f, on purpose: a re-parse of the same bytes produces an equal-but-distinct *step.File, and that is exactly the mismatch this guards against. An Assembled built some other way carries no stamp and is let through unchecked — that caller assembled Result and Scene itself and already owns the pairing.
parent map = IfcRelAggregates ∪ IfcRelContainedInSpatialStructure, FORWARD
(iterate each rel's Related* → RelatingObject/Structure). NEVER
per-node model.Container — Container(storey) self-parents (a storey
is the RelatingStructure of its own containment rels).
order = topo BFS from roots; a parent always precedes its child, so
a consumer's parentID[*ParentIndex] lookup never misses.
geometry = joined to physical nodes by GlobalID (the emitted order is NOT
index-aligned with Scene.Elements once spatial nodes are interleaved).
type ImportNode ¶
type ImportNode struct {
GlobalID string
ExpressID int
IFCClass string
Name string
ParentIndex *int // index into the emitted slice; nil = import root
Qto model.Quantities
QuantitySource string
OriginMin [3]float64 // world-AABB min (transform.origin); zero if no geometry
BBoxMin [3]float64
BBoxMax [3]float64
Material string // model.Element.Material; "" when none
IsExternal *bool // *Common.IsExternal tri-state; nil when unknown
NetArea *float64 // trusted net area (m²) from Scene.NetAreas; nil when absent/untrusted
// OpeningPerimeter is the boundary length (m) of the opening union whose
// area NetArea already nets out — the length facade trades bill reveals
// along. Present exactly when NetArea is: both come from one trusted
// reconciliation, so a consumer can never read a confident perimeter beside
// an absent net.
OpeningPerimeter *float64
// OpeningDeduction is the area (m²) of that same opening union, and
// ProjectedGross is the host's own silhouette on the same plane — the two
// halves NetArea is the difference of. Both are measured on the host's
// winning projection axis, which is what makes them a matched pair.
//
// They are published because NetArea alone cannot be aggregated: hosts with
// no IfcRelVoidsElement are ABSENT from the reconciliation entirely, so
// summing NetArea over a facade silently drops every solid wall. Netting a
// total therefore means subtracting the DEDUCTION from whatever gross the
// caller is totalling, not summing the nets.
//
// That matters most when the gross being netted is measured differently.
// [geometry.Facing].FaceArea is the true on-face area and does not
// foreshorten, while these two are a projection and do. The projection
// foreshortens gross and deduction by the SAME factor, so that factor is
// recoverable as FaceArea/ProjectedGross and a caller netting an on-face
// gross computes:
//
// net = FaceArea - OpeningDeduction*(FaceArea/ProjectedGross)
//
// NOT FaceArea - OpeningDeduction, which under-deducts by that factor.
// Without ProjectedGross the bias is not merely uncorrected, it is
// invisible.
//
// Present exactly when NetArea is, for the reason OpeningPerimeter is: all
// four come from one trusted reconciliation. A zero deduction here means a
// host whose openings measured zero, never an untrusted one.
OpeningDeduction *float64
ProjectedGross *float64
// HasOpenings reports whether this element carries IfcRelVoidsElement
// openings at all, which is the fact the three nil-able fields above CANNOT
// express. They are absent for two opposite reasons — the host has no
// openings, so its net equals its gross; or its reconciliation was refused,
// so its net is unknown — and a consumer netting a total must tell those
// apart. Reading absence as "no openings" reports a fully-glazed wall as
// solid; reading it as "unknown" drops every solid wall from the total.
//
// Unlike the others this is always meaningful, so it is a plain bool: there
// is no third state to encode.
HasOpenings bool
// TypeGlobalID / TypeName / TypeClass identify the element's IfcTypeObject.
// Empty when the element carries no IfcRelDefinesByType — most elements do.
TypeGlobalID string
TypeName string
TypeClass string
}
ImportNode is one node of the import contract: spatial containers + physical elements in a single parents-first tree. Spatial nodes have no geometry, so their AABB stays zero.
type StoreyEntity ¶
StoreyEntity is one element's plan geometry on a storey: its footprint loops (world XY meters, Y-up), tagged by IFC class, keyed by GlobalID. Consumers typically resolve GlobalID to their own domain object id when rendering the storey's floor plan.
type StoreyPlan ¶
type StoreyPlan struct {
StoreyGlobalID string
Elevation float64 // meters, for UI ordering (StoreyElevations; 0 if absent)
Entities []StoreyEntity
}
StoreyPlan is one IfcBuildingStorey's 2D floor plan: the entities a horizontal section at cutZ = floorZ + 1.2 m draws, chosen by geometric membership.
type TypeLayerSet ¶
type TypeLayerSet struct {
Layers []model.MaterialLayer
Direction string
Sense string
}
TypeLayerSet is one IfcTypeObject's assembly build-up, in declared EXPRESS LIST order. Direction/Sense are the raw IFC labels from IfcMaterialLayerSetUsage (AXIS1/AXIS2/AXIS3, POSITIVE/NEGATIVE); mapping them onto a product vocabulary belongs to the consumer.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package geometry builds proxy geometry (a view of the semantic model, no CAD kernel) from a parsed IFC step.File plus the model package's model.Result, and emits a single Y-up GLB whose node names are element GlobalIds.
|
Package geometry builds proxy geometry (a view of the semantic model, no CAD kernel) from a parsed IFC step.File plus the model package's model.Result, and emits a single Y-up GLB whose node names are element GlobalIds. |
|
Package model walks a parsed STEP/IFC entity graph (see the step package) into a canonical semantic []Element.
|
Package model walks a parsed STEP/IFC entity graph (see the step package) into a canonical semantic []Element. |
|
Package step is a schema-agnostic STEP/SPF (ISO 10303-21) tokenizer and entity graph for IFC files, ported from ifcopenshell's parser and entity_instance model into idiomatic Go.
|
Package step is a schema-agnostic STEP/SPF (ISO 10303-21) tokenizer and entity graph for IFC files, ported from ifcopenshell's parser and entity_instance model into idiomatic Go. |