Geometry Solvers & Builders
Utility and solver types that sit alongside the primary geometry hierarchy: a B-spline curve fitter (BSplineApproxInterp), a thin-plate variational solver (PlateSolver), an N-sided surface-filling builder (FillingSurface), a parametric evolution law (LawFunction), closed-form polynomial root solvers (PolynomialSolver / PolynomialRoots), and a spatial KD-tree (KDTree). Each type is self-contained and constructed independently of Shape or Surface.
Topics
- BSplineApproxInterp · PlateSolver · FillingSurface · LawFunction · PolynomialRoots · PolynomialSolver · KDTree
BSplineApproxInterp
Least-squares B-spline curve approximation through a set of 3D points. Backed by GeomAPI_PointsToBSpline since OCCT 8.0.0p1 (the original Approx_BSplineApproxInterp was removed in that release). Inspect maxError for the worst-case residual after calling perform() or performOptimal().
init?(points:nbControlPoints:degree:continuousIfClosed:)
Creates a constrained B-spline approximation solver.
public init?(points: [SIMD3<Double>], nbControlPoints: Int,
degree: Int = 3, continuousIfClosed: Bool = false)
Returns nil if points.count < 2.
- Parameters:
points— array of 3D points to fit.nbControlPoints— advisory number of control points; the approximator chooses the pole count needed to meet tolerance.degree— B-spline degree (default3).continuousIfClosed— enforce C2 continuity when the curve is detected as closed (defaultfalse).
- Returns: A configured solver, or
nilon invalid input. - OCCT:
GeomAPI_PointsToBSpline(replaces removedApprox_BSplineApproxInterp). - Example:
var pts: [SIMD3<Double>] = [] for i in 0..<50 { let t = Double(i) / 49.0 * 2.0 * .pi pts.append(SIMD3(cos(t), sin(t), 0.1 * t)) } if let solver = BSplineApproxInterp(points: pts, nbControlPoints: 20) { solver.perform() if solver.isDone, let curve = solver.curve { print("Max error:", solver.maxError) } }
interpolatePoint(_:withKink:)
Mark a point to be exactly interpolated (0-based index).
public func interpolatePoint(_ index: Int, withKink: Bool = false)
Note: No-op since OCCT 8.0.0p1 —
GeomAPI_PointsToBSplinehas no per-point exact interpolation. The approximation still passes near every point.
- Parameters:
index— 0-based index into the points array.withKink— iftrue, inserts a C0 discontinuity at this parameter (also a no-op in the current backend).
- OCCT: Previously
Approx_BSplineApproxInterp::ChangeConstraints(now inert).
perform()
Perform the fit using automatically computed parameters.
public func perform()
- OCCT:
GeomAPI_PointsToBSplineconstructor (performs the fit on construction). - Example:
solver.perform() if solver.isDone { print(solver.maxError) }
performOptimal(maxIterations:)
Perform the fit with iterative parameter optimization.
public func performOptimal(maxIterations: Int = 10)
- Parameters:
maxIterations— maximum number of optimization iterations (default10). - OCCT:
GeomAPI_PointsToBSplinewith iterative reparametrisation.
isDone
Returns true if the fit was computed successfully.
public var isDone: Bool { get }
- OCCT:
GeomAPI_PointsToBSpline::IsDone.
curve
The resulting B-spline curve, or nil if not done.
public var curve: Curve3D? { get }
- Returns: The fitted
Curve3D, ornilifisDoneisfalse. - OCCT:
GeomAPI_PointsToBSpline::Curve. - Example:
if solver.isDone, let c = solver.curve { print(c.length()) }
maxError
The maximum approximation error.
public var maxError: Double { get }
- Returns: Worst-case 3D distance from any input point to the fitted curve.
- OCCT:
GeomAPI_PointsToBSpline::MaxError.
setParametrizationAlpha(_:)
Set parametrisation power: 0 = uniform, 0.5 = centripetal (default), 1 = chord-length.
public func setParametrizationAlpha(_ alpha: Double)
- Parameters:
alpha— exponent in[0, 1]. - OCCT:
GeomAPI_PointsToBSplineparametrisation mode.
setMinPivot(_:)
Set minimum pivot value for the Gauss solver (default 1e-20).
public func setMinPivot(_ value: Double)
- Parameters:
value— pivot threshold below which a column is considered singular.
setClosedTolerance(_:)
Set relative tolerance for closed-curve detection (default 1e-12).
public func setClosedTolerance(_ value: Double)
setKnotInsertionTolerance(_:)
Set tolerance for knot insertion during kink handling (default 1e-4).
public func setKnotInsertionTolerance(_ value: Double)
setConvergenceTolerance(_:)
Set convergence tolerance for parameter optimization (default 1e-3).
public func setConvergenceTolerance(_ value: Double)
Drive accuracy with this and setProjectionTolerance when the default fit is insufficient.
setProjectionTolerance(_:)
Set projection tolerance for parameter optimization (default 1e-6).
public func setProjectionTolerance(_ value: Double)
PlateSolver
A thin-plate spline solver for smooth surface deformation. Wraps OCCT’s Plate_Plate variational solver: load position and/or derivative pinpoint constraints, call solve(), then evaluate the resulting displacement field at any UV location.
Unlike the higher-level NLPlate methods on Surface, PlateSolver works directly in UV parameter space and returns raw XYZ displacements.
Loading Constraints
init()
Create a new Plate solver.
public init()
- OCCT:
Plate_Platedefault constructor. - Example:
let solver = PlateSolver() solver.loadPinpoint(u: 0, v: 0, position: .zero) solver.loadPinpoint(u: 1, v: 1, position: SIMD3(1, 1, 0.5))
loadPinpoint(u:v:position:)
Load a pinpoint constraint (position at a UV point).
public func loadPinpoint(u: Double, v: Double, position: SIMD3<Double>)
- Parameters:
u,v— UV parameter coordinates.position— target 3D position the surface must pass through.
- OCCT:
Plate_Plate::LoadwithPlate_PinpointConstraint.
loadDerivativeConstraint(u:v:value:derivativeOrderU:derivativeOrderV:)
Load a derivative constraint at a UV point.
public func loadDerivativeConstraint(u: Double, v: Double, value: SIMD3<Double>,
derivativeOrderU: Int, derivativeOrderV: Int)
- Parameters:
u,v— UV parameter coordinates.value— target derivative value.derivativeOrderU— U derivative order (0for position,1+for derivatives).derivativeOrderV— V derivative order.
- OCCT:
Plate_Plate::LoadwithPlate_PinpointConstraintat higher derivative order.
loadGtoC(u:v:sourceD1:targetD1:)
Load a geometric-to-continuity (GtoC) constraint at G1 level.
public func loadGtoC(u: Double, v: Double,
sourceD1: (tangentU: SIMD3<Double>, tangentV: SIMD3<Double>),
targetD1: (tangentU: SIMD3<Double>, tangentV: SIMD3<Double>))
Constrains the surface derivatives to transition from one tangent frame to another at a given UV point.
- Parameters:
u,v— UV parameter coordinates.sourceD1— source surface first derivatives (tangentU, tangentV).targetD1— target surface first derivatives.
- OCCT:
Plate_Plate::LoadwithPlate_GtoCConstraint.
Solving
solve(order:anisotropy:)
Solve the plate system.
@discardableResult
public func solve(order: Int = 4, anisotropy: Double = 1.0) -> Bool
- Parameters:
order— solution polynomial order (default4).anisotropy— anisotropy parameter (default1.0).
- Returns:
trueif the solve succeeded. - OCCT:
Plate_Plate::SolveTI. - Example:
let solver = PlateSolver() solver.loadPinpoint(u: 0.5, v: 0.5, position: SIMD3(0.5, 0.5, 1.0)) if solver.solve() { let pt = solver.evaluate(u: 0.5, v: 0.5) }
isDone
Check if the last solve succeeded.
public var isDone: Bool { get }
- OCCT:
Plate_Plate::IsDone.
Evaluation
evaluate(u:v:)
Evaluate the plate at a UV point.
public func evaluate(u: Double, v: Double) -> SIMD3<Double>
Returns the 3D displacement/position computed by the solver. Must call solve() first.
- Parameters:
u,v— UV parameter coordinates. - Returns: The 3D point on the solved plate surface.
- OCCT:
Plate_Plate::Evaluate.
evaluateDerivative(u:v:derivativeOrderU:derivativeOrderV:)
Evaluate a derivative at a UV point.
public func evaluateDerivative(u: Double, v: Double,
derivativeOrderU: Int, derivativeOrderV: Int) -> SIMD3<Double>
- Parameters:
u,v— UV parameter coordinates.derivativeOrderU— U derivative order.derivativeOrderV— V derivative order.
- Returns: The requested partial derivative vector.
- OCCT:
Plate_Plate::EvaluateDerivative.
uvBox
UV bounding box of the constraint points.
public var uvBox: (umin: Double, umax: Double, vmin: Double, vmax: Double) { get }
- Returns: A tuple describing the UV extent of all loaded constraints.
- OCCT:
Plate_Plate::UVBox.
continuity
Continuity order of the plate solution.
public var continuity: Int { get }
- Returns: Integer continuity order of the computed surface.
- OCCT:
Plate_Plate::Continuity.
FillingSurface
Builder for N-sided surface filling using BRepFill_Filling. Creates a smooth surface that satisfies boundary edge constraints and optional interior point constraints. Useful for creating patches that fill holes or connect multiple surface boundaries.
FillingContinuity
Continuity order for filling surface constraints.
public enum FillingContinuity: Int32, Sendable {
case c0 = 0 // Positional continuity (G0)
case c1 = 1 // Tangent continuity (C1)
case c2 = 2 // Curvature continuity (C2)
}
init(degree:pointsOnCurve:maxDegree:maxSegments:tolerance:)
Create a filling surface builder.
public init(degree: Int = 3, pointsOnCurve: Int = 15, maxDegree: Int = 8,
maxSegments: Int = 9, tolerance: Double = 1e-4)
- Parameters:
degree— target polynomial degree (default3).pointsOnCurve— number of discretisation points on each constraint curve (default15).maxDegree— maximum polynomial degree (default8).maxSegments— maximum number of segments (default9).tolerance— 3D tolerance (default1e-4).
- OCCT:
BRepFill_Fillingconstructor. - Example:
let filling = FillingSurface(degree: 3, tolerance: 1e-5)
add(edge:continuity:)
Add a boundary edge constraint.
@discardableResult
public func add(edge: Edge, continuity: FillingContinuity = .c0) -> Bool
- Parameters:
edge— edge to add as a boundary constraint.continuity— continuity order at this edge (default.c0).
- Returns:
trueif the edge was added successfully. - OCCT:
BRepFill_Filling::Add(boundary edge variant). - Example:
let filling = FillingSurface() for e in someWire.edges() { filling.add(edge: e, continuity: .c1) } if let face = filling.build() { print(face.isValid) }
add(freeEdge:continuity:)
Add a free (non-boundary) edge constraint.
@discardableResult
public func add(freeEdge edge: Edge, continuity: FillingContinuity = .c0) -> Bool
Free edges are not required to be topologically connected to other boundary edges.
- Parameters:
freeEdge— edge to add as a free constraint.continuity— continuity order (default.c0).
- Returns:
trueif the edge was added successfully. - OCCT:
BRepFill_Filling::Add(free edge variant).
add(point:)
Add a point constraint that the filling surface must pass through.
@discardableResult
public func add(point: SIMD3<Double>) -> Bool
- Parameters:
point— 3D point the surface must interpolate. - Returns:
trueif the point was added successfully. - OCCT:
BRepFill_Filling::Add(point variant).
build()
Build the filling surface and return the resulting shape.
public func build() -> Shape?
- Returns: The filled face as a
Shape, ornilif building failed. - OCCT:
BRepFill_Filling::Build,BRepFill_Filling::Face. - Example:
let filling = FillingSurface() filling.add(edge: e0, continuity: .c0) filling.add(edge: e1, continuity: .c0) filling.add(edge: e2, continuity: .c0) filling.add(edge: e3, continuity: .c0) if let face = filling.build() { print("G0 error:", filling.g0Error ?? -1) }
isDone
Whether the filling surface has been successfully built.
public var isDone: Bool { get }
- OCCT:
BRepFill_Filling::IsDone.
g0Error
Positional (G0) error of the built surface.
public var g0Error: Double? { get }
- Returns: Maximum distance from the surface to its constraints, or
nilif not yet built. - OCCT:
BRepFill_Filling::G0Error.
g1Error
Tangent (G1) error of the built surface.
public var g1Error: Double? { get }
- Returns: Maximum tangent deviation, or
nilif not yet built. - OCCT:
BRepFill_Filling::G1Error.
g2Error
Curvature (G2) error of the built surface.
public var g2Error: Double? { get }
- Returns: Maximum curvature deviation, or
nilif not yet built. - OCCT:
BRepFill_Filling::G2Error.
LawFunction
An evolution function defining how a scalar value varies along a parameter range. Used with Shape.pipeShellWithLaw() for variable-section sweeps where the cross-section scales smoothly along the spine path.
Evaluation
value(at:)
Evaluate the law function at a given parameter.
public func value(at parameter: Double) -> Double
- Parameters:
parameter— parameter value withinbounds. - Returns: The scalar value of the law at the given parameter.
- OCCT:
Law_Function::Value. - Example:
if let law = LawFunction.linear(from: 1.0, to: 2.0) { print(law.value(at: 0.5)) // ≈ 1.5 }
bounds
Parameter bounds of the law function.
public var bounds: ClosedRange<Double> { get }
- Returns: The
[first, last]parameter range over which the law is defined. - OCCT:
Law_Function::Bounds.
Factory Methods
constant(_:from:to:)
Create a constant law: the value is uniform over [first, last].
public static func constant(_ value: Double, from first: Double = 0,
to last: Double = 1) -> LawFunction?
- Parameters:
value— the constant scalar output.first,last— parameter range (default0...1).
- Returns: A
LawFunction, ornilon failure. - OCCT:
Law_Constant.
linear(from:to:parameterRange:)
Create a linear law: value ramps from startValue to endValue.
public static func linear(from startValue: Double, to endValue: Double,
parameterRange: ClosedRange<Double> = 0...1) -> LawFunction?
- Parameters:
startValue— value atparameterRange.lowerBound.endValue— value atparameterRange.upperBound.parameterRange— parametric domain (default0...1).
- Returns: A
LawFunction, ornilon failure. - OCCT:
Law_Linear. - Example:
// Scale profile from radius 1 to radius 3 along the sweep if let law = LawFunction.linear(from: 1.0, to: 3.0) { let shape = baseShape.pipeShellWithLaw(spine: spineCurve, law: law) }
sCurve(from:to:parameterRange:)
Create an S-curve law: smooth sigmoid transition between start and end values.
public static func sCurve(from startValue: Double, to endValue: Double,
parameterRange: ClosedRange<Double> = 0...1) -> LawFunction?
- Parameters:
startValue— value at the start of the range.endValue— value at the end of the range.parameterRange— parametric domain (default0...1).
- Returns: A
LawFunction, ornilon failure. - OCCT:
Law_S.
interpolate(points:periodic:)
Create an interpolated law from (parameter, value) pairs.
public static func interpolate(points: [(parameter: Double, value: Double)],
periodic: Bool = false) -> LawFunction?
- Parameters:
points— array of(parameter, value)tuples in ascending parameter order; must have at least 2 elements.periodic— whether the law is periodic (defaultfalse).
- Returns: A
LawFunction, ornilon failure. - OCCT:
Law_Interpol.
bspline(poles:knots:multiplicities:degree:)
Create a BSpline law from control poles and knot vector.
public static func bspline(poles: [Double], knots: [Double],
multiplicities: [Int32],
degree: Int) -> LawFunction?
- Parameters:
poles— control point values (1D); at least 2 required.knots— knot values; at least 2 required.multiplicities— knot multiplicities matchingknots.degree— polynomial degree.
- Returns: A
LawFunction, ornilon failure. - OCCT:
Law_BSpline.
v0.68.0: Composite Law and Knot Splitting
composite(laws:range:)
Create a composite law by stitching multiple sub-laws together.
public static func composite(laws: [LawFunction],
range: ClosedRange<Double> = 0...1) -> LawFunction?
- Parameters:
laws— array of sub-law functions in parameter order; at least 1 required.range— overall parametric range (default0...1).
- Returns: A composite
LawFunction, ornilon failure. - OCCT:
Law_Composite.
knotSplitting(continuityOrder:)
Find knot indices where a BSpline law drops below given continuity.
public func knotSplitting(continuityOrder: Int = 2) -> [Int]
Only works on BSpline-based law functions created via bspline(poles:knots:multiplicities:degree:).
- Parameters:
continuityOrder— continuity level to check (0=C0,1=C1,2=C2). - Returns: Array of knot indices where continuity breaks, or empty array if none or if the function is not BSpline-based.
- OCCT:
Law_BSplineKnotSplitting.
PolynomialRoots
Results from polynomial root solving.
roots
The real roots found, sorted ascending.
public let roots: [Double]
count
Number of real roots found.
public var count: Int { get }
- Returns:
roots.count.
PolynomialSolver
Analytical polynomial solvers for degrees 2–4. Uses OCCT’s numerically stable math_DirectPolynomialRoots implementation with Newton-Raphson refinement and degenerate-case handling. All methods are static; there is no instance to create.
quadratic(a:b:c:)
Solve a quadratic equation: ax² + bx + c = 0.
public static func quadratic(a: Double, b: Double, c: Double) -> PolynomialRoots
- Parameters:
a,b,c— coefficients (highest degree first). - Returns:
PolynomialRootscontaining 0, 1, or 2 real roots sorted ascending. - OCCT:
math_DirectPolynomialRoots(degree-2 constructor). - Example:
// x² - 5x + 6 = 0 → x = 2, 3 let r = PolynomialSolver.quadratic(a: 1, b: -5, c: 6) print(r.roots) // [2.0, 3.0]
cubic(a:b:c:d:)
Solve a cubic equation: ax³ + bx² + cx + d = 0.
public static func cubic(a: Double, b: Double, c: Double, d: Double) -> PolynomialRoots
- Parameters:
a,b,c,d— coefficients (highest degree first). - Returns:
PolynomialRootscontaining 1, 2, or 3 real roots sorted ascending. - OCCT:
math_DirectPolynomialRoots(degree-3 constructor). - Example:
// x³ - 6x² + 11x - 6 = 0 → x = 1, 2, 3 let r = PolynomialSolver.cubic(a: 1, b: -6, c: 11, d: -6) print(r.roots) // [1.0, 2.0, 3.0]
quartic(a:b:c:d:e:)
Solve a quartic equation: ax⁴ + bx³ + cx² + dx + e = 0.
public static func quartic(a: Double, b: Double, c: Double, d: Double, e: Double) -> PolynomialRoots
- Parameters:
a,b,c,d,e— coefficients (highest degree first). - Returns:
PolynomialRootscontaining 0–4 real roots sorted ascending. - OCCT:
math_DirectPolynomialRoots(degree-4 constructor). - Example:
// x⁴ - 10x² + 9 = 0 → x = -3, -1, 1, 3 let r = PolynomialSolver.quartic(a: 1, b: 0, c: -10, d: 0, e: 9) print(r.roots) // [-3.0, -1.0, 1.0, 3.0]
KDTree
A KD-tree for fast spatial queries on 3D point sets. Wraps OCCT’s NCollection_KDTree to provide efficient nearest-neighbor, k-nearest, range, and box queries. Build once from an array of points, then query repeatedly.
init?(points:)
Build a KD-tree from an array of 3D points.
public init?(points: [SIMD3<Double>])
- Parameters:
points— the points to index; must be non-empty. - Returns: A KD-tree, or
nilif the input is empty or construction fails. - OCCT:
NCollection_KDTree<gp_Pnt, 3>. - Example:
let pts: [SIMD3<Double>] = [ SIMD3(0, 0, 0), SIMD3(1, 0, 0), SIMD3(0, 1, 0) ] if let tree = KDTree(points: pts) { let nearest = tree.nearest(to: SIMD3(0.1, 0.1, 0)) }
Queries
nearest(to:)
Find the nearest point to a query location.
public func nearest(to point: SIMD3<Double>) -> (index: Int, distance: Double)?
- Parameters:
point— the query point. - Returns: A
(index, distance)tuple whereindexis the 0-based index into the originalpointsarray anddistanceis the Euclidean distance, ornilon error. - OCCT:
NCollection_KDTree::FindNearest. - Example:
if let (idx, dist) = tree.nearest(to: SIMD3(0.4, 0.4, 0)) { print("Nearest index \(idx), distance \(dist)") }
kNearest(to:k:)
Find the K nearest points to a query location.
public func kNearest(to point: SIMD3<Double>, k: Int) -> [(index: Int, squaredDistance: Double)]
- Parameters:
point— the query point.k— number of neighbors to find.
- Returns: Array of
(index, squaredDistance)tuples sorted by distance. Note: distances are squared. - OCCT:
NCollection_KDTreek-nearest query. - Example:
let neighbors = tree.kNearest(to: SIMD3(0.5, 0.5, 0), k: 3) for (idx, sqDist) in neighbors { print("index \(idx), dist \(sqDist.squareRoot())") }
rangeSearch(center:radius:maxResults:)
Find all points within a sphere.
public func rangeSearch(center: SIMD3<Double>, radius: Double, maxResults: Int = 1000) -> [Int]
- Parameters:
center— center of the search sphere.radius— radius of the search sphere.maxResults— maximum number of results (default1000).
- Returns: Array of 0-based indices of points within the sphere.
- OCCT:
NCollection_KDTreerange query. - Example:
let nearby = tree.rangeSearch(center: .zero, radius: 1.5) print("\(nearby.count) points within radius 1.5")
boxSearch(min:max:maxResults:)
Find all points within an axis-aligned bounding box.
public func boxSearch(min: SIMD3<Double>, max: SIMD3<Double>, maxResults: Int = 1000) -> [Int]
- Parameters:
min— minimum corner of the box.max— maximum corner of the box.maxResults— maximum number of results (default1000).
- Returns: Array of 0-based indices of points within the box.
- OCCT:
NCollection_KDTreebox query. - Example:
let inBox = tree.boxSearch(min: SIMD3(0, 0, 0), max: SIMD3(1, 1, 1))