c5f3248fbc46168e740773d644ae2e7bd9da2c92
7
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c5f3248fbc |
tools/bench_ext.py: measure the bulk APIs against the loops they replace
Keeps the numbers next to the code rather than in a commit message, since the adoption decisions turn on them. Two results are not what the plan assumed: - face_surface_props gains *nothing* from leaving Python — the serial C++ loop costs exactly what the Python loop costs. That agrees with the app's own profile, which found SurfaceProperties to be 94 % of face_candidate_anchors: the time is inside the kernel, so the interpreter round trip was never the problem. The whole 10.9x is OSD_Parallel, reachable only because the GIL is released. - BinTools is already fast enough. Dropping the BytesIO detour saves nothing measurable, so the pools' overhead is not in serialisation — worth knowing before rewriting seventeen call sites for it. Mesh extraction is the clear win at 35x, where the Python loop overhead really does dominate; edge polylines 2.6x. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DfriM8XUkn7uYf5Dwe2xo6 |
||
|
|
01a5bcf188 |
10C Inc 3 + 4: I/O and text — the app's whole OCP surface is bound
138/138 symbols across 53 modules. New: IFSelect, Interface, XSControl, IGESData, STEPControl, IGESControl, RWStl, Graphic3d, NCollection, StdPrs. **The full app suite passes against this wheel: 1797 passed, 1 skipped — the same result as the stock wheel**, run from the parity venv. Getting there needed six methods that no static analysis could have found: `inventory --check` only sees symbols reached through an import, so a method called on an instance is invisible to it. The suite found them in one pass, and one of them (gp_Vec.Reverse, which every outward-normal probe calls) accounted for 311 of the 255 failing tests on its own. The others: gp_Trsf.SetMirror over a plane and a point, Geom_Surface.D0, BRep_Builder.MakeFace from a triangulation, MakePipeShell.SetMode with a fixed binormal, and MakeFace from a surface plus tolerance. Open question **S5 is settled: no**. The wheel does not need OCCT's share/ resources. test_inc3_io.py asserts no CSF_* variable is set and then round-trips STEP and IGES, reading the declared units back off both — which is exactly the resource-less container the question was about. The XSTEP readers keep the GIL, amending the blanket "file readers and writers" line in design.md's GIL policy. STEP and IGES traffic in process-global Interface_Static state, the IGES reader is documented as not thread-safe, and the app already serialises imports behind a lock — so holding it costs nothing and removes a class of question. RWStl, which touches no global state, releases. XSControl_Reader and IGESData are registered although the app imports neither: they are the reader base both concrete readers inherit their transfer surface from, and the model-to-global-section chain the IGES unit probe walks. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DfriM8XUkn7uYf5Dwe2xo6 |
||
|
|
0ce43a94aa |
10C Inc 2: feature tail
119 of the app's 138 symbols now resolve; only the I/O modules and the Inc 4 tail are left. New: GCE2d, GCPnts, BRepFilletAPI, BRepOffsetAPI, ShapeAnalysis, ShapeFix, ShapeUpgrade, BRepCheck, BRepTools, BRepLib, BRepExtrema, BRepClass3d, IntCurvesFace, plus TopTools_HSequenceOfShape. The fillet and chamfer builders derive BRepBuilderAPI_MakeShape, so their history comes from the base bound in Inc 1 — which is what lets the app read a blend's provenance the same way it reads a boolean's. The Inc 2 gate compares that history exactly, alongside the splitter's, which is what sketch-region attribution depends on. Two shapes of deviation, both commented where they are bound: - Trailing enum arguments are left off four constructors (fillet's ChFi3d_FilletShape, MakeThickSolidByJoin's mode/join pair, BRepExtrema's Extrema flags, and Inc 1's GeomAPI_ProjectPointOnSurf). An unregistered enum cannot serve as a default argument — nanobind converts defaults at binding time, so it fails the whole module's import with std::bad_cast. The app never overrides any of them, so OCCT's own defaults apply and behaviour is unchanged. - ShapeAnalysis_FreeBounds.ConnectEdgesToWires reassigns a Handle& out parameter, which the caster cannot honour because it hands C++ a copy of the handle. The lambda splices the result into the sequence the caller passed, so the pass-empty-then-read shape the app uses still works. This is the one place upstream's holder semantics and ours differ observably. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DfriM8XUkn7uYf5Dwe2xo6 |
||
|
|
340315b6e9 |
parity_venv.sh: swap the environment it claims to swap
The script exported UV_PROJECT_ENVIRONMENT and then reached for `uv pip`, which does not honour it — it discovers an environment the way pip does, so the uninstall and install landed in the app's own .venv. The sanity check that follows could not notice, because `uv run` *does* honour the variable and so read the untouched parity venv. Net effect: the swap happened in the wrong place and reported success. Inc 0's parity claim never exercised the parity venv at all. Two fixes, since the second only surfaced once the first was in: - `uv pip` gets --python pointing at the parity venv. - everything afterwards runs that venv's interpreter directly. `uv run` re-syncs the environment against the app's manifest before running, and the manifest still asks for cadquery-ocp-novtk, so it reinstalled the stock wheel on top of the swap. The usage note at the top said to drive the app's tests that way too; it now says to use the venv's python. Also pins `uv sync --python 3.12`, so the parity venv keeps resembling what the image ships instead of following whatever interpreter is newest on the box, and turns the sanity print into an assertion on __occt_version__ — an attribute only our wheel defines, which makes it a check that the swap landed. Verified end to end against the published 7.9.3.1.dev2: installs anonymously from the Gitea index, 31 modules, ocp suite 48 passed, and the app's main venv is left on stock. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DfriM8XUkn7uYf5Dwe2xo6 |
||
|
|
2c2c4e4712 |
10C Inc 1: core modeling
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 |
||
|
|
091a2ad0cf |
Housekeeping ahead of the Inc 1-4 coverage work
- inventory.py --methods: report the instance methods the app calls per class, plus chained calls that constrain a return type. --check says which classes to bind; this says what to bind on them, which is what writing ~38 module TUs needs. - Drop StlAPI from the inventory: StlAPI_Writer has no app call site (the only use was a test fixture, now on the app's own STL writer). 138 symbols / 47 modules. - adding-symbols.md: scope the executing-constructor ban to the BRepAlgoAPI booleans, which are the only classes with a deferred Set*/Build form — BRepMesh_IncrementalMesh, GeomAPI_*, BRepCheck_Analyzer and friends compute in their constructor by design and bind as stock. Replace the per-increment app-test guidance: backend/tests/conftest.py imports n3xd.main, so no app test can collect until the last module is bound. Increments gate on stock-recorded fixtures here; the app suite is the Inc 4 gate. - parity_venv.sh: run the ocp suite in the swapped venv (it imports only OCP/n3xd_ocp, so it works throughout). - Fix a stale macro name in occt_handle.h (ocp_new, not OCP_TRANSIENT_NEW) and drop the unused ocp_transient_class helper. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DfriM8XUkn7uYf5Dwe2xo6 |
||
|
|
6139852768 |
Phase 10A/10B Inc 0: build system, handle model, first module surface
Builds n3xd-ocp end to end and publishes 7.9.3.1.dev1 to the Gitea registry, where it installs anonymously and passes its suite. - occt/Dockerfile: OCCT 7.9.3 compiled once into a manylinux_2_28 builder image (base digest + tarball sha256 pinned), Draw/VTK/Tk/Xlib/OpenGL off, FreeType on, -O2 without fast-math or march=native. A final layer asserts TKService/TKV3d exist with no libGL/libX11 DT_NEEDED, which is what lets the app image drop libgl1/libx11-6. Mounted into, never built FROM. - scikit-build-core + nanobind STABLE_ABI -> one cp312-abi3 extension that registers every OCP.* submodule via PyImport_AddModule, so `import OCP.TopoDS` needs no shim and cls.__module__ is right. Version <occt>.N is asserted against the OCCT found, keeping occt_version() truthful. - occt_handle.h: type caster for opencascade::handle<T> over OCCT's intrusive refcount. Wrappers are non-owning instances holding exactly one handle in their keep-alive list, reusing an existing wrapper so identity survives a round trip. Transient constructors go through ocp_new (never nb::init<>, which would let OCCT delete nanobind's storage); the caster refuses a refcount-0 object rather than corrupt the heap. Verified under ASAN with no memory-safety errors, plus an RSS bound over 50k create/destroy cycles. - Sub-shapes are returned by value everywhere, making the TShape lifetime class that segfaulted a process-global face memo unrepresentable. - Standard_Failure derives RuntimeError, with ~20 concrete types dispatched on the dynamic OCCT type (cad_pool marshals failures home by type name). - Inc 0 surface: gp subset, TopAbs, TopoDS (+ downcasts), TopExp, TopLoc, TopTools, BRep, BinTools, Poly, Standard. 34 of the app's 139 symbols. - n3xd_ocp: additive APIs kept out of the OCP namespace so parity testing stays meaningful. bintools (shape <-> bytes, GIL-free, byte-identical) and _debug. Two findings worth the record, both verified against the stock wheel rather than assumed: upstream binds __hash__ but leaves __eq__ at identity, which is exactly what geom_memo.py's hash-bucket + IsSame scan is built around, so we match it instead of "fixing" it; and BinTools can release the GIL after all, by slurping the file-like object instead of bridging a streambuf that would call back into Python. Gate: BREP round-trips are byte-identical to cadquery-ocp-novtk across six fixtures (the generator asserts stock idempotency first). That matters beyond IPC — derive.py content-addresses BREP payloads by sha256 and stores the ref. |