SuperAPI AI Directory

ifc-impl-reading-parsing

General & other

View repository

Updated 10-15 22:18

About this skill

IFC Reading and Parsing

An IFC file in the .ifc STEP physical file (SPF) encoding is a flat list of numbered entity instances. The semantics are a graph: instances point at each other by #id, and relationships are first-class entities. Reading IFC correctly means two things: indexing every instance before resolving any reference, and computing INVERSE attributes that the file never stores.

This skill covers the two-pass parse, dispatching on FILE_SCHEMA, the positional attribute model with its $ and * tokens, building the inverse index, and traversing from an element to its property sets, type, spatial container, and associations.

Verified against ISO 10303-21 (the SPF encoding) and the IFC 4.3.2 specification (ISO 16739-1:2024), with IFC4 and IFC2x3 deltas noted inline. Applies to IFC2x3, IFC4, IFC4.3.

Quick Reference

The parse contract

A conformant reader ALWAYS runs two passes:

  1. Pass 1, index: scan every #<id>=KEYWORD(...); line and store the id, the entity keyword, and the raw attribute tokens. Resolve NOTHING.
  2. Pass 2, resolve: replace each #id token with the instance from the index, and compute INVERSE attributes.

References ALWAYS may point forward (the referenced #id line appears later in the file). A single-pass reader that resolves on first sight fails on every forward reference. NEVER resolve references during pass 1.

The instance line grammar

#42=IFCWALL('1Hkj5pQ9X8x9bq0d6FZ7yA',#5,'Wall-01',$,$,#60,#80,$,$);
^   ^       ^------------------------ ordered positional attributes -----^
id  keyword
  • The instance name is # followed by a positive integer. It is locally valid within this file ONLY. Numbering is not contiguous and carries no semantic meaning. NEVER treat #id as a stable cross-file identifier.
  • The entity keyword is upper-case (IFCWALL, IFCPROJECT).
  • Attributes are an ordered, unnamed, positional sequence in EXPRESS declared order, including every inherited explicit attribute.
  • Only explicit attributes are serialized. INVERSE, DERIVED, and re-declared attributes are omitted from the value list.

The $ and * tokens

Token Meaning Reader action
$ An unset OPTIONAL explicit attribute Treat the slot as present with value null. It still occupies one position.
* A slot for an inherited explicit attribute that this subtype re-declared as DERIVED Skip the value; it is computed by the schema. It still occupies one position.

ALWAYS count $ and * as occupied positional slots. NEVER drop them when mapping tokens to attribute names.

FILE_SCHEMA dispatch

The HEADER section ends with FILE_SCHEMA(('<name>'));. A reader ALWAYS dispatches its entity definitions on this value before parsing DATA.

FILE_SCHEMA value Schema Notes
IFC2X3 IFC2x3 TC1 IfcOwnerHistory mandatory; IsDefinedBy mixes type and properties; no IsTypedBy.
IFC4 IFC4 ADD2 TC1 IfcOwnerHistory optional; IsTypedBy split from IsDefinedBy.
IFC4X3, IFC4X3_ADD2 IFC4.3 ADD2 IfcBuildingElement renamed to IfcBuiltElement; infrastructure entities added.

NEVER assume IFC4 entity names or attribute order on an IFC2X3 file.

INVERSE attributes are computed, not stored

INVERSE attributes appear in the schema but NEVER in the file. The reader computes them in pass 2 by scanning relationship instances and indexing their forward references back to the referenced objects.

From INVERSE attribute Via relationship Reaches
IfcObject IsDefinedBy IfcRelDefinesByProperties property and quantity sets
IfcObject IsTypedBy (IFC4+) IfcRelDefinesByType the type object
IfcElement ContainedInStructure IfcRelContainedInSpatialStructure the spatial container
IfcObjectDefinition HasAssociations IfcRelAssociates* material, classification
IfcObjectDefinition IsDecomposedBy IfcRelAggregates the parts (this is the whole)
IfcObjectDefinition Decomposes IfcRelAggregates the whole (this is a part)
IfcElement HasOpenings IfcRelVoidsElement the opening elements

Full INVERSE catalogue with FOR clauses is in references/methods.md.

Decision Trees

How do I resolve a #id reference?

Encountered a #id token while reading an attribute.
|
+- Still in pass 1 (indexing)?
|    -> Store the raw token. Do NOT look it up. The target may not be
|       indexed yet (forward reference).
|
+- In pass 2 (resolving)?
     -> Look up #id in the instance index built by pass 1.
        |
        +- Found    -> substitute the resolved instance.
        +- Not found-> the file is malformed. ALWAYS fail loudly with the
                       missing id. NEVER silently substitute null.

Is this attribute slot $, *, or a value?

Reading positional token N of an entity instance.
|
+- Token is $   -> OPTIONAL attribute is unset. Slot value = null.
|                  Advance to slot N+1.
|
+- Token is *   -> Attribute was re-declared DERIVED by this subtype.
|                  Slot value = computed by schema, not in file.
|                  Advance to slot N+1.
|
+- Token is #id        -> a reference. Resolve in pass 2.
+- Token is (..)       -> an aggregate (LIST/SET/BAG/ARRAY). Parse members.
+- Token is .NAME.     -> an enumeration, boolean, or logical value.
+- Token is 'text'     -> a string. Un-escape control directives.
+- Token is TYPE(val)  -> a typed (select/measure) value. Unwrap.
+- Token is a number   -> an integer or real literal.

ALWAYS map slot N to the Nth explicit attribute in EXPRESS declared order, counting inherited attributes first. See references/methods.md for the IfcRoot to concrete-entity attribute order.

Which inverse do I follow to reach properties?

I have an IfcElement and want its property sets.
|
+- FILE_SCHEMA is IFC4 or IFC4X3?
|    -> element.IsDefinedBy holds ONLY IfcRelDefinesByProperties.
|       Follow each to RelatingPropertyDefinition.
|       For the type, follow element.IsTypedBy instead.
|
+- FILE_SCHEMA is IFC2X3?
     -> element.IsDefinedBy mixes IfcRelDefinesByProperties AND
        IfcRelDefinesByType. Filter by relationship keyword:
        keep IfcRelDefinesByProperties for properties,
        keep IfcRelDefinesByType for the type.
        There is NO IsTypedBy inverse in IFC2x3.

This split is the single most version-sensitive part of reading IFC. See the pattern below and references/anti-patterns.md.

Which schema version am I parsing?

Read FILE_SCHEMA from the HEADER.
|
+- 'IFC2X3'              -> IFC2x3 entity set. IfcBuildingElement abstract.
+- 'IFC4'               -> IFC4 entity set. IfcBuildingElement abstract.
+- 'IFC4X3' / 'IFC4X3_ADD2' -> IFC4.3 entity set. IfcBuiltElement (renamed,
|                              non-abstract). Infrastructure entities exist.
+- anything else        -> unknown schema. ALWAYS fail; NEVER guess.

Patterns

Every snippet uses STEP physical file syntax. Full verified signatures are in references/methods.md; complete runnable instances are in references/examples.md.

Pattern: two-pass index then resolve

Pass 1 builds the index. Pass 2 resolves references and computes inverses.

# Pass 1: index every instance, resolve nothing.
index = {}                                  # id -> (keyword, raw_tokens)
for line in data_section_lines:
    inst_id, keyword, raw_tokens = split_instance_line(line)
    index[inst_id] = (keyword, raw_tokens)

# Pass 2: resolve #id tokens against the completed index.
for inst_id, (keyword, raw_tokens) in index.items():
    resolved = [resolve(tok, index) for tok in raw_tokens]

resolve substitutes a #id token with index[id]. Because pass 1 finished first, a forward reference (a #id whose line came later) is already in the index. ALWAYS complete pass 1 over the whole DATA section before starting pass 2.

Pattern: dispatch on FILE_SCHEMA

schema = read_file_schema(header)            # 'IFC2X3' | 'IFC4' | 'IFC4X3'
if schema == 'IFC2X3':
    entity_defs = IFC2X3_DEFS
elif schema == 'IFC4':
    entity_defs = IFC4_DEFS
elif schema in ('IFC4X3', 'IFC4X3_ADD2'):
    entity_defs = IFC4X3_DEFS
else:
    raise ValueError(f'unsupported FILE_SCHEMA: {schema}')

The schema set determines which entities exist and their attribute order. NEVER parse DATA before this dispatch. NEVER hardcode one schema.

Pattern: read explicit attributes positionally

For any rooted entity, positions 1 to 4 are ALWAYS the IfcRoot attributes: GlobalId, OwnerHistory, Name, Description. The concrete entity adds its own explicit attributes after every inherited one.

#42=IFCWALL(
  '1Hkj5pQ9X8x9bq0d6FZ7yA',  /* 1 GlobalId       (IfcRoot)    */
  #5,                        /* 2 OwnerHistory   (IfcRoot)    */
  'Wall-01',                 /* 3 Name           (IfcRoot)    */
  $,                         /* 4 Description    (IfcRoot)    */
  $,                         /* 5 ObjectType     (IfcObject)  */
  #60,                       /* 6 ObjectPlacement(IfcProduct) */
  #80,                       /* 7 Representation (IfcProduct) */
  $,                         /* 8 Tag            (IfcElement) */
  $                          /* 9 PredefinedType (IfcWall)    */
);

Map each token to its attribute by position, NEVER by guessing. A $ or * still consumes one position. See references/methods.md for the declared order of every entity in this skill.

Pattern: build the inverse index

INVERSE attributes are not in the file. Build them in pass 2 by scanning relationship instances and recording the back-link.

inverse = defaultdict(lambda: defaultdict(list))   # obj_id -> name -> [rels]

for rel_id, (keyword, _) in index.items():
    if keyword == 'IFCRELDEFINESBYPROPERTIES':
        rel = resolved[rel_id]
        for obj_id in rel.RelatedObjects:           # the SET of objects
            inverse[obj_id]['IsDefinedBy'].append(rel_id)
    elif keyword == 'IFCRELCONTAINEDINSPATIALSTRUCTURE':
        rel = resolved[rel_id]
        for obj_id in rel.RelatedElements:
            inverse[obj_id]['ContainedInStructure'].append(rel_id)
    # ... one branch per relationship keyword

ALWAYS index relationships by the Related... side to populate the inverse on the related objects. The Relating... side is the single anchor.

Pattern: navigate an element to its property sets

element
  -> element.IsDefinedBy            (INVERSE, computed in pass 2)
  -> keep IfcRelDefinesByProperties instances
  -> rel.RelatingPropertyDefinition (an IfcPropertySet or IfcElementQuantity)
  -> read HasProperties / Quantities

On IFC4 and IFC4.3, IsDefinedBy holds ONLY IfcRelDefinesByProperties, so no filter is needed. On IFC2x3, IsDefinedBy ALSO holds IfcRelDefinesByType, so the reader MUST filter by relationship keyword. NEVER expect a direct PropertySets attribute on the element: it does not exist. See ifc-impl-property-extraction.

Pattern: navigate an element to its type

IFC4 / IFC4.3:  element -> element.IsTypedBy   -> IfcRelDefinesByType
                        -> rel.RelatingType    (the IfcWallType, etc.)
IFC2x3:         element -> element.IsDefinedBy -> filter IfcRelDefinesByType
                        -> rel.RelatingType

IsTypedBy has cardinality SET [0:1]: an occurrence has at most one type. IsTypedBy does NOT exist in IFC2x3.

Pattern: navigate an element to its spatial container

element -> element.ContainedInStructure   (INVERSE, SET [0:1])
        -> IfcRelContainedInSpatialStructure
        -> rel.RelatingStructure           (the IfcBuildingStorey, IfcSpace...)

ContainedInStructure has cardinality SET [0:1]: a physical element sits in at most ONE spatial container. An element with an empty set is an orphan. See ifc-impl-spatial-decomposition.

Pattern: navigate an element to its associations

element -> element.HasAssociations         (INVERSE, on IfcObjectDefinition)
        -> IfcRelAssociatesMaterial         -> rel.RelatingMaterial
        -> IfcRelAssociatesClassification   -> rel.RelatingClassification

HasAssociations is defined on IfcObjectDefinition, so both occurrences AND type objects expose it. Filter by relationship keyword to separate material from classification.

Anti-Patterns

Full detail with the failure mechanism is in references/anti-patterns.md. The ones that matter most:

  • Resolving #id references in a single pass. Every forward reference fails. ALWAYS index the whole DATA section first.
  • Expecting INVERSE attributes (IsDefinedBy, ContainedInStructure) to be values in the file. They are NEVER serialized; the reader computes them.
  • Dropping $ or * when mapping tokens to attribute names. Both occupy a positional slot; dropping them shifts every later attribute by one.
  • Skipping the FILE_SCHEMA dispatch and hardcoding one schema. An IFC2X3 file parsed with IFC4 definitions misreads attributes.
  • Looking for the type link in IsDefinedBy on an IFC4 file. The type moved to IsTypedBy; only IFC2x3 mixes them.
  • Treating a #id as a stable identifier across files. Use GlobalId for cross-file identity; #id is local and meaningless outside one file.

Reference Links

This skill

  • references/methods.md: the instance grammar, header entities, positional attribute order for every entity in this skill, and the full INVERSE catalogue with FOR clauses.
  • references/examples.md: a complete verified .ifc file and worked resolution walkthroughs.
  • references/anti-patterns.md: parsing failure modes and their root cause.

Related skills

  • ifc-syntax-step-physical-file: the SPF grammar in full, including string escaping and the HEADER section.
  • ifc-impl-property-extraction: the type-versus-occurrence property override algorithm once the relationships are traversed.
  • ifc-impl-geometry-extraction: reading Representation and ObjectPlacement after the graph is resolved.
  • ifc-core-relationships: the objectified relationship pattern and the Relating versus Related direction convention.

Official sources