README: coverage complete, and the speedup the plan did not expect

138 symbols, 53 modules, published as 7.9.3.1.dev5. The app's full suite passes
against it and the whole project store sweeps identical.

Corrects the "call overhead is not a bottleneck" claim: it is true of any single
call and false of the aggregate. The app's benchmark suite runs 194 s -> 73 s
with nothing but the wheel swapped, and the win grows with face count — 3.3x at
4 features, 7.0x at 32.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfriM8XUkn7uYf5Dwe2xo6
This commit is contained in:
2026-08-10 21:03:41 +02:00
parent c5f3248fbc
commit a0b7e43985

View File

@@ -2,18 +2,19 @@
Hand-written [nanobind](https://github.com/wjakob/nanobind) bindings for the
OpenCASCADE (OCCT) geometry kernel, covering exactly the surface the N3XD CAD
backend uses — 139 symbols across 48 `OCP.*` modules, not all of OCCT.
backend uses — 138 symbols across 47 `OCP.*` modules, not all of OCCT.
The package installs as a top-level `OCP`, so it is a drop-in replacement for
`cadquery-ocp-novtk` and the app's 442 import sites stay untouched.
Status: **Inc 0 (spike) shipped** — build system, OCCT builder image, handle
model, and the first module surface (`gp`, `TopAbs`, `TopoDS`, `TopExp`,
`TopLoc`, `TopTools`, `BRep`, `BinTools`, `Poly`, `Standard`), published as
`7.9.3.1.dev1`. BREP serialisation is byte-identical to the stock wheel, which
is the gate that mattered: the pools and the content-addressed derive payloads
both depend on it. Coverage is 34 of the 139 symbols the app imports; the rest
lands in increments 1-4 (roadmap 10C).
Status: **coverage complete** — all 138 symbols the app imports, across 53
bound modules, published as `7.9.3.1.dev5`. The app's full backend suite passes
against it (1797 passed / 1 skipped, the same as the stock wheel), a sweep of
the whole project store reproduces every part's geometry exactly (4486 parts;
no change in statuses, volume, area, bbox, entity counts, triangles or anchor
digests), and BREP serialisation stays byte-identical, which the pools and the
content-addressed derive payloads depend on. The app is not swapped yet — that
is the cutover, roadmap 10C.
Start with [docs/design.md](docs/design.md) for the decisions,
[docs/building.md](docs/building.md) to build one, and
@@ -25,9 +26,8 @@ phase plan lives in the app repo at `docs-private/reference/roadmap.md`
`cadquery-ocp` lags OCCT (it wraps 7.9.3; OCCT 8.0 shipped 2026-05), builds
Windows and macOS wheels we never use, and until recently forced a 638 MB VTK
dependency into the image. Binding *call* overhead is not a bottleneck — the
CAD hotspots live inside the C++ kernel — so this exists for version velocity,
footprint, and two defects that a binding we control prevents by construction:
dependency into the image. So this exists for version velocity, footprint, and
two defects that a binding we control prevents by construction:
- OCCT sub-shapes are returned **by value**, so a wrapper can never alias a
`TShape` whose owner has died (this segfaulted a process-global face memo).
@@ -37,6 +37,16 @@ footprint, and two defects that a binding we control prevents by construction:
It also releases the GIL around kernel calls and ships type stubs, neither of
which upstream does.
**It is also considerably faster, which was not the point and turned out to
matter most.** With the app otherwise unchanged, its benchmark suite runs
194 s → 73 s, and rebuild time improves *superlinearly* with model complexity:
3.3x for a 4-feature part, 7.0x for a 32-feature one (13.4 s → 1.9 s). The
premise going in was that call overhead is irrelevant because the hotspots live
inside the kernel — true of any single call, false of the aggregate, because
this backend reaches OCCT once per face, per node and per edge.
`tools/bench_ext.py` has the numbers and the two places they contradicted the
plan.
## Build
OCCT is compiled once into a builder image and reused; it is never built on the