babylon-ifc-loader
v2.2.1
Published
Babylon.js Loader for IFC files using web-ifc
Maintainers
Readme
Babylon.js IFC Loader
IFC Loader built with Babylon.js and web-ifc. Features automatic loading of sample IFC files, drag-and-drop support, intelligent mesh merging, element picking with metadata display, and automatic camera framing.
While providing the minimal viewer experience, this repo is dedicated to developing and testing the IFC Babylon.js Loader. The viewer is provided for testing and demonstration purposes only. Full-featured Babylon.js IFC Babylon.js Viewer will be available in a separate repo later.
Installation
npm install babylon-ifc-loaderUpgrade Guide (NPM users)
Use these steps to upgrade safely to the latest published package version.
- Check your currently installed version.
npm ls babylon-ifc-loader- Upgrade to latest.
npm install babylon-ifc-loader@latest- Verify the installed version.
npm ls babylon-ifc-loaderIf you use a lockfile in CI (
package-lock.json), commit the lockfile changes after upgrade.If you previously used
npm linkfor local testing, unlink and reinstall from registry:
npm unlink babylon-ifc-loader
npm install babylon-ifc-loader@latestMinimal migration checklist
- Prefer unified loader API:
import { createIfcLoader } from "babylon-ifc-loader";
const loader = createIfcLoader({ useWorker: true });
await loader.init("/");- Prefer prepared loading for performance:
const prepared = await loader.loadPreparedIfcModel("/model.ifc");- Build scene from prepared model:
const result = buildIfcModel(prepared, scene, { usePBRMaterials: true });- Use merged-picking-safe resolve helper when needed:
const expressID = resolveExpressIDFromMeshPick(pickedMesh, pickResult.faceId);- Review version notes in
CHANGELOG.mdfor behavior changes before deploy.
Quick Start
# Install dependencies
npm install
# Start development server
npm run dev
# Build for production
npm run build
# Preview production build
npm run preview
# Run tests
npm test
# Run tests with coverage
npm run test:coverageDev Server URL
After running npm run dev, open:
| URL | Entry Point | Description |
| ----------------------------------------- | ---------------------- | ------------------------------------------------ |
| http://localhost:5173/ | src/main.ts | App viewer using worker-prepared geometry flow |
| http://localhost:5173/test-npm/index.html | test-npm/main.ts | NPM package integration test page |
| http://localhost:5173/test-speed/index.html | test-speed/main.ts | Benchmark (main-thread vs worker, all merge modes) |
Features
- Automatic Loading: Sample IFC file loads on startup
- Drag & Drop: Drop
.ifcfiles onto the canvas to load them - Element Picking: Click on elements to view metadata and highlight them
- Intelligent Merging: Automatically merges meshes with same material while preserving metadata
- Structured Logging: Typed logging helpers with optional IFC context (
modelID,expressID,geometryExpressID) - Camera Framing: Automatically positions camera to view the entire model
- Inspector (Dev only): Babylon.js Inspector is loaded dynamically only in dev mode
- Keyboard Shortcuts: Ctrl+I (or Cmd+I on Mac) toggles the inspector, works across all keyboard layouts
- Memory Management: Proper cleanup when loading new files
Architecture
The codebase follows a strict layered architecture with clear separation of concerns:
IFC Data Layer (src/ifcInit.ts)
All web-ifc interaction. Zero Babylon.js dependencies.
initializeWebIFC(wasmPath?, logLevel?)- initialize web-ifc APIloadIfcModel(ifcAPI, source, options?)- load and extract raw geometry datacloseIfcModel(ifcAPI, modelID)- free IFC model memorygetProjectInfo(ifcAPI, modelID)- extract project metadata
Rendering Layer (src/ifcModel.ts)
All Babylon.js scene construction. Zero web-ifc dependencies.
buildIfcModel(model, scene, options?)- create meshes, materials, merge, centerdisposeIfcModel(scene)- dispose all IFC meshes and materialsgetModelBounds(meshes)- calculate bounding boxcenterModelAtOrigin(meshes, rootNode?)- center model at origin
Application Layer
src/main.ts- uses worker + prepared geometry (loadPreparedIfcModel) and Babylon build step
Shared Utilities (src/logging.ts)
Structured logging helpers used across IFC loading and rendering:
logInfo(message, context?)logWarn(message, context?, detail?)logError(message, context?, detail?)
Usage
Two-Step Loading API
For more control, use the two-step API directly:
// Step 1: Initialize web-ifc
const ifcAPI = await initializeWebIFC("./");
// Step 2: Load raw IFC data (web-ifc only)
const model = await loadIfcModel(ifcAPI, "/test.ifc", {
coordinateToOrigin: true,
verbose: true,
});
// Step 3: Extract metadata (optional)
const projectInfo = getProjectInfo(ifcAPI, model.modelID);
// Step 4: Build Babylon.js scene (Babylon only)
const { meshes, rootNode, stats } = buildIfcModel(model, scene, {
autoCenter: true,
mergeMeshes: true,
doubleSided: true,
verbose: true,
});Single Loader API (choose worker or main-thread)
import { createIfcLoader } from "babylon-ifc-loader";
const ifc = createIfcLoader({ useWorker: true }); // false = main-thread
await ifc.init("/"); // Optional second arg: WebIFC.LogLevel
const model = await ifc.loadIfcModel("/test.ifc", {
coordinateToOrigin: true,
verbose: true,
});Prepared Geometry API (recommended)
Use this path to do IFC parse + geometry preparation off-thread (when useWorker: true), then build Babylon meshes on main thread:
const ifc = createIfcLoader({ useWorker: true });
await ifc.init("/");
const prepared = await ifc.loadPreparedIfcModel(
"/test.ifc",
{
coordinateToOrigin: true,
keepModelOpen: false,
},
{
autoMergeStrategy: {
lowMaxParts: 1500,
mediumMaxParts: 5000,
lowMode: "by-express-color",
mediumMode: "by-color",
highMode: "two-material",
},
},
);
const result = buildIfcModel(prepared, scene, { usePBRMaterials: true });renderOnly: true is available for visualization-only flows and forces:
mergeMode: "two-material"keepModelOpen: falseincludeElementMap: false
Load from URL or File
// From URL
const model = await loadIfcModel(ifcAPI, "/path/to/file.ifc");
// From File object (drag-and-drop)
const model = await loadIfcModel(ifcAPI, fileObject);Cleanup before loading a new model
// Dispose Babylon.js scene (meshes, materials, root node)
disposeIfcModel(scene);
// Close IFC model and free WASM memory
closeIfcModel(ifcAPI, modelID);API Reference
See API.md for complete API documentation with all types, parameters, and examples.
NPM Package
The package is published as babylon-ifc-loader and exports:
// Low-level IFC Data Layer (web-ifc only)
import { initializeWebIFC, loadIfcModel, closeIfcModel, getProjectInfo } from "babylon-ifc-loader";
// Rendering Layer (Babylon.js only)
import { buildIfcModel, disposeIfcModel, getModelBounds, centerModelAtOrigin } from "babylon-ifc-loader";Testing NPM Package Locally
To test the npm package locally before publishing:
# 1. Build the npm package
npm run build:npm
# 2. Run Vite dev server with the test-npm entry point
npx vite . --open test-npm/index.htmlThe test-npm/ folder contains a test page that imports from babylon-ifc-loader. Since the package's main entry points to dist-npm/index.js, Vite resolves the import from the built output.
Benchmarking Merge Modes and Worker/Main-Thread
Open test-speed/index.html to benchmark:
- Backends:
main-threadandworker - Merge modes:
by-express-color,by-color,two-material - Metrics: load ms, build ms, total ms, mesh/material count, memory estimate, transfer/map bytes, opaque/transparent counts
Testing from another project:
To test the package in a different project:
# In ifc-babylon root
npm link
# In the other project
npm link babylon-ifc-loaderTesting
The project uses Vitest for unit testing with the following setup:
- Test Runner: Vitest v4 with
jsdomenvironment - Coverage:
@vitest/coverage-v8for code coverage reports - Location: Test files are in
src/__tests__/
Test Files
| File | Description |
| -------------------------- | ------------------------------------- |
| initializeWebIFC.test.ts | Tests for web-ifc initialization |
| loadIfcModel.test.ts | Tests for IFC model loading |
| ifcLoader.test.ts | Tests for unified loader and renderOnly flow |
| ifcModelPreparation.test.ts | Tests for merge tiers/profiles and telemetry |
| closeIfcModel.test.ts | Tests for model cleanup |
| getProjectInfo.test.ts | Tests for project metadata extraction |
| buildIfcModel.test.ts | Tests for Babylon.js scene building |
| zOffset.test.ts | Tests for z-offset material handling |
| buildIfcModel.perf.test.ts | Perf regression tests for synthetic large models |
| logging.test.ts | Unit tests for structured logging helpers |
Running Tests
# Run tests in watch mode
npm test
# Run tests once
npm test -- --run
# Run tests with coverage report
npm run test:coverageWriting Tests
Tests follow the standard Vitest pattern with mocked dependencies:
import { describe, it, expect, beforeEach, vi } from "vitest";
describe("myFunction", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("should do something", async () => {
const result = await myFunction();
expect(result).toBe(expected);
});
});Project Structure
src/
|-- main.ts - app entry (worker-prepared load + Babylon build)
|-- ifcInit.ts - IFC data layer (web-ifc only)
|-- ifcLoader.ts - unified loader facade (worker or main-thread)
|-- ifcWorkerClient.ts - worker client transport
|-- ifc.worker.ts - worker runtime for web-ifc + preparation
|-- ifcModelPreparation.ts - merge strategies and telemetry
|-- ifcModel.ts - rendering layer (Babylon.js only)
|-- style.css - basic styling
`-- __tests__/ - unit tests
public/
|-- test.ifc - sample IFC file loaded at startup
|-- example.ifc - additional sample
`-- bplogo.svg - asset
Root
|-- index.html - HTML entry
|-- vite.config.ts - copies web-ifc.wasm to dist/, sets WASM handling
|-- tsconfig.json - TypeScript config
`-- package.json - scripts and depsPicking and Highlighting
- Left-click a mesh to log full element data via
ifcAPI.GetLine(modelID, expressID, true)and type name viaGetNameFromTypeCode - Highlight uses
renderOverlaywith teal color and alpha=0.3 - Upper text banner shows type, name, and ExpressID; clicking empty space clears it
Materials, Merging, and Performance
- Materials are
StandardMaterialper unique RGBA color, configurablebackFaceCulling, incrementalzOffsetto mitigate z-fighting - Prepared geometry supports merge modes:
by-express-color,by-color,two-material, andnone - Optional auto-merge tiers via
autoMergeStrategy(low,medium,high) - Metadata (
expressID,modelID) preserved on merged meshes - For merged prepared meshes,
elementRangescan preserve per-face expressID mapping for picking - Material metadata includes source color as
material.metadata.colorwith{ r, g, b, a }(ornull) - Telemetry includes tier, opaque/transparent counts, map bytes, geometry bytes, and transfer bytes
Custom Merging Strategy
When mergeMeshes = false, each geometry part remains as a separate mesh with full metadata. This lets you implement your own merging strategy based on:
- Mesh metadata (
expressID,modelID) for element identification - IFC queries via
ifcAPI.GetLine()for property-based grouping - Spatial relationships for storey/zone-based organization
- Material or color-based batching
Example:
const { meshes } = buildIfcModel(model, scene, { mergeMeshes: false });
// Custom grouping by IFC type
for (const mesh of meshes) {
const element = ifcAPI.GetLine(modelID, mesh.metadata.expressID, true);
const typeName = ifcAPI.GetNameFromTypeCode(element.type);
// Group or merge meshes by typeName, storey, etc.
}Coordinate System and Geometry
- web-ifc streams interleaved vertex data
[x,y,z,nx,ny,nz] - Optional normal generation when required
- Per-part transforms baked from placed geometry matrices
- Z-axis flip applied via root node scaling for IFC-to-Babylon coordinate conversion
Build and Deploy Notes
- The Vite config copies
node_modules/web-ifc/web-ifc.wasmtodist/ - In production,
initializeWebIFC("./")ensures the WASM is loaded from the dist root optimizeDeps.exclude = ["web-ifc"]prevents esbuild issues during dev
Dependencies
| Package | Version | Description | | ----------------------- | ------- | ----------------------------------- | | @babylonjs/core | ^8.52.0 | Core Babylon.js engine | | @babylonjs/inspector | ^8.52.0 | Built-in debugging inspector | | web-ifc | ^0.0.75 | IFC parsing and geometry extraction | | vite | ^7.3.1 | Build tool and dev server | | vite-plugin-static-copy | ^3.2.0 | Copy WASM files to dist |
Limitations and Future Improvements
- No spatial tree or filters yet
- No property panel UI
- No outline/edge rendering highlight option
- No UI controls for scene manipulation
License
Apache-2.0 - See LICENSE for details.
Changelog
See CHANGELOG.md for release notes and recent changes.
