BRepGProp.SurfaceProperties is the hottest kernel call the app makes — 94 % of face_candidate_anchors, 0.99 s of 1.05 s for 690 faces — not because the kernel is slow but because it is reached once per face from Python, so a rebuild pays the round-trip hundreds of times per feature over a growing shape. face_surface_props(shape, *, parallel=True, eps=None) runs the whole scan C++-side with the GIL released, optionally over OSD_Parallel, and returns (areas[F], centroids[F,3]) in MapShapes(FACE) order — the face identity the topology layer already keys on, so a caller indexes straight into it. A face OCCT cannot integrate reports zeros, matching what the app's own try/except substitutes. Tested against the same stock-recorded per-face reference the Inc 1 gate uses, and against the one-call-per-face loop it replaces. Parallel and serial are compared with array_equal rather than approx: the parallel path shares one TopoDS_Shape across threads, so an exact match is the evidence that nothing reachable from it gets mutated while measuring. Backend adoption comes after the cutover, so this changes nothing yet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DfriM8XUkn7uYf5Dwe2xo6
n3xd-ocp
Hand-written 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.
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).
Start with docs/design.md for the decisions,
docs/building.md to build one, and
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 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:
- OCCT sub-shapes are returned by value, so a wrapper can never alias a
TShapewhose 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.
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.
make image # once, ~40 min: compiles OCCT 7.9.3 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:
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.