ThatOpen Core Architecture
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 with new directly.
get<U>() creates the instance on first call (lazy singleton pattern).
Subsequent calls return the same instance.
- Each component registers itself by its static
uuid in the constructor.
init() starts the requestAnimationFrame loop using THREE.Clock for
delta time. ALWAYS call init() after setting up your world.
dispose() iterates all components and disposes them. FragmentsManager
is ALWAYS disposed last to prevent dangling references.
- BVH acceleration is auto-patched onto
THREE.BufferGeometry in the
Components constructor. 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
uuid string.
- Every component MUST have an
enabled property.
- The
Base class provides runtime interface detection via is*() methods.
These use duck-typing, not instanceof checks.
- 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
Components animation loop calls update(delta) on EVERY enabled
Updateable component each frame.
Configurable components ALWAYS require an explicit setup() call before
they function. Check isSetup to 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
.three property
when you need direct Three.js manipulation.
Worlds is itself a Component that implements Updateable and
Disposable. It manages a DataMap<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 = false to temporarily suppress an event.
Reactive Collections: DataMap and DataSet
ThatOpen provides reactive wrappers around Map and Set:
- DataMap<K, V> — extends
Map with onItemSet, onItemUpdated,
onItemDeleted, onCleared events.
- DataSet — extends
Set with onItemAdded, onItemDeleted,
onCleared events.
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 use
components.get(ComponentClass).
- NEVER forget to call
components.dispose() on cleanup. Memory leaks
from undisposed BIM models crash browser tabs.
- NEVER import
@thatopen/components-front in 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,
or openbim-components. Use @thatopen/components 3.x.
- NEVER use v2 APIs (
Plans, ClipEdges) — use ClipStyler + View
in v3.
- ALWAYS call
setup() on Configurable components before using them
(e.g., IfcLoader, SimpleScene, Highlighter).
- ALWAYS initialize
FragmentsManager with 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
Configurable and require explicit setup() 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_components main branch (packages/core/src/)
- npm:
@thatopen/components@3.3.3
- Research:
docs/research/vooronderzoek-thatopen.md
1---2name: thatopen-core-architecture3description: Use when creating a ThatOpen BIM application, understanding the component system, or reasoning about the ThatOpen architecture. Prevents direct component instantiation instead of using components.get(). Covers Components container, Component base class, lifecycle interfaces, World system, package split (core vs front), Event system, DataMap/DataSet. Keywords: thatopen, components, world, scene, renderer, camera, lifecycle, disposable, updateable, configurable, bim, architecture, how ThatOpen works, component system, project structure.4license: MIT5---67# ThatOpen Core Architecture89## Overview1011ThatOpen engine_components is a component-based BIM/IFC viewer built on12Three.js. It converts IFC models into optimized Fragment geometry (instanced13meshes) and provides tools for visualization, selection, measurement, and14classification.1516**Version**: @thatopen/components 3.3.x17**License**: MIT1819## Dependency Chain2021```22@thatopen/ui-obc23 └─ @thatopen/components-front (browser-only)24 └─ @thatopen/components (core, browser + Node.js)25 ├─ @thatopen/fragments (FlatBuffers, web workers)26 │ ├─ web-ifc (WASM IFC parser)27 │ └─ three (3D rendering, >=0.175)28 ├─ three-mesh-bvh (accelerated raycasting)29 ├─ camera-controls (orbit/pan/zoom, >=3.1.2)30 ├─ jszip (BCF import/export)31 └─ fast-xml-parser (BCF XML)32```3334## Components Container (Singleton Registry)3536`Components` is the top-level container. It manages all component instances37and the animation loop.3839```typescript40import * as OBC from "@thatopen/components";4142const components = new OBC.Components();43```4445**Key behaviors:**46- ALWAYS use `components.get(ComponentClass)` to obtain a component instance.47 NEVER instantiate a component with `new` directly.48- `get<U>()` creates the instance on first call (lazy singleton pattern).49 Subsequent calls return the same instance.50- Each component registers itself by its static `uuid` in the constructor.51- `init()` starts the `requestAnimationFrame` loop using `THREE.Clock` for52 delta time. ALWAYS call `init()` after setting up your world.53- `dispose()` iterates all components and disposes them. `FragmentsManager`54 is ALWAYS disposed last to prevent dangling references.55- BVH acceleration is auto-patched onto `THREE.BufferGeometry` in the56 `Components` constructor. No manual BVH setup is needed.5758See [references/methods.md](references/methods.md) for full API signatures.5960## Component Base Class6162Every ThatOpen component extends `Component`, which extends `Base`:6364```typescript65abstract class Base {66 constructor(public components: Components) {}67 isDisposeable(): this is Disposable;68 isUpdateable(): this is Updateable;69 isConfigurable(): this is Configurable<any, any>;70 isResizeable(): this is Resizeable;71 isHideable(): this is Hideable;72 isSerializable(): this is Serializable<any>;73}7475abstract class Component extends Base {76 abstract enabled: boolean;77 static readonly uuid: string;78}79```8081**Key points:**82- Every component MUST have a static `uuid` string.83- Every component MUST have an `enabled` property.84- The `Base` class provides runtime interface detection via `is*()` methods.85 These use duck-typing, not `instanceof` checks.86- Components self-register in their constructor by calling87 `components.add(MyComponent.uuid, this)`.8889## Lifecycle Interfaces9091Components opt into behaviors by implementing interfaces (mixin pattern).92This replaces deep inheritance hierarchies.9394| Interface | Methods / Properties | Purpose |95|-----------------|-----------------------------------------------------------------|----------------------|96| `Disposable` | `dispose()`, `onDisposed: Event<void>` | Cleanup resources |97| `Updateable` | `update(delta?)`, `onBeforeUpdate`, `onAfterUpdate` | Per-frame updates |98| `Configurable` | `setup(config?)`, `config`, `isSetup`, `onSetup` | Deferred init |99| `Resizeable` | `resize()`, `getSize()`, `onResize` | Viewport resizing |100| `Hideable` | `visible: boolean` | Show/hide |101| `Createable` | `create()`, `delete()`, `endCreation()`, `cancelCreation()` | Interactive creation |102| `Serializable` | `import()`, `export()` | Persistence |103104**Lifecycle flow:**105106```107Constructor → [setup()] → enabled=true → update() loop → dispose()108 ↑ ↑109 Configurable only Updateable only110```111112- The `Components` animation loop calls `update(delta)` on EVERY enabled113 `Updateable` component each frame.114- `Configurable` components ALWAYS require an explicit `setup()` call before115 they function. Check `isSetup` to verify.116- ALWAYS call `dispose()` on cleanup. Failing to dispose causes memory leaks117 that crash browser tabs with large BIM models.118119## World System120121A `World` connects Scene + Camera + Renderer into a single viewport.122123```typescript124import * as OBC from "@thatopen/components";125126const components = new OBC.Components();127const worlds = components.get(OBC.Worlds);128const world = worlds.create<129 OBC.SimpleScene,130 OBC.OrthoPerspectiveCamera,131 OBC.SimpleRenderer132>();133134world.scene = new OBC.SimpleScene(components);135world.scene.setup();136world.renderer = new OBC.SimpleRenderer(components, container);137world.camera = new OBC.OrthoPerspectiveCamera(components);138139components.init();140```141142**Abstraction layers:**143144| Abstract | Wraps | Access underlying Three.js |145|------------------|-----------------------|----------------------------|146| `BaseScene` | `THREE.Scene` | `.three` |147| `BaseCamera` | `THREE.Camera` | `.three` |148| `BaseRenderer` | `THREE.WebGLRenderer` | `.three` |149| `SimpleWorld` | Scene+Camera+Renderer | `.scene` / `.camera` / `.renderer` |150151- ALWAYS access the underlying Three.js object via the `.three` property152 when you need direct Three.js manipulation.153- `Worlds` is itself a `Component` that implements `Updateable` and154 `Disposable`. It manages a `DataMap<string, World>` of all worlds.155- When a world is disposed via `worlds.delete(world)`, its scene, camera,156 and renderer are ALSO disposed automatically.157158## Package Split: Core vs Front159160### @thatopen/components (core)161Works in **both browser and Node.js**:162- Components container, event system, lifecycle interfaces163- World management (Worlds, SimpleWorld, SimpleScene, SimpleRenderer)164- Camera (OrthoPerspectiveCamera with camera-controls)165- Fragment loading (IfcLoader, FragmentsManager)166- Fragment manipulation (Hider, BoundingBoxer, Classifier, ItemsFinder)167- Raycasting, clipping planes (Clipper), grids168- OpenBIM standards (BCFTopics, IDSSpecifications)169170### @thatopen/components-front (front)171**Browser-only** (requires DOM and WebGL context):172- PostproductionRenderer (AO, edge detection, outlines, SMAA)173- Highlighter (selection visualization with color maps)174- Hoverer (fade animation hover effect) — NEW in v3175- Outliner (geometry outline rendering)176- Marker (2D screen-space annotations with clustering)177- Measurements (length, area, angle, volume)178- ClipStyler (section/plan edge visualization) — replaces v2 Plans/ClipEdges179- Mesher (convert fragments to regular THREE.Mesh) — NEW in v3180- FastModelPicker (GPU-based element picking) — NEW in v3181182NEVER import from `@thatopen/components-front` in Node.js code. It will fail.183184## Event System185186ThatOpen uses a custom pub/sub `Event<T>` class throughout:187188```typescript189class Event<T> {190 add(handler: (data: T) => void): void;191 remove(handler: (data: T) => void): void;192 trigger(data?: T): void;193 reset(): void;194 enabled: boolean;195}196```197198- Events are used for lifecycle hooks (`onDisposed`, `onSetup`), state199 changes (`onBeforeUpdate`, `onAfterUpdate`), and domain events200 (`onFragmentsLoaded`).201- ALWAYS remove event handlers when disposing custom code to prevent leaks.202- Set `event.enabled = false` to temporarily suppress an event.203204## Reactive Collections: DataMap and DataSet205206ThatOpen provides reactive wrappers around `Map` and `Set`:207208- **DataMap<K, V>** — extends `Map` with `onItemSet`, `onItemUpdated`,209 `onItemDeleted`, `onCleared` events.210- **DataSet<T>** — extends `Set` with `onItemAdded`, `onItemDeleted`,211 `onCleared` events.212213These are used throughout the API (e.g., `Components.list`, `Worlds.list`,214`Classifier.list`, `Highlighter.styles`). ALWAYS use event hooks on these215collections when you need to react to changes.216217## ModelIdMap218219The universal data structure for targeting specific items across models:220221```typescript222type ModelIdMap = Record<string, Set<number>>;223// Keys: model IDs (strings)224// Values: sets of local element IDs (numbers)225```226227Used by: Hider, Classifier, FragmentsManager, BoundingBoxer, Highlighter,228and all selection/query operations.229230## Critical Warnings2312321. **NEVER** instantiate components directly with `new`. ALWAYS use233 `components.get(ComponentClass)`.2342. **NEVER** forget to call `components.dispose()` on cleanup. Memory leaks235 from undisposed BIM models crash browser tabs.2363. **NEVER** import `@thatopen/components-front` in Node.js environments.2374. **NEVER** skip `components.init()` — without it, the render loop does238 not start and nothing renders.2395. **NEVER** use deprecated packages: `web-ifc-three`, `web-ifc-viewer`,240 or `openbim-components`. Use `@thatopen/components` 3.x.2416. **NEVER** use v2 APIs (`Plans`, `ClipEdges`) — use `ClipStyler` + `View`242 in v3.2437. **ALWAYS** call `setup()` on `Configurable` components before using them244 (e.g., `IfcLoader`, `SimpleScene`, `Highlighter`).2458. **ALWAYS** initialize `FragmentsManager` with a worker URL before loading246 models: `fragments.init(workerURL)`.247248## Key Design Decisions2492501. **Singleton per Components instance** — `components.get(X)` ALWAYS251 returns the same instance for a given component class.2522. **Mixin interfaces over inheritance** — Components opt into behaviors253 (Disposable, Updateable, etc.) rather than using deep class hierarchies.2543. **Worker offloading** — Fragment operations run in web workers to keep255 the main thread responsive.2564. **BVH raycasting** — three-mesh-bvh is auto-patched. No manual setup257 is needed for fast raycasting on large models.2585. **Configurable deferred setup** — Components with complex initialization259 implement `Configurable` and require explicit `setup()` calls.260261## Reference Files262263- [references/methods.md](references/methods.md) — Full API signatures for264 Components, Component, Base, World, Worlds, Event, DataMap, DataSet265- [references/examples.md](references/examples.md) — World setup, component266 registration, lifecycle patterns267- [references/anti-patterns.md](references/anti-patterns.md) — Common268 mistakes and what NOT to do269270## Source Verification271272All API signatures verified against:273- GitHub: `ThatOpen/engine_components` main branch (packages/core/src/)274- npm: `@thatopen/components@3.3.3`275- Research: `docs/research/vooronderzoek-thatopen.md`