Example 3: Vertical load transfer through walls#
The drawing from papermodels’ Example 3, which adds walls and columns that carry load from
floor to floor. The “Wall bottom” annotation transfers as a line load onto the FB2.2 beam.
loadtracer analyses walls with loadbearing_wall and carries their line reactions down.
The notebook and its PDF are in Examples/Example 3 in the repository.
%matplotlib inline
import sys
import warnings
from decimal import Decimal
import pandas as pd
from papermodels.datatypes.geometry_graph import GeometryGraph
from papermodels.datatypes.joist_models import JoistArrayModel
from loadtracer.load_tracer import LoadTracer
sys.path.insert(0, "..")
from helpers import (
bottom_members,
compare_modes,
load_check,
load_table,
occupancies_in,
quietly,
reaction_table,
)
pd.options.display.float_format = "{:,.2f}".format
pd.options.display.max_rows = 40
1. Build the graph (papermodels)#
pdf_path = "sketch_to_scale-vertical-loads.pdf"
graph = GeometryGraph.from_pdf_file(pdf_path, scale=Decimal(1 / 72 * 4))
graph.assign_collector_behaviour(JoistArrayModel, spacing=1.0)
graph.plot_annotations(2)
2. Occupancies#
loadtracer needs the loads of each occupancy in the drawing, as pressures by load case. These are the occupancies that the loading areas use:
occupancies_in(graph)
['big stuff', 'residential', 'roof']
The drawing is in feet, so these pressures (psf) give loads in lb/ft and lb.
occupancies = {
"big stuff": {"D": 50.0, "L": 100.0},
"residential": {"D": 15.0, "L": 40.0},
"roof": {"D": 20.0, "S": 35.0},
}
3. Run loadtracer#
Every element is analysed from the top of the graph down. Only the graph and the occupancies
are needed; element heights and floor elevations are not inputs. artifacts/ receives the
analysis models (models.json) and the reactions (reaction_forces.json).
lt = LoadTracer(geometry_graph=graph, occupancies=occupancies)
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always")
quietly(lt.execute_analyses, artifacts_dir="artifacts")
print(f"{len(lt.reactions)} members analysed, {len(lt.errors)} errors")
for w in caught:
print(f"{type(w.message).__name__}: {w.message}")
124 members analysed, 0 errors
JoistLoadVariabilityWarning: Member FB3.1: the reactions of joist array J3.0 (D) vary by 3.0:1 along 0.116 to 18.302; they were converted to a uniform load. Review the joist loads.
JoistLoadVariabilityWarning: Member FB3.1: the reactions of joist array J3.0 (L) vary by 2.5:1 along 0.116 to 18.302; they were converted to a uniform load. Review the joist loads.
JoistTribWidthWarning: Member FB3.1: joist array J3.0 carries big stuff, residential; its trib widths are expressed with 'big stuff' and are approximate.
JoistLoadVariabilityWarning: Member FB3.0: the reactions of joist array J3.0 (D) vary by 2.7:1 along 0.0 to 18.187; they were converted to a uniform load. Review the joist loads.
JoistLoadVariabilityWarning: Member FB3.0: the reactions of joist array J3.0 (L) vary by 2.3:1 along 0.0 to 18.187; they were converted to a uniform load. Review the joist loads.
JoistTribWidthWarning: Member FB3.0: joist array J3.0 carries big stuff, residential; its trib widths are expressed with 'big stuff' and are approximate.
LoadTracerWarning: 10 input issue(s) were corrected on 3 member(s); review LoadTracer.issues.
All of the load applied to the building reaches the bottom of the graph:
load_check(lt)
| applied | at the bottom | |
|---|---|---|
| D | 14,958.88 | 14,958.88 |
| L | 28,580.79 | 28,580.79 |
| S | 6,563.13 | 6,563.13 |
lt.issues lists input that loadtracer corrected so that the rundown could continue
(for example, load drawn past the end of a member, which is moved to the member end):
{tag: issues for tag, issues in lt.issues.items()}
{'WT3.0': ['0.274 of the distributed load from WT4.0 (D) extends past the member end at 0.0; its load was applied as a point load at the end.',
'0.231 of the distributed load from WT4.0 (D) extends past the member end at 10.58; its load was applied as a point load at the end.',
'0.274 of the distributed load from WT4.0 (S) extends past the member end at 0.0; its load was applied as a point load at the end.',
'0.231 of the distributed load from WT4.0 (S) extends past the member end at 10.58; its load was applied as a point load at the end.'],
'WB0.0': ['0.010 of the distributed load from WT1.0 (D) extends past the member end at 0.0; its load was applied as a point load at the end.',
'0.010 of the distributed load from WT1.0 (L) extends past the member end at 0.0; its load was applied as a point load at the end.'],
'WB2.0': ['0.051 of the distributed load from WT3.0 (D) extends past the member end at 0.0; its load was applied as a point load at the end.',
'0.170 of the distributed load from WT3.0 (D) extends past the member end at 10.357; its load was applied as a point load at the end.',
'0.051 of the distributed load from WT3.0 (S) extends past the member end at 0.0; its load was applied as a point load at the end.',
'0.170 of the distributed load from WT3.0 (S) extends past the member end at 10.357; its load was applied as a point load at the end.']}
4. One member, end to end: WT4.0#
WT4.0 is a roof-level wall carrying the J4.0 roof joists. Walls get distributed joist
loads too. The trib width is half of the joist span, as in a hand calculation.
pd.DataFrame(lt.converted_joist_loads("WT4.0"))
| member | array | joists | case | dir | profile | start_loc | end_loc | w_start | w_end | total | max_min_ratio | occupancy | pressure | trib_width_start | trib_width_end | approximate | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | WT4.0 | J4.0 | 13 | D | FY | uniform | 0.57 | 11.69 | 122.14 | 122.14 | 1,357.43 | 1.00 | roof | 20.00 | 6.11 | 6.11 | False |
| 1 | WT4.0 | J4.0 | 13 | S | FY | uniform | 0.57 | 11.69 | 213.74 | 213.74 | 2,375.49 | 1.00 | roof | 35.00 | 6.11 | 6.11 | False |
The loads on WT4.0 after expansion, by load case:
load_table(lt, "WT4.0")
| kind | source | case | start | end | w_start / P | w_end | total | profile | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | distributed | J4.0 | D | 0.57 | 11.69 | 122.14 | 122.14 | 1,357.43 | uniform |
| 1 | distributed | J4.0 | S | 0.57 | 11.69 | 213.74 | 213.74 | 2,375.49 | uniform |
The wall model (loadbearing_wall) of WT4.0:
{key: lt.models["WT4.0"][key] for key in ("length", "distributed_loads", "point_loads")}
{'length': 11.914,
'distributed_loads': {'FY': {'D': [{'start_magnitude': 122.13649685744124,
'end_magnitude': 122.13649685744124,
'start_loc': 0.573,
'end_loc': 11.687}],
'S': [{'start_magnitude': 213.7388695005222,
'end_magnitude': 213.7388695005222,
'start_loc': 0.573,
'end_loc': 11.687}]}},
'point_loads': {}}
The reactions of WT4.0 (positive downwards on the supports):
reaction_table(lt, ["WT4.0"])
| D | S | ||
|---|---|---|---|
| member | support | ||
| WT4.0 | 0 | 1,357.43 | 2,375.49 |
FB2.2 carries the line load from the wall above (WB2.0). Only the part of the wall
that is over the beam is transferred to it:
load_table(lt, "FB2.2")
| kind | source | case | start | end | w_start / P | w_end | total | profile | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | distributed | WB2.0 | D | 0.34 | 0.79 | 207.73 | 207.73 | 93.82 | |
| 1 | distributed | WB2.0 | D | 0.79 | 0.84 | 140.96 | 140.96 | 7.24 | |
| 2 | distributed | WB2.0 | D | 0.84 | 10.26 | 121.74 | 121.74 | 1,145.98 | |
| 3 | distributed | WB2.0 | D | 10.26 | 10.43 | 182.28 | 182.28 | 31.19 | |
| 4 | distributed | WB2.0 | D | 10.43 | 10.76 | 238.62 | 238.62 | 79.20 | |
| 5 | distributed | WB2.0 | S | 0.34 | 0.79 | 363.53 | 363.53 | 164.18 | |
| 6 | distributed | WB2.0 | S | 0.79 | 0.84 | 246.69 | 246.69 | 12.68 | |
| 7 | distributed | WB2.0 | S | 0.84 | 10.26 | 213.04 | 213.04 | 2,005.46 | |
| 8 | distributed | WB2.0 | S | 10.26 | 10.43 | 318.98 | 318.98 | 54.57 | |
| 9 | distributed | WB2.0 | S | 10.43 | 10.76 | 417.58 | 417.58 | 138.61 |
5. Joist loads as point loads or distributed loads#
With joist_loads="point" each joist reaction is applied as a point load, which is the most
detailed representation. The total load is the same either way. Individual reactions are the
same for uniform joist reactions and differ more the more the joist reactions vary; arrays that
vary by 2:1 or more are listed in the warnings in section 3. For those, check the point load
results.
lt_point = LoadTracer(geometry_graph=graph, occupancies=occupancies, joist_loads="point")
with warnings.catch_warnings():
warnings.simplefilter("ignore")
quietly(lt_point.execute_analyses)
pd.concat({"point": load_check(lt_point)["at the bottom"], "distributed": load_check(lt)["at the bottom"]}, axis=1)
| point | distributed | |
|---|---|---|
| D | 14,958.88 | 14,958.88 |
| L | 28,580.79 | 28,580.79 |
| S | 6,563.13 | 6,563.13 |
compare_modes(lt_point, lt, ["WT4.0"])
| point | distributed | difference | |||||
|---|---|---|---|---|---|---|---|
| D | S | D | S | D | S | ||
| member | support | ||||||
| WT4.0 | 0 | 1,357.43 | 2,375.49 | 1,357.43 | 2,375.49 | 0.00 | 0.00 |
6. Reactions at the bottom#
The loads delivered to the elements at the bottom of the graph (the foundation):
reaction_table(lt, bottom_members(lt))
| D | L | S | ||
|---|---|---|---|---|
| member | support | |||
| WB0.0 | 0 | 434.14 | 1,157.70 | 0.00 |
| CT1.6 | 0 | 352.03 | 938.74 | 0.00 |
| CB0.2 | 0 | -249.48 | -1,416.60 | 522.63 |
| CB0.5 | 0 | 2,298.78 | 4,284.42 | 1,072.14 |
| CB0.3 | 0 | 5,353.18 | 11,128.02 | 1,698.60 |
| CB0.0 | 0 | 4,138.25 | 8,202.38 | 1,615.36 |
| CB0.1 | 0 | 1,501.00 | 2,264.13 | 1,052.74 |
| CB0.4 | 0 | 1,130.98 | 2,021.99 | 601.65 |