2025
OpenCASCADE im Browser mit WebAssembly
Wie man den OCCT-Kernel nach WebAssembly kompiliert und STEP-Dateien direkt im Browser laden, tessellieren und in Three.js darstellen kann.
Warum OCCT im Browser?
OpenCASCADE Technology (OCCT) ist der De-facto-Standard-Kernel für B-Rep-Modellierung in der Open-Source-Welt. FreeCAD, KiCad und dutzende kommerzielle CAD-Systeme bauen darauf auf. Der Kernel kann STEP- und IGES-Dateien lesen und schreiben, Boolesche Operationen auf Solids ausführen, Flächen tessellieren und vieles mehr.
Das Problem: OCCT ist in C++ geschrieben, wiegt kompiliert mehrere hundert Megabyte und erwartet ein Desktop-Betriebssystem. Wer ein webbasiertes Tool bauen will, das STEP-Dateien verarbeitet oder parametrische Geometrie erzeugt, steht vor der Frage: Server-seitig rechnen und Ergebnisse als Mesh zum Client schicken? Oder den Kernel direkt im Browser laufen lassen?
Beide Ansätze haben ihre Berechtigung. Aber für Tools, die schnelle Interaktion brauchen — z.B. Parameteränderungen mit sofortigem visuellen Feedback — ist die Client-seitige Variante oft überlegen. Kein Roundtrip zum Server, keine Latenz, keine Backend-Infrastruktur für Geometrieberechnung.
Emscripten und der Build-Prozess
OCCT lässt sich mit Emscripten nach WebAssembly kompilieren. Das klingt einfacher als es ist. Der vollständige Kernel hat über 10.000 Quelldateien, und nicht alles kompiliert problemlos in einer Umgebung ohne Filesystem, ohne Threads (bzw. nur mit SharedArrayBuffer) und ohne OpenGL-Kontext.
Der pragmatische Ansatz: Nur die Module kompilieren, die man tatsächlich braucht. Für ein Tool, das STEP-Dateien laden und tessellieren soll, sind das im Wesentlichen:
TKernel,TKMath— Basisdatentypen und MathematikTKG2d,TKG3d,TKGeomBase— Geometrische GrundformenTKBRep,TKTopAlgo— B-Rep-Datenstruktur und AlgorithmenTKSTEP,TKXSBase— STEP-ImportTKMesh— Tessellierung
Das CMake-Build-System von OCCT unterstützt das selektive Kompilieren über BUILD_MODULE_*-Flags. In Kombination mit Emscripten sieht ein typischer Build-Aufruf so aus:
emcmake cmake \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_LIBRARY_TYPE=Static \
-DBUILD_MODULE_Draw=OFF \
-DBUILD_MODULE_Visualization=OFF \
-DBUILD_MODULE_ApplicationFramework=OFF \
-DUSE_FREETYPE=OFF \
-DUSE_TK=OFF \
..
emmake make -j$(nproc)
Das Ergebnis sind statische .a-Bibliotheken, die man in den eigenen Emscripten-Build einlinken kann. Die Gesamtgröße des WASM-Binaries lässt sich so auf 8–15 MB drücken (vor gzip), je nachdem welche Module man einschließt.
Die C++/JavaScript-Brücke
Emscripten bietet mit embind eine Möglichkeit, C++-Klassen und -Funktionen für JavaScript zugänglich zu machen. Für ein STEP-Viewer-Tool braucht man im Kern zwei Operationen: eine Datei laden und die resultierende Geometrie als Dreiecksmesh extrahieren.
#include <emscripten/bind.h>
#include <STEPControl_Reader.hxx>
#include <BRepMesh_IncrementalMesh.hxx>
#include <TopExp_Explorer.hxx>
#include <BRep_Tool.hxx>
#include <TopoDS.hxx>
struct MeshData {
std::vector<float> positions;
std::vector<float> normals;
std::vector<uint32_t> indices;
};
MeshData loadStepAndMesh(const std::string& stepData, double deflection) {
// STEP-Daten aus String lesen
STEPControl_Reader reader;
// ... Datei in virtuelles Filesystem schreiben, laden ...
reader.TransferRoots();
TopoDS_Shape shape = reader.OneShape();
// Tessellieren
BRepMesh_IncrementalMesh mesh(shape, deflection);
MeshData result;
// Dreiecke extrahieren
for (TopExp_Explorer ex(shape, TopAbs_FACE); ex.More(); ex.Next()) {
const TopoDS_Face& face = TopoDS::Face(ex.Current());
TopLoc_Location loc;
auto triangulation = BRep_Tool::Triangulation(face, loc);
if (triangulation.IsNull()) continue;
int offset = result.positions.size() / 3;
for (int i = 1; i <= triangulation->NbNodes(); i++) {
gp_Pnt p = triangulation->Node(i).Transformed(loc);
result.positions.push_back(p.X());
result.positions.push_back(p.Y());
result.positions.push_back(p.Z());
}
for (int i = 1; i <= triangulation->NbTriangles(); i++) {
int n1, n2, n3;
triangulation->Triangle(i).Get(n1, n2, n3);
result.indices.push_back(offset + n1 - 1);
result.indices.push_back(offset + n2 - 1);
result.indices.push_back(offset + n3 - 1);
}
}
return result;
}
EMSCRIPTEN_BINDINGS(occt_module) {
emscripten::value_object<MeshData>("MeshData")
.field("positions", &MeshData::positions)
.field("normals", &MeshData::normals)
.field("indices", &MeshData::indices);
emscripten::function("loadStepAndMesh", &loadStepAndMesh);
}
Der deflection-Parameter steuert die Tessellierungsgenauigkeit. Kleinere Werte erzeugen feinere Meshes, aber auch mehr Dreiecke. Für eine interaktive Vorschau im Browser reicht oft ein Wert von 0.1 bis 1.0, je nach Modellgröße.
Darstellung mit Three.js
Auf der JavaScript-Seite nimmt man die Positionsdaten und Indizes entgegen und baut daraus eine Three.js-Geometrie:
import * as THREE from 'three';
async function displayStep(fileBuffer) {
const Module = await initOCCT(); // WASM-Modul laden
// STEP-Datei an C++ übergeben
const stepString = new TextDecoder().decode(fileBuffer);
const meshData = Module.loadStepAndMesh(stepString, 0.5);
// Three.js BufferGeometry aufbauen
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position',
new THREE.Float32BufferAttribute(meshData.positions, 3));
geometry.setIndex(
new THREE.Uint32BufferAttribute(meshData.indices, 1));
geometry.computeVertexNormals();
const material = new THREE.MeshPhongMaterial({
color: 0x8899aa,
shininess: 40,
side: THREE.DoubleSide,
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
// Kamera auf Modell zentrieren
const box = new THREE.Box3().setFromObject(mesh);
const center = box.getCenter(new THREE.Vector3());
const size = box.getSize(new THREE.Vector3()).length();
camera.position.copy(center).add(
new THREE.Vector3(0, 0, size * 1.5)
);
controls.target.copy(center);
}
Performance und Fallstricke
Ein paar Dinge, die in der Praxis aufgefallen sind:
Dateigröße. Das WASM-Binary ist mit 8–15 MB nicht klein. Für ein internes Tool ist das akzeptabel, für eine öffentliche Website muss man es lazy laden und einen Ladebildschirm zeigen. Gzip oder Brotli-Kompression halbiert die Größe nochmal.
Speicherverbrauch. OCCT allokiert intern viel Speicher. Ein mittelgroßes STEP-Modell (eine Baugruppe mit 50–100 Teilen) kann leicht 200–400 MB RAM im Browser belegen. Emscripten's Standard-Heap ist 256 MB — das muss man über -s INITIAL_MEMORY und -s ALLOW_MEMORY_GROWTH anpassen.
Keine Threads (standardmäßig). OCCT nutzt intern TBB oder OpenMP für Parallelisierung. Im Browser gibt es Web Workers und SharedArrayBuffer, aber das erfordert spezielle HTTP-Header (Cross-Origin-Opener-Policy und Cross-Origin-Embedder-Policy). Ohne diese Header läuft alles single-threaded, was bei der Tessellierung großer Modelle spürbar wird.
Kein Filesystem. OCCT erwartet an vielen Stellen Dateipfade. Emscripten bietet ein virtuelles Filesystem (MEMFS), in das man Dateien vor dem Laden schreiben muss. Das funktioniert, ist aber ein zusätzlicher Schritt, den man in der C++-Brücke kapseln sollte.
Wann lohnt sich der Aufwand?
Die WebAssembly-Variante macht Sinn, wenn:
- Benutzer STEP/IGES-Dateien hochladen und sofort sehen wollen, ohne Server-Roundtrip
- Parameteränderungen in Echtzeit auf die Geometrie wirken sollen
- Das Tool offline oder in geschlossenen Netzwerken funktionieren muss
- Man keine Server-Infrastruktur für Geometrieberechnung betreiben will
Für reine Viewing-Zwecke ohne B-Rep-Operationen gibt es leichtere Alternativen — z.B. STEP-Dateien serverseitig einmal tessellieren und als glTF ausliefern. Aber sobald man Geometrie im Browser bearbeiten will (Schnitte, Boolesche Operationen, Maßabfragen), führt kaum ein Weg an OCCT+WASM vorbei.