v1.17.0: #377 duplication audit pass 1a, arc-length integrator fix, two documented source breaks
LatestPass 1a of the #377 duplication audit: 27 issues, each one a pair of API spellings that turned out not to mean the same thing. Plus a measured arc-length accuracy fix found while reviewing that work.
⚠️ Read before upgrading
Two changes in this release break source compatibility. SEMVER.md reserves that for a major bump; this is a deliberate, recorded exception, and the major version stays reserved for OCCT 9.0. Both are compile errors, never silent.
1. Surface.drawMesh / Surface.evaluateGrid now return SurfaceGrid (#404)
Both previously returned [[SIMD3<Double>]], nested in opposite orders: drawMesh was [uIndex][vIndex], evaluateGrid was [vIndex][uIndex], with nothing at the type level to catch a caller mixing them up.
// Before
let mesh = surface.drawMesh(uCount: 30, vCount: 30)
for row in mesh { for p in row { emit(p) } }
let rows = mesh.count, cols = mesh[0].count
// After
let mesh = surface.drawMesh(uCount: 30, vCount: 30)
for u in 0..<mesh.uCount {
for v in 0..<mesh.vCount {
if let p = mesh.at(u: u, v: v) { emit(p) }
}
}
let rows = mesh.uCount, cols = mesh.vCountSurfaceGrid exposes at(u:v:), uCount, vCount, isEmpty. It is not a Collection and is not subscriptable.
If you are migrating evaluateGrid, check your index order. Its old shape was [v][u], so a mechanical rewrite that assumes [u][v] transposes the data.
No shim is possible: Swift does not overload on return type alone, so a deprecated overload returning the old type would be ambiguous at every call site that binds the result.
2. Curve3D.interpolate(points:startTangent:endTangent:) removed (#400)
The no-tolerance overload shadowed its tolerance-aware sibling. Swift always prefers the exact arity match, so the three-argument call could never reach the tolerance: parameter, which stayed pinned at 1e-6 no matter what a caller asked for.
// Unchanged source, now compiles against the tolerance-aware overload with the same 1e-6 default
let c = Curve3D.interpolate(points: pts, startTangent: t0, endTangent: t1)
// Reachable for the first time
let c = Curve3D.interpolate(points: pts, startTangent: t0, endTangent: t1, tolerance: 1e-4)Most call sites need no edit. It breaks only where the removed overload was referenced as a value or passed as a function argument.
Behaviour changes
Eleven calls return something different without any compiler diagnostic. Full was/now table in CHANGELOG.md. The ones most likely to reach you:
| Call | Was | Now | Issue |
|---|---|---|---|
Curve3D.length, length(from:to:), totalArcLength, arcLength(from:to:), arcLengthBetween |
one Gauss quadrature across the whole domain, up to 5% wrong on a multi-span BSpline | integrated per GeomAbs_CN span; out-of-domain parameters now clamp instead of extrapolating |
#477 |
Point2D.distance(to: Curve2D) with no projection |
-1, which a distance < tolerance test read as "touching" |
.infinity |
#413 |
Curve3D.totalArcLength / arcLength on failure |
0.0, indistinguishable from a real zero length |
-1.0 |
#408 |
Curve2D.arcLength(from:to:) on failure |
0.0 |
-1.0 |
#409 |
Surface.approximated() with no arguments |
tolerance: 0.01, maxDegree: 10 |
tolerance: 1e-3, maxDegree: 8 |
#406 |
Surface.normal(u:v:) at a near-degenerate point |
a normal, accepted on an absolute 1e-15 test |
SIMD3(0, 0, 0), matching normal(atU:v:)'s relative test |
#401 |
Surface.curvatures(u:v:) |
its own solver at resolution 1e-6 |
the shared one at 1e-7, agreeing with gaussianCurvature/meanCurvature |
#405 |
| Zero-radius / zero-focal conic and circle factories | a live degenerate curve | nil, matching their twins |
#399, #411 |
Curve2D.interpolatePeriodic with 2 points |
nil |
a valid out-and-back loop | #412 |
BRepGraph.sampleFaceUVGrid |
unpacked the requested count | unpacks the written count | #419 |
Headline fix: arc length was measurably wrong (#477)
Curve3D.length integrated with CPnts_AbscissaPoint::Length, a single Gauss quadrature of order ≤ 24 across the entire parameter domain. Exact for a line or a circle, wrong for anything with many spans, and nothing signalled it: the call returned a plausible number. Measured against a densely sampled polyline reference on the pinned kernel:
| curve | spans | now | before |
|---|---|---|---|
| 40-pt interpolated BSpline, varying speed | 39 | 2.9e-7 rel. |
5.1e-2 rel. (5% of 356 units) |
| 60-pt interpolated helix | 59 | 4.3e-15 rel. |
3.9e-6 rel. |
| 5-pt interpolated BSpline | 4 | 6.9e-12 rel. |
2.5e-3 rel. |
Worst where |C'(u)| varies sharply, which is the ordinary case for an interpolated toolpath or an imported spline, so a CAM step-over or sweep spacing derived from a curve's length was percent-level wrong. All five Swift spellings now integrate per span. Found while reviewing this branch, so it never reached a release in the arcLength spellings; length had carried it for longer.
Additive
Surface.mirrored(acrossPoint:) / mirrored(acrossAxis:direction:) (#414) · BRepGraph.contains(uid: GraphItemUID) (#417) · Surface.KnotSplitResult.uSplitParams / vSplitParams and LawFunction.knotSplitParameters (#403) · ArcLengthCurveAdaptor protocol shared by EdgeCurve and WireCurve (#422) · FillingSurface.add(edge:support:continuity:) (#434) · tolerance: on two Curve2D factories (#410, #412) · SurfaceContinuity and ParametricContinuity replacing nine overlapping continuity enums, every retired name kept as a deprecated alias (#398).
Deprecated
Curve2D.approximated(first:last:toleranceU:toleranceV:maxDegree:maxSegments:) → approximatedInRange(...). It wraps a genuinely different OCCT algorithm (Approx_Curve2d, explicit sub-range, separate U/V tolerances) from its same-named sibling, and nothing steered a caller between them. The old spelling still compiles and forwards (#407).
Prebuilt bridge consumers
If you build with OCCTSWIFT_BRIDGE_PREBUILT=1, take this release's OCCTBridge.xcframework.zip. The bridge's C ABI changed: OCCTSurfaceKnotSplitting gained four parameters and five functions were removed, so a v1.16.1 bridge binary no longer matches this Swift layer. Package.swift's URL and checksum are bumped for you.
OCCT.xcframework is unchanged and stays pinned at its v1.15.18 asset. This release carries no kernel patch changes.
Verification
swift build clean. swift test: 4,625 tests in 1,315 suites, 0 failures. Both CI jobs (macOS build+test, iOS Simulator smoke) green on the merged head.
Closes
#380 (Pass 1a epic) · #398 · #399 · #400 · #401 · #402 · #403 · #404 · #405 · #406 · #407 · #408 · #409 · #410 · #411 · #412 · #413 · #414 · #415 · #416 · #417 · #418 · #419 · #420 · #421 · #422 · #433 · #434 · #443 · #477
Known and tracked, not shipped in this release: #478, #479, #480, #481, #482.