RasmahRasmah

Machine Setup, Fixtures, and Collision Checking

Overview

Before a single chip is cut, a machinist decides five things: the stock the part comes out of, the work offset that locates it on the table, the fixtures that hold it, the machine envelope the tool can reach, and where the clamps stand so the cutter does not hit them. Rasmah provides the pieces — Stock, Setup, suggest_fixtures for clamping points, and Machine/in_envelope/check_collisions for verifying a toolpath stays inside the machine and clear of obstacles.

The part in this tutorial is the 80 × 60 × 8 mm adapter plate from the Snapping tutorial; dimensions are millimetres.

using Rasmah

Stock and setup

The plate is drawn, so the first question — what block of steel does it come out of? — is a box around the part plus a machining allowance. stock_from_part adds the clearance on every face:

plate = difference(
    extrude(rect_2d(80.0, 60.0), 8.0),
    reduce(union, [translate(cylinder(3.0, 9.0), 25.0 * sx, 15.0 * sy, 0.0)
                   for sx in (-1.0, 1.0) for sy in (-1.0, 1.0)]),
)
part = surface_mesh(plate; n = 48)

s = stock_from_part(part; clearance = 3.0)
su = setup(part; stock = s)
lo, hi = bounds(to_sdf(s.mesh))
println("stock bounds = ", round.(lo; digits = 1), " … ", round.(hi; digits = 1))
stock bounds = [-43.0, -33.0, -7.0] … [43.0, 33.0, 7.0]

With 3 mm all round, the blank is 86 × 66 × 14 mm. The buyer’s arithmetic follows: the blank weighs 624 g of steel, the finished plate 294 g, so 330 g — more than half the bar — becomes chips:

V_stock = prod(hi .- lo)
V_part = 80.0 * 60.0 * 8.0 - 4 * π * 3.0^2 * 8.0
ρ = 7.85e-3                                   # g/mm³
println("blank    = ", round(V_stock * ρ; digits = 1), " g")
println("finished = ", round(V_part * ρ; digits = 1), " g")
println("chips    = ", round((V_stock - V_part) * ρ; digits = 1), " g")
blank    = 623.8 g
finished = 294.3 g
chips    = 329.5 g

Fixture points

Where to clamp? suggest_fixtures proposes evenly-spaced clamping points around the part’s footprint — the starting guess a fixture designer then adjusts around the actual clamp hardware:

fixtures = suggest_fixtures(part; n = 4)
println("fixture count = ", length(fixtures), ", first at ", round.(fixtures[1]; digits = 1))
fixture count = 4, first at [40.0, 0.0, -4.0]

The machine envelope

Not every machine can cut every part. A Haas Mini-Mill-class machine has a 406 × 305 × 254 mm work envelope; in_envelope reports whether a tool position is reachable:

machine = CartesianMachine(406.0, 305.0, 254.0)

println("inside the envelope:  ", in_envelope(machine, [200.0, 150.0, 100.0]))
println("outside it:           ", !in_envelope(machine, [500.0, 0.0, 0.0]))
inside the envelope:  true
outside it:           true

A toe clamp drawn from primitives

The real danger in a setup is the clamp: a rapid traverse into the clamping hardware is the classic crash. Model the clamp the same way as the part — a 44 × 12 × 12 mm body with a Ø9 bolt hole, and the stud that ties it to the T-slot table. The clamp sits over the plate’s front rim at y = 26:

clamp_body = translate(difference(box(44.0, 12.0, 12.0), cylinder(4.5, 13.0)), 0.0, 26.0, 10.0)
stud = translate(cylinder(4.5, 18.0), 0.0, 26.0, 2.0)

println("sdf at the Ø9 bolt hole: ", field(clamp_body, [0.0, 26.0, 10.0]))   # +4.5 → not solid
println("sdf in the clamp body:   ", field(clamp_body, [12.0, 26.0, 10.0]))  # −6 → solid
sdf at the Ø9 bolt hole: 4.5
sdf in the clamp body:   -6.0

Because the clamp is a signed-distance field, “inside or not” is one evaluation — and note the Ø9 hole reports outside: a small tool really could pass through it.

Now run the collision check on a rapid at z = 10 that sweeps along the clamp line. The check samples every motion segment along its length, so it returns the first tool position whose tool volume intersects an obstacle — no need for a waypoint to land exactly inside the clamp — or nothing when the path is clear:

tp = ToolPath(EndMill(0.006))
push_cut!(tp, [-40.0, 26.0, 10.0]); push_cut!(tp, [12.0, 26.0, 10.0]); push_cut!(tp, [40.0, 26.0, 10.0])
println("rapid through the clamp: collision at ", check_collisions(tp, [to_sdf(clamp_body), to_sdf(stud)]))
rapid through the clamp: collision at [-14.0, 26.0, 10.0]

The fix is the same as on the shop floor — move the clamp to the far side of the part and re-run the identical check:

moved = translate(difference(box(44.0, 12.0, 12.0), cylinder(4.5, 13.0)), 0.0, -26.0, 10.0)
moved_stud = translate(cylinder(4.5, 18.0), 0.0, -26.0, 2.0)
println("clamp on the far side: collision = ", check_collisions(tp, [to_sdf(moved), to_sdf(moved_stud)]) !== nothing)
clamp on the far side: collision = false

The clamped setup, part and fixture together:

scene = surface_mesh(reduce(union, [plate, clamp_body, stud]); n = 48)
vtk(scene)

Next steps