Multi-Model Federation¶
IFClite supports loading multiple IFC files simultaneously with unified selection, visibility, and spatial hierarchy. This is essential for real-world BIM workflows where architectural, structural, and MEP models are maintained as separate files.
How It Works¶
Each loaded model is assigned a unique ID offset so that entity IDs never collide across models. The FederationRegistry manages these offsets automatically.
Model A: expressIds 1-5000 -> globalIds 1-5000 (offset: 0)
Model B: expressIds 1-3000 -> globalIds 5001-8000 (offset: 5000)
Model C: expressIds 1-2000 -> globalIds 8001-10000 (offset: 8000)
Global vs Local IDs¶
- Local expressId: The original ID within a single IFC file (e.g.,
#42) - Global ID:
expressId + model.idOffset- unique across all loaded models - EntityRef:
{ modelId: string, expressId: number }- unambiguous reference to any entity
import { federationRegistry } from '@ifc-lite/renderer';
// Convert local to global
const globalId = federationRegistry.toGlobalId('arch-model', expressId);
// Convert global to local
const lookup = federationRegistry.fromGlobalId(globalId);
// { modelId: 'arch-model', expressId: 42 }
Ids Carried on Geometry¶
MeshData.expressId is not the only id a mesh carries, and every id that is
resolved through the registry must be in the same space. When the viewer loads a
federated model it re-homes the mesh's geometryItemId (the
IfcRepresentationItem the mesh was tessellated from, see the
Geometry Guide) by the same idOffset as expressId, on the flat
path and on the instanced shards alike. So a geometryItemId you read off a
loaded mesh is a global id: pass it to fromGlobalId like any other, and
subtract the offset before comparing it to raw ids in the source file.
This matters more than it looks. Resolution back to a model is range-based:
fromGlobalId and getModelForGlobalId ask which model's id range contains the
number. A raw, unshifted item id from a model loaded at offset 1,000,000 is a
small number, so it lands inside the primary model's range: it does not miss
and it does not throw, it resolves to a real entity in the wrong model. Absence
stays absence, though: a mesh with no item id still has none after the shift.
MeshData.materialId is not re-homed and stays in its model's local space
(tracked in #3525). Do not assume the ids on a mesh are uniformly global; today
expressId, textureRef.textureId and geometryItemId are, and materialId is
not.
Loading Multiple Models¶
In the viewer, drop multiple IFC files or load them sequentially. Each model appears as a collapsible group in the hierarchy panel.
Programmatic Usage¶
import { IfcParser } from '@ifc-lite/parser';
import { federationRegistry } from '@ifc-lite/renderer';
const parser = new IfcParser();
// Load first model
const archStore = await parser.parseColumnar(archBuffer);
// Compute maxExpressId from entity index
const archMaxId = Math.max(...archStore.entityIndex.byId.keys());
const archOffset = federationRegistry.registerModel('arch', archMaxId);
// Load second model - IDs start after the first model's range
const structStore = await parser.parseColumnar(structBuffer);
const structMaxId = Math.max(...structStore.entityIndex.byId.keys());
const structOffset = federationRegistry.registerModel('struct', structMaxId);
// Convert IDs
const globalId = federationRegistry.toGlobalId('struct', 42);
const lookup = federationRegistry.fromGlobalId(globalId);
if (lookup) {
console.log(`Model: ${lookup.modelId}, Express ID: ${lookup.expressId}`);
}
Unified Interactions¶
When multiple models are loaded:
- Selection works across all models - clicking any entity in any model selects it
- Visibility can be toggled per-model or per-entity across models
- Spatial hierarchy shows all models as top-level groups, expandable to their internal structure
- Properties panel shows properties for the selected entity regardless of which model it belongs to
- Section planes cut through all visible models simultaneously
- Measurements can span across models
Model Management¶
The viewer provides controls for each loaded model:
| Action | Description |
|---|---|
| Visibility toggle | Show/hide an entire model |
| Collapse/Expand | Collapse a model's hierarchy tree |
| Rename | Give a model a descriptive name |
| Remove | Unload a model and free its ID range |
| Set Active | Focus the properties panel on a specific model |
Merging to a Single File (CLI)¶
In-viewer federation keeps each model as a separate file with an ID offset. When
you instead want to bake several models into one physical IFC file, use the
ifc-lite merge command:
Pass two or more input files (positional args) and one --out target. The
spatial hierarchy (sites, buildings, storeys) is unified by name and elevation by
default so the merged file has one coherent tree rather than duplicated
containers.
| Flag | Values | Default | Effect |
|---|---|---|---|
--out <file> |
path | required | Output file path |
--schema |
IFC2X3 / IFC4 / IFC4X3 |
IFC4 |
Output schema version |
--unit-reconciliation |
auto / normalize / assume-shared |
auto |
How to handle models whose length unit differs from the first file. auto federates them as separate projects; normalize rescales them into the first file's unit; assume-shared forces one project without rescaling |
--merge-sites |
single / by-name |
combined heuristic | How IfcSite records are matched across models |
--merge-buildings |
single / by-name |
combined heuristic | How IfcBuilding records are matched |
--merge-storeys |
by-name / by-elevation / by-name-then-elevation |
combined heuristic | How IfcBuildingStorey records are matched |
--drop-empty-containers |
flag | off | Leave out sites, buildings, storeys and spaces the merged model leaves holding nothing (the "Merge Projects" recipe step matching alone does not cover) |
--json |
flag | off | Emit machine-readable stats (entity counts, warnings) to stdout |
FederatedModel Type¶
Each loaded model is tracked as a FederatedModel:
interface FederatedModel {
id: string; // Unique model identifier
name: string; // Display name
idOffset: number; // Global ID offset
maxExpressId: number; // Highest expressId in this model
visible: boolean; // Visibility state
collapsed: boolean; // Hierarchy tree state
}
Performance Considerations¶
- Each model adds its entities to the shared spatial index and renderer
- Memory usage scales linearly with total entity count across all models
- The FederationRegistry resolves IDs in O(1) local->global, O(log N) global->local
- Visibility toggling per-model is O(1) (GPU-level filtering)
- Loading 5+ large models (100MB+ each) may require the server paradigm for best performance
IFC5 Federated Layers¶
For IFC5 (IFCX) files, federation works differently - files can be loaded as overlay layers where later files override properties from earlier ones:
import { parseFederatedIfcx } from '@ifc-lite/ifcx';
const result = await parseFederatedIfcx([
{ buffer: baseBuffer, name: 'base-model.ifcx' },
{ buffer: overlayBuffer, name: 'add-fire-rating.ifcx' },
]);
// Properties from the overlay take precedence
// Wall now has FireRating property from the overlay file
See the IFC5 Parsing Guide for more details on IFCX format support.