kib-lap 0.5__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.
- Examples/Cross_Section_Thin.py +61 -0
- Examples/__init__.py +0 -0
- KIB_LAP/Betonbau/Bemessung_Polygon.py +667 -0
- KIB_LAP/Betonbau/Bemessung_Zust_II.py +648 -0
- KIB_LAP/Betonbau/Cross_Section_Kappa.py +925 -0
- KIB_LAP/Betonbau/Druckglied_KGV.py +179 -0
- KIB_LAP/Betonbau/Iterative_Design.py +723 -0
- KIB_LAP/Betonbau/Materialkennwerte_Beton.py +196 -0
- KIB_LAP/Betonbau/Querschnittsbreite.py +194 -0
- KIB_LAP/Betonbau/Querschnittsbreite_Kreis.py +63 -0
- KIB_LAP/Betonbau/__init__.py +2 -0
- KIB_LAP/Betonbau/beam_plate_T.py +921 -0
- KIB_LAP/Betonbau/beam_plate_T_reverse.py +915 -0
- KIB_LAP/Betonbau/beam_rectangular.py +635 -0
- KIB_LAP/Betonbau/beam_sub_section.py +9 -0
- KIB_LAP/Dynamik/Cross_Section_Properties.py +155 -0
- KIB_LAP/Dynamik/Deformation_Method.py +587 -0
- KIB_LAP/Dynamik/Duhamel_SDOF.py +221 -0
- KIB_LAP/Dynamik/FFT.py +87 -0
- KIB_LAP/Dynamik/Kontinuum_Eigenmodes.py +418 -0
- KIB_LAP/Dynamik/Kontinuum_Schwingung.py +757 -0
- KIB_LAP/Dynamik/Pendulum_Spring_Linearized.py +91 -0
- KIB_LAP/Dynamik/Pendulum_Spring_Problem.py +94 -0
- KIB_LAP/Dynamik/__init__.py +0 -0
- KIB_LAP/Examples/Cross_Section_Thin.py +61 -0
- KIB_LAP/Examples/Cross_Section_Thin_2.py +14 -0
- KIB_LAP/Examples/Plattentragwerke.py +39 -0
- KIB_LAP/Examples/Plattentragwerke_2.py +60 -0
- KIB_LAP/Examples/ShearDesign.py +28 -0
- KIB_LAP/Examples/__init__.py +0 -0
- KIB_LAP/Plattenbeulen/Plate_Design.py +276 -0
- KIB_LAP/Plattenbeulen/Ritz_Optimiert.py +658 -0
- KIB_LAP/Plattenbeulen/__init__.py +2 -0
- KIB_LAP/Plattenbeulen/dist/__init__.py +0 -0
- KIB_LAP/Plattenbeulen/plate_buckling.cpp +561 -0
- KIB_LAP/Plattenbeulen/plate_buckling_cpp.cp313-win_amd64.pyd +0 -0
- KIB_LAP/Plattenbeulen/plate_buckling_cpp.cpp +561 -0
- KIB_LAP/Plattenbeulen/setup.py +35 -0
- KIB_LAP/Plattentragwerke/Functions.cpp +326 -0
- KIB_LAP/Plattentragwerke/Functions.h +41 -0
- KIB_LAP/Plattentragwerke/NumInte.cpp +23 -0
- KIB_LAP/Plattentragwerke/NumericalIntegration.cpp +23 -0
- KIB_LAP/Plattentragwerke/PlateBendingKirchhoff.py +843 -0
- KIB_LAP/Plattentragwerke/__init__.py +1 -0
- KIB_LAP/Plattentragwerke/plate_bending.cpp +341 -0
- KIB_LAP/Plattentragwerke/plate_bending_cpp.cp313-win_amd64.pyd +0 -0
- KIB_LAP/Plattentragwerke/setup.py +39 -0
- KIB_LAP/Querschnittswerte/Querschnitt_Duenn.py +526 -0
- KIB_LAP/Querschnittswerte/__init__.py +1 -0
- KIB_LAP/STABRAUM/InputData.py +92 -0
- KIB_LAP/STABRAUM/Programm.py +1403 -0
- KIB_LAP/STABRAUM/Steifigkeitsmatrix.py +275 -0
- KIB_LAP/STABRAUM/__init__.py +3 -0
- KIB_LAP/Stahlbau/__init__.py +0 -0
- KIB_LAP/Verbundbau/Verbundtraeger_Bemessung.py +766 -0
- KIB_LAP/Verbundbau/__init__.py +0 -0
- KIB_LAP/__init__.py +4 -0
- KIB_LAP/main.py +2 -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/METADATA +25 -0
- kib_lap-0.5.dist-info/RECORD +64 -0
- kib_lap-0.5.dist-info/WHEEL +5 -0
- kib_lap-0.5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1403 @@
|
|
|
1
|
+
from Steifigkeitsmatrix import *
|
|
2
|
+
from InputData import Input
|
|
3
|
+
import sympy as sp
|
|
4
|
+
|
|
5
|
+
import matplotlib.pyplot as plt
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from mpl_toolkits.mplot3d import Axes3D # nötig für 3D-Plots
|
|
9
|
+
import matplotlib.patches as mpatches
|
|
10
|
+
from matplotlib.widgets import Slider
|
|
11
|
+
from mpl_toolkits.mplot3d.art3d import Line3DCollection
|
|
12
|
+
|
|
13
|
+
def _set_axes_equal(ax, extra: float = 0.0):
|
|
14
|
+
"""
|
|
15
|
+
Erzwingt identische numerische Achsenlimits.
|
|
16
|
+
optional: 'extra' = zusätzlicher Rand als Prozentsatz (0-1).
|
|
17
|
+
"""
|
|
18
|
+
import numpy as np
|
|
19
|
+
|
|
20
|
+
# aktuelle Grenzen holen
|
|
21
|
+
x_limits = np.array(ax.get_xlim3d())
|
|
22
|
+
y_limits = np.array(ax.get_ylim3d())
|
|
23
|
+
z_limits = np.array(ax.get_zlim3d())
|
|
24
|
+
|
|
25
|
+
# Spannweiten & gemeinsames Maximum
|
|
26
|
+
ranges = np.array([np.ptp(lim) for lim in (x_limits, y_limits, z_limits)])
|
|
27
|
+
max_range = ranges.max()
|
|
28
|
+
|
|
29
|
+
# sind alle Punkte (fast) in einer Ebene? -> Mindestspanne ansetzen
|
|
30
|
+
if max_range == 0:
|
|
31
|
+
max_range = 1.0 # beliebiger Würfel von 1 m
|
|
32
|
+
|
|
33
|
+
# Mittelpunktkoordinaten
|
|
34
|
+
mids = np.array([lim.mean() for lim in (x_limits, y_limits, z_limits)])
|
|
35
|
+
|
|
36
|
+
half = (1 + extra) * max_range / 2
|
|
37
|
+
ax.set_xlim3d(mids[0] - half, mids[0] + half)
|
|
38
|
+
ax.set_ylim3d(mids[1] - half, mids[1] + half)
|
|
39
|
+
ax.set_zlim3d(mids[2] - half, mids[2] + half)
|
|
40
|
+
|
|
41
|
+
# Darstellungswürfel in aktuellen MPL-Versionen
|
|
42
|
+
try:
|
|
43
|
+
ax.set_box_aspect((1, 1, 1))
|
|
44
|
+
except AttributeError:
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class mainloop:
|
|
49
|
+
def __init__(self):
|
|
50
|
+
##________ Subclasses __________##
|
|
51
|
+
self.Inp = Input()
|
|
52
|
+
self.ElemStem = ElemStema()
|
|
53
|
+
##_____ Class variables ___________##
|
|
54
|
+
self.K_el_i_store = np.zeros((len(self.Inp.members), 14, 14))
|
|
55
|
+
|
|
56
|
+
self.MY_el_i_store = np.zeros((len(self.Inp.members), 2, 1))
|
|
57
|
+
self.VZ_el_i_store = np.zeros((len(self.Inp.members), 2, 1))
|
|
58
|
+
|
|
59
|
+
self.MZ_el_i_store = np.zeros((len(self.Inp.members), 2, 1))
|
|
60
|
+
self.VY_el_i_store = np.zeros((len(self.Inp.members), 2, 1))
|
|
61
|
+
self.MX_el_i_store = np.zeros((len(self.Inp.members), 2, 1))
|
|
62
|
+
|
|
63
|
+
self.MTP_el_i_store = np.zeros((len(self.Inp.members), 2, 1))
|
|
64
|
+
self.MTS_el_i_store = np.zeros((len(self.Inp.members), 2, 1))
|
|
65
|
+
self.N_el_i_store = np.zeros((len(self.Inp.members), 2, 1))
|
|
66
|
+
|
|
67
|
+
##_____ Class Functions____________##
|
|
68
|
+
self.MainConvergence()
|
|
69
|
+
|
|
70
|
+
def CalculateTransMat(self):
|
|
71
|
+
print("Calculate Transmatrices")
|
|
72
|
+
|
|
73
|
+
TransformationMatrices = np.zeros((len(self.Inp.members), 14, 14))
|
|
74
|
+
na_memb = self.Inp.members["na"]
|
|
75
|
+
ne_memb = self.Inp.members["ne"]
|
|
76
|
+
for i in range(len(self.Inp.members["na"])):
|
|
77
|
+
|
|
78
|
+
node_i = na_memb[i] # Node number for node i of this member
|
|
79
|
+
node_j = ne_memb[i] # Node number for node j of this member
|
|
80
|
+
|
|
81
|
+
# Index of DoF for this member
|
|
82
|
+
ia = 2 * node_i - 2 # horizontal DoF at node i of this member
|
|
83
|
+
ib = 2 * node_i - 1 # vertical DoF at node i of this member
|
|
84
|
+
ja = 2 * node_j - 2 # horizontal DoF at node j of this member
|
|
85
|
+
jb = 2 * node_j - 1 # vertical DoF at node j of this member
|
|
86
|
+
|
|
87
|
+
# New positions = initial pos + cum deflection
|
|
88
|
+
ix = self.Inp.nodes["x[m]"][node_i - 1]
|
|
89
|
+
iy = self.Inp.nodes["y[m]"][node_i - 1]
|
|
90
|
+
iz = self.Inp.nodes["z[m]"][node_i - 1]
|
|
91
|
+
|
|
92
|
+
jx = self.Inp.nodes["x[m]"][node_j - 1]
|
|
93
|
+
jy = self.Inp.nodes["y[m]"][node_j - 1]
|
|
94
|
+
jz = self.Inp.nodes["z[m]"][node_j - 1]
|
|
95
|
+
|
|
96
|
+
TM = self.ElemStem.TransformationMatrix([ix, iy, iz], [jx, jy, jz])
|
|
97
|
+
|
|
98
|
+
TransformationMatrices[i, :, :] = TM
|
|
99
|
+
print("Transmat")
|
|
100
|
+
print(TransformationMatrices[0])
|
|
101
|
+
return TransformationMatrices
|
|
102
|
+
|
|
103
|
+
def BuildStructureStiffnessMatrix(self):
|
|
104
|
+
"""
|
|
105
|
+
Standard construction of Primary and Structure stiffness matrix
|
|
106
|
+
Construction of non-linear element stiffness matrix handled in a child function
|
|
107
|
+
"""
|
|
108
|
+
Kp = np.zeros(
|
|
109
|
+
[self.Inp.nDoF, self.Inp.nDoF]
|
|
110
|
+
) # Initialise the primary stiffness matrix
|
|
111
|
+
self.member_length = []
|
|
112
|
+
|
|
113
|
+
na_memb = self.Inp.members["na"]
|
|
114
|
+
ne_memb = self.Inp.members["ne"]
|
|
115
|
+
crosssec_members = self.Inp.members["cs"]
|
|
116
|
+
|
|
117
|
+
for i in range(0, len(self.Inp.members["na"]), 1):
|
|
118
|
+
node_i = na_memb[i] # Node number for node i of this member
|
|
119
|
+
node_j = ne_memb[i] # Node number for node j of this member
|
|
120
|
+
|
|
121
|
+
# New positions = initial pos + cum deflection
|
|
122
|
+
ix = self.Inp.nodes["x[m]"][node_i - 1]
|
|
123
|
+
iy = self.Inp.nodes["y[m]"][node_i - 1]
|
|
124
|
+
iz = self.Inp.nodes["z[m]"][node_i - 1]
|
|
125
|
+
|
|
126
|
+
jx = self.Inp.nodes["x[m]"][node_j - 1]
|
|
127
|
+
jy = self.Inp.nodes["y[m]"][node_j - 1]
|
|
128
|
+
jz = self.Inp.nodes["z[m]"][node_j - 1]
|
|
129
|
+
|
|
130
|
+
dx = abs(ix - jx)
|
|
131
|
+
dy = abs(iy - jy)
|
|
132
|
+
dz = abs(iz - jz)
|
|
133
|
+
|
|
134
|
+
length = np.sqrt(dx**2 + dy**2 + dz**2)
|
|
135
|
+
self.member_length.append(length)
|
|
136
|
+
|
|
137
|
+
num_cs = crosssec_members[i]
|
|
138
|
+
mat_num_i = self.Inp.CrossSection.loc[
|
|
139
|
+
self.Inp.CrossSection["No"] == num_cs, "material"
|
|
140
|
+
].iloc[0]
|
|
141
|
+
|
|
142
|
+
I_y_i = self.Inp.CrossSection.loc[
|
|
143
|
+
self.Inp.CrossSection["No"] == num_cs, "Iy"
|
|
144
|
+
].iloc[0]
|
|
145
|
+
I_z_i = self.Inp.CrossSection.loc[
|
|
146
|
+
self.Inp.CrossSection["No"] == num_cs, "Iz"
|
|
147
|
+
].iloc[0]
|
|
148
|
+
A_i = self.Inp.CrossSection.loc[
|
|
149
|
+
self.Inp.CrossSection["No"] == num_cs, "A"
|
|
150
|
+
].iloc[0]
|
|
151
|
+
I_w_i = self.Inp.CrossSection.loc[
|
|
152
|
+
self.Inp.CrossSection["No"] == num_cs, "Iw"
|
|
153
|
+
].iloc[0]
|
|
154
|
+
I_T_i = self.Inp.CrossSection.loc[
|
|
155
|
+
self.Inp.CrossSection["No"] == num_cs, "It"
|
|
156
|
+
].iloc[0]
|
|
157
|
+
|
|
158
|
+
print("Material NUmber")
|
|
159
|
+
print(mat_num_i)
|
|
160
|
+
|
|
161
|
+
E_i = self.Inp.Material.loc[self.Inp.Material["No"] == mat_num_i, "E"].iloc[
|
|
162
|
+
0
|
|
163
|
+
]
|
|
164
|
+
G_i = self.Inp.Material.loc[self.Inp.Material["No"] == mat_num_i, "G"].iloc[
|
|
165
|
+
0
|
|
166
|
+
]
|
|
167
|
+
print("Material E_i")
|
|
168
|
+
print(E_i)
|
|
169
|
+
|
|
170
|
+
K_el_i = self.ElemStem.insert_elements(
|
|
171
|
+
S=0,
|
|
172
|
+
E=E_i,
|
|
173
|
+
G=G_i,
|
|
174
|
+
A=A_i,
|
|
175
|
+
I_y=I_y_i,
|
|
176
|
+
I_z=I_z_i,
|
|
177
|
+
I_omega=I_w_i,
|
|
178
|
+
I_T=I_T_i,
|
|
179
|
+
cv=0,
|
|
180
|
+
z1=0,
|
|
181
|
+
cw=0,
|
|
182
|
+
z2=0,
|
|
183
|
+
c_thet=0,
|
|
184
|
+
l=length,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
K_el_i = np.matmul(
|
|
188
|
+
self.TransMats[i].T, np.matmul(K_el_i, self.TransMats[i])
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
self.K_el_i_store[i] = K_el_i
|
|
192
|
+
|
|
193
|
+
K_11 = K_el_i[0:7, 0:7]
|
|
194
|
+
K_12 = K_el_i[0:7, 7:14]
|
|
195
|
+
K_21 = K_el_i[7:14, 0:7]
|
|
196
|
+
K_22 = K_el_i[7:14, 7:14]
|
|
197
|
+
|
|
198
|
+
Kp[
|
|
199
|
+
7 * (node_i - 1) : 7 * (node_i - 1) + 7,
|
|
200
|
+
7 * (node_i - 1) : 7 * (node_i - 1) + 7,
|
|
201
|
+
] += K_11
|
|
202
|
+
|
|
203
|
+
Kp[
|
|
204
|
+
7 * (node_i - 1) : 7 * (node_i - 1) + 7,
|
|
205
|
+
7 * (node_j - 1) : 7 * (node_j - 1) + 7,
|
|
206
|
+
] += K_12
|
|
207
|
+
|
|
208
|
+
Kp[
|
|
209
|
+
7 * (node_j - 1) : 7 * (node_j - 1) + 7,
|
|
210
|
+
7 * (node_i - 1) : 7 * (node_i - 1) + 7,
|
|
211
|
+
] += K_21
|
|
212
|
+
|
|
213
|
+
Kp[
|
|
214
|
+
7 * (node_j - 1) : 7 * (node_j - 1) + 7,
|
|
215
|
+
7 * (node_j - 1) : 7 * (node_j - 1) + 7,
|
|
216
|
+
] += K_22
|
|
217
|
+
|
|
218
|
+
return Kp
|
|
219
|
+
|
|
220
|
+
def RestraintData(self):
|
|
221
|
+
"""
|
|
222
|
+
This functions implements the restraint data, which is loaded from the \n
|
|
223
|
+
input file. \n
|
|
224
|
+
There are 7 DOF's per node. \n
|
|
225
|
+
Therefore the restrained DOF in the global stiffness matrix can be expressed by: \n
|
|
226
|
+
GDOF = 7 * (node-1) + DOF \n
|
|
227
|
+
"""
|
|
228
|
+
res_nodes = self.Inp.RestraintData["Node"]
|
|
229
|
+
res_dof = self.Inp.RestraintData["Dof"]
|
|
230
|
+
res_stif = self.Inp.RestraintData["Cp[MN/m]/[MNm/m]"]
|
|
231
|
+
|
|
232
|
+
for i in range(len(res_dof)):
|
|
233
|
+
glob_dof = 7 * (res_nodes[i] - 1) + res_dof[i]
|
|
234
|
+
print("restrain ", glob_dof)
|
|
235
|
+
self.GesMat[glob_dof, glob_dof] += res_stif[i]
|
|
236
|
+
|
|
237
|
+
def LocalLoadVectorLine(self):
|
|
238
|
+
"""
|
|
239
|
+
This function calculates the local loadvectors for each element \n
|
|
240
|
+
and transforms them into the global coordinate system. \n
|
|
241
|
+
Each local force vector is stored in a separate local force vector, \n
|
|
242
|
+
which is taken into account, when the inner forces are calculated afterwards \n
|
|
243
|
+
Members of the local load vector are: \n
|
|
244
|
+
- Temperature loading \n
|
|
245
|
+
- Member forces \n
|
|
246
|
+
"""
|
|
247
|
+
self.S_loc_elem_line = np.zeros((14, len(self.Inp.members)))
|
|
248
|
+
S_glob_elem = np.zeros((14, len(self.Inp.members)))
|
|
249
|
+
|
|
250
|
+
# Element Loading (line loads, single loads)
|
|
251
|
+
|
|
252
|
+
res_mbr = self.Inp.ElementLoads["Member"]
|
|
253
|
+
res_line_a = self.Inp.ElementLoads["qza"]
|
|
254
|
+
res_line_b = self.Inp.ElementLoads["qze"]
|
|
255
|
+
|
|
256
|
+
for i in range(0, len(res_mbr)):
|
|
257
|
+
j = int(res_mbr[i] - 1)
|
|
258
|
+
print("JJJ")
|
|
259
|
+
print(j)
|
|
260
|
+
print(res_mbr)
|
|
261
|
+
TransMatj = self.TransMats[j]
|
|
262
|
+
|
|
263
|
+
self.S_loc_elem_line[3, j] = (
|
|
264
|
+
+res_line_a[i] * self.member_length[j] / 2
|
|
265
|
+
) # VZ
|
|
266
|
+
self.S_loc_elem_line[10, j] = +res_line_a[i] * self.member_length[j] / 2
|
|
267
|
+
|
|
268
|
+
self.S_loc_elem_line[4, j] = (
|
|
269
|
+
-res_line_a[i] * self.member_length[j] ** 2 / 12
|
|
270
|
+
) # MY
|
|
271
|
+
self.S_loc_elem_line[11, j] = (
|
|
272
|
+
+res_line_a[i] * self.member_length[j] ** 2 / 12
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
S_loc = self.S_loc_elem_line[:, j]
|
|
276
|
+
|
|
277
|
+
# global line loadings
|
|
278
|
+
|
|
279
|
+
S_glob_elem[:, j] = np.matmul(np.transpose(TransMatj), S_loc)
|
|
280
|
+
|
|
281
|
+
return S_glob_elem
|
|
282
|
+
|
|
283
|
+
def LocalLoadVectorTemp(self):
|
|
284
|
+
"""
|
|
285
|
+
This function calculates the local loadvectors for each element \n
|
|
286
|
+
and transforms them into the global coordinate system. \n
|
|
287
|
+
Each local force vector is stored in a separate local force vector, \n
|
|
288
|
+
which is taken into account, when the inner forces are calculated afterwards \n
|
|
289
|
+
Members of the local load vector are: \n
|
|
290
|
+
- Temperature loading \n
|
|
291
|
+
- Member forces \n
|
|
292
|
+
"""
|
|
293
|
+
self.S_loc_elem_temp = np.zeros((14, len(self.Inp.members)))
|
|
294
|
+
S_glob_elem = np.zeros((14, len(self.Inp.members)))
|
|
295
|
+
|
|
296
|
+
# Temperature Loading
|
|
297
|
+
|
|
298
|
+
res_mbr = self.Inp.TemperatureForces["Member"]
|
|
299
|
+
|
|
300
|
+
res_tem_dT = self.Inp.TemperatureForces["dT[K]"]
|
|
301
|
+
res_tem_dTz = self.Inp.TemperatureForces["dTz[K]"]
|
|
302
|
+
res_tem_dTy = self.Inp.TemperatureForces["dTy[K]"]
|
|
303
|
+
|
|
304
|
+
for i in range(0, len(res_mbr)):
|
|
305
|
+
j = int(res_mbr[i] - 1)
|
|
306
|
+
TransMatj = self.TransMats[j]
|
|
307
|
+
# Local temperature loadings
|
|
308
|
+
self.S_loc_elem_temp[0, j] = (
|
|
309
|
+
-self.ElemStem.E * self.ElemStem.A * 1e-5 * res_tem_dT[i]
|
|
310
|
+
) # N
|
|
311
|
+
self.S_loc_elem_temp[7, j] = (
|
|
312
|
+
+self.ElemStem.E * self.ElemStem.A * 1e-5 * res_tem_dT[i]
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
self.S_loc_elem_temp[2, j] = (
|
|
316
|
+
-self.ElemStem.E * self.ElemStem.I_z * 1e-5 * res_tem_dTy[i]
|
|
317
|
+
) # MZ
|
|
318
|
+
self.S_loc_elem_temp[9, j] = (
|
|
319
|
+
+self.ElemStem.E * self.ElemStem.I_z * 1e-5 * res_tem_dTy[i]
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
self.S_loc_elem_temp[4, j] = (
|
|
323
|
+
-self.ElemStem.E * self.ElemStem.I_y * 1e-5 * res_tem_dTz[i]
|
|
324
|
+
) # MY
|
|
325
|
+
self.S_loc_elem_temp[11, j] = (
|
|
326
|
+
+self.ElemStem.E * self.ElemStem.I_y * 1e-5 * res_tem_dTz[i]
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
S_loc = self.S_loc_elem_temp[:, j]
|
|
330
|
+
|
|
331
|
+
# global temperature loadings
|
|
332
|
+
|
|
333
|
+
S_glob_elem[:, j] = np.matmul(np.transpose(TransMatj), S_loc)
|
|
334
|
+
|
|
335
|
+
return S_glob_elem
|
|
336
|
+
|
|
337
|
+
def GlobalLoadVector(self):
|
|
338
|
+
F_loc_temp = self.LocalLoadVectorTemp()
|
|
339
|
+
F_loc_line = self.LocalLoadVectorLine()
|
|
340
|
+
|
|
341
|
+
print("Local element temp")
|
|
342
|
+
print(F_loc_temp)
|
|
343
|
+
|
|
344
|
+
print("Local element line")
|
|
345
|
+
print(F_loc_line)
|
|
346
|
+
|
|
347
|
+
F_glob = np.zeros(self.Inp.nDoF)
|
|
348
|
+
|
|
349
|
+
na_memb = self.Inp.members["na"]
|
|
350
|
+
ne_memb = self.Inp.members["ne"]
|
|
351
|
+
|
|
352
|
+
# Input of Member Forces
|
|
353
|
+
for i in range(0, len(self.Inp.members)):
|
|
354
|
+
node_i = na_memb[i]
|
|
355
|
+
node_j = ne_memb[i]
|
|
356
|
+
|
|
357
|
+
dof_Na = 7 * (node_i - 1)
|
|
358
|
+
dof_Ne = 7 * (node_j - 1)
|
|
359
|
+
|
|
360
|
+
dof_Vya = 7 * (node_i - 1) + 1
|
|
361
|
+
dof_Vye = 7 * (node_j - 1) + 1
|
|
362
|
+
|
|
363
|
+
dof_Vza = 7 * (node_i - 1) + 3
|
|
364
|
+
dof_Vze = 7 * (node_j - 1) + 3
|
|
365
|
+
|
|
366
|
+
dof_Mza = 7 * (node_i - 1) + 2
|
|
367
|
+
dof_Mze = 7 * (node_j - 1) + 2
|
|
368
|
+
|
|
369
|
+
dof_Mya = 7 * (node_i - 1) + 2
|
|
370
|
+
dof_Mye = 7 * (node_j - 1) + 2
|
|
371
|
+
|
|
372
|
+
F_glob[dof_Na] += F_loc_temp[0, i] + F_loc_line[0, i]
|
|
373
|
+
F_glob[dof_Ne] += F_loc_temp[7, i] + F_loc_line[7, i]
|
|
374
|
+
|
|
375
|
+
F_glob[dof_Vya] += F_loc_temp[1, i]
|
|
376
|
+
F_glob[dof_Vye] += F_loc_temp[8, i]
|
|
377
|
+
|
|
378
|
+
F_glob[dof_Vza] += F_loc_temp[3, i] + F_loc_line[3, i]
|
|
379
|
+
F_glob[dof_Vze] += F_loc_temp[10, i] + F_loc_line[10, i]
|
|
380
|
+
|
|
381
|
+
F_glob[dof_Mza] += F_loc_temp[2, i]
|
|
382
|
+
F_glob[dof_Mze] += F_loc_temp[9, i]
|
|
383
|
+
|
|
384
|
+
F_glob[dof_Mya] += F_loc_temp[4, i] + F_loc_line[4, i]
|
|
385
|
+
F_glob[dof_Mye] += F_loc_temp[11, i] + F_loc_line[11, i]
|
|
386
|
+
|
|
387
|
+
# Input of Nodal Forces
|
|
388
|
+
res_nodes = self.Inp.NodalForces["Node"]
|
|
389
|
+
res_dof = self.Inp.NodalForces["Dof"]
|
|
390
|
+
res_forc = self.Inp.NodalForces["Value[MN/MNm]"]
|
|
391
|
+
|
|
392
|
+
for i in range(0, len(res_nodes)):
|
|
393
|
+
node_i = res_nodes[i]
|
|
394
|
+
glob_index = (node_i - 1) * 7
|
|
395
|
+
|
|
396
|
+
if res_dof[i] == "Fx" or res_dof[i] == "fx" or res_dof[i] == "FX":
|
|
397
|
+
glob_index += 0
|
|
398
|
+
elif res_dof[i] == "Fy" or res_dof[i] == "fy" or res_dof[i] == "FY":
|
|
399
|
+
glob_index += 1
|
|
400
|
+
elif res_dof[i] == "Fz" or res_dof[i] == "fz" or res_dof[i] == "FZ":
|
|
401
|
+
glob_index += 3
|
|
402
|
+
|
|
403
|
+
F_glob[glob_index] += res_forc[i]
|
|
404
|
+
|
|
405
|
+
print("Force vector ", F_glob)
|
|
406
|
+
print("Local force vectors ", self.S_loc_elem_temp)
|
|
407
|
+
|
|
408
|
+
return F_glob
|
|
409
|
+
|
|
410
|
+
def SolveDisplacement(self):
|
|
411
|
+
u_glob = np.linalg.solve(self.GesMat, self.FGes)
|
|
412
|
+
return u_glob
|
|
413
|
+
|
|
414
|
+
def StoreLocalDisplacements(self):
|
|
415
|
+
u_el = np.zeros(
|
|
416
|
+
[14, len(self.Inp.members["na"])]
|
|
417
|
+
) # Initialise the primary stiffness matrix
|
|
418
|
+
|
|
419
|
+
na_memb = self.Inp.members["na"]
|
|
420
|
+
ne_memb = self.Inp.members["ne"]
|
|
421
|
+
|
|
422
|
+
for i in range(0, len(self.Inp.members["na"]), 1):
|
|
423
|
+
numa = 7 * (na_memb[i] - 1)
|
|
424
|
+
nume = 7 * (ne_memb[i] - 1)
|
|
425
|
+
|
|
426
|
+
u_el[0:7, i] = self.u_ges[numa : numa + 7]
|
|
427
|
+
u_el[7:14, i] = self.u_ges[nume : nume + 7]
|
|
428
|
+
|
|
429
|
+
# u_el[:,i] = np.matmul(self.TransMats[i],u_el[:,i])
|
|
430
|
+
|
|
431
|
+
return u_el
|
|
432
|
+
|
|
433
|
+
def CalculateLocalInnerForces(self):
|
|
434
|
+
s_el = np.zeros([14, len(self.Inp.members["na"])])
|
|
435
|
+
|
|
436
|
+
for i in range(0, len(self.Inp.members["na"]), 1):
|
|
437
|
+
s_el[:, i] = np.matmul(self.K_el_i_store[i], self.u_el[:, i])
|
|
438
|
+
self.MZ_el_i_store[i] = np.array([s_el[2, i] * (-1), s_el[9, i]]).reshape(
|
|
439
|
+
2, 1
|
|
440
|
+
) # Left is *(-1)
|
|
441
|
+
self.MY_el_i_store[i] = np.array([s_el[4, i] * (-1), s_el[11, i]]).reshape(
|
|
442
|
+
2, 1
|
|
443
|
+
) # Left is *(-1)
|
|
444
|
+
self.MX_el_i_store[i] = np.array([s_el[5, i] * (-1), s_el[12, i]]).reshape(
|
|
445
|
+
2, 1
|
|
446
|
+
) # Left is *(-1)
|
|
447
|
+
|
|
448
|
+
self.N_el_i_store[i] = np.array([s_el[0, i] * (-1), s_el[7, i]]).reshape(
|
|
449
|
+
2, 1
|
|
450
|
+
) # Left is *(-1)
|
|
451
|
+
self.VY_el_i_store[i] = np.array([s_el[1, i] * (-1), s_el[8, i]]).reshape(
|
|
452
|
+
2, 1
|
|
453
|
+
) # Left is *(-1)
|
|
454
|
+
self.VZ_el_i_store[i] = np.array([s_el[3, i] * (-1), s_el[10, i]]).reshape(
|
|
455
|
+
2, 1
|
|
456
|
+
) # Left is *(-1)
|
|
457
|
+
|
|
458
|
+
# Explicit transformation in local coordinates
|
|
459
|
+
|
|
460
|
+
self.N_el_i_store[i] = (
|
|
461
|
+
self.N_el_i_store[i] * self.TransMats[i][0, 0]
|
|
462
|
+
+ self.VY_el_i_store[i] * self.TransMats[i][0, 1]
|
|
463
|
+
+ self.VZ_el_i_store[i] * self.TransMats[i][0, 3]
|
|
464
|
+
- np.array(
|
|
465
|
+
[-self.S_loc_elem_temp[0, i], self.S_loc_elem_temp[7, i]]
|
|
466
|
+
).reshape(
|
|
467
|
+
2, 1
|
|
468
|
+
) # Left is * (-1)
|
|
469
|
+
# - np.array([-self.S_loc_elem_line[0, i], self.S_loc_elem_line[7, i]]).reshape(2, 1) # Left is * (-1)
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
self.MY_el_i_store[i] = (
|
|
473
|
+
self.MX_el_i_store[i] * self.TransMats[i][4, 5]
|
|
474
|
+
+ self.MY_el_i_store[i] * self.TransMats[i][4, 4]
|
|
475
|
+
+ self.MZ_el_i_store[i] * self.TransMats[i][4, 2]
|
|
476
|
+
- np.array(
|
|
477
|
+
[-self.S_loc_elem_temp[4, i], self.S_loc_elem_temp[11, i]]
|
|
478
|
+
).reshape(
|
|
479
|
+
2, 1
|
|
480
|
+
) # Left is * (-1)
|
|
481
|
+
# - np.array([-self.S_loc_elem_line[4, i], self.S_loc_elem_line[11, i]]).reshape(2, 1) # Left is * (-1)
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
return s_el
|
|
485
|
+
|
|
486
|
+
def MainConvergence(self):
|
|
487
|
+
|
|
488
|
+
self.TransMats = self.CalculateTransMat()
|
|
489
|
+
self.GesMat = self.BuildStructureStiffnessMatrix()
|
|
490
|
+
|
|
491
|
+
self.RestraintData()
|
|
492
|
+
|
|
493
|
+
self.FGes = self.GlobalLoadVector()
|
|
494
|
+
|
|
495
|
+
self.u_ges = self.SolveDisplacement()
|
|
496
|
+
|
|
497
|
+
self.u_el = self.StoreLocalDisplacements()
|
|
498
|
+
|
|
499
|
+
self.s_el = self.CalculateLocalInnerForces()
|
|
500
|
+
|
|
501
|
+
def plot_structure_3d(self, nodes, na_memb, ne_memb):
|
|
502
|
+
"""
|
|
503
|
+
Plot the undeformed 3D structure.
|
|
504
|
+
|
|
505
|
+
Parameters:
|
|
506
|
+
nodes: dict with keys ['x[m]', 'y[m]', 'z[m]']
|
|
507
|
+
na_memb, ne_memb: lists with node IDs for start and end of members
|
|
508
|
+
"""
|
|
509
|
+
fig = plt.figure()
|
|
510
|
+
ax = fig.add_subplot(projection="3d")
|
|
511
|
+
|
|
512
|
+
# Plot all members
|
|
513
|
+
for i in range(len(na_memb)):
|
|
514
|
+
node_i = na_memb[i]
|
|
515
|
+
node_j = ne_memb[i]
|
|
516
|
+
|
|
517
|
+
ix = nodes["x[m]"][node_i - 1]
|
|
518
|
+
iy = nodes["y[m]"][node_i - 1]
|
|
519
|
+
iz = nodes["z[m]"][node_i - 1]
|
|
520
|
+
|
|
521
|
+
jx = nodes["x[m]"][node_j - 1]
|
|
522
|
+
jy = nodes["y[m]"][node_j - 1]
|
|
523
|
+
jz = nodes["z[m]"][node_j - 1]
|
|
524
|
+
|
|
525
|
+
X = [ix, jx]
|
|
526
|
+
Y = [iy, jy]
|
|
527
|
+
Z = [iz, jz]
|
|
528
|
+
|
|
529
|
+
ax.plot(X, Y, Z, color="black", lw=1.0)
|
|
530
|
+
|
|
531
|
+
# Achsenbeschriftung und Titel
|
|
532
|
+
ax.set_xlabel("X [m]")
|
|
533
|
+
ax.set_ylabel("Y [m]")
|
|
534
|
+
ax.set_zlabel("Z [m]")
|
|
535
|
+
ax.set_title("Unverformte Struktur")
|
|
536
|
+
|
|
537
|
+
return fig, ax
|
|
538
|
+
|
|
539
|
+
def calculate_orthogonal_unit_vector_cross_product(self, xi, zi, xj, zj):
|
|
540
|
+
"""
|
|
541
|
+
Berechnet einen normierten orthogonalen Vektor zur Strukturlinie von (xi, zi) nach (xj, zj)
|
|
542
|
+
mittels Kreuzprodukt.
|
|
543
|
+
|
|
544
|
+
Parameter:
|
|
545
|
+
- xi, zi: Koordinaten des Startknotens
|
|
546
|
+
- xj, zj: Koordinaten des Endknotens
|
|
547
|
+
|
|
548
|
+
Rückgabe:
|
|
549
|
+
- unit_vector_pos: NumPy-Array des normierten orthogonalen Vektors für positives My
|
|
550
|
+
- unit_vector_neg: NumPy-Array des normierten orthogonalen Vektors für negatives My
|
|
551
|
+
"""
|
|
552
|
+
# Richtungsvektor in 3D (y-Komponente ist 0)
|
|
553
|
+
v = np.array([xj - xi, 0, zj - zi])
|
|
554
|
+
|
|
555
|
+
# Einheitsvektor entlang der y-Achse
|
|
556
|
+
y_unit = np.array([0, 1, 0])
|
|
557
|
+
|
|
558
|
+
# Kreuzprodukt berechnen
|
|
559
|
+
perp_vector = np.cross(v, y_unit) # Ergebnis ist ebenfalls ein 3D-Vektor
|
|
560
|
+
|
|
561
|
+
# Extrahiere die x und z Komponenten
|
|
562
|
+
perp_vector_2d = perp_vector[[0, 2]]
|
|
563
|
+
|
|
564
|
+
# Normierung
|
|
565
|
+
norm = np.linalg.norm(perp_vector_2d)
|
|
566
|
+
if norm == 0:
|
|
567
|
+
raise ValueError(
|
|
568
|
+
"Die Strukturlinie hat keine Länge. Start- und Endknoten sind identisch."
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
unit_vector = perp_vector_2d / norm
|
|
572
|
+
|
|
573
|
+
# Für positives und negatives My
|
|
574
|
+
unit_vector_pos = unit_vector
|
|
575
|
+
unit_vector_neg = -unit_vector
|
|
576
|
+
|
|
577
|
+
return unit_vector_pos, unit_vector_neg
|
|
578
|
+
|
|
579
|
+
def plot_moment_my_2d(self, ax, xi, zi, my_local, unit_vector, scale=1):
|
|
580
|
+
"""
|
|
581
|
+
Plot eines Biegemoments My in 2D (x-z-Ebene) orthogonal zur Strukturlinie.
|
|
582
|
+
|
|
583
|
+
Parameter:
|
|
584
|
+
ax : matplotlib.axes.Axes
|
|
585
|
+
Die 2D-Achse zum Plotten
|
|
586
|
+
xi, zi : float
|
|
587
|
+
Startpunkt (globale Koordinaten in x und z)
|
|
588
|
+
my_local : float
|
|
589
|
+
Lokaler My-Wert
|
|
590
|
+
unit_vector : array-like, shape (2,)
|
|
591
|
+
Normalisierter orthogonaler Vektor für das Moment
|
|
592
|
+
scale : float, optional
|
|
593
|
+
Maßstabsfaktor für die Pfeillänge
|
|
594
|
+
"""
|
|
595
|
+
if my_local == 0:
|
|
596
|
+
return # Kein Moment zu plotten
|
|
597
|
+
|
|
598
|
+
# Kleine Verbindungslinie vom Knoten zum Start des Pfeils
|
|
599
|
+
connection_length = 0.05 * scale # Anpassbarer Wert
|
|
600
|
+
|
|
601
|
+
conn_x = xi + connection_length * unit_vector[0] * my_local * scale
|
|
602
|
+
conn_z = zi + connection_length * unit_vector[1] * my_local * scale
|
|
603
|
+
|
|
604
|
+
if my_local >= 0:
|
|
605
|
+
ax.plot([xi, conn_x], [zi, conn_z], color="blue", linewidth=1)
|
|
606
|
+
if my_local < 0:
|
|
607
|
+
ax.plot([xi, conn_x], [zi, conn_z], color="red", linewidth=1)
|
|
608
|
+
|
|
609
|
+
# Berechnung des Vektors, skaliert durch Moment und Maßstabsfaktor
|
|
610
|
+
vec = scale * my_local * unit_vector
|
|
611
|
+
|
|
612
|
+
# Farbwahl basierend auf dem Vorzeichen des Moments
|
|
613
|
+
color = "blue" if my_local >= 0 else "red"
|
|
614
|
+
|
|
615
|
+
# Plotten des Pfeils
|
|
616
|
+
# ax.arrow(conn_x, conn_z, vec[0], vec[1],
|
|
617
|
+
# head_width=0.05 * scale, head_length=0.1 * scale,
|
|
618
|
+
# fc=color, ec=color, length_includes_head=True)
|
|
619
|
+
|
|
620
|
+
# Berechnung des Endpunkts für die Textplatzierung
|
|
621
|
+
x_end = conn_x
|
|
622
|
+
z_end = conn_z
|
|
623
|
+
|
|
624
|
+
# Moment-Text
|
|
625
|
+
moment_text = f"My = {my_local:.3f} MNm"
|
|
626
|
+
|
|
627
|
+
# Textversatz für bessere Sichtbarkeit
|
|
628
|
+
text_offset = 0 # 0.05 * scale
|
|
629
|
+
ax.text(
|
|
630
|
+
x_end + text_offset * unit_vector[0],
|
|
631
|
+
z_end + text_offset * unit_vector[1],
|
|
632
|
+
moment_text,
|
|
633
|
+
color=color,
|
|
634
|
+
fontsize=8,
|
|
635
|
+
)
|
|
636
|
+
|
|
637
|
+
return conn_x, conn_z
|
|
638
|
+
|
|
639
|
+
def plot_all_My_2d(self, ax, nodes, na_memb, ne_memb, My_el_i_store, scale=0.4):
|
|
640
|
+
"""
|
|
641
|
+
Plot My-Momente für alle Elemente an ihren Knoten in 2D (x-z-Ebene).
|
|
642
|
+
|
|
643
|
+
Parameter:
|
|
644
|
+
ax : matplotlib.axes.Axes
|
|
645
|
+
Die 2D-Achse zum Plotten
|
|
646
|
+
nodes : dict
|
|
647
|
+
Dictionary mit Knotenkordinaten
|
|
648
|
+
na_memb, ne_memb : Listen
|
|
649
|
+
Listen von Knotennummern für die Elemente
|
|
650
|
+
My_el_i_store : ndarray
|
|
651
|
+
Array von My-Werten pro Element und Knoten
|
|
652
|
+
scale : float, optional
|
|
653
|
+
Maßstabsfaktor für die Pfeile
|
|
654
|
+
"""
|
|
655
|
+
for i in range(len(na_memb)):
|
|
656
|
+
node_i = na_memb[i]
|
|
657
|
+
node_j = ne_memb[i]
|
|
658
|
+
|
|
659
|
+
ix = nodes["x[m]"][node_i - 1]
|
|
660
|
+
iz = nodes["z[m]"][node_i - 1]
|
|
661
|
+
|
|
662
|
+
jx = nodes["x[m]"][node_j - 1]
|
|
663
|
+
jz = nodes["z[m]"][node_j - 1]
|
|
664
|
+
|
|
665
|
+
# Berechne die Einheitsvektoren für positives und negatives My mittels Kreuzprodukt
|
|
666
|
+
try:
|
|
667
|
+
unit_vector_pos, unit_vector_neg = (
|
|
668
|
+
self.calculate_orthogonal_unit_vector_cross_product(ix, iz, jx, jz)
|
|
669
|
+
)
|
|
670
|
+
print(
|
|
671
|
+
f"Element {i+1}: unit_vector_pos = {unit_vector_pos}, unit_vector_neg = {unit_vector_neg}"
|
|
672
|
+
)
|
|
673
|
+
except ValueError as e:
|
|
674
|
+
print(f"Fehler bei Mitglied {i+1}: {e}")
|
|
675
|
+
continue
|
|
676
|
+
|
|
677
|
+
# Lokales My an Knoten a / b
|
|
678
|
+
My_a = My_el_i_store[i, 0, 0] # [i,0] -> Knoten a
|
|
679
|
+
My_b = My_el_i_store[i, 1, 0] # [i,1] -> Knoten b
|
|
680
|
+
|
|
681
|
+
try:
|
|
682
|
+
# 1) Pfeil am Knoten a
|
|
683
|
+
conn_ix, conn_iz = self.plot_moment_my_2d(
|
|
684
|
+
ax, ix, iz, My_a, unit_vector_pos, scale=scale
|
|
685
|
+
)
|
|
686
|
+
# 2) Pfeil am Knoten b
|
|
687
|
+
conn_jx, conn_jz = self.plot_moment_my_2d(
|
|
688
|
+
ax, jx, jz, My_b, unit_vector_pos, scale=scale
|
|
689
|
+
)
|
|
690
|
+
ax.plot(
|
|
691
|
+
[conn_ix, conn_jx], [conn_iz, conn_jz], color="black", linewidth=1
|
|
692
|
+
)
|
|
693
|
+
except:
|
|
694
|
+
pass
|
|
695
|
+
|
|
696
|
+
# Hinzufügen einer Legende zur Unterscheidung
|
|
697
|
+
red_patch = mpatches.Patch(color="blue", label="Positives My")
|
|
698
|
+
blue_patch = mpatches.Patch(color="red", label="Negatives My")
|
|
699
|
+
ax.legend(handles=[red_patch, blue_patch])
|
|
700
|
+
|
|
701
|
+
def plot_normalforce_N_2d(self, ax, xi, zi, N_local, unit_vector, scale=1):
|
|
702
|
+
"""
|
|
703
|
+
Plot eines Biegemoments My in 2D (x-z-Ebene) orthogonal zur Strukturlinie.
|
|
704
|
+
|
|
705
|
+
Parameter:
|
|
706
|
+
ax : matplotlib.axes.Axes
|
|
707
|
+
Die 2D-Achse zum Plotten
|
|
708
|
+
xi, zi : float
|
|
709
|
+
Startpunkt (globale Koordinaten in x und z)
|
|
710
|
+
N_local : float
|
|
711
|
+
Lokaler N-Wert
|
|
712
|
+
unit_vector : array-like, shape (2,)
|
|
713
|
+
Normalisierter orthogonaler Vektor für das Moment
|
|
714
|
+
scale : float, optional
|
|
715
|
+
Maßstabsfaktor für die Pfeillänge
|
|
716
|
+
"""
|
|
717
|
+
|
|
718
|
+
if N_local == 0:
|
|
719
|
+
return # Keine Kraft zu plotten
|
|
720
|
+
|
|
721
|
+
# Kleine Verbindungslinie vom Knoten zum Start des Pfeils
|
|
722
|
+
connection_length = 0.05 * scale # Anpassbarer Wert
|
|
723
|
+
|
|
724
|
+
conn_x = xi + connection_length * unit_vector[0] * N_local * scale
|
|
725
|
+
conn_z = zi + connection_length * unit_vector[1] * N_local * scale
|
|
726
|
+
|
|
727
|
+
if N_local >= 0:
|
|
728
|
+
ax.plot([xi, conn_x], [zi, conn_z], color="blue", linewidth=1)
|
|
729
|
+
if N_local < 0:
|
|
730
|
+
ax.plot([xi, conn_x], [zi, conn_z], color="red", linewidth=1)
|
|
731
|
+
|
|
732
|
+
# Berechnung des Vektors, skaliert durch Moment und Maßstabsfaktor
|
|
733
|
+
vec = scale * N_local * unit_vector
|
|
734
|
+
|
|
735
|
+
# Farbwahl basierend auf dem Vorzeichen des Moments
|
|
736
|
+
color = "blue" if N_local >= 0 else "red"
|
|
737
|
+
|
|
738
|
+
# Plotten des Pfeils
|
|
739
|
+
# ax.arrow(conn_x, conn_z, vec[0], vec[1],
|
|
740
|
+
# head_width=0.05 * scale, head_length=0.1 * scale,
|
|
741
|
+
# fc=color, ec=color, length_includes_head=True)
|
|
742
|
+
|
|
743
|
+
# Berechnung des Endpunkts für die Textplatzierung
|
|
744
|
+
x_end = conn_x
|
|
745
|
+
z_end = conn_z
|
|
746
|
+
|
|
747
|
+
# Moment-Text
|
|
748
|
+
moment_text = f"N = {N_local:.3f} MNm"
|
|
749
|
+
|
|
750
|
+
# Textversatz für bessere Sichtbarkeit
|
|
751
|
+
text_offset = 0 # 0.05 * scale
|
|
752
|
+
ax.text(
|
|
753
|
+
x_end + text_offset * unit_vector[0],
|
|
754
|
+
z_end + text_offset * unit_vector[1],
|
|
755
|
+
moment_text,
|
|
756
|
+
color=color,
|
|
757
|
+
fontsize=8,
|
|
758
|
+
)
|
|
759
|
+
|
|
760
|
+
return conn_x, conn_z
|
|
761
|
+
|
|
762
|
+
def plot_all_N_2d(self, ax, nodes, na_memb, ne_memb, N_el_i_store, scale=0.4):
|
|
763
|
+
"""
|
|
764
|
+
Plot N-Momente für alle Elemente an ihren Knoten in 2D (x-z-Ebene).
|
|
765
|
+
|
|
766
|
+
Parameter:
|
|
767
|
+
ax : matplotlib.axes.Axes
|
|
768
|
+
Die 2D-Achse zum Plotten
|
|
769
|
+
nodes : dict
|
|
770
|
+
Dictionary mit Knotenkordinaten
|
|
771
|
+
na_memb, ne_memb : Listen
|
|
772
|
+
Listen von Knotennummern für die Elemente
|
|
773
|
+
N_el_i_store : ndarray
|
|
774
|
+
Array von N-Werten pro Element und Knoten
|
|
775
|
+
scale : float, optional
|
|
776
|
+
Maßstabsfaktor für die Pfeile
|
|
777
|
+
"""
|
|
778
|
+
for i in range(len(na_memb)):
|
|
779
|
+
node_i = na_memb[i]
|
|
780
|
+
node_j = ne_memb[i]
|
|
781
|
+
|
|
782
|
+
ix = nodes["x[m]"][node_i - 1]
|
|
783
|
+
iz = nodes["z[m]"][node_i - 1]
|
|
784
|
+
|
|
785
|
+
jx = nodes["x[m]"][node_j - 1]
|
|
786
|
+
jz = nodes["z[m]"][node_j - 1]
|
|
787
|
+
|
|
788
|
+
# Berechne die Einheitsvektoren für positives und negatives My mittels Kreuzprodukt
|
|
789
|
+
try:
|
|
790
|
+
unit_vector_pos, unit_vector_neg = (
|
|
791
|
+
self.calculate_orthogonal_unit_vector_cross_product(ix, iz, jx, jz)
|
|
792
|
+
)
|
|
793
|
+
print(
|
|
794
|
+
f"Element {i+1}: unit_vector_pos = {unit_vector_pos}, unit_vector_neg = {unit_vector_neg}"
|
|
795
|
+
)
|
|
796
|
+
except ValueError as e:
|
|
797
|
+
print(f"Fehler bei Mitglied {i+1}: {e}")
|
|
798
|
+
continue
|
|
799
|
+
|
|
800
|
+
# Lokales My an Knoten a / b
|
|
801
|
+
N_a = N_el_i_store[i, 0, 0] # [i,0] -> Knoten a
|
|
802
|
+
N_b = N_el_i_store[i, 1, 0] # [i,1] -> Knoten b
|
|
803
|
+
|
|
804
|
+
try:
|
|
805
|
+
# 1) Pfeil am Knoten a
|
|
806
|
+
conn_ix, conn_iz = self.plot_normalforce_N_2d(
|
|
807
|
+
ax, ix, iz, N_a, unit_vector_pos, scale=scale
|
|
808
|
+
)
|
|
809
|
+
# 2) Pfeil am Knoten b
|
|
810
|
+
conn_jx, conn_jz = self.plot_normalforce_N_2d(
|
|
811
|
+
ax, jx, jz, N_b, unit_vector_pos, scale=scale
|
|
812
|
+
)
|
|
813
|
+
ax.plot(
|
|
814
|
+
[conn_ix, conn_jx], [conn_iz, conn_jz], color="black", linewidth=1
|
|
815
|
+
)
|
|
816
|
+
except:
|
|
817
|
+
pass
|
|
818
|
+
|
|
819
|
+
# Hinzufügen einer Legende zur Unterscheidung
|
|
820
|
+
red_patch = mpatches.Patch(color="blue", label="Positives N")
|
|
821
|
+
blue_patch = mpatches.Patch(color="red", label="Negatives N")
|
|
822
|
+
ax.legend(handles=[red_patch, blue_patch])
|
|
823
|
+
|
|
824
|
+
def plot_structure_2d(self, nodes, na_memb, ne_memb):
|
|
825
|
+
"""
|
|
826
|
+
Plot der unverformten 2D-Struktur in der x-z-Ebene.
|
|
827
|
+
|
|
828
|
+
Parameter:
|
|
829
|
+
nodes: dict mit den Schlüsseln ['x[m]', 'y[m]', 'z[m]']
|
|
830
|
+
na_memb, ne_memb: Listen mit Knoten-IDs für Start und Ende der Elemente
|
|
831
|
+
"""
|
|
832
|
+
fig, ax = plt.subplots(figsize=(10, 8))
|
|
833
|
+
|
|
834
|
+
# Alle Elemente plotten
|
|
835
|
+
for i in range(len(na_memb)):
|
|
836
|
+
node_i = na_memb[i]
|
|
837
|
+
node_j = ne_memb[i]
|
|
838
|
+
|
|
839
|
+
ix = nodes["x[m]"][node_i - 1]
|
|
840
|
+
iz = nodes["z[m]"][node_i - 1]
|
|
841
|
+
|
|
842
|
+
jx = nodes["x[m]"][node_j - 1]
|
|
843
|
+
jz = nodes["z[m]"][node_j - 1]
|
|
844
|
+
|
|
845
|
+
X = [ix, jx]
|
|
846
|
+
Z = [iz, jz]
|
|
847
|
+
|
|
848
|
+
ax.plot(X, Z, color="black", lw=1.0)
|
|
849
|
+
|
|
850
|
+
# Achsenbeschriftung und Titel
|
|
851
|
+
ax.set_xlabel("X [m]")
|
|
852
|
+
ax.set_ylabel("Z [m]")
|
|
853
|
+
ax.set_title("Unverformte Struktur (x-z-Ebene)")
|
|
854
|
+
ax.grid(True)
|
|
855
|
+
|
|
856
|
+
return fig, ax
|
|
857
|
+
|
|
858
|
+
def plot_structure_with_moments_2d(self, scale=0.4):
|
|
859
|
+
"""
|
|
860
|
+
Kombinierte Methode zum Plotten der Struktur und der Biegemomente My in 2D (x-z-Ebene).
|
|
861
|
+
|
|
862
|
+
Parameter:
|
|
863
|
+
scale : float, optional
|
|
864
|
+
Skalierungsfaktor für die Momente
|
|
865
|
+
"""
|
|
866
|
+
fig, ax = self.plot_structure_2d(
|
|
867
|
+
self.Inp.nodes, self.Inp.members["na"], self.Inp.members["ne"]
|
|
868
|
+
)
|
|
869
|
+
|
|
870
|
+
# Plot der Biegemomente My mittels Kreuzprodukt
|
|
871
|
+
self.plot_all_My_2d(
|
|
872
|
+
ax,
|
|
873
|
+
self.Inp.nodes,
|
|
874
|
+
self.Inp.members["na"],
|
|
875
|
+
self.Inp.members["ne"],
|
|
876
|
+
self.MY_el_i_store,
|
|
877
|
+
scale=scale,
|
|
878
|
+
)
|
|
879
|
+
|
|
880
|
+
# Achsenlimits automatisch anpassen und gleiches Seitenverhältnis
|
|
881
|
+
ax.set_aspect("equal", adjustable="datalim")
|
|
882
|
+
ax.relim()
|
|
883
|
+
ax.autoscale_view()
|
|
884
|
+
|
|
885
|
+
return fig, ax
|
|
886
|
+
|
|
887
|
+
def plot_structure_with_normalforces_2d(self, scale=0.4):
|
|
888
|
+
"""
|
|
889
|
+
Kombinierte Methode zum Plotten der Struktur und der Biegemomente My in 2D (x-z-Ebene).
|
|
890
|
+
|
|
891
|
+
Parameter:
|
|
892
|
+
scale : float, optional
|
|
893
|
+
Skalierungsfaktor für die Momente
|
|
894
|
+
"""
|
|
895
|
+
fig, ax = self.plot_structure_2d(
|
|
896
|
+
self.Inp.nodes, self.Inp.members["na"], self.Inp.members["ne"]
|
|
897
|
+
)
|
|
898
|
+
|
|
899
|
+
# Plot der Biegemomente My mittels Kreuzprodukt
|
|
900
|
+
self.plot_all_N_2d(
|
|
901
|
+
ax,
|
|
902
|
+
self.Inp.nodes,
|
|
903
|
+
self.Inp.members["na"],
|
|
904
|
+
self.Inp.members["ne"],
|
|
905
|
+
self.N_el_i_store,
|
|
906
|
+
scale=scale,
|
|
907
|
+
)
|
|
908
|
+
|
|
909
|
+
# Achsenlimits automatisch anpassen und gleiches Seitenverhältnis
|
|
910
|
+
ax.set_aspect("equal", adjustable="datalim")
|
|
911
|
+
ax.relim()
|
|
912
|
+
ax.autoscale_view()
|
|
913
|
+
|
|
914
|
+
return fig, ax
|
|
915
|
+
|
|
916
|
+
def plot_structure_deformed_3d(
|
|
917
|
+
self,
|
|
918
|
+
scale: float = 1.0,
|
|
919
|
+
show_undeformed: bool = True,
|
|
920
|
+
node_labels: bool = False,
|
|
921
|
+
undeformed_kwargs: dict | None = None,
|
|
922
|
+
deformed_kwargs: dict | None = None,
|
|
923
|
+
):
|
|
924
|
+
"""
|
|
925
|
+
Zeichnet das räumliche Tragwerk samt skalierter Verformungen.
|
|
926
|
+
|
|
927
|
+
Parameters
|
|
928
|
+
----------
|
|
929
|
+
scale : float, optional
|
|
930
|
+
Maßstabsfaktor für die Verschiebungen.
|
|
931
|
+
show_undeformed : bool, optional
|
|
932
|
+
Wenn True, wird das unverformte System zusätzlich angezeigt.
|
|
933
|
+
node_labels : bool, optional
|
|
934
|
+
Beschriftet die Knoten mit ihrer Nummer.
|
|
935
|
+
undeformed_kwargs / deformed_kwargs : dict, optional
|
|
936
|
+
Extra-Keyword-Argumente für die Linien (Farbe, Linienstärke …).
|
|
937
|
+
"""
|
|
938
|
+
if undeformed_kwargs is None:
|
|
939
|
+
undeformed_kwargs = dict(color="lightgray", lw=1.0, zorder=1)
|
|
940
|
+
if deformed_kwargs is None:
|
|
941
|
+
deformed_kwargs = dict(color="blue", lw=2.0, zorder=3)
|
|
942
|
+
|
|
943
|
+
fig = plt.figure(figsize=(10, 8))
|
|
944
|
+
ax: Axes3D = fig.add_subplot(projection="3d")
|
|
945
|
+
|
|
946
|
+
na = self.Inp.members["na"]
|
|
947
|
+
ne = self.Inp.members["ne"]
|
|
948
|
+
|
|
949
|
+
# Kurzfunktionen für Verschiebungen
|
|
950
|
+
def ux(n):
|
|
951
|
+
return self.u_ges[7 * (n - 1) + 0] # u_x
|
|
952
|
+
|
|
953
|
+
def uy(n):
|
|
954
|
+
return self.u_ges[7 * (n - 1) + 1] # u_y
|
|
955
|
+
|
|
956
|
+
def uz(n):
|
|
957
|
+
return self.u_ges[7 * (n - 1) + 3] # u_z
|
|
958
|
+
|
|
959
|
+
# 1) Unverformte Struktur
|
|
960
|
+
if show_undeformed:
|
|
961
|
+
for a, e in zip(na, ne):
|
|
962
|
+
xa, ya, za = (
|
|
963
|
+
self.Inp.nodes[k][a - 1] for k in ("x[m]", "y[m]", "z[m]")
|
|
964
|
+
)
|
|
965
|
+
xe, ye, ze = (
|
|
966
|
+
self.Inp.nodes[k][e - 1] for k in ("x[m]", "y[m]", "z[m]")
|
|
967
|
+
)
|
|
968
|
+
ax.plot([xa, xe], [ya, ye], [za, ze], **undeformed_kwargs)
|
|
969
|
+
|
|
970
|
+
# 2) Verformte Struktur
|
|
971
|
+
for a, e in zip(na, ne):
|
|
972
|
+
xa, ya, za = (self.Inp.nodes[k][a - 1] for k in ("x[m]", "y[m]", "z[m]"))
|
|
973
|
+
xe, ye, ze = (self.Inp.nodes[k][e - 1] for k in ("x[m]", "y[m]", "z[m]"))
|
|
974
|
+
|
|
975
|
+
ax.plot(
|
|
976
|
+
[xa + scale * ux(a), xe + scale * ux(e)],
|
|
977
|
+
[ya + scale * uy(a), ye + scale * uy(e)],
|
|
978
|
+
[za + scale * uz(a), ze + scale * uz(e)],
|
|
979
|
+
**deformed_kwargs,
|
|
980
|
+
)
|
|
981
|
+
|
|
982
|
+
# 3) Knotennummern (optional)
|
|
983
|
+
if node_labels:
|
|
984
|
+
for n in range(1, len(self.Inp.nodes) + 1):
|
|
985
|
+
x0 = self.Inp.nodes["x[m]"][n - 1] + scale * ux(n)
|
|
986
|
+
y0 = self.Inp.nodes["y[m]"][n - 1] + scale * uy(n)
|
|
987
|
+
z0 = self.Inp.nodes["z[m]"][n - 1] + scale * uz(n)
|
|
988
|
+
ax.text(x0, y0, z0, f"{n}", fontsize=8, ha="center", va="center")
|
|
989
|
+
|
|
990
|
+
# Achsen und Optik
|
|
991
|
+
ax.set_xlabel("X [m]")
|
|
992
|
+
ax.set_ylabel("Y [m]")
|
|
993
|
+
ax.set_zlabel("Z [m]")
|
|
994
|
+
ax.set_title(f"Verformte Struktur 3-D (Skalierung {scale:g})")
|
|
995
|
+
ax.set_box_aspect((1, 1, 1)) # gleiches Seitenverhältnis
|
|
996
|
+
fig.tight_layout()
|
|
997
|
+
|
|
998
|
+
# Optional: Blickwinkel anpassen (z. B. isometrisch)
|
|
999
|
+
# ax.view_init(elev=20, azim=-60)
|
|
1000
|
+
|
|
1001
|
+
return fig, ax
|
|
1002
|
+
|
|
1003
|
+
def plot_structure_deformed_3d_interactive(
|
|
1004
|
+
self,
|
|
1005
|
+
scale_init: float = 1.0,
|
|
1006
|
+
show_undeformed: bool = True,
|
|
1007
|
+
node_labels: bool = False,
|
|
1008
|
+
):
|
|
1009
|
+
"""
|
|
1010
|
+
Öffnet ein 3-D-Fenster mit interaktiven Slidern:
|
|
1011
|
+
• Elevation (θ) • Azimut (φ) • Skalierung s der Verformungen
|
|
1012
|
+
Die x-, y-, z-Achsen werden stets auf identische Länge gebracht.
|
|
1013
|
+
"""
|
|
1014
|
+
|
|
1015
|
+
# ─── Kurzfunktionen für Verschiebungen ────────────────────────────
|
|
1016
|
+
ux = lambda n: self.u_ges[7 * (n - 1) + 0] # DOF 0
|
|
1017
|
+
uy = lambda n: self.u_ges[7 * (n - 1) + 1] # DOF 1
|
|
1018
|
+
uz = lambda n: self.u_ges[7 * (n - 1) + 3] # DOF 3
|
|
1019
|
+
|
|
1020
|
+
# ─── Grundplot anlegen ───────────────────────────────────────────
|
|
1021
|
+
import matplotlib.pyplot as plt
|
|
1022
|
+
|
|
1023
|
+
fig = plt.figure(figsize=(8, 6))
|
|
1024
|
+
ax = fig.add_subplot(projection="3d")
|
|
1025
|
+
|
|
1026
|
+
# 1) nach dem allerersten Zeichnen
|
|
1027
|
+
_set_axes_equal(ax)
|
|
1028
|
+
|
|
1029
|
+
na, ne = self.Inp.members["na"], self.Inp.members["ne"]
|
|
1030
|
+
|
|
1031
|
+
# Listen zum schnellen Updaten
|
|
1032
|
+
deformed_lines: list[Line3D] = []
|
|
1033
|
+
|
|
1034
|
+
# ── Linien zeichnen (undeformed + deformed) ───────────────────────
|
|
1035
|
+
for a, e in zip(na, ne):
|
|
1036
|
+
xa0, ya0, za0 = (self.Inp.nodes[k][a - 1] for k in ("x[m]", "y[m]", "z[m]"))
|
|
1037
|
+
xe0, ye0, ze0 = (self.Inp.nodes[k][e - 1] for k in ("x[m]", "y[m]", "z[m]"))
|
|
1038
|
+
|
|
1039
|
+
if show_undeformed:
|
|
1040
|
+
ax.plot(
|
|
1041
|
+
[xa0, xe0],
|
|
1042
|
+
[ya0, ye0],
|
|
1043
|
+
[za0, ze0],
|
|
1044
|
+
color="lightgray",
|
|
1045
|
+
lw=1,
|
|
1046
|
+
zorder=1,
|
|
1047
|
+
)
|
|
1048
|
+
|
|
1049
|
+
# deformed-Linie initial
|
|
1050
|
+
xd = [xa0 + scale_init * ux(a), xe0 + scale_init * ux(e)]
|
|
1051
|
+
yd = [ya0 + scale_init * uy(a), ye0 + scale_init * uy(e)]
|
|
1052
|
+
zd = [za0 + scale_init * uz(a), ze0 + scale_init * uz(e)]
|
|
1053
|
+
|
|
1054
|
+
(ld,) = ax.plot(xd, yd, zd, color="tab:blue", lw=2, zorder=3)
|
|
1055
|
+
deformed_lines.append(ld)
|
|
1056
|
+
|
|
1057
|
+
# Knotennummern (optional) – als separate Textobjekte
|
|
1058
|
+
text_elems = []
|
|
1059
|
+
if node_labels:
|
|
1060
|
+
for n in range(1, len(self.Inp.nodes) + 1):
|
|
1061
|
+
x0, y0, z0 = (
|
|
1062
|
+
self.Inp.nodes[k][n - 1] for k in ("x[m]", "y[m]", "z[m]")
|
|
1063
|
+
)
|
|
1064
|
+
t = ax.text(
|
|
1065
|
+
x0 + scale_init * ux(n),
|
|
1066
|
+
y0 + scale_init * uy(n),
|
|
1067
|
+
z0 + scale_init * uz(n),
|
|
1068
|
+
f"{n}",
|
|
1069
|
+
fontsize=8,
|
|
1070
|
+
ha="center",
|
|
1071
|
+
va="center",
|
|
1072
|
+
zorder=4,
|
|
1073
|
+
)
|
|
1074
|
+
text_elems.append(t)
|
|
1075
|
+
|
|
1076
|
+
# ─── Achsenformatierung ───────────────────────────────────────────
|
|
1077
|
+
ax.set_xlabel("X [m]")
|
|
1078
|
+
ax.set_ylabel("Y [m]")
|
|
1079
|
+
ax.set_zlabel("Z [m]")
|
|
1080
|
+
ax.set_title("Verformte Struktur – interaktiv")
|
|
1081
|
+
try:
|
|
1082
|
+
ax.set_box_aspect((1, 1, 1)) # Matplotlib ≥ 3.3
|
|
1083
|
+
except AttributeError:
|
|
1084
|
+
_set_axes_equal(ax)
|
|
1085
|
+
|
|
1086
|
+
# ─── Slider-UI unter dem Plot platzieren ──────────────────────────
|
|
1087
|
+
fig.subplots_adjust(bottom=0.25) # Platz für drei Slider
|
|
1088
|
+
|
|
1089
|
+
# Achsen-Koordinaten [links, unten, breite, höhe]
|
|
1090
|
+
ax_elev = fig.add_axes([0.13, 0.15, 0.74, 0.03])
|
|
1091
|
+
ax_azim = fig.add_axes([0.13, 0.10, 0.74, 0.03])
|
|
1092
|
+
ax_scale = fig.add_axes([0.13, 0.05, 0.74, 0.03])
|
|
1093
|
+
|
|
1094
|
+
s_elev = Slider(ax_elev, "Elev (°)", -90, 90, valinit=20, valstep=1)
|
|
1095
|
+
s_azim = Slider(ax_azim, "Azim (°)", -180, 180, valinit=-60, valstep=1)
|
|
1096
|
+
s_scale = Slider(ax_scale, "Scale", 0.0, scale_init * 10000, valinit=scale_init)
|
|
1097
|
+
|
|
1098
|
+
# ─── Callback-Funktionen ──────────────────────────────────────────
|
|
1099
|
+
def _update_view(_):
|
|
1100
|
+
ax.view_init(elev=s_elev.val, azim=s_azim.val)
|
|
1101
|
+
fig.canvas.draw_idle()
|
|
1102
|
+
|
|
1103
|
+
def _update_scale(_):
|
|
1104
|
+
s = s_scale.val
|
|
1105
|
+
for (a, e), line in zip(zip(na, ne), deformed_lines):
|
|
1106
|
+
xa0, ya0, za0 = (
|
|
1107
|
+
self.Inp.nodes[k][a - 1] for k in ("x[m]", "y[m]", "z[m]")
|
|
1108
|
+
)
|
|
1109
|
+
xe0, ye0, ze0 = (
|
|
1110
|
+
self.Inp.nodes[k][e - 1] for k in ("x[m]", "y[m]", "z[m]")
|
|
1111
|
+
)
|
|
1112
|
+
# neue Koordinaten
|
|
1113
|
+
line.set_data_3d(
|
|
1114
|
+
[xa0 + s * ux(a), xe0 + s * ux(e)],
|
|
1115
|
+
[ya0 + s * uy(a), ye0 + s * uy(e)],
|
|
1116
|
+
[za0 + s * uz(a), ze0 + s * uz(e)],
|
|
1117
|
+
)
|
|
1118
|
+
# Knotentexte mit skalieren
|
|
1119
|
+
if node_labels:
|
|
1120
|
+
for n, txt in enumerate(text_elems, start=1):
|
|
1121
|
+
x0, y0, z0 = (
|
|
1122
|
+
self.Inp.nodes[k][n - 1] for k in ("x[m]", "y[m]", "z[m]")
|
|
1123
|
+
)
|
|
1124
|
+
txt.set_position((x0 + s * ux(n), y0 + s * uy(n)))
|
|
1125
|
+
txt.set_3d_properties(z0 + s * uz(n), zdir="z")
|
|
1126
|
+
# Limits neu auf Würfel setzen
|
|
1127
|
+
try:
|
|
1128
|
+
ax.set_box_aspect((1, 1, 1))
|
|
1129
|
+
except AttributeError:
|
|
1130
|
+
_set_axes_equal(ax)
|
|
1131
|
+
fig.canvas.draw_idle()
|
|
1132
|
+
|
|
1133
|
+
# Slider verbinden
|
|
1134
|
+
s_elev.on_changed(_update_view)
|
|
1135
|
+
s_azim.on_changed(_update_view)
|
|
1136
|
+
s_scale.on_changed(_update_scale)
|
|
1137
|
+
|
|
1138
|
+
# Startansicht
|
|
1139
|
+
_update_view(None)
|
|
1140
|
+
_update_scale(None)
|
|
1141
|
+
|
|
1142
|
+
return fig, ax, (s_elev, s_azim, s_scale)
|
|
1143
|
+
|
|
1144
|
+
def orthogonal_unit_vector_3d(self,pt_i, pt_j, prefer_axis="y"):
|
|
1145
|
+
"""
|
|
1146
|
+
Liefert einen normierten Vektor w, der senkrecht auf der
|
|
1147
|
+
Stabachse v = (pt_j - pt_i) steht.
|
|
1148
|
+
|
|
1149
|
+
Parameters
|
|
1150
|
+
----------
|
|
1151
|
+
pt_i, pt_j : array-like (3,)
|
|
1152
|
+
Globale xyz-Koordinaten der Knoten i und j.
|
|
1153
|
+
prefer_axis : {"x","y","z"}, optional
|
|
1154
|
+
Welcher globale Achsvektor soll primär verwendet werden,
|
|
1155
|
+
um das Kreuzprodukt zu bilden? Wähle eine Achse, die
|
|
1156
|
+
im Regelfall nicht parallel zur Elementachse v ist.
|
|
1157
|
+
|
|
1158
|
+
Returns
|
|
1159
|
+
-------
|
|
1160
|
+
w : ndarray (3,)
|
|
1161
|
+
Normierter Orthogonalvektor.
|
|
1162
|
+
"""
|
|
1163
|
+
v = np.asarray(pt_j) - np.asarray(pt_i)
|
|
1164
|
+
v_norm = np.linalg.norm(v)
|
|
1165
|
+
if v_norm == 0:
|
|
1166
|
+
raise ValueError("Elementlänge 0 – identische Knoten?")
|
|
1167
|
+
|
|
1168
|
+
v = v / v_norm
|
|
1169
|
+
|
|
1170
|
+
# Globalen Hilfsvektor wählen
|
|
1171
|
+
axes = {"x": np.array([1, 0, 0]),
|
|
1172
|
+
"y": np.array([0, 1, 0]),
|
|
1173
|
+
"z": np.array([0, 0, 1])}
|
|
1174
|
+
u = axes.get(prefer_axis, axes["y"])
|
|
1175
|
+
|
|
1176
|
+
# Prüfen, ob v und u (fast) parallel sind
|
|
1177
|
+
if abs(np.dot(v, u)) > 0.95: # nahezu kollinear
|
|
1178
|
+
u = axes["z"] if prefer_axis != "z" else axes["x"]
|
|
1179
|
+
|
|
1180
|
+
w = np.cross(v, u)
|
|
1181
|
+
w_norm = np.linalg.norm(w)
|
|
1182
|
+
if w_norm == 0:
|
|
1183
|
+
raise ValueError("Konnte keinen Orthogonalvektor berechnen.")
|
|
1184
|
+
return w / w_norm
|
|
1185
|
+
|
|
1186
|
+
def plot_moment_my_3d(self,ax, x0, y0, z0, m_val, w, scale=1.0,
|
|
1187
|
+
color_pos="tab:blue", color_neg="tab:red",
|
|
1188
|
+
text=True):
|
|
1189
|
+
"""
|
|
1190
|
+
Zeichnet einen Momentenpfeil (My) im 3-D-Plot.
|
|
1191
|
+
|
|
1192
|
+
Parameter
|
|
1193
|
+
---------
|
|
1194
|
+
ax : mpl_toolkits.mplot3d.Axes3D
|
|
1195
|
+
x0,y0,z0 : float
|
|
1196
|
+
Koordinaten des Ausgangsknotens (global).
|
|
1197
|
+
m_val : float
|
|
1198
|
+
Momentwert (positiv / negativ gemäß System).
|
|
1199
|
+
w : ndarray (3,)
|
|
1200
|
+
Normierter Orthogonalvektor (Ausgabe der o.g. Funktion).
|
|
1201
|
+
scale : float
|
|
1202
|
+
Skalierungsfaktor für die Pfeillänge.
|
|
1203
|
+
"""
|
|
1204
|
+
if m_val == 0:
|
|
1205
|
+
return None # nichts zu plotten
|
|
1206
|
+
|
|
1207
|
+
# Farbe
|
|
1208
|
+
col = color_pos if m_val >= 0 else color_neg
|
|
1209
|
+
|
|
1210
|
+
# kleine Verbindungslinie vom Knoten nach außen
|
|
1211
|
+
link_len = 0.05 * scale
|
|
1212
|
+
P1 = np.array([x0, y0, z0])
|
|
1213
|
+
P2 = P1 + link_len * w * abs(m_val)
|
|
1214
|
+
|
|
1215
|
+
# Pfeil
|
|
1216
|
+
vec = scale * m_val * w
|
|
1217
|
+
Q = P2 + vec
|
|
1218
|
+
|
|
1219
|
+
# Plotten
|
|
1220
|
+
ax.plot([P1[0], P2[0]], [P1[1], P2[1]], [P1[2], P2[2]], color=col, lw=1)
|
|
1221
|
+
ax.quiver(P2[0], P2[1], P2[2],
|
|
1222
|
+
vec[0], vec[1], vec[2],
|
|
1223
|
+
arrow_length_ratio=0.15, color=col, linewidth=1)
|
|
1224
|
+
|
|
1225
|
+
# Text
|
|
1226
|
+
if text:
|
|
1227
|
+
txt = f"My={m_val:.2f}"
|
|
1228
|
+
ax.text(Q[0], Q[1], Q[2], txt, fontsize=8, color=col)
|
|
1229
|
+
|
|
1230
|
+
return (P1, P2, Q) # falls du später noch updaten willst
|
|
1231
|
+
|
|
1232
|
+
def plot_My_3d(
|
|
1233
|
+
self,
|
|
1234
|
+
scale: float = 1,
|
|
1235
|
+
show_axes: bool = True,
|
|
1236
|
+
show_stabs: bool = True,
|
|
1237
|
+
text: bool = True,
|
|
1238
|
+
prefer_axis: str = "y",
|
|
1239
|
+
):
|
|
1240
|
+
"""
|
|
1241
|
+
Erstellt EINEN separaten 3-D-Plot, in dem ausschließlich die
|
|
1242
|
+
My-Momente (Biegung um lokale y-Achse) dargestellt werden.
|
|
1243
|
+
|
|
1244
|
+
Parameter
|
|
1245
|
+
---------
|
|
1246
|
+
scale : float globaler Faktor für die Pfeillängen (Moment * scale)
|
|
1247
|
+
show_axes : bool Achsenbeschriftungen + Würfelanzeige?
|
|
1248
|
+
show_stabs : bool graue Stabachsen als Anhalt zeichnen?
|
|
1249
|
+
text : bool Momentwert als Text an Pfeilspitze?
|
|
1250
|
+
prefer_axis : {"x","y","z"} welche globale Achse zur Bildung
|
|
1251
|
+
des Orthogonalvektors primär verwenden?
|
|
1252
|
+
"""
|
|
1253
|
+
fig = plt.figure(figsize=(8, 6))
|
|
1254
|
+
ax = fig.add_subplot(projection="3d")
|
|
1255
|
+
ax.set_title("My-Momente 3-D")
|
|
1256
|
+
|
|
1257
|
+
na, ne = self.Inp.members["na"], self.Inp.members["ne"]
|
|
1258
|
+
|
|
1259
|
+
# ----- 1. optional: Stabachsen als helle Linien --------------------
|
|
1260
|
+
if show_stabs:
|
|
1261
|
+
segs = []
|
|
1262
|
+
for a, e in zip(na, ne):
|
|
1263
|
+
Pi = [self.Inp.nodes[k][a - 1] for k in ("x[m]", "y[m]", "z[m]")]
|
|
1264
|
+
Pj = [self.Inp.nodes[k][e - 1] for k in ("x[m]", "y[m]", "z[m]")]
|
|
1265
|
+
segs.append([Pi, Pj])
|
|
1266
|
+
lc = Line3DCollection(segs, colors="lightgray", linewidths=1, zorder=0)
|
|
1267
|
+
ax.add_collection3d(lc)
|
|
1268
|
+
|
|
1269
|
+
# ----- 2. Momente plotten ------------------------------------------
|
|
1270
|
+
for idx, (a, e) in enumerate(zip(na, ne)):
|
|
1271
|
+
Pi = np.array([self.Inp.nodes[k][a - 1] for k in ("x[m]", "y[m]", "z[m]")])
|
|
1272
|
+
Pj = np.array([self.Inp.nodes[k][e - 1] for k in ("x[m]", "y[m]", "z[m]")])
|
|
1273
|
+
|
|
1274
|
+
w = self.orthogonal_unit_vector_3d(Pi, Pj, prefer_axis=prefer_axis)
|
|
1275
|
+
|
|
1276
|
+
My_a = float(self.MY_el_i_store[idx, 0, 0])
|
|
1277
|
+
My_b = float(self.MY_el_i_store[idx, 1, 0])
|
|
1278
|
+
|
|
1279
|
+
self.plot_moment_my_3d(ax, *Pi, My_a, w, scale=scale, text=text)
|
|
1280
|
+
self.plot_moment_my_3d(ax, *Pj, My_b, w, scale=scale, text=text)
|
|
1281
|
+
|
|
1282
|
+
# ----- 3. Optik -----------------------------------------------------
|
|
1283
|
+
if show_axes:
|
|
1284
|
+
ax.set_xlabel("X [m]")
|
|
1285
|
+
ax.set_ylabel("Y [m]")
|
|
1286
|
+
ax.set_zlabel("Z [m]")
|
|
1287
|
+
else: # Achsen ausblenden
|
|
1288
|
+
ax.set_axis_off()
|
|
1289
|
+
|
|
1290
|
+
# kubische Limits
|
|
1291
|
+
_set_axes_equal(ax)
|
|
1292
|
+
fig.tight_layout()
|
|
1293
|
+
|
|
1294
|
+
return fig, ax
|
|
1295
|
+
|
|
1296
|
+
def plot_My_3d_interactive(
|
|
1297
|
+
self,
|
|
1298
|
+
scale_init: float = 1e-3,
|
|
1299
|
+
show_axes: bool = True,
|
|
1300
|
+
show_stabs: bool = True,
|
|
1301
|
+
text: bool = True,
|
|
1302
|
+
prefer_axis: str = "y",
|
|
1303
|
+
):
|
|
1304
|
+
"""
|
|
1305
|
+
Interaktive 3-D-Darstellung der My-Momente mit drei Slidern:
|
|
1306
|
+
• Elevation (θ) • Azimut (φ) • Skalierung s
|
|
1307
|
+
"""
|
|
1308
|
+
import matplotlib.pyplot as plt
|
|
1309
|
+
|
|
1310
|
+
# ---------- Grundplot zeichnen -------------------------------------
|
|
1311
|
+
fig, ax = self.plot_My_3d(
|
|
1312
|
+
scale=scale_init,
|
|
1313
|
+
show_axes=show_axes,
|
|
1314
|
+
show_stabs=show_stabs,
|
|
1315
|
+
text=text,
|
|
1316
|
+
prefer_axis=prefer_axis,
|
|
1317
|
+
)
|
|
1318
|
+
|
|
1319
|
+
# ── alle momentan gezeichneten Artists merken (fürs Löschen) ───────
|
|
1320
|
+
base_children = set(ax.get_children()) # alles, was NICHT neu erzeugt wird
|
|
1321
|
+
|
|
1322
|
+
# ---------- UI-Platz reservieren -----------------------------------
|
|
1323
|
+
fig.subplots_adjust(bottom=0.25)
|
|
1324
|
+
|
|
1325
|
+
ax_elev = fig.add_axes([0.14, 0.15, 0.72, 0.03])
|
|
1326
|
+
ax_azim = fig.add_axes([0.14, 0.10, 0.72, 0.03])
|
|
1327
|
+
ax_scale = fig.add_axes([0.14, 0.05, 0.72, 0.03])
|
|
1328
|
+
|
|
1329
|
+
s_elev = Slider(ax_elev, "Elev (°)", -90, 90, valinit=20, valstep=1)
|
|
1330
|
+
s_azim = Slider(ax_azim, "Azim (°)", -180, 180, valinit=-60, valstep=1)
|
|
1331
|
+
s_scale = Slider(ax_scale, "Scale", 0.0, scale_init*100, valinit=scale_init)
|
|
1332
|
+
|
|
1333
|
+
# ---------- Helfer: Momente neu zeichnen ---------------------------
|
|
1334
|
+
def _redraw_moments(scale):
|
|
1335
|
+
# 1) alles löschen, was nach dem Basisset erzeugt wurde
|
|
1336
|
+
for art in list(ax.get_children()):
|
|
1337
|
+
if art not in base_children:
|
|
1338
|
+
art.remove()
|
|
1339
|
+
|
|
1340
|
+
# 2) neu zeichnen (ohne Achsen neu zu beschriften)
|
|
1341
|
+
na, ne = self.Inp.members["na"], self.Inp.members["ne"]
|
|
1342
|
+
for idx, (a, e) in enumerate(zip(na, ne)):
|
|
1343
|
+
Pi = np.array([self.Inp.nodes[k][a - 1] for k in ("x[m]","y[m]","z[m]")])
|
|
1344
|
+
Pj = np.array([self.Inp.nodes[k][e - 1] for k in ("x[m]","y[m]","z[m]")])
|
|
1345
|
+
|
|
1346
|
+
w = self.orthogonal_unit_vector_3d(Pi, Pj, prefer_axis=prefer_axis)
|
|
1347
|
+
My_a = float(self.MY_el_i_store[idx, 0, 0])
|
|
1348
|
+
My_b = float(self.MY_el_i_store[idx, 1, 0])
|
|
1349
|
+
|
|
1350
|
+
self.plot_moment_my_3d(ax, *Pi, My_a, w, scale=scale, text=text)
|
|
1351
|
+
self.plot_moment_my_3d(ax, *Pj, My_b, w, scale=scale, text=text)
|
|
1352
|
+
|
|
1353
|
+
_set_axes_equal(ax)
|
|
1354
|
+
fig.canvas.draw_idle()
|
|
1355
|
+
|
|
1356
|
+
# ---------- Callbacks ----------------------------------------------
|
|
1357
|
+
def _update_view(_):
|
|
1358
|
+
ax.view_init(elev=s_elev.val, azim=s_azim.val)
|
|
1359
|
+
fig.canvas.draw_idle()
|
|
1360
|
+
|
|
1361
|
+
def _update_scale(_):
|
|
1362
|
+
_redraw_moments(s_scale.val)
|
|
1363
|
+
|
|
1364
|
+
s_elev.on_changed(_update_view)
|
|
1365
|
+
s_azim.on_changed(_update_view)
|
|
1366
|
+
s_scale.on_changed(_update_scale)
|
|
1367
|
+
|
|
1368
|
+
# ---------- Initiale Ansicht ---------------------------------------
|
|
1369
|
+
_update_view(None) # stellt Kamera
|
|
1370
|
+
# (Momenten-Plot ist schon mit scale_init gezeichnet)
|
|
1371
|
+
|
|
1372
|
+
return fig, ax, (s_elev, s_azim, s_scale)
|
|
1373
|
+
|
|
1374
|
+
# Beispielhafte Hauptfunktion
|
|
1375
|
+
if __name__ == "__main__":
|
|
1376
|
+
import matplotlib
|
|
1377
|
+
# Für Desktop-Skripte (nicht im Notebook) ein GUI-Backend wählen:
|
|
1378
|
+
matplotlib.use("TkAgg") # oder "QtAgg"
|
|
1379
|
+
import matplotlib.pyplot as plt
|
|
1380
|
+
|
|
1381
|
+
# ── Instanz erzeugen (rechnet nur einmal) ───────────────────────────
|
|
1382
|
+
calc = mainloop()
|
|
1383
|
+
|
|
1384
|
+
# 2-D-Momente --------------------------------------------------------
|
|
1385
|
+
fig1, ax1 = calc.plot_structure_with_moments_2d(scale=5)
|
|
1386
|
+
ax1.invert_yaxis()
|
|
1387
|
+
fig1.savefig("plots/ClampHinged_Inclination.eps", format="eps")
|
|
1388
|
+
|
|
1389
|
+
# 2-D-Normalkräfte ---------------------------------------------------
|
|
1390
|
+
fig2, ax2 = calc.plot_structure_with_normalforces_2d(scale=5)
|
|
1391
|
+
ax2.invert_yaxis()
|
|
1392
|
+
|
|
1393
|
+
# 3-D-Plot mit Slidern ----------------------------------------------
|
|
1394
|
+
fig3, ax3, sliders = calc.plot_structure_deformed_3d_interactive(
|
|
1395
|
+
scale_init=10,
|
|
1396
|
+
node_labels=True,
|
|
1397
|
+
)
|
|
1398
|
+
|
|
1399
|
+
calc.plot_My_3d_interactive(scale_init=1e-3, text=True)
|
|
1400
|
+
|
|
1401
|
+
# ── genau EIN Aufruf ────────────────────────────────────────────────
|
|
1402
|
+
plt.show() # Fenster offen → hier läuft die Event-Schleife
|
|
1403
|
+
# Fenster zu → show() kehrt zurück → Script endet
|