Skip to content

step parser

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

@ifc-factory/step-parser

Layer 2 — Depends on step-serializer and schema. Schema-aware STEP parser that combines raw STEP parsing with IFC type resolution.

Installation

pnpm add @ifc-factory/step-parser

Usage

Parse STEP to Typed Entities

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

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

console.log(`Schema: ${result.schema}`);
console.log(`Entities: ${result.entities.size}`);

// All entities are now typed with named attributes
for (const [id, entity] of result.entities) {
  console.log(`#${id}: ${entity.type}`);
  if (entity.type === 'IfcWall') {
    console.log(`  Name: ${entity.Name}`);
    console.log(`  GlobalId: ${entity.GlobalId}`);
    console.log(`  PredefinedType: ${entity.PredefinedType}`);
  }
}

Write Entities to STEP

import { writeEntitiesToStep } from '@ifc-factory/step-parser';

const stepOutput = writeEntitiesToStep(result.entities, 'IFC4X3');
writeFileSync('output.ifc', stepOutput);

Full Round-Trip

import { parseStepToEntities, writeEntitiesToStep } from '@ifc-factory/step-parser';

const source = readFileSync('input.ifc', 'utf-8');
const result = parseStepToEntities(source);

// Modify an entity
const wall = result.entities.get(42);
if (wall) {
  wall.Name = 'Updated Wall Name';
}

// Write back
const output = writeEntitiesToStep(result.entities, result.schema);
writeFileSync('output.ifc', output);

How It Works

The step-parser implements a two-pass architecture. See Architecture for the full picture.

Pass 1: Raw STEP Parsing

Delegates to @ifc-factory/step-serializer:

STEP source → Tokenizer → Reader → StepFile
                                      ├── header: StepHeader
                                      └── entities: Map<number, StepEntityInstance>
                                                     { id: 1, typeName: "IFCWALL", attributes: [...] }

Pass 2: Typed Entity Construction

Uses @ifc-factory/schema metadata:

StepEntityInstance → Instance Builder → IfcGenericEntity

For each raw instance:
1. Look up ENTITY_REGISTRY["IFCWALL"] → "IfcWall"
2. Look up SCHEMA_METADATA["IfcWall"] → { allAttributes: [...] }
3. Map positional attributes to named properties
4. Convert StepEntityRef → number (expressID)
5. Convert StepEnum → string
6. Convert StepTypedValue → unwrapped value
7. Convert null ($) → null
8. Convert StepDerived (*) → undefined

Value Conversion Table

STEP Value TypeScript Value
42 (integer) 42 (number)
3.14 (real) 3.14 (number)
'text' (string) 'text' (string)
.T. (boolean) true (boolean)
.F. (boolean) false (boolean)
$ (omitted) null
* (derived) undefined
#123 (entity ref) 123 (number)
.SOLIDWALL. (enum) 'SOLIDWALL' (string)
IFCLABEL('text') (typed) 'text' (unwrapped)
(1, 2, 3) (list) [1, 2, 3] (array)
(#10, #20) (ref list) [10, 20] (number array)

Unknown Entity Types

If a STEP entity type is not found in ENTITY_REGISTRY (e.g., from a newer or different schema), the parser creates an IfcGenericEntity with:

  • type set to the raw type name
  • All attributes stored as an indexed object (attr_0, attr_1, etc.)

This provides graceful degradation — the model can still be loaded and round-tripped even with unrecognized types.

API

parseStepToEntities(source: string): ParseResult

Full two-pass parse from STEP string to typed entities.

Parameters:

  • source — Complete STEP file content

Returns:

interface ParseResult {
  schema: string;                                  // e.g., 'IFC4X3'
  entities: Map<number, IfcGenericEntity>;         // All typed entities
  header: StepHeader;                              // Parsed header info
}

writeEntitiesToStep(entities: Map<number, IfcGenericEntity>, schema?: string): string

Serialize typed entities back to a STEP file string.

Parameters:

  • entities — Map of expressID → entity
  • schema — Schema identifier (default: 'IFC4X3')

Returns: Valid ISO 10303-21 string

Behavior:

  • Uses SCHEMA_METADATA to determine attribute order
  • Converts named properties back to positional attributes
  • Entity references (numbers) become #N
  • Enum strings become .VALUE.
  • Entities sorted by ID for deterministic output

Internal Components

Instance Builder

Maps raw StepEntityInstance to IfcGenericEntity:

function buildTypedEntity(
  instance: StepEntityInstance,
  metadata: EntityMetadata
): IfcGenericEntity {
  const entity: IfcGenericEntity = {
    expressID: instance.id,
    type: ENTITY_REGISTRY[instance.typeName.toUpperCase()],
  };

  for (let i = 0; i < metadata.allAttributes.length; i++) {
    const attrMeta = metadata.allAttributes[i];
    const rawValue = instance.attributes[i];
    entity[attrMeta.name] = convertValue(rawValue);
  }

  return entity;
}

Value Parser

Converts StepValue to JavaScript values:

function convertValue(value: StepValue): unknown {
  if (value === null) return null;              // $
  if (typeof value === 'number') return value;  // integer/real
  if (typeof value === 'string') return value;  // string
  if (typeof value === 'boolean') return value; // .T./.F.
  if ('ref' in value) return value.ref;         // #N → number
  if ('enum' in value) return value.enum;       // .ENUM. → string
  if ('typeName' in value) return convertValue(value.value);  // unwrap
  if ('derived' in value) return undefined;     // *
  if (Array.isArray(value)) return value.map(convertValue);   // list
  return value;
}

Reference Resolver

Entity references are kept as plain numbers (expressIDs). No object-level resolution is performed — this is by design. See Architecture for the rationale.

See Also

Clone this wiki locally