…iven
Six bridge functions build a surface with GeomPlate_MakeApprox, not the three
the issue lists. Five of them passed Nbmax = 1 with dmax = tolerance * 10, and
between them those two arguments made `tolerance` unenforceable.
Nbmax caps the number of Bezier patches, and 1 is the one value that disarms
the algorithm. AdvApp2Var_ApproxAFunc2Var::ComputePatches derives its cut
decision from that cap; at 1 every branch leaves it at "do not cut", so
AdvApp2Var_Patch::CutSense returns the same answer whether or not the G0
criterion was satisfied. The criterion is still evaluated and still reported
through CriterionError() -- it just cannot act. Measured on a 25-point wavy
plate at tolerance 1e-2: the criterion came back at 9.8e-2 against its own
1e-2 threshold, violated, and the surface was returned unchanged. So the
issue's question 2 is answered yes, the criterion was inert, but not for the
reason it proposed: the threshold was exceeded and nothing happened, rather
than being unreachable. Sweeping dmax across nine orders of magnitude at
Nbmax = 1 gives bit-identical control nets.
dmax sets that threshold, as seuil = max(Tol3d, 10 * dmax), so tolerance * 10
asked the criterion to accept 100x what the caller requested. It is not merely
dead weight once subdivision is allowed: at Nbmax = 20 that value reproduces
the bad single-patch answer exactly, while tolerance * 0.1 gives the good one.
tolerance * 0.1 makes 10 * dmax == Tol3d, and is what the sixth site used.
All six now share occtPlateApproxSurface. At tolerance 1e-2 the worst deviation
on that fixture goes from 0.0724 (7.2x the request) to 0.0032, and
Shape.plateSurface(through:) and Shape.plateSurface(points:) -- overloads of
one name that were 22x apart on accuracy -- agree exactly. Surfaces from these
entry points move; callers holding derived geometry should regenerate it.
maxSegments = 1 is clamped to 2 at the one entry point that exposes the cap,
since it is not a coarser request but the value that voids tolerance entirely.
Continuity (question 3) is now passed explicitly and stays GeomAbs_C1. It is
the continuity of joins between patches, a different axis from the constraint
order, so plateSurface(constrainedBy:continuity:) still applies the caller's
value to the boundary constraints only. Only C0/C1/C2 are accepted at all --
G1, G2, C3 and CN each throw "AdvApp2Var_ApproxAFunc2Var : UContinuity Error",
measured -- which is why occtGeomAbsFromSurfaceContinuity, whose order-1 answer
is GeomAbs_G1, must not feed it.
Question 1: the surfaces did not move under patch 0019. Fingerprinting the
control net of 54 plate fits either side of it, the stock side built by
override-linking the unpatched AdvApp2Var_ApproxF2var.cxx, shows every one
identical; only the reported ApproxError() rose, by 1.03x to 5.37x, as the
interior contribution is counted for the first time. At the implicit C1 default
the degree floor is already 8, so #522's collapse could not reach these sites,
exactly as the issue predicted. Nothing needed re-baselining.
Two plate suites carrying .disabled("Plate surface operations cause segfault in
OCCT") are re-enabled: 18 tests, 13 consecutive clean runs, and they pass
against the pre-fix arguments too, so the annotation was stale rather than
describing anything this change cured. They cover two of the six sites.
Bridge-only: no kernel patch, no xcframework rebuild. Each new test was checked
by injecting the defect it targets and confirming it fails.
Closes #571
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Six bridge functions build a surface with
GeomPlate_MakeApprox— not the three #571 lists.Five of them passed
Nbmax = 1withdmax = tolerance * 10, and between them those two argumentsmade
toleranceunenforceable.OCCTShapePlatePoints(OCCTBridge_Healing.mm)tolerance * 10OCCTShapePlateCurves(OCCTBridge_Healing.mm)tolerance * 10OCCTShapePlatePointsAdvanced(OCCTBridge_ProjLib_NLPlate.mm)tolerance * 10OCCTShapePlateMixed(OCCTBridge_ProjLib_NLPlate.mm)tolerance * 10OCCTSurfacePlateThrough(OCCTBridge_ProjLib_NLPlate.mm)tolerance * 10OCCTGeomPlateSurface(OCCTBridge_ProjLib_NLPlate.mm)maxSegments(default 20)tolerance * 0.1Sites 1 and 6 are reachable from overloads of one Swift name —
Shape.plateSurface(through:)and
Shape.plateSurface(points:)— doing one job with contracts 22x apart on accuracy.Q2 — was the
CritOrder = 0criterion inert?Yes, and
dmaxwas a dead argument — but not for the reason the issue proposed. The issueguessed the threshold could never be exceeded. It is exceeded, and nothing happens.
Nbmaxcaps the Bezier patch count, and 1 is the one value that disarms the algorithm.AdvApp2Var_ApproxAFunc2Var::ComputePatchesderives its cut decisionNumDecfrommyMaxPatches;every branch needs a sum of at least 2 to fit under the cap, so at 1
NumDecstays 0.AdvApp2Var_Patch::CutSensethen returns 0 whether or not the criterion is satisfied — "the fitmissed" and "the fit is fine" issue the same instruction. The criterion is still computed and still
reported through
CriterionError(); it just cannot act:Sweeping
dmaxacross nine orders of magnitude atNbmax = 1gives bit-identical control nets.Nbmax = 2is enough; 2 through 100 produce the identical surface on this fixture.dmaxsets that threshold, asseuil = max(Tol3d, 10 * dmax), sotolerance * 10asked the G0criterion to accept 100x the requested tolerance. It is not merely dead weight once subdivision
is allowed: at
Nbmax = 20that value reproduces the bad single-patch answer exactly, whiletolerance * 0.1gives the good one.tolerance * 0.1makes10 * dmax == Tol3d— the value thesixth site already used, so measurement picks it over the other five.
Q1 — did the surfaces move under patch
0019?No. 54 plate fits fingerprinted either side of
0019, the stock side built by override-linkingthe unpatched
AdvApp2Var_ApproxF2var.cxxahead of the archive: every fingerprint identical,degrees and pole counts identical. Only the reported
ApproxError()moved, rising 1.03x to 5.37x(median 1.15x) as the interior contribution is counted for the first time. At the implicit
C1default the
NDMINUfloor is already 8, so #522's collapse could not reach these sites — exactlyas #571 predicted. Nothing in the plate family needed re-baselining.
Q3 — should
Continuitybe explicit?Passed explicitly, staying
GeomAbs_C1. It is the continuity of joins between patches, adifferent axis from the constraint order handed to
GeomPlate_PointConstraint/CurveConstraint, soplateSurface(constrainedBy:continuity:)still applies the caller's.g0/.g1/.g2to theboundary constraints only and does not forward it to the fit. It is not cosmetic — C0/C1/C2 give
17x17, 16x16 and 21x21 control nets. Only those three are accepted at all:
G1,G2,C3andCNeach throwAdvApp2Var_ApproxAFunc2Var : UContinuity Error(measured), which is whyocctGeomAbsFromSurfaceContinuity— whose order-1 answer isGeomAbs_G1— must not feed it.What changes for callers
tolerance: 0.010.0724(7.2x the request)0.0032plateSurface(through:)vsplateSurface(points:), same inputSurfaces from these six entry points move; callers holding derived geometry should regenerate
it.
maxSegments: 1is clamped to 2 at the one entry point exposing the cap, since it is not acoarser request but the value that voids
toleranceentirely.Re-enabled suites
Two plate suites carried
.disabled("Plate surface operations cause segfault in OCCT"). 18 tests,13 consecutive clean runs — and they pass against the pre-fix arguments too, so the annotation
was stale rather than describing anything this change cured. They cover two of the six sites.
Verification
swift test: 5049 tests, 3 failures, all inIssue496CylindricalHoleTestsand confirmedidentical on the base commit
5452a73— pre-existing, unrelated to plates.Nbmax/dmax(3 tests), re-diverging site 1 from the shared helper (1), removing themaxSegmentsclamp (1), and the curve-constrained boundary deviation (1).OCCT.xcframeworkrebuild.Package.swiftuntouched, per theconvention that the url/checksum bump belongs to the release commit.
Scripts/check-bridge-index.py: 680 symbols across 369 classes, 0 stale.Reproducers, both sweep transcripts and the criterion probe:
Scripts/repro/571-plate-approx-contract/.Closes #571
🤖 Generated with Claude Code