Link Search Menu Expand Document

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 (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 nil if fewer than 3 points or GeomPlate_MakeApprox fails.
  • 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 nil if the array is empty, resolutionOrder is outside 2...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: see occtswift-wrapping-gaps.md for 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 nil if the array is empty, resolutionOrder is outside 2...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 return nil, 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 nil if 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: c1 and c3, the outer boundaries, either direction; c2, the middle boundary, adjacent to both; style, fill style.
  • Returns: Bezier surface, or nil if any curve is not Bezier, c2 is 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 nil on 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 .curved additionally requires them to share an endpoint.
  • Returns: BSpline surface, or nil if the fill fails, including a .curved request 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, with curves.1 adjacent to both others within 1e-7 and each individual direction free; style, fill style.
  • Returns: BSpline surface, or nil if curves.1 is not adjacent to both of the others, .coons was 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 that curves.0’s direction becomes U; style, fill style.
  • Returns: BSpline surface, or nil if the four do not close a loop, .coons was 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 nil on failure.
  • OCCT: BRepBuilderAPI_MakeFace (via Shape.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 nil on failure.
  • OCCT: BRepBuilderAPI_MakeFace (via Shape.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 nil if fewer than 3 points or construction fails.
  • OCCT: OCCTShapeCreateFaceFromSurfaceUVPolygon → BRepBuilderAPI_MakeFace with 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 nil if semiAngle is out of range or construction fails.
  • OCCT: Geom_ConicalSurface with gp_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 at point1; r2, radius at point2.
  • Returns: Conical surface, or nil on failure.
  • OCCT: Geom_ConicalSurface derived 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 nil on failure.
  • OCCT: Geom_CylindricalSurface with gp_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 nil on failure.
  • OCCT: Geom_CylindricalSurface from 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 nil if 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 nil if normal has 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 radius r1); point2, top centre (with radius r2); r1, r2, respective radii.
  • Returns: Bounded trimmed conical surface, or nil on 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 nil on 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: KnotSplitResult with uSplitCount/vSplitCount, uSplitParams/vSplitParams (ascending, bounded by the surface’s own U/V domain) and uSplitIndices/vSplitIndices (1-based into the surface’s own knot tables, so uSplitParams[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 is 0...degree and it saturates there. A bicubic surface with simple interior knots is already C2 at every interior knot, which means .c0, .c1 and .c2 all report just the two bounding curves per direction and .c3 is the order that reports the interior ones. On a degree-4-or-higher surface .c3 is 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 equal rows * cols); rows, patch row count; cols, patch column count.
  • Returns: Combined BSpline surface, or nil if the count doesn’t match rows * 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: AnalyticalConversion with the recognised surface and its gap (max deviation), or nil if 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: ContinuitySplitResult with wasSplit, alreadyMeetsCriterion, uSplitCount, and vSplitCount.
  • 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: ContinuitySplitResult with wasSplit, alreadyMeetsCriterion, uSplitCount, and vSplitCount.
  • 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 on other; order, the class to measure (default .c2).
  • Returns: ContinuityAnalysis, or nil if 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 as curves).
  • Returns: BSpline surface, or nil if counts don’t match or GeomFill_NSections fails.
  • 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), or nil on 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 nil on 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 nil on 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 nil on 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 nil on 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 nil if fewer than 2 curves or GeomFill_Generator fails.
  • 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 GeomFill boundary 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, or nil on 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, or nil if 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, or nil if 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: PointSurfaceExtrema with isDone and count (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: ExtremaPointOnSurface with squareDistance, point, u, and v.
  • OCCT: Extrema_ExtPS::Point / SquareDistance.
  • Note: Call extremaPS(point:) first to confirm isDone and obtain the valid count.

extremaSS(other:)

Computes surface-to-surface extrema.

public func extremaSS(other: Surface) -> SurfaceSurfaceExtrema
  • Parameters: other, the second surface.
  • Returns: SurfaceSurfaceExtrema with isDone, isParallel, and count.
  • 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: ExtremaSurfacePointPair with squareDistance, 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 at p1; radius2, radius at p2.
  • Returns: Conical surface, or nil on 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 nil on 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 nil on 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 nil if points are collinear (including coincident).
  • OCCT: GC_MakePlane(gp_Pnt, gp_Pnt, gp_Pnt) (via planeFromPoints).
  • 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 nil on 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 by serializeSurfaces(_:).
  • Returns: Array of restored surfaces, or nil if 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()
        }
    }