Querying Data¶
Guide to querying IFC data with IFClite.
Overview¶
IFClite provides multiple query interfaces:
IfcQuery's fluent and SQL bulk queries read the parsed IfcDataStore passed
to its constructor. They do not include entities created or deleted in a
session's mutation view. Use the SDK query backend for live edited models.
Fluent Query API¶
Basic Queries¶
import { IfcQuery } from '@ifc-lite/query';
const query = new IfcQuery(store); // store from parseColumnar()
// Get all walls
const walls = query.walls().execute();
// Get all doors
const doors = query.doors().execute();
// Get all windows
const windows = query.windows().execute();
// Get specific entity types
const beams = query.ofType('IFCBEAM').execute();
const columns = query.ofType('IFCCOLUMN').execute();
Type Shortcuts¶
| Method | Entity Type |
|---|---|
.walls() |
IFCWALL, IFCWALLSTANDARDCASE |
.doors() |
IFCDOOR |
.windows() |
IFCWINDOW |
.slabs() |
IFCSLAB |
.columns() |
IFCCOLUMN |
.beams() |
IFCBEAM |
.spaces() |
IFCSPACE |
Property Filters¶
// Filter by property value
const externalWalls = query
.walls()
.whereProperty('Pset_WallCommon', 'IsExternal', '=', true)
.execute();
// Filter by numeric comparison
const wellInsulated = query
.walls()
.whereProperty('Pset_WallCommon', 'ThermalTransmittance', '<=', 0.25)
.execute();
// Filter by boolean
const loadBearing = query
.walls()
.whereProperty('Pset_WallCommon', 'LoadBearing', '=', true)
.execute();
// Filter by string pattern
const fireRated = query
.walls()
.whereProperty('Pset_WallCommon', 'FireRating', 'startsWith', 'REI')
.execute();
// Quantity sets work through the same call — name the Qto_ set as the
// first argument
const largeWalls = query
.walls()
.whereProperty('Qto_WallBaseQuantities', 'NetSideArea', '>', 10)
.execute();
Comparisons are same-type only: '60' (a string FireRating) does not match
the number 60, and a null on either side never matches — not even with !=.
contains and startsWith are string-only. A filter matches when any
property of that name, in any set of that name, satisfies it — which can
differ from EntityNode.property(), a single-value getter that returns the
first match, for an entity that carries the same property twice.
Scope the query before filtering on a property
On a STEP (.ifc) model the property sets are read lazily from the source
buffer rather than from a pre-built index, so whereProperty resolves each
candidate entity and the cost grows with how many entities reach the
filter. Call .walls() / .ofType(...) / .onStorey(...) first:
query.all().whereProperty(...) resolves every entity in the model, which on
a large model can cost many times the type-scoped form.
This applies to a cache-restored .ifc model too: the cache stores the
property table as it was built, and a STEP parse leaves it empty, so a
restored model resolves per candidate exactly like a fresh parse.
The rule is the store, not the file format: a query answers from the property index whenever the store carries table rows, and resolves per candidate when it does not. An indexed store's cost scales with the number of rows carrying the name rather than with the candidate count. Both paths return the same entities.
Chained Queries¶
// Complex query chain
const walls = query
.walls()
.whereProperty('Pset_WallCommon', 'IsExternal', '=', true)
.whereProperty('Pset_WallCommon', 'FireRating', '=', 'REI60')
.whereProperty('Qto_WallBaseQuantities', 'NetSideArea', '>', 10)
.execute();
// execute() returns QueryResultEntity[]; project the fields you need
const results = walls.map(w => ({ expressId: w.expressId, name: w.name, type: w.type }));
console.log(results);
// [
// { expressId: 123, name: 'Wall-001', type: 'IfcWall' },
// { expressId: 456, name: 'Wall-002', type: 'IfcWallStandardCase' },
// ...
// ]
Spatial Queries¶
Hierarchy Navigation¶
// Get all storeys (getter, returns EntityNode[])
const storeys = query.storeys;
// Get elements on a specific storey
const groundFloor = query.storeys.find(s => s.name === 'Ground Floor');
const groundFloorElements = groundFloor
? query.onStorey(groundFloor.expressId).execute()
: [];
// Get the direct elements of every storey (contains() is one hop, not recursive;
// use traverse() or decomposes() to walk nested aggregation)
const buildingElements = query.storeys.flatMap(storey => storey.contains());
// Navigate up the hierarchy
const wall = query.entity(123);
const storey = wall.containedIn(); // EntityNode | null
const building = wall.building(); // walks up to the containing IfcBuilding
Spatial Relationships¶
// Get entities contained by a spatial container (returns EntityNode[])
const contained = query.entity(storeyId).contains();
// Get the container of an element (EntityNode | null)
const container = query.entity(wallId).containedIn();
Relationship Queries¶
Finding Related Entities¶
// Get property sets for an entity (returns PropertySet[])
const psets = query.entity(wallId).properties();
// Get quantity sets for an entity (returns QuantitySet[])
const qtos = query.entity(wallId).quantities();
// Get openings in a wall (VoidsElement, returns EntityNode[])
const openings = query.entity(wallId).voids();
// Get filling elements (doors/windows in openings; FillsElement)
const fillings = query.entity(openingId).filledBy();
Relationship Types¶
related(ref, relationshipType, direction) accepts the exact EXPRESS name of
every schema-resolvable concrete IfcRelationship subtype available in the
model's IFC schema. IFC2X3 IfcRelAssociates is excluded because it has no
Relating* attribute. Names are never shortened aliases. For example, use IfcRelContainedInSpatialStructure,
IfcRelAggregates, IfcRelNests, IfcRelDefinesByObject,
IfcRelAssociatesMaterial, IfcRelConnectsPorts, IfcRelSequence, or
IfcRelPositions (IFC4X3). forward follows the EXPRESS relating-to-related
slots; inverse walks them in reverse.
The SDK's bim.relationships(ref) also returns relations, alongside
the convenience voids, fills, groups, and connections arrays. Each
entry identifies one relationship record and its opposite endpoint:
const { relations = [] } = bim.relationships({ modelId, expressId: wallId });
for (const edge of relations) {
console.log(
edge.relationshipId, // express id of the IfcRel* record
edge.relationshipType, // exact name, such as IfcRelVoidsElement
edge.direction, // forward or inverse relative to wallRef
edge.entity, // { id, type, name? } at the other end
);
}
relationshipId distinguishes separate IFC relationship records even when
they connect the same pair of entities. It is 0 only for payloads from an
older server that did not provide relationship record ids.
SQL Queries¶
For complex analytics, use SQL via DuckDB. SQL support is optional: install the
@duckdb/duckdb-wasm package in your app (it is lazy-loaded on the first sql()
call, so it adds nothing to your bundle until used; sql() throws if the package
is not installed):
import { IfcQuery } from '@ifc-lite/query';
const query = new IfcQuery(store); // store from parseColumnar()
// sql() lazily initializes DuckDB-WASM on first call
const result = await query.sql(`
SELECT
e.type,
COUNT(*) as count,
AVG(p.value_int) as avg_fire_rating
FROM entities e
JOIN properties p ON e.express_id = p.entity_id
WHERE e.type LIKE 'IfcWall%'
AND p.pset_name = 'Pset_WallCommon'
AND p.prop_name = 'FireRating'
GROUP BY e.type
ORDER BY count DESC
`);
console.table(result);
// ┌──────────────────────┬───────┬─────────────────┐
// │ type │ count │ avg_fire_rating │
// ├──────────────────────┼───────┼─────────────────┤
// │ IfcWallStandardCase │ 42 │ 45 │
// │ IfcWall │ 18 │ 30 │
// └──────────────────────┴───────┴─────────────────┘
SQL Table Schema¶
// Entities table
interface EntitiesTable {
express_id: number;
global_id: string;
name: string | null;
description: string | null;
type: string;
object_type: string | null;
has_geometry: boolean;
is_type: boolean;
contained_in_storey: number | null;
defined_by_type: number | null;
}
// Properties table
interface PropertiesTable {
entity_id: number;
pset_name: string;
pset_global_id: string;
prop_name: string;
prop_type: string;
value_string: string | null;
value_real: number | null;
value_int: number | null;
value_bool: boolean | null;
}
// Quantities table
interface QuantitiesTable {
entity_id: number;
qset_name: string;
quantity_name: string;
quantity_type: string;
value: number;
formula: string | null;
}
// Relationships table
interface RelationshipsTable {
source_id: number;
target_id: number;
rel_type: string;
rel_id: number;
}
Complex SQL Examples¶
-- Find walls with their storey names
-- IfcRelContainedInSpatialStructure edges run storey (source_id) -> element (target_id)
SELECT
e.express_id,
e.name as wall_name,
s.name as storey_name
FROM entities e
JOIN relationships r ON e.express_id = r.target_id
JOIN entities s ON r.source_id = s.express_id
WHERE e.type LIKE 'IfcWall%'
AND r.rel_type = 'IfcRelContainedInSpatialStructure'
AND s.type = 'IfcBuildingStorey';
-- Calculate total area by entity type
SELECT
e.type,
SUM(q.value) as total_area
FROM entities e
JOIN quantities q ON e.express_id = q.entity_id
WHERE q.quantity_name = 'NetArea'
GROUP BY e.type
ORDER BY total_area DESC;
-- Find walls with missing fire ratings
SELECT e.express_id, e.name, e.type
FROM entities e
WHERE e.type LIKE 'IfcWall%'
AND NOT EXISTS (
SELECT 1 FROM properties p
WHERE p.entity_id = e.express_id
AND p.pset_name = 'Pset_WallCommon'
AND p.prop_name = 'FireRating'
);
Direct Data Access¶
For performance-critical operations, access columnar data directly:
import { IfcTypeEnumFromString } from '@ifc-lite/data';
// store is the IfcDataStore from parseColumnar()
// Access the entity table
console.log(`Total entities: ${store.entities.count}`);
// Iterate efficiently over the columnar express-id array
for (let i = 0; i < store.entities.count; i++) {
const expressId = store.entities.expressId[i];
const name = store.entities.getName(expressId);
const type = store.entities.getTypeName(expressId);
}
// Fetch every entity of a given type
const wallIds = store.entities.getByType(IfcTypeEnumFromString('IfcWall'));
// Match entities by property value (prop, operator, value, psetName)
const externalWallIds = store.properties.findByProperty(
'IsExternal', '=', true, 'Pset_WallCommon',
);
findByProperty reads the columnar property table, which a STEP parse leaves
empty on purpose (store.properties.count === 0) — on a .ifc model it returns
[], and it still returns [] after that model is restored from cache, because
the empty table is what was cached. Use query.walls().whereProperty(...), which
picks the right source for the store it is given, unless you know the table is
materialised.
Query Performance¶
Performance Tips¶
- Use type shortcuts for common entity types
- Filter early to reduce result set size
- Use direct access for performance-critical loops
- Use SQL for complex aggregations
- Cache query results when reusing
// Efficient: filter by type first
const externalWalls = query
.walls()
.whereProperty('Pset_WallCommon', 'IsExternal', '=', true)
.execute();
// Inefficient: scan all entities, then narrow by type in JS
const alsoExternalWalls = query
.all()
.whereProperty('Pset_WallCommon', 'IsExternal', '=', true)
.execute()
.filter(e => e.type === 'IfcWall');
Captured populations¶
A capture supports up to 20,000 members across 1,024 source files. Larger or malformed saved populations refuse explicitly; capture a smaller population instead.
For a pinned selected or visible Rules population, persist a CapturedEntityScope and resolve it with resolveCapturedEntityScope(scope, models). Models supply their filterIdentity, full-content sourceContentHash, parsed store, and live mutation view. The resulting per-model candidate map includes explicit empty sets for all other loaded models; pass it as candidateExpressIdsByModel to the federated filter evaluator. Scope resolution refuses missing, ambiguous, replaced, or deleted members before a run. Source identity must cover every source byte, not a sampled hash or filename. Authored members additionally store their original CREATE_ENTITY mutation ID and refuse recovery that loses that provenance.
Next Steps¶
- Selector Syntax - the IfcOpenShell one-line filter syntax, and what each construct maps to here
- Export Guide - Export query results
- API Reference - Complete API docs