threejs-impl-ifc-viewer

Installation
SKILL.md

threejs-impl-ifc-viewer

Quick Reference

Library Comparison

Library License Level Status Use Case
web-ifc MIT Low-level WASM parser Active (v0.77+) Custom pipelines, full control
@thatopen/components MIT (v3.x) High-level BIM toolkit Active (v3.3.2+) Full BIM viewers, rapid prototyping
@thatopen/components-front MIT Browser-specific extensions Active UI, advanced visualization
web-ifc-three Deprecated bridge DEAD NEVER use
openbim-components (v1) Deprecated toolkit DEAD NEVER use

Package Installation

# Low-level approach (MIT, full control)
npm install web-ifc three

# High-level approach (MIT, batteries-included)
npm install @thatopen/components @thatopen/components-front web-ifc three

Critical Warnings

LICENSE HISTORY WARNING: The @thatopen packages (v3.x) are licensed under MIT. However, older IFC.js ecosystem packages (openbim-components v1, IFC.js v0.x) used AGPL-3.0. ALWAYS verify the license of the exact package version you install. If using any package with AGPL-3.0, your entire application MUST be open-sourced under AGPL-3.0 or a compatible license.

NEVER use web-ifc-three -- it is deprecated and unmaintained. Use web-ifc directly or @thatopen/components instead.

NEVER use openbim-components (v1) -- it is deprecated. Use @thatopen/components (v3.x) instead.

NEVER load entire IFC geometry at once for files >50MB -- ALWAYS stream or load geometry on demand. WASM memory is limited and cannot be reclaimed without page reload.

NEVER forget to call ifcApi.CloseModel(modelID) after processing -- WASM memory leaks are permanent until page reload.

ALWAYS call components.dispose() when unmounting a @thatopen viewer -- failure to do so leaks GPU memory, Three.js objects, and event listeners.

ALWAYS initialize IfcAPI asynchronously -- await ifcApi.Init() MUST complete before any model operations.


Approach 1: web-ifc (Low-Level WASM Parser)

When to Use

  • You need full control over geometry extraction and rendering
  • You are building a custom rendering pipeline
  • You want MIT-licensed code only
  • You need to extract IFC properties without rendering geometry

WASM Setup

import * as WebIFC from 'web-ifc';

const ifcApi = new WebIFC.IfcAPI();
ifcApi.SetWasmPath('./');  // MUST point to directory containing web-ifc.wasm
await ifcApi.Init();       // ALWAYS await before any operations

WASM file requirement: The web-ifc.wasm file MUST be served from a location accessible by the browser. Copy it from node_modules/web-ifc/ to your public/static directory. Bundlers like Vite require explicit configuration to serve WASM files.

IfcAPI Core Methods

Method Signature Description
Init() () => Promise<void> Initialize WASM module
SetWasmPath(path) (string) => void Set directory containing .wasm file
OpenModel(data, settings?) (Uint8Array, object?) => number Load IFC data, returns modelID
CloseModel(modelID) (number) => void Release model memory
GetGeometry(modelID, expressID) (number, number) => object Get geometry for element
GetFlatMesh(modelID, expressID) (number, number) => FlatMesh Get flattened mesh data
GetPlacedGeometry(modelID, pg) (number, PlacedGeometry) => MeshData Get positioned geometry with transform
GetLine(modelID, expressID, flatten?) (number, number, boolean?) => object Read single IFC entity by expressID
GetAllLines(modelID) (number) => number[] Get all express IDs in model
GetLineIDsWithType(modelID, type) (number, number) => number[] Get IDs by IFC type constant
GetAllTypesOfModel(modelID) (number) => TypeInfo[] List all entity types in model

Geometry Extraction Pattern

const modelID = ifcApi.OpenModel(ifcData); // ifcData: Uint8Array

const wallIDs = ifcApi.GetLineIDsWithType(modelID, WebIFC.IFCWALL);
for (const wallID of wallIDs) {
  const flatMesh = ifcApi.GetFlatMesh(modelID, wallID);
  for (const pg of flatMesh.geometries) {
    const meshData = ifcApi.GetPlacedGeometry(modelID, pg);
    // meshData.vertexData: Float32Array (interleaved position + normal, 6 floats per vertex)
    // meshData.indexData: Uint32Array (triangle indices)
    // pg.flatTransformation: Float64Array (4x4 column-major matrix)
    // pg.color: { x: r, y: g, z: b, w: alpha } (0-1 range)
  }
}

ifcApi.CloseModel(modelID); // ALWAYS release when done

Converting web-ifc Geometry to Three.js

import * as THREE from 'three';

function ifcMeshToThree(meshData, placedGeometry) {
  const { vertexData, indexData } = meshData;

  // vertexData is interleaved: [px, py, pz, nx, ny, nz, px, py, pz, nx, ny, nz, ...]
  const posFloats = new Float32Array(vertexData.length / 2);
  const normFloats = new Float32Array(vertexData.length / 2);
  for (let i = 0; i < vertexData.length; i += 6) {
    const j = i / 2;
    posFloats[j]     = vertexData[i];
    posFloats[j + 1] = vertexData[i + 1];
    posFloats[j + 2] = vertexData[i + 2];
    normFloats[j]     = vertexData[i + 3];
    normFloats[j + 1] = vertexData[i + 4];
    normFloats[j + 2] = vertexData[i + 5];
  }

  const geometry = new THREE.BufferGeometry();
  geometry.setAttribute('position', new THREE.BufferAttribute(posFloats, 3));
  geometry.setAttribute('normal', new THREE.BufferAttribute(normFloats, 3));
  geometry.setIndex(new THREE.BufferAttribute(indexData, 1));

  const { x: r, y: g, z: b, w: a } = placedGeometry.color;
  const material = new THREE.MeshPhongMaterial({
    color: new THREE.Color(r, g, b),
    opacity: a,
    transparent: a < 1,
    side: THREE.DoubleSide,
  });

  const mesh = new THREE.Mesh(geometry, material);

  // Apply the 4x4 transform matrix (column-major)
  const mat = new THREE.Matrix4();
  mat.fromArray(placedGeometry.flatTransformation);
  mesh.applyMatrix4(mat);

  return mesh;
}

IFC Property Extraction

// Read a specific entity
const wall = ifcApi.GetLine(modelID, wallExpressID, true); // flatten=true resolves references
console.log(wall.Name?.value);       // "Basic Wall:Generic - 200mm"
console.log(wall.GlobalId?.value);   // IFC GUID

// Get all property sets for an element
const relDefines = ifcApi.GetLineIDsWithType(modelID, WebIFC.IFCRELDEFINESBYPROPERTIES);
for (const relID of relDefines) {
  const rel = ifcApi.GetLine(modelID, relID, false);
  // Check if this relation references our element
  // rel.RelatedObjects contains expressIDs of related elements
  // rel.RelatingPropertyDefinition points to the property set
}

IFC Type Constants

Constant IFC Entity
WebIFC.IFCWALL Walls
WebIFC.IFCWALLSTANDARDCASE Standard walls
WebIFC.IFCSLAB Slabs / floors
WebIFC.IFCCOLUMN Columns
WebIFC.IFCBEAM Beams
WebIFC.IFCDOOR Doors
WebIFC.IFCWINDOW Windows
WebIFC.IFCROOF Roofs
WebIFC.IFCSTAIR Stairs
WebIFC.IFCSPACE Spaces / rooms
WebIFC.IFCSITE Site
WebIFC.IFCBUILDING Building
WebIFC.IFCBUILDINGSTOREY Storey / floor level
WebIFC.IFCPROJECT Project root
WebIFC.IFCRELDEFINESBYPROPERTIES Property set relations
WebIFC.IFCPROPERTYSET Property sets
WebIFC.IFCPROPERTYSINGLEVALUE Individual property values

Approach 2: @thatopen/components (High-Level BIM Toolkit)

When to Use

  • You need a full-featured BIM viewer quickly
  • You want built-in fragment optimization for large models
  • You need element highlighting, section planes, or floor plans
  • MIT license (v3.x) is acceptable for your project

Architecture Setup

import * as OBC from '@thatopen/components';

const components = new OBC.Components();
const worlds = components.get(OBC.Worlds);
const world = worlds.create();

// Scene
world.scene = new OBC.SimpleScene(components);
world.scene.setup(); // Adds default lights and grid

// Camera
world.camera = new OBC.SimpleCamera(components);
world.camera.controls.setLookAt(10, 10, 10, 0, 0, 0);

// Renderer (container is an HTMLDivElement)
world.renderer = new OBC.SimpleRenderer(components, container);

// ALWAYS call init after setup
components.init();

Loading IFC Files

const ifcLoader = components.get(OBC.IfcLoader);
await ifcLoader.setup(); // Downloads and initializes WASM

// Load from file input
const file = event.target.files[0];
const data = new Uint8Array(await file.arrayBuffer());
const model = await ifcLoader.load(data);

// model is added to the active world automatically
// model contains Three.js Group with fragment meshes

Key Components

Component Access Pattern Purpose
OBC.Components new OBC.Components() Central manager for all subsystems
OBC.Worlds components.get(OBC.Worlds) Multi-world environment management
OBC.SimpleScene Constructor Three.js scene wrapper with defaults
OBC.SimpleCamera Constructor Camera with built-in orbit controls
OBC.SimpleRenderer Constructor WebGL renderer bound to DOM element
OBC.IfcLoader components.get(OBC.IfcLoader) IFC file loading and fragment conversion
OBC.FragmentsManager components.get(OBC.FragmentsManager) Efficient batched geometry management
OBC.Highlighter components.get(OBC.Highlighter) Element selection and highlighting
OBC.Clipper components.get(OBC.Clipper) Section plane tools
OBC.Plans components.get(OBC.Plans) Floor plan generation

Fragment System

The fragment system converts IFC geometry into batched draw calls. This is critical for performance with large BIM models (10,000+ elements).

  • Each IFC type gets batched into a single fragment mesh
  • Fragments share materials where possible, minimizing state changes
  • Individual elements can still be picked, highlighted, and hidden by expressID
  • ALWAYS use fragments for models with >1,000 elements

Cleanup

// ALWAYS dispose when unmounting the viewer
components.dispose();
// This releases: Three.js scene, renderer, geometries, materials, textures,
// WASM memory, event listeners, and all component state

IFC Spatial Tree

IFC files follow a hierarchical spatial structure:

IFCPROJECT
  └── IFCSITE
        └── IFCBUILDING
              ├── IFCBUILDINGSTOREY (Level 0)
              │     ├── IFCWALL
              │     ├── IFCSLAB
              │     └── IFCSPACE
              └── IFCBUILDINGSTOREY (Level 1)
                    ├── IFCWALL
                    ├── IFCCOLUMN
                    └── IFCDOOR

Spatial containment is defined by IFCRELCONTAINEDINSPATIALSTRUCTURE and IFCRELAGGREGATES relationships. ALWAYS traverse these relationships to build a navigable tree -- do NOT assume flat element lists represent the building structure.


Memory Management for Large IFC Files

File Size Strategy
<10MB Load entirely, render all geometry at once
10-50MB Load entirely, use fragment batching
50-200MB Stream geometry by storey or type, dispose unused
>200MB Server-side preprocessing into fragments, load on demand

Rules

  • ALWAYS monitor WASM heap usage -- web-ifc allocates in WASM linear memory which has a hard cap
  • ALWAYS dispose geometry that is not currently visible (off-screen storeys, hidden types)
  • NEVER keep duplicate geometry in both WASM and JavaScript heap
  • ALWAYS use BufferGeometry (never legacy Geometry)
  • ALWAYS call geometry.dispose() and material.dispose() when removing meshes from scene

Reference Links

Official Sources

Related skills
Installs
8
GitHub Stars
1
First Seen
Apr 1, 2026