Working with Meshes
A Mesh is OCCTSwift’s triangle-soup value type, vertices, per-vertex normals, and triangle indices. Meshing & Export covers how to get one (tessellate a shape) and how to write it to a file; this page is about operating on the Mesh itself: building one, inspecting it, mesh-level booleans, mapping triangles back to B-Rep faces, lifting it back to a solid, and feeding it to a renderer.
Build a mesh from your own arrays
You don’t have to start from a Shape, construct a Mesh directly from vertex and index arrays (indices are flat, three per triangle). Omit normals and they’re computed for you:
// an octahedron
let v: [SIMD3<Float>] = [
SIMD3(0, 0, 1), SIMD3(1, 0, 0), SIMD3(0, 1, 0),
SIMD3(-1, 0, 0), SIMD3(0, -1, 0), SIMD3(0, 0, -1),
]
let idx: [UInt32] = [0,1,2, 0,2,3, 0,3,4, 0,4,1, 5,2,1, 5,3,2, 5,4,3, 5,1,4]
guard let mesh = Mesh(vertices: v, indices: idx) else { return } // normals auto-computed
The init returns nil if the arrays are inconsistent (empty, indices.count % 3 != 0, an out-of-range index, or a normals count that doesn’t match vertices).
An octahedron built from 6 vertices + 8 triangles |
Inspect it
mesh.vertexCount // Int
mesh.triangleCount // Int
mesh.vertices // [SIMD3<Float>]
mesh.normals // [SIMD3<Float>] (per-vertex)
mesh.indices // [UInt32], 3 per triangle
mesh.vertexData // [Float], interleaved xyz, ready for a GPU buffer
mesh.normalData // [Float], interleaved xyz
mesh.boundingBox // (min: SIMD3<Float>, max: SIMD3<Float>)
mesh.size // max − min
mesh.center
Triangles ↔ B-Rep faces (picking)
trianglesWithFaces() gives per-triangle access that carries the source B-Rep face index and a per-triangle normal, so when a user picks a triangle on screen you can recover which face of the original solid they hit:
for tri in mesh.trianglesWithFaces() {
// tri.v1 / tri.v2 / tri.v3 : UInt32 vertex indices
// tri.faceIndex : Int32, the B-Rep face this triangle came from
// tri.normal : SIMD3<Float>
}
Mesh-level booleans
Mesh.union(with:), subtracting(_:) and intersection(with:) are Booleans on the sewn surfaces, not on volumes. toShape gives a shell, and a Boolean takes the dimension of its arguments, so the results are the surface operations: the union keeps the interior walls, the subtraction splits faces without removing anything, and the intersection is usually empty because two surfaces meet along curves. See docs/reference/Mesh.md for the measured numbers and why this is faithful to OCCT rather than a defect.
guard let a = Shape.box(width: 12, height: 12, depth: 12)?.mesh(linearDeflection: 0.3),
let b = Shape.cylinder(at: SIMD3(6, 6, -1), direction: SIMD3(0, 0, 1),
radius: 3, height: 14)?.mesh(linearDeflection: 0.3) else { return }
let splitFaces = a.subtracting(b, deflection: 0.3) // faces split, no hole drilled
let joinedSurfaces = a.union(with: b, deflection: 0.3) // interior walls kept
For the volume operations on mesh input, promote each sewn shell to a solid first and use the B-Rep booleans:
guard let solidA = Shape.solid(from: a.toShape()!),
let solidB = Shape.solid(from: b.toShape()!) else { return }
let drilled = solidA.subtracting(solidB)?.mesh(linearDeflection: 0.3)
The inputs are still tessellations, so the result is the faceted approximation of the exact solid operation. When the exact geometry is available, run the B-Rep Boolean on the shapes and mesh at the end instead.
Mesh → B-Rep
Lift a triangle mesh back to a shell of planar faces. The weld tolerance must scale with the model size (too tight and the mesh stays unwelded):
let shape = mesh.toShape(weldTolerance: 1e-6) // raise for large-coordinate meshes
The result is a faceted shell, not necessarily a valid solid, run healing if you need one. (For an STL on disk, prefer Shape.loadSTLRobust, which sews + heals as it loads, a multibody mesh comes back as a compound of solids.)
Hand it to a renderer
Mesh converts straight to the platform 3D types (each guarded by #if canImport, so they’re available where the framework is):
#if canImport(SceneKit)
let geometry = mesh.sceneKitGeometry() // SCNGeometry
let node = mesh.sceneKitNode() // SCNNode (optional material)
#endif
// raw Metal buffers (positions / normals / indices as Data)
let (positions, normals, indices) = mesh.metalBufferData()
There’s also RealityKit interop, mesh.realityKitMeshResource() (MeshResource) and mesh.realityKitModelEntity() (ModelEntity), both throws. They’re @MainActor-isolated and gated to macOS 15+ / iOS 18+ (RealityKit’s LowLevelMesh), so call them from the main actor inside an if #available check.
See also
- Meshing & Export, tessellate a
Shapeand write STL / OBJ / glTF. - Healing & Validity, clean up a shell from
mesh.toShape. - The OCCTSwiftMesh package adds decimation, smoothing, and repair on top of this
Meshtype.