Design Loop: Sizing a Part and Printing It
using Rasmah
using LinearAlgebraOverview
A real design is not one analysis — it is a loop: build the part, check it against the requirements, change it, and check again until it passes, then hand the result to manufacturing. This tutorial walks the full loop end to end:
- size a steel strap by running the finite-element check at several thicknesses until the factor of safety meets the requirement;
- verify the chosen design and its mass;
- slice it and emit the G-code a 3D printer executes.
The part is the same tension strap as the linear elasticity tutorial: 100 mm long, 30 mm wide, pulled by a 30 kN service load at its ends, made of A36 structural steel. The requirement: a factor of safety of at least 1.5 against yield, meaning the working stress must stay below 250 / 1.5 ≈ 167 MPa. The question: how thick does the strap need to be — and no thicker, because every extra millimetre is material, mass, and cost?
set_unitsystem!(MM_TONNE_S)UnitSystem(mm, tonne, s, A, K, mol, cd, deg, N, MPa, MPa, mJ, W, mm/s, tonne/mm^3, tonne/(mm·s), Hz)
The sizing loop
For a strap in pure tension the working stress is σ = F/(W·t), so the required thickness is t = F/(W·σ_allow) = 30000/(30·167) = 6 mm. But that hand formula assumes uniform stress; the point of the loop is to prove it with the finite-element model — the same check you would run on a part without a closed-form answer. We solve the full model at t = 3, 4, 5, 6 mm and report the mid-span von Mises stress and the resulting factor of safety:
# 1. the design space — a strap of variable thickness, 30 kN at the ends
for t in (3.0, 4.0, 5.0, 6.0)
# 2. geometry + boundary sets (the workflow order never changes)
part = box(100u"mm", 30u"mm", t * u"mm")
b = brep(part)
left = faces_in_box(b, [-55u"mm", -20u"mm", -10u"mm"], [-45u"mm", 20u"mm", 10u"mm"])
right = faces_in_box(b, [ 45u"mm", -20u"mm", -10u"mm"], [ 55u"mm", 20u"mm", 10u"mm"])
# 3. mesh, 4. boundary conditions (30 kN spread over the 30 × t mm² face), 5. solve
mesh = tetrahedralize(b, 10)
u = solve(Elasticity(), mesh, steel;
fixed = left => [0.0, 0.0, 0.0],
loads = right => [(30000.0 / (30.0 * t))u"MPa", 0.0u"MPa", 0.0u"MPa"])
# post-process: mid-span von Mises stress and the factor of safety
σv = von_mises_stress(mesh, ustrip(u), stiffness(steel))
mid = [sum(mesh.nodes[1, mesh.elements[:, e]]) / 4 for e in axes(mesh.elements, 2)]
σmid = maximum(σv[i] for i in eachindex(mid) if 0.04 < mid[i] < 0.06)
fos = yield_strength(steel) / σmid
mass = density(steel) * ustrip(volume(part)) * 1e3 # grams
println("t = ", t, " mm: σ = ", round(σmid / 1e6; digits = 1), " MPa, FoS = ",
round(fos; digits = 2), ", mass = ", round(mass; digits = 1), " g")
endt = 3.0 mm: σ = 333.3 MPa, FoS = 0.75, mass = 70.6 g
t = 4.0 mm: σ = 250.0 MPa, FoS = 1.0, mass = 94.2 g
t = 5.0 mm: σ = 200.0 MPa, FoS = 1.25, mass = 117.8 g
t = 6.0 mm: σ = 166.7 MPa, FoS = 1.5, mass = 141.3 g
The table tells the story:
t = 3, 4 mmfail: factors of safety 0.75 and 1.0 are at or below the yield point — the strap would plastically deform in service.t = 5 mmyields, but the safety factor 1.25 is below the required 1.5.t = 6 mmgivesFoS = 1.5— exactly the requirement.
So the lightest strap that passes is 6 mm thick, at 141 g. The finite element model confirms the hand formula element-by-element; the loop is what automates the search over the design.
Verify the chosen design
Lock in the winner and look at the full stress picture — the mid-span field is uniform at the design stress, with the familiar corner concentrations at the clamped face:
part = box(100u"mm", 30u"mm", 6u"mm")
b = brep(part)
left = faces_in_box(b, [-55u"mm", -20u"mm", -10u"mm"], [-45u"mm", 20u"mm", 10u"mm"])
right = faces_in_box(b, [ 45u"mm", -20u"mm", -10u"mm"], [ 55u"mm", 20u"mm", 10u"mm"])
mesh = tetrahedralize(b, 10)
u = solve(Elasticity(), mesh, steel;
fixed = left => [0.0, 0.0, 0.0],
loads = right => [(30000.0 / (30.0 * 6.0))u"MPa", 0.0u"MPa", 0.0u"MPa"])
σv = von_mises_stress(mesh, ustrip(u), stiffness(steel))
println("max stretch = ", round(uconvert(u"mm", maximum(abs, u)); digits = 4), " mm")
println("mass = ", round(density(steel) * ustrip(volume(part)) * 1e3; digits = 1), " g")max stretch = 0.0835 mm
mass = 141.3 g
vtk(mesh; field = σv, fieldname = "von Mises stress", colormap = :inferno, scalarbar = true)From analysis to G-code
The design now goes to manufacturing. Rasmah’s additive pipeline takes the part from mesh to machine instructions: tessellate the surface, slice it into layers, and turn the layers into extruder moves:
# tessellate the strap to a triangle mesh, then slice it for 0.2 mm layers
sm = surface_mesh(part; n = 20)
layers = slice(sm; layer_height = ustrip(0.2u"mm"))
println("triangles: ", size(sm.faces, 2))
println("layers: ", length(layers))triangles: 4328
layers: 31
vtk(sm)A print plan records the slicing plus the extrusion settings — perimeters, infill density, line width, filament diameter:
pp = print_plan(sm; layer_height = ustrip(0.2u"mm"))
println("perimeters: ", pp.perimeters, " infill: ", pp.infill_density * 100, "%")perimeters: 3 infill: 20.0%
print_gcode turns the plan into the Marlin-flavoured G-code a typical FDM printer executes — heating, per-layer perimeter loops, and infill with extrusion:
gcode = print_gcode(pp)
lines = split(gcode, '\n')
println(length(lines), " lines of G-code, starting:")
for l in lines[1:4]
println(" ", l)
end14731 lines of G-code, starting:
; Rasmah print plan
M104 S210 ; set extruder temp
M140 S60 ; set bed temp
M190 S60 ; wait bed
That is the whole loop, closed: a load requirement became a thickness, the thickness passed a factor-of-safety check, and the same model became printer code. Every step ran on the same geometry — change the load, re-run the loop, and the design, the check, and the G-code all update together.
Next steps
- Linear elasticity with the finite element method — the detailed walkthrough of the solve and design checks used here.
- Density-based topology optimization — instead of sizing a fixed shape, let the optimizer find where the material should go.
- CAM toolpaths — the subtractive counterpart: milling toolpaths instead of printed layers.
