Files
ocp/tests/conftest.py
stroblme 4bb4805ccb n3xd_ocp.measure: every face's area and centroid in one call
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
2026-08-10 20:37:01 +02:00

60 lines
1.7 KiB
Python

"""Shared fixtures.
Inc 0 binds no modelling API, so shapes for the tests are built the long way:
a compound assembled through BRep_Builder, and shapes read back from the
committed .brep fixtures (tests/data), which were produced by the *stock*
wheel — see tools/gen_fixtures.py.
"""
from __future__ import annotations
import io
import json
import pathlib
import pytest
from OCP.BinTools import BinTools
from OCP.TopoDS import TopoDS_Shape
DATA = pathlib.Path(__file__).parent / "data"
def load_brep(path: pathlib.Path) -> TopoDS_Shape:
shape = TopoDS_Shape()
with path.open("rb") as fh:
BinTools.Read_s(shape, io.BytesIO(fh.read()))
return shape
@pytest.fixture(scope="session")
def manifest() -> dict:
path = DATA / "manifest.json"
if not path.exists():
pytest.skip("fixtures not generated — run tools/gen_fixtures.py")
return json.loads(path.read_text())
@pytest.fixture(scope="session")
def fixture_shapes(manifest) -> dict[str, TopoDS_Shape]:
return {name: load_brep(DATA / f"{name}.brep") for name in manifest["shapes"]}
@pytest.fixture
def a_face(fixture_shapes):
"""First face of the meshed box fixture."""
from OCP.TopAbs import TopAbs_FACE
from OCP.TopExp import TopExp
from OCP.TopTools import TopTools_IndexedMapOfShape
faces = TopTools_IndexedMapOfShape()
TopExp.MapShapes_s(fixture_shapes["box_meshed"], TopAbs_FACE, faces)
return faces.FindKey(1)
@pytest.fixture(scope="session")
def inc1_or_skip(manifest):
"""The Inc 1 reference block, for tests outside test_inc1_modeling.py."""
if "inc1" not in manifest:
pytest.skip("manifest predates the Inc 1 reference block")
return manifest["inc1"]