Binds the modeling core the app builds every feature out of — 93 of its 138 symbols now resolve, up from 34. New modules: GeomAbs, Geom2d, Geom, TCollection, TColgp, TColStd, GProp, BRepGProp, Bnd, BRepBndLib, Adaptor3d, BRepAdaptor, GeomLProp, GeomAPI, BRepBuilderAPI, BOPAlgo, BRepAlgoAPI, BRepPrimAPI, GC, BRepMesh; gp and BRep_Tool completed. Three structural decisions: - BRepBuilderAPI_MakeShape carries Build/Shape/Generated/Modified/IsDeleted for every maker in the binding, so the booleans, the primitives and (later) the fillet builders all answer the app's duck-typed provenance layer through ordinary virtual dispatch. History lists come back copied, so they outlive the builder. - The executing two-argument BRepAlgoAPI constructors stay unbound; operands go in through SetArguments/SetTools. Section keeps Init1/Init2, which are plain setters. BOPAlgo moved up from Inc 2 — SetGlue needs its enum. - Adaptor3d is registered although the app never imports it: every method it calls on BRepAdaptor_Curve/Surface is a virtual declared there, so binding them once on the bases leaves mod_BRepAdaptor.cpp with just constructors. Gate: tests/test_inc1_modeling.py against reference values gen_fixtures.py now records from the stock wheel — measurements, per-face area/centroid in map order, mesh counts, and the boolean history map compared exactly, since that is the substrate the app's topological naming is built on. tools/sigdiff.py compares our bound surface against stock's, because a wrong nb::init<> is silent: MakePrism's five-argument form bound OCCT's semi-infinite gp_Dir overload (gp_Dir converts from gp_Vec), producing a valid solid of the wrong shape with the flags shifted along. The fixture digest caught it; sigdiff finds the class of bug directly, and now reports only one deliberate deviation. 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.