Overview
The fragment system is ThatOpen's optimized geometry pipeline for BIM models.
It converts IFC files into a GPU-friendly binary format built on FlatBuffers
and THREE.InstancedMesh, enabling fast rendering of large models with millions
of elements. The @thatopen/fragments package provides the binary format,
worker architecture, and core operations. FragmentsManager in
@thatopen/components orchestrates model lifecycle, raycasting, data queries,
and coordinate alignment.
Pipeline:
IFC File → web-ifc (WASM) → IfcLoader → FragmentsModel (binary .frag)
│
┌─────────┴─────────┐
│ Fragment[] │
│ (InstancedMesh) │
└────────────────────┘
Convert once, reload fast: ALWAYS convert IFC to fragments once via
IfcLoader, then store the binary .frag file. Subsequent loads skip WASM
parsing entirely and load the pre-converted fragment binary directly.
Critical Warnings
-
ALWAYS call
FragmentsManager.init(workerURL)before ANY fragment operation. The worker handles raycasting, data queries, and model loading off the main thread. Omitting this causes silent failures or crashes. -
ALWAYS dispose models via
FragmentsManager.disposeModel(modelId)orcomponents.dispose()when done. Fragment models hold GPU buffers (InstancedMesh geometry, textures) and worker state. Undisposed models cause memory leaks that crash browser tabs. -
NEVER skip coordinate alignment in multi-model scenarios. Models from different origins will appear scattered in 3D space. Use
applyBaseCoordinateSystem()to align them to a common origin. -
ALWAYS match the worker.mjs URL to your installed
@thatopen/fragmentsversion. A version mismatch between the main-thread library and the worker script causes deserialization failures.
Core Concepts
Fragment Binary Format
Fragments use Google FlatBuffers for zero-copy binary serialization:
| Layer | Content |
|---|---|
FragmentGroup (root) |
Coordination matrix, IFC metadata, array of Fragments |
Fragment |
ID, type (Mesh/InstancedMesh/Point/Line), geometry, transforms, colors |
Geometry |
Position/normal/index arrays, groups, bounding box |
Transform |
4x4 matrix, local ID, express ID per instance |
File identifier: FRAG. Dependencies: flatbuffers, pako (compression),
earcut (triangulation).
Key advantage: Geometry arrays (position, index) map directly to GPU
buffers as zero-copy typed array views. No deserialization step.
GPU Instancing
Each Fragment wraps a THREE.InstancedMesh. Identical geometries (e.g., all
doors of the same type) share one GPU geometry buffer with per-instance
transform matrices. This reduces draw calls from thousands to dozens.
FragmentsModel
A FragmentsModel represents one loaded BIM model. It contains:
- An array of
Fragmentobjects (instanced meshes) - The coordination matrix (world positioning)
- IFC metadata and property data
- GUID-to-localID mappings
Access loaded models via FragmentsManager.list: Map<string, FragmentsModel>.
FragmentsManager API
class FragmentsManager extends Component implements Disposable {
// State
list: Map<string, FragmentsModel>;
initialized: boolean;
baseCoordinationModel: string;
baseCoordinationMatrix: THREE.Matrix4;
// Initialization — MUST call before any operation
init(workerURL: string, options?): void;
// Raycasting
raycast(config: {
camera: THREE.Camera,
mouse: THREE.Vector2,
dom: HTMLElement,
snappingClasses?: number[]
}): Promise<Result | undefined>;
// Visual operations
highlight(style: MaterialDefinition, items?: ModelIdMap): Promise<void>;
resetHighlight(items?: ModelIdMap): Promise<void>;
// Data queries
getData(items: ModelIdMap, config?): Promise<Record<string, ItemData[]>>;
getPositions(items: ModelIdMap): Promise<THREE.Vector3[]>;
getBBoxes(items: ModelIdMap): Promise<THREE.Box3[]>;
// GUID mapping
guidsToModelIdMap(guids: Iterable<string>): Promise<ModelIdMap>;
modelIdMapToGuids(modelIdMap: ModelIdMap): Promise<string[]>;
// Coordinate alignment
applyBaseCoordinateSystem(object: THREE.Object3D, originalMatrix?: THREE.Matrix4): THREE.Matrix4;
// Disposal
disposeModel(modelId: string): void;
}
Events:
onFragmentsLoaded: Event<FragmentsModel>— fires after a model is loadedonBeforeDispose: Event<FragmentsModel>— fires before model disposalonDisposed: Event<void>— fires after FragmentsManager itself is disposed
ModelIdMap
The universal data structure for targeting items across models:
type ModelIdMap = Record<string, Set<number>>;
// Keys: model UUID strings
// Values: sets of local element IDs (numbers)
Used by: FragmentsManager, Hider, Classifier, BoundingBoxer, Highlighter, and every component that operates on specific BIM elements.
ALWAYS use ModelIdMap to reference items. NEVER reference items by expressID alone — expressIDs are only unique within a single model.
Worker Architecture
FragmentsManager offloads heavy operations (raycasting, data extraction, model loading) to a dedicated web worker.
// Worker initialization — ALWAYS do this first
const fragments = components.get(OBC.FragmentsManager);
fragments.init("https://unpkg.com/@thatopen/fragments@3.3.6/dist/Worker/worker.mjs");
What runs in the worker:
- FlatBuffers deserialization
- Raycast intersection tests
- Property data extraction (getData)
- Position and bounding box calculations
- GUID-to-ID mapping
What stays on the main thread:
- THREE.InstancedMesh creation and scene graph management
- Highlight/resetHighlight (GPU material swaps)
- Coordinate alignment (matrix multiplication)
Coordinate Alignment
When loading multiple models, each may have a different world origin stored in its coordination matrix.
// Set the first loaded model as the base
fragments.baseCoordinationModel = firstModel.modelId;
fragments.baseCoordinationMatrix = firstModel.coordinationMatrix;
// Align subsequent models
fragments.applyBaseCoordinateSystem(secondModel, secondModel.coordinationMatrix);
NEVER skip this step for multi-model federation. Models will appear at wrong positions without alignment.
Data Operations
getData: Extract IFC Properties
const items: ModelIdMap = { [model.modelId]: new Set([42, 43, 44]) };
const data = await fragments.getData(items);
// Returns: Record<string, ItemData[]>
// Keys are model IDs, values are arrays of property data per element
getPositions: Get 3D Coordinates
const positions = await fragments.getPositions(items);
// Returns: THREE.Vector3[] — center positions of targeted elements
getBBoxes: Get Bounding Boxes
const boxes = await fragments.getBBoxes(items);
// Returns: THREE.Box3[] — axis-aligned bounding boxes
GUID Mapping
Convert between IFC GlobalId (GUID) strings and ModelIdMap:
// GUIDs → ModelIdMap (for targeting elements by GUID)
const items = await fragments.guidsToModelIdMap(["2O2Fr$t4X7Zf8NOew3FLOH"]);
// ModelIdMap → GUIDs (for exporting selections)
const guids = await fragments.modelIdMapToGuids(items);
Highlight and Raycast
Raycasting
const result = await fragments.raycast({
camera: world.camera.three,
mouse: new THREE.Vector2(normalizedX, normalizedY),
dom: renderer.three.domElement,
snappingClasses: [IFCWALL, IFCSLAB] // optional: restrict hit targets
});
if (result) {
console.log(result.modelId, result.localId, result.point);
}
Highlighting
// Define a highlight style (material definition)
const style: MaterialDefinition = {
color: new THREE.Color("#BCF124"),
opacity: 0.6
};
// Highlight specific items
await fragments.highlight(style, items);
// Reset to original appearance
await fragments.resetHighlight(items);
IFC-to-Fragment Pipeline
The recommended workflow for production:
- First time: Convert IFC via IfcLoader, export binary
- Subsequent loads: Load the binary directly (10-100x faster)
// Step 1: Convert IFC to FragmentsModel
const ifcLoader = components.get(OBC.IfcLoader);
await ifcLoader.setup();
const model = await ifcLoader.load(ifcBytes, true, "MyBuilding");
// Step 2: Export as binary for storage
// The FragmentsModel binary can be persisted (IndexedDB, server, etc.)
// Step 3: On next load, skip IFC parsing entirely
// Load the pre-converted fragment binary through FragmentsManager
Dependencies
| Package | Purpose |
|---|---|
@thatopen/fragments |
Core fragment engine, FlatBuffers format, worker |
flatbuffers |
Binary serialization (zero-copy reads) |
pako |
Compression/decompression of fragment binaries |
earcut |
Polygon triangulation for 2D profiles |
three (>=0.175) |
3D rendering, InstancedMesh, scene graph |
web-ifc (>=0.0.74) |
IFC parsing (used by IfcLoader, not fragments directly) |
Quick Reference
| Task | Method |
|---|---|
| Initialize worker | fragments.init(workerURL) |
| Get loaded models | fragments.list |
| Raycast scene | fragments.raycast({camera, mouse, dom}) |
| Get element properties | fragments.getData(items) |
| Get element positions | fragments.getPositions(items) |
| Get bounding boxes | fragments.getBBoxes(items) |
| GUID to items | fragments.guidsToModelIdMap(guids) |
| Items to GUIDs | fragments.modelIdMapToGuids(items) |
| Highlight elements | fragments.highlight(style, items) |
| Reset highlights | fragments.resetHighlight(items) |
| Align coordinates | fragments.applyBaseCoordinateSystem(obj, matrix) |
| Dispose a model | fragments.disposeModel(modelId) |
| Dispose everything | components.dispose() |
Related Skills
thatopen-core-architecture— Component system, world setup, lifecyclethatopen-syntax-ifc-loading— IfcLoader configuration and WASM setupthatopen-syntax-properties— Deep property extraction from IFC datathatopen-impl-viewer— Full viewer setup including fragments initialization
References
- references/methods.md — Complete FragmentsManager API, FragmentsModel, ModelIdMap details
- references/examples.md — Init worker, load model, getData, raycast, coordinate alignment examples
- references/anti-patterns.md — Missing worker init, disposal failures, common mistakes