117
README.md
117
README.md
@@ -1,76 +1,67 @@
|
||||
# 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.
|
||||
Hand-written [nanobind](https://github.com/wjakob/nanobind) wrapper for the OpenCASCADE (OCCT) geometry kernel.
|
||||
In comparison to [`cadquery-ocp](https://github.com/cadquery/OCP), we get superlinear 7.0x speedup on the use cases in `n3xd`.
|
||||
|
||||
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).
|
||||
[docs/adding-symbols.md](docs/adding-symbols.md) to extend the surface.
|
||||
|
||||
## Why
|
||||
## Installation
|
||||
|
||||
`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:
|
||||
As packages sit on our Gitea instance for, you must install by providing the specific url, like:
|
||||
|
||||
```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.
|
||||
Versions are `<occt-version>.N`, enforced at configure time against the OCCT actually found.
|
||||
|
||||
## Usage
|
||||
|
||||
`OCP` mirrors [`cadquery-ocp`](https://github.com/cadquery/OCP) symbol-for-symbol, so code written against it runs unchanged:
|
||||
|
||||
```python
|
||||
from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox
|
||||
from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut
|
||||
from OCP.TopTools import TopTools_ListOfShape
|
||||
|
||||
box = BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()
|
||||
hole = BRepPrimAPI_MakeBox(3.0, 3.0, 30.0).Shape()
|
||||
|
||||
args, tools = TopTools_ListOfShape(), TopTools_ListOfShape()
|
||||
args.Append(box)
|
||||
tools.Append(hole)
|
||||
|
||||
cut = BRepAlgoAPI_Cut()
|
||||
cut.SetArguments(args)
|
||||
cut.SetTools(tools)
|
||||
cut.Build()
|
||||
result = cut.Shape()
|
||||
```
|
||||
|
||||
One deliberate gap from upstream: constructors that run the algorithm immediately (the two-argument `BRepAlgoAPI_Cut(a, b)` form) aren't bound, only the deferred `SetArguments`/`SetTools`/`Build()` sequence above. See [docs/design.md](docs/design.md) for why.
|
||||
|
||||
`n3xd_ocp` adds a handful of batch operations OCP doesn't have. They run on the same OCCT build and take/return plain `OCP` shapes:
|
||||
|
||||
```python
|
||||
import n3xd_ocp
|
||||
|
||||
areas, centroids = n3xd_ocp.measure.face_surface_props(result) # one call for every face
|
||||
meshes = n3xd_ocp.tess.extract_meshes(result) # triangulated faces, ready to render
|
||||
data = n3xd_ocp.bintools.write_bytes(result) # BREP bytes, no temp file needed
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
Wheels are built on a dev box and published to the Gitea package registry [here](https://git.stroblme.de/api/packages/N3XD/pypi).
|
||||
|
||||
If you want to make modifications or build it yourself, here are some shortcuts:
|
||||
|
||||
```bash
|
||||
make image # compiles OCCT 8.0.1 into the builder image
|
||||
make dev # incremental compile + tests
|
||||
make wheel # compile, stubs, auditwheel, self-containment smoke test
|
||||
make publish # publish to Gitea using .secret credentials
|
||||
```
|
||||
Reference in New Issue
Block a user