
Thatopen Agents Model Analyzer
- 5 installs
- 17 repo stars
- Updated July 8, 2026
- openaec-foundation/thatopen-claude-skill-package
Helps with ai & agent building tasks.
About
thatopen-agents-model-analyzer is a Claude Code skill in the AI & Agent Building category.
- thatopen-agents-model-analyzer
- AI & Agent Building
- AI-coding skill
Thatopen Agents Model Analyzer by the numbers
- 5 all-time installs (skills.sh)
- Ranked #13,064 of 16,544 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-agents-model-analyzerAdd 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
ThatOpen Model Analyzer: Agent Workflow
Purpose
This is an agent skill — it defines a guided analysis workflow for extracting structured information from loaded IFC models. Use it to produce model summaries, element inventories, property reports, spatial structure maps, classification breakdowns, and quality validation results.
The workflow combines ThatOpen's Classifier, FragmentsManager.getData(), ItemsFinder, and direct web-ifc queries to build a complete picture of a model's contents.
Prerequisites
Before starting any analysis workflow, verify these conditions:
1. Model is loaded — A FragmentsModel exists in components.get(OBC.FragmentsManager).list. NEVER attempt analysis on an unloaded model. 2. FragmentsManager is initialized — fragments.init(workerURL) has been called. ALWAYS verify this before calling getData(). 3. web-ifc is accessible — For low-level queries, IfcLoader.webIfc provides the IfcAPI instance. NEVER create a second IfcAPI.
Critical Rules
1. ALWAYS classify before querying. Run Classifier.byCategory() and Classifier.byIfcBuildingStorey() before any analysis step that depends on classification groups.
2. NEVER query properties for all elements at once. ALWAYS paginate getData() calls — batch by type or storey to avoid main-thread jank.
3. ALWAYS use `ModelIdMap` as the interchange format between analysis steps. Every Classifier result, ItemsFinder result, and getData input uses ModelIdMap (Map<string, Set<number>>).
4. ALWAYS dispose analysis resources when done. If you created temporary classifications or groups, clean them up.
5. NEVER assume property sets exist. Not all IFC models have complete property data. ALWAYS handle missing Psets gracefully.
6. ALWAYS detect the IFC schema version first. Use ifcApi.GetModelSchema(modelID) — behavior differs between IFC2X3, IFC4, and IFC4X3.
---
Analysis Workflow: Step by Step
Phase 1: Model Identification
Collect basic model metadata before deeper analysis.
import * as OBC from "@thatopen/components";
const fragments = components.get(OBC.FragmentsManager);
const loader = components.get(OBC.IfcLoader);
const ifcApi = loader.webIfc;
// Step 1a: List loaded models
for (const [modelId, model] of fragments.list) {
console.log(`Model: ${modelId}`);
}
// Step 1b: Get schema version (requires the web-ifc modelID)
const schema = ifcApi.GetModelSchema(modelID);
// Returns: "IFC2X3" | "IFC4" | "IFC4X3"
// Step 1c: Get all IFC types present in model
const allTypes = ifcApi.GetAllTypesOfModel(modelID);
// Returns: Array<{ typeID: number, typeName: string }>Decision point: If allTypes returns fewer than expected types, the model may have been loaded with filtered IFC classes. Check IfcLoader settings.
Phase 2: Classification
Build the classification index for all subsequent queries.
const classifier = components.get(OBC.Classifier);
// ALWAYS run both — they are independent and fast
await classifier.byCategory();
await classifier.byIfcBuildingStorey();
await classifier.byModel();Decision point: After classification, inspect classifier.list.get("Categories") — if it contains fewer groups than expected from Phase 1's type list, some elements may lack geometry (spatial elements like IfcProject are not classified by category).
Phase 3: Element Inventory
Count elements by IFC type using classification groups.
// Method A: Via Classifier (fragment-level, includes only geometric elements)
const categories = classifier.list.get("Categories");
if (categories) {
const inventory: Record<string, number> = {};
for (const [categoryName, groupData] of categories) {
const items = await groupData.get();
let count = 0;
for (const [, ids] of Object.entries(items)) {
count += (ids as Set<number>).size;
}
inventory[categoryName] = count;
}
console.log("Element inventory:", inventory);
}
// Method B: Via web-ifc (includes ALL entities, not just geometric)
import { IFCWALL, IFCSLAB, IFCDOOR, IFCWINDOW, IFCBEAM, IFCCOLUMN,
IFCROOF, IFCSTAIR, IFCFURNISHINGELEMENT } from "web-ifc";
const typesToCount = [
{ type: IFCWALL, name: "Walls" },
{ type: IFCSLAB, name: "Slabs" },
{ type: IFCDOOR, name: "Doors" },
{ type: IFCWINDOW, name: "Windows" },
{ type: IFCBEAM, name: "Beams" },
{ type: IFCCOLUMN, name: "Columns" },
{ type: IFCROOF, name: "Roofs" },
{ type: IFCSTAIR, name: "Stairs" },
{ type: IFCFURNISHINGELEMENT, name: "Furniture" },
];
for (const { type, name } of typesToCount) {
const ids = ifcApi.GetLineIDsWithType(modelID, type);
console.log(`${name}: ${ids.size()}`);
}Decision point: Choose Method A for visual/geometric element counts. Choose Method B for complete IFC entity counts (includes non-geometric entities). For a full report, use both and note the difference.
Phase 4: Spatial Structure
Extract the project hierarchy.
// Method A: Via web-ifc properties helper (complete tree)
const spatialTree = await ifcApi.properties.getSpatialStructure(modelID);
// Returns: { expressID, type, children: [...] }
function printTree(node: any, indent = 0) {
const prefix = " ".repeat(indent);
console.log(`${prefix}${node.type} [#${node.expressID}]`);
if (node.children) {
for (const child of node.children) {
printTree(child, indent + 1);
}
}
}
printTree(spatialTree);
// Method B: Via Classifier storey groups (element-to-storey mapping)
const storeys = classifier.list.get("Storeys");
if (storeys) {
for (const [storeyName, groupData] of storeys) {
const items = await groupData.get();
let elementCount = 0;
for (const ids of Object.values(items)) {
elementCount += (ids as Set<number>).size;
}
console.log(`${storeyName}: ${elementCount} elements`);
}
}Decision point: Method A gives the full IFC hierarchy tree (Project > Site > Building > Storey > Space). Method B gives only storey-level grouping with element counts. Use Method A for structural reports, Method B for per-storey analysis.
Phase 5: Property Analysis
Extract and enumerate property sets for targeted elements.
// Step 5a: Pick a target group (e.g., all walls)
const wallItems = await classifier.find({
Categories: ["IFCWALL"]
});
// Step 5b: Extract property data (paginated)
const wallData = await fragments.getData(wallItems);
// Step 5c: Enumerate property sets
for (const [modelId, itemDataArray] of Object.entries(wallData)) {
for (const itemData of itemDataArray) {
console.log("Element:", itemData);
// itemData contains property sets, type info, attributes
}
}
// Step 5d: For detailed property sets via web-ifc
const wallIDs = ifcApi.GetLineIDsWithType(modelID, IFCWALL);
for (let i = 0; i < Math.min(wallIDs.size(), 5); i++) {
const psets = await ifcApi.properties.getPropertySets(
modelID, wallIDs.get(i), false
);
console.log(`Wall #${wallIDs.get(i)} property sets:`, psets);
}ALWAYS limit property queries. In Step 5d, the Math.min(... , 5) pattern demonstrates sampling. For full reports, iterate in batches.
Phase 6: Cross-Classification Analysis
Combine classifications for targeted analysis.
// Example: Walls on the ground floor
const groundFloorWalls = await classifier.find({
Categories: ["IFCWALL"],
Storeys: ["Ground Floor"]
});
// Example: All structural elements on Level 1
const structuralLevel1 = await classifier.find({
Categories: ["IFCWALL", "IFCSLAB", "IFCBEAM", "IFCCOLUMN"],
Storeys: ["Level 1"]
});
// Extract properties for the cross-classified items
const structData = await fragments.getData(structuralLevel1);Phase 7: Validation Checks
Run quality checks on the model.
// Check 1: Orphaned elements (not in any storey)
const allCategoryItems = await classifier.find({ Categories: ["IFCWALL"] });
const storeyWalls = await classifier.find({
Categories: ["IFCWALL"],
Storeys: Array.from(storeys?.keys() ?? [])
});
// Compare: items in allCategoryItems but not in storeyWalls are orphaned
// Check 2: Elements without property sets
for (let i = 0; i < wallIDs.size(); i++) {
const psets = await ifcApi.properties.getPropertySets(
modelID, wallIDs.get(i), false
);
if (!psets || psets.length === 0) {
console.warn(`Wall #${wallIDs.get(i)} has no property sets`);
}
}
// Check 3: Missing spatial hierarchy levels
import { IFCPROJECT, IFCSITE, IFCBUILDING, IFCBUILDINGSTOREY } from "web-ifc";
const requiredTypes = [
{ type: IFCPROJECT, name: "IfcProject" },
{ type: IFCSITE, name: "IfcSite" },
{ type: IFCBUILDING, name: "IfcBuilding" },
{ type: IFCBUILDINGSTOREY, name: "IfcBuildingStorey" },
];
for (const { type, name } of requiredTypes) {
const ids = ifcApi.GetLineIDsWithType(modelID, type);
if (ids.size() === 0) {
console.warn(`Missing required spatial element: ${name}`);
}
}Phase 8: Report Generation
Compile findings into structured output.
ALWAYS use this output format for analysis reports:
=== IFC MODEL ANALYSIS REPORT ===
Model: {filename}
Schema: {IFC2X3 | IFC4 | IFC4X3}
Total IFC entity types: {count}
--- ELEMENT INVENTORY ---
| IFC Type | Count |
|------------------|-------|
| IFCWALL | {n} |
| IFCSLAB | {n} |
| ... | ... |
| TOTAL | {sum} |
--- SPATIAL STRUCTURE ---
IfcProject: {name}
IfcSite: {name}
IfcBuilding: {name}
IfcBuildingStorey: {name} ({n} elements)
IfcBuildingStorey: {name} ({n} elements)
...
--- PROPERTY SETS ---
| Property Set Name | Occurrence Count |
|----------------------|------------------|
| Pset_WallCommon | {n} |
| ... | ... |
--- VALIDATION ---
[PASS/WARN] Spatial hierarchy completeness
[PASS/WARN] Elements with property sets: {n}/{total} ({%})
[PASS/WARN] Elements assigned to storeys: {n}/{total} ({%})
--- NOTES ---
{Any observations, anomalies, or recommendations}
=== END REPORT ===---
Decision Tree
Use this to determine which analysis path to follow:
User wants to analyze a model
├─ "What's in this model?" → Phase 1 + 2 + 3 (inventory)
├─ "Show me the structure" → Phase 1 + 2 + 4 (spatial)
├─ "What properties do X have?" → Phase 1 + 2 + 5 (properties)
├─ "How many X on floor Y?" → Phase 1 + 2 + 6 (cross-classification)
├─ "Is this model valid?" → Phase 1 + 2 + 7 (validation)
└─ "Full report" → All phases, output Phase 8 format---
Performance Guidelines
1. Batch property queries by type. Query all walls, then all slabs — NEVER query one element at a time in a loop without batching.
2. Use web-ifc `GetLineIDsWithType` for counting. It returns a Vector<number> with a .size() method — NEVER load full entity data just to count elements.
3. Limit `getData()` result sets. For models with 10,000+ elements, ALWAYS filter via Classifier first. NEVER pass the entire model to getData().
4. Cache classification results. classifier.byCategory() reads the entire model — call it once and reuse classifier.list across analysis steps.
5. Use `GetRawLineData` for statistics. When you only need type and ID (not full properties), GetRawLineData is faster than GetLine.
---
Quick Reference
| Analysis Task | Primary API | Fallback API |
|---|---|---|
| Schema version | ifcApi.GetModelSchema() | Header line query |
| Type inventory | ifcApi.GetAllTypesOfModel() | GetLineIDsWithType per type |
| Element count by type | classifier.list.get("Categories") | GetLineIDsWithType |
| Spatial tree | ifcApi.properties.getSpatialStructure() | Classifier storeys |
| Storey element counts | classifier.list.get("Storeys") | Spatial tree traversal |
| Property sets | fragments.getData(items) | ifcApi.properties.getPropertySets() |
| Cross-classification | classifier.find({...}) | ItemsFinder.getItems() |
| Orphan detection | Compare category vs storey sets | Spatial tree analysis |
Related Skills
thatopen-syntax-properties— Classifier API, getData, ItemsFinder detailsthatopen-core-web-ifc— Raw web-ifc query methodsthatopen-core-fragments— FragmentsManager, ModelIdMap, worker setupthatopen-syntax-ifc-loading— IfcLoader, model loading prerequisites
References
- references/methods.md — Analysis APIs, classification queries, data extraction
- references/examples.md — Model summary, property report, element inventory
- references/anti-patterns.md — Inefficient queries, missing classification
Model Analyzer — Anti-Patterns
AP-1: Querying All Properties Without Filtering
Wrong:
// Fetching properties for EVERY element in the model
const allItems: ModelIdMap = {};
for (const [modelId, model] of fragments.list) {
const allIDs = ifcApi.GetAllLines(modelID);
const ids = new Set<number>();
for (let i = 0; i < allIDs.size(); i++) {
ids.add(allIDs.get(i));
}
allItems[modelId] = ids;
}
const data = await fragments.getData(allItems); // Blocks main threadWhy it fails: getData() serializes results from the worker back to the main thread. For models with 50,000+ entities, this transfer causes severe jank or out-of-memory errors.
Correct:
// Filter by type first, then batch
await classifier.byCategory();
const wallItems = await classifier.find({ Categories: ["IFCWALL"] });
const wallData = await fragments.getData(wallItems);
// Repeat for other types as needed---
AP-2: Classifying Before Loading
Wrong:
const classifier = components.get(OBC.Classifier);
await classifier.byCategory(); // No models loaded yet!
// ... later ...
const model = await loader.load(data, true, "model.ifc");
// classifier.list is empty — classification ran on nothingWhy it fails: Classification methods read from loaded FragmentsModels. Running them before any model is loaded produces empty groups.
Correct:
const model = await loader.load(data, true, "model.ifc");
// THEN classify
await classifier.byCategory();
await classifier.byIfcBuildingStorey();---
AP-3: Using GetLine with flatten:true for Bulk Queries
Wrong:
const allIDs = ifcApi.GetAllLines(modelID);
for (let i = 0; i < allIDs.size(); i++) {
const entity = ifcApi.GetLine(modelID, allIDs.get(i), true); // flatten=true
// Process...
}Why it fails: flatten: true recursively resolves every reference in the entity. For a model with 100,000 entities, this creates millions of recursive calls and causes exponential slowdown or stack overflow.
Correct:
// Use flatten:false (default) for bulk queries
for (let i = 0; i < allIDs.size(); i++) {
const entity = ifcApi.GetLine(modelID, allIDs.get(i));
// Resolve specific references manually only when needed
}
// Or use GetRawLineData for statistics (faster, no parsing)
for (let i = 0; i < allIDs.size(); i++) {
const raw = ifcApi.GetRawLineData(modelID, allIDs.get(i));
// raw.type gives the IFC type code without parsing properties
}---
AP-4: Missing FragmentsManager Initialization
Wrong:
const fragments = components.get(OBC.FragmentsManager);
const data = await fragments.getData(items); // Throws or fails silentlyWhy it fails: getData() delegates to a web worker. Without calling fragments.init(workerURL) first, the worker is not available.
Correct:
const fragments = components.get(OBC.FragmentsManager);
fragments.init(workerURL); // MUST be called once before any getData()
// ... later ...
const data = await fragments.getData(items);---
AP-5: Ignoring Vector Access Pattern for web-ifc Results
Wrong:
const wallIDs = ifcApi.GetLineIDsWithType(modelID, IFCWALL);
for (let i = 0; i < wallIDs.length; i++) { // .length does not exist!
const wall = ifcApi.GetLine(modelID, wallIDs[i]); // [] indexing fails!
}Why it fails: web-ifc returns Vector<number>, a WASM type. It does NOT have .length or [] indexing. ALWAYS use .size() and .get(i).
Correct:
const wallIDs = ifcApi.GetLineIDsWithType(modelID, IFCWALL);
for (let i = 0; i < wallIDs.size(); i++) {
const wall = ifcApi.GetLine(modelID, wallIDs.get(i));
}---
AP-6: Not Handling Missing Property Sets
Wrong:
const psets = await ifcApi.properties.getPropertySets(modelID, expressID);
for (const pset of psets) { // Crashes if psets is null/undefined
console.log(pset.Name.value); // Crashes if Name is missing
}Why it fails: Not all elements have property sets. Not all property sets have all expected fields. IFC models from different authoring tools vary significantly in completeness.
Correct:
const psets = await ifcApi.properties.getPropertySets(modelID, expressID);
if (psets && psets.length > 0) {
for (const pset of psets) {
const name = pset.Name?.value ?? "Unnamed";
console.log(name);
}
} else {
console.log(`Element #${expressID}: no property sets`);
}---
AP-7: Re-running Classification for Every Query
Wrong:
// In a loop or repeated function
for (const type of typesToAnalyze) {
await classifier.byCategory(); // Redundant! Rebuilds entire index
const items = await classifier.find({ Categories: [type] });
// Process items...
}Why it fails: byCategory() reads the entire model and rebuilds the classification index. Calling it repeatedly wastes time. The classification persists in classifier.list until the model is disposed.
Correct:
// Classify ONCE
await classifier.byCategory();
// Query multiple times from cached classification
for (const type of typesToAnalyze) {
const items = await classifier.find({ Categories: [type] });
// Process items...
}---
AP-8: Creating Multiple IfcAPI Instances for Analysis
Wrong:
import * as WebIFC from "web-ifc";
const myApi = new WebIFC.IfcAPI(); // Second instance!
await myApi.Init();
const myModelID = myApi.OpenModel(data);
// Now two WASM modules loaded — doubled memory usageWhy it fails: Each IfcAPI instance loads its own WASM module. The IfcLoader component already has a webIfc property with the initialized instance. Creating a second one wastes memory.
Correct:
const loader = components.get(OBC.IfcLoader);
const ifcApi = loader.webIfc; // Reuse the existing instance---
AP-9: Incomplete Spatial Hierarchy Validation
Wrong:
// Only checking if IfcBuildingStorey exists
const storeyIDs = ifcApi.GetLineIDsWithType(modelID, IFCBUILDINGSTOREY);
if (storeyIDs.size() > 0) {
console.log("Model has valid spatial structure"); // Not necessarily true
}Why it fails: A valid IFC spatial hierarchy requires IfcProject > IfcSite > IfcBuilding > IfcBuildingStorey. Having storeys without a proper parent chain indicates a malformed model.
Correct:
import {
IFCPROJECT, IFCSITE, IFCBUILDING, IFCBUILDINGSTOREY
} from "web-ifc";
const requiredLevels = [
{ type: IFCPROJECT, name: "IfcProject" },
{ type: IFCSITE, name: "IfcSite" },
{ type: IFCBUILDING, name: "IfcBuilding" },
{ type: IFCBUILDINGSTOREY, name: "IfcBuildingStorey" },
];
let hierarchyValid = true;
for (const { type, name } of requiredLevels) {
const ids = ifcApi.GetLineIDsWithType(modelID, type);
if (ids.size() === 0) {
console.warn(`Missing: ${name}`);
hierarchyValid = false;
}
}
// Additionally verify the tree structure
const tree = await ifcApi.properties.getSpatialStructure(modelID);
if (!tree.children || tree.children.length === 0) {
console.warn("Spatial tree has no children — possibly corrupt");
hierarchyValid = false;
}---
AP-10: Assuming Consistent Property Set Names Across Models
Wrong:
// Hardcoding property set names from one specific model
const psets = await ifcApi.properties.getPropertySets(modelID, wallID);
const wallCommon = psets.find(p => p.Name.value === "Pset_WallCommon");
const fireRating = wallCommon.HasProperties.find(
p => p.Name.value === "FireRating"
);Why it fails: Property set names and contents vary between authoring tools (Revit, ArchiCAD, Tekla, etc.) and project standards. Some tools use Pset_WallCommon, others use custom names. Properties within sets also vary.
Correct:
const psets = await ifcApi.properties.getPropertySets(modelID, wallID);
if (!psets) return;
// Search flexibly
for (const pset of psets) {
const name = pset.Name?.value ?? "";
if (/wall/i.test(name) || /common/i.test(name)) {
// Found a wall-related property set
const props = pset.HasProperties ?? [];
for (const prop of props) {
const propName = prop.Name?.value ?? "";
const propValue = prop.NominalValue?.value ?? "N/A";
console.log(` ${propName}: ${propValue}`);
}
}
}Model Analyzer — Complete Examples
Example 1: Full Model Summary
Produces a complete overview of a loaded model.
import * as OBC from "@thatopen/components";
import {
IFCWALL, IFCSLAB, IFCDOOR, IFCWINDOW, IFCBEAM, IFCCOLUMN,
IFCROOF, IFCSTAIR, IFCFURNISHINGELEMENT, IFCSPACE,
IFCPROJECT, IFCSITE, IFCBUILDING, IFCBUILDINGSTOREY,
} from "web-ifc";
const fragments = components.get(OBC.FragmentsManager);
const classifier = components.get(OBC.Classifier);
const loader = components.get(OBC.IfcLoader);
const ifcApi = loader.webIfc;
// Assume model is already loaded and we have the web-ifc modelID
// Step 1: Schema and type overview
const schema = ifcApi.GetModelSchema(modelID);
const allTypes = ifcApi.GetAllTypesOfModel(modelID);
const totalEntities = ifcApi.GetAllLines(modelID).size();
console.log(`Schema: ${schema}`);
console.log(`Entity types present: ${allTypes.length}`);
console.log(`Total entities: ${totalEntities}`);
// Step 2: Element counts by type
const buildingElements = [
{ type: IFCWALL, name: "IFCWALL" },
{ type: IFCSLAB, name: "IFCSLAB" },
{ type: IFCDOOR, name: "IFCDOOR" },
{ type: IFCWINDOW, name: "IFCWINDOW" },
{ type: IFCBEAM, name: "IFCBEAM" },
{ type: IFCCOLUMN, name: "IFCCOLUMN" },
{ type: IFCROOF, name: "IFCROOF" },
{ type: IFCSTAIR, name: "IFCSTAIR" },
{ type: IFCFURNISHINGELEMENT, name: "IFCFURNISHINGELEMENT" },
{ type: IFCSPACE, name: "IFCSPACE" },
];
const inventory: Record<string, number> = {};
let totalElements = 0;
for (const { type, name } of buildingElements) {
const ids = ifcApi.GetLineIDsWithType(modelID, type);
const count = ids.size();
if (count > 0) {
inventory[name] = count;
totalElements += count;
}
}
// Step 3: Spatial structure
const spatialTree = await ifcApi.properties.getSpatialStructure(modelID);
function formatTree(node: any, indent = 0): string {
const prefix = " ".repeat(indent);
let result = `${prefix}${node.type} [#${node.expressID}]\n`;
if (node.children) {
for (const child of node.children) {
result += formatTree(child, indent + 1);
}
}
return result;
}
console.log("Spatial Structure:");
console.log(formatTree(spatialTree));
// Step 4: Storey breakdown
await classifier.byCategory();
await classifier.byIfcBuildingStorey();
const storeys = classifier.list.get("Storeys");
if (storeys) {
console.log("\nElements per storey:");
for (const [storeyName, groupData] of storeys) {
const items = await groupData.get();
let count = 0;
for (const ids of Object.values(items)) {
count += (ids as Set<number>).size;
}
console.log(` ${storeyName}: ${count} elements`);
}
}---
Example 2: Property Report for Walls
Generates a property set report for all wall elements.
import * as OBC from "@thatopen/components";
import { IFCWALL } from "web-ifc";
const fragments = components.get(OBC.FragmentsManager);
const classifier = components.get(OBC.Classifier);
const loader = components.get(OBC.IfcLoader);
const ifcApi = loader.webIfc;
// Classify first
await classifier.byCategory();
// Get walls via classifier
const wallItems = await classifier.find({ Categories: ["IFCWALL"] });
// Extract properties via fragments (high-level API)
const wallData = await fragments.getData(wallItems);
for (const [modelId, itemDataArray] of Object.entries(wallData)) {
console.log(`Model: ${modelId}`);
for (const itemData of itemDataArray) {
console.log(" Element data:", JSON.stringify(itemData, null, 2));
}
}
// Alternatively: detailed property sets via web-ifc (low-level API)
const wallIDs = ifcApi.GetLineIDsWithType(modelID, IFCWALL);
const psetReport: Record<string, number> = {};
const BATCH_SIZE = 50;
for (let batch = 0; batch < wallIDs.size(); batch += BATCH_SIZE) {
const end = Math.min(batch + BATCH_SIZE, wallIDs.size());
for (let i = batch; i < end; i++) {
const expressID = wallIDs.get(i);
const psets = await ifcApi.properties.getPropertySets(
modelID, expressID, false
);
if (psets) {
for (const pset of psets) {
const name = pset.Name?.value || "Unnamed";
psetReport[name] = (psetReport[name] || 0) + 1;
}
}
}
}
console.log("\nProperty Set Occurrence Report:");
for (const [name, count] of Object.entries(psetReport).sort(
(a, b) => b[1] - a[1]
)) {
console.log(` ${name}: ${count} occurrences`);
}---
Example 3: Element Inventory by Storey
Produces a matrix of element types vs building storeys.
import * as OBC from "@thatopen/components";
const classifier = components.get(OBC.Classifier);
// Classify
await classifier.byCategory();
await classifier.byIfcBuildingStorey();
const categories = classifier.list.get("Categories");
const storeys = classifier.list.get("Storeys");
if (!categories || !storeys) {
console.error("Classification data not available");
throw new Error("Run classifier before inventory");
}
// Build the matrix
const matrix: Record<string, Record<string, number>> = {};
const categoryNames = Array.from(categories.keys());
const storeyNames = Array.from(storeys.keys());
for (const storeyName of storeyNames) {
matrix[storeyName] = {};
for (const categoryName of categoryNames) {
// Cross-query: elements of this type on this storey
const items = await classifier.find({
Categories: [categoryName],
Storeys: [storeyName],
});
let count = 0;
for (const ids of Object.values(items)) {
count += (ids as Set<number>).size;
}
if (count > 0) {
matrix[storeyName][categoryName] = count;
}
}
}
// Output as table
console.log("\nElement Inventory by Storey:");
console.log("Storey | " + categoryNames.join(" | "));
console.log("-".repeat(70));
for (const storeyName of storeyNames) {
const row = categoryNames.map(
(cat) => String(matrix[storeyName][cat] || 0).padStart(5)
);
console.log(`${storeyName.padEnd(20)} | ${row.join(" | ")}`);
}---
Example 4: Model Validation Check
Runs quality checks and outputs a validation summary.
import * as OBC from "@thatopen/components";
import {
IFCWALL, IFCSLAB, IFCDOOR, IFCWINDOW,
IFCPROJECT, IFCSITE, IFCBUILDING, IFCBUILDINGSTOREY,
} from "web-ifc";
const classifier = components.get(OBC.Classifier);
const loader = components.get(OBC.IfcLoader);
const ifcApi = loader.webIfc;
await classifier.byCategory();
await classifier.byIfcBuildingStorey();
const results: Array<{ check: string; status: string; detail: string }> = [];
// Check 1: Required spatial elements
const spatialChecks = [
{ type: IFCPROJECT, name: "IfcProject" },
{ type: IFCSITE, name: "IfcSite" },
{ type: IFCBUILDING, name: "IfcBuilding" },
{ type: IFCBUILDINGSTOREY, name: "IfcBuildingStorey" },
];
for (const { type, name } of spatialChecks) {
const ids = ifcApi.GetLineIDsWithType(modelID, type);
const count = ids.size();
results.push({
check: `${name} present`,
status: count > 0 ? "PASS" : "WARN",
detail: `Found ${count} instance(s)`,
});
}
// Check 2: Elements with property sets (sample first 20 walls)
const wallIDs = ifcApi.GetLineIDsWithType(modelID, IFCWALL);
let wallsWithPsets = 0;
const sampleSize = Math.min(wallIDs.size(), 20);
for (let i = 0; i < sampleSize; i++) {
const psets = await ifcApi.properties.getPropertySets(
modelID, wallIDs.get(i), false
);
if (psets && psets.length > 0) wallsWithPsets++;
}
const psetPercent = sampleSize > 0
? Math.round((wallsWithPsets / sampleSize) * 100)
: 0;
results.push({
check: "Walls with property sets",
status: psetPercent > 80 ? "PASS" : "WARN",
detail: `${wallsWithPsets}/${sampleSize} sampled (${psetPercent}%)`,
});
// Check 3: Storey assignment
const storeys = classifier.list.get("Storeys");
const storeyCount = storeys?.size ?? 0;
results.push({
check: "Storey classification",
status: storeyCount > 0 ? "PASS" : "WARN",
detail: `${storeyCount} storey(s) detected`,
});
// Output validation report
console.log("\n=== VALIDATION RESULTS ===");
for (const r of results) {
console.log(`[${r.status}] ${r.check}: ${r.detail}`);
}---
Example 5: Export Analysis Data as JSON
Structures analysis results for downstream use.
import * as OBC from "@thatopen/components";
const fragments = components.get(OBC.FragmentsManager);
const classifier = components.get(OBC.Classifier);
const loader = components.get(OBC.IfcLoader);
const ifcApi = loader.webIfc;
await classifier.byCategory();
await classifier.byIfcBuildingStorey();
// Build exportable report
const report = {
schema: ifcApi.GetModelSchema(modelID),
totalTypes: ifcApi.GetAllTypesOfModel(modelID).length,
totalEntities: ifcApi.GetAllLines(modelID).size(),
categories: {} as Record<string, number>,
storeys: {} as Record<string, number>,
generatedAt: new Date().toISOString(),
};
// Populate categories
const categories = classifier.list.get("Categories");
if (categories) {
for (const [name, groupData] of categories) {
const items = await groupData.get();
let count = 0;
for (const ids of Object.values(items)) {
count += (ids as Set<number>).size;
}
report.categories[name] = count;
}
}
// Populate storeys
const storeys = classifier.list.get("Storeys");
if (storeys) {
for (const [name, groupData] of storeys) {
const items = await groupData.get();
let count = 0;
for (const ids of Object.values(items)) {
count += (ids as Set<number>).size;
}
report.storeys[name] = count;
}
}
// Output as JSON
const jsonReport = JSON.stringify(report, null, 2);
console.log(jsonReport);Model Analyzer — Analysis APIs Reference
Classification APIs
Classifier.byCategory(config?)
Groups loaded fragment items by IFC entity type.
const classifier = components.get(OBC.Classifier);
await classifier.byCategory();
// Creates "Categories" classification with groups: "IFCWALL", "IFCSLAB", etc.Config (AddClassificationConfig):
classificationName?: string— Override default name "Categories"modelIds?: RegExp[]— Filter to specific models
Classifier.byIfcBuildingStorey(config?)
Groups items by building storey via the ContainsElements relationship.
await classifier.byIfcBuildingStorey();
// Creates "Storeys" classification with groups per storey nameClassifier.byModel(config?)
Groups items by their parent FragmentsModel.
await classifier.byModel();
// Creates "Models" classification with one group per loaded modelClassifier.find(query)
Returns the intersection of specified classification groups as a ModelIdMap.
const items = await classifier.find({
Categories: ["IFCWALL", "IFCSLAB"],
Storeys: ["Ground Floor"]
});
// Returns: ModelIdMap — walls AND slabs on the ground floorType: ClassifierIntersectionInput — keys are classification names, values are arrays of group names.
Classifier.list
classifier.list: DataMap<string, DataMap<string, ClassificationGroupData>>Access pattern:
const categories = classifier.list.get("Categories");
if (categories) {
for (const [groupName, groupData] of categories) {
const items: ModelIdMap = await groupData.get();
// groupName: "IFCWALL", "IFCSLAB", etc.
// items: Map<string, Set<number>>
}
}---
Data Extraction APIs
FragmentsManager.getData(items, config?)
Extracts IFC property data for targeted elements via the web worker.
const fragments = components.get(OBC.FragmentsManager);
const data = await fragments.getData(items);
// Returns: Record<string, ItemData[]>
// Keys: model UUID strings
// Values: arrays of ItemData objectsALWAYS call fragments.init(workerURL) before using getData.
web-ifc Properties Helper
High-level async methods on ifcApi.properties:
| Method | Signature | Returns |
|---|---|---|
getItemProperties | (modelID, expressID, recursive?, inverse?) | All properties for one element |
getPropertySets | (modelID, expressID, recursive?) | Property sets (Pset_) for one element |
getTypeProperties | (modelID, expressID, recursive?) | Type object properties |
getMaterialsProperties | (modelID, expressID, recursive?) | Material definitions |
getSpatialStructure | (modelID, includeProperties?) | Full spatial hierarchy tree |
Spatial structure return format:
{
expressID: number,
type: string, // e.g., "IFCPROJECT"
children: [
{
expressID: number,
type: string, // e.g., "IFCSITE"
children: [...]
}
]
}---
web-ifc Query APIs
GetAllTypesOfModel(modelID)
Returns all IFC entity types present in the model.
const types: Array<{ typeID: number, typeName: string }> =
ifcApi.GetAllTypesOfModel(modelID);Use for: generating a complete type inventory without querying each type.
GetLineIDsWithType(modelID, type, includeInherited?)
Returns expressIDs of all entities of a given IFC type.
import { IFCWALL } from "web-ifc";
const wallIDs = ifcApi.GetLineIDsWithType(modelID, IFCWALL);
// Returns: Vector<number> — use .size() and .get(i)Set includeInherited: true to include subtypes (e.g., IFCWALL includes IFCWALLSTANDARDCASE).
GetLine(modelID, expressID, flatten?, inverse?)
Returns the full entity data for a single expressID.
const wall = ifcApi.GetLine(modelID, expressID);
console.log(wall.Name?.value, wall.GlobalId?.value);NEVER use flatten: true on large scopes — it recursively resolves all references.
GetRawLineData(modelID, expressID)
Returns unparsed entity data. Faster than GetLine when you only need type and raw arguments.
const raw = ifcApi.GetRawLineData(modelID, expressID);
// raw.ID, raw.type, raw.argumentsGetModelSchema(modelID)
Returns the IFC schema version string.
const schema = ifcApi.GetModelSchema(modelID);
// "IFC2X3" | "IFC4" | "IFC4X3"GetAllLines(modelID)
Returns all expressIDs in the model.
const allIDs = ifcApi.GetAllLines(modelID);
console.log(`Total entities: ${allIDs.size()}`);GetHeaderLine(modelID, headerType)
Returns IFC file header information (file description, file name, etc.).
---
ItemsFinder API
Create Named Queries
const finder = components.get(OBC.ItemsFinder);
const query = finder.create("Structural Walls", [
{
categories: [/WALL/],
attributes: {
queries: [{ name: /IsStructural/, value: /true/i }]
}
}
]);Execute Queries
const items = await finder.getItems(
[{ categories: [/WALL/, /SLAB/] }],
{ aggregation: "union" }
);
// Returns: ModelIdMapAuto-Generate Category Queries
const categoryNames = await finder.addFromCategories();
// Returns: string[] of all geometric categories found---
GUID Utilities
For mapping between IFC GlobalId strings and expressIDs:
// Build index first for performance
ifcApi.CreateIfcGuidToExpressIdMapping(modelID);
// Then convert
const expressID = ifcApi.GetExpressIdFromGuid(modelID, guid);
const guid = ifcApi.GetGuidFromExpressId(modelID, expressID);ALWAYS call CreateIfcGuidToExpressIdMapping before batch GUID lookups.
---
ModelIdMap Type
The universal interchange format for element sets:
type ModelIdMap = Map<string, Set<number>>;
// Key: model UUID (string)
// Value: Set of local element IDs (expressIDs)Counting elements in a ModelIdMap:
function countItems(items: ModelIdMap): number {
let total = 0;
for (const ids of items.values()) {
total += ids.size;
}
return total;
}