Skip to content

Commit 6e38921

Browse files
committed
added point loads
1 parent e93a064 commit 6e38921

7 files changed

Lines changed: 195 additions & 33 deletions

File tree

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ readme = "README.md"
66
authors = [
77
{ name = "Connor Ferster", email = "connorferster@gmail.com" }
88
]
9-
requires-python = ">=3.13"
9+
requires-python = ">=3.11"
1010
dependencies = [
11-
"load-distribution>=0.1.0",
11+
"load-distribution==0.1.4",
1212
]
1313

1414
[project.scripts]

src/loadbearing_wall/geom_ops.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,24 @@ def apply_spread_angle(
2727
else:
2828
projected_x1 = min(wall_length, x1 + spread_amount)
2929
projected_length = projected_x1 - projected_x0
30-
assert projected_length <= wall_length
31-
original_length = x1 - x0
30+
if x1 is not None:
31+
original_length = x1 - x0
32+
else:
33+
original_length = 1
3234
ratio = original_length / projected_length
3335
projected_w0 = w0 * ratio
34-
projected_w1 = w1 * ratio
35-
assert (projected_w0 + projected_w1) / 2 * projected_length == (w0 + w1) / 2 * original_length
36-
return (projected_w0, projected_w1, projected_x0, projected_x1)
36+
if w1 is not None:
37+
projected_w1 = w1 * ratio
38+
else:
39+
projected_w1 = w0 * ratio
40+
return (round_to_close_integer(projected_w0), round_to_close_integer(projected_w1), round_to_close_integer(projected_x0), round_to_close_integer(projected_x1))
41+
42+
43+
def round_to_close_integer(x: float, eps = 1e-7) -> float | int:
44+
"""
45+
Rounds to the nearest int if it is REALLY close
46+
"""
47+
if abs(abs(round(x)) - abs(x)) < eps:
48+
return round(x)
49+
else:
50+
return x

src/loadbearing_wall/linear_reactions.py

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from dataclasses import dataclass
22
from typing import Optional
33
from load_distribution import Singularity, singularities_to_polygon
4+
from .geom_ops import round_to_close_integer as rtci
45

56
@dataclass
67
class LinearReaction:
@@ -45,7 +46,7 @@ def extract_reaction(self, xa: float, xb: float) -> "LinearReaction":
4546
xj = xb
4647
yj = y0 + (xj - self.x0) * m
4748

48-
return LinearReaction(yi, yj, xi, xj)
49+
return LinearReaction(rtci(yi), rtci(yj), rtci(xi), rtci(xj))
4950

5051

5152
@dataclass
@@ -73,18 +74,19 @@ def from_projected_loads(
7374
x0 = location_start_key
7475
x1 = location_end_key
7576
linear_reaction_components = {}
76-
for load_dir, dir_loads in projected_loads.items():
77+
for load_dir, load_cases in projected_loads.items():
7778
linear_reaction_components.setdefault(load_dir, {})
78-
for load_case, distributed_loads in dir_loads.items():
79+
for load_case, applied_loads in load_cases.items():
7980
linear_reaction_components[load_dir].setdefault(load_case, [])
80-
for distributed_load in distributed_loads:
81+
for applied_load in applied_loads:
8182
linear_reaction = LinearReaction(
82-
distributed_load[w0],
83-
distributed_load[w1],
84-
distributed_load[x0],
85-
distributed_load[x1],
83+
applied_load[w0],
84+
applied_load.get(w1),
85+
applied_load[x0],
86+
applied_load.get(x1),
8687
)
87-
linear_reaction_components[load_dir][load_case].append(linear_reaction)
88+
89+
linear_reaction_components[load_dir][load_case].append(linear_reaction)
8890
return cls(linear_reaction_components, w0, w1, x0, x1)
8991

9092

@@ -147,16 +149,28 @@ def consolidate_reactions(
147149
x1 = self.location_end_key
148150
reaction_components = {}
149151
flattened_reaction_components = []
150-
for load_dir, dir_loads in self.linear_reactions.items():
152+
for load_dir, load_cases in self.linear_reactions.items():
151153
reaction_components.setdefault(load_dir, {})
152-
for load_case, linear_reactions in dir_loads.items():
154+
for load_case, linear_reactions in load_cases.items():
153155
reaction_components[load_dir].setdefault(load_case, [])
154156
singularity_functions = []
155157
for lr in linear_reactions:
156-
m = (lr.w1 - lr.w0) / (lr.x1 - lr.x0)
157-
y0 = lr.w0
158-
singularity_function = Singularity(x0=lr.x0, y0=y0, x1=lr.x1, m=m, precision=3)
159-
singularity_functions.append(singularity_function)
158+
if lr.w1 is None and lr.x1 is None:
159+
point_load = {w0: lr.w0, x0: lr.x0, dir_key: load_dir, case_key: load_case}
160+
print(f"{point_load=}")
161+
flattened_reaction_components.append(
162+
point_load
163+
)
164+
reaction_components[load_dir][load_case].append(
165+
point_load
166+
)
167+
else:
168+
m = (lr.w1 - lr.w0) / (lr.x1 - lr.x0)
169+
y0 = lr.w0
170+
singularity_function = Singularity(x0=lr.x0, y0=y0, x1=lr.x1, m=m, precision=6)
171+
print(singularity_function)
172+
singularity_functions.append(singularity_function)
173+
if not singularity_functions: continue
160174
linear_reactions = singularity_xy_to_distributed_loads(
161175
singularities_to_polygon(
162176
singularity_functions, xy=True
@@ -173,7 +187,7 @@ def consolidate_reactions(
173187
flattened_reaction_components.append(linear_reactions)
174188

175189
# Get ride of the extrandious dir and case keys for unflattened results
176-
reaction_components[load_dir][load_case] = linear_reactions
190+
reaction_components[load_dir][load_case] += linear_reactions
177191
if flatten:
178192
return flattened_reaction_components
179193
return reaction_components
@@ -219,16 +233,16 @@ def singularity_xy_to_distributed_loads(
219233
w1 = magnitude_end_key
220234
x0 = location_start_key
221235
x1 = location_end_key
222-
filtered = filter_repeated_y_values(xy_vals)
223236
dist_loads = []
224237
prev_x = None
225-
for idx, (x, y) in enumerate(filtered):
238+
for idx, (x, y) in enumerate(zip(*xy_vals)):
239+
print(f"{(x, y)=}")
226240
if idx == 0: continue
227241
if prev_x is None:
228242
prev_x = x
229243
prev_y = y
230244
elif x - prev_x > 1e-3:
231-
dist_load = {w0: prev_y, w1: y, x0: prev_x, x1: x, case_key: case, dir_key: dir}
245+
dist_load = {w0: float(rtci(prev_y)), w1: float(rtci(y)), x0: float(rtci(prev_x)), x1: float(rtci(x)), case_key: case, dir_key: dir}
232246
dist_loads.append(dist_load)
233247
prev_x = x
234248
prev_y = y

src/loadbearing_wall/wall_model.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,8 @@ def spread_loads(self) -> None:
144144
self.length,
145145
self.vertical_spread_angle,
146146
dist_load[w0],
147-
dist_load.get(w1),
148147
dist_load[x0],
148+
dist_load.get(w1),
149149
dist_load.get(x1),
150150
)
151151
proj[load_dir][load_case].append({
@@ -156,6 +156,44 @@ def spread_loads(self) -> None:
156156
})
157157
else:
158158
proj[load_dir][load_case].append(dist_load)
159+
160+
for load_dir, load_cases in self.point_loads.items():
161+
proj.setdefault(load_dir, {})
162+
should_apply_spread_angle = (
163+
(
164+
load_dir == self.gravity_dir
165+
and self.apply_spread_angle_gravity
166+
and self.vertical_spread_angle != 0.0
167+
)
168+
or
169+
(
170+
load_dir == self.inplane_dir
171+
and self.apply_spread_angle_inplane
172+
and self.vertical_spread_angle != 0.0
173+
)
174+
)
175+
for load_case, point_loads in load_cases.items():
176+
proj[load_dir].setdefault(load_case, [])
177+
for point_load in point_loads:
178+
if should_apply_spread_angle:
179+
projected_load = geom.apply_spread_angle(
180+
self.height,
181+
self.length,
182+
self.vertical_spread_angle,
183+
point_load[w0],
184+
point_load[x0],
185+
point_load.get(w1),
186+
point_load.get(x1),
187+
)
188+
proj[load_dir][load_case].append({
189+
w0: projected_load[0],
190+
w1: projected_load[1],
191+
x0: projected_load[2],
192+
x1: projected_load[3]
193+
})
194+
else:
195+
proj[load_dir][load_case].append(point_load)
196+
159197
self._projected_loads = proj
160198

161199

tests/test_geom.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from loadbearing_wall.geom_ops import apply_spread_angle
2+
3+
4+
def test_apply_spread_angle():
5+
ret = apply_spread_angle(
6+
4,
7+
3,
8+
spread_angle=10,
9+
w0=10,
10+
x0=1,
11+
w1=10,
12+
x1=2
13+
14+
)
15+
assert ret == (4.148317542163208, 4.148317542163208, 0.2946920771661401, 2.70530792283386)

tests/test_wall_model.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ def WM0():
2525
"L": [
2626
{"w1": 100.0, "x1": 0.5},
2727
]
28+
},
29+
"Fx": {
30+
"W": [
31+
{"w1": 2000, "x1": 0.0}
32+
]
2833
}
2934
},
3035
gravity_dir = "Fz",
@@ -43,4 +48,27 @@ def WM1(WM0):
4348

4449
def test_wall_model_runs(WM0, WM1):
4550
assert WM0.get_reactions()
46-
assert WM1.get_reactions()
51+
assert WM1.get_reactions()
52+
53+
def test_no_spread(WM0):
54+
rxn = WM0.get_reactions(flattened=False)
55+
assert rxn['Fz']['D'] == [
56+
{"dir": "Fz", "case": "D", "w1": 100.0, "x1": 0.5},
57+
{"dir": "Fz", "case": "D", "w1": 10.0, "w2": 10.0, "x1": 1.0, "x2": 3.0},
58+
]
59+
assert rxn['Fz']['L'] == [
60+
{"dir": "Fz", "case": "L", "w1": 100.0, "x1": 0.5},
61+
{"dir": "Fz", "case": "L", "w1": 15.0, "w2": 15.0, "x1": 0.0, "x2": 2.0},
62+
]
63+
64+
def test_45_spread(WM1):
65+
rxn = WM1.get_reactions()
66+
assert rxn['Fz']['D'] == [
67+
{"dir": "Fz", "case": "D", "w1": 45.0, "w2": 45.0, "x1": 0.0, "x2": 2.499999999999},
68+
{"dir": "Fz", "case": "D", "w1": 5.0, "w2": 5.0, "x1": 2.500000000001, "x2": 4.0},
69+
]
70+
71+
assert rxn['Fz']['L'] == [
72+
{"dir": "Fz", "case": "L", "w1": 47.5, "w2": 47.5, "x1": 0.0, "x2": 2.499999999999},
73+
{"dir": "Fz", "case": "L", "w1": 7.5, "w2": 7.5, "x1": 2.500000000001, "x2": 4.0},
74+
]

0 commit comments

Comments
 (0)