-
Notifications
You must be signed in to change notification settings - Fork 1
ifc utils
Layer 4 — Depends on core and schema. Optional convenience utilities for model validation, diffing, batch operations, schema detection, and GUID utilities.
pnpm add @ifc-factory/ifc-utilsValidate 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}`);
}| 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 |
interface ValidationIssue {
severity: 'error' | 'warning';
message: string;
entityId?: number; // The expressID of the problematic entity (if applicable)
}[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
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`);// 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}`);
}
}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
}[];
}Changes are formatted as "attributeName: oldValue -> newValue":
Name: "Wall A" -> "Wall B"
PredefinedType: "SOLIDWALL" -> "PARTITIONING"
type: IfcWall -> IfcWallStandardCase
- Entities are matched by expressID (not by GlobalId)
- Attribute comparison uses JSON serialization for deep equality
- The
expressIDandtypefields are handled specially (type change is reported) - Entity references (numbers) are compared as-is
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`);
}| 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 |
type IfcSchemaVersion = 'IFC2X3' | 'IFC4' | 'IFC4X1' | 'IFC4X2' | 'IFC4X3' | 'UNKNOWN';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.
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)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"
}- Exactly 22 characters long
- Uses custom base64 alphabet:
0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_$ - Encodes a 128-bit UUID (same entropy as a standard UUID v4)
- Example:
'2TGt$H0E5Cexq0Fmv1x7tP'
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' },
],
},
]);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');| 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 |
- core — The main library that ifc-utils extends
- Tutorials — Batch operations tutorial
- API Reference — Consolidated reference
Ifc-Factory
Getting Started
Concepts
Packages
API & Reference
Development