SuperAPI AI Directory

ifcos-impl-relationships

Coding & development

View repository

Updated 10-15 22:17

About this skill

IFC Relationship Management with IfcOpenShell

Quick Reference

Decision Tree: Which Relationship Do I Need?

What are you connecting?
│
├── Spatial hierarchy (Project → Site → Building → Storey)?
│   └── Use aggregate.assign_object
│       └── Creates IfcRelAggregates
│
├── Element inside a spatial container (Wall in Storey)?
│   └── Use spatial.assign_container
│       └── Creates IfcRelContainedInSpatialStructure
│
├── Element referenced in (but not contained by) a spatial structure?
│   └── Use spatial.reference_structure
│       └── Creates IfcRelReferencedInSpatialStructure (IFC4+ only)
│
├── Type assignment (WallType → Wall occurrences)?
│   └── Use type.assign_type
│       └── Creates IfcRelDefinesByType
│
├── Property set on element?
│   └── Use pset.add_pset (creates IfcRelDefinesByProperties automatically)
│
├── Material on element?
│   └── Use material.assign_material
│       └── Creates IfcRelAssociatesMaterial
│
├── Opening/void in element (hole in wall)?
│   └── Use void.add_opening
│       └── Creates IfcRelVoidsElement
│
├── Filling an opening (door in hole)?
│   └── Use void.add_filling
│       └── Creates IfcRelFillsElement
│
├── Nesting (component attached to host at connection point)?
│   └── Use nest.assign_object
│       └── Creates IfcRelNests
│
├── Physical assembly (stair = flights + landings + railings)?
│   └── Use aggregate.assign_object
│       └── Creates IfcRelAggregates
│
└── Grouping (logical set, e.g. "External Walls")?
    └── Use group.assign_group
        └── Creates IfcRelAssignsToGroup

Critical Warnings

  • ALWAYS use ifcopenshell.api.run() to create relationships. NEVER create relationship entities directly with model.create_entity("IfcRelAggregates", ...) — the API handles GlobalId generation, OwnerHistory, placement recalculation, and cleanup of prior relationships.
  • NEVER assign attributes directly to set relationship properties. IFC uses objectified relationships — relationships are first-class entities.
  • ALWAYS check model.schema before using schema-specific inverse attributes. IsTypedBy exists only in IFC4+; in IFC2X3, type relations are in IsDefinedBy.
  • NEVER contain an element in multiple spatial structures. Each element has exactly ONE spatial container via IfcRelContainedInSpatialStructure. Use spatial.reference_structure for secondary references.
  • ALWAYS use ifcopenshell.util.element for querying relationships. It handles version differences internally.

Relationship Entity Hierarchy

IfcRelationship (abstract)
├── IfcRelDecomposes (abstract)
│   ├── IfcRelAggregates          ← aggregate.assign_object
│   ├── IfcRelNests               ← nest.assign_object
│   ├── IfcRelProjectsElement
│   └── IfcRelVoidsElement        ← void.add_opening
├── IfcRelAssigns (abstract)
│   ├── IfcRelAssignsToGroup      ← group.assign_group
│   └── ... (Actor, Control, Process, Product, Resource)
├── IfcRelAssociates (abstract)
│   ├── IfcRelAssociatesMaterial  ← material.assign_material
│   └── ... (Classification, Document, Library, etc.)
├── IfcRelConnects (abstract)
│   ├── IfcRelContainedInSpatialStructure  ← spatial.assign_container
│   ├── IfcRelFillsElement        ← void.add_filling
│   └── ... (Ports, Structural, Space Boundary, etc.)
├── IfcRelDeclares (IFC4+)
└── IfcRelDefines (abstract)
    ├── IfcRelDefinesByProperties  ← pset.add_pset (automatic)
    └── IfcRelDefinesByType        ← type.assign_type

Version Differences: Relationship Splits Between IFC2X3 and IFC4

Inverse Attribute Changes

Query IFC2X3 IFC4 / IFC4X3
Type of element element.IsDefinedBy → filter for IfcRelDefinesByType element.IsTypedBy (dedicated inverse)
Type's inverse type.ObjectTypeOf type.Types (renamed)
Aggregation children obj.IsDecomposedBy → returns IfcRelDecomposes obj.IsDecomposedBy → returns IfcRelAggregates only
Nesting children obj.IsDecomposedBy → filter for IfcRelNests obj.IsNestedBy (dedicated inverse, IFC4+)

Entity Classification Changes

Aspect IFC2X3 IFC4+
IfcRelVoidsElement parent IfcRelDecomposes IfcRelDecomposes (unchanged)
IfcRelProjectsElement parent IfcRelDecomposes IfcRelDecomposes (unchanged)
IfcRelReferencedInSpatialStructure Does not exist Available
IfcRelDeclares Does not exist Available
IfcRelDefinesByObject Does not exist Available
IfcRelAssignsToGroupByFactor Does not exist Available
IfcRelInterferesElements Does not exist Available
IfcRelPositions Does not exist IFC4X3 only

Valid Spatial Containers Per Version

Container Entity IFC2X3 IFC4 IFC4X3
IfcSite YES YES YES
IfcBuilding YES YES YES
IfcBuildingStorey YES YES YES
IfcSpace YES YES YES
IfcExternalSpatialElement — YES YES
IfcFacility (IfcBridge, IfcRoad, etc.) — — YES
IfcFacilityPart (IfcRoadPart, etc.) — — YES

Material Types Per Version

Material Concept IFC2X3 IFC4+
IfcMaterial YES YES
IfcMaterialLayerSet YES YES
IfcMaterialLayerSetUsage YES YES
IfcMaterialProfileSet — YES
IfcMaterialProfileSetUsage — YES
IfcMaterialConstituentSet — YES
IfcMaterialList YES YES (deprecated)

Type Entity Changes

IFC2X3 IFC4+ Replacement
IfcDoorStyle IfcDoorType
IfcWindowStyle IfcWindowType

Essential Patterns

Pattern 1: Build Spatial Hierarchy (Aggregation)

# IfcOpenShell: IFC4
import ifcopenshell
import ifcopenshell.api

model = ifcopenshell.api.run("project.create_file", version="IFC4")
project = ifcopenshell.api.run("root.create_entity", model,
    ifc_class="IfcProject", name="My Project")
ifcopenshell.api.run("unit.assign_unit", model)

site = ifcopenshell.api.run("root.create_entity", model,
    ifc_class="IfcSite", name="Site")
building = ifcopenshell.api.run("root.create_entity", model,
    ifc_class="IfcBuilding", name="Building")
storey = ifcopenshell.api.run("root.create_entity", model,
    ifc_class="IfcBuildingStorey", name="Ground Floor")

# Each call creates an IfcRelAggregates
ifcopenshell.api.run("aggregate.assign_object", model,
    relating_object=project, products=[site])
ifcopenshell.api.run("aggregate.assign_object", model,
    relating_object=site, products=[building])
ifcopenshell.api.run("aggregate.assign_object", model,
    relating_object=building, products=[storey])

Pattern 2: Contain Elements in Spatial Structure

# IfcOpenShell: all schema versions
wall = ifcopenshell.api.run("root.create_entity", model,
    ifc_class="IfcWall", name="Wall 001")

# Creates IfcRelContainedInSpatialStructure
ifcopenshell.api.run("spatial.assign_container", model,
    relating_structure=storey, products=[wall])

Pattern 3: Assign Type to Occurrences

# IfcOpenShell: IFC4
wall_type = ifcopenshell.api.run("root.create_entity", model,
    ifc_class="IfcWallType", name="Standard Wall 200mm")

# Creates IfcRelDefinesByType
ifcopenshell.api.run("type.assign_type", model,
    related_objects=[wall], relating_type=wall_type)

Pattern 4: Add Property Set

# IfcOpenShell: all schema versions
# pset.add_pset creates IfcRelDefinesByProperties automatically
pset = ifcopenshell.api.run("pset.add_pset", model,
    product=wall, name="Pset_WallCommon")
ifcopenshell.api.run("pset.edit_pset", model,
    pset=pset, properties={
        "IsExternal": True,
        "FireRating": "REI120",
        "ThermalTransmittance": 0.24
    })

Pattern 5: Assign Material

# IfcOpenShell: all schema versions
material = ifcopenshell.api.run("material.add_material", model,
    name="Concrete")

# Creates IfcRelAssociatesMaterial
ifcopenshell.api.run("material.assign_material", model,
    products=[wall], material=material)

Pattern 6: Create Opening and Fill It

# IfcOpenShell: all schema versions
opening = ifcopenshell.api.run("root.create_entity", model,
    ifc_class="IfcOpeningElement", name="Door Opening")

# Creates IfcRelVoidsElement
ifcopenshell.api.run("void.add_opening", model,
    opening=opening, element=wall)

door = ifcopenshell.api.run("root.create_entity", model,
    ifc_class="IfcDoor", name="Door 001")

# Creates IfcRelFillsElement
ifcopenshell.api.run("void.add_filling", model,
    opening=opening, element=door)

Pattern 7: Nest Components

# IfcOpenShell: all schema versions
sink = ifcopenshell.api.run("root.create_entity", model,
    ifc_class="IfcSanitaryTerminal", name="Kitchen Sink")
faucet = ifcopenshell.api.run("root.create_entity", model,
    ifc_class="IfcValve", name="Faucet")

# Creates IfcRelNests: faucet is nested in sink
ifcopenshell.api.run("nest.assign_object", model,
    related_objects=[faucet], relating_object=sink)

Querying Relationships

Version-Safe Queries Using ifcopenshell.util.element

# IfcOpenShell: all schema versions
import ifcopenshell.util.element

# Get spatial container of element
container = ifcopenshell.util.element.get_container(wall)

# Get type of element
element_type = ifcopenshell.util.element.get_type(wall)

# Get all occurrences of a type
occurrences = ifcopenshell.util.element.get_types(wall_type)

# Get property sets
psets = ifcopenshell.util.element.get_psets(wall)
# Returns: {"Pset_WallCommon": {"IsExternal": True, ...}}

# Get material
material = ifcopenshell.util.element.get_material(wall)

# Get full decomposition tree
children = ifcopenshell.util.element.get_decomposition(building)

# Get aggregate parent
parent = ifcopenshell.util.element.get_aggregate(storey)

Direct Inverse Attribute Queries

# IfcOpenShell: IFC4+
# Aggregation children
for rel in building.IsDecomposedBy:
    for child in rel.RelatedObjects:
        print(f"Child: {child.Name}")

# Aggregation parent
for rel in storey.Decomposes:
    print(f"Parent: {rel.RelatingObject.Name}")

# Spatial containment
for rel in storey.ContainsElements:
    for elem in rel.RelatedElements:
        print(f"Contains: {elem.is_a()} - {elem.Name}")

# Element's container
for rel in wall.ContainedInStructure:
    print(f"In: {rel.RelatingStructure.Name}")

# Type assignment (IFC4+)
for rel in wall.IsTypedBy:
    print(f"Type: {rel.RelatingType.Name}")

# Openings
for rel in wall.HasOpenings:
    opening = rel.RelatedOpeningElement
    for fill in opening.HasFillings:
        print(f"Filled by: {fill.RelatedBuildingElement.Name}")

# Property sets
for rel in wall.IsDefinedBy:
    if rel.is_a("IfcRelDefinesByProperties"):
        pset = rel.RelatingPropertyDefinition
        print(f"PSet: {pset.Name}")

# Material
for rel in wall.HasAssociations:
    if rel.is_a("IfcRelAssociatesMaterial"):
        print(f"Material: {rel.RelatingMaterial.is_a()}")

# Nesting (IFC4+)
for rel in sink.IsNestedBy:
    for child in rel.RelatedObjects:
        print(f"Nested: {child.Name}")

IFC2X3 Inverse Attribute Differences

# IfcOpenShell: IFC2X3 ONLY
# Type assignment: NO IsTypedBy inverse in IFC2X3
for rel in wall.IsDefinedBy:
    if rel.is_a("IfcRelDefinesByType"):
        print(f"Type: {rel.RelatingType.Name}")

# Aggregation/Nesting: shared inverse in IFC2X3
for rel in building.IsDecomposedBy:
    # Returns IfcRelDecomposes instances (includes both aggregation and nesting)
    if rel.is_a("IfcRelAggregates"):
        for child in rel.RelatedObjects:
            print(f"Aggregated: {child.Name}")
    elif rel.is_a("IfcRelNests"):
        for child in rel.RelatedObjects:
            print(f"Nested: {child.Name}")

Removing Relationships

# IfcOpenShell: all schema versions

# Remove spatial containment
ifcopenshell.api.run("spatial.unassign_container", model, products=[wall])

# Remove aggregation
ifcopenshell.api.run("aggregate.unassign_object", model, products=[storey])

# Remove type assignment
ifcopenshell.api.run("type.unassign_type", model, related_objects=[wall])

# Remove material assignment
ifcopenshell.api.run("material.unassign_material", model, products=[wall])

# Remove nesting
ifcopenshell.api.run("nest.unassign_object", model, related_objects=[faucet])

# Remove property set
ifcopenshell.api.run("pset.remove_pset", model, product=wall, pset=pset)

# Remove spatial reference
ifcopenshell.api.run("spatial.dereference_structure", model,
    products=[wall], relating_structure=other_storey)

Version-Safe Type Query Pattern

# IfcOpenShell: all schema versions
def get_element_type_safe(element):
    """Get element type across all IFC versions."""
    schema = element.wrapped_data.file.schema
    if schema == "IFC2X3":
        for rel in element.IsDefinedBy:
            if rel.is_a("IfcRelDefinesByType"):
                return rel.RelatingType
    else:
        for rel in element.IsTypedBy:
            return rel.RelatingType
    return None

# PREFERRED: use ifcopenshell.util.element.get_type() which handles this internally
import ifcopenshell.util.element
element_type = ifcopenshell.util.element.get_type(wall)

Reference Links