This year, I worked on a generated C API, native WebAssembly builds and a TypeScript library for IfcOpenShell as part of Google Summer of Code 2026, under the BRL-CAD organisation.
The main goal was to make the IfcOpenShell C++ core easier to use from other languages, and make it possible to run IfcOpenShell directly on the browser without Pyodide or a Python runtime.
Relevant links:
Since wrapping up last year’s GSoC project, I’ve continued being active in the OSArch community, mostly maintaining the IfcTester web app and following new developments in IfcOpenShell.
I’m a big performance nerd and perfectionist, so it always bugged me that using IfcOpenShell in a browser meant running ifcopenshell-python through Pyodide. Pyodide is an amazing project and it works, but downloading and using a heavy Python runtime has significant overhead both in terms of network, startup time and runtime speed.
I’m also a big fan of Zig, and earlier this year I tried building the IfcOpenShell core using the Zig build system as a proof-of-concept. Zig also has great support for cross-compiling to WebAssembly, so I could experiment with a native browser build at the same time.
This resulted in the zig-build branch, a small TypeScript package called ifczero, and a quick web demo. The experiment eventually had working WASM builds and generated JavaScript bindings.
While writing the Zig bindings, I realized a broader issue: IfcOpenShell did not have a C API. Zig can easily call C, but directly interfacing with a large C++ API is much more difficult. The same problem would affect bindings for Rust, JavaScript and many other languages.
This year, I didn’t have a plan to apply for GSoC again. However the community response to my experiments was encouraging, and this was a problem I found very interesting from an engineering perspective. With recommendations from Thomas and Dion, I applied to work on “IfcOpenShell C API, Improved WASM builds, and migration of high-level APIs from Python”, under the guidance of IfcOpenShell’s founder, Thomas Krijnen.
The C API in my Zig experiment was handwritten, which doesn’t scale well to a project as large as IfcOpenShell. So, I spent the first part of GSoC building a generator which could discover the required C++ APIs using Clang and produce a C-compatible interface for them.
Most of this work happened across the bindgen-v1, bindgen-v2 and bindgen-v3 branches. The early versions first covered ifcparse, followed by the geometry APIs in ifcgeom. The generator went through many redesigns and optimizations as I explored the APIs, ownership rules and what information could be safely inferred from the C++ code.
The final version uses small annotated C++ specification files to select what should be exported and describe things such as ownership or custom adapters. It then generates the public C header and the C++ bridge implementation. The same information is also reused by the WASM and TypeScript targets, so these bindings don’t need to be maintained as separate handwritten APIs.
Development initially targeted v0.8.0, and was later rebased on datamodel-v1.0, which contained a major refactor of the IfcOpenShell C++ core. Near the end of GSoC, I had to rebase the work again on v0.9.0, so the final branch had to be ported to the new API (thanks to Claude for this).
After getting the low-level generator working, I spent a lot of time experimenting with porting functions from ifcopenshell.api and ifcopenshell.util to C++. This would make the same high-level functionality available to Python, JavaScript and any future bindings instead of keeping most of it inside ifcopenshell-python.
A lot of LLM-driven iteration was involved here. A rewrite of this size would otherwise take many months. To check behaviour against the Python implementation, I also created a local fork of ifcopenshell-python which used the generated C API and a draft CPython extension instead of SWIG. The existing Python tests were run while porting each set of APIs, which helped catch many differences and bugs upfront.
This experiment became very large, and passing the existing tests still does not guarantee that every edge case behaves exactly like the Python implementation (and sometimes it shouldn’t!). Some of the C++ interfaces were also too closely modelled after Python and need more thought around type safety and API design.
After discussing this with Thomas, we decided to keep the high-level ifcapi work out of the final GSoC pull request. It is still available in my development branches, but needs to be reviewed, refined and split into much smaller pieces before it can be considered for upstream.
The next part was getting IfcOpenShell to compile directly to WebAssembly and expose the generated C API without Pyodide. The initial native WASM target was added here.
One of the trickier parts was supporting IfcOpenShell’s dynamic plugins on the web. Schemas, geometry kernels and serializers are loaded as separate libraries, and this behaviour needs to work in a browser as well. Emscripten has first-class support for dynamic linking, allowing the main module and its plugins to be compiled as separate WASM files.
The main WASM binary is roughly 6 MiB before transfer compression, excluding the dynamic dependencies. With the compression normally applied by CDNs, it was about ~1 MiB to download in my tests. Plugins can be loaded lazily when needed instead of putting every schema, geometry kernel and serializer into the initial download.
Now, the WASM binary itself only exposes low-level C functions, so it still needed a usable JavaScript interface. I created @ifcopenshell-js/wasm for the generated bindings and WASM assets, along with @ifcopenshell-js/web for the higher-level TypeScript API. The wrapper covers files, entities, attributes, geometry iteration, meshes, settings, serializers and native resource cleanup. Both browser and Node are supported by the same package.
The first version of these packages was added in this commit.
Note that the npm organization will also likely change to the official @ifcopenshell namespace once that happens.
Of course, I had to build another demo :)
IFCZero is an AI copilot for IFC workflows built using the new WASM and TypeScript library. It can load and render an IFC model, inspect its data and let an AI agent interact with it through tools. The agent loop is loosely based on the Pi coding agent, and uses OpenCode’s deepseek-v4-flash-free model by default, with the option to bring-your-own-key.
For geometry, it uses the new Manifold kernel, which is MUCH faster than OpenCASCADE for generating meshes to render with Three.js.
On my Mac, IFCZero cold-starts with the full IfcOpenShell runtime in roughly ~2 seconds, with most of that time spent downloading files. For comparison, ifctester.org, which uses the Pyodide-based build, takes over 30 seconds to cold-start and around 10 seconds on a warm start on the same machine. Feel free to try it yourself!
As GSoC approached its final weeks, the bindgen-v3 branch had become enormous. It contained the generator, multiple binding targets, the high-level C++ ports, a modified Python package, WASM infrastructure, TypeScript packages, tests and various experiments. The diff was well over 100,000 lines(!) and would have been extremely difficult to review as a single pull request.
So, I extracted sayan/bindgen-1, a cleaned-up branch containing only the binding generator and its C, WASM and TypeScript targets. The CPython backend and high-level ifcapi work are intentionally excluded here.
The final work is submitted in PR #9325. It’s still a large change, especially because generated files are committed, but it is now split into a clean series of commits and much more review-friendly.
This project ended up being much more ambitious than I expected when I started experimenting with Zig earlier this year. I experimented and learned a lot about Clang’s AST, C ABI, ownership across language boundaries, Emscripten, and WASM. While the project is not yet a “polished” deliverable (where I’d like it to be), I’m still happy with the progress made and hope that it continues into a native IfcOpenShell on the web!
Thanks to my mentor Thomas Krijnen for his guidance and for pushing me to think more carefully about the architecture, and to both Thomas and Dion Moult for encouraging me to apply again. I am also grateful to everyone in the OSArch community who tested the demos, shared feedback or showed interest in the project.