stroblmeandClaude Opus 5 b9fb8ca469 Fix the README's broken markdown and drop the unsourced speedup claim
Three fixes. The cadquery-ocp link was missing its closing bracket and rendered
literally. "As packages sit on our Gitea instance for, you must install" had a
stray word. And the headline "superlinear 7.0x speedup" cited nothing and is
contradicted by docs/design.md, which says binding call overhead was never the
bottleneck: the payoff is version velocity, footprint, ownership correctness and
the batch APIs upstream lacks. That is what the intro says now, and the one
measured number that does exist (face extraction 4.6-5.5x over the Python loop)
sits with the batch operations it belongs to.

Restructured onto the shared skeleton on the way through, adding a status, a
layout table and a links-out section.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WsxkJEmqEECM1PygXdN1zU
2026-08-12 09:16:53 +02:00
2026-08-11 12:43:54 +02:00
2026-08-11 15:07:47 +02:00
2026-08-11 12:43:54 +02:00

n3xd-ocp

Hand-written nanobind wrapper for the OpenCASCADE (OCCT) geometry kernel. It installs as a top-level OCP and is a drop-in replacement for cadquery-ocp: same symbols, same call shapes. What it buys over upstream is version velocity (the kernel is ours to bump), a much smaller footprint, correctness around object ownership, and room to add APIs upstream does not offer, such as releasing the GIL and extracting whole shapes in one call.

Status

The binding covers the subset of OCCT that n3xd actually uses, not the whole kernel; run tools/inventory.py for the current count. Adding a class is a routine, documented change, see docs/adding-symbols.md.

Installation

The packages sit on our Gitea instance, so install by pointing at its index:

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.

Usage

OCP mirrors cadquery-ocp symbol-for-symbol, so code written against it runs unchanged:

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) are not bound, only the deferred SetArguments/SetTools/Build() sequence above. See docs/design.md for why.

n3xd_ocp adds a handful of batch operations OCP does not have. They run on the same OCCT build and take and return plain OCP shapes, and they exist because doing the same work as a per-face Python loop is measurably slower (face extraction came out 4.6-5.5x faster on ordinary geometry when the app adopted it):

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

points, normals, uv_bounds = n3xd_ocp.sample.face_grid(face, 33)  # a 33x33 UV grid on one face

n3xd_ocp.helix reaches OCCT 8.0's TKHelix, which upstream has no binding for. For N segments it wants N pitches, N turn counts and N+1 diameters, one per segment boundary, so consecutive values that differ taper across that segment:

from OCP.gp import gp_Ax3, gp_Dir, gp_Pnt

axis = gp_Ax3(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1), gp_Dir(1, 0, 0))
wire, tolerance_reached = n3xd_ocp.helix.pure_helix(axis, 8.0, [1.25], [12.0])

builder = n3xd_ocp.helix.BuilderHelix()                  # tapered, e.g. an NPT thread
builder.set_parameters(axis, [10.0, 8.0], [2.0], [4.0])
builder.set_approx_parameters(1.0e-4)
builder.perform()

Layout

Path What it is
src/modules/ The bound OCCT classes, one file per OCCT module
src/ext/ The n3xd_ocp extras: batch measure, tessellation, sampling, helix
src/common/ Handle and transient casters, the machinery every module relies on
python/OCP/, python/n3xd_ocp/ The Python packages and their type stubs
occt/ The Dockerfile that compiles OCCT 8.0.1 into the builder image
tools/ Inventory, signature diffing against upstream, and benchmarks

Build

Wheels are built on a dev box and published to the Gitea package registry. To build one yourself:

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

More

Licensed LGPL-2.1-only, following OCCT.

S
Description
No description provided
Readme
280 KiB
Languages
C++ 59.6%
Python 34.2%
Shell 3.2%
Dockerfile 1.2%
CMake 0.9%
Other 0.9%