Snippet Wednesday - Working with IFC

Hi everyone,

I thought I’d share some thoughts and snippets on the subject of IFC. IFC is an iso-certified, vendor-neutral and international standard for digital descriptions in the built industry. Or in short; it’s a format where we can combine data and geometry! More and more vendors are building IFC-imports, so we like to acknowledge IFC as the spider-in-the-web for many workflows. This is why we created our IFC-viewer (blog).

So why are we such a fan of IFC?

  • You can add data to native VIKTOR geometries
  • You can reintegrate your tool in exisiting workflows
  • All your workflows talk the same language

How to work with IFC in VIKTOR?

Aside from inspecting your IFC in VIKTOR, you can use the power of programming to inspect, modify or build your IFC from scratch. For that, we use the IFCOpenShell package. This is a very powerful package, that does it all! It can generate geometry, and add parameter sets to the object.

How to integrate IFCOpenShell in my VIKTOR app?

You can use any version of IFCOpenShell and use it in your VIKTOR app.

However, we love to use the capabilities of IFCOpenShell’s API and often find it necessary to do so. In that case, bpy and mathutils are required. If you want to use the API, please use the following requirements:

  • ifcopenshell==0.7.0.240627
  • bpy
  • mathutils
  • System dependencies:
    • packages=["libxxf86vm-dev", "libxfixes-dev", "libxi-dev", "libxkbcommon-dev", "libgl1"]

A simple example

Inspired by this documentation we can create the following example. It creates a simple plane, using the add_mesh_representation function. This function expects faces, vertices and edges to create the mesh representation. Then we use ifcopenshell.api.run to run pset.add_pset, which adds parameter set to the element.

Code:

import viktor as vkt
from ifcopenshell.api import run
from ifcopenshell.api import geometry, context, project, root, unit

@vkt.IFCView("Geometry + Data!")
def create_geometry_with_data(self, **kwargs):
    # Create an IFC project and its root
    model = project.create_file()
    root.create_entity(model, ifc_class="IfcProject")
    unit.assign_unit(model)
    
    # Add contexts
    model3d = context.add_context(model, context_type="Model")
    body = context.add_context(
        model,
        context_type="Model",
        context_identifier="Body",
        target_view="MODEL_VIEW",
        parent=model3d
    )
    
    # Create the entity and mesh representation
    element = root.create_entity(model, ifc_class="IfcFurniture")
    geometry.edit_object_placement(model, product=element)
    representation = geometry.add_mesh_representation(
        model,
        context=body,
        vertices=[[(-5, 5, 0), (-5, -5, 0), (5, -5, 0), (5, 5, 0)]],
        edges=[(0, 1), (1, 2), (2, 3), (0, 3)],
        faces=[[[2, 1, 0], [0, 3, 2]]]
    )
    geometry.assign_representation(model, product=element, representation=representation)
    pset = run("pset.add_pset", model, product=element, name="Custom Properties")
    run("pset.edit_pset", model, pset=pset, properties={"foo": "bar"})
    
    viktor_file = vkt.File()
    model.write(viktor_file.source)
    return vkt.IFCResult(viktor_file)

3DM to IFC

An example of a nice implementation is to save a Rhino file and fill it with data. This results in a usable IFC file with your own data. And potentially load it in Revit, another VIKTOR app, or whatever other tool you are using!

3DM to IFC
@IFCView("IFC")
def create_ifc_from_rhino3dm(self, params, workspace_id, **kwargs):
    if not params.filefield.file:
        return UserError("No file has been uploaded! Please upload a rhino file")
    # Load the Rhino file
    rhino_file = rhino3dm.File3dm.FromByteArray(params.filefield.file.getvalue_binary())

    logger.info(f"Loaded Rhino file. Number of objects: {len(rhino_file.Objects)}")

    # Create an IFC project and its root
    model = project.create_file()
    root.create_entity(model, ifc_class="IfcProject")
    unit.assign_unit(model)

    # Add contexts
    model3d = context.add_context(model, context_type="Model")
    body = context.add_context(
        model,
        context_type="Model",
        context_identifier="Body",
        target_view="MODEL_VIEW",
        parent=model3d
    )

    # Create our element with an object placement.
    for obj in rhino_file.Objects:
        if isinstance(obj.Geometry, rhino3dm.Mesh):
            mesh = obj.Geometry
            vertices = [[(v.X, v.Y, v.Z) for v in mesh.Vertices]]
            faces = [[face for face in mesh.Faces]]
            # Initialize a set to store unique edges
            edges = set()

            # Loop through each face to get its edges
            for i, face in enumerate(mesh.Faces):
                # Check if it's a triangular or quadrilateral face
                if len(face) == 3:
                    # Triangular face (3 edges)
                    edges.add(tuple(sorted([face[0], face[1]])))
                    edges.add(tuple(sorted([face[1], face[2]])))
                    edges.add(tuple(sorted([face[2], face[1]])))
                elif len(face) == 4:
                    # Quadrilateral face (4 edges)
                    edges.add(tuple(sorted([face[0], face[1]])))
                    edges.add(tuple(sorted([face[1], face[2]])))
                    edges.add(tuple(sorted([face[2], face[3]])))
                    edges.add(tuple(sorted([face[3], face[0]])))
                else:
                    raise UserError(f"ERROR WITH {face}")
            element = root.create_entity(model, ifc_class="IfcWall")
            geometry.edit_object_placement(model, product=element)
            representation = geometry.add_mesh_representation(
                model,
                context=body,
                vertices=vertices,
                edges=list(edges),
                faces=faces
            )
            geometry.assign_representation(model, product=element, representation=representation)
            pset = run("pset.add_pset", model, product=element, name="Custom Properties")
            run(
                "pset.edit_pset",
                model,
                pset=pset,
                properties={
                    "vertices_count": len(vertices[0]),
                    "face_count": len(faces[0])
                }
            )
        viktor_file = vkt.File()
        model.write(viktor_file.source)    
        return IFCResult(viktor_file)
3 Likes