OpenCascade.js

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:

  1. additionalCppCode — inline C++ embedded directly in the YAML scalar.
  2. additionalCppFiles — one or more .cpp files concatenated onto additionalCppCode.
  3. mainBuild.additionalBindCode — per-build raw EMSCRIPTEN_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.additionalBindCode

The 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 in additionalCppCode / additionalCppFiles.
  • Registering an emscripten::vector / emscripten::map an 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

wrappers/shape-cast.cpp
#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); }
};
build-configs/my-config.yml
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_BINDINGS group 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. Add typedef opencascade::handle<T> Handle_T;.

On this page