n3xd_ocp.tess: a shape's triangulation without the per-node round trip

cad/tessellation.py walks every node and every triangle of every face in
Python — three interpreter loops per face — to turn OCCT's mesh into flat
arrays the kernel already holds. extract_meshes hands them over in one call
with the GIL released; extract_edge_polylines does the same for the per-edge
GCPnts discretisation behind the viewport's edge overlay.

The output reproduces the Python loops exactly, including the parts that look
like quirks: unmeshed faces are omitted so face_index keeps its gaps (it is the
app-wide face identity, not a list position), triangles are rewound and normals
negated for reversed faces, and nodes come back float64 — the binary transport
narrows to float32 on its way out but the JSON one does not, so narrowing here
would quietly change what the REST payload says.

Plain dicts of arrays rather than a bound class, because the cad pool sends
meshes across a pipe and dicts of ndarrays pickle natively. There is a test for
that, and one asserting the arrays outlive the shape.

The equality tests re-implement the Python loops in full and compare with
array_equal, not approx: a difference here moves what the viewport draws or
what a face pick resolves to.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfriM8XUkn7uYf5Dwe2xo6
This commit is contained in:
2026-08-10 20:40:25 +02:00
parent 4bb4805ccb
commit 8255fb949c
4 changed files with 423 additions and 1 deletions

View File

@@ -17,6 +17,7 @@ import OCP as _OCP_pkg # noqa: F401 (loads the extension, registering us)
bintools = sys.modules["n3xd_ocp.bintools"]
measure = sys.modules["n3xd_ocp.measure"]
tess = sys.modules["n3xd_ocp.tess"]
_debug = sys.modules["n3xd_ocp._debug"]
__all__ = ["bintools", "measure"]
__all__ = ["bintools", "measure", "tess"]