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.
Files changed (44) hide show
  1. KIB_LAP/Betonbau/TEST_Rectangular.py +21 -0
  2. KIB_LAP/Betonbau/beam_rectangular.py +4 -0
  3. KIB_LAP/FACHWERKEBEN/Elements.py +209 -0
  4. KIB_LAP/FACHWERKEBEN/InputData.py +118 -0
  5. KIB_LAP/FACHWERKEBEN/Iteration.py +967 -0
  6. KIB_LAP/FACHWERKEBEN/Materials.py +30 -0
  7. KIB_LAP/FACHWERKEBEN/Plotting.py +681 -0
  8. KIB_LAP/FACHWERKEBEN/__init__.py +4 -0
  9. KIB_LAP/FACHWERKEBEN/main.py +27 -0
  10. KIB_LAP/Plattentragwerke/PlateBendingKirchhoff.py +36 -29
  11. KIB_LAP/STABRAUM/InputData.py +13 -2
  12. KIB_LAP/STABRAUM/Output_Data.py +61 -0
  13. KIB_LAP/STABRAUM/Plotting.py +1453 -0
  14. KIB_LAP/STABRAUM/Programm.py +518 -1026
  15. KIB_LAP/STABRAUM/Steifigkeitsmatrix.py +338 -117
  16. KIB_LAP/STABRAUM/main.py +58 -0
  17. KIB_LAP/STABRAUM/results.py +37 -0
  18. KIB_LAP/Scheibe/Assemble_Stiffness.py +246 -0
  19. KIB_LAP/Scheibe/Element_Stiffness.py +362 -0
  20. KIB_LAP/Scheibe/Meshing.py +365 -0
  21. KIB_LAP/Scheibe/Output.py +34 -0
  22. KIB_LAP/Scheibe/Plotting.py +722 -0
  23. KIB_LAP/Scheibe/Shell_Calculation.py +523 -0
  24. KIB_LAP/Scheibe/Testing_Mesh.py +25 -0
  25. KIB_LAP/Scheibe/__init__.py +14 -0
  26. KIB_LAP/Scheibe/main.py +33 -0
  27. KIB_LAP/StabEbenRitz/Biegedrillknicken.py +757 -0
  28. KIB_LAP/StabEbenRitz/Biegedrillknicken_Trigeometry.py +328 -0
  29. KIB_LAP/StabEbenRitz/Querschnittswerte.py +527 -0
  30. KIB_LAP/StabEbenRitz/Stabberechnung_Klasse.py +868 -0
  31. KIB_LAP/plate_bending_cpp.cp313-win_amd64.pyd +0 -0
  32. KIB_LAP/plate_buckling_cpp.cp313-win_amd64.pyd +0 -0
  33. {kib_lap-0.5.dist-info → kib_lap-0.7.7.dist-info}/METADATA +1 -1
  34. {kib_lap-0.5.dist-info → kib_lap-0.7.7.dist-info}/RECORD +37 -19
  35. Examples/Cross_Section_Thin.py +0 -61
  36. KIB_LAP/Betonbau/Bemessung_Zust_II.py +0 -648
  37. KIB_LAP/Betonbau/Iterative_Design.py +0 -723
  38. KIB_LAP/Plattentragwerke/NumInte.cpp +0 -23
  39. KIB_LAP/Plattentragwerke/NumericalIntegration.cpp +0 -23
  40. KIB_LAP/Plattentragwerke/plate_bending_cpp.cp313-win_amd64.pyd +0 -0
  41. KIB_LAP/main.py +0 -2
  42. {Examples → KIB_LAP/StabEbenRitz}/__init__.py +0 -0
  43. {kib_lap-0.5.dist-info → kib_lap-0.7.7.dist-info}/WHEEL +0 -0
  44. {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()
@@ -0,0 +1,4 @@
1
+ from .Iteration import IterationClass
2
+ from .Plotting import PlottingStructure
3
+ from .Iteration import IterationClass
4
+ from .Plotting import PlottingStructure