OpenCASCADE Concepts for Swift Developers
This guide explains core OCCT concepts for developers coming from iOS/Swift backgrounds who may not be familiar with CAD kernel terminology.
What is a CAD Kernel?
A CAD kernel is the mathematical engine that represents and manipulates 3D geometry. Unlike game engines (Unity, SceneKit) that work with triangulated meshes, CAD kernels use exact mathematical representations.
Key difference:
SceneKit sphere:
- List of triangles approximating a sphere
- More triangles = smoother, but never perfect
- Can't calculate exact surface area
OCCT sphere:
- Mathematical equation: x² + y² + z² = r²
- Infinitely smooth (conceptually)
- Exact calculations possible
- Convert to triangles only when needed for display
B-Rep: Boundary Representation
OCCT uses B-Rep (Boundary Representation) to define solids. A solid is defined by its boundary surfaces, not by filling space with voxels or triangles.
Topology Hierarchy
COMPOUND ─── Contains multiple unrelated shapes
│
COMPSOLID ── Multiple solids sharing faces
│
SOLID ────── A watertight 3D volume
│
SHELL ────── Collection of connected faces (surface)
│
FACE ─────── A bounded portion of a surface
│
WIRE ─────── A connected sequence of edges (loop)
│
EDGE ─────── A bounded portion of a curve
│
VERTEX ───── A point in 3D space
Example: A Simple Box
Box (SOLID)
├── Shell (SHELL) - the 6 faces
│ ├── Top face (FACE) - bounded by 4 edges
│ │ └── Wire (WIRE) - the boundary
│ │ ├── Edge 1 (EDGE) - top front edge
│ │ ├── Edge 2 (EDGE) - top right edge
│ │ ├── Edge 3 (EDGE) - top back edge
│ │ └── Edge 4 (EDGE) - top left edge
│ ├── Bottom face (FACE)
│ ├── Front face (FACE)
│ ├── Back face (FACE)
│ ├── Left face (FACE)
│ └── Right face (FACE)
└── Vertices (8 corners)
In OCCTSwift
// Creates a full B-Rep structure internally
let box = Shape.box(width: 10, height: 5, depth: 3)
// You don't see the topology directly - Shape wraps it
// But OCCT maintains full topological information
Geometry vs Topology
OCCT separates geometry (mathematical shapes) from topology (how they connect).
Geometry (Mathematical Definitions)
- gp_Pnt: A 3D point (x, y, z)
- Geom_Line: An infinite line
- Geom_Circle: A circle (center, radius, plane)
- Geom_BSplineCurve: A NURBS curve
- Geom_Plane: An infinite plane
- Geom_CylindricalSurface: An infinite cylinder
- Geom_BSplineSurface: A NURBS surface
Topology (Bounded Regions)
- TopoDS_Vertex: A point on geometry
- TopoDS_Edge: A bounded curve segment
- TopoDS_Wire: Connected edges forming a loop
- TopoDS_Face: A bounded surface region
- TopoDS_Shell: Connected faces
- TopoDS_Solid: A closed volume
Example
A cylinder face:
- Geometry: Geom_CylindricalSurface (infinite)
- Topology: TopoDS_Face with boundary wires
- Wire 1: Circle at top
- Wire 2: Circle at bottom
- These bound the finite portion of the infinite surface
Wires: The Building Blocks
A Wire is a connected sequence of edges forming a path. Wires are essential for:
- 2D Profiles: Cross-sections to sweep or extrude
- 3D Paths: Curves along which to sweep profiles
- Face Boundaries: Defining the edges of a face
Creating Wires in OCCTSwift
// 2D rectangle profile (for extrusion)
let profile = Wire.rectangle(width: 10, height: 5)
// 2D custom profile (for rail cross-section)
let railProfile = Wire.polygon([
SIMD2(0, 0),
SIMD2(3, 0),
SIMD2(3, 1),
SIMD2(2, 1),
SIMD2(2, 8),
SIMD2(1, 8),
SIMD2(1, 1),
SIMD2(0, 1)
], closed: true)
// 3D arc path (for curved track)
let path = Wire.arc(
center: .zero,
radius: 500,
startAngle: 0,
endAngle: .pi / 4
)
Shape Operations
Boolean Operations (CSG)
Constructive Solid Geometry combines shapes:
// Union: Add shapes together
let combined = shapeA + shapeB // or shapeA.union(with: shapeB)
// Subtraction: Cut one from another
let holed = block - cylinder // or block.subtracting(cylinder)
// Intersection: Keep only overlapping volume
let common = shapeA & shapeB // or shapeA.intersection(with: shapeB)
How it works internally:
- Find intersection curves between surfaces
- Split faces along intersection curves
- Classify face regions (inside/outside/on boundary)
- Build result from appropriate regions
Pitfalls:
- Shapes must actually intersect for useful result
- Tangent (just-touching) cases can be problematic
- Result may be invalid if input shapes have issues
Sweep Operations
Create solids by moving profiles:
// Extrusion: Move profile linearly
let beam = Shape.extrude(
profile: rectangle,
direction: SIMD3(0, 0, 1),
length: 100
)
// Revolution: Rotate profile around axis
let vase = Shape.revolve(
profile: crossSection,
axisOrigin: .zero,
axisDirection: SIMD3(0, 1, 0),
angle: .pi * 2 // Full rotation
)
// Pipe sweep: Move profile along arbitrary path
let rail = Shape.sweep(
profile: railProfile,
along: curvedPath
)
Pipe sweep details:
- Profile must be planar (2D wire)
- Profile is positioned perpendicular to path at start
- Profile “follows” the path, staying perpendicular
- For curved paths, the profile rotates (Frenet frame)
Modification Operations
// Fillet: Round edges
let rounded = shape.filleted(radius: 2.0)
// Finds all edges and applies circular fillet
// Chamfer: Bevel edges
let beveled = shape.chamfered(distance: 1.0)
// Creates flat cut at 45° along edges
// Shell: Hollow out a solid
let hollow = shape.shelled(thickness: 1.0)
// Removes interior, leaving walls of given thickness
// Offset: Expand/shrink all faces
let bigger = shape.offset(by: 0.5)
// Moves all faces outward (positive) or inward (negative)
Meshing: From B-Rep to Triangles
For visualization (SceneKit) and export (STL), we convert exact geometry to triangles.
Deflection Parameters
let mesh = shape.mesh(
linearDeflection: 0.1, // Max distance from true surface (mm)
angularDeflection: 0.5 // Max angle between adjacent segments (radians)
)
Linear deflection: Controls chord height - how far a triangle edge can deviate from the true curve.
True curve: ────────────────
\ /
\ 0.1mm /
\ max /
Tessellation: ────────
Angular deflection: Controls how much adjacent polygon edges can turn. Smaller = more segments on tight curves.
Choosing Deflection Values
| Use Case | Linear | Angular | Notes |
|---|---|---|---|
| Quick preview | 0.5 | 1.0 | Fast, chunky |
| Interactive display | 0.1 | 0.5 | Good balance |
| 3D print (FDM) | 0.05 | 0.3 | Suitable for ~0.2mm layers |
| 3D print (SLA) | 0.02 | 0.2 | High detail |
| CNC machining | 0.01 | 0.1 | Precision output |
Precision and Tolerances
OCCT uses 64-bit doubles and tracks tolerances explicitly.
Default Precision
Precision::Confusion() = 1e-7 // Points closer than this are "same"
Tolerance in Practice
Each topological entity has a tolerance:
- Vertex: sphere of radius tolerance (point is “somewhere in there”)
- Edge: tube around curve
- Face: band around surface
This handles numeric imprecision from floating-point calculations.
For 3D Printing
Model railway at HO scale (1:87):
- 0.01mm in model = 0.87mm prototype
- OCCT’s default precision (1e-7 = 0.0001mm) is more than sufficient
- FDM printers typically have 0.1-0.4mm resolution anyway
Common Patterns in OCCTSwift
Creating a Rail Section
// 1. Define the rail cross-section (2D profile)
let railProfile = Wire.polygon([
SIMD2(0, 0), // Base left
SIMD2(2.5, 0), // Base right
SIMD2(2.5, 0.8), // Web start
SIMD2(1.8, 0.8), // Web right
SIMD2(1.8, 6.5), // Head start
SIMD2(2.2, 6.8), // Head flare
SIMD2(2.2, 7.5), // Head top right
SIMD2(0.3, 7.5), // Head top left
SIMD2(0.3, 6.8), // Head flare left
SIMD2(0.7, 6.5), // Head start left
SIMD2(0.7, 0.8), // Web left
SIMD2(0, 0.8) // Back to base
], closed: true)
// 2. Define the track path (3D curve)
let straightPath = Wire.line(
from: SIMD3(0, 0, 0),
to: SIMD3(100, 0, 0)
)
// 3. Sweep to create the rail
let rail = Shape.sweep(profile: railProfile, along: straightPath)
// 4. Position for left/right rail
let leftRail = rail.translated(by: SIMD3(0, 0, -6)) // Half gauge
let rightRail = rail.translated(by: SIMD3(0, 0, 6))
// 5. Combine
let trackSection = Shape.compound([leftRail, rightRail])
Creating a Sleeper with Holes
// Base sleeper block
let sleeper = Shape.box(width: 25, height: 2, depth: 4)
// Mounting holes for clips
let hole = Shape.cylinder(radius: 0.4, height: 2.5)
.translated(by: SIMD3(0, -0.25, 0)) // Slightly below top
// Position holes at rail locations
let holeLeft = hole.translated(by: SIMD3(0, 0, -6))
let holeRight = hole.translated(by: SIMD3(0, 0, 6))
// Subtract holes from sleeper
let sleeperWithHoles = sleeper - holeLeft - holeRight
Exporting for 3D Printing
// Combine all track components
let fullTrack = Shape.compound([
rails,
sleepers,
clips
])
// Export as STL with appropriate resolution
try Exporter.writeSTL(
shape: fullTrack,
to: URL(fileURLWithPath: "track.stl"),
deflection: 0.03 // Fine for FDM printing
)
Error Handling
OCCT operations can fail silently, returning empty or invalid shapes.
Checking Validity
let result = shapeA - shapeB
if !result.isValid {
// Boolean failed - maybe shapes don't intersect
// or geometry is too complex
let healed = result.healed()
if healed.isValid {
// Healing fixed it
} else {
// Need to adjust input geometry
}
}
Common Failure Causes
- Non-intersecting booleans: Subtracting a shape that doesn’t touch
- Self-intersecting profiles: Wire crosses itself
- Degenerate geometry: Zero-length edges, zero-area faces
- Tolerance issues: Parts that should touch, don’t quite
- Complex fillet: Radius too large for edge configuration