
Threejs Impl Ifc Viewer
- 18 installs
- 11 repo stars
- Updated July 8, 2026
- openaec-foundation/three.js-claude-skill-package
Helps with ai & agent building tasks.
About
threejs-impl-ifc-viewer is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- threejs-impl-ifc-viewer
- AI & Agent Building
- AI-coding skill
Threejs Impl Ifc Viewer by the numbers
- 18 all-time installs (skills.sh)
- +2 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #10,736 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/three.js-claude-skill-package --skill threejs-impl-ifc-viewerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 18 |
|---|---|
| repo stars | ★ 11 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/three.js-claude-skill-package ↗ |
What it does
Helps with ai & agent building tasks.
Files
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 threeCritical 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 operationsWASM 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 doneConverting 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 meshesKey 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
└── IFCDOORSpatial 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 legacyGeometry) - ALWAYS call
geometry.dispose()andmaterial.dispose()when removing meshes from scene
---
Reference Links
- references/methods.md -- Complete API signatures for web-ifc and @thatopen/components
- references/examples.md -- Working code examples for IFC loading and viewing
- references/anti-patterns.md -- What NOT to do when working with IFC in Three.js
Official Sources
- https://github.com/IFCjs/web-ifc
- https://github.com/ThatOpen/engine_components
- https://docs.thatopen.com/
- https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/
Anti-Patterns (IFC/BIM Viewer)
1. Using Deprecated web-ifc-three
// WRONG: web-ifc-three is deprecated and unmaintained
import { IFCLoader } from 'web-ifc-three/IFCLoader';
const loader = new IFCLoader();
loader.ifcManager.setWasmPath('./');
const model = await loader.loadAsync('/model.ifc');
// CORRECT: Use web-ifc directly with manual Three.js conversion
import * as WebIFC from 'web-ifc';
const ifcApi = new WebIFC.IfcAPI();
ifcApi.SetWasmPath('./');
await ifcApi.Init();
// ... extract geometry and convert to Three.js BufferGeometry
// CORRECT: Or use @thatopen/components for a high-level approach
import * as OBC from '@thatopen/components';
const components = new OBC.Components();
const ifcLoader = components.get(OBC.IfcLoader);
await ifcLoader.setup();
const model = await ifcLoader.load(data);WHY: web-ifc-three has not been updated since 2023. It has unresolved bugs, lacks support for newer IFC schemas, and will NEVER receive security patches. The IFC.js project officially migrated to @thatopen/components.
---
2. Forgetting to Close Models (WASM Memory Leak)
// WRONG: Model is never closed, WASM memory leaks permanently
async function loadAndRender(ifcData) {
const modelID = ifcApi.OpenModel(ifcData);
const walls = ifcApi.GetLineIDsWithType(modelID, WebIFC.IFCWALL);
// ... render walls
// modelID is lost -- WASM memory is leaked until page reload
}
// CORRECT: ALWAYS close the model when done
async function loadAndRender(ifcData) {
const modelID = ifcApi.OpenModel(ifcData);
try {
const walls = ifcApi.GetLineIDsWithType(modelID, WebIFC.IFCWALL);
// ... render walls
} finally {
ifcApi.CloseModel(modelID);
}
}WHY: WASM linear memory cannot be partially freed. Every OpenModel allocates memory inside the WASM heap. Without CloseModel, that memory is permanently occupied until the browser tab is refreshed or closed.
---
3. Loading All Geometry at Once for Large Files
// WRONG: Loading 200MB IFC file entirely into WASM and extracting all geometry
const modelID = ifcApi.OpenModel(hugeIfcData); // 200MB Uint8Array
const allLines = ifcApi.GetAllLines(modelID);
for (const id of allLines) {
const mesh = ifcApi.GetFlatMesh(modelID, id); // Extracts ALL geometry at once
// ... thousands of Three.js meshes created simultaneously
}
// CORRECT: Load by storey or element type, dispose when not visible
const storeyIDs = ifcApi.GetLineIDsWithType(modelID, WebIFC.IFCBUILDINGSTOREY);
for (const storeyID of storeyIDs) {
// Only load geometry for elements in this storey
// Hide/dispose storeys that are not currently viewed
}WHY: Large IFC files can produce millions of triangles. Loading all geometry simultaneously exhausts both WASM heap memory and GPU memory. The browser will either crash with an OOM error or become unresponsive. ALWAYS load incrementally and dispose unused geometry.
---
4. Skipping components.init() or components.dispose()
// WRONG: Missing init -- subsystems are not started
const components = new OBC.Components();
const worlds = components.get(OBC.Worlds);
const world = worlds.create();
world.scene = new OBC.SimpleScene(components);
world.renderer = new OBC.SimpleRenderer(components, container);
// Forgot components.init() -- render loop never starts
// WRONG: Missing dispose -- memory leaks on unmount
function destroyViewer() {
container.innerHTML = ''; // DOM cleared but Three.js objects still in memory
}
// CORRECT: ALWAYS init after setup, ALWAYS dispose on teardown
const components = new OBC.Components();
// ... setup world, scene, camera, renderer ...
components.init(); // Start render loop and subsystems
// On teardown:
components.dispose(); // Releases ALL resourcesWHY: components.init() starts the render loop and initializes all registered subsystems. Without it, nothing renders. components.dispose() releases Three.js scenes, renderers, geometries, materials, textures, WASM memory, and event listeners. Without it, every viewer mount/unmount cycle leaks GPU and CPU memory.
---
5. Ignoring License Implications
// WRONG: Using AGPL-licensed code in a proprietary application
// Old IFC.js packages (openbim-components v1) were AGPL-3.0
// Using them in closed-source software violates the license
import { Components } from 'openbim-components'; // AGPL-3.0!
// CORRECT: Use @thatopen/components v3.x (MIT licensed)
import * as OBC from '@thatopen/components'; // MIT
// CORRECT: Or use web-ifc directly (MIT licensed)
import * as WebIFC from 'web-ifc'; // MITWHY: AGPL-3.0 requires that ANY application using the library (including over a network) must release its complete source code under AGPL-3.0. This applies even if the AGPL code runs server-side. The @thatopen packages v3.x moved to MIT, but ALWAYS verify the license field in package.json of your installed version.
---
6. Not Setting WASM Path Before Init
// WRONG: Init without setting WASM path -- fails to find .wasm file
const ifcApi = new WebIFC.IfcAPI();
await ifcApi.Init(); // Error: web-ifc.wasm not found
// WRONG: Setting WASM path AFTER Init
const ifcApi = new WebIFC.IfcAPI();
await ifcApi.Init();
ifcApi.SetWasmPath('./wasm/'); // Too late -- Init already failed or used wrong path
// CORRECT: Set WASM path BEFORE Init
const ifcApi = new WebIFC.IfcAPI();
ifcApi.SetWasmPath('./wasm/'); // MUST point to directory with web-ifc.wasm
await ifcApi.Init();WHY: SetWasmPath tells the WASM loader where to find the .wasm binary. If not set before Init(), the loader searches relative to the page URL and will fail in most bundler configurations. The WASM file MUST be copied to the public/static assets directory.
---
7. Not Disposing Three.js Geometry and Materials
// WRONG: Removing mesh from scene without disposing GPU resources
scene.remove(ifcMesh);
// geometry and material are still in GPU memory
// CORRECT: ALWAYS dispose geometry and materials when removing meshes
function removeIfcMesh(mesh, scene) {
scene.remove(mesh);
mesh.geometry.dispose();
if (Array.isArray(mesh.material)) {
mesh.material.forEach((m) => m.dispose());
} else {
mesh.material.dispose();
}
}
// For groups with many children:
function disposeGroup(group, scene) {
group.traverse((child) => {
if (child.isMesh) {
child.geometry.dispose();
if (Array.isArray(child.material)) {
child.material.forEach((m) => m.dispose());
} else {
child.material.dispose();
}
}
});
scene.remove(group);
}WHY: Three.js allocates GPU buffers for each geometry and material. scene.remove() only detaches the object from the scene graph -- it does NOT free GPU memory. Without explicit dispose() calls, GPU memory usage grows with every model load/unload cycle until the tab crashes.
---
8. Using GetLine with flatten=true on Large Models
// WRONG: Flattening all entities in a loop (extremely slow for large models)
const allIDs = ifcApi.GetAllLines(modelID);
for (const id of allIDs) {
const entity = ifcApi.GetLine(modelID, id, true); // flatten=true resolves ALL references
processEntity(entity);
}
// CORRECT: Use flatten=false by default, only flatten when you need resolved references
const allIDs = ifcApi.GetAllLines(modelID);
for (const id of allIDs) {
const entity = ifcApi.GetLine(modelID, id, false); // Fast -- returns reference IDs
if (needsDetails(entity)) {
const detailed = ifcApi.GetLine(modelID, id, true); // Only flatten when needed
processDetailedEntity(detailed);
}
}WHY: flatten=true recursively resolves all entity references, which triggers many additional WASM calls per entity. On a model with 50,000+ entities, flattening every line can take minutes. ALWAYS use flatten=false for bulk operations and only flatten specific entities when their resolved properties are needed.
Working Code Examples (IFC/BIM Viewer)
Example 1: Minimal web-ifc Viewer with Three.js
import * as THREE from 'three';
import * as WebIFC from 'web-ifc';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// Scene setup
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f0f0);
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(15, 15, 15);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
scene.add(new THREE.AmbientLight(0xffffff, 0.6));
scene.add(new THREE.DirectionalLight(0xffffff, 0.8));
// Initialize web-ifc
const ifcApi = new WebIFC.IfcAPI();
ifcApi.SetWasmPath('./wasm/'); // Directory containing web-ifc.wasm
await ifcApi.Init();
// Load IFC file from fetch
const response = await fetch('/models/building.ifc');
const buffer = await response.arrayBuffer();
const ifcData = new Uint8Array(buffer);
const modelID = ifcApi.OpenModel(ifcData);
// Extract and render all geometry
const allTypes = ifcApi.GetAllTypesOfModel(modelID);
for (const typeInfo of allTypes) {
const ids = ifcApi.GetLineIDsWithType(modelID, typeInfo.typeID);
for (const id of ids) {
try {
const flatMesh = ifcApi.GetFlatMesh(modelID, id);
for (const pg of flatMesh.geometries) {
const meshData = ifcApi.GetPlacedGeometry(modelID, pg);
const threeMesh = convertToThreeMesh(meshData, pg);
threeMesh.userData.expressID = id;
scene.add(threeMesh);
}
} catch {
// Not all entities have geometry -- skip silently
}
}
}
ifcApi.CloseModel(modelID); // Release WASM memory
function convertToThreeMesh(meshData, pg) {
const { vertexData, indexData } = meshData;
const vertexCount = vertexData.length / 6;
const positions = new Float32Array(vertexCount * 3);
const normals = new Float32Array(vertexCount * 3);
for (let i = 0; i < vertexCount; i++) {
positions[i * 3] = vertexData[i * 6];
positions[i * 3 + 1] = vertexData[i * 6 + 1];
positions[i * 3 + 2] = vertexData[i * 6 + 2];
normals[i * 3] = vertexData[i * 6 + 3];
normals[i * 3 + 1] = vertexData[i * 6 + 4];
normals[i * 3 + 2] = vertexData[i * 6 + 5];
}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3));
geometry.setIndex(new THREE.BufferAttribute(indexData, 1));
const { x: r, y: g, z: b, w: a } = pg.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);
const mat = new THREE.Matrix4().fromArray(pg.flatTransformation);
mesh.applyMatrix4(mat);
return mesh;
}
// Render loop
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
animate();---
Example 2: @thatopen/components Full BIM Viewer
import * as OBC from '@thatopen/components';
// Container element
const container = document.getElementById('viewer-container');
// Initialize components
const components = new OBC.Components();
const worlds = components.get(OBC.Worlds);
const world = worlds.create();
world.scene = new OBC.SimpleScene(components);
world.scene.setup();
world.camera = new OBC.SimpleCamera(components);
world.camera.controls.setLookAt(20, 20, 20, 0, 0, 0);
world.renderer = new OBC.SimpleRenderer(components, container);
components.init();
// Load IFC
const ifcLoader = components.get(OBC.IfcLoader);
await ifcLoader.setup();
const fileInput = document.getElementById('ifc-input');
fileInput.addEventListener('change', async (event) => {
const file = event.target.files[0];
if (!file) return;
const data = new Uint8Array(await file.arrayBuffer());
const model = await ifcLoader.load(data);
// model is automatically added to the world scene
console.log('Loaded model with', model.children.length, 'fragment meshes');
});
// Cleanup on page unload
window.addEventListener('beforeunload', () => {
components.dispose();
});---
Example 3: IFC Element Picking with Raycasting
import * as THREE from 'three';
import * as WebIFC from 'web-ifc';
// Assumes scene, camera, renderer already set up
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();
const ifcMeshes = []; // Populated during IFC loading
// Store expressID on each mesh during loading
function addIfcMesh(mesh, expressID) {
mesh.userData.expressID = expressID;
ifcMeshes.push(mesh);
scene.add(mesh);
}
// Highlight material
const highlightMaterial = new THREE.MeshPhongMaterial({
color: 0xff6600,
opacity: 0.8,
transparent: true,
side: THREE.DoubleSide,
});
let previousSelection = null;
let previousMaterial = null;
renderer.domElement.addEventListener('click', (event) => {
// Restore previous selection
if (previousSelection) {
previousSelection.material = previousMaterial;
}
pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster.intersectObjects(ifcMeshes);
if (intersects.length > 0) {
const hit = intersects[0].object;
previousSelection = hit;
previousMaterial = hit.material;
hit.material = highlightMaterial;
console.log('Selected element expressID:', hit.userData.expressID);
// Read properties from web-ifc (if model still open)
const entity = ifcApi.GetLine(modelID, hit.userData.expressID, true);
console.log('Entity type:', entity.constructor.name);
console.log('Name:', entity.Name?.value);
}
});---
Example 4: Extracting IFC Spatial Tree
import * as WebIFC from 'web-ifc';
function buildSpatialTree(ifcApi, modelID) {
const tree = {};
// Get the project (root node)
const projectIDs = ifcApi.GetLineIDsWithType(modelID, WebIFC.IFCPROJECT);
if (projectIDs.length === 0) return tree;
function getChildren(parentID) {
const node = {
expressID: parentID,
entity: ifcApi.GetLine(modelID, parentID, false),
children: [],
elements: [],
};
node.name = node.entity.Name?.value || `#${parentID}`;
// Aggregated children (IFCSITE -> IFCBUILDING -> IFCBUILDINGSTOREY)
const aggIDs = ifcApi.GetLineIDsWithType(modelID, WebIFC.IFCRELAGGREGATES);
for (const aggID of aggIDs) {
const rel = ifcApi.GetLine(modelID, aggID, false);
if (rel.RelatingObject?.value === parentID) {
const related = rel.RelatedObjects;
for (const ref of related) {
node.children.push(getChildren(ref.value));
}
}
}
// Contained elements (storey -> walls, slabs, etc.)
const containIDs = ifcApi.GetLineIDsWithType(
modelID, WebIFC.IFCRELCONTAINEDINSPATIALSTRUCTURE
);
for (const containID of containIDs) {
const rel = ifcApi.GetLine(modelID, containID, false);
if (rel.RelatingStructure?.value === parentID) {
for (const ref of rel.RelatedElements) {
node.elements.push({
expressID: ref.value,
entity: ifcApi.GetLine(modelID, ref.value, false),
});
}
}
}
return node;
}
return getChildren(projectIDs[0]);
}
// Usage
const spatialTree = buildSpatialTree(ifcApi, modelID);
console.log(JSON.stringify(spatialTree, null, 2));---
Example 5: Loading IFC by Storey (Memory-Efficient)
import * as WebIFC from 'web-ifc';
import * as THREE from 'three';
async function loadByStorey(ifcApi, modelID, scene) {
const storeyIDs = ifcApi.GetLineIDsWithType(modelID, WebIFC.IFCBUILDINGSTOREY);
const storeyGroups = new Map();
for (const storeyID of storeyIDs) {
const storey = ifcApi.GetLine(modelID, storeyID, false);
const storeyName = storey.Name?.value || `Storey #${storeyID}`;
const group = new THREE.Group();
group.name = storeyName;
group.userData.expressID = storeyID;
// Find elements contained in this storey
const containIDs = ifcApi.GetLineIDsWithType(
modelID, WebIFC.IFCRELCONTAINEDINSPATIALSTRUCTURE
);
for (const containID of containIDs) {
const rel = ifcApi.GetLine(modelID, containID, false);
if (rel.RelatingStructure?.value !== storeyID) continue;
for (const ref of rel.RelatedElements) {
try {
const flatMesh = ifcApi.GetFlatMesh(modelID, ref.value);
for (const pg of flatMesh.geometries) {
const meshData = ifcApi.GetPlacedGeometry(modelID, pg);
const mesh = convertToThreeMesh(meshData, pg); // from Example 1
mesh.userData.expressID = ref.value;
group.add(mesh);
}
} catch {
// Element has no geometry -- skip
}
}
}
scene.add(group);
storeyGroups.set(storeyID, group);
}
return storeyGroups;
}
// Toggle storey visibility
function setStoreyVisible(storeyGroups, storeyID, visible) {
const group = storeyGroups.get(storeyID);
if (group) {
group.visible = visible;
}
}
// Dispose a storey to free GPU memory
function disposeStorey(storeyGroups, storeyID, scene) {
const group = storeyGroups.get(storeyID);
if (!group) return;
group.traverse((child) => {
if (child.isMesh) {
child.geometry.dispose();
if (Array.isArray(child.material)) {
child.material.forEach((m) => m.dispose());
} else {
child.material.dispose();
}
}
});
scene.remove(group);
storeyGroups.delete(storeyID);
}API Signatures Reference (IFC/BIM Viewer)
web-ifc — IfcAPI
The central class for all low-level IFC operations. ALWAYS initialize asynchronously before use.
class IfcAPI {
// Initialization
SetWasmPath(path: string): void
Init(): Promise<void>
// Model lifecycle
OpenModel(data: Uint8Array, settings?: LoaderSettings): number
CloseModel(modelID: number): void
IsModelOpen(modelID: number): boolean
// Geometry extraction
GetGeometry(modelID: number, expressID: number): IfcGeometry
GetFlatMesh(modelID: number, expressID: number): FlatMesh
GetPlacedGeometry(modelID: number, pg: PlacedGeometry): MeshData
// Entity access
GetLine(modelID: number, expressID: number, flatten?: boolean): object
GetAllLines(modelID: number): number[]
GetLineIDsWithType(modelID: number, type: number): number[]
GetAllTypesOfModel(modelID: number): TypeInfo[]
// Entity modification
WriteLine(modelID: number, lineObject: object): void
CreateModel(settings?: LoaderSettings): number
// Coordinate system
GetCoordinationMatrix(modelID: number): number[]
SetGeometryTransformation(modelID: number, transformationMatrix: number[]): void
}LoaderSettings
interface LoaderSettings {
COORDINATE_TO_ORIGIN?: boolean; // Move model to origin
USE_FAST_BOOLS?: boolean; // Faster boolean operations (less accurate)
CIRCLE_SEGMENTS_LOW?: number; // Segment count for small circles (default: 5)
CIRCLE_SEGMENTS_MEDIUM?: number; // Segment count for medium circles (default: 8)
CIRCLE_SEGMENTS_HIGH?: number; // Segment count for large circles (default: 12)
BOOL_ABORT_THRESHOLD?: number; // Abort boolean ops after N iterations
}FlatMesh
interface FlatMesh {
geometries: PlacedGeometry[]; // Array of geometry placements
expressID: number; // ExpressID of the IFC element
}PlacedGeometry
interface PlacedGeometry {
color: { x: number; y: number; z: number; w: number }; // RGBA (0-1)
flatTransformation: Float64Array; // 4x4 column-major transform matrix
geometryExpressID: number; // ExpressID of the geometry definition
}MeshData
interface MeshData {
vertexData: Float32Array; // Interleaved [px, py, pz, nx, ny, nz, ...] per vertex
indexData: Uint32Array; // Triangle indices
}---
@thatopen/components — Core API
Components (Central Manager)
class Components {
get<T extends Component>(ComponentClass: new (...args: any[]) => T): T
init(): void
dispose(): void
readonly enabled: boolean
}Worlds
class Worlds extends Component {
create(): World
list: Map<string, World>
delete(world: World): void
}World
interface World {
scene: SimpleScene
camera: SimpleCamera
renderer: SimpleRenderer
uuid: string
enabled: boolean
}SimpleScene
class SimpleScene {
constructor(components: Components)
setup(config?: { backgroundColor?: THREE.Color }): void
readonly three: THREE.Scene // Access underlying Three.js scene
dispose(): void
}SimpleCamera
class SimpleCamera {
constructor(components: Components)
readonly three: THREE.PerspectiveCamera | THREE.OrthographicCamera
readonly controls: CameraControls // camera-controls library instance
dispose(): void
}SimpleRenderer
class SimpleRenderer {
constructor(components: Components, container: HTMLElement)
readonly three: THREE.WebGLRenderer
dispose(): void
}IfcLoader
class IfcLoader extends Component {
setup(config?: IfcLoaderConfig): Promise<void>
load(data: Uint8Array, coordinateToOrigin?: boolean): Promise<THREE.Group>
readonly settings: IfcLoaderSettings
}FragmentsManager
class FragmentsManager extends Component {
readonly list: Map<string, Fragment>
dispose(): void
export(group: THREE.Group): Uint8Array // Export to .frag format
load(data: Uint8Array): THREE.Group // Load from .frag format
}Highlighter
class Highlighter extends Component {
setup(config?: { world: World }): void
highlight(name: string, removePrevious?: boolean): Map<string, Set<number>>
clear(name?: string): void
readonly selection: Map<string, Map<string, Set<number>>>
}Clipper
class Clipper extends Component {
enabled: boolean
create(world: World): void // Creates a clipping plane at click position
delete(world: World): void // Removes last clipping plane
deleteAll(): void // Removes all clipping planes
dispose(): void
}---
IFC Type Constants (web-ifc)
All constants are exported from the web-ifc module as numeric values:
// Structural elements
const IFCWALL: number
const IFCWALLSTANDARDCASE: number
const IFCSLAB: number
const IFCCOLUMN: number
const IFCBEAM: number
const IFCPLATE: number
const IFCMEMBER: number
const IFCFOOTING: number
const IFCPILE: number
// Openings and furnishing
const IFCDOOR: number
const IFCWINDOW: number
const IFCFURNISHINGELEMENT: number
const IFCCOVERING: number
const IFCRAILING: number
const IFCSTAIR: number
const IFCSTAIRFLIGHT: number
const IFCRAMP: number
const IFCRAMPFLIGHT: number
// Spatial structure
const IFCPROJECT: number
const IFCSITE: number
const IFCBUILDING: number
const IFCBUILDINGSTOREY: number
const IFCSPACE: number
// Relationships
const IFCRELAGGREGATES: number
const IFCRELCONTAINEDINSPATIALSTRUCTURE: number
const IFCRELDEFINESBYPROPERTIES: number
const IFCRELDEFINESBYTYPE: number
const IFCRELASSOCIATESMATERIAL: number
const IFCRELCONNECTSPATHELEMENTS: number
// Properties
const IFCPROPERTYSET: number
const IFCPROPERTYSINGLEVALUE: number
const IFCELEMENTQUANTITY: number
const IFCQUANTITYLENGTH: number
const IFCQUANTITYAREA: number
const IFCQUANTITYVOLUME: number
// MEP (Mechanical, Electrical, Plumbing)
const IFCFLOWSEGMENT: number
const IFCFLOWTERMINAL: number
const IFCFLOWFITTING: number
const IFCDISTRIBUTIONELEMENT: number