…the "weaker" duplicate was the stronger one
`GeomConvert_BSplineSurfaceKnotSplitting` and `Geom2dConvert_BSplineCurveKnotSplitting` were each
wrapped twice, by two families added three releases apart. The five v0.105.0 spellings
(`Surface.bsplineKnotSplitsU`/`bsplineKnotSplitsV`/`bsplineKnotSplitValues`,
`Curve2D.bsplineKnotSplits`/`bsplineKnotSplitValues`) are deprecated and forward to
`Surface.knotSplitting` / `Curve2D.splitIndicesAtDiscontinuities`; their five bridge functions
are deleted.
The issue's premise that the five were strictly weaker did not survive measurement.
`Curve2D.bsplineKnotSplitValues` sized its buffer from the analyzer's own count, where
`splitIndicesAtDiscontinuities` read a fixed 256 entries and the bridge returned the count it
had *written* -- indistinguishable from a curve with exactly 256 splits. On a cubic with 302
splits the canonical call returned 256 and the duplicate returned 302, so forwarding without
fixing that would have regressed the deprecated spelling. `OCCTCurve2DSplitAtDiscontinuities`
now reports the true count and the Swift caller re-reads at it, the #481 contract the rest of
the family already shared. C-layer contract change; OCCTBridge is not an SPM product (#486).
The one thing the deleted family carried that the canonical calls did not is the raw knot-table
indices: the analyzer reports indices and `OCCTSurfaceKnotSplitting` converted them to
parameters, so the raw form was reachable only through `bsplineKnotSplitValues`, which
constructed the analyzer three more times to get it. `KnotSplitResult` now carries
`uSplitIndices`/`vSplitIndices` from the one construction already happening, with
`uSplitParams[i] == bsplineUKnot(index: uSplitIndices[i])` by construction. That answers the
issue's open question: the information was worth keeping, the three entry points were not.
Both deleted values functions also took no buffer capacity at all, each writing `NbSplits()`
entries into a buffer the caller had sized from a separate call. Recorded in the bridge header.
`OCCTBridge.h`'s cross-reference index named none of the three `*KnotSplitting` conversion
classes, which is half of why the double-wrap survived three releases (#510). It gains the two
`GeomConvert_*` entries and a `--- Geom2dConvert ---` section that did not exist at all,
censused by call site across its six classes.
Tests: `Issue562Curve2DKnotSplitDuplicateTests` (4) and `Issue562SurfaceKnotSplitDuplicateTests`
(5), with absolute expectations against the fixture's own knot table rather than parity between
the two spellings, which stopped being evidence once one forwards to the other. Three injected
defects (written-count truncation, 0-based indices, V continuity collapsed onto U) each fail the
tests that should catch them; the last is caught by the new suite alone.
Closes #562.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes #562.
GeomConvert_BSplineSurfaceKnotSplittingandGeom2dConvert_BSplineCurveKnotSplittingwere each wrapped twice, by two families added three releases apart. The five v0.105.0 spellings are deprecated and forward to their canonical sibling; their five bridge functions are deleted.The issue's premise did not survive measurement
The issue called the five "strictly weaker, not differently useful". That held for four of them.
Curve2D.bsplineKnotSplitValuessized its buffer from the analyzer's own count, wheresplitIndicesAtDiscontinuitiesread a fixed 256 entries and the bridge returned the count it had written — indistinguishable from a curve with exactly 256 splits. Measured on a cubic with 300 interior knots at multiplicity 3 (302 splits):splitIndicesAtDiscontinuities(continuity: .c1)256302bsplineKnotSplitValues(continuity: .c1)Forwarding onto the canonical spelling without fixing that would have regressed the deprecated one, so
OCCTCurve2DSplitAtDiscontinuitiesnow reports the true count and the Swift caller re-reads at it — the #481 contract every other member of this family already shared.C-layer contract change: a direct bridge caller that treated the return as "how many were written" must now clamp it.
OCCTBridgeis not an SPM product, so no Swift package is affected (#486).Checkbox 4: is the surface index form worth keeping?
The information, yes. The three entry points, no.
The analyzer reports knot-table indices and
OCCTSurfaceKnotSplittingconverted them to parameters, so the raw form was reachable only throughbsplineKnotSplitValues— which constructed the analyzer three more times to get it, once per count call and once for the values.KnotSplitResultnow carriesuSplitIndices/vSplitIndicesfrom the one construction that was already happening, withuSplitParams[i] == bsplineUKnot(index: uSplitIndices[i])by construction. That also makes the deprecated shim exact, rather than a float-equality reverse lookup through the knot table.Both deleted values functions also took no buffer capacity at all — each wrote
NbSplits()entries into a buffer the caller had sized from a separate call, safe only because the analyzer is deterministic. Recorded in the bridge header so it is not reintroduced.The index gap that let this survive three releases
OCCTBridge.h's cross-reference index named none of the three*KnotSplittingconversion classes. That index is the map used to find every call site of a class (#510), so a by-class audit of either analyzer returned nothing. It gainsGeomConvert_BSplineCurveKnotSplitting,GeomConvert_BSplineSurfaceKnotSplitting, and a--- Geom2dConvert ---section that did not exist at all, censused by call site across its six classes.Tests
Issue562Curve2DKnotSplitDuplicateTests(OCCTGeom2dTests, 4) andIssue562SurfaceKnotSplitDuplicateTests(OCCTSurfaceTests, 5). Expectations are absolute — the fixture's own knot indices and knot table — rather than agreement between the two spellings, which stopped being evidence the moment one started forwarding to the other.Issue480Curve2DKnotSplitContinuityTests.alternateSpellingsAgreewas rewritten for the same reason.Three injected defects, each failing the tests that should catch it:
moreSplitsThanTheFirstPassBuffer,retryBoundaryIsExact(at 257 only, correctly),deprecatedPairForwardsindicesResolveToParams,indicesAreOneBasedAndBracketing,deprecatedTrioForwardsdirectionsAreIndependent— and by no existing #403 or #480 coverageVerification
swift buildclean, no deprecation warnings (every remaining call site is inside an@available(*, deprecated)test function).swift test: 5052 tests. The 23 failures are all inIssue522ApproxC0CollapseTests/Issue491SurfaceApproxParityTests— surface approximation, untouched here. Confirmed identical (same 23 issues, same 5 test cases) against unmodifiedrefactor/381-pass1bin a separate worktree; they clear when chore(#512) + fix(#518, #555, #522): rebuild the kernel, redesign 0016 after upstream review, add 0018 and 0019 #519's kernel binary lands.check-bridge-index.py0 stale across 692 symbols / 377 classes,--self-test5/5;count-operations.py4299 ✓ (deprecated methods still count as entry points);check-null-handle-guards.pyclean.Docs updated in the same commit:
docs/reference/Document-Geometry-Constructors.md(two duplicated sections collapsed into one deprecation pointer with a migration table),Surface-Advanced.md,Curve2D.md,API_REFERENCE.md,CHANGELOG.md.🤖 Generated with Claude Code