| name | thatopen-impl-federation |
| description | Use when loading multiple IFC models, coordinating model positions, controlling per-model visibility, or working with multi-model scenarios. Prevents coordinate misalignment and visibility management errors. Covers multi-model loading, coordination matrix, Hider (set/isolate/toggle), BoundingBoxer (fit, camera orientation), per-model visibility, cross-model queries via ModelIdMap. Keywords: federation, multi-model, coordination, hider, visibility, isolate, bounding box, alignment, model, hide, show, load multiple models, combine IFC files, show hide elements.
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires @thatopen/components 3.3.x. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
Model Federation
Overview
Model federation is the practice of loading multiple IFC models into a single
3D scene, aligning them to a shared coordinate system, and controlling their
visibility independently. In ThatOpen Engine, federation is not a single
component but a workflow that combines FragmentsManager (loading, alignment),
Hider (visibility), BoundingBoxer (spatial queries), and Classifier (per-model
grouping).
Core principle: Each model loaded via IfcLoader produces an independent
FragmentsModel. These models may originate from different authoring tools with
different world origins. ALWAYS apply coordinate alignment before interacting
with multi-model scenes.
Federation workflow:
Load Model A (IfcLoader) → Set as base coordination model
Load Model B (IfcLoader) → Align to base via applyBaseCoordinateSystem()
Load Model C (IfcLoader) → Align to base via applyBaseCoordinateSystem()
│
├─ Classify by model → Classifier.byModel()
├─ Control visibility → Hider.set() / isolate() / toggle()
├─ Compute bounds → BoundingBoxer.addFromModels()
└─ Query cross-model → Classifier.find() with ModelIdMap
Critical Warnings
-
ALWAYS call applyBaseCoordinateSystem() for every model after the
first. Without coordination, models will appear scattered across 3D space
at their original authoring origins. This is the most common federation bug.
-
ALWAYS set baseCoordinationModel and baseCoordinationMatrix before
loading additional models. The base defines the shared origin. All
subsequent models are transformed relative to it.
-
NEVER assume models share the same coordinate origin. Even models from
the same project may use different site placement or project base points.
-
ALWAYS use ModelIdMap for cross-model operations. A local element ID
is only unique within its model. Use Record<string, Set<number>> to
address elements across models unambiguously.
-
ALWAYS dispose individual models via FragmentsManager.disposeModel()
when removing them from a federated scene. Do not rely on full
components.dispose() for selective unloading.
Multi-Model Loading
Sequential Loading Pattern
Load models one at a time through IfcLoader. The first model sets the
coordination base; subsequent models are aligned to it.
import * as OBC from "@thatopen/components";
import * as THREE from "three";
const components = new OBC.Components();
const fragments = components.get(OBC.FragmentsManager);
const ifcLoader = components.get(OBC.IfcLoader);
fragments.init(workerURL);
await ifcLoader.setup();
const architecturalBytes = new Uint8Array();
const archModel = await ifcLoader.load(architecturalBytes, true, "Architectural");
fragments.baseCoordinationModel = archModel.modelId;
fragments.baseCoordinationMatrix = archModel.coordinationMatrix;
const structuralBytes = new Uint8Array();
const structModel = ifcLoader.(structuralBytes, , );
fragments.(structModel, structModel.);
mepBytes = ();
mepModel = ifcLoader.(mepBytes, , );
fragments.(mepModel, mepModel.);
Coordination Matrix
The coordination matrix is a 4x4 transformation matrix stored in each IFC
file's header. It encodes the model's position, rotation, and scale relative
to a world origin.
fragments.baseCoordinationModel: string;
fragments.baseCoordinationMatrix: THREE.Matrix4;
fragments.applyBaseCoordinateSystem(
object: THREE.Object3D,
originalMatrix?: THREE.Matrix4
): THREE.Matrix4;
How alignment works: The method computes the inverse of the base matrix,
multiplies it with the object's original matrix, and applies the result. This
effectively re-parents the object into the base model's coordinate space.
Adding Models to the World
After loading and aligning, add models to the world scene:
const world = worlds.create();
for (const [id, model] of fragments.list) {
console.log(`Model ${id}: ${model.name}`);
}
Hider: Visibility Control
The Hider component controls element visibility at the fragment level. It
operates on ModelIdMap, making it ideal for per-model and cross-model
visibility management.
API
class Hider extends Component {
set(visible: boolean, modelIdMap?: ModelIdMap): void;
isolate(modelIdMap: ModelIdMap): void;
toggle(modelIdMap: ModelIdMap): void;
getVisibilityMap(state: boolean, modelIds?: string[]): Map<string, Set<number>>;
}
Per-Model Visibility
const hider = components.get(OBC.Hider);
const classifier = components.get(OBC.Classifier);
classifier.byModel();
const structuralItems = await classifier.find({
models: ["Structural"]
});
hider.set(false, structuralItems);
hider.set(true, structuralItems);
Isolate a Single Model
const mepItems = await classifier.find({
models: ["MEP"]
});
hider.isolate(mepItems);
Toggle Visibility
const archItems = await classifier.find({
models: ["Architectural"]
});
hider.toggle(archItems);
Hide by Category Across Models
classifier.byCategory();
const allWalls = await classifier.find({
categories: ["IFCWALL"]
});
hider.set(false, allWalls);
Get Visibility State
const visibleItems = hider.getVisibilityMap(true);
const visibleInArch = hider.getVisibilityMap(true, [archModel.modelId]);
const hiddenItems = hider.getVisibilityMap(false);
Show All (Reset Visibility)
hider.set(true);
BoundingBoxer: Spatial Queries
BoundingBoxer computes axis-aligned bounding boxes for elements or entire
models. Use it to fit the camera to selections, compute model extents, or
orient the camera for specific views.
API
class BoundingBoxer extends Component {
addFromModelIdMap(items: ModelIdMap): void;
addFromModels(modelIds?: string[]): void;
get(): THREE.Box3;
getCenter(modelIdMap: ModelIdMap): THREE.Vector3;
getCameraOrientation(
orientation: "front" | "back" | "left" | "right" | "top" | "bottom",
offsetFactor?: number
): { position: THREE.Vector3; target: THREE.Vector3 };
}
Fit Camera to All Models
const boxer = components.get(OBC.BoundingBoxer);
boxer.addFromModels();
const box = boxer.get();
const camera = world.camera as OBC.OrthoPerspectiveCamera;
camera.fit([box]);
Fit Camera to a Selection
const selectedItems: ModelIdMap = {
[archModel.modelId]: new Set([101, 102, 103]),
[structModel.modelId]: new Set([201, 202])
};
boxer.addFromModelIdMap(selectedItems);
const selectionBox = boxer.get();
camera.fit([selectionBox]);
Camera Orientation for Views
boxer.addFromModels();
const frontView = boxer.getCameraOrientation("front", 1.5);
camera.controls.setLookAt(
frontView.position.x, frontView.position.y, frontView.position.z,
frontView.target.x, frontView.target.y, frontView.target.z,
true
);
Get Center of Items
const center = boxer.getCenter(selectedItems);
Cross-Model Queries
Classifier.byModel
Group all loaded elements by their source model. This is the foundation for
per-model operations.
const classifier = components.get(OBC.Classifier);
classifier.byModel();
Classifier.find with Filters
const archItems = await classifier.find({ models: ["Architectural"] });
const archWalls = await classifier.find({
models: ["Architectural"],
categories: ["IFCWALL"]
});
const groundFloor = await classifier.find({
storeys: ["Ground Floor"]
});
Model Lifecycle
ALWAYS use coordinate=true in ifcLoader.load() for federated models.
Track loads via fragments.onFragmentsLoaded to auto-classify new models.
Dispose individual models with fragments.disposeModel(modelId). Clean up
everything with components.dispose().
Complete Federation Workflow
import * as OBC from "@thatopen/components";
import * as OBCF from "@thatopen/components-front";
import * as THREE from "three";
const components = new OBC.Components();
const worlds = components.get(OBC.Worlds);
const world = worlds.create();
world.scene = new OBC.SimpleScene(components);
world.renderer = new OBCF.PostproductionRenderer(components, container);
world.camera = new OBC.OrthoPerspectiveCamera(components);
const fragments = components.get(OBC.FragmentsManager);
fragments.init(workerURL);
const ifcLoader = components.get(OBC.IfcLoader);
await ifcLoader.setup();
const hider = components.(.);
classifier = components.(.);
boxer = components.(.);
archModel = ifcLoader.(archBytes, , );
fragments. = archModel.;
fragments. = archModel.;
structModel = ifcLoader.(structBytes, , );
mepModel = ifcLoader.(mepBytes, , );
classifier.();
classifier.();
classifier.();
boxer.();
camera = world. .;
camera.([boxer.()]);
() {
items = classifier.({ : [modelName] });
hider.(items);
}
() {
items = classifier.({ : [modelName] });
hider.(items);
}
() {
hider.();
}
() {
fragments.(modelId);
}
Quick Reference
| Task | Method |
|---|
| Set coordination base | fragments.baseCoordinationModel = id + fragments.baseCoordinationMatrix = matrix |
| Align model to base | fragments.applyBaseCoordinateSystem(object, matrix) |
| Hide elements | hider.set(false, modelIdMap) |
| Show elements | hider.set(true, modelIdMap) |
| Show all | hider.set(true) |
| Isolate elements | hider.isolate(modelIdMap) |
| Toggle visibility | hider.toggle(modelIdMap) |
| Get visibility state | hider.getVisibilityMap(true/false, modelIds?) |
| Bounding box from models | boxer.addFromModels(modelIds?) |
| Bounding box from items | boxer.addFromModelIdMap(items) |
| Get box | boxer.get() |
| Get center | boxer.getCenter(items) |
| Camera orientation | boxer.getCameraOrientation(direction, offset?) |
| Classify by model | classifier.byModel() |
| Find by model | classifier.find({ models: [name] }) |
| Dispose single model | fragments.disposeModel(modelId) |
Related Skills
thatopen-core-fragments — FragmentsManager, ModelIdMap, worker initialization
thatopen-core-architecture — Component system, world setup, lifecycle
thatopen-syntax-ifc-loading — IfcLoader configuration and WASM setup
thatopen-impl-selection — Highlighter for visual selection in federated scenes
thatopen-impl-viewer — Full viewer setup with fragments initialization
References