Parsing IFC Files¶
Complete guide to parsing IFC files with IFClite, covering both IFC4 (STEP) and IFC5 (IFCX) formats.
Overview¶
IFClite supports the columnar STEP parser for IFC4-style files and the IFCX parser for IFC5 JSON files:
| Aspect | Options |
|---|---|
| Processing | Client-side (WASM) or Server-side (Rust) |
| Format | IFC4 (STEP) or IFC5 (IFCX JSON) |
| Mode | Columnar or streaming |
Client-Side Parsing¶
Basic Parsing¶
import { IfcParser } from '@ifc-lite/parser';
const parser = new IfcParser();
const buffer = await fetch('model.ifc').then(r => r.arrayBuffer());
// Columnar parse (returns IfcDataStore - recommended for all STEP files)
const store = await parser.parseColumnar(buffer);
parseColumnar() is the canonical IFC STEP path. It centralizes scan selection
across pre-scanned indexes, the browser worker scanner, the WASM byte scanner,
and the TypeScript tokenizer fallback, then builds an IfcDataStore for
on-demand extraction. parse() remains as a compatibility adapter for callers
that still need the older eager ParseResult shape.
Columnar Parsing (Recommended)¶
The columnar parser returns an IfcDataStore with memory-efficient data structures:
const store = await parser.parseColumnar(buffer, {
onProgress: ({ phase, percent }) => {
console.log(`${phase}: ${percent}%`);
}
});
// Access entities by type
const wallIds = store.entityIndex.byType.get('IFCWALL') ?? [];
const doorIds = store.entityIndex.byType.get('IFCDOOR') ?? [];
// Access entity by ID
const entityRef = store.entityIndex.byId.get(123);
// Metadata
console.log(`Schema: ${store.schemaVersion}`); // IFC2X3, IFC4, IFC4X3, IFC5
console.log(`Entities: ${store.entityCount}`);
console.log(`Parse time: ${store.parseTime}ms`);
Browser Worker Mode¶
For non-blocking parsing in the browser:
import { WorkerParser } from '@ifc-lite/parser/browser';
const parser = new WorkerParser();
const store = await parser.parseColumnar(buffer, {
onProgress: ({ phase, percent }) => {
// Updates from worker thread
updateProgressUI(phase, percent);
}
});
// Clean up when done
parser.terminate();
Streaming Geometry¶
For large files, stream geometry progressively using GeometryProcessor.processStreaming():
import { GeometryProcessor } from '@ifc-lite/geometry';
const parser = new IfcParser();
const geometry = new GeometryProcessor();
await geometry.init();
// Parse first (fast, metadata only)
const store = await parser.parseColumnar(buffer);
// Stream geometry progressively
for await (const event of geometry.processStreaming(new Uint8Array(buffer))) {
switch (event.type) {
case 'start':
console.log(`Starting geometry extraction`);
break;
case 'batch':
// Add meshes to renderer as they arrive
renderer.addMeshes(event.meshes, true); // isStreaming = true
progressBar.value = event.totalSoFar; // cumulative mesh count (no percentage on batch events)
break;
case 'complete':
console.log(`Done: ${event.totalMeshes} meshes`);
renderer.fitToView();
break;
}
}
On-Demand Property Extraction¶
Properties and quantities are extracted lazily for better performance with large files:
import {
extractPropertiesOnDemand,
extractQuantitiesOnDemand,
extractEntityAttributesOnDemand
} from '@ifc-lite/parser';
// Parse without pre-loading properties
const store = await parser.parseColumnar(buffer);
// Extract properties only when needed
const wallId = wallIds[0];
const psets = extractPropertiesOnDemand(store, wallId);
for (const pset of psets) {
console.log(`Property Set: ${pset.name}`);
for (const prop of pset.properties) {
console.log(` ${prop.name}: ${prop.value}`);
}
}
// Extract quantities
const qsets = extractQuantitiesOnDemand(store, wallId);
for (const qset of qsets) {
console.log(`Quantity Set: ${qset.name}`);
for (const qty of qset.quantities) {
console.log(` ${qty.name}: ${qty.value} ${qty.type}`);
}
}
// Extract IFC attributes
const attrs = extractEntityAttributesOnDemand(store, wallId);
console.log(`Name: ${attrs.name}`);
console.log(`GlobalId: ${attrs.globalId}`);
console.log(`Description: ${attrs.description}`);
How On-Demand Works¶
IFC5 (IFCX) Parsing¶
IFClite natively supports the new IFC5 JSON-based format with ECS composition and USD geometry.
Format Detection¶
import { parseAuto } from '@ifc-lite/parser';
import { detectFormat } from '@ifc-lite/ifcx';
// Auto-detect and parse
const result = await parseAuto(buffer);
if (result.format === 'ifcx') {
// IFC5 file: parsed data lives under result.data, meshes at the top level
const { entities, spatialHierarchy } = result.data;
const meshes = result.meshes;
} else {
// IFC4 STEP file: the IfcDataStore is result.data
const store = result.data;
}
// Or detect format manually
const format = detectFormat(buffer); // 'ifc', 'ifcx', 'glb', or 'unknown'
.ifcZIP Containers¶
parseAuto (and every built-in loader — CLI, MCP, the viewer) transparently
unwraps the buildingSMART .ifcZIP container format: a zip archive wrapping
a single .ifc/.ifcxml file. Feed it the zip bytes directly — no manual
unzip step needed:
import { parseAuto, unwrapIfcZip } from '@ifc-lite/parser';
// parseAuto detects and unwraps .ifcZIP automatically
const result = await parseAuto(zipBuffer);
// Or unwrap explicitly (a no-op for a non-zip buffer)
const ifcBuffer = await unwrapIfcZip(zipBuffer);
Referenced resources inside the archive (textures, documents) are not
extracted — only the model file's bytes. An archive with zero or more than
one .ifc/.ifcxml entry throws rather than guessing which one to load.
Direct IFCX Parsing¶
import { parseIfcx } from '@ifc-lite/ifcx';
const result = await parseIfcx(buffer, {
onProgress: ({ phase, percent }) => {
console.log(`${phase}: ${percent}%`);
}
});
// IFC5 uses ECS (Entity-Component-System) composition
console.log(`Entities: ${result.entityCount}`);
console.log(`Meshes: ${result.meshes.length}`);
// Pre-tessellated USD geometry
for (const mesh of result.meshes) {
console.log(`Entity #${mesh.expressId}: ${mesh.ifcType}`);
// mesh.positions, mesh.normals, mesh.indices ready for GPU
}
// Same data structures as IFC4
console.log(`Schema: ${result.schemaVersion}`); // 'IFC5'
console.log(`Parse time: ${result.parseTime}ms`);
IFC5 Features¶
| Feature | Description |
|---|---|
| ECS Composition | Entities composed from components (attributes) |
| USD Geometry | Pre-tessellated meshes (no WASM triangulation needed) |
| Layer Semantics | Multiple nodes at same path merge (later overrides) |
| Namespace Attributes | Properties prefixed with namespace (e.g., bsi::ifc::prop::) |
| JSON Format | Human-readable, streamable |
IFC5 Data Model¶
// Entities include metadata
result.entities.forEach((entity, id) => {
console.log(`Entity #${id}: ${entity.type_name}`);
console.log(` GlobalId: ${entity.global_id}`);
console.log(` Name: ${entity.name}`);
console.log(` Has geometry: ${entity.has_geometry}`);
});
// Property sets grouped by namespace
result.propertySets.forEach((pset, id) => {
console.log(`PropertySet: ${pset.pset_name}`);
for (const prop of pset.properties) {
console.log(` ${prop.property_name}: ${prop.property_value}`);
}
});
// Spatial hierarchy
const hierarchy = result.spatialHierarchy;
console.log(`Project: ${hierarchy.project.name}`);
// Element-to-storey lookup
const storeyId = hierarchy.elementToStorey.get(wallId);
Server-Side Parsing¶
For production deployments, use the server for parallel processing and caching:
import { IfcServerClient } from '@ifc-lite/server-client';
const client = new IfcServerClient({
baseUrl: 'http://localhost:3001'
});
// Parquet format (15x smaller than JSON)
const result = await client.parseParquet(file);
// Streaming for large files (onBatch callback fires per geometry batch)
await client.parseParquetStream(file, (batch) => {
renderer.addMeshes(batch.meshes);
});
See the Server Guide for complete server documentation.
Parse Options¶
interface ParseOptions {
// Progress callback
onProgress?: (progress: { phase: string; percent: number }) => void;
// Diagnostic message callback
onDiagnostic?: (message: string) => void;
// Optional IfcAPI instance for WASM-accelerated entity scanning
wasmApi?: WasmScanApi;
// Yield budget for large incremental parses (higher finishes faster with longer main-thread slices)
yieldIntervalMs?: number;
// Keep property-set containers indexed but defer individual property/quantity atoms
deferPropertyAtomIndex?: boolean;
// Skip worker-based entity scanning and stay in-process
disableWorkerScan?: boolean;
// Called when spatial hierarchy is ready, before property/association parsing completes
onSpatialReady?: (partialStore: IfcDataStore) => void;
// Pre-built entity index from another worker (e.g. the streaming geometry pre-pass)
preScannedEntityIndex?: PreScannedEntityIndex;
}
const store = await parser.parseColumnar(buffer, {
deferPropertyAtomIndex: true,
onProgress: ({ phase, percent }) => console.log(`${phase}: ${percent}%`)
});
Tessellation and geometry quality are configured on the GeometryProcessor,
not on parseColumnar().
IfcDataStore Structure¶
interface IfcDataStore {
// Metadata
fileSize: number;
schemaVersion: 'IFC2X3' | 'IFC4' | 'IFC4X3' | 'IFC5';
entityCount: number;
parseTime: number;
// Raw source (for on-demand parsing)
source: Uint8Array;
// Entity index
entityIndex: {
byId: Map<number, EntityRef>; // expressId -> EntityRef
byType: Map<string, number[]>; // type -> [expressId, ...]
};
// Columnar tables
strings: StringTable; // Deduplicated strings
entities: EntityTable; // Entity metadata
properties: PropertyTable; // Pre-computed (or empty for on-demand)
quantities: QuantityTable; // Pre-computed (or empty for on-demand)
relationships: RelationshipGraph; // Relationship edges
// Spatial structures
spatialHierarchy?: SpatialHierarchy;
spatialIndex?: SpatialIndex;
// On-demand maps
onDemandPropertyMap?: Map<number, number[]>; // entityId -> [psetId, ...]
onDemandQuantityMap?: Map<number, number[]>; // entityId -> [qsetId, ...]
}
Spatial Hierarchy¶
import { IfcTypeEnum } from '@ifc-lite/data';
// spatialHierarchy is optional on IfcDataStore; guard before use
const hierarchy = store.spatialHierarchy;
if (!hierarchy) throw new Error('No spatial hierarchy in this model');
// Project structure
console.log(`Project: ${hierarchy.project.name}`);
// Navigate storeys (SpatialNode.type is a numeric IfcTypeEnum)
for (const child of hierarchy.project.children) {
if (child.type === IfcTypeEnum.IfcBuildingStorey) {
const storey = child;
console.log(`Storey: ${storey.name}`);
// Get elements on this storey (byStorey is keyed by the storey express id)
const elements = hierarchy.byStorey.get(storey.expressId) ?? [];
console.log(` Elements: ${elements.length}`);
// Get storey elevation
const elevation = hierarchy.storeyElevations.get(storey.expressId);
console.log(` Elevation: ${elevation}m`);
}
}
// Find storey for an element
const storeyId = hierarchy.elementToStorey.get(wallId);
Schema Support¶
| Schema | Entities | Status |
|---|---|---|
| IFC2X3 | 653 | Supported |
| IFC4 | 776 | Full Support |
| IFC4X3 | 876 | Supported |
| IFC5 (IFCX) | - | Beta |
Schema Registry¶
Access runtime schema metadata:
import {
SCHEMA_REGISTRY,
getEntityMetadata,
getAllAttributesForEntity,
isKnownEntity
} from '@ifc-lite/parser';
// Check if entity type is known
if (isKnownEntity('IFCWALL')) {
const meta = getEntityMetadata('IFCWALL');
console.log(`Parent: ${meta.parent}`); // 'IFCBUILDINGELEMENT'
console.log(`Abstract: ${meta.isAbstract}`); // false
// Get all attributes including inherited
const attrs = getAllAttributesForEntity('IFCWALL');
for (const attr of attrs) {
console.log(`${attr.name}: ${attr.type}`);
}
}
Advanced Extractors¶
Materials¶
import {
extractMaterials,
getMaterialForElement,
getMaterialNameForElement
} from '@ifc-lite/parser';
const materials = extractMaterials(store);
// Get material for an element
// getMaterialForElement returns a material express id (or undefined)
const materialId = getMaterialForElement(wallId, materials);
if (materialId !== undefined) {
const material = materials.materials.get(materialId);
if (material) {
console.log(`Material: ${material.name}`);
}
// Layered materials resolve to a MaterialLayerSet of layer ids
const layerSet = materials.materialLayerSets.get(materialId);
if (layerSet) {
for (const layerId of layerSet.layers) {
const layer = materials.materialLayers.get(layerId);
if (!layer) continue;
const layerMat = materials.materials.get(layer.material);
console.log(` Layer: ${layerMat?.name ?? layer.material} (${layer.thickness}mm)`);
}
}
}
Georeferencing¶
import {
extractGeoreferencing,
transformToWorld,
transformToLocal
} from '@ifc-lite/parser';
const georef = extractGeoreferencing(store);
if (georef) {
console.log(`CRS: ${georef.projectedCRS?.name}`);
console.log(`Eastings: ${georef.mapConversion?.eastings}`);
console.log(`Northings: ${georef.mapConversion?.northings}`);
// Transform a local coordinate (tuple) to world; returns a tuple or null
const world = transformToWorld([10, 20, 0], georef);
if (world) {
console.log(`World: ${world[0]}, ${world[1]}, ${world[2]}`);
}
}
Classifications¶
import {
extractClassifications,
getClassificationsForElement,
groupElementsByClassification
} from '@ifc-lite/parser';
const classifications = extractClassifications(store);
// Get classifications for an element
const codes = getClassificationsForElement(wallId, classifications);
for (const code of codes) {
console.log(`${code.identification} - ${code.name}`);
// e.g., "Pr_60_10_32 - External walls" (owning system via code.referencedSource)
}
// Group elements by classification
const groups = groupElementsByClassification(classifications);
groups.forEach((elementIds, code) => {
console.log(`${code}: ${elementIds.length} elements`);
});
Error Handling¶
import { IfcParser } from '@ifc-lite/parser';
const parser = new IfcParser();
try {
const store = await parser.parseColumnar(buffer);
} catch (error) {
// parseColumnar throws standard Error instances on malformed STEP syntax,
// unknown or unsupported schemas, and other parse failures.
if (error instanceof Error) {
console.error(`Parse failed: ${error.message}`);
} else {
throw error;
}
}
Performance Comparison¶
| Mode | Use Case | Memory | Speed |
|---|---|---|---|
parse() |
Small files, full object access | High | Moderate |
parseColumnar() |
Most use cases | Low | Fast |
GeometryProcessor.processStreaming() |
Large files (>50MB) | Very Low | Progressive |
| Server | Production, caching | Server-side | Fastest (cached) |
Performance Tips¶
- Use columnar parsing -
parseColumnar()for best memory efficiency - Use on-demand properties - Don't pre-load all properties
- Use workers - Import from
@ifc-lite/parser/browserfor non-blocking - Use server for large files - Parallel processing and caching
- Filter entity types - Exclude IFCSPACE, IFCOPENINGELEMENT if not needed
Multi-Model Loading¶
When working with multiple IFC files (e.g., architectural, structural, and MEP models), use the federation system to load and coordinate them with unified selection and visibility. Each model receives an ID offset to prevent express ID collisions across files. See the Federation Guide for details on multi-model loading, global ID resolution, and coordinated visibility control.
Next Steps¶
- Server Guide - Server-based parsing with caching
- Geometry Guide - Process geometry
- Query Guide - Query parsed data
- Federation Guide - Load and coordinate multiple models
- API Reference - Complete API docs