Code organization

pybFoam is a Python package that ships several compiled nanobind extension modules and a small pure-Python layer on top. This page explains how those pieces fit together and why the package is split the way it is.

Module layout

At import time import pybFoam brings in:

pybFoam/
├── __init__.py            ← flat re-export from pybFoam_core
├── pybFoam_core.so        ← core bindings (Time, fvMesh, fields, dicts, …)
├── fvc.so                 ← explicit finite-volume operators
├── fvm.so                 ← implicit finite-volume operators (matrices)
├── meshing.so             ← blockMesh / snappyHexMesh / checkMesh
├── thermo.so              ← thermophysical models
├── turbulence.so          ← turbulence models
├── sampling_bindings.so   ← sampledSurface / sampledSet
├── runTimeTables.so       ← runtime table registry
├── libnanobind.so         ← shared nanobind runtime (see below)
├── sampling/              ← pure-Python Pydantic sampling configs
└── embed/                 ← C++ embed library (for OpenFOAM solvers)

Each .so is a separate nanobind module compiled against the OpenFOAM::* CMake targets it needs — pybFoam_core links only against finiteVolume; meshing additionally links the mesh utilities; sampling against OpenFOAM::sampling; and so on. The split mirrors OpenFOAM’s own library layout.

Why several modules and not one

A single monolithic .so linking every OpenFOAM library would work, but has two practical drawbacks:

  1. Link time dominates rebuilds. Touching one binding would relink every OpenFOAM library — tens of seconds of wasted time in an edit loop.

  2. Users who only need part of the surface still pay. Importing pybFoam.sampling would pull turbulence and thermo into the process unconditionally.

Splitting by subsystem keeps build feedback fast and keeps the import cost proportional to what was asked for.

The shared libnanobind.so

Every binding module is built with NB_SHARED, so the nanobind type registry lives in a separate libnanobind.so installed alongside the modules (located at load time via RPATH=$ORIGIN). All of pybFoam’s submodules share that one registry.

A downstream nanobind extension that wants to take pybFoam types as parameters has to opt in to the same registry — it isn’t automatic. The consumer must build with NB_SHARED against the same nanobind SONAME, RPATH-link to pybFoam’s installed libnanobind.so, and ensure pybFoam is imported before any of its types are looked up (import pybFoam from Python, or nb::module_::import_("pybFoam") from an embedded interpreter). pyOFTools is the worked example — see its cmake/Dependencies.cmake and embeddingPython/pyFunctionObject.cpp.

The embed library

pybFoam/embed/ is a separate C++ shared library (libpybFoamEmbed.so) built when PYBFOAM_BUILD_EMBED=ON (the default). It is designed to be consumed from the OpenFOAM side — OpenFOAM solvers link against it via a CMake package config to embed a Python interpreter. The install layout and CMake targets are asserted in the verify-embed-install CI job.