Example 1 - Mild Steel Warren Truss
In this example, we're going to play around with a few design parameters for a Warren-like steel truss with verticals, where we want to see what size of materials we can get away with in certain sections of the truss in order to minimize the cost.
We will do this by:
- Making a function that will generate a truss based on a few parameters.
- Generating a number of trusses with a certain load and analyzing it.
- Viewing stresses and finding structural weak points.
- Seeing what happens under extreme loads.
1. Making the setup function
The first thing we need to do is make a function that will generate a truss for us, given a few parameters. Here, we'll focus on making the function build_truss
, where the number of cross member sections (n), the width, height, and top load will be considered.
We will start by making the geometry of the system.
using SimpleStatics
width = (23 * 12 + 10) * 0.0254 # 23', 10" long -> Meters
height = 18 * 0.0254 # 18" tall -> Meters
I'll introduce this function all at once, but we'll go over every part in detail after that!
function build_truss(width_m, height_m, n, top_load = 0;
thick_material=Materials.SquareTubing(Materials.MildSteel, 1.5 * 0.0381, 1.897 / 1000), # 1.5" steel tubing with 14 gauge thickness
medium_material=Materials.SquareTubing(Materials.MildSteel, 1.0 * 0.0381, 1.518 / 1000), # 1.0" steel tubing with 16 gauge thickness
thin_material=Materials.SquareTubing(Materials.MildSteel, 0.75 * 0.0381, 1.518 / 1000) # .75" steel tubing with 16 gauge thickness
)
s = StaticSetup()
m = width_m / (2*n)
top_joint_indices = []
for m in range(0, width_m, 2n + 1)
j = add_joint!(s, m, height_m)
push!(top_joint_indices, j)
end
bottom_joint_indices = []
for m in range(m, width_m - m, n)
j = add_joint!(s, m, 0)
push!(bottom_joint_indices, j)
end
top_bar_members = []
for i in top_joint_indices[1:end-1]
m = add_member!(s, i, i+1)
push!(top_bar_members, m, thick_material)
end
bottom_bar_members = []
for i in bottom_joint_indices[1:end-1]
m = add_member!(s, i, i+1)
push!(bottom_bar_members, m, medium_material)
end
cross_members = []
for i in 1:n
for j in -1:1
m = add_member!(s, bottom_joint_indices[i], top_joint_indices[2*i + j], thin_material)
push!(cross_members, m)
end
end
set_constraint!(s, top_joint_indices[1], AnchorConstraint())
set_constraint!(s, top_joint_indices[end], XRollerConstraint())
SimpleStatics.add_member_weights!(s)
# add_force!(s, top_joint_indices[n+1], 0, -top_load) # middle of top bar
for i in top_joint_indices
add_force!(s, i, 0, -top_load/length(top_joint_indices))
end
return s
end
build_truss (generic function with 2 methods)
So that was a bit of code to chew on all at once. Before we pick this apart, lets just take a look at what this function creates with n=3 to make sure that it works.
plot_setup(build_truss(width, height, 3), dsize=1500)
So, in this function we start by defining some materials and dimensions, as well as the number of cross member repeat units n
and the top_load
that will be distributed over our truss itself.
function build_truss(width_m, height_m, n, top_load = 0;
thick_material=Materials.SquareTubing(Materials.MildSteel, 1.5 * 0.0381, 1.897 / 1000), # 1.5" steel tubing with 14 gauge thickness
medium_material=Materials.SquareTubing(Materials.MildSteel, 1.0 * 0.0381, 1.518 / 1000), # 1.0" steel tubing with 16 gauge thickness
thin_material=Materials.SquareTubing(Materials.MildSteel, 0.75 * 0.0381, 1.518 / 1000) # .75" steel tubing with 16 gauge thickness
)
Then, we start initialize our setup and define segment_length
, or the length of each member of the top bar in the truss. A truss with n
repeats will have 2n+1
joints in its top bar.
s = StaticSetup()
segment_len = width_m / (2*n)
top_joint_indices = []
for m in range(0, width_m, 2n + 1)
j = add_joint!(s, m, height_m)
push!(top_joint_indices, j)
end
And n
joints at the bottom bar, which is two segment_lengths
shorter than the top bar, so we have to shift the starting joint in by a segment_length
and stop one segment_length
early.
bottom_joint_indices = []
for m in range(segment_len, width_m - segment_len, n)
j = add_joint!(s, m, 0)
push!(bottom_joint_indices, j)
end
We can then connect all the top and bottom truss members, recording their indexes while doing so.
top_bar_members = []
for i in top_joint_indices[1:end-1]
m = add_member!(s, i, i+1)
push!(top_bar_members, m, thick_material)
end
bottom_bar_members = []
for i in bottom_joint_indices[1:end-1]
m = add_member!(s, i, i+1)
push!(bottom_bar_members, m, medium_material)
end
We can visualize what we've created so far with our plot_setup(setup)
function
truss = build_truss(width, height, 3)
plot_setup(truss)
Looks good so far! For the cross members, we want each bottom member to connect to its respective closest three top members.
cross_members = []
for i in 1:n
for j in -1:1
m = add_member!(s, bottom_joint_indices[i], top_joint_indices[2*i + j], thin_material)
push!(cross_members, m)
end
end
Lets see how this looks now.
truss = build_truss(width, height, 3)
plot_setup(truss)
Looks good! Finally, we want to add constraints to the first and last top member, so that the truss is held essentially by a fixed point and is able to freely expand and contract horizontally under load.
set_constraint!(s, top_joint_indices[1], AnchorConstraint())
set_constraint!(s, top_joint_indices[end], XRollerConstraint())
Finally, we want to add loads to this structure, both from the top_load
as well as the weights that the members themselves exert on it. Thankfully, we have the convenience function add_member_weights!(setup)
to do this for us.
SimpleStatics.add_member_weights!(s)
for i in top_joint_indices
add_force!(s, i, 0, -top_load/length(top_joint_indices))
end
We can visualize this final setup function and see that the forces and member weights were all applied.
truss = build_truss(width, height, 3)
plot_setup(truss)
Looks like we're ready to move on!
2. Generating some trusses and analyzing their properties.
Now we can generate a few structures with different values of n
, seeing how it affects the weight and strength of our structure. Lets try n values from 1 to 10 and look at 1, 5, and 10 to see what's going on.
width = (23 * 12 + 10) * 0.0254 # 23', 10" long -> Meters
height = 18 * 0.0254 # 18" tall -> Meters
load = 500 * 0.453592 * 9.81 # lbf -> N, 500 lb load
nvals = 1:10
trusses = [build_truss(width, height, nval, load) for nval in nvals]
plot_setup(trusses[1], dsize=2400)
plot_setup(trusses[5], dsize=2400)
plot_setup(trusses[10], dsize=2400)
This is in line with what we expected. Lets look at how much each truss weighs.
mass.(trusses) .* 2.20462 # kg -> lbs
10-element Vector{Float64}:
21.976536545521984
23.746595969880484
25.81242069250016
28.144134871246273
30.707550312022075
33.46831741325648
36.394655803347284
39.458700441922076
42.636849811263254
45.909536643712684
And finally we can solve the entire system and visualize the results for a couple structures. This time we'll hide the labels as well to be able to see more clearly.
displacements = solve_displacements.(trusses)
forces = solve_member_forces.(trusses, displacements)
reactions = solve_reaction_forces.(trusses, displacements)
member_stresses = solve_member_stresses.(trusses, forces)
plot_setup(trusses[1]; displacements=displacements[1], member_forces=forces[1], reactions=reactions[1], draw_labels=false)
plot_setup(trusses[5]; displacements=displacements[5], member_forces=forces[5], reactions=reactions[5], draw_labels=false)
plot_setup(trusses[10]; displacements=displacements[10], member_forces=forces[10], reactions=reactions[10], draw_labels=false)
The reaction forces quickly become too large to view on screen, but this is simply because the additional cross members make the structure heavier.
3. Viewing stresses rather than forces, and identifying weak members.
Interestingly, we can also view the maximum stress in the structure to see which members are bottlenecking our truss strength.
The default member property to view in the plot_setup
function is force, however, it is often more useful to view stress, which is a better indicator of how close a member is to reaching its yield point, or the stress at which permanent deformation (and therefore damage) to the member begins to occur.
This is useful as some members may be under comparitively little force but are very close to their yield point as they are thin, or you may want to identify members that are too thick and can be replaced with thinner materials you have on hand without jeopardizing the strength of the structure.
max_stresses = maximum.(member_stresses)
10-element Vector{Float64}:
4.531597867456804e6
5.840644324978685e6
8.684378825001232e6
8.875092204986885e6
8.619573018195927e6
8.327717774900751e6
8.083523719923201e6
7.896893429192176e6
7.76050837239896e6
7.664416633273726e6
One interesting thing to note in our structure is that we distributed the load perfectly over all the top joints, meaning we accidentally created an edge case where some portion of the load, specifcally $(2/2n+1)$ of our load, was distributed to constrained joints, which will not affect the system at all. As a consequence, the first three or so trusses actually appear to experience less stress than the trusses with more members. For now, we can ignore this, and look further to looking at stress utilization rather than stress outright.
max_stresses = maximum.(solve_stress_utilization.(trusses, member_stresses))
10-element Vector{Float64}:
0.018496317826354303
0.023839364591749734
0.0354464441836785
0.03622486614280361
0.03518193068651399
0.033990684795513273
0.032993974367033475
0.032232218078335415
0.03167554437713861
0.03128333319703562
It looks like this was a non issue for our larger trusses, as we never really end up using more than about 3.5% of our structures strength.
To visualize this, lets hand member_forces
our member_stresses
rather than the forces
themselves.
plot_setup(trusses[1]; dsize=1000, displacements=displacements[1], member_forces=member_stresses[1], reactions=reactions[1], draw_labels=false)
plot_setup(trusses[5]; dsize=1000, displacements=displacements[5], member_forces=member_stresses[5], reactions=reactions[5], draw_labels=false)
plot_setup(trusses[10]; dsize=1000, displacements=displacements[10], member_forces=member_stresses[10], reactions=reactions[10], draw_labels=false)
Interesting! So the cross members are under the most stress. This makes sense, as they're made from the thinnest material we had. Note that since stress is the force normalized by the materials cross sectional area, the member highlighting would be the same for either property if all members were the made from the same material.
4. Increasing the load.
For the sake of seeing deformations, lets increase the load until we see some interesting changes in our structure.
load = 100000 * 0.453592 * 9.81 # lbf -> N, 100000 lb load
tr = build_truss(width, height, 5, load)
displacements = solve_displacements(tr)
forces = solve_member_forces(tr, displacements)
reactions = solve_reaction_forces(tr, displacements)
member_stresses = solve_member_stresses(tr, forces)
plot_setup(tr; dsize=1000, displacements=displacements, member_forces=member_stresses, reactions=reactions, draw_labels=false)
Clearly this would destroy the truss, as we can see the stress utilization:
maximum(solve_stress_utilization(tr, member_stresses))
6.6058311956885465
The cross member is under more then 6x its yield stress.