Overview
ThatOpen engine_components is a component-based BIM/IFC viewer built on Three.js. It converts IFC models into optimized Fragment geometry (instanced meshes) and provides tools for visualization, selection, measurement, and classification.
Version: @thatopen/components 3.3.x License: MIT
Dependency Chain
@thatopen/ui-obc
└─ @thatopen/components-front (browser-only)
└─ @thatopen/components (core, browser + Node.js)
├─ @thatopen/fragments (FlatBuffers, web workers)
│ ├─ web-ifc (WASM IFC parser)
│ └─ three (3D rendering, >=0.175)
├─ three-mesh-bvh (accelerated raycasting)
├─ camera-controls (orbit/pan/zoom, >=3.1.2)
├─ jszip (BCF import/export)
└─ fast-xml-parser (BCF XML)
Components Container (Singleton Registry)
Components is the top-level container. It manages all component instances
and the animation loop.
import * as OBC from "@thatopen/components";
const components = new OBC.Components();
Key behaviors:
- ALWAYS use
components.get(ComponentClass)to obtain a component instance. NEVER instantiate a component withnewdirectly. get<U>()creates the instance on first call (lazy singleton pattern). Subsequent calls return the same instance.- Each component registers itself by its static
uuidin the constructor. init()starts therequestAnimationFrameloop usingTHREE.Clockfor delta time. ALWAYS callinit()after setting up your world.dispose()iterates all components and disposes them.FragmentsManageris ALWAYS disposed last to prevent dangling references.- BVH acceleration is auto-patched onto
THREE.BufferGeometryin theComponentsconstructor. No manual BVH setup is needed.
See references/methods.md for full API signatures.
Component Base Class
Every ThatOpen component extends Component, which extends Base:
abstract class Base {
constructor(public components: Components) {}
isDisposeable(): this is Disposable;
isUpdateable(): this is Updateable;
isConfigurable(): this is Configurable<any, any>;
isResizeable(): this is Resizeable;
isHideable(): this is Hideable;
isSerializable(): this is Serializable<any>;
}
abstract class Component extends Base {
abstract enabled: boolean;
static readonly uuid: string;
}
Key points:
- Every component MUST have a static
uuidstring. - Every component MUST have an
enabledproperty. - The
Baseclass provides runtime interface detection viais*()methods. These use duck-typing, notinstanceofchecks. - Components self-register in their constructor by calling
components.add(MyComponent.uuid, this).
Lifecycle Interfaces
Components opt into behaviors by implementing interfaces (mixin pattern). This replaces deep inheritance hierarchies.
| Interface | Methods / Properties | Purpose |
|---|---|---|
Disposable |
dispose(), onDisposed: Event<void> |
Cleanup resources |
Updateable |
update(delta?), onBeforeUpdate, onAfterUpdate |
Per-frame updates |
Configurable |
setup(config?), config, isSetup, onSetup |
Deferred init |
Resizeable |
resize(), getSize(), onResize |
Viewport resizing |
Hideable |
visible: boolean |
Show/hide |
Createable |
create(), delete(), endCreation(), cancelCreation() |
Interactive creation |
Serializable |
import(), export() |
Persistence |
Lifecycle flow:
Constructor → [setup()] → enabled=true → update() loop → dispose()
↑ ↑
Configurable only Updateable only
- The
Componentsanimation loop callsupdate(delta)on EVERY enabledUpdateablecomponent each frame. Configurablecomponents ALWAYS require an explicitsetup()call before they function. CheckisSetupto verify.- ALWAYS call
dispose()on cleanup. Failing to dispose causes memory leaks that crash browser tabs with large BIM models.
World System
A World connects Scene + Camera + Renderer into a single viewport.
import * as OBC from "@thatopen/components";
const components = new OBC.Components();
const worlds = components.get(OBC.Worlds);
const world = worlds.create<
OBC.SimpleScene,
OBC.OrthoPerspectiveCamera,
OBC.SimpleRenderer
>();
world.scene = new OBC.SimpleScene(components);
world.scene.setup();
world.renderer = new OBC.SimpleRenderer(components, container);
world.camera = new OBC.OrthoPerspectiveCamera(components);
components.init();
Abstraction layers:
| Abstract | Wraps | Access underlying Three.js |
|---|---|---|
BaseScene |
THREE.Scene |
.three |
BaseCamera |
THREE.Camera |
.three |
BaseRenderer |
THREE.WebGLRenderer |
.three |
SimpleWorld |
Scene+Camera+Renderer | .scene / .camera / .renderer |
- ALWAYS access the underlying Three.js object via the
.threeproperty when you need direct Three.js manipulation. Worldsis itself aComponentthat implementsUpdateableandDisposable. It manages aDataMap<string, World>of all worlds.- When a world is disposed via
worlds.delete(world), its scene, camera, and renderer are ALSO disposed automatically.
Package Split: Core vs Front
@thatopen/components (core)
Works in both browser and Node.js:
- Components container, event system, lifecycle interfaces
- World management (Worlds, SimpleWorld, SimpleScene, SimpleRenderer)
- Camera (OrthoPerspectiveCamera with camera-controls)
- Fragment loading (IfcLoader, FragmentsManager)
- Fragment manipulation (Hider, BoundingBoxer, Classifier, ItemsFinder)
- Raycasting, clipping planes (Clipper), grids
- OpenBIM standards (BCFTopics, IDSSpecifications)
@thatopen/components-front (front)
Browser-only (requires DOM and WebGL context):
- PostproductionRenderer (AO, edge detection, outlines, SMAA)
- Highlighter (selection visualization with color maps)
- Hoverer (fade animation hover effect) — NEW in v3
- Outliner (geometry outline rendering)
- Marker (2D screen-space annotations with clustering)
- Measurements (length, area, angle, volume)
- ClipStyler (section/plan edge visualization) — replaces v2 Plans/ClipEdges
- Mesher (convert fragments to regular THREE.Mesh) — NEW in v3
- FastModelPicker (GPU-based element picking) — NEW in v3
NEVER import from @thatopen/components-front in Node.js code. It will fail.
Event System
ThatOpen uses a custom pub/sub Event<T> class throughout:
class Event<T> {
add(handler: (data: T) => void): void;
remove(handler: (data: T) => void): void;
trigger(data?: T): void;
reset(): void;
enabled: boolean;
}
- Events are used for lifecycle hooks (
onDisposed,onSetup), state changes (onBeforeUpdate,onAfterUpdate), and domain events (onFragmentsLoaded). - ALWAYS remove event handlers when disposing custom code to prevent leaks.
- Set
event.enabled = falseto temporarily suppress an event.
Reactive Collections: DataMap and DataSet
ThatOpen provides reactive wrappers around Map and Set:
- DataMap<K, V> — extends
MapwithonItemSet,onItemUpdated,onItemDeleted,onClearedevents. - DataSet — extends
SetwithonItemAdded,onItemDeleted,onClearedevents.
These are used throughout the API (e.g., Components.list, Worlds.list,
Classifier.list, Highlighter.styles). ALWAYS use event hooks on these
collections when you need to react to changes.
ModelIdMap
The universal data structure for targeting specific items across models:
type ModelIdMap = Record<string, Set<number>>;
// Keys: model IDs (strings)
// Values: sets of local element IDs (numbers)
Used by: Hider, Classifier, FragmentsManager, BoundingBoxer, Highlighter, and all selection/query operations.
Critical Warnings
- NEVER instantiate components directly with
new. ALWAYS usecomponents.get(ComponentClass). - NEVER forget to call
components.dispose()on cleanup. Memory leaks from undisposed BIM models crash browser tabs. - NEVER import
@thatopen/components-frontin Node.js environments. - NEVER skip
components.init()— without it, the render loop does not start and nothing renders. - NEVER use deprecated packages:
web-ifc-three,web-ifc-viewer, oropenbim-components. Use@thatopen/components3.x. - NEVER use v2 APIs (
Plans,ClipEdges) — useClipStyler+Viewin v3. - ALWAYS call
setup()onConfigurablecomponents before using them (e.g.,IfcLoader,SimpleScene,Highlighter). - ALWAYS initialize
FragmentsManagerwith a worker URL before loading models:fragments.init(workerURL).
Key Design Decisions
- Singleton per Components instance —
components.get(X)ALWAYS returns the same instance for a given component class. - Mixin interfaces over inheritance — Components opt into behaviors (Disposable, Updateable, etc.) rather than using deep class hierarchies.
- Worker offloading — Fragment operations run in web workers to keep the main thread responsive.
- BVH raycasting — three-mesh-bvh is auto-patched. No manual setup is needed for fast raycasting on large models.
- Configurable deferred setup — Components with complex initialization
implement
Configurableand require explicitsetup()calls.
Reference Files
- references/methods.md — Full API signatures for Components, Component, Base, World, Worlds, Event, DataMap, DataSet
- references/examples.md — World setup, component registration, lifecycle patterns
- references/anti-patterns.md — Common mistakes and what NOT to do
Source Verification
All API signatures verified against:
- GitHub:
ThatOpen/engine_componentsmain branch (packages/core/src/) - npm:
@thatopen/components@3.3.3 - Research:
docs/research/vooronderzoek-thatopen.md