Surface. Advanced Construction
This page covers the higher-level surface construction APIs: energy-minimising plate and NLPlate surfaces, Bezier and BSpline fills, face-from-surface conversions, constrained geometry factories, and GeomTools persistence. For the core Surface type (properties, analytic primitives, swept surfaces, evaluation), see the main Surface page.
Topics
- Advanced Plate Surfaces · Bezier Surface Fill · BSpline Fill from Boundary Curves · Face from Surface · Constrained Construction, Knot Splitting, Conversions · LocalAnalysis · GeomFill NSections · NLPlate G2/G3, IncrementalSolve, GeomFill_Generator · Extrema, gce Factories, GeomTools Persistence
Advanced Plate Surfaces (v0.23.0)
Surface.plateThrough(_:degree:tolerance:)
Creates a plate surface (parametric BSpline) that passes through a set of unordered 3D points.
public static func plateThrough(
_ points: [SIMD3<Double>],
degree: Int = 3,
tolerance: Double = 0.01
) -> Surface?
Unlike the grid-based fromPointGrid, the points need no ordering or row/column counts, suitable for scattered probe data, feature points, or any unstructured point set. See also the Surfaces from Points cookbook page.
- Parameters:
points, 3D point cloud (minimum 3);degree, maximum polynomial degree;tolerance, approximation tolerance. - Returns: BSpline surface, or
nilif fewer than 3 points orGeomPlate_MakeApproxfails. - OCCT:
GeomPlate_BuildPlateSurface+GeomPlate_PointConstraint+GeomPlate_MakeApprox. - Example:
let points: [SIMD3<Double>] = [ SIMD3(0, 0, 0), SIMD3(10, 0, 1), SIMD3(10, 10, 2), SIMD3(0, 10, 1), SIMD3(5, 5, 3), ] if let plate = Surface.plateThrough(points, degree: 3, tolerance: 0.01) { let face = plate.toFace() }
What tolerance means here (#571)
tolerance bounds the distance between the fitted BSpline and the plate at the plate’s own constraint points, and the approximator subdivides into more Bezier patches until it is met. Before #571 it could not be met: five of the six plate entry points capped the approximation at a single patch, which is the one value that stops AdvApp2Var_ApproxAFunc2Var from acting on its own error criterion, so a violated tolerance and a satisfied one produced the same surface. A 25-point wavy plate asked for tolerance: 0.01 came back deviating 0.072.
Verify it rather than assume it, the fit is a least-squares approximation, not an interpolation, and a plate that genuinely cannot be fitted still returns its best effort:
let points: [SIMD3<Double>] = (0..<5).flatMap { i -> [SIMD3<Double>] in
(0..<5).map { j -> SIMD3<Double> in
let x = Double(i) * 4
let y = Double(j) * 4
let z = 4 * sin(Double(i) * 1.3) * cos(Double(j) * 1.1)
return SIMD3(x, y, z)
}
}
if let plate = Surface.plateThrough(points, degree: 3, tolerance: 0.01) {
let worst = points.compactMap { plate.projectPoint($0)?.distance }.max() ?? 0
print(worst) // 0.0032, inside tolerance
print(plate.uPoleCount) // 16, more than one degree-8 patch, so it did subdivide
}
uPoleCount <= degreeCap + 1 (9 at the default cap of 8) means the fit never subdivided.
nlPlateDeformed(constraints:resolutionOrder:tolerance:)
Deforms this surface to pass through target positions using the non-linear plate solver (NLPlate G0, position-only constraints).
public func nlPlateDeformed(
constraints: [(uv: SIMD2<Double>, target: SIMD3<Double>)],
resolutionOrder: Int = 4,
tolerance: Double = 1e-3
) -> Surface?
Each constraint pins the surface at a (u, v) parameter to a desired 3D location. The result preserves the original shape except where pulled by the constraints. Not a displacement field: NLPlate_NLPlate::Evaluate seeds its accumulator with the input surface’s own value before summing the plates, so it returns the deformed point rather than an offset from the input, which is what the nlPlateDerivative entry below spells out. (#811)
- Parameters:
constraints, array of(uv, target)pairs (non-empty);resolutionOrder, the plate’s resolution order, 2 through 9;tolerance, the sampling density between the constraints (a tighter value samples more finely: 20 nodes per direction at 0.1, 63 at 1e-3, at most 80); it does not bound the error between samples. - Returns: New deformed surface, or
nilif the array is empty,resolutionOrderis outside2...9, or the solver fails. - OCCT:
NLPlate_NLPlate+NLPlate_HPG0Constraint+Geom_RectangularTrimmedSurface+BSplCLib::Interpolate. - Example:
let plane = Surface.plane(origin: .zero, normal: SIMD3(0, 0, 1))! if let bumped = plane.nlPlateDeformed( constraints: [(uv: SIMD2(0, 0), target: SIMD3(0, 0, 5))], resolutionOrder: 4, tolerance: 1e-3 ) { let face = bumped.toFace() } - Note: The result is a fresh cubic BSpline that interpolates a sample lattice of the solved plate. Every constraint’s
(u, v)is a node of the lattice, so the result passes through each target (to the kernel’s own residual, about 5e-6 for several G0 targets), which the earlier least-squares refit did not (#3133). It carries the parametrisation of the working domain the samples were taken over, so the(u, v)a constraint was written at addresses the same place on the result as it did on the input. For a direction the input surface already bounds, the working domain is the input’s own range; for one it leaves unbounded, it is the span of the constraint parameters padded by 10 in each direction. A periodic input still comes back non-periodic, and the sample grid is not caller-adjustable: seeocctswift-wrapping-gaps.mdfor both. (#1046)
nlPlateDeformedG1(constraints:resolutionOrder:tolerance:)
Deforms this surface with position and tangent constraints (NLPlate G0+G1).
public func nlPlateDeformedG1(
constraints: [(uv: SIMD2<Double>, target: SIMD3<Double>,
tangentU: SIMD3<Double>, tangentV: SIMD3<Double>)],
resolutionOrder: Int = 4,
tolerance: Double = 1e-3
) -> Surface?
Extends nlPlateDeformed by also constraining the partial derivatives (tangent vectors) in the U and V directions at each constraint point. Use to enforce tangency continuity at the constrained locations.
- Parameters:
constraints, array of(uv, target, tangentU, tangentV)tuples (non-empty);resolutionOrder, the plate’s resolution order, 2 through 9;tolerance, the sampling density between the constraints (a tighter value samples more finely: 20 nodes per direction at 0.1, 63 at 1e-3, at most 80); it does not bound the error between samples. - Returns: New deformed surface, or
nilif the array is empty,resolutionOrderis outside2...9, or the solver fails. - OCCT:
NLPlate_NLPlate+NLPlate_HPG0G1Constraint+Plate_D1+Geom_RectangularTrimmedSurface+BSplCLib::Interpolate. - Note: The returned parametrisation and working domain are as described for
nlPlateDeformed(constraints:resolutionOrder:tolerance:). The result passes through each target position (#3135: two position+tangent constraints used to returnnil, and one used to miss by 0.7), but the tangent targets are met by the solver and not reproduced by the interpolant: see the wrapping-gaps entry. - Example:
if let shaped = plane.nlPlateDeformedG1( constraints: [( uv: SIMD2(0.5, 0.5), target: SIMD3(5, 5, 2), tangentU: SIMD3(1, 0, 0), tangentV: SIMD3(0, 1, 0) )], resolutionOrder: 4, tolerance: 1e-3 ) { let face = shaped.toFace() }
Bezier Surface Fill (v0.31.0)
BezierFillStyle
Filling style for Bezier surface construction from boundary curves.
public enum BezierFillStyle: Int32, Sendable {
case stretch = 0
case coons = 1
case curved = 2
}
Case meanings, from GeomFill_FillingStyle:
BezierFillStyle.stretch
The style with the flattest patches.
BezierFillStyle.coons
A rounded style of patch, with less depth than curved.
BezierFillStyle.curved
The style with the most rounded patches.
Surface.bezierFill(_:_:_:_:style:)
Creates a Bezier surface by filling 4 Bezier boundary curves.
public static func bezierFill(
_ c1: Curve3D, _ c2: Curve3D, _ c3: Curve3D, _ c4: Curve3D,
style: BezierFillStyle = .stretch
) -> Surface?
All four curves must be Bezier, and together they must form a closed loop. They do not have to be given in loop order and do not have to run the same way round it: GeomFill_BezierCurves arranges them itself, walking them head to tail at Precision::Confusion() (1e-7) and reversing whichever curve runs backwards. Only c1 is pinned, and its own direction becomes U.
A set that does not close returns nil. OCCT refuses it with Standard_ConstructionError (“Courbes non jointives”), but that refusal is Standard_ConstructionError_Raise_if and is compiled out of the kernel this package ships, where a non-joining set was an uncatchable crash until #2829 restored the check in the bridge. .coons raises every side to at least degree 3 first, so it has no minimum pole count of its own here.
- Parameters:
c1, whose direction becomes U;c2–c4, the rest of the loop in any order and either direction;style, fill style. - Returns: Bezier surface, or
nilif any curve is not Bezier, the four do not close a loop, or the fill fails. - OCCT:
GeomFill_BezierCurves(4-curve constructor). - Example:
// c1..c4 are Bezier curves forming a closed loop, in any order if let surf = Surface.bezierFill(c1, c2, c3, c4, style: .coons) { let face = surf.toFace() }
Surface.bezierFill(_:_:_:style:)
Creates a Bezier surface from 3 Bezier boundary curves, closing the fourth side with a straight chord.
public static func bezierFill(
_ c1: Curve3D, _ c2: Curve3D, _ c3: Curve3D,
style: BezierFillStyle = .stretch
) -> Surface?
c2 is the middle curve, and the position is not negotiable. GeomFill_BezierCurves::Init(C1, C2, C3, Type) builds the missing fourth side as a two-pole segment between the far ends of c1 and c3, where “far” means the endpoint that does not meet c2 (GeomFill_BezierCurves.cxx:304-335), and then runs the four-curve construction. Put one of the outer curves in that slot and the synthesised side closes nothing.
The direction of each of the three is free: the far-end test answers the same either way round, and the four-curve Arrange reverses whatever still runs backwards. Measured over all 8 direction combinations and all 3 choices of middle slot in Scripts/repro/2841/.
A wrong middle returns nil. OCCT refuses it with Standard_ConstructionError, and that refusal is compiled out of the kernel this package ships, where it was an uncatchable crash until #2841 added the guard (#2842). .coons raises every side to at least degree 3 first, so unlike the BSpline flavour it works even on two-pole sides.
The result is a four-sided patch with one straight side, not a triangle. For a genuine three-sided patch, collapse one side to a point and use the four-curve overload.
- Parameters:
c1andc3, the outer boundaries, either direction;c2, the middle boundary, adjacent to both;style, fill style. - Returns: Bezier surface, or
nilif any curve is not Bezier,c2is not adjacent to both of the others, or the fill fails. - OCCT:
GeomFill_BezierCurves(3-curve constructor). - Example:
// Three sides of a square. The fourth, from (0, 10, 0) back to (0, 0, 0), is synthesised. let a = SIMD3<Double>(0, 0, 0), b = SIMD3<Double>(10, 0, 0) let c = SIMD3<Double>(10, 10, 0), d = SIMD3<Double>(0, 10, 0) let side = { (p: SIMD3<Double>, q: SIMD3<Double>) -> Curve3D? in let step = (q - p) / 3 return Curve3D.bezier(poles: [p, p + step, p + step * 2, q]) } if let bottom = side(a, b), let right = side(b, c), let top = side(c, d), let surf = Surface.bezierFill(bottom, right, top, style: .coons) { print(surf.point(atU: 0, v: 0)) // (0.0, 0.0, 0.0) }
Surface.bezierFill(_:_:style:)
Creates a Bezier surface by filling 2 Bezier boundary curves as opposite edges.
public static func bezierFill(
_ c1: Curve3D, _ c2: Curve3D,
style: BezierFillStyle = .stretch
) -> Surface?
- Parameters:
c1,c2, two opposing Bezier boundary curves;style, fill style. - Returns: Bezier surface, or
nilon failure. - OCCT:
GeomFill_BezierCurves(2-curve constructor). - Example:
if let surf = Surface.bezierFill(bottom, top, style: .coons) { let face = surf.toFace() }
BSpline Fill from Boundary Curves (v0.43.0)
Surface.FillStyle
Filling style for BSpline surface construction from boundary curves.
public enum FillStyle: Int32, Sendable {
case stretch = 0
case coons = 1
case curved = 2
}
| Case | Meaning |
|---|---|
stretch | Flattest result: minimal curvature between the boundaries. |
coons | Coons-style blending: moderate curvature. |
curved | Most curved result: maximum curvature. |
(Per-case anchors below, for cross-reference; the table above has the actual meaning of each.)
curved
Surface.bsplineFill(curve1:curve2:style:)
Creates a BSpline surface spanning between 2 boundary curves.
public static func bsplineFill(
curve1: Curve3D,
curve2: Curve3D,
style: FillStyle = .coons
) -> Surface?
Each curve is converted to a BSpline first, so any Curve3D will do. The result spans from curve1 to curve2 in the V direction. See also the Gordon Surfaces cookbook for the full fill-vs-Gordon decision guide.
.stretch and .coons take the two curves as opposite edges; .curved takes them as two sides meeting at a corner and returns nil if they share no endpoint. OCCT runs no Arrange on a pair, and its .curved branch refuses a disjoint pair with a real throw Standard_OutOfRange (GeomFill_BSplineCurves.cxx:545) that the bridge turns into nil. The default is .coons, so switching a working call to .curved can start returning nil on the same two curves. Measured in Scripts/repro/2829-geomfill-arrange-guard/, section [I].
- Parameters:
curve1,curve2, the two boundary curves;style, fill style, where.curvedadditionally requires them to share an endpoint. - Returns: BSpline surface, or
nilif the fill fails, including a.curvedrequest for two curves that share no endpoint. - OCCT:
GeomFill_BSplineCurves(2-curve constructor). - Example:
guard let c1 = Curve3D.interpolate(points: [SIMD3(0,0,0), SIMD3(5,0,2), SIMD3(10,0,0)]), let c2 = Curve3D.interpolate(points: [SIMD3(0,5,0), SIMD3(5,5,3), SIMD3(10,5,0)]) else { return } if let surf = Surface.bsplineFill(curve1: c1, curve2: c2, style: .coons) { let face = surf.toFace() }
Surface.bsplineFill(curves:style:) (3 curves)
Creates a BSpline surface from 3 boundary curves, closing the fourth side with a straight chord.
public static func bsplineFill(
curves: (Curve3D, Curve3D, Curve3D),
style: FillStyle = .coons
) -> Surface?
curves.1 is the middle curve, and the position is not negotiable. GeomFill_BSplineCurves::Init(C1, C2, C3, Type) builds the missing fourth side as a degree-1 two-pole segment between the far ends of curves.0 and curves.2, where “far” means the endpoint that does not meet curves.1 (GeomFill_BSplineCurves.cxx:396-439), and then runs the four-curve construction. Put one of the outer curves in that slot and the synthesised side closes nothing.
The direction of each of the three is free. Measured over all 8 direction combinations and all 3 choices of middle slot, both flavours, in Scripts/repro/2841/: the 16 correct-middle cases all build a surface and the 32 wrong-middle ones all refuse, matching the kernel 48/48.
A wrong middle returns nil. OCCT refuses it with Standard_ConstructionError, and that refusal is compiled out of the kernel this package ships, where it was an uncatchable crash until #2841 added the guard (#2842).
.coons needs at least 4 poles per direction after the knots are aligned, and here the V direction is curves.1 against the synthesised side. Degrees are raised before the knot distributions are aligned, so the synthesised side is lifted to curves.1’s degree first and caps nothing by itself: three cubic sides give a 4 x 4 Coons surface. What refuses is a low-degree middle curve, because there is then nothing to raise it to. Every curve is converted with Convert_QuasiAngular on the way in, which takes a quarter arc from 3 poles to 7, so an arc boundary does not trip the Coons minimum here even though OCCT’s default conversion of the same arc would.
The result is a four-sided patch with one straight side, not a triangle. For a genuine three-sided patch, collapse one side to a point and use the four-curve overload, whose Arrange accepts a degenerate side.
- Parameters:
curves, the three boundaries, withcurves.1adjacent to both others within 1e-7 and each individual direction free;style, fill style. - Returns: BSpline surface, or
nilifcurves.1is not adjacent to both of the others,.coonswas asked for too few poles, or the fill fails. - OCCT:
GeomFill_BSplineCurves(3-curve constructor). - Example:
// A quarter patch on a cylinder of radius 5: the bottom arc, one seam, the top arc. // The second seam, from (5, 0, 8) back to (5, 0, 0), is the synthesised chord. let p0 = SIMD3<Double>(5, 0, 0), p1 = SIMD3<Double>(0, 5, 0) let p2 = SIMD3<Double>(0, 5, 8), p3 = SIMD3<Double>(5, 0, 8) let mid = 5 * 0.7071067811865476 if let bottom = Curve3D.arc(through: p0, SIMD3(mid, mid, 0), p1), let seam = Curve3D.bezier(poles: [p1, p2]), let top = Curve3D.arc(through: p3, SIMD3(mid, mid, 8), p2), let surf = Surface.bsplineFill(curves: (bottom, seam, top), style: .curved) { let d = surf.domain print(surf.point(atU: d.uMin, v: d.vMin)) // (5.0, 0.0, 0.0) }
Surface.bsplineFill(curves:style:) (4 curves)
Creates a BSpline surface bounded by 4 boundary curves.
public static func bsplineFill(
curves: (Curve3D, Curve3D, Curve3D, Curve3D),
style: FillStyle = .coons
) -> Surface?
The four curves must together form a closed loop. Each is converted to a BSpline first, so any Curve3D will do, and the order and the individual directions do not matter: a private Arrange walks them head to tail at Precision::Confusion() (1e-7), swapping slots and reversing whichever curve runs backwards, then aligns their degrees and knots. Only curves.0 is pinned, and its own direction becomes U. All 24 permutations and 16 direction flips of one square give the same surface, measured in Scripts/repro/2829-geomfill-arrange-guard/.
A set that does not close returns nil. OCCT’s own refusal is Standard_ConstructionError_Raise_if, which -DNo_Exception compiles out of the kernel this package ships, so a non-joining set was an uncatchable crash until #2829 restored the check in the bridge. One side collapsed to a point is still accepted, which is how a triangular patch is expressed.
.coons additionally needs at least 4 poles per direction once the knots are aligned and returns nil below that, so a boundary of straight two-pole segments or a three-pole arc needs .curved or .stretch.
- Parameters:
curves, the four boundary curves, closing a loop within 1e-7, in any order and either direction, except thatcurves.0’s direction becomes U;style, fill style. - Returns: BSpline surface, or
nilif the four do not close a loop,.coonswas asked for too few poles, or the fill fails. - OCCT:
GeomFill_BSplineCurves(4-curve constructor). - Example:
// A quarter patch on a cylinder of radius 5: two arcs on a periodic support, two seams. let p0 = SIMD3<Double>(5, 0, 0), p1 = SIMD3<Double>(0, 5, 0) let p2 = SIMD3<Double>(0, 5, 8), p3 = SIMD3<Double>(5, 0, 8) let mid = 5 * 0.7071067811865476 if let bottom = Curve3D.arc(through: p0, SIMD3(mid, mid, 0), p1), let top = Curve3D.arc(through: p3, SIMD3(mid, mid, 8), p2), let left = Curve3D.bezier(poles: [p1, p2]), let right = Curve3D.bezier(poles: [p3, p0]), // Slots in no particular order; Arrange sorts them out. let surf = Surface.bsplineFill(curves: (bottom, top, right, left), style: .curved) { let face = surf.toFace() print(face != nil) }
Face from Surface (v0.33.0)
toFace(tolerance:)
Creates a face from this surface using its full parameter domain.
public func toFace(tolerance: Double = 1e-6) -> Shape?
Delegates to Shape.face(from:uRange:vRange:tolerance:) with the full domain.uMin...uMax and domain.vMin...vMax ranges. The standard way to convert a parametric Surface to a renderable or sewable Shape.
- Parameters:
tolerance, face creation tolerance. - Returns: Face shape covering the surface’s full UV domain, or
nilon failure. - OCCT:
BRepBuilderAPI_MakeFace(viaShape.face). - Example:
if let surf = Surface.plateThrough(pts), let face = surf.toFace() { let mesh = face.mesh(linearDeflection: 0.1, angularDeflection: 0.3) }
toFace(uRange:vRange:tolerance:)
Creates a face from this surface with specific UV parameter bounds.
public func toFace(
uRange: ClosedRange<Double>,
vRange: ClosedRange<Double>,
tolerance: Double = 1e-6
) -> Shape?
Use to cut out a rectangular UV sub-patch of the surface. For a non-rectangular region, use toFace(uvBoundary:).
- Parameters:
uRange, U parameter range;vRange, V parameter range;tolerance, tolerance. - Returns: Face shape, or
nilon failure. - OCCT:
BRepBuilderAPI_MakeFace(viaShape.face). - Example:
if let face = surf.toFace(uRange: 0.0...0.5, vRange: 0.0...1.0) { // half of the surface in U }
toFace(uvBoundary:)
Creates a face trimmed to a non-rectangular region defined by a closed boundary polygon in UV space.
public func toFace(uvBoundary: [SIMD2<Double>]) -> Shape?
Each segment of the polygon becomes a 2D edge carrying a pcurve on the surface, so the face footprint follows the polygon exactly, unlike toFace(uRange:vRange:), which only makes rectangular UV patches. Ideal for reconstructing a fitted analytic surface trimmed to the real boundary of a region.
- Parameters:
uvBoundary, closed polygon of (u, v) points (minimum 3; the closing segment is implicit). - Returns: The trimmed face, or
nilif fewer than 3 points or construction fails. - OCCT:
OCCTShapeCreateFaceFromSurfaceUVPolygon→BRepBuilderAPI_MakeFacewith pcurve-backed edges. - Example:
// Triangle trimmed from a BSpline surface in UV space let boundary = [SIMD2(0.1, 0.1), SIMD2(0.9, 0.1), SIMD2(0.5, 0.8)] if let face = surf.toFace(uvBoundary: boundary) { let mesh = face.mesh(linearDeflection: 0.1, angularDeflection: 0.3) }
Constrained Geometry Construction, Knot Splitting, Conversions (v0.50.0)
Surface.conicalSurface(origin:direction:semiAngle:radius:)
Creates an infinite conical surface from an axis placement, semi-angle, and base radius.
public static func conicalSurface(
origin: SIMD3<Double> = .zero,
direction: SIMD3<Double> = SIMD3(0, 0, 1),
semiAngle: Double,
radius: Double
) -> Surface?
- Parameters:
origin, apex axis origin;direction, axis direction;semiAngle, half-angle in radians (must be in (0, π/2));radius, base radius at the origin. - Returns: Conical surface, or
nilifsemiAngleis out of range or construction fails. - OCCT:
Geom_ConicalSurfacewithgp_Ax3. - Example:
if let cone = Surface.conicalSurface(semiAngle: .pi / 6, radius: 5) { let face = cone.toFace(uRange: 0...2 * .pi, vRange: 0...20) }
Surface.conicalSurface(point1:point2:r1:r2:)
Creates a conical surface passing through two points with specified radii.
public static func conicalSurface(
point1: SIMD3<Double>,
point2: SIMD3<Double>,
r1: Double,
r2: Double
) -> Surface?
The axis runs from point1 to point2. The cone is defined by radius r1 at point1 and r2 at point2.
- Parameters:
point1, first axis point;point2, second axis point;r1, radius atpoint1;r2, radius atpoint2. - Returns: Conical surface, or
nilon failure. - OCCT:
Geom_ConicalSurfacederived from the two-point-radii geometry. - Example:
if let cone = Surface.conicalSurface( point1: SIMD3(0, 0, 0), point2: SIMD3(0, 0, 10), r1: 5, r2: 2 ) { let face = cone.toFace(uRange: 0...2 * .pi, vRange: 0...10) }
Surface.cylindricalSurface(origin:direction:radius:)
Creates a cylindrical surface from an axis and radius.
public static func cylindricalSurface(
origin: SIMD3<Double> = .zero,
direction: SIMD3<Double> = SIMD3(0, 0, 1),
radius: Double
) -> Surface?
- Parameters:
origin, axis base point;direction, axis direction;radius, cylinder radius. - Returns: Cylindrical surface, or
nilon failure. - OCCT:
Geom_CylindricalSurfacewithgp_Ax3. - Example:
if let cyl = Surface.cylindricalSurface(radius: 5) { let face = cyl.toFace(uRange: 0...2 * .pi, vRange: 0...20) }
Surface.cylindricalSurface(point1:point2:point3:)
Creates a cylindrical surface through three points.
public static func cylindricalSurface(
point1: SIMD3<Double>,
point2: SIMD3<Double>,
point3: SIMD3<Double>
) -> Surface?
The axis passes through point1 and point2; the radius is the distance from point3 to the axis.
- Parameters:
point1, first axis point;point2, second axis point;point3, point defining the radius. - Returns: Cylindrical surface, or
nilon failure. - OCCT:
Geom_CylindricalSurfacefrom the three-point geometry. - Example:
if let cyl = Surface.cylindricalSurface( point1: SIMD3(0, 0, 0), point2: SIMD3(0, 0, 10), point3: SIMD3(5, 0, 0) ) { let face = cyl.toFace(uRange: 0...2 * .pi, vRange: 0...10) }
Surface.planeFromPoints(_:_:_:)
Creates a plane surface through three points. Canonical implementation for the 3-point constructor pair, planeFrom3Points(p1:p2:p3:) (below) is a labeled-argument spelling and delegates here (#421); a ground-truth check against the pinned OCCT headers confirmed GC_MakePlane and gce_MakePln’s 3-point overloads agree on every case tried (well-separated, collinear, and coincident points).
public static func planeFromPoints(
_ point1: SIMD3<Double>,
_ point2: SIMD3<Double>,
_ point3: SIMD3<Double>
) -> Surface?
- Parameters:
point1,point2,point3, three non-collinear points. - Returns: Plane surface, or
nilif points are collinear (including coincident). - OCCT:
GC_MakePlane(gp_Pnt, gp_Pnt, gp_Pnt). - Example:
if let plane = Surface.planeFromPoints( SIMD3(0, 0, 0), SIMD3(10, 0, 0), SIMD3(0, 10, 0) ) { let face = plane.toFace(uRange: 0...10, vRange: 0...10) }
Surface.planeFromPointNormal(point:normal:)
Creates a plane surface from a point and normal direction. Canonical implementation for the point+normal constructor pair, Surface.plane(origin:normal:) is an unlabeled-positional spelling and delegates here (#421).
public static func planeFromPointNormal(
point: SIMD3<Double>,
normal: SIMD3<Double>
) -> Surface?
- Parameters:
point, a point on the plane;normal, plane normal direction. - Returns: Plane surface, or
nilifnormalhas zero (or near-zero) length. - OCCT:
GC_MakePlane(gp_Pnt, gp_Dir). - Example:
if let plane = Surface.planeFromPointNormal( point: SIMD3(0, 0, 5), normal: SIMD3(0, 0, 1) ) { let face = plane.toFace(uRange: -10...10, vRange: -10...10) }
Surface.trimmedCone(point1:point2:r1:r2:)
Creates a trimmed (bounded) conical surface between two endpoints with specified radii.
public static func trimmedCone(
point1: SIMD3<Double>,
point2: SIMD3<Double>,
r1: Double,
r2: Double
) -> Surface?
Unlike conicalSurface, the result is a Geom_RectangularTrimmedSurface already bounded to the axial extent between point1 and point2.
- Parameters:
point1, base centre (with radiusr1);point2, top centre (with radiusr2);r1,r2, respective radii. - Returns: Bounded trimmed conical surface, or
nilon failure. - OCCT:
GC_MakeTrimmedCone. - Example:
if let cone = Surface.trimmedCone( point1: SIMD3(0, 0, 0), point2: SIMD3(0, 0, 10), r1: 5, r2: 2 ) { let face = cone.toFace() }
Surface.trimmedCylinder(origin:direction:radius:height:)
Creates a trimmed (bounded) cylindrical surface from axis, radius, and height.
public static func trimmedCylinder(
origin: SIMD3<Double> = .zero,
direction: SIMD3<Double> = SIMD3(0, 0, 1),
radius: Double,
height: Double
) -> Surface?
- Parameters:
origin, base centre;direction, axis direction;radius, cylinder radius;height, axial height (negative = downward). - Returns: Bounded trimmed cylindrical surface, or
nilon failure. - OCCT:
GC_MakeTrimmedCylinder. - Example:
if let cyl = Surface.trimmedCylinder(radius: 5, height: 20) { let face = cyl.toFace() }
knotSplitting(uContinuity:vContinuity:)
Analyses where a BSpline surface would need to be split to achieve a given continuity level.
public func knotSplitting(
uContinuity: ParametricContinuity = .c1,
vContinuity: ParametricContinuity = .c1
) -> KnotSplitResult
Returns the number of U and V splits needed, the actual U/V parameter values at each split, and the knot-table indices those parameters were read from; does not modify the surface. Each direction’s own first and last knots are always included, so a direction that never drops below the requested continuity reports exactly those two rather than nothing.
- Parameters:
uContinuity: minimum continuity to require of each U patch;vContinuity: the same against the V degree and V knots. - Returns:
KnotSplitResultwithuSplitCount/vSplitCount,uSplitParams/vSplitParams(ascending, bounded by the surface’s own U/V domain) anduSplitIndices/vSplitIndices(1-based into the surface’s own knot tables, souSplitParams[i] == bsplineUKnot(index: uSplitIndices[i])). - OCCT:
GeomConvert_BSplineSurfaceKnotSplitting, the sole wrapper of it, since #562 deleted the second family (bsplineKnotSplitsU/bsplineKnotSplitsV/bsplineKnotSplitValues) that also drove it. The indices are what that family carried and this call previously discarded. - Continuity range (#480): the continuity is a derivative order, and a knot splits only when
degree - multiplicity < continuity. So the meaningful range is0...degreeand it saturates there. A bicubic surface with simple interior knots is already C2 at every interior knot, which means.c0,.c1and.c2all report just the two bounding curves per direction and.c3is the order that reports the interior ones. On a degree-4-or-higher surface.c3is the strictest question this vocabulary can ask;toBezierPatches()is the dedicated API for the every-knot split at the far end of that ladder. - Example:
// Where does the surface actually kink? (multiplicity == degree) let kinks = surf.knotSplitting() // Every interior knot of a bicubic surface. let result = surf.knotSplitting(uContinuity: .c3, vContinuity: .c3) print("U splits needed:", result.uSplitCount, result.uSplitParams) print("V splits needed:", result.vSplitCount, result.vSplitParams) // The raw knot indices, and the identity that ties them to the parameters. print(result.uSplitIndices.map { surf.bsplineUKnot(index: $0) } == result.uSplitParams) // true
Surface.joinBezierPatches(_:rows:cols:)
Joins a rectangular grid of Bezier surface patches into a single BSpline surface.
public static func joinBezierPatches(
_ patches: [Surface],
rows: Int,
cols: Int
) -> Surface?
Adjacent patches must share boundary curves. The patches array is row-major (patches[v * cols + u]).
Every patch must be non-rational in both directions. GeomConvert_CompBezierSurfacesToBSplineSurface has no rational path at all: its own precondition against rational input is compiled out by this project’s Release kernel, so without this check it would silently join the polynomial surface through a rational patch’s control net (dropping its weights) and report success (#725). Weights are never clamped or dropped: a rational patch is refused outright, not silently approximated.
- Parameters:
patches, 2D array of Bezier surfaces (row-major order; must equalrows * cols);rows, patch row count;cols, patch column count. - Returns: Combined BSpline surface, or
nilif the count doesn’t matchrows * cols, any patch is rational in either direction, or joining fails. - OCCT:
GeomConvert_CompBezierSurfacesToBSplineSurface. - Example:
// Assume patches is a 2×2 grid of Bezier surfaces if let combined = Surface.joinBezierPatches(patches, rows: 2, cols: 2) { let face = combined.toFace() }
Surface.AnalyticalConversion
Result of trying to recognise an analytical surface from a BSpline/Bezier surface.
public struct AnalyticalConversion {
public let surface: Surface
public let gap: Double
}
| Field | Meaning |
|---|---|
surface | The recognised analytical surface (plane, cylinder, cone, sphere, or torus) |
gap | Maximum deviation between surface and the original surface |
Surface.AnalyticalConversion.gap
Maximum deviation between the recognised analytical surface and the original BSpline/Bezier surface.
convertToAnalytical(tolerance:)
Tries to recognise this BSpline or Bezier surface as a plane, cylinder, cone, sphere, or torus.
public func convertToAnalytical(tolerance: Double = 1e-4) -> AnalyticalConversion?
- Parameters:
tolerance, recognition tolerance. - Returns:
AnalyticalConversionwith the recognised surface and itsgap(max deviation), ornilif not recognisable. - OCCT:
ShapeCustom_Surface::ConvertToAnalytical. - Example:
if let result = surf.convertToAnalytical(tolerance: 1e-4) { print("recognised surface, gap:", result.gap) }
Surface.SweptProperties
Accessor for the direction and basis curve of a swept surface (extrusion or revolution). Obtained via Surface.sweptProperties (see Surface Analytic Types); documented separately here because Surface-Advanced.md is this struct’s assigned reference page for its internal storage.
public struct SweptProperties: @unchecked Sendable {
public var direction: SIMD3<Double> { get }
public var basisCurve: Curve3D? { get }
}
splitByContinuity(criterion:tolerance:)
Analyses and splits a BSpline surface at continuity breaks.
public func splitByContinuity(criterion: Int = 2, tolerance: Double = 1e-6) -> ContinuitySplitResult
- Parameters:
criterion, continuity level (0=C0, 1=C1, 2=C2, 3=C3);tolerance, tolerance for continuity checking. - Returns:
ContinuitySplitResultwithwasSplit,alreadyMeetsCriterion,uSplitCount, andvSplitCount. - OCCT:
ShapeUpgrade_SplitSurfaceContinuity via OCCTSurfaceSplitByContinuity. - Example:
let r = surf.splitByContinuity(criterion: 2) if r.alreadyMeetsCriterion { print("surface is already C2") } else { print("needs \(r.uSplitCount) U splits and \(r.vSplitCount) V splits") }
Surface.ContinuitySplitResult
public struct ContinuitySplitResult {
public let wasSplit: Bool
public let alreadyMeetsCriterion: Bool
public let uSplitCount: Int
public let vSplitCount: Int
}
ContinuitySplitResult.wasSplit
Whether the surface was actually split.
ContinuitySplitResult.alreadyMeetsCriterion
Whether the surface already met the requested continuity, so no split was needed.
ContinuitySplitResult.uSplitCount
Number of split values found along U.
ContinuitySplitResult.vSplitCount
Number of split values found along V.
- Parameters:
criterion, continuity level (0=C0, 1=C1, 2=C2, 3=C3);tolerance, tolerance for continuity checking. - Returns:
ContinuitySplitResultwithwasSplit,alreadyMeetsCriterion,uSplitCount, andvSplitCount. - OCCT:
ShapeUpgrade_SplitSurfaceContinuity via OCCTSurfaceSplitByContinuity. - Example:
let r = surf.splitByContinuity(criterion: 2) if r.alreadyMeetsCriterion { print("surface is already C2") } else { print("needs \(r.uSplitCount) U splits and \(r.vSplitCount) V splits") }
LocalAnalysis
continuityWith(_:u1:v1:u2:v2:order:)
Analyses the geometric continuity between this surface at (u1, v1) and another surface at (u2, v2).
public func continuityWith(
_ other: Surface,
u1: Double, v1: Double,
u2: Double, v2: Double,
order: ContinuityClass = .c2
) -> ContinuityAnalysis?
order selects which classes are measured, not just a ceiling, one branch of LocalAnalysis_SurfaceContinuity runs per call, and ContinuityAnalysis.holds(_:) reports nil for anything outside it. See Surface-Analysis for the per-order measured sets and the .c2/second-derivative caveat.
- Parameters:
other, second surface;u1,v1, UV parameters on this surface;u2,v2, UV parameters onother;order, the class to measure (default.c2). - Returns:
ContinuityAnalysis, ornilif analysis fails. - OCCT:
LocalAnalysis_SurfaceContinuity. - Example:
if let ca = surf1.continuityWith(surf2, u1: 1.0, v1: 0.5, u2: 0.0, v2: 0.5, order: .g1) { #expect(ca.holds(.g1) == true) print("C0 gap:", ca.c0Value, "G1 angle:", ca.g1Angle) }
GeomFill_NSections (v0.68.0)
Surface.nSections(curves:params:)
Creates a BSpline surface by lofting through N section curves at specified parameter values.
public static func nSections(curves: [Curve3D], params: [Double]) -> Surface?
curves and params must have the same length (minimum 2). The params array assigns a V parameter to each section, typically in 0...1 order.
- Parameters:
curves, section curves (minimum 2);params, V parameter for each section (same count ascurves). - Returns: BSpline surface, or
nilif counts don’t match orGeomFill_NSectionsfails. - OCCT:
GeomFill_NSections. - Example:
guard let c1 = Curve3D.interpolate(points: [SIMD3(0,0,0), SIMD3(5,0,0), SIMD3(10,0,0)]), let c2 = Curve3D.interpolate(points: [SIMD3(0,5,2), SIMD3(5,5,4), SIMD3(10,5,2)]) else { return } if let surf = Surface.nSections(curves: [c1, c2], params: [0, 1]) { let face = surf.toFace() }
Surface.nSectionsInfo(curves:params:)
Queries the pole count, knot count, and degree that nSections would produce, without building the surface.
public static func nSectionsInfo(
curves: [Curve3D],
params: [Double]
) -> (poleCount: Int, knotCount: Int, degree: Int)?
- Parameters:
curves, section curves (minimum 2);params, V parameters (same count). - Returns: Tuple of
(poleCount, knotCount, degree), ornilon failure. - OCCT:
GeomFill_NSections(introspection without surface extraction). - Example:
if let info = Surface.nSectionsInfo(curves: [c1, c2], params: [0, 1]) { print("poles:", info.poleCount, "degree:", info.degree) }
NLPlate G2/G3, IncrementalSolve, GeomFill_Generator (v0.69.0)
nlPlateDeformedG2(constraints:tolerance:)
Deforms this surface with position, tangent, and curvature constraints (NLPlate G0+G2).
public func nlPlateDeformedG2(
constraints: [(uv: SIMD2<Double>, target: SIMD3<Double>,
tangentU: SIMD3<Double>, tangentV: SIMD3<Double>,
curvatureUU: SIMD3<Double>, curvatureUV: SIMD3<Double>, curvatureVV: SIMD3<Double>)],
tolerance: Double = 1e-3
) -> Surface?
Extends nlPlateDeformedG1 by also constraining the second derivatives (curvature tensors) at each point. Produces curvature-continuous deformations. Each constraint carries 20 doubles.
There is no iteration count. NLPlate_NLPlate::Solve2(ord, InitialConsraintOrder) takes none, and neither does Solve(). IncrementalSolve does, but it is a different solver rather than a bound on this one, and it is wrapped separately as nlPlateDeformedIncremental(constraints:maxOrder:initConstraintOrder:nbIncrements:).
- Parameters:
constraints, array of constraint tuples (non-empty);tolerance, the sampling density between the constraints (a tighter value samples more finely: 20 nodes per direction at 0.1, 63 at 1e-3, at most 80); it does not bound the error between samples. - Returns: New deformed surface, or
nilon failure. - OCCT:
NLPlate_NLPlate+NLPlate_HPG0G2Constraint+Plate_D1+Plate_D2+Geom_RectangularTrimmedSurface+BSplCLib::Interpolate. - Note: The returned parametrisation and working domain are as described for
nlPlateDeformed(constraints:resolutionOrder:tolerance:). - Example:
if let deformed = surf.nlPlateDeformedG2(constraints: [( uv: SIMD2(0.5, 0.5), target: SIMD3(5, 5, 3), tangentU: SIMD3(1, 0, 0), tangentV: SIMD3(0, 1, 0), curvatureUU: .zero, curvatureUV: .zero, curvatureVV: .zero )]) { let face = deformed.toFace() }
nlPlateDeformedG3(constraints:tolerance:)
Deforms this surface with G0+G1+G2+G3 constraints (position, tangent, curvature, and third-order derivatives).
public func nlPlateDeformedG3(
constraints: [(uv: SIMD2<Double>, target: SIMD3<Double>,
tangentU: SIMD3<Double>, tangentV: SIMD3<Double>,
curvatureUU: SIMD3<Double>, curvatureUV: SIMD3<Double>, curvatureVV: SIMD3<Double>,
d3UUU: SIMD3<Double>, d3UUV: SIMD3<Double>, d3UVV: SIMD3<Double>, d3VVV: SIMD3<Double>)],
tolerance: Double = 1e-3
) -> Surface?
The highest-order NLPlate constraint set: 32 doubles per constraint (uv + target + 3 first derivatives + 3 second derivatives + 4 third derivatives). Achieves G3-continuous deformations.
There is no iteration count, for the same reason as nlPlateDeformedG2(constraints:tolerance:).
- Parameters:
constraints, G0+G1+G2+G3 constraint tuples (non-empty);tolerance, the sampling density between the constraints (a tighter value samples more finely: 20 nodes per direction at 0.1, 63 at 1e-3, at most 80); it does not bound the error between samples. - Returns: New deformed surface, or
nilon failure. - OCCT:
NLPlate_NLPlate+NLPlate_HPG0G3Constraint+Plate_D1+Plate_D2+Plate_D3+Geom_RectangularTrimmedSurface+BSplCLib::Interpolate. - Note: The returned parametrisation and working domain are as described for
nlPlateDeformed(constraints:resolutionOrder:tolerance:). - Example:
if let deformed = surf.nlPlateDeformedG3(constraints: [( uv: SIMD2(0.5, 0.5), target: SIMD3(5, 5, 3), tangentU: SIMD3(1, 0, 0), tangentV: SIMD3(0, 1, 0), curvatureUU: .zero, curvatureUV: .zero, curvatureVV: .zero, d3UUU: .zero, d3UUV: .zero, d3UVV: .zero, d3VVV: .zero )]) { let face = deformed.toFace() }
nlPlateDeformedIncremental(constraints:maxOrder:initConstraintOrder:nbIncrements:)
Deforms this surface with G0 constraints using the incremental NLPlate solver strategy.
public func nlPlateDeformedIncremental(
constraints: [(uv: SIMD2<Double>, target: SIMD3<Double>)],
maxOrder: Int = 2,
initConstraintOrder: Int = 1,
nbIncrements: Int = 4
) -> Surface?
Progressively adds constraints for better convergence on challenging constraint sets where the standard nlPlateDeformed may not converge well.
- Parameters:
constraints, G0 constraint pairs (non-empty);maxOrder, maximum polynomial order;initConstraintOrder, initial constraint order;nbIncrements, number of increments. - Returns: New deformed surface, or
nilon failure. - OCCT:
NLPlate_NLPlate::IncrementalSolve+NLPlate_HPG0Constraint+Geom_RectangularTrimmedSurface+BSplCLib::Interpolate. - Note: The returned parametrisation and working domain are as described for
nlPlateDeformed(constraints:resolutionOrder:tolerance:). - Example:
if let deformed = surf.nlPlateDeformedIncremental( constraints: [(uv: SIMD2(0.5, 0.5), target: SIMD3(5, 5, 3))], nbIncrements: 6 ) { let face = deformed.toFace() }
nlPlateDerivative(constraints:u:v:iu:iv:)
Evaluates a partial derivative of the NLPlate G0 solution at a UV point. Not of a displacement field: see the NLPlate_NLPlate::EvaluateDerivative paragraph in this same entry. (#811)
public func nlPlateDerivative(
constraints: [(uv: SIMD2<Double>, target: SIMD3<Double>)],
u: Double, v: Double,
iu: Int = 1, iv: Int = 0
) -> SIMD3<Double>?
Solves the NLPlate problem with the given G0 constraints and returns the (iu, iv)-th partial derivative at (u, v) without approximating the surface.
NLPlate_NLPlate::EvaluateDerivative seeds its accumulator with the input surface’s own value or derivative before summing the plates, so this is a derivative of the deformed surface, not of a displacement away from the input. At iu == 0, iv == 0 it is the deformed point itself. (#1049)
- Parameters:
constraints, G0 constraint pairs;u,v, evaluation parameters;iu,iv, derivative orders in U and V. - Returns: Derivative vector, or
nilon failure. - OCCT:
NLPlate_NLPlate::EvaluateDerivative+NLPlate_HPG0Constraint. - Example:
if let d = surf.nlPlateDerivative( constraints: [(uv: SIMD2(0.5, 0.5), target: SIMD3(5, 5, 2))], u: 0.5, v: 0.5, iu: 1, iv: 0 ) { print("dU of the deformed surface at (0.5, 0.5):", d) }
Surface.generatedFromSections(curves:tolerance:)
Generates a ruled/lofted surface from a sequence of section curves using linear interpolation in V.
public static func generatedFromSections(
curves: [Curve3D],
tolerance: Double = 1e-6
) -> Surface?
Uses GeomFill_Generator (linear V-direction interpolation), which is simpler and faster than nSections but less flexible, the V blending is always linear between consecutive sections.
- Parameters:
curves, section curves (minimum 2);tolerance, parametric tolerance. - Returns: Generated surface, or
nilif fewer than 2 curves orGeomFill_Generatorfails. - OCCT:
GeomFill_Generator. - Example:
guard let c1 = Curve3D.interpolate(points: [SIMD3(0,0,0), SIMD3(10,0,0)]), let c2 = Curve3D.interpolate(points: [SIMD3(0,5,3), SIMD3(10,5,3)]) else { return } if let surf = Surface.generatedFromSections(curves: [c1, c2]) { let face = surf.toFace() }
Surface.degeneratedBoundaryValue(point:first:last:parameter:)
Evaluates a degenerated boundary (a boundary that has collapsed to a single point) at a parameter.
public static func degeneratedBoundaryValue(
point: SIMD3<Double>,
first: Double = 0,
last: Double = 1,
parameter: Double
) -> SIMD3<Double>
Returns point regardless of parameter, models the apex of a cone or any other degenerate pole boundary.
- Parameters:
point, the degenerate point;first,last, parameter range;parameter, evaluation parameter. - Returns: The degenerate point (always equal to
point). - OCCT:
GeomFill_DegeneratedBound::Value. - Example:
let apex = Surface.degeneratedBoundaryValue( point: SIMD3(0, 0, 10), first: 0, last: 1, parameter: 0.5 ) // apex == SIMD3(0, 0, 10)
Surface.isDegeneratedBoundary(point:first:last:)
Returns true, always, for degenerated boundaries defined by a single point.
public static func isDegeneratedBoundary(
point: SIMD3<Double>,
first: Double = 0,
last: Double = 1
) -> Bool
- Parameters:
point, the degenerate point;first,last, parameter range. - Returns: Always
true. - OCCT:
GeomFill_DegeneratedBound::IsDegenerated. - Note: Provided for API completeness when working with
GeomFillboundary objects.
boundaryWithSurfaceEvaluate(curve2d:first:last:parameter:)
Evaluates a boundary-with-surface (a 2D curve on this surface) at a parameter, returning the 3D point and surface normal.
public func boundaryWithSurfaceEvaluate(
curve2d: Curve2D,
first: Double,
last: Double,
parameter: Double
) -> (point: SIMD3<Double>, normal: SIMD3<Double>)?
- Parameters:
curve2d, a 2D curve lying on this surface;first,last, parameter range of the 2D curve;parameter, evaluation parameter. - Returns: Tuple of
(point, normal)at the boundary parameter, ornilon failure. - OCCT:
GeomFill_BoundWithSurf::Value+ normal from the host surface. - Example:
if let seg = Curve2D.segment(from: SIMD2(0, 0), to: SIMD2(1, 0)), let (pt, n) = surf.boundaryWithSurfaceEvaluate(curve2d: seg, first: 0, last: 1, parameter: 0.5) { print("boundary point:", pt, "normal:", n) }
Surface.averagePlane(points:boundaryPointCount:tolerance:)
Computes the average plane through a set of 3D points.
public static func averagePlane(
points: [SIMD3<Double>],
boundaryPointCount: Int? = nil,
tolerance: Double = 1e-3
) -> AveragePlaneResult?
Returns the plane normal, origin, and UV bounding box. Also handles the collinear case, returning isLine == true with a line origin and direction.
- Parameters:
points, 3D points (minimum 3);boundaryPointCount, number of boundary points used for orientation (defaults to all points);tolerance, planarity check tolerance. - Returns:
AveragePlaneResult, ornilif the point set is degenerate. - OCCT:
GeomPlate_BuildAveragePlane. - Example:
let pts: [SIMD3<Double>] = [.zero, SIMD3(10,0,0), SIMD3(0,10,0), SIMD3(10,10,0.1)] if let result = Surface.averagePlane(points: pts) { if result.isPlane { print("normal:", result.normal) } }
Surface.AveragePlaneResult.lineOrigin
A point on the fitted line; meaningful only when isLine is true (the input points are collinear rather than planar).
Surface.AveragePlaneResult.lineDirection
Unit direction of the fitted line; meaningful only when isLine is true.
- Parameters:
points, 3D points (minimum 3);boundaryPointCount, number of boundary points used for orientation (defaults to all points);tolerance, planarity check tolerance. - Returns:
AveragePlaneResult, ornilif the point set is degenerate. - OCCT:
GeomPlate_BuildAveragePlane. - Example:
let pts: [SIMD3<Double>] = [.zero, SIMD3(10,0,0), SIMD3(0,10,0), SIMD3(10,10,0.1)] if let result = Surface.averagePlane(points: pts) { if result.isPlane { print("normal:", result.normal) } }
Extrema, gce Factories, GeomTools Persistence (v0.80.0)
extremaPS(point:)
Computes point-to-surface extrema (nearest/farthest points).
public func extremaPS(point: SIMD3<Double>) -> PointSurfaceExtrema
- Parameters:
point, the query point. - Returns:
PointSurfaceExtremawithisDoneandcount(number of extremum solutions). - OCCT:
Extrema_ExtPS. - Example:
let e = surf.extremaPS(point: SIMD3(5, 5, 10)) if e.isDone { for i in 1...e.count { let p = surf.extremaPSPoint(point: SIMD3(5, 5, 10), index: i) print("dist²:", p.squareDistance, "uv:", p.u, p.v) } }
extremaPSPoint(point:index:)
Returns the Nth extremum from a point-surface extrema computation (1-based).
public func extremaPSPoint(point: SIMD3<Double>, index: Int) -> ExtremaPointOnSurface
- Parameters:
point, the query point;index, 1-based extremum index. - Returns:
ExtremaPointOnSurfacewithsquareDistance,point,u, andv. - OCCT:
Extrema_ExtPS::Point/SquareDistance. - Note: Call
extremaPS(point:)first to confirmisDoneand obtain the validcount.
extremaSS(other:)
Computes surface-to-surface extrema.
public func extremaSS(other: Surface) -> SurfaceSurfaceExtrema
- Parameters:
other, the second surface. - Returns:
SurfaceSurfaceExtremawithisDone,isParallel, andcount. - OCCT:
Extrema_ExtSS. - Example:
let e = surf1.extremaSS(other: surf2) if e.isDone, !e.isParallel { for i in 1...e.count { let pair = surf1.extremaSSPoint(other: surf2, index: i) print("dist²:", pair.squareDistance) } }
ExtremaSurfacePointPair
A point pair from a surface-surface extrema result, one point on each surface. Unlike Curve3D.ExtremaPointPair (one param per point, correct for a curve-side point), both points here live on a surface, so each carries a full (u, v). Reusing the curve-curve struct used to silently discard the V coordinate of both points (#1502).
public struct ExtremaSurfacePointPair: Sendable {
public let squareDistance: Double
public let point1: SIMD3<Double>
public let u1: Double
public let v1: Double
public let point2: SIMD3<Double>
public let u2: Double
public let v2: Double
}
extremaSSPoint(other:index:)
Returns the Nth extremum pair from a surface-surface extrema computation.
public func extremaSSPoint(other: Surface, index: Int) -> ExtremaSurfacePointPair
- Parameters:
other, the second surface;index, 1-based extremum index. - Returns:
ExtremaSurfacePointPairwithsquareDistance,point1,u1,v1,point2,u2,v2. - OCCT:
Extrema_ExtSS::Points/SquareDistance.
Surface.coneFrom2PointsRadii(p1:p2:radius1:radius2:)
Creates a conical surface from 2 axis points and 2 radii. Equivalent to conicalSurface(point1:point2:r1:r2:): as of #420 both call the same GC_MakeConicalSurface construction, kept as a separate gce_Make-style entry point.
public static func coneFrom2PointsRadii(
p1: SIMD3<Double>,
p2: SIMD3<Double>,
radius1: Double,
radius2: Double
) -> Surface?
- Parameters:
p1,p2, axis points;radius1, radius atp1;radius2, radius atp2. - Returns: Conical surface, or
nilon failure. - OCCT:
GC_MakeConicalSurface(gp_Pnt, gp_Pnt, Standard_Real, Standard_Real). - Example:
if let cone = Surface.coneFrom2PointsRadii( p1: .zero, p2: SIMD3(0, 0, 10), radius1: 5, radius2: 2 ) { let face = cone.toFace() }
Surface.cylinderFrom3Points(p1:p2:p3:)
Creates a cylindrical surface from 3 points. Equivalent to cylindricalSurface(point1:point2:point3:): as of #420 both call the same GC_MakeCylindricalSurface construction, kept as a separate gce_Make-style entry point.
public static func cylinderFrom3Points(
p1: SIMD3<Double>,
p2: SIMD3<Double>,
p3: SIMD3<Double>
) -> Surface?
The axis passes through p1 and p2; p3 defines the radius (its distance to the axis).
- Parameters:
p1,p2, axis points;p3, radius-defining point. - Returns: Cylindrical surface, or
nilon failure. - OCCT:
GC_MakeCylindricalSurface(gp_Pnt, gp_Pnt, gp_Pnt). - Example:
if let cyl = Surface.cylinderFrom3Points( p1: .zero, p2: SIMD3(0, 0, 10), p3: SIMD3(5, 0, 0) ) { let face = cyl.toFace(uRange: 0...2 * .pi, vRange: 0...10) }
Surface.planeFromEquation(a:b:c:d:)
Creates a plane surface from the equation Ax + By + Cz + D = 0.
public static func planeFromEquation(
a: Double, b: Double, c: Double, d: Double
) -> Surface?
- Parameters:
a,b,c, normal direction coefficients;d, plane offset. - Returns: Plane surface, or
nilon failure. - OCCT:
gce_MakePln(Standard_Real, Standard_Real, Standard_Real, Standard_Real). - Example:
// XY plane at Z = 5: 0x + 0y + 1z - 5 = 0 if let plane = Surface.planeFromEquation(a: 0, b: 0, c: 1, d: -5) { let face = plane.toFace(uRange: -10...10, vRange: -10...10) }
Surface.planeFrom3Points(p1:p2:p3:)
Creates a plane surface from 3 points. A labeled-argument spelling of planeFromPoints(_:_:_:) (above) and delegates to it (#421).
public static func planeFrom3Points(
p1: SIMD3<Double>,
p2: SIMD3<Double>,
p3: SIMD3<Double>
) -> Surface?
- Parameters:
p1,p2,p3, three non-collinear points. - Returns: Plane surface, or
nilif points are collinear (including coincident). - OCCT:
GC_MakePlane(gp_Pnt, gp_Pnt, gp_Pnt)(viaplaneFromPoints). - Example:
if let plane = Surface.planeFrom3Points( p1: .zero, p2: SIMD3(10, 0, 0), p3: SIMD3(0, 10, 0) ) { let face = plane.toFace(uRange: 0...10, vRange: 0...10) }
Surface.serializeSurfaces(_:)
Serialises an array of surfaces to a string using GeomTools_SurfaceSet.
public static func serializeSurfaces(_ surfaces: [Surface]) -> String?
The produced string can be stored and later restored with deserializeSurfaces(_:). Uses OCCT’s native binary-text stream format.
- Parameters:
surfaces, array of surfaces to serialise. - Returns: Serialised string, or
nilon failure. - OCCT:
GeomTools_SurfaceSet::Write. - Example:
if let data = Surface.serializeSurfaces([surf1, surf2]) { // save data to disk if let restored = Surface.deserializeSurfaces(data) { print("restored \(restored.count) surfaces") } }
Surface.deserializeSurfaces(_:)
Deserialises surfaces from a string produced by serializeSurfaces(_:).
public static func deserializeSurfaces(_ data: String) -> [Surface]?
- Parameters:
data, string produced byserializeSurfaces(_:). - Returns: Array of restored surfaces, or
nilif parsing fails or the string is empty. - OCCT:
GeomTools_SurfaceSet::Read. - Example:
if let surfaces = Surface.deserializeSurfaces(savedData) { for s in surfaces { let face = s.toFace() } }