Four mechanical binding edits; the bound Python surface is unchanged (sigdiff compared the 7.9.3.1 dump against 8.0.1.1's in both directions and found no class, member or constructor arity moved), so the app needs no change. - NCollection_Utf8String is gone; NCollection_String is the same UTF-8 type. Python name kept, since the app imports it. - Standard_Failure lost its Standard_Transient RTTI when it moved to deriving std::exception; ExceptionType() replaced DynamicType()->Name() and returns the same class names the exception dispatch keys on. - FindKey gained a size_t overload beside the int one, so the plain member pointer is ambiguous; overload_cast picks the int form, which keeps the negative-index guard. - StdPrs moved from TKService to TKV3d, so CMakeLists links both. The byte-identity fixtures did NOT retire, contrary to the watchlist: all six round-trip to the same digests under the new kernel, as do the measurement, history and mesh-count blocks, so tests/data is untouched and the app's content-addressed BREP payloads stay valid. Gates: binding suite 83 passed, 138/138 symbols, sigdiff clean both directions, ASAN clean, app suite 1798 passed / 1 skipped (unchanged), sweep over 4486 parts with anchors_digest and every status unchanged, -m perf 16% faster on the 32-feature chain.
77 lines
3.7 KiB
Markdown
77 lines
3.7 KiB
Markdown
# n3xd-ocp
|
|
|
|
Hand-written [nanobind](https://github.com/wjakob/nanobind) bindings for the
|
|
OpenCASCADE (OCCT) geometry kernel, covering exactly the surface the N3XD CAD
|
|
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: **in production use** — all 138 symbols the app imports, across 53
|
|
bound modules, published as `8.0.1.1` (OCCT 8.0.1). The app cut over on
|
|
2026-08-10 at `7.9.3.1` and took the kernel bump on 2026-08-11; its full
|
|
backend suite passes against both (1798 passed / 1 skipped, the same as the
|
|
stock wheel it replaced), 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 — across the kernel bump as well, so nothing stored had to
|
|
be rewritten.
|
|
|
|
Start with [docs/design.md](docs/design.md) for the decisions,
|
|
[docs/building.md](docs/building.md) to build one, and
|
|
[docs/adding-symbols.md](docs/adding-symbols.md) to extend the surface. The
|
|
phase plan lives in the app repo at `docs-private/reference/roadmap.md`
|
|
(Phase 10).
|
|
|
|
## Why
|
|
|
|
`cadquery-ocp` lags OCCT (it still wraps 7.9.3; we are on 8.0.1), builds
|
|
Windows and macOS wheels we never use, and until recently forced a 638 MB VTK
|
|
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).
|
|
- Executing constructors (the two-argument `BRepAlgoAPI_*` forms) are **not
|
|
bound**, so the double-execution footgun is unrepresentable.
|
|
|
|
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
|
|
production host (4 cores, and a kernel build is multi-hour). Wheels are built
|
|
here on a dev box and published to the Gitea package registry.
|
|
|
|
```bash
|
|
make image # once, ~40 min: compiles OCCT 8.0.1 into the builder image
|
|
make dev # inner loop: incremental compile + tests
|
|
make wheel # compile, stubs, auditwheel, self-containment smoke test
|
|
make publish # -> https://git.stroblme.de/api/packages/N3XD/pypi
|
|
```
|
|
|
|
Credentials go in `.secrets` (gitignored) as `UV_PUBLISH_USERNAME` /
|
|
`UV_PUBLISH_PASSWORD`. Consumers read anonymously — the package is public:
|
|
|
|
```bash
|
|
uv pip install --index-url https://git.stroblme.de/api/packages/N3XD/pypi/simple/ \
|
|
--prerelease=allow n3xd-ocp
|
|
```
|
|
|
|
Versions are `<occt-version>.N`, enforced at configure time against the OCCT
|
|
actually found, so the kernel a wheel wraps is readable from its version alone.
|
|
The registry refuses to republish a version; iteration builds therefore carry a
|
|
`.devN` suffix and are the only ones the registry's cleanup rule collects.
|