| name | ifc-impl-reading-parsing |
| description | Use when reading or parsing an IFC STEP physical file: indexing instances, resolving #id references, dispatching on FILE_SCHEMA, reading positional attributes, or traversing the entity graph from an element to its relationships. Prevents resolving references in a single pass and failing on forward references, expecting INVERSE attributes to be stored in the file, miscounting positional attribute slots when $ or * appear, skipping the FILE_SCHEMA dispatch, and looking for a wall's properties as a direct attribute. Covers the two-pass parse, the instance line grammar, the $ and * tokens, building the inverse index, and navigating IsDefinedBy, IsTypedBy, ContainedInStructure, and HasAssociations. Keywords: IFC parser, parse IFC file, read .ifc file, STEP physical file, SPF, FILE_SCHEMA, two-pass parse, forward reference, #id reference, instance index, INVERSE attribute, IsDefinedBy, IsTypedBy, ContainedInStructure, HasAssociations, IfcRelDefinesByProperties, positional attributes, dollar token, asterisk token, property set not found, element has no properties, reference not resolving, KeyError on entity id, how do I read an IFC file, what is in a DATA section, why is my IFC reference null.
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires IFC IFC2x3,IFC4,IFC4.3. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
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:
- Pass 1, index: scan every
#<id>=KEYWORD(...); line and store the id,
the entity keyword, and the raw attribute tokens. Resolve NOTHING.
- 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.
index = {}
for line in data_section_lines:
inst_id, keyword, raw_tokens = split_instance_line(line)
index[inst_id] = (keyword, raw_tokens)
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)
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))
for rel_id, (keyword, _) in index.items():
if keyword == 'IFCRELDEFINESBYPROPERTIES':
rel = resolved[rel_id]
for obj_id in rel.RelatedObjects:
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)
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