5-Axis Tool Orientation and Gouge Avoidance
Overview
A 3-axis mill keeps the cutter vertical, so a concave surface that is more curved than the tool gets gouged — the tool digs into the wall. A 5-axis machine solves this by tilting the cutter to follow the surface normal, choosing a lead/tilt orientation that clears the local curvature and avoids rear-gouge and holder collisions. This tutorial shows how Rasmah decides that orientation: it measures the surface curvature, rules out orientations that would gouge, and picks the safe orientation closest to the surface normal.
The worked part is the classic 5-axis job: a die block — a 40 × 40 × 20 mm block of tool steel with a Ø30 mm spherical cavity scooped into the top face — finished with a Ø8 mm ball-nose mill. The cavity floor wants a vertical tool; its steep 60° rim wants the tool tilted onto the wall normal, and not every tilt is safe.
The key physical fact: a ball-nose cutter of radius R is a sphere, so its contact curvature is 1/R in every direction. It locally gouges a surface whose principal curvature is more concave than the ball — i.e. κ < -1/R.
using Rasmah
using LinearAlgebra
mm(v) = round(uconvert(u"mm", v); digits = 1)mm (generic function with 1 method)
Drawing the die block
The block is box − sphere: a 40 × 40 × 20 mm box with a Ø30 mm sphere whose centre sits 12 mm above the base centreline — so the cavity runs 13 mm deep and its mouth is Ø29.7 mm across. to_sdf turns the feature into the callable field the curvature analysis samples:
part = difference(box(0.040, 0.040, 0.020), translate(sphere(0.015), 0.0, 0.0, 0.012))
g = to_sdf(part)
floor = [0.0, 0.0, -0.003] # the deepest point of the cavity
θ = deg2rad(60.0)
rim = [0.015 * sin(θ), 0.0, 0.012 - 0.015 * cos(θ)] # a point on the 60° wall
println("rim: ", mm.(rim), " mm on surface: ", round(g(rim); digits = 6))rim: [13.0, 0.0, 4.5] mm on surface: -0.0
vtk(surface_mesh(part; n = 48))Curvature of a surface
surface_principal_curvatures reads the two principal curvatures κ₁ ≥ κ₂ of an implicit surface from the signed-distance field’s Hessian. Convex surfaces are positive, concave are negative. A sphere of radius r gives (1/r, 1/r), a cylinder (1/r, 0), a plane (0, 0):
println("R50 sphere : ", round.(surface_principal_curvatures(to_sdf(sphere(0.05)), [0.0, 0.0, 0.05]); digits = 1))
println("R50 cylinder : ", round.(surface_principal_curvatures(to_sdf(cylinder(0.05, 0.1)), [0.05, 0.0, 0.0]); digits = 1))R50 sphere : (20.0, 20.0)
R50 cylinder : (20.0, 0.0)
The concave cavity scooped from the block has negative curvature — -1/r for the Ø30 mm sphere, ≈ -66.7 in m⁻¹:
κ1, κ2 = surface_principal_curvatures(g, floor)
println("cavity floor : ", round(κ1; digits = 1), ", ", round(κ2; digits = 1), " (= -1/0.015)")cavity floor : -66.7, -66.7 (= -1/0.015)
Does the ball gouge locally?
ball_local_gouge(κ₁, κ₂, R) is the curvature-interference check: it is true when the surface is more concave than a ball of radius R (i.e. min(κ₁, κ₂) < -1/R). The Ø8 mm finishing ball fits the Ø30 mm cavity; a Ø40 mm ball would not:
println("R = 4 mm fits : ", !ball_local_gouge(-1 / 0.015, -1 / 0.015, 0.004))
println("R = 20 mm gouges : ", ball_local_gouge(-1 / 0.015, -1 / 0.015, 0.020))R = 4 mm fits : true
R = 20 mm gouges : true
Choosing a gouge-free orientation
gouge_free_orientation searches the lead/tilt angles (within the machine’s inclination limit) for an orientation that satisfies the curvature check, does not rear-gouge, and does not collide the holder — and returns the one closest to the surface normal. On the cavity floor the answer is vertical; on the 60° rim the answer tilts to follow the wall normal:
R = 0.004
a_floor = gouge_free_orientation(g, floor, [1.0, 0.0, 0.0], R; grid = 9)
println("floor axis : ", round.(a_floor; digits = 3)) # ≈ [0, 0, 1]
n = gradient(g, rim); n ./= norm(n)
a_rim = gouge_free_orientation(g, rim, [0.0, 1.0, 0.0], R; grid = 9)
println("rim normal : ", round.(n; digits = 3))
println("rim axis : ", round.(a_rim; digits = 3)) # ≈ the wall normal
println("a_rim ≈ normal: ", norm(a_rim .- n) < 1e-3)floor axis : [0.0, 0.0, 1.0]
rim normal : [-0.866, 0.0, 0.5]
rim axis : [-0.866, 0.0, 0.5]
a_rim ≈ normal: true
The steep rim is where the tilting earns its pay: only 54 of the 81 lead/tilt candidates clear the wall without rear-gouging or holder collision — a vertical 3-axis finish would gouge here:
fm = feasible_orientation_map(g, rim, [0.0, 1.0, 0.0], R; grid = 9)
println(count(fm), " of ", length(fm), " orientations are feasible")54 of 81 orientations are feasible
A full gouge-aware 5-axis finish
five_axis_finish_gouge_free rasters the surface and picks the safe orientation at every contact point, returning the tool path and the per-move tool axes:
tp, axes = five_axis_finish_gouge_free(surface_mesh(part; n = 48), BallEndMill(2R); stepover = 0.004, grid = 5)
oriented = [a for a in axes if a !== nothing] # drop infeasible points
println("moves: ", num_motions(tp), " (", length(oriented), " with an orientation)")
println("all axes unit-length: ", all(abs(norm(a) - 1.0) < 1e-9 for a in oriented))moves: 750 (750 with an orientation)
all axes unit-length: true
Summary
surface_principal_curvaturesmeasures the surface (convex > 0, concave < 0).ball_local_gouge(κ₁, κ₂, R)is theκ < -1/Rcurvature-interference rule.gouge_free_orientation/feasible_orientation_map/five_axis_finish_gouge_freechoose the gouge- and collision-free orientation closest to the surface normal.
Next steps
- CNC Toolpaths and 3D-Print Slicing — the CL-data model and 2.5D milling this orientation applies to.
- Boundary Representation (BRep) — the exact topology behind feature recognition.
- Mesh and SDF — signed fields and marching cubes.
