Skip to content

ifc utils

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

@ifc-factory/ifc-utils

Layer 4 — Depends on core and schema. Optional convenience utilities for model validation, diffing, batch operations, schema detection, and GUID utilities.

Installation

pnpm add @ifc-factory/ifc-utils

Model Validation

Validate an IfcModel for common issues and data quality problems.

import { validateModel } from '@ifc-factory/ifc-utils';
import type { ValidationIssue } from '@ifc-factory/ifc-utils';

const issues = validateModel(model);

// Separate by severity
const errors = issues.filter(i => i.severity === 'error');
const warnings = issues.filter(i => i.severity === 'warning');

console.log(`${errors.length} errors, ${warnings.length} warnings`);

for (const issue of issues) {
  const entity = issue.entityId ? ` (entity #${issue.entityId})` : '';
  console.log(`[${issue.severity.toUpperCase()}] ${issue.message}${entity}`);
}

Validation Checks

Check Severity Description
Missing IfcProject error Model must contain at least one IfcProject
Duplicate GlobalId error Two entities share the same GlobalId
Orphaned relationship warning IfcRelAggregates references non-existent entity
Missing required attributes warning Entity missing non-optional attributes

ValidationIssue

interface ValidationIssue {
  severity: 'error' | 'warning';
  message: string;
  entityId?: number;    // The expressID of the problematic entity (if applicable)
}

Example Output

[ERROR] No IfcProject entity found in model
[ERROR] Duplicate GlobalId '2TGt$H0E5Cexq0Fmv1x7tP' found on entities #42 and #87
[WARNING] IfcRelAggregates #200 references non-existent entity #999

Model Diffing

Compare two IFC models and find added, removed, and modified entities.

import { diffModels } from '@ifc-factory/ifc-utils';
import type { ModelDiff } from '@ifc-factory/ifc-utils';

const diff = diffModels(modelA, modelB);

console.log(`Added:    ${diff.added.length} entities`);
console.log(`Removed:  ${diff.removed.length} entities`);
console.log(`Modified: ${diff.modified.length} entities`);

Detailed Diff Output

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

// Removed entities (only in modelA)
for (const id of diff.removed) {
  const entity = modelA.get(id);
  console.log(`- #${id} ${entity?.type} "${entity?.Name ?? ''}"`);
}

// Modified entities (exist in both, but differ)
for (const mod of diff.modified) {
  const entity = modelA.get(mod.id);
  console.log(`~ #${mod.id} ${entity?.type}:`);
  for (const change of mod.changes) {
    console.log(`    ${change}`);
  }
}

ModelDiff

interface ModelDiff {
  added: number[];       // expressIDs present only in modelB
  removed: number[];     // expressIDs present only in modelA
  modified: {
    id: number;
    changes: string[];   // Human-readable change descriptions
  }[];
}

Change Description Format

Changes are formatted as "attributeName: oldValue -> newValue":

Name: "Wall A" -> "Wall B"
PredefinedType: "SOLIDWALL" -> "PARTITIONING"
type: IfcWall -> IfcWallStandardCase

How Comparison Works

  • Entities are matched by expressID (not by GlobalId)
  • Attribute comparison uses JSON serialization for deep equality
  • The expressID and type fields are handled specially (type change is reported)
  • Entity references (numbers) are compared as-is

Schema Version Detection

Detect the IFC schema version from a STEP file without fully parsing it.

import { detectSchemaVersion, isIfc4x3 } from '@ifc-factory/ifc-utils';
import type { IfcSchemaVersion } from '@ifc-factory/ifc-utils';

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

// Detect schema version
const version: IfcSchemaVersion = detectSchemaVersion(source);
console.log(`Schema: ${version}`);

// Quick check for IFC4X3
if (isIfc4x3(source)) {
  console.log('This is an IFC4X3 file — fully supported');
} else {
  console.log(`Schema ${version} detected — parsing may have limited type support`);
}

Supported Schema Versions

Return Value Matches
'IFC4X3' IFC4X3, IFC4X3_ADD1, IFC4X3_ADD2, etc.
'IFC4X2' IFC4X2
'IFC4X1' IFC4X1
'IFC4' IFC4, IFC4_ADD1, IFC4_ADD2
'IFC2X3' IFC2X3, IFC2X3_TC1
'UNKNOWN' Unrecognized or missing FILE_SCHEMA

IfcSchemaVersion

type IfcSchemaVersion = 'IFC2X3' | 'IFC4' | 'IFC4X1' | 'IFC4X2' | 'IFC4X3' | 'UNKNOWN';

How It Works

The function reads the FILE_SCHEMA header entry using a regex, without parsing the full file:

FILE_SCHEMA(('IFC4X3_ADD2'));

The regex extracts the schema string and matches against known patterns.


GUID Utilities

Generate GUIDs

import { generateIfcGuid, generateMultipleGuids } from '@ifc-factory/ifc-utils';

// Single GUID
const guid = generateIfcGuid();
// e.g., '2TGt$H0E5Cexq0Fmv1x7tP'

// Multiple GUIDs (guaranteed unique)
const guids = generateMultipleGuids(100);
console.log(guids.length);  // 100
console.log(new Set(guids).size);  // 100 (all unique)

Validate GUIDs

import { validateGuid, isValidIfcGuid } from '@ifc-factory/ifc-utils';

// Quick check
console.log(isValidIfcGuid('2TGt$H0E5Cexq0Fmv1x7tP'));  // true
console.log(isValidIfcGuid('invalid'));                     // false
console.log(isValidIfcGuid(''));                            // false

// Detailed validation
const issues = validateGuid('too-short');
for (const issue of issues) {
  console.log(issue);  // "GUID must be exactly 22 characters"
}

IFC GUID Format

  • Exactly 22 characters long
  • Uses custom base64 alphabet: 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_$
  • Encodes a 128-bit UUID (same entropy as a standard UUID v4)
  • Example: '2TGt$H0E5Cexq0Fmv1x7tP'

Batch Operations

Batch Assign Properties

Assign different property sets to different element groups in a single call:

import { batchAssignProperties } from '@ifc-factory/ifc-utils';

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);

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' },
    ],
  },
]);

Batch Update Property

Update a single property on multiple entities:

import { batchUpdateProperty } from '@ifc-factory/ifc-utils';

// Update the Name on multiple entities
const ids = [10, 20, 30, 40, 50];
const updated = batchUpdateProperty(model, ids, 'Name', 'Renamed Element');
console.log(`Updated ${updated} entities`);  // 5

// Update Description on all walls
const wallIds = model.getAllOfType('IfcWall').map(e => e.expressID);
batchUpdateProperty(model, wallIds, 'Description', 'Auto-tagged by script');

Full API Summary

Function Returns Description
validateModel(model) ValidationIssue[] Validate model for issues
diffModels(a, b) ModelDiff Compare two models
detectSchemaVersion(source) IfcSchemaVersion Detect schema from STEP string
isIfc4x3(source) boolean Check if IFC4X3
generateIfcGuid() string Generate single IFC GUID
generateMultipleGuids(count) string[] Generate multiple GUIDs
validateGuid(guid) string[] Validate GUID format
isValidIfcGuid(guid) boolean Quick GUID validity check
batchAssignProperties(model, assignments) void Batch property set assignment
batchUpdateProperty(model, ids, prop, value) number Batch property update

See Also

  • core — The main library that ifc-utils extends
  • Tutorials — Batch operations tutorial
  • API Reference — Consolidated reference

Clone this wiki locally