Skip to content

Lists and Schedules

IFClite can turn model data into configurable property tables, the BIM equivalent of door schedules and wall schedules. The @ifc-lite/lists package evaluates a list definition against parsed model data and produces rows you can display, group, summarise, or export as CSV.

How It Works

A list definition describes what to tabulate:

  • Entity types - Which IFC classes to include (e.g. all IfcDoor)
  • Columns - Which values to pull for each entity (attributes, properties, quantities, ...)
  • Conditions - Optional filters on property values

executeList runs the definition against a data provider (an adapter over your parsed model) and returns a ListResult with one row per matching entity.

Quick Start

import { executeList, listResultToCSV, LIST_PRESETS } from '@ifc-lite/lists';
import type { ListDataProvider } from '@ifc-lite/lists';

// LIST_PRESETS[0] is the Wall Schedule
const result = executeList(LIST_PRESETS[0], provider);

console.log(`${result.rows.length} walls`);

// Export as CSV
const csv = listResultToCSV(result);

executeList(definition, provider, modelId?) takes an optional third argument tagging rows with a model id (defaults to 'default'), useful when running the same list across multiple loaded models.

Column Sources

Each ColumnDefinition has a source that says where the value comes from:

Source Description Example
attribute Direct IFC attribute Name, GlobalId, ObjectType, Class
property Property from a pset Pset_WallCommon.FireRating
quantity Quantity from a qset Qto_WallBaseQuantities.NetArea
material Associated material names (joined with ", ") Concrete, Insulation
classification Classification references (joined with ", ") Uniclass Ss_25_10
spatial Containing spatial element name; propertyName picks the level (Storey (default), Building, Site, Project) Level 2
model Source file name office.ifc
zone Location-zone assignment (user-defined 3D zone boxes, viewer-computed); psetName holds the zone-set id, propertyName picks Zone (default, the zone name — straddling elements show every touched zone joined with ", ") or Straddles (boolean) Section A

A column looks like:

import type { ColumnDefinition } from '@ifc-lite/lists';

const fireRating: ColumnDefinition = {
  id: 'prop-pset_doorcommon-firerating',
  source: 'property',
  psetName: 'Pset_DoorCommon',
  propertyName: 'FireRating',
  label: 'FireRating',
};

For quantity columns, psetName holds the quantity set name (e.g. Qto_DoorBaseQuantities).

Built-in Presets

LIST_PRESETS is an array of ready-made ListDefinitions:

Preset Entity types Columns
Wall Schedule IfcWall, IfcWallStandardCase Common properties and base quantities
Door Schedule IfcDoor FireRating, IsExternal, AcousticRating, Width, Height, Area
Window Schedule IfcWindow Dimensions
Space Areas IfcSpace Areas and volumes
Zones & Systems IfcSpatialZone, IfcZone, IfcSystem, IfcDistributionSystem Names
All Elements Walls, doors, windows, slabs, columns, beams, stairs, roofs, coverings, curtain walls, railings Overview columns

Worked Example: Door Schedule to CSV

import { executeList, listResultToCSV, LIST_PRESETS } from '@ifc-lite/lists';

// LIST_PRESETS[1] is the Door Schedule:
//   Name, Class, ObjectType,
//   Pset_DoorCommon.FireRating / IsExternal / AcousticRating,
//   Qto_DoorBaseQuantities.Width / Height / Area
const doorSchedule = LIST_PRESETS[1];

const result = executeList(doorSchedule, provider, 'office.ifc');

for (const row of result.rows) {
  console.log(row.values);
}

const csv = listResultToCSV(result);
// listResultToCSV(result, delimiter?) - default delimiter is ','

CSV Safety

listResultToCSV guards against spreadsheet formula injection (CWE-1236): any cell that starts with =, +, -, @, tab, or carriage return is prefixed with a single quote so Excel and Google Sheets treat it as text rather than a formula. Standard CSV quoting (double quotes, "" escaping) is applied on top.

Grouping, Aggregation & Schedules

Set grouping on a ListDefinition to bucket rows by one or more columns (outermost first) and sum numeric columns per group:

import { executeList, summariseListRows, toScheduleRows, LIST_PRESETS } from '@ifc-lite/lists';

const definition = {
  ...LIST_PRESETS[0], // Wall Schedule
  grouping: {
    columnId: 'attr-name',       // legacy single-column field, kept in sync with columnIds[0]
    columnIds: ['attr-name'],    // ordered group-by columns, outermost first
    sumColumnIds: ['quant-qto_wallbasequantities-length'],
  },
};

const result = executeList(definition, provider);

// result.groups is a flat PRE-ORDER list: each parent group is immediately
// followed by its subgroups. Every group carries a Count aggregate (`count`)
// and per-column sums (`sums`).
for (const group of result.groups ?? []) {
  console.log(group.label, group.count, group.sums);
}

// A Bonsai-style schedule/pivot table — one row per group-value tuple (the
// LEAF groups only) instead of a nested tree. `levelCount` must be the number
// of grouping columns that were ACTUALLY applied: the engine drops grouping
// ids whose column is no longer in the definition (a stale persisted grouping
// is the common case), so passing the raw `columnIds.length` can ask for a
// deeper leaf level than the groups have — and `toScheduleRows` then matches
// nothing and returns no rows.
const activeGroupIds = definition.grouping.columnIds.filter(
  (id) => definition.columns.some((c) => c.id === id),
);
const scheduleRows = toScheduleRows(result.groups, activeGroupIds.length);
for (const row of scheduleRows) {
  console.log(row.path, row.count, row.sums); // e.g. ["Wall-01"], 1, { ... }
}

summariseListRows(definition, rows) is what executeList calls internally to build groups/summary; call it directly when you already have rows from elsewhere (e.g. merged across federated models) and just need to re-derive the grouping.

The Data Provider

executeList reads model data through the ListDataProvider interface, so the package has no hard dependency on how you parsed the model. Required methods include getEntitiesByType, getEntityName, getEntityGlobalId, getPropertySets, and getQuantitySets; optional methods (getMaterialNames, getClassifications, getStoreyName, getProjectName, getZoneAssignment, getZoneSetNames, ...) unlock the material, classification, spatial, model, and zone column sources, and the engine degrades gracefully when they are absent (a zone column simply resolves to null on a provider without zone data).

Discovering Columns

To build a column picker UI (or just see what a model contains), use discoverColumns:

import { discoverColumns } from '@ifc-lite/lists';
import { IfcTypeEnum } from '@ifc-lite/data';

// Accepts one provider or an array of providers
const discovered = discoverColumns(provider, [IfcTypeEnum.IfcDoor]);

discovered.attributes;  // available entity attributes
discovered.properties;  // Map<psetName, propertyNames[]>
discovered.quantities;  // Map<qsetName, quantityNames[]>

It samples up to 50 entities per type per provider, so it stays fast on large models.

Name Patterns

Conditions and lookups that match by name accept either an exact string or a regex literal. compileNameMatcher(pattern) returns a (name: string) => boolean:

  • /fire.*rating/i - a /body/flags string compiles to a regular expression
  • anything else - exact, case-sensitive match

isNamePattern(pattern) tells you whether a string will be treated as a regex.

Key Exports

Export Description
executeList(definition, provider, modelId?) Run a list definition, returns ListResult
listResultToCSV(result, delimiter?) CSV export with formula-injection guard
summariseListRows Aggregate rows into group summaries (ListGroup[] + whole-result ListSummary)
groupingColumnIds(grouping) Resolve a grouping config's ordered group-by column ids
toScheduleRows(groups, levelCount) Project grouped ListGroup[] to a schedule/pivot ListScheduleRow[] — one row per group-value tuple
discoverColumns(providers, entityTypes) Sample available attributes/properties/quantities
compileNameMatcher(pattern) / isNamePattern(pattern) Exact-or-regex name matching
LIST_PRESETS Built-in schedule definitions
ENTITY_ATTRIBUTES The attribute names available to attribute columns

See the package README and the type definitions (ListDefinition, ColumnDefinition, PropertyCondition, ListDataProvider) for the full API.