Extend with C++
Decision tree across additionalCppCode, additionalCppFiles, and additionalBindCode mechanisms.
OCCT exposes hundreds of free functions, POD structs, and helper utilities the bindgen does not (and cannot) reach automatically. When you need one of them — or want an ergonomic wrapper around an OCCT API — the YAML config gives you three mechanisms:
additionalCppCode— inline C++ embedded directly in the YAML scalar.additionalCppFiles— one or more.cppfiles concatenated ontoadditionalCppCode.mainBuild.additionalBindCode— per-build rawEMSCRIPTEN_BINDINGS(...)registrations.
Decision tree
Are you adding new C++ implementation code (wrapper class, free function, POD)?
├── YES → Will the impl grow beyond ~100 lines or want syntax highlighting?
│ ├── YES → additionalCppFiles
│ └── NO → additionalCppCode (inline)
└── NO → You are only adding raw embind registrations on existing symbols
→ mainBuild.additionalBindCodeThe three compose. A typical custom binding ships a .cpp file
(additionalCppFiles) plus an EMSCRIPTEN_BINDINGS(...) block
(additionalBindCode) that registers what the .cpp defined.
Mechanism 1 — additionalCppCode (inline)
For short snippets that belong with the YAML for context — handle typedefs, a single free function, a small POD.
additionalCppCode: |
#include <Standard_Real.hxx>
Standard_Real addReals(Standard_Real a, Standard_Real b) { return a + b; }
mainBuild:
name: my_build
additionalBindCode: |
EMSCRIPTEN_BINDINGS(my_build_extras) {
emscripten::function("addReals", &addReals);
}Now oc.addReals(1.5, 2.25) returns 3.75.
Mechanism 2 — additionalCppFiles (multi-file)
When the wrapper grows past ~100 lines or you want real .cpp files for editor
support and review, move it to additionalCppFiles. The files are concatenated
onto additionalCppCode (in that order) into one translation unit before custom-binding
generation runs.
additionalCppFiles:
- wrappers/fair-curve.cpp
mainBuild:
additionalBindCode: |
EMSCRIPTEN_BINDINGS(my_build_faircurve) {
emscripten::function("computeFairCurve", &computeFairCurve);
}Paths in additionalCppFiles are resolved relative to the YAML file's
directory. A missing file fails-loud at validate time.
Mechanism 3 — additionalBindCode (raw embind)
Use this when bindgen cannot emit the registration you need. Common cases:
- Binding a free function (bindgen registers classes, not free functions).
- Defining a
value_object<T>POD authored inadditionalCppCode/additionalCppFiles. - Registering an
emscripten::vector/emscripten::mapan NCollection auto-discovery pass does not cover.
additionalCppCode: |
struct PointXY { double X; double Y; };
mainBuild:
additionalBindCode: |
EMSCRIPTEN_BINDINGS(my_build_pointxy) {
emscripten::value_object<PointXY>("PointXY")
.field("X", &PointXY::X)
.field("Y", &PointXY::Y);
}Every EMSCRIPTEN_BINDINGS(<name>) group must use a unique name across the
block and any auto-generated binding TUs — embind enforces uniqueness at module
load time.
Worked example — wrapper class with constructor + methods
#include <TopoDS.hxx>
#include <TopoDS_Edge.hxx>
#include <TopoDS_Face.hxx>
#include <TopoDS_Shape.hxx>
class ShapeCast {
public:
static TopoDS_Edge toEdge(const TopoDS_Shape& s) { return TopoDS::Edge(s); }
static TopoDS_Face toFace(const TopoDS_Shape& s) { return TopoDS::Face(s); }
};additionalCppFiles:
- wrappers/shape-cast.cpp
mainBuild:
bindings:
- symbol: TopoDS_Shape
- symbol: TopoDS_Edge
- symbol: TopoDS_Face
additionalBindCode: |
EMSCRIPTEN_BINDINGS(my_build_shape_cast) {
emscripten::class_<ShapeCast>("ShapeCast")
.class_function("toEdge", &ShapeCast::toEdge)
.class_function("toFace", &ShapeCast::toFace);
}JS:
const edge = oc.ShapeCast.toEdge(genericShape);Pitfalls
- Duplicate
EMSCRIPTEN_BINDINGSgroup names crash the module at load time. - Forgetting to bind the underlying OCCT class in
bindings:causes the wrapper to compile but the runtime cast to throw. - Using
Handle<T>without the typedef works in C++ but produces no useful JS shape. Addtypedef opencascade::handle<T> Handle_T;.
Related
- YAML schema — schema for every YAML key.
- Emscripten embind reference.