FreeCAD in your browser — magik.net
magik.net
FreeCAD in your browser
Posted July 7, 2026
It started with a Hacker News thread:<br>LibreCAD, the whole Qt desktop application, compiled to WebAssembly and running in a tab.<br>magik shipped that, then OpenSCAD, and the pattern looked like a recipe — Qt for<br>WebAssembly, JSPI, a static dependency stack, and an honest audit of whatever the app assumed<br>about threads, filesystems and OpenGL. The obvious next question was how far it scales. So here<br>is FreeCAD: parametric 3D CAD, sketch-constrained solid<br>modeling, an embedded Python, a dozen-plus workbenches. It is roughly an order of magnitude<br>larger than the other two — about 1.5 million lines of C++ and 700 thousand of<br>Python — and it now runs in a browser tab.
The port took ~4 days from the HackerNews thread to the current state. The implementation was done<br>almost entirely by Fable, and was a way for me to stress-test Fable capabilities in really big complex<br>projects. If that sounds like a large claim, it is checkable. Every prompt over those four<br>days is reproduced verbatim near the end of this post — all forty-eight of<br>them — and the complete session transcripts, every sub-agent and every workflow, are<br>published as a browseable archive.
Launch FreeCAD →
First load is ~96 MB compressed (Brotli) — this is a big program and<br>I won’t pretend otherwise; your browser caches it afterward. Needs a recent Chromium-based<br>browser (Chrome/Edge 137+): the port relies on WebAssembly JSPI, which Firefox and Safari<br>don’t ship yet. When it boots, a demo document (a box with a cylindrical cut) opens in a<br>live 3D viewport.
What’s inside
the rest of this post is a technical note written entirely by Fable to answer the most burning questions, but it is an AI post from here on
FreeCAD is not one program; it’s a platform. Under the Qt GUI sits<br>OpenCASCADE (OCCT), the industrial B-rep geometry kernel; Coin3D with<br>Quarter , an Open Inventor scene graph driving the 3D view; a full embedded<br>CPython 3.14 ; the PySide6 /shiboken6 Qt bindings (so Python code can<br>build Qt UI); and this time, cross-compiled specifically for the FEM workbench, a subset of<br>VTK 9.3 and the Salome SMESH mesher. On top of that: 17+ workbenches<br>— Part, Sketcher, PartDesign, Draft, Spreadsheet, Measure, Surface, Import,<br>Mesh/MeshPart/Points/Inspection/Robot, TechDraw, Assembly, CAM, BIM, FEM, Material and Start.<br>All of it compiles statically into one 196 MB WebAssembly module next to Qt 6.11.
Two pieces of that had never been done before in a browser, as far as I can tell: PySide6 and<br>pivy (the Coin bindings) running under wasm at all, and OCCT+Coin+CPython+PySide+VTK+SMESH linked<br>together into a single module. The rest of this post is how it got there, in the order it<br>happened.
The build, in sequence
This section is long on purpose — the request was every fix and every direction, in order,<br>and the specificity is the point. If you just want to poke at the app, the launch button is above.
0 · Toolchain: a Qt you have to build yourself
LibreCAD could use a prebuilt Qt-for-wasm. FreeCAD can’t, for one reason: JSPI. FreeCAD is<br>saturated with modal dialogs — 185 C++ exec() sites and 156 more in Python<br>— and blocking Python code cannot be mechanically rewritten into callbacks. The only way to<br>keep QDialog::exec() semantics is to suspend the wasm stack while the dialog<br>is open, which is exactly what WebAssembly JSPI (JavaScript Promise Integration) does. That<br>requires -feature-wasm-jspi, which requires a Qt built from source. So the toolchain<br>under /opt/toolchains is Qt 6.11.1 compiled for wasm with JSPI and native wasm<br>exceptions, on Emscripten 4.0.12, sitting next to hand-built static prefixes for OCCT, CPython,<br>ICU, Boost, Xerces, {fmt} and yaml-cpp.
1 · The kernel, headless first
The discipline that made a project this size tractable was kernel-first: prove OCCT +<br>CPython + the FreeCAD App layer run in wasm before touching a single pixel of GUI. FreeCAD’s<br>C++ modules are Python extension modules (PyMOD_INIT_FUNC), normally<br>dlopened at runtime. There is no dlopen in a wasm module, so every module<br>is statically linked and registered on the interpreter’s inittab with<br>PyImport_AppendInittab before Py_Initialize. With the home path, the<br>Python environment and a NODERAWFS build for node-side testing sorted, FreeCADCmd ran<br>headless — open an .FCStd, recompute, round-trip STEP — first in node,<br>then in the browser. That was the first thing that shipped, and it’s a product on its own: a<br>scriptable CAD kernel in a tab.
2 · Linking the GUI, and lying to OpenGL
Getting the full GUI target to link was mostly bookkeeping — a shared inittab, a<br>QProcess stand-in (no child processes in a tab), guarding the Start module’s<br>timezone/process assumptions. The interesting part was OpenGL. FreeCAD renders through Coin3D,<br>which is honest 1990s fixed-function OpenGL: matrix stacks,...