Importing IFC Geometry

25 January 2026

In order for my 3D modeling application to be useful in the workflow of an architect, I need to be able to import and export document interchange formats such as IFC and STEP.

For that reason I wrote an IFC reader, which consists of an EXPRESS parser, an EXPRESS runtime, a STEP parser, a STEP reader and IFC-specific logic for iterating over the hierarchy and e.g. extracting all geometry.

Part of the imported geometry, currently only importing IfcFacetedBrep.

See the following code snippet for the API of iterating over the IFC file.

I only implemented converting the geometry for an IfcFacetedBrep right now, because that is the same geometry representation as a polygon mesh: a boundary representation with straight edges and planar faces. See the IFC specification entry for IfcFacetedBrep. In the case of a face with not just a boundary loop but also an inner loop, which represents a hole in the face, I have to add an edge that changes the loop from two loops two one.

This case is also not handled yet.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
[[nodiscard]] StatusOr<mesh::EditMesh> ifc_object_to_mesh(const express::runtime::Entity& root,
                                                          express::runtime::Runtime& runtime) {
  mesh::EditMesh out;

  std::stack<std::reference_wrapper<const ert::Entity>> stack;
  stack.emplace(root);

  while (!stack.empty()) {
    const ert::Entity& current = stack.top();
    stack.pop();

    if (current.is_type("IfcRelAggregates")) {
      for (const ert::Entity& related_object :
           current.get_collection_attribute<ert::Entity>("RelatedObjects")) {
        stack.emplace(related_object);
      }
    } else if (current.is_type("IfcObjectDefinition")) {
      for (const ert::Entity& decomposed_by :
           current.get_inverse_attribute("IsDecomposedBy", runtime)) {
        stack.emplace(decomposed_by);
      }

      if (current.is_type("IfcSpatialElement")) {
        for (const ert::Entity& element :
             current.get_inverse_attribute("ContainsElements", runtime)) {
          assert(element.is_type("IfcRelContainedInSpatialStructure"));

          for (const auto& related_element :
               element.get_collection_attribute<ert::Entity>("RelatedElements")) {
            stack.emplace(related_element);
          }
        }
      }

      if (current.is_type("IfcProduct")) {
        // a product can have a placement
        glm::mat4x4 product_local_to_world_transform =
            ifc_product_get_local_to_world_transform(current);

        // a product can have a representation
        if (const ert::Entity* product_representation =
                current.get_attribute<ert::Entity>("Representation")) {
          assert(product_representation->is_type("IfcProductRepresentation"));

          // iterate over representations in product representation
          for (const auto& representation :
               product_representation->get_collection_attribute<ert::Entity>("Representations")) {
            assert(representation.is_type("IfcRepresentation"));

            // IfcRepresentation can either be a geometric representation or topology representation

            // iterate over items in representation
            for (const auto& item : representation.get_collection_attribute<ert::Entity>("Items")) {
              assert(item.is_type("IfcRepresentationItem"));

              if (item.is_type("IfcFacetedBrep")) {
                StatusOr<IfcFacetedBrep> result = read_ifc_faceted_brep(item, runtime);
                if (!result.has_value()) {
                  return error(result.error());
                }

                // convert to mesh
                mesh::EditMesh brep_mesh = ifc_faceted_brep_to_mesh(result.value());
                const glm::mat4x4 flip_y_z{1, 0, 0, 0, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 1};

                mesh::edit_mesh_transform_vertices(brep_mesh,
                                                   flip_y_z * product_local_to_world_transform);
                out.insert_mesh(brep_mesh);
              }
            }
          }
        }
      }
    }
  }
  return out;
}