
Thatopen Impl Federation
- 5 installs
- 17 repo stars
- Updated July 8, 2026
- openaec-foundation/thatopen-claude-skill-package
Helps with ai & agent building tasks.
About
thatopen-impl-federation is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- thatopen-impl-federation
- AI & Agent Building
- AI-coding skill
Thatopen Impl Federation by the numbers
- 5 all-time installs (skills.sh)
- Ranked #13,046 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Jul 26, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/thatopen-claude-skill-package --skill thatopen-impl-federationAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 5 |
|---|---|
| repo stars | ★ 17 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/thatopen-claude-skill-package ↗ |
What it does
Helps with ai & agent building tasks.
Files
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 ModelIdMapCritical Warnings
1. 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.
2. ALWAYS set `baseCoordinationModel` and `baseCoordinationMatrix` before loading additional models. The base defines the shared origin. All subsequent models are transformed relative to it.
3. NEVER assume models share the same coordinate origin. Even models from the same project may use different site placement or project base points.
4. 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.
5. 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);
// Initialize worker and loader
fragments.init(workerURL);
await ifcLoader.setup();
// Load first model — becomes the base
const architecturalBytes = new Uint8Array(/* ... */);
const archModel = await ifcLoader.load(architecturalBytes, true, "Architectural");
// Set coordination base from first model
fragments.baseCoordinationModel = archModel.modelId;
fragments.baseCoordinationMatrix = archModel.coordinationMatrix;
// Load second model — coordinate=true triggers alignment
const structuralBytes = new Uint8Array(/* ... */);
const structModel = await ifcLoader.load(structuralBytes, true, "Structural");
// Manually align if needed (automatic when coordinate=true in load)
fragments.applyBaseCoordinateSystem(structModel, structModel.coordinationMatrix);
// Load third model
const mepBytes = new Uint8Array(/* ... */);
const mepModel = await ifcLoader.load(mepBytes, true, "MEP");
fragments.applyBaseCoordinateSystem(mepModel, mepModel.coordinationMatrix);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.
// FragmentsManager coordination properties
fragments.baseCoordinationModel: string; // model ID of the reference model
fragments.baseCoordinationMatrix: THREE.Matrix4; // matrix of the reference model
// Apply alignment to any THREE.Object3D
fragments.applyBaseCoordinateSystem(
object: THREE.Object3D, // the object to transform
originalMatrix?: THREE.Matrix4 // its original coordination matrix
): THREE.Matrix4; // returns the applied transformationHow 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();
// ... scene, camera, renderer setup ...
// Models are automatically added to the scene on load
// To verify:
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 {
// Show or hide elements
set(visible: boolean, modelIdMap?: ModelIdMap): void;
// Show ONLY the specified elements, hide everything else
isolate(modelIdMap: ModelIdMap): void;
// Toggle visibility of specified elements
toggle(modelIdMap: ModelIdMap): void;
// Get current visibility state
getVisibilityMap(state: boolean, modelIds?: string[]): Map<string, Set<number>>;
}Per-Model Visibility
const hider = components.get(OBC.Hider);
const classifier = components.get(OBC.Classifier);
// Classify models
classifier.byModel();
// Get ModelIdMap for a specific model
const structuralItems = await classifier.find({
models: ["Structural"]
});
// Hide the structural model
hider.set(false, structuralItems);
// Show it again
hider.set(true, structuralItems);Isolate a Single Model
// Show ONLY the MEP model, hide everything else
const mepItems = await classifier.find({
models: ["MEP"]
});
hider.isolate(mepItems);Toggle Visibility
// Toggle architectural model on/off
const archItems = await classifier.find({
models: ["Architectural"]
});
hider.toggle(archItems);Hide by Category Across Models
// Classify by IFC category
classifier.byCategory();
// Hide all walls across ALL models
const allWalls = await classifier.find({
categories: ["IFCWALL"]
});
hider.set(false, allWalls);Get Visibility State
// Get all currently visible items
const visibleItems = hider.getVisibilityMap(true);
// Get visible items for specific models only
const visibleInArch = hider.getVisibilityMap(true, [archModel.modelId]);
// Get all currently hidden items
const hiddenItems = hider.getVisibilityMap(false);Show All (Reset Visibility)
// Show everything — call set(true) with no ModelIdMap
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 {
// Add items to the bounding box computation
addFromModelIdMap(items: ModelIdMap): void;
addFromModels(modelIds?: string[]): void;
// Get the computed bounding box
get(): THREE.Box3;
// Get center point of items
getCenter(modelIdMap: ModelIdMap): THREE.Vector3;
// Get camera position and target for a given orientation
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);
// Add all loaded models
boxer.addFromModels();
// Get the unified bounding box
const box = boxer.get();
// Fit camera to encompass all models
const camera = world.camera as OBC.OrthoPerspectiveCamera;
camera.fit([box]);Fit Camera to a Selection
// Compute bounding box for specific items
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
// Get camera position for a front view of the model
boxer.addFromModels();
const frontView = boxer.getCameraOrientation("front", 1.5);
// frontView.position → THREE.Vector3 (camera position)
// frontView.target → THREE.Vector3 (look-at point)
// Apply to camera
camera.controls.setLookAt(
frontView.position.x, frontView.position.y, frontView.position.z,
frontView.target.x, frontView.target.y, frontView.target.z,
true // animate
);Get Center of Items
const center = boxer.getCenter(selectedItems);
// center → THREE.Vector3 at the centroid of the selected elementsCross-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);
// Create model-based classification
classifier.byModel();
// The classifier.list now contains a "models" group
// Each entry maps a model name to its ModelIdMapClassifier.find with Filters
// Find items by model name
const archItems = await classifier.find({ models: ["Architectural"] });
// Find items by category within a specific model
const archWalls = await classifier.find({
models: ["Architectural"],
categories: ["IFCWALL"]
});
// Find items by storey
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";
// 1. Setup
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.get(OBC.Hider);
const classifier = components.get(OBC.Classifier);
const boxer = components.get(OBC.BoundingBoxer);
// 2. Load and coordinate models
const archModel = await ifcLoader.load(archBytes, true, "Architectural");
fragments.baseCoordinationModel = archModel.modelId;
fragments.baseCoordinationMatrix = archModel.coordinationMatrix;
const structModel = await ifcLoader.load(structBytes, true, "Structural");
const mepModel = await ifcLoader.load(mepBytes, true, "MEP");
// 3. Classify
classifier.byModel();
classifier.byCategory();
classifier.byIfcBuildingStorey();
// 4. Fit camera to all models
boxer.addFromModels();
const camera = world.camera as OBC.OrthoPerspectiveCamera;
camera.fit([boxer.get()]);
// 5. Per-model visibility controls (e.g., UI toggle)
async function toggleModel(modelName: string) {
const items = await classifier.find({ models: [modelName] });
hider.toggle(items);
}
// 6. Isolate a discipline
async function isolateDiscipline(modelName: string) {
const items = await classifier.find({ models: [modelName] });
hider.isolate(items);
}
// 7. Show all
function showAll() {
hider.set(true);
}
// 8. Cleanup
function removeModel(modelId: string) {
fragments.disposeModel(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 initializationthatopen-core-architecture— Component system, world setup, lifecyclethatopen-syntax-ifc-loading— IfcLoader configuration and WASM setupthatopen-impl-selection— Highlighter for visual selection in federated scenesthatopen-impl-viewer— Full viewer setup with fragments initialization
References
- references/methods.md — Hider, BoundingBoxer, coordination matrix APIs
- references/examples.md — Multi-model load, isolate, visibility, bounding box
- references/anti-patterns.md — Missing coordination, wrong isolation
Federation Anti-Patterns
AP-1: Missing Coordinate Alignment
Wrong:
const archModel = await ifcLoader.load(archBytes, true, "Architectural");
const structModel = await ifcLoader.load(structBytes, true, "Structural");
// Models appear at different positions — no coordination setWhy it fails: Each IFC file contains a coordination matrix that positions the model in world space. Without setting a base and aligning subsequent models, they appear at their original authoring origins, which may be hundreds of meters apart.
Correct:
const archModel = await ifcLoader.load(archBytes, true, "Architectural");
fragments.baseCoordinationModel = archModel.modelId;
fragments.baseCoordinationMatrix = archModel.coordinationMatrix;
const structModel = await ifcLoader.load(structBytes, true, "Structural");
// With coordinate=true and base set, alignment happens automaticallyRule: ALWAYS set baseCoordinationModel and baseCoordinationMatrix from the first loaded model before loading any additional models.
---
AP-2: Using expressID Instead of ModelIdMap
Wrong:
// Trying to hide element 42 — but which model?
hider.set(false, { "": new Set([42]) });Why it fails: Express IDs are only unique within a single IFC model. Two models may both have an element with express ID 42 referring to completely different objects. Using an empty string or wrong model ID causes silent failures or affects the wrong elements.
Correct:
const items: ModelIdMap = {
[archModel.modelId]: new Set([42])
};
hider.set(false, items);Rule: ALWAYS use the correct model UUID as the key in ModelIdMap. NEVER use express IDs without their model context.
---
AP-3: Manual Hide-All-Then-Show Instead of Isolate
Wrong:
// Hide everything, then show one model
hider.set(false);
const mepItems = await classifier.find({ models: ["MEP"] });
hider.set(true, mepItems);
// Two separate operations — may cause visual flickerWhy it fails: Two separate visibility calls create a frame where everything is hidden, causing a visual flash. The state between calls is inconsistent.
Correct:
const mepItems = await classifier.find({ models: ["MEP"] });
hider.isolate(mepItems);
// Atomic operation — no flickerRule: ALWAYS use hider.isolate() when you want to show only specific elements. NEVER implement isolation manually with sequential set() calls.
---
AP-4: Forgetting to Classify Before Querying
Wrong:
const classifier = components.get(OBC.Classifier);
// No classification calls made
const items = await classifier.find({ models: ["Architectural"] });
// Returns empty — classifier has no dataWhy it fails: classifier.find() queries the internal classification lists. These lists are empty until you call byModel(), byCategory(), or byIfcBuildingStorey(). The classifier does not auto-populate.
Correct:
const classifier = components.get(OBC.Classifier);
classifier.byModel();
classifier.byCategory();
const items = await classifier.find({ models: ["Architectural"] });Rule: ALWAYS call the relevant by*() methods before using classifier.find(). Call them again after loading new models.
---
AP-5: Not Re-Classifying After Loading New Models
Wrong:
classifier.byModel();
// Load model A — classified
const modelA = await ifcLoader.load(bytesA, true, "ModelA");
// Load model B — NOT in classifier yet
const modelB = await ifcLoader.load(bytesB, true, "ModelB");
const bItems = await classifier.find({ models: ["ModelB"] });
// Returns empty — ModelB was loaded after classificationWhy it fails: Classification is a snapshot operation. It groups elements that are loaded at the time of the call. Models loaded after byModel() are not included in the classification.
Correct:
const modelA = await ifcLoader.load(bytesA, true, "ModelA");
const modelB = await ifcLoader.load(bytesB, true, "ModelB");
classifier.byModel(); // classify AFTER all models are loaded
// OR re-classify after each load
fragments.onFragmentsLoaded.add(() => {
classifier.byModel();
classifier.byCategory();
});Rule: ALWAYS re-classify after loading new models. Use the onFragmentsLoaded event for automatic re-classification.
---
AP-6: Calling BoundingBoxer.get() Without Adding Items
Wrong:
const boxer = components.get(OBC.BoundingBoxer);
const box = boxer.get();
// Returns an empty or invalid Box3 — nothing was addedWhy it fails: BoundingBoxer accumulates items through addFromModels() or addFromModelIdMap(). Calling get() without adding anything returns an uninitialized bounding box.
Correct:
const boxer = components.get(OBC.BoundingBoxer);
boxer.addFromModels(); // or boxer.addFromModelIdMap(items)
const box = boxer.get();Rule: ALWAYS call addFromModels() or addFromModelIdMap() before get() or getCameraOrientation().
---
AP-7: Disposing Models Without Updating Classification
Wrong:
fragments.disposeModel(mepModel.modelId);
// classifier still has stale entries for the disposed model
const mepItems = await classifier.find({ models: ["MEP"] });
hider.set(false, mepItems);
// Operates on stale data — may cause errors or no-opsWhy it fails: Disposing a model removes it from fragments.list and frees GPU resources, but the classifier retains its stale classification entries. Operating on these stale entries targets elements that no longer exist.
Correct:
fragments.disposeModel(mepModel.modelId);
// Re-classify to remove stale entries
classifier.byModel();
classifier.byCategory();Rule: ALWAYS re-classify after disposing models to keep classification data in sync with loaded models.
---
AP-8: Setting Coordination Base After Loading Multiple Models
Wrong:
const archModel = await ifcLoader.load(archBytes, true, "Architectural");
const structModel = await ifcLoader.load(structBytes, true, "Structural");
// Too late — structural model was already placed without coordination
fragments.baseCoordinationModel = archModel.modelId;
fragments.baseCoordinationMatrix = archModel.coordinationMatrix;Why it fails: The coordination base must be set before loading subsequent models. When coordinate=true is passed to load(), the loader uses the current base to align the model. If no base is set, alignment is skipped.
Correct:
const archModel = await ifcLoader.load(archBytes, true, "Architectural");
fragments.baseCoordinationModel = archModel.modelId;
fragments.baseCoordinationMatrix = archModel.coordinationMatrix;
// NOW load additional models
const structModel = await ifcLoader.load(structBytes, true, "Structural");Rule: ALWAYS set the coordination base immediately after loading the first model, before loading any subsequent models.
Federation Examples
Example 1: Multi-Model Load with Coordination
Load three discipline models and align them to a shared coordinate system.
import * as OBC from "@thatopen/components";
import * as OBCF from "@thatopen/components-front";
import * as THREE from "three";
const components = new OBC.Components();
// World setup
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);
// Fragment and loader setup
const fragments = components.get(OBC.FragmentsManager);
fragments.init("https://unpkg.com/@thatopen/fragments@3.3.6/dist/Worker/worker.mjs");
const ifcLoader = components.get(OBC.IfcLoader);
await ifcLoader.setup();
// Load architectural model as base
const archModel = await ifcLoader.load(archBytes, true, "Architectural");
fragments.baseCoordinationModel = archModel.modelId;
fragments.baseCoordinationMatrix = archModel.coordinationMatrix;
// Load structural model — aligned automatically (coordinate=true)
const structModel = await ifcLoader.load(structBytes, true, "Structural");
// Load MEP model — aligned automatically
const mepModel = await ifcLoader.load(mepBytes, true, "MEP");
// Fit camera to encompass all models
const boxer = components.get(OBC.BoundingBoxer);
boxer.addFromModels();
const camera = world.camera as OBC.OrthoPerspectiveCamera;
camera.fit([boxer.get()]);
console.log(`Loaded ${fragments.list.size} models`);---
Example 2: Per-Model Visibility Toggle
Create a UI-driven model visibility toggle using Hider and Classifier.
const hider = components.get(OBC.Hider);
const classifier = components.get(OBC.Classifier);
// Classify by model after loading
classifier.byModel();
// Toggle function for UI buttons
async function setModelVisibility(modelName: string, visible: boolean) {
const items = await classifier.find({ models: [modelName] });
hider.set(visible, items);
}
// Usage
await setModelVisibility("Structural", false); // hide structural
await setModelVisibility("Structural", true); // show structural---
Example 3: Isolate a Single Model
Show only one discipline, hiding everything else.
const hider = components.get(OBC.Hider);
const classifier = components.get(OBC.Classifier);
classifier.byModel();
// Isolate MEP — hides architectural and structural
const mepItems = await classifier.find({ models: ["MEP"] });
hider.isolate(mepItems);
// Later, show everything again
hider.set(true);---
Example 4: Hide Category Across All Models
Hide all walls regardless of which model they belong to.
const hider = components.get(OBC.Hider);
const classifier = components.get(OBC.Classifier);
classifier.byCategory();
// Find all walls across all models
const allWalls = await classifier.find({ categories: ["IFCWALL"] });
hider.set(false, allWalls);
// Show them again
hider.set(true, allWalls);---
Example 5: BoundingBoxer — Fit Camera to Selection
Compute a bounding box for a subset of elements and fit the camera.
const boxer = components.get(OBC.BoundingBoxer);
const classifier = components.get(OBC.Classifier);
classifier.byModel();
classifier.byIfcBuildingStorey();
// Get ground floor elements across all models
const groundFloor = await classifier.find({ storeys: ["Ground Floor"] });
// Compute bounding box and fit camera
boxer.addFromModelIdMap(groundFloor);
const box = boxer.get();
const camera = world.camera as OBC.OrthoPerspectiveCamera;
camera.fit([box]);---
Example 6: Camera Orientation for Architectural Views
Use BoundingBoxer to position the camera for standard views.
const boxer = components.get(OBC.BoundingBoxer);
boxer.addFromModels(); // include all models
// Front elevation
const front = boxer.getCameraOrientation("front", 1.5);
camera.controls.setLookAt(
front.position.x, front.position.y, front.position.z,
front.target.x, front.target.y, front.target.z,
true
);
// Top-down plan view
const top = boxer.getCameraOrientation("top", 2.0);
camera.controls.setLookAt(
top.position.x, top.position.y, top.position.z,
top.target.x, top.target.y, top.target.z,
true
);---
Example 7: Cross-Model Query and Highlight
Find elements across models and highlight them.
const classifier = components.get(OBC.Classifier);
const fragments = components.get(OBC.FragmentsManager);
classifier.byModel();
classifier.byCategory();
// Find walls in architectural + columns in structural
const archWalls = await classifier.find({
models: ["Architectural"],
categories: ["IFCWALL"]
});
const structColumns = await classifier.find({
models: ["Structural"],
categories: ["IFCCOLUMN"]
});
// Merge into one ModelIdMap
const combined: Record<string, Set<number>> = { ...archWalls };
for (const [modelId, ids] of Object.entries(structColumns)) {
if (combined[modelId]) {
for (const id of ids) combined[modelId].add(id);
} else {
combined[modelId] = new Set(ids);
}
}
// Highlight the combined selection
await fragments.highlight(
{ color: new THREE.Color("#FF6600"), opacity: 0.7 },
combined
);---
Example 8: Model Load Event and Auto-Classify
Automatically classify every model as it loads.
const fragments = components.get(OBC.FragmentsManager);
const classifier = components.get(OBC.Classifier);
fragments.onFragmentsLoaded.add((model) => {
console.log(`Model loaded: ${model.name}`);
// Re-classify to include the new model
classifier.byModel();
classifier.byCategory();
classifier.byIfcBuildingStorey();
});---
Example 9: Selective Model Disposal
Remove one model from a federated scene without affecting others.
const fragments = components.get(OBC.FragmentsManager);
// Check loaded models
console.log("Before:", [...fragments.list.keys()]);
// Dispose only the MEP model
fragments.disposeModel(mepModel.modelId);
console.log("After:", [...fragments.list.keys()]);
// MEP model is gone; architectural and structural remain---
Example 10: Get Visibility State
Query which elements are currently visible or hidden.
const hider = components.get(OBC.Hider);
// Hide some elements first
hider.set(false, someItems);
// Query visible elements across all models
const visibleMap = hider.getVisibilityMap(true);
for (const [modelId, ids] of visibleMap) {
console.log(`Model ${modelId}: ${ids.size} visible elements`);
}
// Query hidden elements in a specific model only
const hiddenInArch = hider.getVisibilityMap(false, [archModel.modelId]);
console.log(`Hidden in arch: ${hiddenInArch.get(archModel.modelId)?.size ?? 0}`);Federation API Reference
Hider
Controls element visibility at the fragment level. Operates on ModelIdMap for per-model and cross-model visibility management.
Class Definition
class Hider extends Component {
static readonly uuid: string;
enabled: boolean;
}Methods
set(visible, modelIdMap?)
Show or hide elements. When called without a ModelIdMap, affects ALL elements in ALL loaded models.
set(visible: boolean, modelIdMap?: ModelIdMap): void| Parameter | Type | Required | Description |
|---|---|---|---|
visible | boolean | Yes | true to show, false to hide |
modelIdMap | ModelIdMap | No | Elements to affect. Omit to affect all. |
Behavior:
set(true)— shows ALL elements (reset visibility)set(false)— hides ALL elementsset(true, items)— shows only specified itemsset(false, items)— hides only specified items
isolate(modelIdMap)
Shows ONLY the specified elements. Everything else is hidden. This is equivalent to calling set(false) followed by set(true, modelIdMap) but executed atomically.
isolate(modelIdMap: ModelIdMap): void| Parameter | Type | Required | Description |
|---|---|---|---|
modelIdMap | ModelIdMap | Yes | Elements to keep visible |
ALWAYS use `isolate()` instead of manual hide-all-then-show. The atomic operation prevents flickering and ensures correct state.
toggle(modelIdMap)
Inverts visibility of specified elements. Visible elements become hidden; hidden elements become visible.
toggle(modelIdMap: ModelIdMap): void| Parameter | Type | Required | Description |
|---|---|---|---|
modelIdMap | ModelIdMap | Yes | Elements to toggle |
getVisibilityMap(state, modelIds?)
Returns a map of elements matching the requested visibility state.
getVisibilityMap(state: boolean, modelIds?: string[]): Map<string, Set<number>>| Parameter | Type | Required | Description |
|---|---|---|---|
state | boolean | Yes | true for visible items, false for hidden |
modelIds | string[] | No | Filter to specific models. Omit for all models. |
Returns: Map<string, Set<number>> — model ID to set of local element IDs.
---
BoundingBoxer
Computes axis-aligned bounding boxes for elements and models. Used for camera fitting, spatial queries, and view orientation.
Class Definition
class BoundingBoxer extends Component {
static readonly uuid: string;
enabled: boolean;
}Methods
addFromModelIdMap(items)
Add specific elements to the bounding box computation.
addFromModelIdMap(items: ModelIdMap): void| Parameter | Type | Required | Description |
|---|---|---|---|
items | ModelIdMap | Yes | Elements to include in the bounding box |
addFromModels(modelIds?)
Add entire models to the bounding box computation. When called without arguments, includes ALL loaded models.
addFromModels(modelIds?: string[]): void| Parameter | Type | Required | Description |
|---|---|---|---|
modelIds | string[] | No | Model IDs to include. Omit for all models. |
get()
Returns the computed axis-aligned bounding box encompassing all added items.
get(): THREE.Box3Returns: THREE.Box3 — the unified bounding box.
ALWAYS call `addFromModelIdMap()` or `addFromModels()` before `get()`. The bounding box is computed incrementally from added items.
getCenter(modelIdMap)
Returns the centroid of the bounding box of the specified items.
getCenter(modelIdMap: ModelIdMap): THREE.Vector3| Parameter | Type | Required | Description |
|---|---|---|---|
modelIdMap | ModelIdMap | Yes | Elements to compute center for |
Returns: THREE.Vector3 — center point.
getCameraOrientation(orientation, offsetFactor?)
Computes camera position and look-at target for standard architectural views.
getCameraOrientation(
orientation: "front" | "back" | "left" | "right" | "top" | "bottom",
offsetFactor?: number
): { position: THREE.Vector3; target: THREE.Vector3 }| Parameter | Type | Required | Description |
|---|---|---|---|
orientation | string | Yes | View direction |
offsetFactor | number | No | Distance multiplier from bounding box center. Default: 1.0. Use >1 for more padding. |
Returns: Object with position and target vectors for camera placement.
ALWAYS call `addFromModels()` or `addFromModelIdMap()` before `getCameraOrientation()`. The orientation is computed from the current bounding box state.
---
FragmentsManager — Coordination API
These methods on FragmentsManager handle multi-model coordinate alignment.
Properties
baseCoordinationModel
baseCoordinationModel: stringThe model ID of the reference model. All other models are aligned relative to this model's coordinate system.
ALWAYS set this before loading additional models.
baseCoordinationMatrix
baseCoordinationMatrix: THREE.Matrix4The 4x4 transformation matrix of the base model. Retrieved from the first loaded model's coordinationMatrix property.
ALWAYS set this alongside `baseCoordinationModel`.
Methods
applyBaseCoordinateSystem(object, originalMatrix?)
Transforms an object from its original coordinate system into the base coordinate system.
applyBaseCoordinateSystem(
object: THREE.Object3D,
originalMatrix?: THREE.Matrix4
): THREE.Matrix4| Parameter | Type | Required | Description |
|---|---|---|---|
object | THREE.Object3D | Yes | The object to transform (typically a FragmentsModel) |
originalMatrix | THREE.Matrix4 | No | The object's original coordination matrix |
Returns: THREE.Matrix4 — the transformation matrix that was applied.
Algorithm: 1. Compute inverse of baseCoordinationMatrix 2. Multiply inverse by originalMatrix 3. Apply result to object.matrix
disposeModel(modelId)
Removes a single model from the scene and frees its resources.
disposeModel(modelId: string): void| Parameter | Type | Required | Description |
|---|---|---|---|
modelId | string | Yes | The model ID to dispose |
Behavior: Removes the model from fragments.list, disposes all Fragment GPU buffers (geometry, material, textures), removes meshes from the scene graph, and fires onBeforeDispose.
---
Classifier — Model Grouping
byModel()
Groups all loaded elements by their source model name. Creates entries in classifier.list under the "models" key.
byModel(): voidALWAYS call after loading models to enable per-model queries.
find(classificationData)
Queries elements matching classification criteria. Returns a ModelIdMap of matching elements across all loaded models.
find(classificationData: {
models?: string[];
categories?: string[];
storeys?: string[];
}): Promise<ModelIdMap>| Parameter | Type | Required | Description |
|---|---|---|---|
models | string[] | No | Model names to filter by |
categories | string[] | No | IFC categories (e.g., "IFCWALL") |
storeys | string[] | No | Building storey names |
Returns: ModelIdMap matching all specified criteria (intersection).
---
ModelIdMap Type
type ModelIdMap = Record<string, Set<number>>;- Keys: Model UUID strings (from
FragmentsModel.modelId) - Values: Sets of local element IDs (numbers, unique within each model)
ALWAYS use ModelIdMap for any operation targeting specific elements. NEVER use express IDs alone — they are only unique within a single model.