
Thatopen Syntax Properties
- 5 installs
- 17 repo stars
- Updated July 8, 2026
- openaec-foundation/thatopen-claude-skill-package
Helps with ai & agent building tasks.
About
thatopen-syntax-properties is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- thatopen-syntax-properties
- AI & Agent Building
- AI-coding skill
Thatopen Syntax Properties 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 21, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/thatopen-claude-skill-package --skill thatopen-syntax-propertiesAdd 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 Property Queries and Classification
Overview
ThatOpen provides two complementary systems for organizing and extracting data from loaded BIM models:
1. Classifier — Groups fragment items by IFC category, building storey, model, or custom criteria. Produces ModelIdMap results for downstream operations (hiding, highlighting, data extraction). 2. FragmentsManager.getData() — Extracts IFC property data (property sets, quantity sets, type information, relationships) for targeted items. 3. ItemsFinder — Searches items across models using query parameters with category, attribute, and relationship filters.
All three operate on the fragment layer, not raw web-ifc. For direct web-ifc property access (GetLine, GetPropertySets), see thatopen-core-web-ifc.
Pipeline:
Loaded FragmentsModel
├─ Classifier.byCategory() → classification groups
├─ Classifier.byIfcBuildingStorey() → storey groups
├─ Classifier.find({...}) → ModelIdMap (intersection)
├─ ItemsFinder.getItems(queries) → ModelIdMap (filtered)
└─ FragmentsManager.getData(items) → IFC property dataCritical Warnings
1. ALWAYS classify models AFTER loading. Classification methods read data from loaded FragmentsModels. Calling byCategory() before any model is loaded produces empty groups.
2. ALWAYS call `FragmentsManager.init(workerURL)` before getData(). Property extraction runs in the web worker. Without initialization, getData() fails silently or throws.
3. NEVER assume classification groups persist across model loads. When a model is disposed and reloaded, ALWAYS re-run classification methods to rebuild groups.
4. ALWAYS use `find()` to get a ModelIdMap from classifications. NEVER manually iterate classifier.list to build item maps — find() handles intersection logic correctly across multiple classifications.
5. NEVER query properties for thousands of items at once without pagination. getData() runs in the worker but still serializes results back to the main thread. Large result sets cause jank.
Classifier
Purpose
The Classifier organizes fragment items into named classification groups. Each classification (e.g., "Categories", "Storeys", "Models") contains named groups (e.g., "IFCWALL", "Ground Floor", "Arch Model") that map to sets of element IDs via ModelIdMap.
Data Structure
classifier.list: DataMap<string, DataMap<string, ClassificationGroupData>>
// │ │ │
// classification group name group data (items + query)
// nameClassificationGroupData contains:
- A
get()method returningPromise<ModelIdMap>— the items in the group - Optional query configuration for dynamic groups
- Item storage maps keyed by model ID
Getting the Classifier
import * as OBC from "@thatopen/components";
const classifier = components.get(OBC.Classifier);Built-in Classification Methods
byCategory(config?): Promise<void>
Groups all items by their IFC entity type (e.g., IFCWALL, IFCSLAB, IFCDOOR). Default classification name: "Categories".
await classifier.byCategory();
// classifier.list now has "Categories" with groups like "IFCWALL", "IFCSLAB"byIfcBuildingStorey(config?): Promise<void>
Groups items by the building storey they belong to, using the ContainsElements IFC relationship. Default classification name: "Storeys".
await classifier.byIfcBuildingStorey();
// classifier.list now has "Storeys" with groups like "Ground Floor", "Level 1"byModel(config?): Promise<void>
Groups items by their parent FragmentsModel. Default classification name: "Models".
await classifier.byModel();
// classifier.list now has "Models" with groups per loaded modelAddClassificationConfig
All three methods accept an optional config:
interface AddClassificationConfig {
classificationName?: string; // Override default name
modelIds?: RegExp[]; // Filter: only classify matching models
}Example with custom name and model filter:
await classifier.byCategory({
classificationName: "Element Types",
modelIds: [/arch/i] // Only classify models whose ID matches "arch"
});Querying Classifications with find()
find() accepts an object where keys are classification names and values are arrays of group names. It returns the intersection of all specified groups as a ModelIdMap.
// Type: ClassifierIntersectionInput
// Keys = classification names, Values = arrays of group names
const items = await classifier.find({
Categories: ["IFCWALL"],
Storeys: ["Ground Floor"]
});
// Returns: ModelIdMap of walls on the ground floor onlyIntersection logic: When multiple classifications are specified, find() returns only items that appear in ALL specified groups. This enables powerful cross-classification queries.
Custom Groups
getGroupData(classification, group): ClassificationGroupData
Retrieves or creates a group. Use this to build custom classifications.
const groupData = classifier.getGroupData("Fire Rating", "REI 120");addGroupItems(classification, group, items): void
Adds items to a specific group within a classification.
const wallItems: ModelIdMap = {
[model.modelId]: new Set([42, 43, 44])
};
classifier.addGroupItems("Custom", "Selected Walls", wallItems);removeItems(modelIdMap, config?): void
Removes items from the classifier. Without config, removes from ALL classifications.
// Remove specific items from all classifications
classifier.removeItems(itemsToRemove);
// Remove with config (RemoveClassifierItemsConfig)
classifier.removeItems(itemsToRemove, { /* config */ });setGroupQuery(classification, group, query): void
Assigns a dynamic query to a group. When get() is called on the group, the query runs via ItemsFinder to produce fresh results.
classifier.setGroupQuery("Custom", "First Floor Walls", {
name: "First Floor Walls" // References a named ItemsFinder query
});Aggregation Methods
aggregateItems(classification, query, config?): Promise<void>
Groups items per classification using a query. Each item matching the query is placed into a group based on the aggregation callback.
aggregateItemRelations(classification, query, relation, config?): Promise<void>
Groups items by IFC relationships. Used internally by byIfcBuildingStorey() to group elements by their containing storey via the ContainsElements relation.
await classifier.aggregateItemRelations(
"Storeys",
{ categories: [/BUILDINGSTOREY/] },
"ContainsElements"
);FragmentsManager.getData()
Purpose
Extracts IFC property data for targeted items. Runs in the web worker for performance. Returns structured property information including property sets, quantity sets, type data, and relationship data.
Signature
getData(
items: ModelIdMap,
config?: Partial<ItemsDataConfig>
): Promise<Record<string, ItemData[]>>Parameters
- items —
ModelIdMapspecifying which elements to query - config — Optional
Partial<ItemsDataConfig>controlling what data
to retrieve
Return Value
Returns Record<string, ItemData[]>:
- Keys are model UUID strings
- Values are arrays of
ItemDataobjects, one per queried element
ItemData
Each ItemData object represents the IFC data for one element. It contains the element's properties organized by their IFC structure: property sets, type information, attributes, and relationships.
Basic Usage
const fragments = components.get(OBC.FragmentsManager);
// Target specific elements
const items: ModelIdMap = {
[model.modelId]: new Set([42, 43])
};
// Extract all available property data
const data = await fragments.getData(items);
for (const [modelId, itemDataArray] of Object.entries(data)) {
for (const itemData of itemDataArray) {
console.log("Element data:", itemData);
}
}Combined with Classifier
The most common pattern: classify first, then extract properties for the classified items.
const classifier = components.get(OBC.Classifier);
const fragments = components.get(OBC.FragmentsManager);
// Classify
await classifier.byCategory();
await classifier.byIfcBuildingStorey();
// Find walls on the ground floor
const wallItems = await classifier.find({
Categories: ["IFCWALL"],
Storeys: ["Ground Floor"]
});
// Extract properties for those walls
const wallData = await fragments.getData(wallItems);ItemsFinder
Purpose
ItemsFinder searches and filters items across loaded models using structured query parameters. It supports filtering by IFC category, element attributes, and IFC relationships. Queries can be saved, named, and reused.
Getting ItemsFinder
const finder = components.get(OBC.ItemsFinder);Creating Named Queries
const query = finder.create("First Floor Walls", [
{
categories: [/WALL/],
relation: {
name: "ContainedInStructure",
query: {
categories: [/STOREY/],
attributes: {
queries: [{ name: /Name/, value: /01/ }]
}
}
}
}
]);Query Parameters (ItemsQueryParams)
interface ItemsQueryParams {
categories?: RegExp[]; // Filter by IFC category (regex match)
relation?: { // Filter by IFC relationship
name: string; // Relationship name (e.g., "ContainedInStructure")
query: ItemsQueryParams; // Nested query for related items
};
attributes?: { // Filter by element attributes
queries: Array<{
name: RegExp; // Attribute name pattern
value: RegExp; // Attribute value pattern
}>;
};
}Executing Queries
// Execute with getItems()
const items = await finder.getItems(
[{ categories: [/WALL/, /SLAB/] }],
{
modelIds: [/arch/i], // Optional: filter models by ID pattern
aggregation: "union" // "union" or "intersection"
}
);
// Returns: ModelIdMapAuto-Generate Category Queries
// Create queries for all geometric categories in loaded models
const categoryNames = await finder.addFromCategories();
// Returns: string[] of category names found (e.g., ["IFCWALL", "IFCSLAB"])Serialization
// Export queries for storage
const serialized = finder.export();
// Import previously saved queries
await finder.import(serialized);IFC Relationships in Fragment Context
The fragment system preserves key IFC relationships that can be queried through Classifier and ItemsFinder:
| Relationship | IFC Entity | Fragment Usage |
|---|---|---|
| Spatial containment | IfcRelContainedInSpatialStructure | byIfcBuildingStorey(), ContainedInStructure relation queries |
| Aggregation | IfcRelAggregates | Spatial hierarchy traversal |
| Property assignment | IfcRelDefinesByProperties | getData() returns property sets |
| Type assignment | IfcRelDefinesByType | getData() returns type information |
| Material assignment | IfcRelAssociatesMaterial | Available through getData() |
Spatial Structure via Classification
The spatial hierarchy (Project > Site > Building > Storey > Space) is accessible through the Classifier's storey classification:
await classifier.byIfcBuildingStorey();
// Access storey groups
const storeyGroups = classifier.list.get("Storeys");
if (storeyGroups) {
for (const [storeyName, groupData] of storeyGroups) {
const items = await groupData.get();
console.log(`${storeyName}: ${Object.keys(items).length} models`);
}
}Property Sets and Quantity Sets
IFC models contain two types of element metadata accessible via getData():
1. Property Sets (Pset_) — Named groups of properties assigned via IfcRelDefinesByProperties. Common examples: Pset_WallCommon, Pset_DoorCommon. Contains IfcPropertySingleValue entries with name-value pairs.
2. Quantity Sets (Qto_) — Physical measurements assigned via the same relationship. Common examples: Qto_WallBaseQuantities, Qto_SpaceBaseQuantities. Contains typed quantities (length, area, volume, weight, count).
For detailed IFC property set and quantity set schemas, see the ifc-bim-standards skill.
Workflow: Classify, Query, Extract
The standard workflow for working with IFC properties in ThatOpen:
1. Load model → FragmentsModel in scene
2. Classify → Classifier.byCategory(), byIfcBuildingStorey()
3. Query → Classifier.find() or ItemsFinder.getItems()
4. Extract properties → FragmentsManager.getData(items)
5. Use the data → Display in UI, export, validateALWAYS follow this order. Classifying before loading produces empty groups. Extracting properties without targeting specific items wastes worker bandwidth.
Quick Reference
| Task | Method |
|---|---|
| Classify by IFC type | classifier.byCategory() |
| Classify by storey | classifier.byIfcBuildingStorey() |
| Classify by model | classifier.byModel() |
| Find items across classifications | classifier.find({ Classification: ["Group"] }) |
| Add items to custom group | classifier.addGroupItems(class, group, items) |
| Set dynamic query on group | classifier.setGroupQuery(class, group, query) |
| Remove items from classifier | classifier.removeItems(items) |
| Create named search query | finder.create(name, queries) |
| Search items by query | finder.getItems(queries, config?) |
| Auto-create category queries | finder.addFromCategories() |
| Extract IFC properties | fragments.getData(items) |
Related Skills
thatopen-core-fragments— FragmentsManager, ModelIdMap, worker setupthatopen-core-web-ifc— Raw web-ifc property access (GetLine, GetPropertySets)thatopen-core-architecture— Component system, world setupthatopen-syntax-ifc-loading— IfcLoader configuration, WASM setupthatopen-impl-viewer— Full viewer setup including classification UI
References
- references/methods.md — Classifier API, getData config, ItemsFinder
- references/examples.md — Classify, find, getData, spatial queries
- references/anti-patterns.md — Wrong query patterns, missing classification
Property Queries and Classification Anti-Patterns
1. Classifying Before Loading
WRONG — calling classification methods with no loaded models:
const classifier = components.get(OBC.Classifier);
// No models loaded yet!
await classifier.byCategory();
await classifier.byIfcBuildingStorey();
// Result: empty classifications, no groups created
const items = await classifier.find({ Categories: ["IFCWALL"] });
// items is empty — no walls found because no models were loadedCORRECT — classify after model loading completes:
const classifier = components.get(OBC.Classifier);
const ifcLoader = components.get(OBC.IfcLoader);
// Load model first
await ifcLoader.setup();
const model = await ifcLoader.load(data, true, "Building");
// NOW classify
await classifier.byCategory();
await classifier.byIfcBuildingStorey();Rule: ALWAYS load at least one model before calling classification methods. Classification reads data from loaded FragmentsModels.
---
2. Not Re-Classifying After New Model Loads
WRONG — assuming old classifications include new models:
// Load model A and classify
const modelA = await ifcLoader.load(dataA, true, "Arch");
await classifier.byCategory();
// Load model B later
const modelB = await ifcLoader.load(dataB, true, "Struct");
// BUG: classifier still only has categories from model A
const allWalls = await classifier.find({ Categories: ["IFCWALL"] });
// Missing walls from model B!CORRECT — re-classify after each new model load:
const modelA = await ifcLoader.load(dataA, true, "Arch");
await classifier.byCategory();
const modelB = await ifcLoader.load(dataB, true, "Struct");
await classifier.byCategory(); // Re-run to include model BRule: ALWAYS re-run classification methods after loading additional models. Classifications are NOT automatically updated when new models are added.
---
3. Manually Iterating classifier.list Instead of Using find()
WRONG — building ModelIdMap by hand from classification groups:
const classifier = components.get(OBC.Classifier);
// Manually digging into the data structure
const categories = classifier.list.get("Categories");
const storeys = classifier.list.get("Storeys");
const wallGroup = categories?.get("IFCWALL");
const storeyGroup = storeys?.get("Ground Floor");
// Manually intersecting... error-prone and verbose
const wallItems = wallGroup ? await wallGroup.get() : {};
const storeyItems = storeyGroup ? await storeyGroup.get() : {};
// Now what? Manual intersection logic? Bug city.CORRECT — use find() for intersection queries:
const items = await classifier.find({
Categories: ["IFCWALL"],
Storeys: ["Ground Floor"]
});
// find() handles intersection correctlyRule: ALWAYS use classifier.find() for cross-classification queries. NEVER manually iterate classifier.list to build intersections.
---
4. Querying getData Without Worker Initialization
WRONG — calling getData before FragmentsManager.init():
const fragments = components.get(OBC.FragmentsManager);
// MISSING: fragments.init(workerURL)
const items = { [model.modelId]: new Set([42]) };
const data = await fragments.getData(items);
// Result: crash, silent failure, or empty resultCORRECT:
const fragments = components.get(OBC.FragmentsManager);
fragments.init("https://unpkg.com/@thatopen/fragments@3.3.6/dist/Worker/worker.mjs");
// Worker initialized — safe to query
const data = await fragments.getData(items);Rule: ALWAYS initialize the FragmentsManager worker before calling getData(). The property extraction runs entirely in the worker thread.
---
5. Querying Too Many Items at Once
WRONG — extracting properties for entire model without filtering:
const fragments = components.get(OBC.FragmentsManager);
// Build a ModelIdMap with ALL elements in ALL models
const allItems: OBC.ModelIdMap = {};
for (const [modelId, model] of fragments.list) {
// Collecting thousands of IDs...
allItems[modelId] = new Set(/* all local IDs */);
}
// Querying properties for 50,000 elements at once
const data = await fragments.getData(allItems);
// Result: massive serialization overhead, UI freezes, memory spikeCORRECT — classify first, then query targeted subsets:
const classifier = components.get(OBC.Classifier);
await classifier.byCategory();
// Query only the elements you need
const walls = await classifier.find({ Categories: ["IFCWALL"] });
const wallData = await fragments.getData(walls);Rule: ALWAYS use Classifier or ItemsFinder to narrow down items before calling getData(). NEVER extract properties for all elements in a large model at once.
---
6. Using Wrong Classification or Group Names
WRONG — typo in classification or group name:
await classifier.byCategory();
await classifier.byIfcBuildingStorey();
// Typo: "Category" instead of "Categories"
const items = await classifier.find({
Category: ["IFCWALL"] // WRONG: should be "Categories"
});
// Result: empty ModelIdMap — no classification named "Category" exists
// Wrong case in group name
const items2 = await classifier.find({
Categories: ["IfcWall"] // WRONG: should be "IFCWALL" (uppercase)
});CORRECT — use exact default names:
// Default classification names (case-sensitive):
// byCategory() -> "Categories"
// byIfcBuildingStorey() -> "Storeys"
// byModel() -> "Models"
const items = await classifier.find({
Categories: ["IFCWALL"], // Uppercase IFC type names
Storeys: ["Ground Floor"] // Exact storey name from the IFC model
});Rule: ALWAYS use the exact default classification names: "Categories", "Storeys", "Models". Group names for categories are uppercase IFC type strings (e.g., "IFCWALL", not "IfcWall"). Storey group names match the Name attribute in the IFC model exactly.
---
7. Forgetting to Await Async Classification Methods
WRONG — not awaiting classification before querying:
// byCategory is async! Missing await
classifier.byCategory();
classifier.byIfcBuildingStorey();
// Classification not yet complete — find() sees empty groups
const items = await classifier.find({
Categories: ["IFCWALL"]
});
// Result: empty or incomplete ModelIdMapCORRECT:
await classifier.byCategory();
await classifier.byIfcBuildingStorey();
const items = await classifier.find({
Categories: ["IFCWALL"]
});Rule: ALWAYS await byCategory(), byIfcBuildingStorey(), byModel(), and find(). All classification operations are async.
---
8. Using ItemsFinder Regex Without Understanding Matching
WRONG — overly broad regex matches wrong categories:
const finder = components.get(OBC.ItemsFinder);
// /WALL/ also matches IFCWALLSTANDARDCASE, IFCCURTAINWALL, etc.
const items = await finder.getItems([
{ categories: [/WALL/] }
]);
// May include curtain walls when you only wanted standard wallsCORRECT — use precise regex when specificity matters:
// Match only IFCWALL (not subtypes)
const items = await finder.getItems([
{ categories: [/^IFCWALL$/] }
]);
// Or include standard case explicitly
const items2 = await finder.getItems([
{ categories: [/^IFCWALL$/, /^IFCWALLSTANDARDCASE$/] }
]);Rule: ALWAYS consider regex matching scope. Use anchors (^, $) when you need exact type matching. Unanchored patterns like /WALL/ match any type containing "WALL".
---
9. Not Disposing Classifier on Model Removal
WRONG — disposing a model without cleaning classifier data:
const classifier = components.get(OBC.Classifier);
await classifier.byCategory();
// Dispose a model
fragments.disposeModel(model.modelId);
// BUG: classifier still references items from the disposed model
const items = await classifier.find({ Categories: ["IFCWALL"] });
// items may contain IDs from the disposed model — stale referencesCORRECT — remove items or re-classify after model disposal:
// Option A: Remove model's items from classifier
const modelItems: OBC.ModelIdMap = {
[model.modelId]: new Set(/* all IDs */)
};
classifier.removeItems(modelItems);
fragments.disposeModel(model.modelId);
// Option B: Re-classify from scratch after disposal
fragments.disposeModel(model.modelId);
await classifier.byCategory();
await classifier.byIfcBuildingStorey();Rule: ALWAYS clean up classifier data when disposing models. Either call removeItems() before disposal or re-run classification methods after disposal.
---
Summary Table
| Anti-Pattern | Consequence | Rule |
|---|---|---|
| Classify before loading | Empty classifications | ALWAYS load models first |
| Skip re-classification | Missing new model data | ALWAYS re-classify after new loads |
| Manual list iteration | Incorrect intersections | ALWAYS use find() for queries |
| getData without worker init | Silent failure or crash | ALWAYS init worker first |
| Query all items at once | Memory spike, UI freeze | ALWAYS filter items first |
| Wrong classification names | Empty results | ALWAYS use exact default names |
| Missing await on async methods | Incomplete results | ALWAYS await classification calls |
| Overly broad regex | Wrong type matches | ALWAYS use anchors for exact matching |
| Stale classifier after disposal | Stale references | ALWAYS clean up after model disposal |
Property Queries and Classification Examples
1. Classify by Category and Storey
ALWAYS classify after models are loaded.
import * as OBC from "@thatopen/components";
const components = new OBC.Components();
const fragments = components.get(OBC.FragmentsManager);
const classifier = components.get(OBC.Classifier);
// Assume model is already loaded (see thatopen-syntax-ifc-loading)
// ...
// Classify all loaded models by IFC entity type
await classifier.byCategory();
// Classify by building storey
await classifier.byIfcBuildingStorey();
// Classify by source model
await classifier.byModel();
// Now classifier.list has three classifications:
// "Categories" -> {"IFCWALL": ..., "IFCSLAB": ..., "IFCDOOR": ...}
// "Storeys" -> {"Ground Floor": ..., "Level 1": ..., "Level 2": ...}
// "Models" -> {"model-uuid-1": ..., "model-uuid-2": ...}---
2. Find Items with Classifier.find()
Use find() to get a ModelIdMap from classification intersections.
const classifier = components.get(OBC.Classifier);
// Find all walls
const allWalls = await classifier.find({
Categories: ["IFCWALL"]
});
// Find all items on the ground floor
const groundFloorItems = await classifier.find({
Storeys: ["Ground Floor"]
});
// Find walls on the ground floor (intersection)
const groundFloorWalls = await classifier.find({
Categories: ["IFCWALL"],
Storeys: ["Ground Floor"]
});
// Find walls AND slabs on a specific storey
const structuralItems = await classifier.find({
Categories: ["IFCWALL", "IFCSLAB"],
Storeys: ["Level 1"]
});---
3. Extract Properties with getData()
const fragments = components.get(OBC.FragmentsManager);
const classifier = components.get(OBC.Classifier);
// First: classify
await classifier.byCategory();
await classifier.byIfcBuildingStorey();
// Second: find items of interest
const wallItems = await classifier.find({
Categories: ["IFCWALL"],
Storeys: ["Ground Floor"]
});
// Third: extract IFC property data
const wallData = await fragments.getData(wallItems);
// Process results
for (const [modelId, itemDataArray] of Object.entries(wallData)) {
console.log(`Model ${modelId}: ${itemDataArray.length} walls`);
for (const itemData of itemDataArray) {
console.log("Wall properties:", itemData);
}
}---
4. getData for Specific Elements
const fragments = components.get(OBC.FragmentsManager);
// Target specific elements by local ID
const items: OBC.ModelIdMap = {
[model.modelId]: new Set([42, 43, 44])
};
const data = await fragments.getData(items);
for (const [modelId, elements] of Object.entries(data)) {
for (const element of elements) {
console.log(element);
}
}---
5. ItemsFinder — Search by Category and Relationship
const finder = components.get(OBC.ItemsFinder);
// Simple category search: find all walls
const walls = await finder.getItems([
{ categories: [/WALL/] }
]);
// Multi-category search: walls and slabs
const structural = await finder.getItems([
{ categories: [/WALL/, /SLAB/] }
]);
// Filter by model ID pattern
const archWalls = await finder.getItems(
[{ categories: [/WALL/] }],
{ modelIds: [/arch/i] }
);---
6. ItemsFinder — Relationship Queries
Find items by their IFC relationships using nested queries.
const finder = components.get(OBC.ItemsFinder);
// Find walls contained in a specific storey
const firstFloorWalls = await finder.getItems([
{
categories: [/WALL/],
relation: {
name: "ContainedInStructure",
query: {
categories: [/STOREY/],
attributes: {
queries: [{ name: /Name/, value: /First Floor/ }]
}
}
}
}
]);
// Find all elements in spaces named "Office"
const officeElements = await finder.getItems([
{
relation: {
name: "ContainedInStructure",
query: {
categories: [/SPACE/],
attributes: {
queries: [{ name: /Name/, value: /Office/ }]
}
}
}
}
]);---
7. ItemsFinder — Named Queries for Reuse
const finder = components.get(OBC.ItemsFinder);
// Create a reusable named query
finder.create("Ground Floor Walls", [
{
categories: [/WALL/],
relation: {
name: "ContainedInStructure",
query: {
categories: [/STOREY/],
attributes: {
queries: [{ name: /Name/, value: /Ground|00|GF/i }]
}
}
}
}
]);
// Execute by name later (through Classifier integration)
const classifier = components.get(OBC.Classifier);
classifier.setGroupQuery("Custom", "Ground Floor Walls", {
name: "Ground Floor Walls"
});
// Now find() will execute the query dynamically
const items = await classifier.find({ Custom: ["Ground Floor Walls"] });---
8. Auto-Generate Category Queries
const finder = components.get(OBC.ItemsFinder);
// Create queries for all IFC categories present in loaded models
const categories = await finder.addFromCategories();
console.log("Found categories:", categories);
// ["IFCWALL", "IFCSLAB", "IFCDOOR", "IFCWINDOW", ...]
// Now use them
const slabs = await finder.getItems([{ categories: [/SLAB/] }]);---
9. Custom Classification Groups
Build custom classifications for domain-specific grouping.
const classifier = components.get(OBC.Classifier);
const fragments = components.get(OBC.FragmentsManager);
// Classify by category first
await classifier.byCategory();
// Get all walls
const wallItems = await classifier.find({ Categories: ["IFCWALL"] });
// Get property data for walls
const wallData = await fragments.getData(wallItems);
// Build custom groups based on property values
for (const [modelId, elements] of Object.entries(wallData)) {
for (const element of elements) {
// Example: group by some property value
// (actual structure depends on the IFC model)
const groupName = "External Walls"; // derive from property data
const itemMap: OBC.ModelIdMap = {
[modelId]: new Set([/* element local IDs */])
};
classifier.addGroupItems("Fire Rating", groupName, itemMap);
}
}
// Now query custom groups
const ratedWalls = await classifier.find({
"Fire Rating": ["External Walls"]
});---
10. Iterate Classification Groups
const classifier = components.get(OBC.Classifier);
await classifier.byCategory();
await classifier.byIfcBuildingStorey();
// Iterate all classifications and groups
for (const [classificationName, groups] of classifier.list) {
console.log(`Classification: ${classificationName}`);
for (const [groupName, groupData] of groups) {
const items = await groupData.get();
const totalItems = Object.values(items)
.reduce((sum, set) => sum + set.size, 0);
console.log(` ${groupName}: ${totalItems} items`);
}
}---
11. Spatial Structure Traversal
Access the building's spatial hierarchy through storey classification.
const classifier = components.get(OBC.Classifier);
const fragments = components.get(OBC.FragmentsManager);
await classifier.byIfcBuildingStorey();
const storeys = classifier.list.get("Storeys");
if (storeys) {
for (const [storeyName, groupData] of storeys) {
const items = await groupData.get();
// Count elements per storey
let count = 0;
for (const set of Object.values(items)) {
count += set.size;
}
// Get property data for all elements on this storey
const data = await fragments.getData(items);
console.log(`${storeyName}: ${count} elements`);
}
}---
12. Serialize and Restore Queries
const finder = components.get(OBC.ItemsFinder);
// Create queries
finder.create("Structural Walls", [
{ categories: [/WALL/], attributes: {
queries: [{ name: /LoadBearing/, value: /true/i }]
}}
]);
// Export for storage (e.g., localStorage, server)
const serialized = finder.export();
localStorage.setItem("queries", JSON.stringify(serialized));
// Restore in a new session
const saved = JSON.parse(localStorage.getItem("queries")!);
await finder.import(saved);Property Queries and Classification API Reference
Classifier
The Classifier component organizes fragment items into named classification groups. Extends Component, implements Disposable.
UUID: e25a7f3c-46c4-4a14-9d3d-5115f24ebeb7
Properties
| Property | Type | Description |
|---|---|---|
enabled | boolean | Component activation state (default: true) |
list | DataMap<string, DataMap<string, ClassificationGroupData>> | Nested map: classification name -> group name -> group data |
onDisposed | Event<unknown> | Fires when the component is disposed |
Classification Building Methods
byCategory(config?: AddClassificationConfig): Promise<void>
Creates groups for each IFC entity type found across loaded models. Default classification name: "Categories". Group names are IFC type strings (e.g., "IFCWALL", "IFCSLAB", "IFCDOOR").
Internally uses ItemsFinder to discover geometric categories and assigns group queries for each.
byIfcBuildingStorey(config?: AddClassificationConfig): Promise<void>
Creates groups for each building storey. Default classification name: "Storeys". Group names are the storey Name attribute values (e.g., "Ground Floor", "Level 1").
Internally uses aggregateItemRelations() with the ContainsElements relationship to group elements by their containing storey.
byModel(config?: AddClassificationConfig): Promise<void>
Creates one group per loaded FragmentsModel. Default classification name: "Models". Group names are model identifiers.
Filters models using modelIds regex patterns from config if provided.
AddClassificationConfig
interface AddClassificationConfig {
classificationName?: string; // Override default classification name
modelIds?: RegExp[]; // Only process models matching these patterns
}Query Methods
find(data: ClassifierIntersectionInput): Promise<ModelIdMap>
Returns the intersection of items across the specified classification groups.
type ClassifierIntersectionInput = {
[classificationName: string]: string[]; // group names
};When multiple classifications are specified, the result contains only items that appear in ALL of the specified groups (set intersection).
getGroupData(classification: string, group: string): ClassificationGroupData
Retrieves or creates a group within a classification. Returns a ClassificationGroupData object.
ClassificationGroupData
Represents a single group within a classification.
| Member | Type | Description |
|---|---|---|
get() | Promise<ModelIdMap> | Returns the items in this group |
query? | ClassificationGroupQuery | Optional dynamic query config |
When a query is set, get() executes the query via ItemsFinder to produce fresh results. Without a query, get() returns the statically assigned items.
ClassificationGroupQuery
interface ClassificationGroupQuery {
name: string; // Name of an ItemsFinder query to execute
config?: QueryTestConfig; // Optional query configuration
}Item Management Methods
addGroupItems(classification: string, group: string, items: ModelIdMap): void
Adds items to a specific group. Creates the group if it does not exist.
removeItems(modelIdMap: ModelIdMap, config?: RemoveClassifierItemsConfig): void
Removes items from the classifier. Without config, removes from ALL classifications and ALL groups.
setGroupQuery(classification: string, group: string, query: ClassificationGroupQuery): void
Assigns a dynamic query to a group. The query references a named ItemsFinder query by its name property.
Aggregation Methods
aggregateItems(classification: string, query: ItemsQueryParams, config?): Promise<void>
Groups items matching a query into the specified classification. Uses an aggregation callback (default: defaultSaveFunction) to determine which group each item belongs to.
aggregateItemRelations(classification: string, query: ItemsQueryParams, relation: string, config?: ClassifyItemRelationsConfig): Promise<void>
Groups items by IFC relationships. Processes related items and places them into groups based on the related entity's name.
Utility Methods
defaultSaveFunction(item: ItemData): null | string
Default aggregation callback. Extracts the value from the item's Name property. Returns null if the Name property is not available.
dispose(): void
Clears all classifications and releases resources.
---
FragmentsManager.getData()
Signature
getData(
items: ModelIdMap,
config?: Partial<ItemsDataConfig>
): Promise<Record<string, ItemData[]>>Parameters
| Parameter | Type | Description |
|---|---|---|
items | ModelIdMap | Elements to query (model ID -> local ID sets) |
config | Partial<ItemsDataConfig> | Optional: controls what data to retrieve |
Return Value
Record<string, ItemData[]> — Keys are model UUID strings, values are arrays of ItemData objects (one per queried element).
ItemData
Interface from @thatopen/fragments representing the data of one item in a fragments model. Contains the element's IFC properties organized by their schema structure: attributes, property sets, quantity sets, type information, and relationships.
Performance Notes
- Runs in the web worker (non-blocking)
- Results are serialized back to the main thread via
postMessage - Large queries (thousands of items) can cause memory pressure during
serialization — batch queries for large datasets
---
ItemsFinder
Manages and executes queries to find items across loaded models. Extends Component, implements Disposable, Serializable.
UUID: 0da7ad77-f734-42ca-942f-a074adfd1e3a
Properties
| Property | Type | Description |
|---|---|---|
enabled | boolean | Component activation state (default: true) |
list | DataMap<string, FinderQuery> | Named queries indexed by string keys |
Methods
create(name: string, queries: ItemsQueryParams[]): FinderQuery
Creates and registers a named query with the specified parameters. Returns a FinderQuery object that can be referenced by Classifier via setGroupQuery().
getItems(queries: ItemsQueryParams[], config?): Promise<ModelIdMap>
Executes queries and returns matching items.
Config options:
| Property | Type | Description |
|---|---|---|
modelIds | RegExp[] | Filter: only search models matching these patterns |
items | ModelIdMap | Restrict search to these items only |
aggregation | QueryResultAggregation | "union" or "intersection" |
addFromCategories(modelIds?: RegExp[]): Promise<string[]>
Auto-generates queries for all geometric IFC categories found in loaded models. Returns an array of category names created.
export(): SerializedFinderQuery[]
Serializes all named queries for persistence.
import(result: SerializationResult): void
Restores queries from serialized data.
ItemsQueryParams
interface ItemsQueryParams {
categories?: RegExp[]; // Match IFC entity types (e.g., /WALL/, /SLAB/)
relation?: { // Filter by IFC relationship
name: string; // Relationship name
query: ItemsQueryParams; // Recursive: query the related items
};
attributes?: { // Filter by element attributes
queries: Array<{
name: RegExp; // Attribute name pattern
value: RegExp; // Attribute value pattern
}>;
};
}Supported relationship names:
"ContainedInStructure"— IfcRelContainedInSpatialStructure"ContainsElements"— Inverse of ContainedInStructure"Aggregates"— IfcRelAggregates"IsDecomposedBy"— Inverse of Aggregates
---
ModelIdMap (recap)
type ModelIdMap = Record<string, Set<number>>;The universal data structure for targeting items. Keys are model UUID strings, values are sets of local element IDs. Used as input for getData(), highlight(), Hider.set(), and as output from Classifier.find() and ItemsFinder.getItems().