Skip to content

Tutorials

Maarten Vroegindeweij edited this page Feb 18, 2026 · 1 revision

Tutorials

Step-by-step walkthroughs for common tasks with Ifc-Factory.


Tutorial 1: Extracting Data from an IFC File

Goal: Read an IFC file and extract a summary of all building elements with their properties.

import { IfcModel, getPropertySets } from '@ifc-factory/core';
import { readFileSync, writeFileSync } from 'node:fs';

const source = readFileSync('building.ifc', 'utf-8');
const model = IfcModel.fromStep(source);

// Define the element types we're interested in
const elementTypes = [
  'IfcWall', 'IfcDoor', 'IfcWindow', 'IfcSlab',
  'IfcColumn', 'IfcBeam', 'IfcStair', 'IfcRoof',
];

const report: string[] = [];
report.push('# Building Element Report');
report.push(`Project: ${model.project?.Name ?? 'Unknown'}`);
report.push(`Total entities: ${model.size}`);
report.push('');

for (const typeName of elementTypes) {
  const elements = model.getAllOfType(typeName);
  if (elements.length === 0) continue;

  report.push(`## ${typeName} (${elements.length})`);
  report.push('');

  for (const element of elements) {
    report.push(`### ${element.Name ?? '(unnamed)'} [#${element.expressID}]`);
    report.push(`- GlobalId: ${element.GlobalId}`);
    report.push(`- Description: ${element.Description ?? '—'}`);

    // Get property sets
    const psets = getPropertySets(model, element.expressID);
    if (psets.length > 0) {
      report.push('- Property Sets:');
      for (const pset of psets) {
        report.push(`  - **${pset.Name}**`);
        const propIds = pset.HasProperties as number[] | undefined;
        if (propIds) {
          for (const propId of propIds) {
            const prop = model.get(propId);
            if (prop) {
              report.push(`    - ${prop.Name}: ${prop.NominalValue ?? '—'}`);
            }
          }
        }
      }
    }
    report.push('');
  }
}

writeFileSync('report.md', report.join('\n'));
console.log('Report written to report.md');

Tutorial 2: Building a Complete Model with Properties

Goal: Create a model from scratch with spatial structure, elements, and rich property data.

import {
  IfcModel,
  createSpatialStructure,
  createPropertySet,
  createQuantitySet,
  assignPropertySet,
  generateIfcGuid,
} from '@ifc-factory/core';
import { writeFileSync } from 'node:fs';

const model = new IfcModel();

// Step 1: Create spatial hierarchy
const { project, site, building, storeys } = createSpatialStructure(model, {
  projectName: 'Kantoorgebouw De Toren',
  siteName: 'Locatie Zuidas',
  buildingName: 'Toren A',
  storeyNames: ['Kelder', 'Begane grond', 'Verdieping 1', 'Verdieping 2'],
});

// Step 2: Create walls for the ground floor
const wallConfigs = [
  { name: 'Buitenwand Noord', external: true, bearing: true },
  { name: 'Buitenwand Zuid', external: true, bearing: true },
  { name: 'Buitenwand Oost', external: true, bearing: false },
  { name: 'Buitenwand West', external: true, bearing: false },
  { name: 'Binnenwand 1', external: false, bearing: false },
  { name: 'Binnenwand 2', external: false, bearing: false },
];

const wallIds: number[] = [];

for (const config of wallConfigs) {
  const wall = model.create('IfcWall', {
    GlobalId: generateIfcGuid(),
    Name: config.name,
    PredefinedType: config.external ? 'SOLIDWALL' : 'PARTITIONING',
  });
  wallIds.push(wall.expressID);

  // Place in ground floor
  model.containInSpatialStructure(storeys[1].expressID, wall.expressID);

  // Add common properties
  const pset = createPropertySet(model, 'Pset_WallCommon', [
    { name: 'IsExternal', value: config.external },
    { name: 'LoadBearing', value: config.bearing },
    { name: 'FireRating', value: config.external ? 'REI90' : 'EI30' },
    { name: 'ThermalTransmittance', value: config.external ? 0.18 : 0.0, type: 'IFCREAL' },
  ]);
  assignPropertySet(model, pset.expressID, [wall.expressID]);
}

// Step 3: Create doors
const door = model.create('IfcDoor', {
  GlobalId: generateIfcGuid(),
  Name: 'Hoofdingang',
  OverallHeight: 2.4,
  OverallWidth: 1.2,
  PredefinedType: 'DOOR',
});
model.containInSpatialStructure(storeys[1].expressID, door.expressID);

const doorPset = createPropertySet(model, 'Pset_DoorCommon', [
  { name: 'IsExternal', value: true },
  { name: 'FireRating', value: 'EI30' },
  { name: 'SecurityRating', value: 'RC3' },
  { name: 'AcousticRating', value: '42 dB' },
]);
assignPropertySet(model, doorPset.expressID, [door.expressID]);

// Step 4: Create windows
const window = model.create('IfcWindow', {
  GlobalId: generateIfcGuid(),
  Name: 'Panoraamraam Zuid',
  OverallHeight: 1.8,
  OverallWidth: 2.4,
  PredefinedType: 'WINDOW',
});
model.containInSpatialStructure(storeys[1].expressID, window.expressID);

// Step 5: Add quantity sets
const qset = createQuantitySet(model, 'Qto_WallBaseQuantities', [
  { name: 'Length', value: 12.0, quantityType: 'LENGTH' },
  { name: 'Height', value: 3.0, quantityType: 'LENGTH' },
  { name: 'Width', value: 0.3, quantityType: 'LENGTH' },
  { name: 'GrossArea', value: 36.0, quantityType: 'AREA' },
  { name: 'NetArea', value: 32.4, quantityType: 'AREA' },
  { name: 'GrossVolume', value: 10.8, quantityType: 'VOLUME' },
  { name: 'NetVolume', value: 9.72, quantityType: 'VOLUME' },
]);
assignPropertySet(model, qset.expressID, [wallIds[0]]);

// Step 6: Write the model
const output = model.toStep();
writeFileSync('kantoorgebouw.ifc', output);
console.log(`Model created with ${model.size} entities`);

Tutorial 3: Comparing Two IFC Models

Goal: Load two versions of a model and find what changed.

import { IfcModel } from '@ifc-factory/core';
import { diffModels } from '@ifc-factory/ifc-utils';
import { readFileSync } from 'node:fs';

const sourceA = readFileSync('model_v1.ifc', 'utf-8');
const sourceB = readFileSync('model_v2.ifc', 'utf-8');

const modelA = IfcModel.fromStep(sourceA);
const modelB = IfcModel.fromStep(sourceB);

const diff = diffModels(modelA, modelB);

console.log('=== Model Comparison ===');
console.log(`Model A: ${modelA.size} entities`);
console.log(`Model B: ${modelB.size} entities`);
console.log('');

// Added entities
if (diff.added.length > 0) {
  console.log(`Added (${diff.added.length}):`);
  for (const id of diff.added) {
    const entity = modelB.get(id);
    console.log(`  + #${id} ${entity?.type} "${entity?.Name ?? ''}"`)
  }
}

// Removed entities
if (diff.removed.length > 0) {
  console.log(`\nRemoved (${diff.removed.length}):`);
  for (const id of diff.removed) {
    const entity = modelA.get(id);
    console.log(`  - #${id} ${entity?.type} "${entity?.Name ?? ''}"`)
  }
}

// Modified entities
if (diff.modified.length > 0) {
  console.log(`\nModified (${diff.modified.length}):`);
  for (const mod of diff.modified) {
    const entity = modelA.get(mod.id);
    console.log(`  ~ #${mod.id} ${entity?.type} "${entity?.Name ?? ''}":`);
    for (const change of mod.changes) {
      console.log(`      ${change}`);
    }
  }
}

Tutorial 4: Batch Property Assignment

Goal: Assign property sets to many elements at once.

import { IfcModel } from '@ifc-factory/core';
import { batchAssignProperties } from '@ifc-factory/ifc-utils';
import { readFileSync, writeFileSync } from 'node:fs';

const model = IfcModel.fromStep(readFileSync('model.ifc', 'utf-8'));

// Get IDs by type
const wallIds = model.getAllOfType('IfcWall').map(e => e.expressID);
const doorIds = model.getAllOfType('IfcDoor').map(e => e.expressID);
const windowIds = model.getAllOfType('IfcWindow').map(e => e.expressID);
const slabIds = model.getAllOfType('IfcSlab').map(e => e.expressID);

// Batch assign different property sets to different element groups
batchAssignProperties(model, [
  {
    elementIds: wallIds,
    psetName: 'Pset_WallCommon',
    properties: [
      { name: 'IsExternal', value: true },
      { name: 'ThermalTransmittance', value: 0.22, type: 'IFCREAL' },
      { name: 'FireRating', value: 'REI60' },
    ],
  },
  {
    elementIds: doorIds,
    psetName: 'Pset_DoorCommon',
    properties: [
      { name: 'IsExternal', value: false },
      { name: 'FireRating', value: 'EI30' },
    ],
  },
  {
    elementIds: windowIds,
    psetName: 'Pset_WindowCommon',
    properties: [
      { name: 'IsExternal', value: true },
      { name: 'ThermalTransmittance', value: 1.1, type: 'IFCREAL' },
      { name: 'GlazingAreaFraction', value: 0.8, type: 'IFCREAL' },
    ],
  },
  {
    elementIds: slabIds,
    psetName: 'Pset_SlabCommon',
    properties: [
      { name: 'IsExternal', value: false },
      { name: 'LoadBearing', value: true },
      { name: 'FireRating', value: 'REI120' },
    ],
  },
]);

writeFileSync('output.ifc', model.toStep());
console.log('Properties assigned to all elements');

Tutorial 5: Dutch Compliance (BBL + BENG + Aerius)

Goal: Create a model with full Dutch regulatory compliance data.

See the Dutch Compliance page for a complete walkthrough.


Tutorial 6: Working with Documents and Classifications

Goal: Attach documents, library references, and classifications to elements.

import {
  IfcModel,
  createSpatialStructure,
  createDocumentInformation,
  associateDocument,
  createLibraryInformation,
  createLibraryReference,
  associateLibrary,
  createClassification,
  createClassificationReference,
  associateClassification,
  generateIfcGuid,
} from '@ifc-factory/core';

const model = new IfcModel();
const { project, building, storeys } = createSpatialStructure(model, {
  projectName: 'Test Project',
  siteName: 'Test Site',
  buildingName: 'Building A',
  storeyNames: ['Ground Floor'],
});

// Create some elements
const wall = model.create('IfcWall', {
  GlobalId: generateIfcGuid(),
  Name: 'Main Wall',
});
model.containInSpatialStructure(storeys[0].expressID, wall.expressID);

// --- Documents ---
const doc = createDocumentInformation(model, {
  name: 'Constructietekening Detail A',
  description: 'Detailtekening wandaansluiting',
  location: 'https://docs.example.com/detail-a.pdf',
});
associateDocument(model, doc.expressID, [wall.expressID]);

// --- Classifications ---
const nlsfb = createClassification(model, {
  name: 'NL-SfB',
  edition: '2005',
  source: 'BIM Loket',
});

const classRef = createClassificationReference(model, {
  identification: '21.22',
  name: 'Buitenwanden; niet-dragende wanden',
  classificationId: nlsfb.expressID,
});
associateClassification(model, classRef.expressID, [wall.expressID]);

// --- Libraries ---
const lib = createLibraryInformation(model, {
  name: 'STABU Besteksystematiek',
  version: '2024',
  description: 'Nederlandse bestekssystematiek voor de bouw',
});

const libRef = createLibraryReference(model, {
  name: 'Metselwerk baksteen',
  identification: '22.21.10',
  libraryId: lib.expressID,
});
associateLibrary(model, libRef.expressID, [wall.expressID]);

console.log(`Model: ${model.size} entities`);

Tutorial 7: Low-Level STEP File Manipulation

Goal: Work directly with the STEP file format without IFC schema awareness.

import { readStepFile, writeStepFile } from '@ifc-factory/step-serializer';
import { readFileSync, writeFileSync } from 'node:fs';

const source = readFileSync('model.ifc', 'utf-8');
const stepFile = readStepFile(source);

// Inspect header
console.log('File Description:', stepFile.header.fileDescription.description);
console.log('File Name:', stepFile.header.fileName.name);
console.log('Schema:', stepFile.header.fileSchema.schemas);

// Count entity types
const typeCounts = new Map<string, number>();
for (const [, entity] of stepFile.entities) {
  typeCounts.set(entity.typeName, (typeCounts.get(entity.typeName) ?? 0) + 1);
}

// Sort by count
const sorted = [...typeCounts.entries()].sort((a, b) => b[1] - a[1]);
console.log('\nTop 20 entity types:');
for (const [type, count] of sorted.slice(0, 20)) {
  console.log(`  ${type}: ${count}`);
}

// Modify an entity's raw attributes
const entity42 = stepFile.entities.get(42);
if (entity42) {
  console.log(`\n#42 = ${entity42.typeName}(${entity42.attributes.length} attributes)`);
  // Attributes are StepValue[] — raw STEP values
  for (let i = 0; i < entity42.attributes.length; i++) {
    console.log(`  [${i}] = ${JSON.stringify(entity42.attributes[i])}`);
  }
}

// Write back
const output = writeStepFile(stepFile);
writeFileSync('output.ifc', output);

Tutorial 8: Parsing an EXPRESS Schema

Goal: Parse the IFC EXPRESS schema and analyze its structure.

import { parseExpress } from '@ifc-factory/express-parser';
import { readFileSync } from 'node:fs';

const source = readFileSync('schemas/IFC4X3_ADD2.exp', 'utf-8');
const schema = parseExpress(source);

console.log(`Schema: ${schema.name}`);
console.log(`Total declarations: ${schema.declarations.length}`);

// Count by kind
const kinds = { entity: 0, type: 0, function: 0, rule: 0 };
for (const decl of schema.declarations) {
  kinds[decl.kind]++;
}
console.log(`Entities: ${kinds.entity}`);
console.log(`Types: ${kinds.type}`);
console.log(`Functions: ${kinds.function}`);
console.log(`Rules: ${kinds.rule}`);

// Find all abstract entities
const abstractEntities = schema.declarations
  .filter(d => d.kind === 'entity' && d.abstract)
  .map(d => d.name);
console.log(`\nAbstract entities (${abstractEntities.length}):`);
for (const name of abstractEntities.slice(0, 10)) {
  console.log(`  ${name}`);
}

// Find entity inheritance tree for IfcWall
function getInheritanceChain(entityName: string): string[] {
  const chain = [entityName];
  let current = schema.declarations.find(d => d.kind === 'entity' && d.name === entityName);
  while (current?.kind === 'entity' && current.subtypeOf) {
    chain.push(current.subtypeOf);
    current = schema.declarations.find(d => d.kind === 'entity' && d.name === current!.subtypeOf);
  }
  return chain.reverse();
}

console.log('\nIfcWall inheritance:');
console.log(getInheritanceChain('IfcWall').join(' → '));
// IfcRoot → IfcObjectDefinition → IfcObject → IfcProduct → IfcElement → IfcBuildingElement → IfcWall

Clone this wiki locally