Surface
A Surface is a parametric 2D manifold in 3D space — the Swift analog of OCCT’s Geom_Surface class hierarchy. It wraps analytic surfaces (plane, cylinder, cone, sphere, torus), swept surfaces (extrusion, revolution), and freeform surfaces (Bezier, BSpline) polymorphically behind a single opaque handle. Obtain a surface via one of the static factory methods, by extracting it from a Face (face.surface), or by converting a Shape to its underlying geometry.
Note:
Surfaceis documented across several pages — see also Surface — Analytic Types, Surface — BSpline & Bezier, Surface — Analysis, and Surface — Advanced Construction.
Topics
- Properties · Evaluation · Analytic Surfaces · Swept Surfaces · Freeform Surfaces · Operations · Conversion · Iso Curves · Pipe Surfaces · Draw Methods · Bounding Box · Surface Transform (v0.128.0) · GeomEval Surface Factories (v0.130.0) · GeomEval TBezier / AHTBezier Surfaces (v0.131.0) · v0.115.0: Surface from point grid, normal, curvatures
Properties
SurfaceType
Classification enum matching OCCT’s GeomAbs_SurfaceType.
public enum SurfaceType: Int32, Sendable {
case plane = 0, cylinder = 1, cone = 2, sphere = 3, torus = 4
case bezierSurface = 5, bsplineSurface = 6
case surfaceOfRevolution = 7, surfaceOfExtrusion = 8
case offsetSurface = 9, other = 10
}
Use surfaceKind to query which case applies to a given Surface instance.
surfaceKind
The specific geometric kind of this surface.
public var surfaceKind: SurfaceType { get }
- Returns: The
SurfaceTypecase that classifies this surface. - OCCT:
GeomAdaptor_Surface::GetType(viaOCCTSurfaceGetType). - Example:
let s = Surface.sphere(center: .zero, radius: 5)! print(s.surfaceKind) // .sphere
Continuity (removed in v2.0.0)
Was a deprecated alias of the top-level ContinuityClass, which Curve3D and Curve2D now share (#485); the alias itself was removed at v2.0.0 (#784). Use ContinuityClass directly. The raw values are unchanged.
ContinuityClass
The continuity OCCT measured on an existing curve or surface: the one continuity vocabulary that reports rather than requests, shared by Curve3D, Curve2D and Surface alike (#485). Its raw values are not a 0/1/2 order: they are GeomAbs_Shape’s own ordinals, which interleave the geometric classes with the parametric ones.
public enum ContinuityClass: Int32, Sendable, CaseIterable {
case c0 = 0
case g1 = 1
case c1 = 2
case g2 = 3
case c2 = 4
case c3 = 5
case cN = 6
}
| Case | Raw | Meaning |
|---|---|---|
.c0 | 0 | Positional continuity only (GeomAbs_C0). |
.g1 | 1 | Tangent continuity (GeomAbs_G1): tangent directions agree, magnitudes need not. |
.c1 | 2 | First-derivative continuity (GeomAbs_C1). |
.g2 | 3 | Curvature continuity (GeomAbs_G2). |
.c2 | 4 | Second-derivative continuity (GeomAbs_C2). |
.c3 | 5 | Third-derivative continuity (GeomAbs_C3). |
.cN | 6 | Infinite continuity (GeomAbs_CN): every derivative exists. Analytic geometry. |
Do not compare a raw value against ParametricContinuity or SurfaceContinuity: those are request orders counting 0, 1, 2; these are GeomAbs_Shape ordinals where C1 is 2 and C2 is 4. Use satisfies(_:) for a continuity-floor check instead of comparing raw values.
isParametric
Whether this class guarantees parametric continuity (a C-class), not merely geometric.
public var isParametric: Bool { get }
.g1/.g2 are false: they constrain tangent direction and curvature but not the derivative vectors themselves. false means “not a C-class”, not “meets no floor at all”: a geometric class still meets the C0 floor.
derivativeOrder
The highest continuously-differentiable derivative order this class guarantees, if it guarantees a parametric one at all.
public var derivativeOrder: Int? { get }
- Returns:
nilfor.g1/.g2, which promise nothing about derivative vectors;Int.maxfor.cN; otherwise the C-order (.c2returns2).
satisfies(_:)
Whether the measured continuity meets a required parametric floor.
public func satisfies(_ required: ParametricContinuity) -> Bool
Use this instead of comparing raw values against ParametricContinuity; the two encodings differ (ContinuityClass.c1 is 2, ParametricContinuity.c1 is 1). This asks a parametric question, which is not what </>= answer: those rank two measured classes by their place in GeomAbs_Shape’s ladder, and outranking a class in that ladder is not the same as entailing it. The two answers differ at exactly one pair: .g2 sorts above .c1 (raw 3 > 2) yet guarantees nothing about first-derivative vectors, so .g2.satisfies(.c1) is false while .g2 >= .c1 is true. They agree everywhere else, .c0 included: a geometric class does meet the C0 floor, since G1 entails G0 (#623).
ContinuityClass.g1.satisfies(.c0) // true
ContinuityClass.g1.satisfies(.c1) // false
ContinuityClass.g2 >= .c1 // true
ContinuityClass.g2.satisfies(.c1) // false
- OCCT: none directly; a pure-Swift comparison against the
derivativeOrderabove.
<(lhs:rhs:)
Ranks two measured classes by their place in GeomAbs_Shape’s ladder (Comparable conformance). GeomAbs_Shape declares its cases in ascending order of how much smoothness is being claimed (C0 < G1 < C1 < G2 < C2 < C3 < CN), so the raw values compare correctly with no lookup table needed.
public static func < (lhs: ContinuityClass, rhs: ContinuityClass) -> Bool
Read it as ranking claims, not as an implication chain: .g2 sorts above .c1 yet does not satisfy .c1 (see satisfies(_:) above), because a geometric class is a claim about a reparametrisable curve rather than the parametrisation actually in hand.
- Note: This is the real declaration behind a
check-docs-existence.py--coverageartifact: the checker’s operator-overload regex cannot capture a symbolic operator name and records a pseudo-member literally calledfuncforContinuityClass(and separately forShapeandMaterial.PredefinedMaterial, each with their own operator). That pseudo-member is not a real symbol and is intentionally left undocumented; this section is the actual, correct fix.
continuityClass
The measured overall continuity of the surface.
public var continuityClass: ContinuityClass { get }
- Returns: A
ContinuityClassdescribing positional through CN continuity. Raw values areGeomAbs_Shape’s own ordinals (c0=0, g1=1, c1=2, g2=3, c2=4, c3=5, cN=6), which are not a 0/1/2 order — usesatisfies(_:)rather than comparing raw values against aParametricContinuity. - OCCT:
Geom_Surface::Continuity(viaOCCTSurfaceGetContinuity). - Example:
let bsp = Surface.bspline(poles: ..., ...)! print(bsp.continuityClass) // typically .c2 print(bsp.continuityClass.satisfies(.c2)) // true - Note:
satisfies(_:)and>=answer different questions and are not interchangeable.satisfies(_:)tests a measured class against a requested parametric floor;</>=ranks two measured classes by their place inGeomAbs_Shape’s ladder. Because that ladder interleaves the geometric classes with the parametric ones, outranking a class is not the same as entailing it:.g2sorts above.c1but does not satisfy.c1, since curvature continuity says nothing about first-derivative vectors. They agree on every other pair,.c0included — a geometric class does meet the positional floor, because G1 entails G0 entails position, and position is what C0 is (#623).ContinuityClass.g1.satisfies(.c0) // true — tangent-continuous implies connected ContinuityClass.g1.satisfies(.c1) // false — but says nothing about derivative vectors ContinuityClass.g2 >= .c1 // true — the ladder ranks G2 above C1 ... ContinuityClass.g2.satisfies(.c1) // false — ... without G2 entailing C1
isPlane, isCylinder, isCone, isSphere, isTorus
Boolean type-test properties.
public var isPlane: Bool { get }
public var isCylinder: Bool { get }
public var isCone: Bool { get }
public var isSphere: Bool { get }
public var isTorus: Bool { get }
Convenience wrappers around surfaceKind. All query surfaceKind == .<type>.
- Example:
if surface.isSphere { /* analytic sphere geometry available */ }
Surface.isCylinder
true when surfaceKind == .cylinder.
Surface.isCone
true when surfaceKind == .cone.
Surface.isSphere
true when surfaceKind == .sphere.
Surface.isTorus
true when surfaceKind == .torus.
isBezier, isBSpline, isSurfaceOfRevolution, isSurfaceOfExtrusion, isOffsetSurface
Additional boolean type-test properties.
public var isBezier: Bool { get }
public var isBSpline: Bool { get }
public var isSurfaceOfRevolution: Bool { get }
public var isSurfaceOfExtrusion: Bool { get }
public var isOffsetSurface: Bool { get }
Surface.isBSpline
true when surfaceKind == .bsplineSurface.
Surface.isSurfaceOfRevolution
true when surfaceKind == .surfaceOfRevolution.
Surface.isSurfaceOfExtrusion
true when surfaceKind == .surfaceOfExtrusion.
Surface.isOffsetSurface
true when surfaceKind == .offsetSurface.
domain
The parameter domain (uMin, uMax, vMin, vMax).
public var domain: (uMin: Double, uMax: Double, vMin: Double, vMax: Double) { get }
For infinite analytic surfaces (planes, full cylinders) the returned bounds may be very large values. Always clamp before iterating.
- Returns: Tuple of four doubles describing the full UV parameter range.
- OCCT:
Geom_Surface::Bounds. - Example:
let d = Surface.plane(origin: .zero, normal: SIMD3(0, 0, 1))!.domain // d.uMin, d.uMax will be large (±1e100)
isUClosed
Whether the surface is closed in the U direction.
public var isUClosed: Bool { get }
- OCCT:
Geom_Surface::IsUClosed.
isVClosed
Whether the surface is closed in the V direction.
public var isVClosed: Bool { get }
- OCCT:
Geom_Surface::IsVClosed.
isUPeriodic
Whether the surface is periodic in the U direction.
public var isUPeriodic: Bool { get }
- OCCT:
Geom_Surface::IsUPeriodic.
isVPeriodic
Whether the surface is periodic in the V direction.
public var isVPeriodic: Bool { get }
- OCCT:
Geom_Surface::IsVPeriodic.
uPeriod
The period in the U direction, if periodic.
public var uPeriod: Double? { get }
- Returns: Period value, or
nilifisUPeriodicisfalse. - OCCT:
Geom_Surface::UPeriod.
vPeriod
The period in the V direction, if periodic.
public var vPeriod: Double? { get }
- Returns: Period value, or
nilifisVPeriodicisfalse. - OCCT:
Geom_Surface::VPeriod.
Evaluation
point(atU:v:)
Evaluate the surface point at (u, v).
public func point(atU u: Double, v: Double) -> SIMD3<Double>
- Parameters:
u— U parameter;v— V parameter. Both must lie withindomain. - Returns: 3D point on the surface.
- OCCT:
Geom_Surface::D0. - Example:
let s = Surface.sphere(center: .zero, radius: 5)! let pt = s.point(atU: 0, v: 0) // (5, 0, 0)
d1(atU:v:)
First-order derivatives at (u, v).
public func d1(atU u: Double, v: Double) -> (point: SIMD3<Double>, du: SIMD3<Double>, dv: SIMD3<Double>)
Returns the point together with the first partial derivatives in U and V in a single call.
- Parameters:
u— U parameter;v— V parameter. - Returns: Tuple of position, dS/dU, and dS/dV.
- OCCT:
Geom_Surface::D1. - Example:
let (pt, du, dv) = surface.d1(atU: 0.5, v: 0.5) let normal = simd_cross(du, dv)
d2(atU:v:)
Second-order derivatives at (u, v).
public func d2(atU u: Double, v: Double) -> (
point: SIMD3<Double>,
d1u: SIMD3<Double>, d1v: SIMD3<Double>,
d2u: SIMD3<Double>, d2v: SIMD3<Double>, d2uv: SIMD3<Double>
)
Returns position, first partials, and second partials (including the mixed partial d²S/dUdV) in a single call.
- Parameters:
u— U parameter;v— V parameter. - Returns: Tuple of point, ∂S/∂U, ∂S/∂V, ∂²S/∂U², ∂²S/∂V², ∂²S/∂U∂V.
- OCCT:
Geom_Surface::D2. - Example:
let (pt, d1u, d1v, d2u, d2v, d2uv) = surface.d2(atU: 0.3, v: 0.7)
normal(atU:v:)
Surface normal at (u, v).
public func normal(atU u: Double, v: Double) -> SIMD3<Double>?
- Parameters:
u— U parameter;v— V parameter. - Returns: Unit normal vector, or
nilat singular points where the tangent plane is degenerate. - OCCT:
GeomLProp_SLProps::Normal(order 1,Precision::Confusion()), gated onIsNormalDefined(). - See also:
normal(u:v:)— same computation and same degeneracy test, but returns the zero vector instead ofnilwhere the normal is undefined (#401). - Example:
if let n = Surface.plane(origin: .zero, normal: SIMD3(0, 0, 1))!.normal(atU: 0, v: 0) { // n ≈ SIMD3(0, 0, 1) }
Analytic Surfaces
Surface.plane(origin:normal:)
Creates an infinite plane from a point and normal direction.
public static func plane(origin: SIMD3<Double>, normal: SIMD3<Double>) -> Surface?
The surface is infinite in both U and V. Trim it with trimmed(u1:u2:v1:v2:) or convert to a face with toFace(uRange:vRange:) before use in B-Rep operations.
An unlabeled-positional spelling of planeFromPointNormal(point:normal:) and delegates to it (#421) — the two cannot produce different planes for the same input.
- Parameters:
origin— a point on the plane;normal— outward normal direction. - Returns:
Geom_Planesurface, ornilifnormalhas zero (or near-zero) length. - OCCT:
GC_MakePlane(gp_Pnt, gp_Dir). - Example:
let floor = Surface.plane(origin: .zero, normal: SIMD3(0, 0, 1))
Surface.cylinder(origin:axis:radius:)
Creates a cylindrical surface.
public static func cylinder(origin: SIMD3<Double>, axis: SIMD3<Double>,
radius: Double) -> Surface?
The cylinder is infinite along axis. U is the angular parameter (0 to 2π), V is the axial parameter.
- Parameters:
origin— base point on the axis;axis— axis direction;radius— cylinder radius (must be > 0). - Returns:
Geom_CylindricalSurface, ornilon failure. - OCCT:
Geom_CylindricalSurface(gp_Ax3, radius). - Example:
let cyl = Surface.cylinder(origin: .zero, axis: SIMD3(0, 0, 1), radius: 5)
Surface.cone(origin:axis:radius:semiAngle:)
Creates a conical surface.
public static func cone(origin: SIMD3<Double>, axis: SIMD3<Double>,
radius: Double, semiAngle: Double) -> Surface?
The cone apex is located along axis from origin. semiAngle is in radians and must be in (0, π/2).
- Parameters:
origin— axis base point;axis— cone axis direction;radius— base radius;semiAngle— half-angle in radians. - Returns:
Geom_ConicalSurface, ornilon failure. - OCCT:
Geom_ConicalSurface(gp_Ax3, semiAngle, radius). - Example:
let cone = Surface.cone(origin: .zero, axis: SIMD3(0, 0, 1), radius: 10, semiAngle: .pi / 6)
Surface.sphere(center:radius:)
Creates a spherical surface.
public static func sphere(center: SIMD3<Double>, radius: Double) -> Surface?
U is the longitude parameter (0 to 2π), V is the latitude parameter (−π/2 to π/2). The surface is closed in U and has singular poles at V = ±π/2.
- Parameters:
center— sphere centre;radius— sphere radius (must be > 0). - Returns:
Geom_SphericalSurface, ornilon failure. - OCCT:
Geom_SphericalSurface(gp_Ax3, radius). - Example:
let sphere = Surface.sphere(center: .zero, radius: 10)
Surface.torus(origin:axis:majorRadius:minorRadius:)
Creates a toroidal surface.
public static func torus(origin: SIMD3<Double>, axis: SIMD3<Double>,
majorRadius: Double, minorRadius: Double) -> Surface?
Both U and V are angular parameters (0 to 2π). The surface is closed and periodic in both directions.
- Parameters:
origin— torus centre;axis— torus symmetry axis;majorRadius— distance from centre to tube centre;minorRadius— tube radius. Both radii must be > 0 andminorRadius < majorRadius. - Returns:
Geom_ToroidalSurface, ornilon failure. - OCCT:
Geom_ToroidalSurface(gp_Ax3, majorRadius, minorRadius). - Example:
let torus = Surface.torus(origin: .zero, axis: SIMD3(0, 0, 1), majorRadius: 20, minorRadius: 5)
Swept Surfaces
Surface.extrusion(profile:direction:)
Creates a surface by extruding a curve along a direction.
public static func extrusion(profile: Curve3D, direction: SIMD3<Double>) -> Surface?
Produces a Geom_SurfaceOfLinearExtrusion. The U parameter follows the profile curve; the V parameter measures distance along the extrusion direction. The surface is infinite in V.
- Parameters:
profile— the generator curve;direction— extrusion direction vector. - Returns: Extrusion surface, or
nilifdirectionis zero or construction fails. - OCCT:
Geom_SurfaceOfLinearExtrusion(profile, direction). - Example:
if let line = Curve3D.line(from: .zero, to: SIMD3(10, 0, 0)), let surf = Surface.extrusion(profile: line, direction: SIMD3(0, 0, 1)) { let trimmed = surf.trimmed(u1: 0, u2: 1, v1: 0, v2: 20) }
Surface.revolution(meridian:axisOrigin:axisDirection:)
Creates a surface of revolution by revolving a curve around an axis.
public static func revolution(meridian: Curve3D,
axisOrigin: SIMD3<Double>,
axisDirection: SIMD3<Double>) -> Surface?
The U parameter is the angle of revolution (0 to 2π); V follows the meridian curve parameter. Pass a trimmed(u1:u2:v1:v2:) result to limit the angular sweep.
- Parameters:
meridian— the profile curve to revolve;axisOrigin— origin of the revolution axis;axisDirection— direction of the revolution axis. - Returns:
Geom_SurfaceOfRevolution, ornilon failure. - OCCT:
Geom_SurfaceOfRevolution(meridian, gp_Ax1). - Example:
if let profile = Curve3D.line(from: SIMD3(5, 0, 0), to: SIMD3(5, 0, 10)), let surf = Surface.revolution(meridian: profile, axisOrigin: .zero, axisDirection: SIMD3(0, 0, 1)) { // surf is a cylinder of radius 5 and infinite height }
Freeform Surfaces
Surface.bezier(poles:weights:)
Creates a Bezier surface from a 2D grid of control points.
public static func bezier(poles: [[SIMD3<Double>]],
weights: [[Double]]? = nil) -> Surface?
poles is a 2D array indexed [uRow][vCol], both dimensions must be ≥ 2. The surface degree in U is poles.count − 1 and in V is poles[0].count − 1. When weights is nil the surface is non-rational.
- Parameters:
poles— 2D grid of control points (minimum 2×2).weights— optional 2D grid of per-pole weights (same dimensions aspoles);nil= uniform 1.0.
- Returns:
Geom_BezierSurface, ornilif dimensions are invalid or construction fails. - OCCT:
Geom_BezierSurface(TColgp_Array2OfPnt)or the weighted overload. - Example:
let poles: [[SIMD3<Double>]] = [ [SIMD3(0, 0, 0), SIMD3(0, 5, 1)], [SIMD3(5, 0, 1), SIMD3(5, 5, 0)] ] if let s = Surface.bezier(poles: poles) { let pt = s.point(atU: 0.5, v: 0.5) }
Surface.bspline(poles:weights:knotsU:multiplicitiesU:knotsV:multiplicitiesV:degreeU:degreeV:)
Creates a BSpline surface with full explicit control.
public static func bspline(poles: [[SIMD3<Double>]],
weights: [[Double]]? = nil,
knotsU: [Double], multiplicitiesU: [Int32],
knotsV: [Double], multiplicitiesV: [Int32],
degreeU: Int, degreeV: Int) -> Surface?
poles is indexed [uRow][vCol]. Knot vectors and multiplicities must satisfy standard BSpline constraints (sum of multiplicities = number of poles + degree + 1 in each direction). When weights is nil the surface is non-rational.
- Parameters:
poles— 2D control point grid (minimum 2×2).weights— optional 2D weight grid;nil= non-rational.knotsU,knotsV— distinct knot values in U and V.multiplicitiesU,multiplicitiesV— per-knot multiplicities.degreeU,degreeV— polynomial degrees in U and V (≥ 1).
- Returns:
Geom_BSplineSurface, ornilif parameters are invalid. - OCCT:
Geom_BSplineSurface(poles, knotsU, knotsV, multsU, multsV, degU, degV). - Example:
// Bilinear (degree 1×1) BSpline — a flat quad patch let poles: [[SIMD3<Double>]] = [ [SIMD3(0, 0, 0), SIMD3(0, 10, 0)], [SIMD3(10, 0, 0), SIMD3(10, 10, 2)] ] let s = Surface.bspline( poles: poles, knotsU: [0, 1], multiplicitiesU: [2, 2], knotsV: [0, 1], multiplicitiesV: [2, 2], degreeU: 1, degreeV: 1 )
Operations
trimmed(u1:u2:v1:v2:)
Creates a rectangular trim of this surface.
public func trimmed(u1: Double, u2: Double, v1: Double, v2: Double) -> Surface?
The trim bounds must be within the surface domain. Use this to bound infinite analytic surfaces (planes, cylinders) before face creation.
- Parameters:
u1,u2— U parameter bounds;v1,v2— V parameter bounds. - Returns:
Geom_RectangularTrimmedSurface, ornilon failure. - OCCT:
Geom_RectangularTrimmedSurface(surface, u1, u2, v1, v2). - Example:
let plane = Surface.plane(origin: .zero, normal: SIMD3(0, 0, 1))! let patch = plane.trimmed(u1: 0, u2: 10, v1: 0, v2: 10)
offset(distance:)
Creates an offset surface at a given distance from this surface.
public func offset(distance: Double) -> Surface?
Positive distance offsets in the direction of the surface normal. Complex surfaces may produce self-intersections; use ShapeHealing tools to fix them before B-Rep operations.
- Parameters:
distance— offset distance in model units. - Returns:
Geom_OffsetSurface, ornilon failure. - OCCT:
Geom_OffsetSurface(surface, distance). - Example:
let sphere = Surface.sphere(center: .zero, radius: 10)! let outer = sphere.offset(distance: 2) // radius-12 sphere
translated(by:)
Returns a translated copy of this surface.
public func translated(by delta: SIMD3<Double>) -> Surface?
- Parameters:
delta— translation vector. - Returns: New surface shifted by
delta, ornilon failure. - OCCT:
Geom_Surface::Translated(gp_Vec). - Example:
let moved = sphere.translated(by: SIMD3(0, 0, 5))
rotated(axisOrigin:axisDirection:angle:)
Returns a rotated copy of this surface.
public func rotated(axisOrigin: SIMD3<Double>, axisDirection: SIMD3<Double>,
angle: Double) -> Surface?
- Parameters:
axisOrigin— a point on the rotation axis;axisDirection— axis direction;angle— angle in radians. - Returns: Rotated surface copy, or
nilon failure. - OCCT:
Geom_Surface::Rotated(gp_Ax1, angle). - Example:
let tilted = cylinder.rotated(axisOrigin: .zero, axisDirection: SIMD3(0, 1, 0), angle: .pi / 4)
scaled(center:factor:)
Returns a scaled copy of this surface.
public func scaled(center: SIMD3<Double>, factor: Double) -> Surface?
- Parameters:
center— scaling centre point;factor— scale factor (negative mirrors through centre). - Returns: Scaled surface copy, or
nilon failure. - OCCT:
Geom_Surface::Scaled(gp_Pnt, factor). - Example:
let doubled = sphere.scaled(center: .zero, factor: 2)
mirrored(planeOrigin:planeNormal:)
Returns a mirrored copy of this surface across a plane.
public func mirrored(planeOrigin: SIMD3<Double>, planeNormal: SIMD3<Double>) -> Surface?
- Parameters:
planeOrigin— a point on the mirror plane;planeNormal— plane normal direction. - Returns: Mirrored surface copy, or
nilon failure. - OCCT:
Geom_Surface::Mirrored(gp_Ax2). - Example:
let reflected = cone.mirrored(planeOrigin: .zero, planeNormal: SIMD3(1, 0, 0))
mirrored(acrossPoint:)
Returns a mirrored copy of this surface across a point.
public func mirrored(acrossPoint point: SIMD3<Double>) -> Surface?
- Parameters:
point— the mirror point. - Returns: Mirrored surface copy, or
nilon failure. - OCCT:
Geom_Surface::Mirrored(gp_Pnt). - Example:
let plane = Surface.plane(origin: SIMD3(0, 0, 0), normal: SIMD3(0, 0, 1)) let mirrored = plane?.mirrored(acrossPoint: SIMD3(1, 1, 1))
mirrored(acrossAxis:direction:)
Returns a mirrored copy of this surface across an axis (line).
public func mirrored(acrossAxis point: SIMD3<Double>, direction: SIMD3<Double>) -> Surface?
- Parameters:
point— a point on the mirror axis;direction— axis direction. - Returns: Mirrored surface copy, or
nilon failure. - OCCT:
Geom_Surface::Mirrored(gp_Ax1). - Example:
let plane = Surface.plane(origin: SIMD3(0, 0, 0), normal: SIMD3(0, 0, 1)) let mirrored = plane?.mirrored(acrossAxis: SIMD3(0, 0, 0), direction: SIMD3(0, 0, 1))
Conversion
toBSpline()
Converts this surface to a BSpline representation.
public func toBSpline() -> Surface?
Uses OCCT’s exact conversion where one exists, and approximates where it does not. Infinite surfaces must be trimmed first. The result is a Geom_BSplineSurface.
- Returns: BSpline surface, or
nilif conversion fails (e.g. surface is already a non-convertible type). - OCCT:
GeomConvert::SurfaceToBSplineSurface. - Note: Infinite surfaces (planes, full cylinders) will cause conversion to fail — trim the domain first with
trimmed(u1:u2:v1:v2:). - Note: This is not an exactness guarantee, and it takes no tolerance. Analytic families (plane, cylinder, cone, sphere, torus, surface of revolution) plus Bezier and BSpline surfaces convert exactly. Everything else, including an offset surface with no analytic equivalent, is handed to
GeomConvert_ApproxSurfaceat a tolerance OCCT hardcodes to1e-4(GeomConvert_1.cxx:786for a trimmed surface,:960otherwise), with a continuity derived from the surface’s ownIsCNu/IsCNv, and the fit is returned whether or not that tolerance was met. Measured on a trimmed offset of a BSpline that is C1 but not C2 in U, it caps out at degree 14 and sits 0.038 from its source (#572; before the0019kernel patch it stopped at degree 12x9 and sat 0.104 out while reporting success internally). Useapproximated(tolerance:...)to name the tolerance andapproxWithDetails(...)to learn whether it was reached. - Example:
let sphere = Surface.sphere(center: .zero, radius: 10)! let trimmed = sphere.trimmed(u1: 0, u2: .pi * 2, v1: -.pi / 2, v2: .pi / 2)! let bsp = trimmed.toBSpline()
approximated(tolerance:continuity:maxSegments:maxDegree:)
Approximates this surface as a BSpline surface within a tolerance.
public func approximated(tolerance: Double = 1e-3, continuity: Int = 2,
maxSegments: Int = 100, maxDegree: Int = 8) -> Surface?
Useful when exact toBSpline() conversion is unavailable (e.g. offset or composite surfaces).
- Parameters:
tolerance— maximum approximation deviation.continuity— desired continuity order, applied to both parametric directions, aParametricContinuityraw value (0=C0, 1=C1, 2=C2). C2 is the ceiling:AdvApproxthrows for C3 and above, which surfaces asnil.maxSegments— maximum number of BSpline segments.maxDegree— maximum polynomial degree.
- Returns: Approximated BSpline surface, or
nilwhen OCCT produced no fit at all. - OCCT:
GeomConvert_ApproxSurface, gated onHasResult(), withPrecisCode = 0. - Note: Defaults match
Curve3D.approximated/Curve2D.approximated(#406) — all three wrap the sameGeomConvert_Approx*/Geom2dConvert_ApproxCurvefamily applied to a different OCCT geometry hierarchy, not independent algorithms whose numeric defaults should diverge. (Before #406 this defaulted totolerance: 0.01, maxDegree: 10, a 10x looser tolerance with no documented reason; measurement found the tighter shared values succeed on every case tried, with no meaningful cost difference.) - Note: A non-
nilresult is not a promise thattolerancewas met. This gates on OCCT’sHasResult(), documented as true for a fit that is “not NECESSARILY within the required tolerance” — on a surfacemaxDegreecannot fit (a torus at1e-9) OCCT returns a usable best effort anyway.approxWithDetailsruns the identical approximation and reports the actualmaxError. - Example:
let offset = sphere.offset(distance: 1)! let bsp = offset.approximated(tolerance: 0.001)
approxWithDetails(tolerance:uContinuity:vContinuity:maxDegree:maxSegments:)
The same approximation as approximated, reporting the fit’s error and completion status, with the two parametric directions requested separately.
public func approxWithDetails(tolerance: Double, uContinuity: ParametricContinuity = .c2,
vContinuity: ParametricContinuity = .c2,
maxDegree: Int = 8, maxSegments: Int = 100) -> ApproxSurfaceResult
One shared GeomConvert_ApproxSurface run backs both entry points (#491), so for identical arguments they return the same surface — this one just also carries the diagnostics OCCT already computed. Note maxDegree precedes maxSegments here, the reverse of approximated’s order.
- Returns:
ApproxSurfaceResult(surface:maxError:isDone:hasResult:).surfaceis populated exactly whenhasResult;isDoneis whether the fit reachedtolerance;maxErroris the greatest distance between the source surface and the fit. - OCCT:
GeomConvert_ApproxSurface—Surface(),MaxError(),IsDone(),HasResult(). - Note: Before #491 the continuity defaults here were C1, and this entry point passed
PrecisCode = 1toGeomConvert_ApproxSurfacewhereapproximatedpassed0, so the two returned measurably different surfaces for the same request. Both now default to C2 and pass0. - Note:
maxErroris not trustworthy for a.c0request — see #522, an upstream defect where a C0 fit can collapse a direction to degree 1 and still report an error five orders of magnitude too small. - Example:
let sphere = Surface.sphere(center: .zero, radius: 10)! let fit = sphere.approxWithDetails(tolerance: 1e-5) if let bspline = fit.surface, fit.isDone { print("fitted to \(fit.maxError) with \(bspline.uPoleCount)x\(bspline.vPoleCount) poles") }
Iso Curves
uIso(at:)
Extracts a U-iso curve (constant U, varying V).
public func uIso(at u: Double) -> Curve3D?
- Parameters:
u— the fixed U parameter value. - Returns: A
Curve3Dat the given U value, ornilon failure. - OCCT:
Geom_Surface::UIso(u). - Example:
// Extract the meridian at U=0 on a sphere let meridian = Surface.sphere(center: .zero, radius: 10)!.uIso(at: 0)
vIso(at:)
Extracts a V-iso curve (constant V, varying U).
public func vIso(at v: Double) -> Curve3D?
- Parameters:
v— the fixed V parameter value. - Returns: A
Curve3Dat the given V value, ornilon failure. - OCCT:
Geom_Surface::VIso(v). - Example:
// Extract the equatorial circle of a sphere let equator = Surface.sphere(center: .zero, radius: 10)!.vIso(at: 0)
Pipe Surfaces
Surface.pipe(path:radius:)
Creates a pipe surface by sweeping a circle along a path.
public static func pipe(path: Curve3D, radius: Double) -> Surface?
The cross-section is a circle of the given radius. Orientation is determined by the Frenet frame of the path.
- Parameters:
path— sweep path curve;radius— pipe radius (must be > 0). - Returns: Pipe surface, or
nilif construction fails (e.g. path is too tightly curved for the radius). - OCCT:
GeomFill_Pipe(path, radius)::Perform. - Example:
if let helix = Curve3D.bspline(throughPoints: [SIMD3(0,0,0), SIMD3(5,5,5)]), let pipe = Surface.pipe(path: helix, radius: 2) { let face = pipe.toFace() }
Surface.pipe(path:section:)
Creates a pipe surface by sweeping a section curve along a path.
public static func pipe(path: Curve3D, section: Curve3D) -> Surface?
The section curve defines the cross-sectional shape at each point along path.
- Parameters:
path— sweep path curve;section— cross-section curve. - Returns: Pipe surface, or
nilon failure. - OCCT:
GeomFill_Pipe(path, section)::Perform. - Example:
if let arc = Curve3D.arcOfCircle(center: .zero, radius: 3, startAngle: 0, endAngle: .pi), let line = Curve3D.line(from: .zero, to: SIMD3(0, 0, 10)), let pipe = Surface.pipe(path: line, section: arc) { let trimmed = pipe.trimmed(u1: 0, u2: 1, v1: 0, v2: 1) }
Draw Methods
drawGrid(uLineCount:vLineCount:pointsPerLine:)
Draws iso-parameter grid lines for Metal visualisation.
public func drawGrid(uLineCount: Int = 10, vLineCount: Int = 10,
pointsPerLine: Int = 50) -> [[SIMD3<Double>]]
Samples uLineCount U-iso lines and vLineCount V-iso lines, each discretised to pointsPerLine 3D points. Infinite surfaces are clamped to ±100 before sampling.
- Parameters:
uLineCount— number of U iso-lines, at least 0;vLineCount— number of V iso-lines, at least 0;pointsPerLine— points per iso-line, at least 1. - Returns: Array of polylines (one per iso-line), empty if the surface is null or the grid cannot be served. The bound is on the total:
(uLineCount + vLineCount) * pointsPerLinemust not exceedSampling.maximumSampleCount(10,000,000), and each factor is checked on its own, since a negative line count used to abort the process unless the other count happened to outweigh it (#558). - OCCT:
Geom_Surface::Bounds+Geom_Surface::D0viaOCCTSurfaceDrawGrid. - Example:
let gridLines = surface.drawGrid(uLineCount: 10, vLineCount: 10, pointsPerLine: 50) // Pass to Metal vertex buffer for wireframe preview
SurfaceGrid
A 2D grid of points sampled over a surface’s UV parameter space, returned by drawMesh(uCount:vCount:) and evaluateGrid(uParameters:vParameters:) (see Surface-Analysis.md). Both methods share this one type specifically so indexing is unambiguous — access is always .at(u:v:), regardless of how each method lays out its own bridge buffer internally. Before #404, the two returned raw [[SIMD3<Double>]] with opposite nesting order ([uIndex][vIndex] vs [vIndex][uIndex]) and nothing at the type level stopped a caller from mixing them up.
public struct SurfaceGrid: Sendable {
public let uCount: Int
public let vCount: Int
public var isEmpty: Bool { get }
public func at(u: Int, v: Int) -> SIMD3<Double>
}
uCount/vCount: number of samples in each direction.at(u:v:): the point at that grid index. Traps if either index is out of range.isEmpty:truefor the grid returned when sampling fails.
Storage is U-major (u * vCount + v). Every surface grid producer in the bridge writes that same layout (drawMesh, evaluateGrid and evaluateGridD1) since #486; before it, two bridge functions wrote opposite layouts, each describing its own as “row-major”.
SurfaceGrid.at
SurfaceGridD1
The D1 counterpart of SurfaceGrid, returned by evaluateGridD1(uParameters:vParameters:) (see Surface-Analysis.md). Indexed .at(u:v:) for the same reason: a flat [(point:, d1u:, d1v:)] array leaves the caller guessing whether u or v runs fastest, which is exactly the ambiguity the deprecated gridEvalD1(uParams:vParams:) shipped with (#486).
public struct SurfaceGridD1: Sendable {
public let uCount: Int
public let vCount: Int
public var isEmpty: Bool { get }
public func at(u: Int, v: Int) -> (point: SIMD3<Double>, d1u: SIMD3<Double>, d1v: SIMD3<Double>)
}
at(u:v:): point and both first partial derivatives at that grid index. Traps if either index is out of range.isEmpty:truefor the grid returned when evaluation fails.- Example:
let grid = surface.evaluateGridD1(uParameters: [0, 0.5, 1], vParameters: [0, 1]) let sample = grid.at(u: 2, v: 0) let normal = simd_normalize(simd_cross(sample.d1u, sample.d1v))
| Member | Kind | Meaning |
|---|---|---|
uCount | public stored property | Number of samples in the U direction. |
vCount | public stored property | Number of samples in the V direction. |
SurfaceGridD1.at(u:v:)
Point and both first partial derivatives at grid index (u, v).
public func at(u: Int, v: Int) -> (point: SIMD3<Double>, d1u: SIMD3<Double>, d1v: SIMD3<Double>)
Looks up the flat, U-major backing arrays (points, d1u, d1v) at surfaceGridIndex(u:v:vCount:), the same indexing function SurfaceGrid.at(u:v:) uses, so the two types can never disagree about which of U or V runs fastest.
- Parameters:
u,v: grid indices;uin0..<uCount,vin0..<vCount. - Returns: A tuple of the sampled point and its first partial derivatives in U and V.
- Precondition: traps if either index is out of range.
- OCCT: Pure-Swift indexing over buffers
Surface.evaluateGridD1(uParameters:vParameters:)fills.
drawMesh(uCount:vCount:)
Samples a uniform grid of points over the surface’s parametric bounds.
public func drawMesh(uCount: Int = 20, vCount: Int = 20) -> SurfaceGrid
- Parameters:
uCount— number of U sample points, at least 1;vCount— number of V sample points, at least 1. - Returns: A
SurfaceGridindexed.at(u:v:), or an empty grid if sampling fails or the grid cannot be served. The bound is on the product:uCount * vCountmust not exceedSampling.maximumSampleCount(10,000,000). Each factor is also checked on its own, which is not redundant — two negative counts multiply to a plausible positive total, sodrawMesh(uCount: -1, vCount: -1)used to look well-behaved whiledrawMesh(uCount: -1, vCount: 3)aborted the process (#558). - Sampled range: the surface’s parametric domain with infinite bounds clamped to ±100. For a bounded surface that is
domain; for an unbounded one it is not, and the difference is extreme rather than marginal — a plane’sdomain.uMinis about -2e100 while its first sample sits at -100. - One sample in a direction is a request, not a degenerate case. Despite the name nothing here triangulates, so
uCount: 1is the single iso-row at the low end of the sampled U range and a 1×1 grid is one point. The bridge used to demand 2 per direction — its own interpolation divisor, not an OCCT rule — so a request this page called in-range came back as an empty grid indistinguishable from a failed sample (#620). Counts below 1 are still rejected, and rejection is still an empty grid. - OCCT:
Geom_Surface::D0sampled on a uniform UV grid. - Example:
let mesh = surface.drawMesh(uCount: 30, vCount: 30) let p = mesh.at(u: 5, v: 3) // A single V iso-row: 20 points along v, at the low end of the sampled U range. let isoRow = surface.drawMesh(uCount: 1, vCount: 20) let along = (0..<isoRow.vCount).map { isoRow.at(u: 0, v: $0) } // Bounded: that low end is domain.uMin. Unbounded: it is the clamp instead. let plane = Surface.plane(origin: .zero, normal: SIMD3(0, 0, 1))! plane.drawMesh(uCount: 1, vCount: 4).at(u: 0, v: 0).x // -100, not plane.domain.uMin
Bounding Box
boundingBox
The axis-aligned bounding box of this surface.
public var boundingBox: (min: SIMD3<Double>, max: SIMD3<Double>)? { get }
Returns nil for infinite surfaces (planes, full cylinders) because BndLib_AddSurface cannot produce a finite box. Trim the surface first.
- Returns: Tuple of min and max AABB corners, or
nilfor infinite or degenerate surfaces. - OCCT:
GeomAdaptor_Surface+BndLib_AddSurface::Add. - Example:
let sphere = Surface.sphere(center: .zero, radius: 5)! if let bb = sphere.boundingBox { // bb.min ≈ SIMD3(-5, -5, -5), bb.max ≈ SIMD3(5, 5, 5) }
Surface Transform (v0.128.0)
In-place transform variants that modify the surface geometry directly rather than returning a copy. All return @discardableResult Bool.
translate(dx:dy:dz:)
Translates the surface in place.
@discardableResult
public func translate(dx: Double, dy: Double, dz: Double) -> Bool
- Parameters:
dx,dy,dz— translation components. - Returns:
trueif successful. - OCCT:
Geom_Surface::Translate(gp_Vec)applied in-place viaOCCTSurfaceTransform.
rotate(axisOrigin:axisDirection:angle:)
Rotates the surface in place around an axis.
@discardableResult
public func rotate(axisOrigin: SIMD3<Double>, axisDirection: SIMD3<Double>, angle: Double) -> Bool
- Parameters:
axisOrigin— point on the rotation axis;axisDirection— axis direction;angle— angle in radians. - Returns:
trueif successful. - OCCT:
Geom_Surface::Rotate(gp_Ax1, angle)in-place.
scale(center:factor:)
Scales the surface in place from a centre point.
@discardableResult
public func scale(center: SIMD3<Double>, factor: Double) -> Bool
- Parameters:
center— scaling origin;factor— scale factor. - Returns:
trueif successful. - OCCT:
Geom_Surface::Scale(gp_Pnt, factor)in-place.
mirrorPoint(_:)
Mirrors the surface in place through a point.
@discardableResult
public func mirrorPoint(_ point: SIMD3<Double>) -> Bool
- Parameters:
point— the mirror centre. - Returns:
trueif successful. - OCCT:
Geom_Surface::Mirror(gp_Pnt)in-place.
mirrorAxis(origin:direction:)
Mirrors the surface in place through an axis.
@discardableResult
public func mirrorAxis(origin: SIMD3<Double>, direction: SIMD3<Double>) -> Bool
- Parameters:
origin— a point on the mirror axis;direction— axis direction. - Returns:
trueif successful. - OCCT:
Geom_Surface::Mirror(gp_Ax1)in-place.
mirrorPlane(origin:normal:)
Mirrors the surface in place through a plane.
@discardableResult
public func mirrorPlane(origin: SIMD3<Double>, normal: SIMD3<Double>) -> Bool
- Parameters:
origin— a point on the mirror plane;normal— plane normal direction. - Returns:
trueif successful. - OCCT:
Geom_Surface::Mirror(gp_Ax2)in-place.
GeomEval Surface Factories (v0.130.0)
Parametric surface factories backed by Geom_CartesianPoint-derived evaluator surfaces. All return nil on invalid parameters.
Surface.ellipsoid(a:b:c:)
Creates a triaxial ellipsoid surface.
public static func ellipsoid(a: Double, b: Double, c: Double) -> Surface?
Parametrisation: P(u,v) = a·cos(v)·cos(u)·X + b·cos(v)·sin(u)·Y + c·sin(v)·Z.
- Parameters:
a— semi-axis along X (> 0);b— semi-axis along Y (> 0);c— semi-axis along Z (> 0). - Returns: Ellipsoid surface, or
nilif any semi-axis ≤ 0. - OCCT:
OCCTGeomEvalEllipsoidCreate— customGeom_Surfaceevaluator. - Example:
let ellipsoid = Surface.ellipsoid(a: 10, b: 6, c: 4)
Surface.hyperboloid(r1:r2:twoSheets:)
Creates a hyperboloid of revolution surface.
public static func hyperboloid(r1: Double, r2: Double, twoSheets: Bool = false) -> Surface?
- Parameters:
r1— first semi-axis radius (> 0);r2— second semi-axis radius (> 0);twoSheets— iftrue, creates a two-sheet hyperboloid. - Returns: Hyperboloid surface, or
nilon failure. - OCCT:
OCCTGeomEvalHyperboloidCreate.
Surface.paraboloid(focal:)
Creates a circular paraboloid of revolution surface.
public static func paraboloid(focal: Double) -> Surface?
- Parameters:
focal— focal distance (must be > 0). - Returns: Paraboloid surface, or
niliffocal ≤ 0. - OCCT:
OCCTGeomEvalParaboloidCreate. - Example:
let dish = Surface.paraboloid(focal: 5)
Surface.circularHelicoid(pitch:)
Creates a circular helicoid (ruled surface).
public static func circularHelicoid(pitch: Double) -> Surface?
Parametrisation: S(u,v) = v·cos(u)·X + v·sin(u)·Y + (P·u / 2π)·Z.
- Parameters:
pitch— axial advance per 2π turn (must be ≠ 0). - Returns: Helicoid surface, or
nilon failure. - OCCT:
OCCTGeomEvalCircularHelicoidCreate. - Example:
let helicoid = Surface.circularHelicoid(pitch: 3.0)
Surface.hyperbolicParaboloid(a:b:)
Creates a hyperbolic paraboloid (saddle surface).
public static func hyperbolicParaboloid(a: Double, b: Double) -> Surface?
Parametrisation: P(u,v) = u·X + v·Y + (u²/a² − v²/b²)·Z.
- Parameters:
a— first semi-axis length (> 0);b— second semi-axis length (> 0). - Returns: Saddle surface, or
nilon failure. - OCCT:
OCCTGeomEvalHypParaboloidCreate.
Surface.gordon(profiles:guides:tolerance:)
Builds a Gordon surface from a network of profile and guide curves.
public static func gordon(profiles: [Curve3D], guides: [Curve3D], tolerance: Double = 1e-3) -> Surface?
Requires at least 2 profile curves (V-direction) and 2 guide curves (U-direction) that form a complete grid: every profile must intersect every guide within tolerance.
- Parameters:
profiles— profile curves (V-direction, ≥ 2);guides— guide curves (U-direction, ≥ 2);tolerance— geometric tolerance for intersection detection. - Returns: Gordon BSpline surface, or
nilif construction fails. - OCCT:
OCCTGeomFillGordon/GeomFill_Gordon. - Note: Use
gordonReport(profiles:guides:tolerance:allowApproximateFallback:)to get detailed failure diagnostics. - Example:
// Requires profiles and guides to intersect at a grid of points if let surf = Surface.gordon(profiles: profiles, guides: guides, tolerance: 1e-3) { let face = surf.toFace() }
GordonResultStatus
Result status enum mirroring GeomFill_Gordon::ResultStatus.
public enum GordonResultStatus: Int, Sendable {
case notStarted, done, invalidInput, conversionFailed, intersectionFailed,
orderingFailed, reparametrizationFailed, compatibilityFailed,
curveCompatibilityFailed, rationalReparametrizationFailed,
skinningFailed, referenceSurfaceFailed, knotAlignmentFailed,
rationalDegreeOverflow, rationalConstructionFailed,
periodicityFailed, approximationFailed, constructionFailed
}
One case per stage of GeomFill_Gordon::Perform(); the descriptions below are the C++ ResultStatus enum’s own doc comments in GeomFill_Gordon.hxx, carried over case for case.
| Case | Meaning |
|---|---|
.notStarted | Perform() has not been called since initialization. |
.done | Surface has been constructed. |
.invalidInput | Input network has too few profile or guide curves. |
.conversionFailed | Curves could not be converted or reparametrized to B-splines. |
.intersectionFailed | Full profile/guide intersection table could not be built. |
.orderingFailed | Network curves could not be ordered consistently. |
.reparametrizationFailed | Intersections could not be equalized in parameter space. |
.compatibilityFailed | Prepared network failed geometric compatibility checks. |
.curveCompatibilityFailed | Prepared curve families are not B-spline compatible. |
.rationalReparametrizationFailed | Rational curves require unsupported exact reparametrization. |
.skinningFailed | Intermediate profile/guide skinning has failed. |
.referenceSurfaceFailed | Intersection-grid reference surface could not be built. |
.knotAlignmentFailed | Intermediate surfaces could not be aligned. |
.rationalDegreeOverflow | Exact rational product degree exceeds OCCT’s B-spline limit. |
.rationalConstructionFailed | Exact rational numerator/denominator construction has failed. |
.periodicityFailed | Closed seam could not be converted to periodic form. |
.approximationFailed | Optional approximate fallback (allowApproximateFallback) has failed. |
.constructionFailed | Final B-spline surface construction has failed. |
Each case is indexed below too, so a reference link can land on one directly; the table above is the authoritative description.
Surface.GordonResultStatus.constructionFailed
GordonResult
Outcome struct returned by gordonReport(...).
public struct GordonResult: Sendable {
public let surface: Surface?
public let status: GordonResultStatus
public let isApproximate: Bool
}
isApproximate is true when the result was produced by a sampled B-spline fallback rather than exact interpolation.
GordonResult.isApproximate
Surface.gordonReport(profiles:guides:tolerance:allowApproximateFallback:)
Builds a Gordon surface, returning the result status and approximate flag.
public static func gordonReport(profiles: [Curve3D], guides: [Curve3D],
tolerance: Double = 1e-3,
allowApproximateFallback: Bool = false) -> GordonResult
- Parameters:
profiles— profile curves (≥ 2);guides— guide curves (≥ 2);tolerance— intersection tolerance;allowApproximateFallback— permit sampled B-spline fallback when exact construction fails. - Returns:
GordonResultwith the surface (ornil), status code, and approximate flag. - OCCT:
OCCTGeomFillGordonReport.
NetworkSurfaceStatus
Result status enum mirroring GeomFill_NetworkSurface::ResultStatus.
public enum NetworkSurfaceStatus: Int, Sendable {
case notStarted, done, invalidInput, curveCompatibilityFailed,
skinningFailed, referenceSurfaceFailed, knotAlignmentFailed,
rationalDegreeOverflow, rationalConstructionFailed,
constructionFailed, periodicityFailed
}
One case per stage of GeomFill_NetworkSurface::Perform(); the descriptions below are the C++ ResultStatus enum’s own doc comments in GeomFill_NetworkSurface.hxx, carried over case for case. NetworkSurfaceStatus is the low-level counterpart of GordonResultStatus: fewer cases because networkSurface(...) receives an already-ordered, already-compatible network (no orderingFailed/reparametrizationFailed/compatibilityFailed/conversionFailed/ intersectionFailed/approximationFailed, which are GeomFill_Gordon’s own upstream-preparation stages).
| Case | Meaning |
|---|---|
.notStarted | Perform() has not been called since initialization. |
.done | Surface has been constructed. |
.invalidInput | Prepared network does not satisfy the builder’s requirements. |
.curveCompatibilityFailed | Curve families could not be converted to a compatible basis. |
.skinningFailed | Profile or guide skin interpolation has failed. |
.referenceSurfaceFailed | Intersection-grid reference surface could not be built. |
.knotAlignmentFailed | Intermediate surfaces could not be aligned to one knot basis. |
.rationalDegreeOverflow | Exact rational product degree exceeds OCCT’s B-spline limit. |
.rationalConstructionFailed | Exact rational numerator/denominator construction has failed. |
.constructionFailed | Internal B-spline construction has failed. |
.periodicityFailed | Closed seam could not be converted to periodic form. |
Surface.NetworkSurfaceStatus.periodicityFailed
Surface.networkSurface(profiles:guides:tolerance:)
Builds a surface with the low-level GeomFill_NetworkSurface builder.
public static func networkSurface(profiles: [Curve3D], guides: [Curve3D],
tolerance: Double = 1e-3)
-> (surface: Surface?, status: NetworkSurfaceStatus)
Curves are converted to non-periodic BSplines; each profile/guide pair’s real contact point and its parameter in the other family’s domain are found via GeomAPI_ExtremaCurveCurve and averaged across the family, matching what GeomFill_NetworkSurface::Perform() requires to align its internal profile/guide/reference skins to one knot basis (#689). This is a lower-level alternative to gordon(...): it does not reorder a scrambled network, run gordon’s reparametrization pass, or derive rational contact weights (every contact point is weighted 1.0), so a network gordon can complete can still decline here.
- Parameters:
profiles— profile curves in U, at least 2;guides— guide curves in V, at least 2;tolerance— geometric tolerance for closed-seam checks. - Returns: Tuple of the surface (or
nil) and a status code. - OCCT:
OCCTGeomFillNetworkSurface/GeomFill_NetworkSurface.
Surface.KnotSplitResult
Result of knotSplitting(uContinuity:vContinuity:) (documented in full on Surface-Advanced.md): the split counts, parameter values, and knot-table indices needed to divide a BSpline surface into pieces meeting a requested continuity in U and V.
public struct KnotSplitResult {
public let uSplitCount: Int
public let vSplitCount: Int
public let uSplitParams: [Double]
public let vSplitParams: [Double]
public let uSplitIndices: [Int]
public let vSplitIndices: [Int]
}
| Field | Meaning |
|---|---|
uSplitCount | Number of U split locations needed for the requested continuity. |
vSplitCount | Number of V split locations needed for the requested continuity. |
uSplitParams | U parameter values (ascending, bounded by the surface’s own U range) at each split. |
vSplitParams | V parameter values (ascending, bounded by the surface’s own V range) at each split. |
uSplitIndices | 1-based indices into the surface’s own U knot table, one per split: uSplitParams[i] == bsplineUKnot(index: uSplitIndices[i]). |
vSplitIndices | 1-based indices into the surface’s own V knot table, one per split. |
All six fields are empty/zero for a non-BSpline surface. uSplitIndices/vSplitIndices exist because the underlying analyzer (GeomConvert_BSplineSurfaceKnotSplitting) reports both the parameter values and the knot-table indices those values came from; before #562 only the parameter-value form was exposed here.
Surface.KnotSplitResult.vSplitIndices
GeomEval TBezier / AHTBezier Surfaces (v0.131.0)
Surface.tBezier(poles:uCount:vCount:alphaU:alphaV:)
Creates a Trigonometric Bezier surface.
public static func tBezier(poles: [SIMD3<Double>], uCount: Int, vCount: Int,
alphaU: Double, alphaV: Double) -> Surface?
A tensor-product surface using trigonometric Bernstein-like bases in both U and V. Parameter domain is U ∈ [0, π/alphaU], V ∈ [0, π/alphaV].
- Parameters:
poles— control points in row-major order (must have exactlyuCount * vCountelements).uCount— number of poles in U (must be odd, ≥ 3).vCount— number of poles in V (must be odd, ≥ 3).alphaU— frequency parameter in U (> 0).alphaV— frequency parameter in V (> 0).
- Returns: TBezier surface, or
nilif counts are invalid or even. - OCCT:
OCCTGeomEvalTBezierSurfaceCreate. - Example:
var poles = [SIMD3<Double>](repeating: .zero, count: 9) // fill 3×3 grid ... let s = Surface.tBezier(poles: poles, uCount: 3, vCount: 3, alphaU: 1, alphaV: 1)
Surface.ahtBezier(poles:uCount:vCount:algDegreeU:algDegreeV:alphaU:alphaV:betaU:betaV:)
Creates an Algebraic-Hyperbolic-Trigonometric (AHT) Bezier surface.
public static func ahtBezier(poles: [SIMD3<Double>], uCount: Int, vCount: Int,
algDegreeU: Int, algDegreeV: Int,
alphaU: Double, alphaV: Double,
betaU: Double, betaV: Double) -> Surface?
A tensor-product surface using mixed AHT bases in both U and V. Parameter domain is U, V ∈ [0, 1]. Combines algebraic (polynomial), hyperbolic, and trigonometric basis functions.
- Parameters:
poles— control points in row-major order (uCount * vCountelements).uCount,vCount— grid dimensions (≥ 1).algDegreeU,algDegreeV— algebraic degree in U and V (≥ 0).alphaU,alphaV— hyperbolic frequency in U and V (≥ 0).betaU,betaV— trigonometric frequency in U and V (≥ 0).
- Returns: AHT Bezier surface, or
nilon invalid parameters. - OCCT:
OCCTGeomEvalAHTBezierSurfaceCreate.
v0.115.0: Surface from point grid, normal, curvatures
Surface.fromPointGrid(points:uCount:vCount:degMin:degMax:continuity:tolerance:)
Approximates a BSpline surface through a grid of 3D points.
public static func fromPointGrid(points: [SIMD3<Double>], uCount: Int, vCount: Int,
degMin: Int = 3, degMax: Int = 8,
continuity: Int = 2, tolerance: Double = 1e-3) -> Surface?
Points must be in row-major order: point[v * uCount + u]. Uses GeomAPI_PointsToBSplineSurface for the fit.
- Parameters:
points— flat array of 3D points in row-major order (must have exactlyuCount * vCountelements).uCount— number of points in U direction.vCount— number of points in V direction.degMin— minimum BSpline degree (default 3).degMax— maximum BSpline degree (default 8).continuity— desired continuity, aParametricContinuityraw value (0=C0, 1=C1, 2=C2, 3=C3; default 2). Unlikeapproximated(...), the point fitter accepts the whole range without failing.tolerance— approximation tolerance.
- Returns: Fitted BSpline surface, or
nilifpoints.count ≠ uCount * vCountor fitting fails. - OCCT:
GeomAPI_PointsToBSplineSurface. - Example:
// Build a 4×4 grid from a sampled cloud var pts = [SIMD3<Double>]() for v in 0..<4 { for u in 0..<4 { pts.append(SIMD3(Double(u), Double(v), sin(Double(u + v)))) }} if let surf = Surface.fromPointGrid(points: pts, uCount: 4, vCount: 4) { let bb = surf.boundingBox }
normal(u:v:)
Computes the surface normal at (u, v).
public func normal(u: Double, v: Double) -> SIMD3<Double>
Always returns a vector; where the normal is undefined the result is the zero vector. Prefer the optional-returning normal(atU:v:) (from the Evaluation section) when you need to tell “undefined” apart from a genuine result.
Both entry points evaluate the same GeomLProp_SLProps normal and gate on the same IsNormalDefined() test, so they always agree on where the normal exists — they differ only in how the absence is reported (nil vs. the zero vector). Before #401 this method hand-rolled Geom_Surface::D1 plus a cross product against a literal 1e-15 magnitude epsilon, which classified degeneracy differently: arbitrarily close to a cone apex it returned a spurious zero vector for a normal OCCT resolves perfectly well.
A cone apex is a genuine singularity for this test; a sphere pole (v = ±π/2) is not — OCCT still resolves the tangent plane there.
- Parameters:
u— U parameter;v— V parameter. - Returns: Unit surface normal at (u, v), or
SIMD3(0, 0, 0)where the normal is undefined. - OCCT:
GeomLProp_SLProps::Normal(via the sharedOCCTSurfaceGetNormalbridge path). - Example:
let cone = Surface.cone(origin: .zero, axis: SIMD3(0, 0, 1), radius: 0, semiAngle: .pi / 6)! #expect(simd_length(cone.normal(u: 0, v: 0)) < 1e-12) // apex: undefined #expect(cone.normal(atU: 0, v: 0) == nil) // ... reported as nil here
curvatures(u:v:)
Computes Gaussian and mean curvature at (u, v).
public func curvatures(u: Double, v: Double) -> (gaussian: Double, mean: Double)?
Equivalent to calling gaussianCurvature(atU:v:) and meanCurvature(atU:v:) at the same point, for one GeomLProp_SLProps evaluation instead of two. All three share that single construction — and therefore its resolution argument, Precision::Confusion(), which is what IsCurvatureDefined() tests tangent vectors against for nullity. Before #405 this method built its own GeomLProp_SLProps with a hardcoded 1e-6, ten times looser, and could report (0, 0) at a point where its two siblings returned a real curvature.
- Parameters:
u— U parameter;v— V parameter. - Returns: Tuple of Gaussian curvature (K = k_min × k_max) and mean curvature (H = (k_min + k_max) / 2), or
nilwhere curvature is undefined. It returned(0, 0)there until #595 — which is also a plane’s real answer, so the agreement on definedness this method’s own contract claims was not one it could express. - OCCT:
GeomLProp_SLProps::GaussianCurvatureandMeanCurvature(order 2,Precision::Confusion()). - Example:
let sphere = Surface.sphere(center: .zero, radius: 5)! if let (K, H) = sphere.curvatures(u: 0, v: 0) { // K ≈ 0.04 (= 1/25), H ≈ 0.2 (= 1/5) } sphere.curvatures(u: 0, v: .pi / 2) // nil — the pole has no curvature