kib-lap 0.5__cp313-cp313-win_amd64.whl → 0.7.7__cp313-cp313-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- KIB_LAP/Betonbau/TEST_Rectangular.py +21 -0
- KIB_LAP/Betonbau/beam_rectangular.py +4 -0
- KIB_LAP/FACHWERKEBEN/Elements.py +209 -0
- KIB_LAP/FACHWERKEBEN/InputData.py +118 -0
- KIB_LAP/FACHWERKEBEN/Iteration.py +967 -0
- KIB_LAP/FACHWERKEBEN/Materials.py +30 -0
- KIB_LAP/FACHWERKEBEN/Plotting.py +681 -0
- KIB_LAP/FACHWERKEBEN/__init__.py +4 -0
- KIB_LAP/FACHWERKEBEN/main.py +27 -0
- KIB_LAP/Plattentragwerke/PlateBendingKirchhoff.py +36 -29
- KIB_LAP/STABRAUM/InputData.py +13 -2
- KIB_LAP/STABRAUM/Output_Data.py +61 -0
- KIB_LAP/STABRAUM/Plotting.py +1453 -0
- KIB_LAP/STABRAUM/Programm.py +518 -1026
- KIB_LAP/STABRAUM/Steifigkeitsmatrix.py +338 -117
- KIB_LAP/STABRAUM/main.py +58 -0
- KIB_LAP/STABRAUM/results.py +37 -0
- KIB_LAP/Scheibe/Assemble_Stiffness.py +246 -0
- KIB_LAP/Scheibe/Element_Stiffness.py +362 -0
- KIB_LAP/Scheibe/Meshing.py +365 -0
- KIB_LAP/Scheibe/Output.py +34 -0
- KIB_LAP/Scheibe/Plotting.py +722 -0
- KIB_LAP/Scheibe/Shell_Calculation.py +523 -0
- KIB_LAP/Scheibe/Testing_Mesh.py +25 -0
- KIB_LAP/Scheibe/__init__.py +14 -0
- KIB_LAP/Scheibe/main.py +33 -0
- KIB_LAP/StabEbenRitz/Biegedrillknicken.py +757 -0
- KIB_LAP/StabEbenRitz/Biegedrillknicken_Trigeometry.py +328 -0
- KIB_LAP/StabEbenRitz/Querschnittswerte.py +527 -0
- KIB_LAP/StabEbenRitz/Stabberechnung_Klasse.py +868 -0
- KIB_LAP/plate_bending_cpp.cp313-win_amd64.pyd +0 -0
- KIB_LAP/plate_buckling_cpp.cp313-win_amd64.pyd +0 -0
- {kib_lap-0.5.dist-info → kib_lap-0.7.7.dist-info}/METADATA +1 -1
- {kib_lap-0.5.dist-info → kib_lap-0.7.7.dist-info}/RECORD +37 -19
- Examples/Cross_Section_Thin.py +0 -61
- KIB_LAP/Betonbau/Bemessung_Zust_II.py +0 -648
- KIB_LAP/Betonbau/Iterative_Design.py +0 -723
- KIB_LAP/Plattentragwerke/NumInte.cpp +0 -23
- KIB_LAP/Plattentragwerke/NumericalIntegration.cpp +0 -23
- KIB_LAP/Plattentragwerke/plate_bending_cpp.cp313-win_amd64.pyd +0 -0
- KIB_LAP/main.py +0 -2
- {Examples → KIB_LAP/StabEbenRitz}/__init__.py +0 -0
- {kib_lap-0.5.dist-info → kib_lap-0.7.7.dist-info}/WHEEL +0 -0
- {kib_lap-0.5.dist-info → kib_lap-0.7.7.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
# ============================================================
|
|
2
|
+
# FULL, RUNNABLE SCRIPT (UPDATED)
|
|
3
|
+
# - Loads + reactions + spring reactions + member forces
|
|
4
|
+
# - Sliders + CheckButtons
|
|
5
|
+
# - Optional node numbers + element numbers
|
|
6
|
+
# - Springs plotted ONLY where k>0 (or spring_dofs provided)
|
|
7
|
+
# ============================================================
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import matplotlib.pyplot as plt
|
|
11
|
+
from matplotlib.widgets import Slider, CheckButtons
|
|
12
|
+
from matplotlib.patches import FancyArrowPatch
|
|
13
|
+
from matplotlib.lines import Line2D
|
|
14
|
+
from matplotlib import transforms
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PlottingStructure:
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
_UG_FINAL,
|
|
21
|
+
_FI_FINAL,
|
|
22
|
+
_EXTFORCES,
|
|
23
|
+
_MBRFORCES,
|
|
24
|
+
_nodes,
|
|
25
|
+
_members,
|
|
26
|
+
_P0,
|
|
27
|
+
_restrainedIndex,
|
|
28
|
+
_reactionsFlag=True,
|
|
29
|
+
_forceVector=None,
|
|
30
|
+
_memberType=None,
|
|
31
|
+
):
|
|
32
|
+
self.UG_FINAL = np.asarray(_UG_FINAL, dtype=float) # (2*nNodes, nSteps)
|
|
33
|
+
self.FI_FINAL = np.asarray(_FI_FINAL, dtype=float) # (2*nNodes, nSteps)
|
|
34
|
+
self.EXTFORCES = np.asarray(_EXTFORCES, dtype=float) # (2*nNodes, nSteps)
|
|
35
|
+
self.MBRFORCES = np.asarray(_MBRFORCES, dtype=float) # (nMembers, nSteps)
|
|
36
|
+
|
|
37
|
+
self.nodes = np.asarray(_nodes, dtype=float) # (nNodes, 2)
|
|
38
|
+
self.members = list(_members) # [(i,j), ...] with 1-based node indices
|
|
39
|
+
self.P0 = np.asarray(_P0, dtype=float) # (nMembers,)
|
|
40
|
+
self.restrainedIndex = set(int(i) for i in _restrainedIndex) # dof indices (0-based)
|
|
41
|
+
self.reactionsFlag = bool(_reactionsFlag)
|
|
42
|
+
|
|
43
|
+
self.forceVector = _forceVector
|
|
44
|
+
self.memberType = _memberType if _memberType is not None else ["b"] * len(self.members)
|
|
45
|
+
|
|
46
|
+
# Optional (set from outside):
|
|
47
|
+
# self.Kspring_diag : (nDoF,) diagonal spring stiffness in global DOFs
|
|
48
|
+
# self.spring_dofs : set[int] DOFs that truly have springs (optional)
|
|
49
|
+
self.Kspring_diag = None
|
|
50
|
+
self.spring_dofs = None
|
|
51
|
+
|
|
52
|
+
# Tunable parameters
|
|
53
|
+
self.params = {
|
|
54
|
+
"Axial_Forces": False,
|
|
55
|
+
"Show_Loads": True,
|
|
56
|
+
"Show_Reactions": True,
|
|
57
|
+
"Show_Springs": True, # NEW
|
|
58
|
+
"Show_MemberVectors": True,
|
|
59
|
+
"Show_Legend": True,
|
|
60
|
+
|
|
61
|
+
"Show_NodeNumbers": True, # NEW
|
|
62
|
+
"Show_ElementNumbers": False, # NEW
|
|
63
|
+
|
|
64
|
+
"label_offset": 0.02, # node number offset (data coords)
|
|
65
|
+
"elem_label_offset": 0.00, # extra offset for element labels (data coords)
|
|
66
|
+
"xMargin": 0.2,
|
|
67
|
+
"yMargin": 0.4,
|
|
68
|
+
|
|
69
|
+
"scaleFactor": 1.0, # deformation scale
|
|
70
|
+
"Load_Increment": self.UG_FINAL.shape[1] - 1,
|
|
71
|
+
"Final_config": True,
|
|
72
|
+
|
|
73
|
+
# label & arrow tuning
|
|
74
|
+
"textScale": 1.0,
|
|
75
|
+
"arrowLenMin": 0.03, # relative to span
|
|
76
|
+
"arrowLenMax": 0.12, # relative to span
|
|
77
|
+
"arrowHeadScale": 12.0, # points
|
|
78
|
+
"labelOffsetPts": 10, # points
|
|
79
|
+
"refQuantile": 0.95, # reference force quantile for scaling
|
|
80
|
+
"softPower": 0.65, # 0.5..0.9 (lower => more compression)
|
|
81
|
+
"collisionIters": 25,
|
|
82
|
+
"collisionRadiusPts": 14,
|
|
83
|
+
"vectorScaleFactor": 1.0, # member vector scale
|
|
84
|
+
|
|
85
|
+
# NEW: spring filter thresholds
|
|
86
|
+
"springK_tol": 1e-12,
|
|
87
|
+
"springF_tol": 1e-9,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# -----------------------
|
|
91
|
+
# Helpers
|
|
92
|
+
# -----------------------
|
|
93
|
+
@staticmethod
|
|
94
|
+
def _soft_scale(value, ref, Lmin, Lmax, p=0.65):
|
|
95
|
+
if ref <= 0:
|
|
96
|
+
return 0.0
|
|
97
|
+
x = abs(value) / ref
|
|
98
|
+
s = x**p
|
|
99
|
+
s = min(max(s, 0.0), 1.0)
|
|
100
|
+
return Lmin + (Lmax - Lmin) * s
|
|
101
|
+
|
|
102
|
+
@staticmethod
|
|
103
|
+
def _auto_fontsize(ax, base=10, min_fs=7, max_fs=13, textScale=1.0):
|
|
104
|
+
bbox = ax.get_window_extent().transformed(ax.figure.dpi_scale_trans.inverted())
|
|
105
|
+
width_inch = bbox.width
|
|
106
|
+
fs = base * (width_inch / 6.0) ** 0.4
|
|
107
|
+
fs *= textScale
|
|
108
|
+
return float(np.clip(fs, min_fs, max_fs))
|
|
109
|
+
|
|
110
|
+
def _add_force_arrow(
|
|
111
|
+
self,
|
|
112
|
+
ax,
|
|
113
|
+
x, y,
|
|
114
|
+
vx, vy,
|
|
115
|
+
magnitude,
|
|
116
|
+
color="blue",
|
|
117
|
+
label=None,
|
|
118
|
+
Lmin=0.1, Lmax=0.5,
|
|
119
|
+
ref=1.0,
|
|
120
|
+
lw=1.2,
|
|
121
|
+
fontsize=10,
|
|
122
|
+
text_offset_pts=10,
|
|
123
|
+
head_scale=12.0,
|
|
124
|
+
zorder=5,
|
|
125
|
+
):
|
|
126
|
+
dir_vec = np.array([vx, vy], dtype=float)
|
|
127
|
+
nrm = np.linalg.norm(dir_vec)
|
|
128
|
+
if nrm < 1e-12:
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
dir_vec /= nrm
|
|
132
|
+
L = self._soft_scale(magnitude, ref, Lmin, Lmax, p=self.params["softPower"])
|
|
133
|
+
dx, dy = dir_vec[0] * L, dir_vec[1] * L
|
|
134
|
+
|
|
135
|
+
arrow = FancyArrowPatch(
|
|
136
|
+
(x, y), (x + dx, y + dy),
|
|
137
|
+
arrowstyle="-|>",
|
|
138
|
+
mutation_scale=head_scale,
|
|
139
|
+
linewidth=lw,
|
|
140
|
+
color=color,
|
|
141
|
+
zorder=zorder
|
|
142
|
+
)
|
|
143
|
+
ax.add_patch(arrow)
|
|
144
|
+
|
|
145
|
+
txt_artist = None
|
|
146
|
+
if label:
|
|
147
|
+
perp = np.array([-dir_vec[1], dir_vec[0]])
|
|
148
|
+
perp_sign = 1.0
|
|
149
|
+
if (dir_vec[0] < 0) or (dir_vec[1] < 0):
|
|
150
|
+
perp_sign = -1.0
|
|
151
|
+
|
|
152
|
+
offset = perp_sign * perp * text_offset_pts
|
|
153
|
+
trans = ax.transData + transforms.ScaledTranslation(
|
|
154
|
+
offset[0] / 72.0, offset[1] / 72.0, ax.figure.dpi_scale_trans
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
txt_artist = ax.text(
|
|
158
|
+
x + dx, y + dy, label,
|
|
159
|
+
transform=trans,
|
|
160
|
+
fontsize=fontsize,
|
|
161
|
+
weight="bold",
|
|
162
|
+
va="center", ha="center",
|
|
163
|
+
color=color,
|
|
164
|
+
bbox=dict(facecolor="white", edgecolor="none", alpha=0.80, pad=1.5),
|
|
165
|
+
zorder=zorder + 1
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
return txt_artist
|
|
169
|
+
|
|
170
|
+
def _resolve_text_collisions(self, ax, text_artists, iters=25, min_dist_pts=14):
|
|
171
|
+
text_artists = [t for t in text_artists if t is not None]
|
|
172
|
+
if not text_artists:
|
|
173
|
+
return
|
|
174
|
+
|
|
175
|
+
fig = ax.figure
|
|
176
|
+
fig.canvas.draw()
|
|
177
|
+
renderer = fig.canvas.get_renderer()
|
|
178
|
+
|
|
179
|
+
min_dist_px = (min_dist_pts / 72.0) * fig.dpi
|
|
180
|
+
|
|
181
|
+
for _ in range(int(iters)):
|
|
182
|
+
moved = False
|
|
183
|
+
|
|
184
|
+
centers = []
|
|
185
|
+
for t in text_artists:
|
|
186
|
+
bb = t.get_window_extent(renderer=renderer).expanded(1.05, 1.10)
|
|
187
|
+
centers.append(np.array([bb.x0 + bb.width / 2, bb.y0 + bb.height / 2]))
|
|
188
|
+
|
|
189
|
+
for i in range(len(text_artists)):
|
|
190
|
+
for j in range(i + 1, len(text_artists)):
|
|
191
|
+
d = centers[j] - centers[i]
|
|
192
|
+
dist = float(np.linalg.norm(d))
|
|
193
|
+
if dist < min_dist_px:
|
|
194
|
+
if dist < 1e-6:
|
|
195
|
+
d = np.array([1.0, 0.0])
|
|
196
|
+
dist = 1.0
|
|
197
|
+
push = (min_dist_px - dist) * 0.5
|
|
198
|
+
step = (d / dist) * push
|
|
199
|
+
|
|
200
|
+
for idx, sgn in [(i, -1.0), (j, +1.0)]:
|
|
201
|
+
t = text_artists[idx]
|
|
202
|
+
x, y = t.get_position()
|
|
203
|
+
p_disp = ax.transData.transform((x, y))
|
|
204
|
+
p_disp = p_disp + sgn * step
|
|
205
|
+
p_data = ax.transData.inverted().transform(p_disp)
|
|
206
|
+
t.set_position((p_data[0], p_data[1]))
|
|
207
|
+
|
|
208
|
+
moved = True
|
|
209
|
+
|
|
210
|
+
if not moved:
|
|
211
|
+
break
|
|
212
|
+
|
|
213
|
+
fig.canvas.draw_idle()
|
|
214
|
+
|
|
215
|
+
def _apply_legend(self, ax):
|
|
216
|
+
proxies = [
|
|
217
|
+
Line2D([0], [0], color="blue", lw=2, label="Loads"),
|
|
218
|
+
Line2D([0], [0], color="black", lw=2, label="Reactions (BC)"),
|
|
219
|
+
Line2D([0], [0], color="purple", lw=2, label="Springs"),
|
|
220
|
+
Line2D([0], [0], color="purple", lw=2, linestyle=":", label="Member vectors"),
|
|
221
|
+
Line2D([0], [0], color="#33cc99", lw=2, linestyle="--", label="Undeformed"),
|
|
222
|
+
Line2D([0], [0], color="#1f77b4", lw=3, label="Deformed (tension)"),
|
|
223
|
+
Line2D([0], [0], color="#d62728", lw=3, label="Deformed (compression)"),
|
|
224
|
+
]
|
|
225
|
+
ax.legend(handles=proxies, loc="upper right", framealpha=0.9)
|
|
226
|
+
|
|
227
|
+
# -----------------------
|
|
228
|
+
# Main plot on axis
|
|
229
|
+
# -----------------------
|
|
230
|
+
def _plot_on_axis(self, ax, **kwargs):
|
|
231
|
+
# params
|
|
232
|
+
Axial_Forces = kwargs["Axial_Forces"]
|
|
233
|
+
Show_Loads = kwargs["Show_Loads"]
|
|
234
|
+
Show_Reactions = kwargs["Show_Reactions"]
|
|
235
|
+
Show_Springs = kwargs["Show_Springs"]
|
|
236
|
+
Show_MemberVectors = kwargs["Show_MemberVectors"]
|
|
237
|
+
Show_Legend = kwargs["Show_Legend"]
|
|
238
|
+
Show_NodeNumbers = kwargs["Show_NodeNumbers"]
|
|
239
|
+
Show_ElementNumbers = kwargs["Show_ElementNumbers"]
|
|
240
|
+
|
|
241
|
+
label_offset = kwargs["label_offset"]
|
|
242
|
+
elem_label_offset = kwargs["elem_label_offset"]
|
|
243
|
+
xMargin = kwargs["xMargin"]
|
|
244
|
+
yMargin = kwargs["yMargin"]
|
|
245
|
+
scaleFactor = kwargs["scaleFactor"]
|
|
246
|
+
Load_Increment = int(kwargs["Load_Increment"])
|
|
247
|
+
Final_config = kwargs["Final_config"]
|
|
248
|
+
|
|
249
|
+
textScale = kwargs["textScale"]
|
|
250
|
+
arrowLenMin = kwargs["arrowLenMin"]
|
|
251
|
+
arrowLenMax = kwargs["arrowLenMax"]
|
|
252
|
+
arrowHeadScale = kwargs["arrowHeadScale"]
|
|
253
|
+
labelOffsetPts = kwargs["labelOffsetPts"]
|
|
254
|
+
refQuantile = kwargs["refQuantile"]
|
|
255
|
+
collisionIters = kwargs["collisionIters"]
|
|
256
|
+
collisionRadiusPts = kwargs["collisionRadiusPts"]
|
|
257
|
+
vectorScaleFactor = kwargs["vectorScaleFactor"]
|
|
258
|
+
|
|
259
|
+
springK_tol = kwargs["springK_tol"]
|
|
260
|
+
springF_tol = kwargs["springF_tol"]
|
|
261
|
+
|
|
262
|
+
if Final_config:
|
|
263
|
+
Load_Increment = -1
|
|
264
|
+
|
|
265
|
+
ug = np.asarray(self.UG_FINAL[:, Load_Increment]).flatten()
|
|
266
|
+
fi = np.asarray(self.FI_FINAL[:, Load_Increment]).flatten()
|
|
267
|
+
forceVector = np.asarray(self.EXTFORCES[:, Load_Increment]).flatten()
|
|
268
|
+
mbrForces = np.asarray(self.MBRFORCES[:, Load_Increment]).flatten()
|
|
269
|
+
|
|
270
|
+
ax.clear()
|
|
271
|
+
ax.set_aspect("equal", adjustable="datalim")
|
|
272
|
+
ax.grid(True)
|
|
273
|
+
|
|
274
|
+
# span for relative arrow lengths
|
|
275
|
+
min_x, max_x = self.nodes[:, 0].min(), self.nodes[:, 0].max()
|
|
276
|
+
min_y, max_y = self.nodes[:, 1].min(), self.nodes[:, 1].max()
|
|
277
|
+
span = max(max_x - min_x, max_y - min_y)
|
|
278
|
+
Lmin = max(1e-9, arrowLenMin * span)
|
|
279
|
+
Lmax = max(Lmin * 1.05, arrowLenMax * span)
|
|
280
|
+
|
|
281
|
+
# reference force
|
|
282
|
+
allF = np.concatenate([np.abs(forceVector), np.abs(fi), np.abs(mbrForces)])
|
|
283
|
+
allF = allF[allF > 1e-9]
|
|
284
|
+
ref_force = float(np.quantile(allF, refQuantile)) if allF.size else 1.0
|
|
285
|
+
|
|
286
|
+
# fontsize
|
|
287
|
+
fs = self._auto_fontsize(ax, base=10, textScale=textScale)
|
|
288
|
+
|
|
289
|
+
# --- SPRINGS computed ONCE ---
|
|
290
|
+
u_vec = ug # same as UG_FINAL[:, step]
|
|
291
|
+
if hasattr(self, "Kspring_diag") and self.Kspring_diag is not None:
|
|
292
|
+
k_diag = np.asarray(self.Kspring_diag, dtype=float).flatten()
|
|
293
|
+
f_spring = -k_diag * u_vec
|
|
294
|
+
else:
|
|
295
|
+
k_diag = None
|
|
296
|
+
f_spring = np.zeros_like(u_vec)
|
|
297
|
+
|
|
298
|
+
# DOFs which truly have springs
|
|
299
|
+
if self.spring_dofs is not None:
|
|
300
|
+
spring_dofs = set(int(d) for d in self.spring_dofs)
|
|
301
|
+
elif k_diag is not None:
|
|
302
|
+
spring_dofs = set(np.where(k_diag > springK_tol)[0].tolist())
|
|
303
|
+
else:
|
|
304
|
+
spring_dofs = set()
|
|
305
|
+
|
|
306
|
+
# nodes + node labels
|
|
307
|
+
node_texts = []
|
|
308
|
+
for i, (x, y) in enumerate(self.nodes):
|
|
309
|
+
ax.plot(x, y, "o", color="#33cc99", zorder=3)
|
|
310
|
+
|
|
311
|
+
if Show_NodeNumbers:
|
|
312
|
+
t = ax.text(
|
|
313
|
+
x + label_offset, y + label_offset, str(i + 1),
|
|
314
|
+
fontsize=fs, weight="bold",
|
|
315
|
+
bbox=dict(facecolor="white", edgecolor="none", alpha=0.6, pad=1.0),
|
|
316
|
+
zorder=10
|
|
317
|
+
)
|
|
318
|
+
node_texts.append(t)
|
|
319
|
+
|
|
320
|
+
# undeformed members
|
|
321
|
+
print(self.members)
|
|
322
|
+
for (ni, nj) in self.members:
|
|
323
|
+
ix, iy = self.nodes[ni - 1]
|
|
324
|
+
jx, jy = self.nodes[nj - 1]
|
|
325
|
+
ax.plot([ix, jx], [iy, jy], "--", color="#33cc99", linewidth=1.5, zorder=1)
|
|
326
|
+
|
|
327
|
+
# deformed members (thickness by abs force, color by sign)
|
|
328
|
+
max_force_abs = float(np.max(np.abs(mbrForces))) if np.max(np.abs(mbrForces)) != 0 else 1.0
|
|
329
|
+
member_texts = []
|
|
330
|
+
|
|
331
|
+
for e, (ni, nj) in enumerate(self.members):
|
|
332
|
+
ix, iy = self.nodes[ni - 1]
|
|
333
|
+
jx, jy = self.nodes[nj - 1]
|
|
334
|
+
|
|
335
|
+
ia = 2 * ni - 2
|
|
336
|
+
ib = 2 * ni - 1
|
|
337
|
+
ja = 2 * nj - 2
|
|
338
|
+
jb = 2 * nj - 1
|
|
339
|
+
|
|
340
|
+
ixN = ix + ug[ia] * scaleFactor
|
|
341
|
+
iyN = iy + ug[ib] * scaleFactor
|
|
342
|
+
jxN = jx + ug[ja] * scaleFactor
|
|
343
|
+
jyN = jy + ug[jb] * scaleFactor
|
|
344
|
+
|
|
345
|
+
force_abs = abs(mbrForces[e])
|
|
346
|
+
line_width = 1.0 + 4.0 * (force_abs / max_force_abs)
|
|
347
|
+
|
|
348
|
+
col = "#1f77b4" if mbrForces[e] >= 0 else "#d62728"
|
|
349
|
+
ax.plot([ixN, jxN], [iyN, jyN], "-", linewidth=line_width, color=col, zorder=2)
|
|
350
|
+
|
|
351
|
+
# element numbering (NEW)
|
|
352
|
+
if Show_ElementNumbers:
|
|
353
|
+
mx = 0.5 * (ixN + jxN)
|
|
354
|
+
my = 0.5 * (iyN + jyN)
|
|
355
|
+
t_el = ax.text(
|
|
356
|
+
mx + elem_label_offset, my + elem_label_offset, f"E{e+1}",
|
|
357
|
+
fontsize=max(7, fs - 1),
|
|
358
|
+
weight="bold",
|
|
359
|
+
bbox=dict(facecolor="white", edgecolor="none", alpha=0.65, pad=1.0),
|
|
360
|
+
zorder=12,
|
|
361
|
+
color="purple"
|
|
362
|
+
)
|
|
363
|
+
member_texts.append(t_el)
|
|
364
|
+
|
|
365
|
+
if Axial_Forces:
|
|
366
|
+
preTen = self.P0[e] / 1000.0
|
|
367
|
+
axialForce = mbrForces[e] / 1000.0 - preTen
|
|
368
|
+
label = f"{axialForce:.2f} kN\n(+{preTen:.2f} PT)"
|
|
369
|
+
mx = 0.5 * (ixN + jxN)
|
|
370
|
+
my = 0.5 * (iyN + jyN)
|
|
371
|
+
t = ax.text(
|
|
372
|
+
mx, my, label,
|
|
373
|
+
fontsize=max(7, fs - 1),
|
|
374
|
+
weight="bold",
|
|
375
|
+
bbox=dict(facecolor="white", edgecolor="none", alpha=0.75, pad=1.5),
|
|
376
|
+
zorder=11
|
|
377
|
+
)
|
|
378
|
+
member_texts.append(t)
|
|
379
|
+
|
|
380
|
+
# loads + reactions + springs
|
|
381
|
+
force_texts = []
|
|
382
|
+
nNodes = self.nodes.shape[0]
|
|
383
|
+
|
|
384
|
+
for n in range(nNodes):
|
|
385
|
+
node_num = n + 1
|
|
386
|
+
i_hor = 2 * node_num - 2
|
|
387
|
+
i_ver = 2 * node_num - 1
|
|
388
|
+
|
|
389
|
+
ix, iy = self.nodes[n]
|
|
390
|
+
ixN = ix + ug[i_hor] * scaleFactor
|
|
391
|
+
iyN = iy + ug[i_ver] * scaleFactor
|
|
392
|
+
|
|
393
|
+
# loads
|
|
394
|
+
if Show_Loads:
|
|
395
|
+
if abs(forceVector[i_hor]) > 1e-9:
|
|
396
|
+
t = self._add_force_arrow(
|
|
397
|
+
ax, ixN, iyN,
|
|
398
|
+
np.sign(forceVector[i_hor]), 0.0,
|
|
399
|
+
magnitude=forceVector[i_hor],
|
|
400
|
+
color="blue",
|
|
401
|
+
label=f"{forceVector[i_hor]/1000:.2f} kN",
|
|
402
|
+
Lmin=Lmin, Lmax=Lmax, ref=ref_force,
|
|
403
|
+
lw=1.2, fontsize=fs,
|
|
404
|
+
text_offset_pts=labelOffsetPts,
|
|
405
|
+
head_scale=arrowHeadScale,
|
|
406
|
+
zorder=6
|
|
407
|
+
)
|
|
408
|
+
if t is not None:
|
|
409
|
+
force_texts.append(t)
|
|
410
|
+
|
|
411
|
+
if abs(forceVector[i_ver]) > 1e-9:
|
|
412
|
+
t = self._add_force_arrow(
|
|
413
|
+
ax, ixN, iyN,
|
|
414
|
+
0.0, np.sign(forceVector[i_ver]),
|
|
415
|
+
magnitude=forceVector[i_ver],
|
|
416
|
+
color="blue",
|
|
417
|
+
label=f"{forceVector[i_ver]/1000:.2f} kN",
|
|
418
|
+
Lmin=Lmin, Lmax=Lmax, ref=ref_force,
|
|
419
|
+
lw=1.2, fontsize=fs,
|
|
420
|
+
text_offset_pts=labelOffsetPts,
|
|
421
|
+
head_scale=arrowHeadScale,
|
|
422
|
+
zorder=6
|
|
423
|
+
)
|
|
424
|
+
if t is not None:
|
|
425
|
+
force_texts.append(t)
|
|
426
|
+
|
|
427
|
+
# reactions (BC)
|
|
428
|
+
if Show_Reactions:
|
|
429
|
+
if (i_hor in self.restrainedIndex) and abs(fi[i_hor]) > 1e-9:
|
|
430
|
+
t = self._add_force_arrow(
|
|
431
|
+
ax, ixN, iyN,
|
|
432
|
+
np.sign(fi[i_hor]), 0.0,
|
|
433
|
+
magnitude=fi[i_hor],
|
|
434
|
+
color="black",
|
|
435
|
+
label=f"{fi[i_hor]/1000:.2f} kN",
|
|
436
|
+
Lmin=Lmin, Lmax=Lmax, ref=ref_force,
|
|
437
|
+
lw=1.4, fontsize=fs,
|
|
438
|
+
text_offset_pts=labelOffsetPts,
|
|
439
|
+
head_scale=arrowHeadScale,
|
|
440
|
+
zorder=7
|
|
441
|
+
)
|
|
442
|
+
if t is not None:
|
|
443
|
+
force_texts.append(t)
|
|
444
|
+
|
|
445
|
+
if (i_ver in self.restrainedIndex) and abs(fi[i_ver]) > 1e-9:
|
|
446
|
+
t = self._add_force_arrow(
|
|
447
|
+
ax, ixN, iyN,
|
|
448
|
+
0.0, np.sign(fi[i_ver]),
|
|
449
|
+
magnitude=fi[i_ver],
|
|
450
|
+
color="black",
|
|
451
|
+
label=f"{fi[i_ver]/1000:.2f} kN",
|
|
452
|
+
Lmin=Lmin, Lmax=Lmax, ref=ref_force,
|
|
453
|
+
lw=1.4, fontsize=fs,
|
|
454
|
+
text_offset_pts=labelOffsetPts,
|
|
455
|
+
head_scale=arrowHeadScale,
|
|
456
|
+
zorder=7
|
|
457
|
+
)
|
|
458
|
+
if t is not None:
|
|
459
|
+
force_texts.append(t)
|
|
460
|
+
|
|
461
|
+
# springs (NEW, independent of reactions)
|
|
462
|
+
if Show_Springs and (k_diag is not None):
|
|
463
|
+
# x spring at this node?
|
|
464
|
+
if (i_hor in spring_dofs) and abs(f_spring[i_hor]) > springF_tol:
|
|
465
|
+
t = self._add_force_arrow(
|
|
466
|
+
ax, ixN, iyN,
|
|
467
|
+
np.sign(f_spring[i_hor]), 0.0,
|
|
468
|
+
magnitude=f_spring[i_hor],
|
|
469
|
+
color="purple",
|
|
470
|
+
label=f"{f_spring[i_hor]/1000:.2f} kN",
|
|
471
|
+
Lmin=Lmin, Lmax=Lmax, ref=ref_force,
|
|
472
|
+
lw=1.4, fontsize=fs,
|
|
473
|
+
text_offset_pts=labelOffsetPts,
|
|
474
|
+
head_scale=arrowHeadScale,
|
|
475
|
+
zorder=7
|
|
476
|
+
)
|
|
477
|
+
if t is not None:
|
|
478
|
+
force_texts.append(t)
|
|
479
|
+
|
|
480
|
+
# y spring at this node?
|
|
481
|
+
if (i_ver in spring_dofs) and abs(f_spring[i_ver]) > springF_tol:
|
|
482
|
+
t = self._add_force_arrow(
|
|
483
|
+
ax, ixN, iyN,
|
|
484
|
+
0.0, np.sign(f_spring[i_ver]),
|
|
485
|
+
magnitude=f_spring[i_ver],
|
|
486
|
+
color="purple",
|
|
487
|
+
label=f"{f_spring[i_ver]/1000:.2f} kN",
|
|
488
|
+
Lmin=Lmin, Lmax=Lmax, ref=ref_force,
|
|
489
|
+
lw=1.4, fontsize=fs,
|
|
490
|
+
text_offset_pts=labelOffsetPts,
|
|
491
|
+
head_scale=arrowHeadScale,
|
|
492
|
+
zorder=7
|
|
493
|
+
)
|
|
494
|
+
if t is not None:
|
|
495
|
+
force_texts.append(t)
|
|
496
|
+
|
|
497
|
+
# member vectors (purple, orthogonal)
|
|
498
|
+
if Show_MemberVectors:
|
|
499
|
+
for e, (ni, nj) in enumerate(self.members):
|
|
500
|
+
ix, iy = self.nodes[ni - 1]
|
|
501
|
+
jx, jy = self.nodes[nj - 1]
|
|
502
|
+
|
|
503
|
+
ia = 2 * ni - 2
|
|
504
|
+
ib = 2 * ni - 1
|
|
505
|
+
ja = 2 * nj - 2
|
|
506
|
+
jb = 2 * nj - 1
|
|
507
|
+
|
|
508
|
+
ixN = ix + ug[ia] * scaleFactor
|
|
509
|
+
iyN = iy + ug[ib] * scaleFactor
|
|
510
|
+
jxN = jx + ug[ja] * scaleFactor
|
|
511
|
+
jyN = jy + ug[jb] * scaleFactor
|
|
512
|
+
|
|
513
|
+
direction = np.array([jx - ix, jy - iy], dtype=float)
|
|
514
|
+
L = np.linalg.norm(direction)
|
|
515
|
+
if L < 1e-12:
|
|
516
|
+
continue
|
|
517
|
+
|
|
518
|
+
dir_norm = direction / L
|
|
519
|
+
orth = np.array([-dir_norm[1], dir_norm[0]])
|
|
520
|
+
|
|
521
|
+
vec_L = self._soft_scale(mbrForces[e], ref_force, Lmin * 0.7, Lmax * 0.7, p=self.params["softPower"])
|
|
522
|
+
vec_L *= np.sign(mbrForces[e]) * vectorScaleFactor
|
|
523
|
+
vec = orth * vec_L
|
|
524
|
+
|
|
525
|
+
for (x0, y0) in [(ixN, iyN), (jxN, jyN)]:
|
|
526
|
+
a = FancyArrowPatch(
|
|
527
|
+
(x0, y0), (x0 + vec[0], y0 + vec[1]),
|
|
528
|
+
arrowstyle="-|>",
|
|
529
|
+
mutation_scale=max(8.0, 0.8 * arrowHeadScale),
|
|
530
|
+
linewidth=1.0,
|
|
531
|
+
color="purple",
|
|
532
|
+
alpha=0.75,
|
|
533
|
+
zorder=4
|
|
534
|
+
)
|
|
535
|
+
ax.add_patch(a)
|
|
536
|
+
|
|
537
|
+
ax.plot(
|
|
538
|
+
[ixN + vec[0], jxN + vec[0]],
|
|
539
|
+
[iyN + vec[1], jyN + vec[1]],
|
|
540
|
+
color="purple",
|
|
541
|
+
linestyle=":",
|
|
542
|
+
linewidth=1.0,
|
|
543
|
+
alpha=0.75,
|
|
544
|
+
zorder=3
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
# axis limits / labels
|
|
548
|
+
ax.set_xlim(min_x - xMargin, max_x + xMargin)
|
|
549
|
+
ax.set_ylim(min_y - yMargin, max_y + yMargin)
|
|
550
|
+
ax.set_xlabel("Distance (m)", fontsize=fs)
|
|
551
|
+
ax.set_ylabel("Distance (m)", fontsize=fs)
|
|
552
|
+
ax.set_title("Deflected shape, forces & reactions", fontsize=fs + 2, weight="bold")
|
|
553
|
+
|
|
554
|
+
if Show_Legend:
|
|
555
|
+
self._apply_legend(ax)
|
|
556
|
+
|
|
557
|
+
# collision reduction
|
|
558
|
+
self._resolve_text_collisions(
|
|
559
|
+
ax,
|
|
560
|
+
node_texts + member_texts + force_texts,
|
|
561
|
+
iters=collisionIters,
|
|
562
|
+
min_dist_pts=collisionRadiusPts
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
# -----------------------
|
|
566
|
+
# Interactive window
|
|
567
|
+
# -----------------------
|
|
568
|
+
def create_structure_plot(self):
|
|
569
|
+
fig = plt.figure(figsize=(11, 9))
|
|
570
|
+
ax_main = fig.add_axes([0.25, 0.35, 0.70, 0.60])
|
|
571
|
+
fig.suptitle("Structure Plot (optimized)", fontsize=14)
|
|
572
|
+
|
|
573
|
+
slider_left = 0.25
|
|
574
|
+
slider_width = 0.65
|
|
575
|
+
slider_height = 0.03
|
|
576
|
+
slider_y = 0.28
|
|
577
|
+
|
|
578
|
+
def add_slider(label, vmin, vmax, vinit, vstep):
|
|
579
|
+
nonlocal slider_y
|
|
580
|
+
ax_s = fig.add_axes([slider_left, slider_y, slider_width, slider_height])
|
|
581
|
+
s = Slider(ax=ax_s, label=label, valmin=vmin, valmax=vmax, valinit=vinit, valstep=vstep)
|
|
582
|
+
slider_y -= 0.04
|
|
583
|
+
return s
|
|
584
|
+
|
|
585
|
+
# Sliders
|
|
586
|
+
s_xMargin = add_slider("xMargin", 0.0, 5.0, self.params["xMargin"], 0.1)
|
|
587
|
+
s_yMargin = add_slider("yMargin", 0.0, 5.0, self.params["yMargin"], 0.1)
|
|
588
|
+
s_scaleFactor = add_slider("scaleFactor", 0.0, 10.0, self.params["scaleFactor"], 0.1)
|
|
589
|
+
|
|
590
|
+
s_textScale = add_slider("textScale", 0.6, 1.8, self.params["textScale"], 0.05)
|
|
591
|
+
s_arrowMin = add_slider("arrowLenMin", 0.005, 0.10, self.params["arrowLenMin"], 0.005)
|
|
592
|
+
s_arrowMax = add_slider("arrowLenMax", 0.02, 0.30, self.params["arrowLenMax"], 0.01)
|
|
593
|
+
s_head = add_slider("arrowHeadScale", 6.0, 26.0, self.params["arrowHeadScale"], 1.0)
|
|
594
|
+
s_lblPts = add_slider("labelOffsetPts", 0.0, 25.0, self.params["labelOffsetPts"], 1.0)
|
|
595
|
+
s_vecScale = add_slider("vectorScaleFactor", 0.0, 5.0, self.params["vectorScaleFactor"], 0.1)
|
|
596
|
+
|
|
597
|
+
max_load = self.UG_FINAL.shape[1] - 1
|
|
598
|
+
s_load = add_slider("Load_Increment", 0, max_load, self.params["Load_Increment"], 1)
|
|
599
|
+
|
|
600
|
+
s_collRad = add_slider("collisionRadiusPts", 6, 30, self.params["collisionRadiusPts"], 1)
|
|
601
|
+
s_collIt = add_slider("collisionIters", 0, 60, self.params["collisionIters"], 1)
|
|
602
|
+
|
|
603
|
+
# CheckButtons
|
|
604
|
+
ax_check = fig.add_axes([0.05, 0.38, 0.18, 0.32])
|
|
605
|
+
labels = [
|
|
606
|
+
"Loads", "Reactions", "Springs",
|
|
607
|
+
"MemberVectors", "Axial_Forces",
|
|
608
|
+
"NodeNumbers", "ElementNumbers",
|
|
609
|
+
"Final_config", "Legend"
|
|
610
|
+
]
|
|
611
|
+
actives = [
|
|
612
|
+
self.params["Show_Loads"],
|
|
613
|
+
self.params["Show_Reactions"],
|
|
614
|
+
self.params["Show_Springs"],
|
|
615
|
+
self.params["Show_MemberVectors"],
|
|
616
|
+
self.params["Axial_Forces"],
|
|
617
|
+
self.params["Show_NodeNumbers"],
|
|
618
|
+
self.params["Show_ElementNumbers"],
|
|
619
|
+
self.params["Final_config"],
|
|
620
|
+
self.params["Show_Legend"]
|
|
621
|
+
]
|
|
622
|
+
check = CheckButtons(ax_check, labels=labels, actives=actives)
|
|
623
|
+
|
|
624
|
+
def redraw():
|
|
625
|
+
self._plot_on_axis(ax_main, **self.params)
|
|
626
|
+
fig.canvas.draw_idle()
|
|
627
|
+
|
|
628
|
+
def slider_update(_):
|
|
629
|
+
self.params["xMargin"] = float(s_xMargin.val)
|
|
630
|
+
self.params["yMargin"] = float(s_yMargin.val)
|
|
631
|
+
self.params["scaleFactor"] = float(s_scaleFactor.val)
|
|
632
|
+
|
|
633
|
+
self.params["textScale"] = float(s_textScale.val)
|
|
634
|
+
self.params["arrowLenMin"] = float(s_arrowMin.val)
|
|
635
|
+
self.params["arrowLenMax"] = float(s_arrowMax.val)
|
|
636
|
+
self.params["arrowHeadScale"] = float(s_head.val)
|
|
637
|
+
self.params["labelOffsetPts"] = float(s_lblPts.val)
|
|
638
|
+
self.params["vectorScaleFactor"] = float(s_vecScale.val)
|
|
639
|
+
|
|
640
|
+
self.params["Load_Increment"] = int(s_load.val)
|
|
641
|
+
|
|
642
|
+
self.params["collisionRadiusPts"] = int(s_collRad.val)
|
|
643
|
+
self.params["collisionIters"] = int(s_collIt.val)
|
|
644
|
+
|
|
645
|
+
redraw()
|
|
646
|
+
|
|
647
|
+
def check_update(label):
|
|
648
|
+
if label == "Loads":
|
|
649
|
+
self.params["Show_Loads"] = not self.params["Show_Loads"]
|
|
650
|
+
elif label == "Reactions":
|
|
651
|
+
self.params["Show_Reactions"] = not self.params["Show_Reactions"]
|
|
652
|
+
elif label == "Springs":
|
|
653
|
+
self.params["Show_Springs"] = not self.params["Show_Springs"]
|
|
654
|
+
elif label == "MemberVectors":
|
|
655
|
+
self.params["Show_MemberVectors"] = not self.params["Show_MemberVectors"]
|
|
656
|
+
elif label == "Axial_Forces":
|
|
657
|
+
self.params["Axial_Forces"] = not self.params["Axial_Forces"]
|
|
658
|
+
elif label == "NodeNumbers":
|
|
659
|
+
self.params["Show_NodeNumbers"] = not self.params["Show_NodeNumbers"]
|
|
660
|
+
elif label == "ElementNumbers":
|
|
661
|
+
self.params["Show_ElementNumbers"] = not self.params["Show_ElementNumbers"]
|
|
662
|
+
elif label == "Final_config":
|
|
663
|
+
self.params["Final_config"] = not self.params["Final_config"]
|
|
664
|
+
elif label == "Legend":
|
|
665
|
+
self.params["Show_Legend"] = not self.params["Show_Legend"]
|
|
666
|
+
|
|
667
|
+
redraw()
|
|
668
|
+
|
|
669
|
+
# connect events
|
|
670
|
+
for s in [
|
|
671
|
+
s_xMargin, s_yMargin, s_scaleFactor,
|
|
672
|
+
s_textScale, s_arrowMin, s_arrowMax,
|
|
673
|
+
s_head, s_lblPts, s_vecScale,
|
|
674
|
+
s_load, s_collRad, s_collIt
|
|
675
|
+
]:
|
|
676
|
+
s.on_changed(slider_update)
|
|
677
|
+
|
|
678
|
+
check.on_clicked(check_update)
|
|
679
|
+
|
|
680
|
+
redraw()
|
|
681
|
+
plt.show()
|