midas-civil 1.4.1__py3-none-any.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.
- midas_civil/_BoundaryChangeAssignment.py +278 -0
- midas_civil/__init__.py +51 -0
- midas_civil/_analysiscontrol.py +585 -0
- midas_civil/_boundary.py +888 -0
- midas_civil/_construction.py +1004 -0
- midas_civil/_element.py +1346 -0
- midas_civil/_group.py +337 -0
- midas_civil/_load.py +967 -0
- midas_civil/_loadcomb.py +159 -0
- midas_civil/_mapi.py +249 -0
- midas_civil/_material.py +1692 -0
- midas_civil/_model.py +522 -0
- midas_civil/_movingload.py +1479 -0
- midas_civil/_node.py +532 -0
- midas_civil/_result_table.py +929 -0
- midas_civil/_result_test.py +5455 -0
- midas_civil/_section/_TapdbSecSS.py +175 -0
- midas_civil/_section/__init__.py +413 -0
- midas_civil/_section/_compositeSS.py +283 -0
- midas_civil/_section/_dbSecSS.py +164 -0
- midas_civil/_section/_offsetSS.py +53 -0
- midas_civil/_section/_pscSS copy.py +455 -0
- midas_civil/_section/_pscSS.py +822 -0
- midas_civil/_section/_tapPSC12CellSS.py +565 -0
- midas_civil/_section/_unSupp.py +58 -0
- midas_civil/_settlement.py +161 -0
- midas_civil/_temperature.py +677 -0
- midas_civil/_tendon.py +1016 -0
- midas_civil/_thickness.py +147 -0
- midas_civil/_utils.py +529 -0
- midas_civil/_utilsFunc/__init__.py +0 -0
- midas_civil/_utilsFunc/_line2plate.py +636 -0
- midas_civil/_view.py +891 -0
- midas_civil/_view_trial.py +430 -0
- midas_civil/_visualise.py +347 -0
- midas_civil-1.4.1.dist-info/METADATA +74 -0
- midas_civil-1.4.1.dist-info/RECORD +40 -0
- midas_civil-1.4.1.dist-info/WHEEL +5 -0
- midas_civil-1.4.1.dist-info/licenses/LICENSE +21 -0
- midas_civil-1.4.1.dist-info/top_level.txt +1 -0
midas_civil/_element.py
ADDED
|
@@ -0,0 +1,1346 @@
|
|
|
1
|
+
from ._mapi import MidasAPI,NX
|
|
2
|
+
from ._node import Node,nodeByID,nodesInGroup
|
|
3
|
+
from ._group import _add_node_2_stGroup,Group, _add_elem_2_stGroup
|
|
4
|
+
import numpy as np
|
|
5
|
+
# from scipy.interpolate import splev, splprep , interp1d , Akima1DInterpolator
|
|
6
|
+
from math import hypot
|
|
7
|
+
from ._utils import _convItem2List , _longestList,sFlatten
|
|
8
|
+
from colorama import Fore,Style
|
|
9
|
+
from typing import Literal
|
|
10
|
+
|
|
11
|
+
_meshType = Literal['Quad','Tri']
|
|
12
|
+
|
|
13
|
+
def _createSurface(points,mSize,tagID):
|
|
14
|
+
import gmsh
|
|
15
|
+
final_points, num_points = _dividePoints(points,mSize)
|
|
16
|
+
|
|
17
|
+
point_tags = []
|
|
18
|
+
for pt in final_points:
|
|
19
|
+
# print(pt)
|
|
20
|
+
point_tags.append(gmsh.model.occ.addPoint(pt[0],pt[1],pt[2],mSize))
|
|
21
|
+
|
|
22
|
+
line_tags = []
|
|
23
|
+
for i in range(num_points):
|
|
24
|
+
start = point_tags[i]
|
|
25
|
+
end = point_tags[(i+1) % num_points]
|
|
26
|
+
line_tags.append(gmsh.model.occ.addLine(start, end))
|
|
27
|
+
loop = gmsh.model.occ.addCurveLoop(line_tags)
|
|
28
|
+
surface = gmsh.model.occ.addPlaneSurface([loop],tag=tagID)
|
|
29
|
+
gmsh.model.occ.synchronize()
|
|
30
|
+
return surface
|
|
31
|
+
|
|
32
|
+
def _dividePoints(points,mSize):
|
|
33
|
+
num_points = len(points)
|
|
34
|
+
finer_points = [[points[0]]]
|
|
35
|
+
|
|
36
|
+
for q in range(num_points):
|
|
37
|
+
s_node = points[q]
|
|
38
|
+
e_node = points[(q+1)% num_points]
|
|
39
|
+
|
|
40
|
+
dist_node = hypot(e_node[0]-s_node[0],e_node[1]-s_node[1],e_node[2]-s_node[2])
|
|
41
|
+
n_div = max(int(dist_node//mSize),1)
|
|
42
|
+
|
|
43
|
+
int_nodes = np.linspace(s_node,e_node,n_div+1)
|
|
44
|
+
finer_points.append(int_nodes[1:])
|
|
45
|
+
# print(int_nodes)
|
|
46
|
+
|
|
47
|
+
final_points = sFlatten(finer_points)[:-1]
|
|
48
|
+
num_points = len(final_points)
|
|
49
|
+
|
|
50
|
+
return final_points,num_points
|
|
51
|
+
|
|
52
|
+
def _SInterp(angle,num_points):
|
|
53
|
+
''' Angle -> Input list | Num Points -> Output length'''
|
|
54
|
+
from scipy.interpolate import interp1d , Akima1DInterpolator
|
|
55
|
+
angle = _convItem2List(angle)
|
|
56
|
+
if len(angle) == 1 :
|
|
57
|
+
angle.append(angle[0])
|
|
58
|
+
angle.append(angle[0])
|
|
59
|
+
if len(angle) == 2 :
|
|
60
|
+
angle.append(angle[-1])
|
|
61
|
+
angle[1] = (angle[0]+angle[2])*0.5
|
|
62
|
+
|
|
63
|
+
num_angle = len(angle)
|
|
64
|
+
angle_intrp_x = [0]
|
|
65
|
+
angle_intrp_y = [angle[0]]
|
|
66
|
+
for a in range(num_angle-1):
|
|
67
|
+
angle_intrp_x.append((a+1)*(num_points-1)/(num_angle-1))
|
|
68
|
+
angle_intrp_y.append(angle[a+1])
|
|
69
|
+
|
|
70
|
+
_alignment = Akima1DInterpolator(angle_intrp_x, angle_intrp_y,method='makima')
|
|
71
|
+
angle_intrp_func = interp1d(angle_intrp_x, angle_intrp_y)
|
|
72
|
+
|
|
73
|
+
angle_intrp_finalY = []
|
|
74
|
+
for i in range(num_points):
|
|
75
|
+
angle_intrp_finalY.append(_alignment(i))
|
|
76
|
+
|
|
77
|
+
return angle_intrp_finalY
|
|
78
|
+
|
|
79
|
+
def _interpolateAlignment(pointsArray,n_seg=10,deg=1,mSize=0,includePoint:bool=True,div_axis="L") -> list:
|
|
80
|
+
''' Returns point list and beta angle list'''
|
|
81
|
+
from scipy.interpolate import splev, splprep
|
|
82
|
+
pointsArray = np.array(pointsArray)
|
|
83
|
+
x_p, y_p , z_p = pointsArray[:,0] , pointsArray[:,1] , pointsArray[:,2]
|
|
84
|
+
|
|
85
|
+
if deg < 1 :
|
|
86
|
+
deg = 1
|
|
87
|
+
if deg > len(pointsArray)-1:
|
|
88
|
+
deg = len(pointsArray)-1
|
|
89
|
+
|
|
90
|
+
#-- Actual length ----
|
|
91
|
+
|
|
92
|
+
tck, u = splprep([x_p, y_p, z_p], s=0, k=deg)
|
|
93
|
+
|
|
94
|
+
u_fine = np.linspace(0, 1, 500)
|
|
95
|
+
x_den, y_den, z_den = splev(u_fine, tck)
|
|
96
|
+
|
|
97
|
+
dx = np.diff(x_den)
|
|
98
|
+
dy = np.diff(y_den)
|
|
99
|
+
dz = np.diff(z_den)
|
|
100
|
+
dl=[]
|
|
101
|
+
for i in range(len(dx)):
|
|
102
|
+
dl.append(hypot(dx[i],dy[i],dz[i]))
|
|
103
|
+
|
|
104
|
+
cum_l = np.insert(np.cumsum(dl),0,0)
|
|
105
|
+
total_l = cum_l[-1]
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
if n_seg==0 or mSize!=0:
|
|
109
|
+
n_seg=int(total_l/mSize)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
if div_axis == "X":
|
|
113
|
+
eq_x = np.linspace(x_p[0],x_p[-1],n_seg+1)
|
|
114
|
+
interp_u = np.interp(eq_x,x_den,u_fine)
|
|
115
|
+
elif div_axis == "Y":
|
|
116
|
+
eq_y = np.linspace(y_p[0],y_p[-1],n_seg+1)
|
|
117
|
+
interp_u = np.interp(eq_y,y_den,u_fine)
|
|
118
|
+
elif div_axis == "Z":
|
|
119
|
+
eq_z = np.linspace(z_p[0],z_p[-1],n_seg+1)
|
|
120
|
+
interp_u = np.interp(eq_z,z_den,u_fine)
|
|
121
|
+
else :
|
|
122
|
+
eq_len = np.linspace(0,total_l,n_seg+1)
|
|
123
|
+
interp_u = np.interp(eq_len,cum_l,u_fine)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
if includePoint:
|
|
127
|
+
interp_u = np.sort(np.append(interp_u,u[1:-1])).tolist()
|
|
128
|
+
|
|
129
|
+
eq_u = 1/n_seg # for filtering close points
|
|
130
|
+
|
|
131
|
+
new_u = []
|
|
132
|
+
skip=0
|
|
133
|
+
for i in range(len(interp_u)-1):
|
|
134
|
+
if skip == 1:
|
|
135
|
+
skip = 0
|
|
136
|
+
continue
|
|
137
|
+
if interp_u[i+1]-interp_u[i] < 0.2*eq_u:
|
|
138
|
+
if interp_u[i] in u:
|
|
139
|
+
new_u.append(interp_u[i])
|
|
140
|
+
skip=1
|
|
141
|
+
else:
|
|
142
|
+
new_u.append(interp_u[i+1])
|
|
143
|
+
skip=1
|
|
144
|
+
else:
|
|
145
|
+
new_u.append(interp_u[i])
|
|
146
|
+
new_u.append(interp_u[-1])
|
|
147
|
+
else:
|
|
148
|
+
new_u = interp_u
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
interp_x, interp_y , interp_z = splev(new_u, tck)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
align_fine_points = [ [round(x,6), round(y,6), round(z,6)] for x, y, z in zip(interp_x, interp_y , interp_z) ]
|
|
155
|
+
|
|
156
|
+
return align_fine_points
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _nodeDIST(a:Node,b:Node):
|
|
161
|
+
return round(hypot((a.X-b.X),(a.Y-b.Y),(a.Z-b.Z)),6)
|
|
162
|
+
|
|
163
|
+
def _nodeAngleVector(b:Node,a:Node):
|
|
164
|
+
|
|
165
|
+
Z_new = np.array([0.000001,0,1])
|
|
166
|
+
X_new = np.array([(a.X-b.X),(a.Y-b.Y),(a.Z-b.Z)])
|
|
167
|
+
Y_new = np.cross(Z_new, X_new)
|
|
168
|
+
|
|
169
|
+
Z_new = np.cross(X_new, Y_new) # Recomputing
|
|
170
|
+
|
|
171
|
+
X_new = X_new / (np.linalg.norm(X_new)+0.000001)
|
|
172
|
+
Y_new = Y_new / (np.linalg.norm(Y_new)+0.000001)
|
|
173
|
+
Z_new = Z_new / (np.linalg.norm(Z_new)+0.000001)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
return [X_new,Y_new,Z_new]
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _triangleAREA(a:Node,b:Node,c:Node):
|
|
180
|
+
v1 = np.array([a.X-b.X,a.Y-b.Y,a.Z-b.Z])
|
|
181
|
+
v2 = np.array([b.X-c.X,b.Y-c.Y,b.Z-c.Z])
|
|
182
|
+
mag = np.linalg.norm(np.cross(v1, v2))
|
|
183
|
+
return float(0.5 * mag) , np.cross(v1, v2)/mag
|
|
184
|
+
|
|
185
|
+
def _calcVector(deltaLocation,angle=0): # Returns normalised local X,Y,Z for line
|
|
186
|
+
Z_new = np.array([0.000001,0,1])
|
|
187
|
+
X_new = np.array(deltaLocation)
|
|
188
|
+
Y_new = np.cross(Z_new, X_new)
|
|
189
|
+
|
|
190
|
+
Z_new = np.cross(X_new, Y_new) # Recomputing
|
|
191
|
+
|
|
192
|
+
X_new = X_new / (np.linalg.norm(X_new)+0.000001)
|
|
193
|
+
Y_new = Y_new / (np.linalg.norm(Y_new)+0.000001)
|
|
194
|
+
Z_new = Z_new / (np.linalg.norm(Z_new)+0.000001)
|
|
195
|
+
|
|
196
|
+
from scipy.spatial.transform import Rotation as R
|
|
197
|
+
p_y = np.array(Y_new)
|
|
198
|
+
p_z = np.array(Z_new)
|
|
199
|
+
|
|
200
|
+
axis = np.array(X_new)
|
|
201
|
+
theta = np.deg2rad(angle) # or radians directly
|
|
202
|
+
rot = R.from_rotvec(axis * theta) # axis-angle as rotation vector
|
|
203
|
+
|
|
204
|
+
rt_y = rot.apply(p_y) # rotated point around axis through origin
|
|
205
|
+
rt_z = rot.apply(p_z)
|
|
206
|
+
|
|
207
|
+
return [X_new,rt_y,rt_z]
|
|
208
|
+
|
|
209
|
+
def _rotatePT(pt,axis,deg):
|
|
210
|
+
from scipy.spatial.transform import Rotation as R
|
|
211
|
+
p = np.array(pt)
|
|
212
|
+
axis = np.array(axis)
|
|
213
|
+
theta = np.deg2rad(deg) # or radians directly
|
|
214
|
+
rot = R.from_rotvec(axis * theta) # axis-angle as rotation vector
|
|
215
|
+
return rot.apply(p) # rotated point around axis through origin
|
|
216
|
+
|
|
217
|
+
def _pointOffset(pts,yEcc=0,zEcc=0,angle=0):
|
|
218
|
+
from ._utils import _matchArray
|
|
219
|
+
|
|
220
|
+
angle2 = _matchArray(pts,angle)
|
|
221
|
+
yEcc2 = _matchArray(pts,yEcc)
|
|
222
|
+
zEcc2 = _matchArray(pts,zEcc)
|
|
223
|
+
|
|
224
|
+
norm = []
|
|
225
|
+
norm.append(_calcVector(np.subtract(pts[1],pts[0]),angle2[0])) # first X- along vector
|
|
226
|
+
|
|
227
|
+
for i in range(len(pts)-2): # Averaged X- along vector for middle points
|
|
228
|
+
X_new1 = np.array(np.subtract(pts[i+1],pts[i]))
|
|
229
|
+
X_new2 = np.array(np.subtract(pts[i+2],pts[i+1]))
|
|
230
|
+
|
|
231
|
+
X_new1 = X_new1 / (np.linalg.norm(X_new1)+0.000001)
|
|
232
|
+
X_new2 = X_new2 / (np.linalg.norm(X_new2)+0.000001)
|
|
233
|
+
|
|
234
|
+
norm.append(_calcVector(np.add(X_new1,X_new2),angle2[i+1]))
|
|
235
|
+
|
|
236
|
+
norm.append(_calcVector(np.subtract(pts[-1],pts[-2]),angle2[-1])) # last X- along vector
|
|
237
|
+
|
|
238
|
+
# print(norm)
|
|
239
|
+
|
|
240
|
+
pt_new = []
|
|
241
|
+
for i in range(len(pts)):
|
|
242
|
+
pt_new.append(pts[i]+yEcc2[i]*norm[i][1]+zEcc2[i]*norm[i][2])
|
|
243
|
+
|
|
244
|
+
return pt_new
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _ADD(self):
|
|
248
|
+
"""
|
|
249
|
+
Adds an element to the main list. If the ID is 0, it auto-increments.
|
|
250
|
+
If the ID already exists, it replaces the existing element.
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
# ------------ ID assignment -----------------------
|
|
254
|
+
if NX.onlyNode == False :
|
|
255
|
+
id = int(self.ID)
|
|
256
|
+
# if not Element.ids:
|
|
257
|
+
# count = 1
|
|
258
|
+
# else:
|
|
259
|
+
# count = max(Element.ids) + 1
|
|
260
|
+
|
|
261
|
+
count = Element.maxID+1
|
|
262
|
+
if id == 0:
|
|
263
|
+
self.ID = count
|
|
264
|
+
Element.elements.append(self)
|
|
265
|
+
Element.ids.append(int(self.ID))
|
|
266
|
+
Element.maxID+= 1
|
|
267
|
+
elif id in Element.ids:
|
|
268
|
+
self.ID = int(id)
|
|
269
|
+
print(f'⚠️ Element with ID {id} already exists! It will be replaced.')
|
|
270
|
+
index = Element.ids.index(id)
|
|
271
|
+
Element.elements[index] = self
|
|
272
|
+
else:
|
|
273
|
+
self.ID = id
|
|
274
|
+
Element.elements.append(self)
|
|
275
|
+
Element.ids.append(int(self.ID))
|
|
276
|
+
if id > Element.maxID:
|
|
277
|
+
Element.maxID = id
|
|
278
|
+
Element.__elemDIC__[str(self.ID)] = self
|
|
279
|
+
|
|
280
|
+
# ------------ Group assignment -----------------------
|
|
281
|
+
if self._GROUP == "" :
|
|
282
|
+
pass
|
|
283
|
+
elif isinstance(self._GROUP, list):
|
|
284
|
+
for gpName in self._GROUP:
|
|
285
|
+
_add_elem_2_stGroup(self.ID,gpName)
|
|
286
|
+
for nd in self.NODE:
|
|
287
|
+
_add_node_2_stGroup(nd,gpName)
|
|
288
|
+
elif isinstance(self._GROUP, str):
|
|
289
|
+
_add_elem_2_stGroup(self.ID,self._GROUP)
|
|
290
|
+
# for nd in self.NODE:
|
|
291
|
+
_add_node_2_stGroup(self.NODE,self._GROUP)
|
|
292
|
+
else:
|
|
293
|
+
if self._GROUP == "" :
|
|
294
|
+
pass
|
|
295
|
+
elif isinstance(self._GROUP, list):
|
|
296
|
+
for gpName in self._GROUP:
|
|
297
|
+
for nd in self.NODE:
|
|
298
|
+
_add_node_2_stGroup(nd,gpName)
|
|
299
|
+
elif isinstance(self._GROUP, str):
|
|
300
|
+
for nd in self.NODE:
|
|
301
|
+
_add_node_2_stGroup(nd,self._GROUP)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _updateElem(self):
|
|
309
|
+
"""Sends a PUT request to update a single element in Midas."""
|
|
310
|
+
js2s = {'Assign': {self.ID: _Obj2JS(self)}}
|
|
311
|
+
MidasAPI('PUT', '/db/elem', js2s)
|
|
312
|
+
return js2s
|
|
313
|
+
|
|
314
|
+
def _Obj2JS(obj):
|
|
315
|
+
"""Converts a Python element object to its JSON dictionary representation."""
|
|
316
|
+
# Base attributes common to many elements
|
|
317
|
+
js = {
|
|
318
|
+
"TYPE": obj.TYPE,
|
|
319
|
+
"MATL": obj.MATL,
|
|
320
|
+
"SECT": obj.SECT,
|
|
321
|
+
"NODE": obj.NODE,
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
# Add optional attributes if they exist on the object
|
|
325
|
+
if hasattr(obj, 'ANGLE'): js["ANGLE"] = obj.ANGLE
|
|
326
|
+
if hasattr(obj, 'STYPE'): js["STYPE"] = obj.STYPE
|
|
327
|
+
|
|
328
|
+
# Handle type-specific and subtype-specific attributes
|
|
329
|
+
if obj.TYPE == 'TENSTR': # Tension/Hook/Cable
|
|
330
|
+
# Tension-only (stype=1) - can have TENS parameter
|
|
331
|
+
if obj.STYPE == 1:
|
|
332
|
+
if hasattr(obj, 'TENS'): js["TENS"] = obj.TENS
|
|
333
|
+
if hasattr(obj, 'T_LIMIT'): js["T_LIMIT"] = obj.T_LIMIT
|
|
334
|
+
if hasattr(obj, 'T_bLMT'): js["T_bLMT"] = obj.T_bLMT
|
|
335
|
+
|
|
336
|
+
# Hook (stype=2) - has NON_LEN parameter
|
|
337
|
+
elif obj.STYPE == 2:
|
|
338
|
+
if hasattr(obj, 'NON_LEN'): js["NON_LEN"] = obj.NON_LEN
|
|
339
|
+
|
|
340
|
+
# Cable (stype=3) - has CABLE, NON_LEN, and TENS parameters
|
|
341
|
+
elif obj.STYPE == 3:
|
|
342
|
+
if hasattr(obj, 'CABLE'): js["CABLE"] = obj.CABLE
|
|
343
|
+
if hasattr(obj, 'NON_LEN'): js["NON_LEN"] = obj.NON_LEN
|
|
344
|
+
if hasattr(obj, 'TENS'): js["TENS"] = obj.TENS
|
|
345
|
+
|
|
346
|
+
elif obj.TYPE == 'COMPTR': # Compression/Gap
|
|
347
|
+
# Compression-only (stype=1) - can have TENS, T_LIMIT, T_bLMT
|
|
348
|
+
if obj.STYPE == 1:
|
|
349
|
+
if hasattr(obj, 'TENS'): js["TENS"] = obj.TENS
|
|
350
|
+
if hasattr(obj, 'T_LIMIT'): js["T_LIMIT"] = obj.T_LIMIT
|
|
351
|
+
if hasattr(obj, 'T_bLMT'): js["T_bLMT"] = obj.T_bLMT
|
|
352
|
+
|
|
353
|
+
# Gap (stype=2) - has NON_LEN parameter
|
|
354
|
+
elif obj.STYPE == 2:
|
|
355
|
+
if hasattr(obj, 'NON_LEN'): js["NON_LEN"] = obj.NON_LEN
|
|
356
|
+
|
|
357
|
+
return js
|
|
358
|
+
|
|
359
|
+
def _JS2Obj(id, js):
|
|
360
|
+
"""Converts a JSON dictionary back into a Python element object during sync."""
|
|
361
|
+
elem_type = js.get('TYPE')
|
|
362
|
+
|
|
363
|
+
# Prepare arguments for constructors
|
|
364
|
+
args = {
|
|
365
|
+
'id': int(id),
|
|
366
|
+
'mat': js.get('MATL'),
|
|
367
|
+
'sect': js.get('SECT'),
|
|
368
|
+
'node': js.get('NODE'),
|
|
369
|
+
'angle': js.get('ANGLE'),
|
|
370
|
+
'stype': js.get('STYPE')
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
args['node'] = [x for x in args['node'] if x != 0]
|
|
374
|
+
nNodes = len(args['node'])
|
|
375
|
+
# Prepare individual parameters for optional/subtype-specific parameters
|
|
376
|
+
non_len = js.get('NON_LEN')
|
|
377
|
+
cable_type = js.get('CABLE')
|
|
378
|
+
tens = js.get('TENS')
|
|
379
|
+
t_limit = js.get('T_LIMIT')
|
|
380
|
+
|
|
381
|
+
if elem_type == 'BEAM':
|
|
382
|
+
Element.Beam(args['node'][0], args['node'][1], args['mat'], args['sect'], args['angle'], '', args['id'])
|
|
383
|
+
elif elem_type == 'TRUSS':
|
|
384
|
+
Element.Truss(args['node'][0], args['node'][1], args['mat'], args['sect'], args['angle'],'', args['id'])
|
|
385
|
+
elif elem_type == 'PLATE':
|
|
386
|
+
Element.Plate(args['node'][:nNodes], args['stype'], args['mat'], args['sect'], args['angle'], '', args['id'])
|
|
387
|
+
elif elem_type == 'TENSTR':
|
|
388
|
+
Element.Tension(args['node'][0], args['node'][1], args['stype'], args['mat'], args['sect'], args['angle'], '', args['id'], non_len, cable_type, tens, t_limit)
|
|
389
|
+
elif elem_type == 'COMPTR':
|
|
390
|
+
Element.Compression(args['node'][0], args['node'][1], args['stype'], args['mat'], args['sect'], args['angle'], '', args['id'], tens, t_limit, non_len)
|
|
391
|
+
elif elem_type == 'SOLID':
|
|
392
|
+
Element.Solid(nodes=args['node'][:nNodes], mat=args['mat'], sect=args['sect'],group='', id=args['id'])
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
class _helperELEM:
|
|
396
|
+
ID, TYPE, MATL,SECT,NODE,ANGLE,LENGTH,STYPE,AREA,NORMAL,CENTER,LOCALX = 0,0,0,0,0,0,0,0,0,0,0,0
|
|
397
|
+
class _common:
|
|
398
|
+
"""Common base class for all element types."""
|
|
399
|
+
def __str__(self):
|
|
400
|
+
return str(f'ID = {self.ID} \nJSON : {_Obj2JS(self)}\n')
|
|
401
|
+
|
|
402
|
+
def update(self):
|
|
403
|
+
return _updateElem(self)
|
|
404
|
+
|
|
405
|
+
# --- Main Element Class ---
|
|
406
|
+
class Element():
|
|
407
|
+
"""
|
|
408
|
+
Main class to create and manage structural elements like Beams, Trusses,
|
|
409
|
+
Plates, Tension/Compression-only elements, and Solids.
|
|
410
|
+
"""
|
|
411
|
+
elements:list[_helperELEM] = []
|
|
412
|
+
ids:list[int] = []
|
|
413
|
+
maxID:int = 0
|
|
414
|
+
__elemDIC__ = {}
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
lastLoc = (0,0,0) #Last Location created using Beam element
|
|
418
|
+
'''Last Node Location created by Beam / Truss element - (x,y,z)'''
|
|
419
|
+
|
|
420
|
+
@classmethod
|
|
421
|
+
def json(cls):
|
|
422
|
+
json_data = {"Assign": {}}
|
|
423
|
+
for elem in cls.elements:
|
|
424
|
+
js = _Obj2JS(elem)
|
|
425
|
+
json_data["Assign"][elem.ID] = js
|
|
426
|
+
return json_data
|
|
427
|
+
|
|
428
|
+
@classmethod
|
|
429
|
+
def create(cls):
|
|
430
|
+
if cls.elements:
|
|
431
|
+
MidasAPI("PUT", "/db/ELEM", Element.json())
|
|
432
|
+
|
|
433
|
+
@staticmethod
|
|
434
|
+
def get():
|
|
435
|
+
return MidasAPI("GET", "/db/ELEM")
|
|
436
|
+
|
|
437
|
+
@staticmethod
|
|
438
|
+
def sync():
|
|
439
|
+
a = Element.get()
|
|
440
|
+
if a and 'ELEM' in a and a['ELEM']:
|
|
441
|
+
Element.elements = []
|
|
442
|
+
Element.ids = []
|
|
443
|
+
Element.__elemDIC__={}
|
|
444
|
+
for elem_id, data in a['ELEM'].items():
|
|
445
|
+
_JS2Obj(elem_id, data)
|
|
446
|
+
|
|
447
|
+
@staticmethod
|
|
448
|
+
def delete():
|
|
449
|
+
MidasAPI("DELETE", "/db/ELEM")
|
|
450
|
+
Element.clear()
|
|
451
|
+
|
|
452
|
+
@staticmethod
|
|
453
|
+
def clear():
|
|
454
|
+
Element.elements = []
|
|
455
|
+
Element.ids = []
|
|
456
|
+
Element.__elemDIC__={}
|
|
457
|
+
|
|
458
|
+
# --- Element Type Subclasses ---
|
|
459
|
+
|
|
460
|
+
class Beam(_common):
|
|
461
|
+
|
|
462
|
+
def __init__(self, i: int, j: int, mat: int = 1, sect: int = 1, angle: float = 0, group:str = "" , id: int = None,bLocalAxis:bool=False):
|
|
463
|
+
"""
|
|
464
|
+
Creates a BEAM element for frame analysis.
|
|
465
|
+
|
|
466
|
+
Parameters:
|
|
467
|
+
i: Start node ID
|
|
468
|
+
j: End node ID
|
|
469
|
+
mat: Material property number (default 1)
|
|
470
|
+
sect: Section property number (default 1)
|
|
471
|
+
angle: Beta angle for section orientation in degrees (default 0.0)
|
|
472
|
+
group: Structure group of the element (str or list; 'SG1' or ['SG1','SG2'])
|
|
473
|
+
id: Element ID (default 0 for auto-increment)
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
Examples:
|
|
477
|
+
```python
|
|
478
|
+
# Simple beam with default properties
|
|
479
|
+
Element.Beam(1, 2)
|
|
480
|
+
|
|
481
|
+
# Beam with specific material and section
|
|
482
|
+
Element.Beam(1, 2, mat=2, sect=3)
|
|
483
|
+
|
|
484
|
+
# Beam with 90° rotation (strong axis vertical)
|
|
485
|
+
Element.Beam(1, 2, mat=1, sect=1, angle=90.0)
|
|
486
|
+
|
|
487
|
+
# Beam with specific ID
|
|
488
|
+
Element.Beam(1, 2, mat=1, sect=1, angle=0.0, id=100)
|
|
489
|
+
```
|
|
490
|
+
"""
|
|
491
|
+
if id == None: id =0
|
|
492
|
+
self.ID = id
|
|
493
|
+
self.TYPE = 'BEAM'
|
|
494
|
+
self.MATL = mat
|
|
495
|
+
self.SECT = sect
|
|
496
|
+
self.NODE = [i, j]
|
|
497
|
+
self.ANGLE = angle
|
|
498
|
+
self._GROUP = group
|
|
499
|
+
|
|
500
|
+
_n1 = nodeByID(i)
|
|
501
|
+
_n2 = nodeByID(j)
|
|
502
|
+
self.LENGTH = _nodeDIST(_n1,_n2)
|
|
503
|
+
self.CENTER = np.average([_n1.LOC,_n2.LOC],0)
|
|
504
|
+
_dirVect = np.subtract(_n2.LOC,_n1.LOC)
|
|
505
|
+
self.LOCALX = _dirVect/(np.linalg.norm(_dirVect))
|
|
506
|
+
|
|
507
|
+
if bLocalAxis:
|
|
508
|
+
_tempAngle = _nodeAngleVector(_n1,_n2)
|
|
509
|
+
_n1.AXIS = np.add(_n1.AXIS,_tempAngle)
|
|
510
|
+
_n2.AXIS = np.add(_n2.AXIS,_tempAngle)
|
|
511
|
+
|
|
512
|
+
_norm1 = np.linalg.norm(_n1.AXIS ,axis=1,keepdims=True)
|
|
513
|
+
_n1.AXIS = _n1.AXIS /_norm1
|
|
514
|
+
|
|
515
|
+
_norm2 = np.linalg.norm(_n2.AXIS ,axis=1,keepdims=True)
|
|
516
|
+
_n2.AXIS = _n2.AXIS /_norm2
|
|
517
|
+
|
|
518
|
+
Element.lastLoc = (_n2.X,_n2.Y,_n2.Z)
|
|
519
|
+
|
|
520
|
+
_ADD(self)
|
|
521
|
+
|
|
522
|
+
@staticmethod
|
|
523
|
+
def SDL(s_loc:list,dir:list,l:float,n:int=1,mat:int=1,sect:int=1,angle:float=0, group:str = "" , id: int = None,bLocalAxis:bool=False): #CHANGE TO TUPLE
|
|
524
|
+
if id == None: id =0
|
|
525
|
+
if isinstance(s_loc,Node):
|
|
526
|
+
s_loc = (s_loc.X,s_loc.Y,s_loc.Z)
|
|
527
|
+
|
|
528
|
+
beam_nodes =[]
|
|
529
|
+
beam_obj = []
|
|
530
|
+
s_locc = np.array(s_loc)
|
|
531
|
+
unit_vec = np.array(dir)/np.linalg.norm(dir)
|
|
532
|
+
|
|
533
|
+
for i in range(n+1):
|
|
534
|
+
locc = s_locc+i*l*unit_vec/n
|
|
535
|
+
Enode=Node(locc[0].item(),locc[1].item(),locc[2].item())
|
|
536
|
+
beam_nodes.append(Enode.ID)
|
|
537
|
+
Element.lastLoc = (locc[0].item(),locc[1].item(),locc[2].item())
|
|
538
|
+
for i in range(n):
|
|
539
|
+
if id == 0 : id_new = 0
|
|
540
|
+
else: id_new = id+i
|
|
541
|
+
beam_obj.append(Element.Beam(beam_nodes[i],beam_nodes[i+1],mat,sect,angle,group,id_new,bLocalAxis))
|
|
542
|
+
|
|
543
|
+
return beam_obj
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
@staticmethod
|
|
547
|
+
def SE(s_loc:list,e_loc:list,n:int=1,mat:int=1,sect:int=1,angle:float=0, group:str = "" , id: int = None,bLocalAxis:bool=False):
|
|
548
|
+
if id == None: id =0
|
|
549
|
+
if isinstance(s_loc,Node):
|
|
550
|
+
s_loc = (s_loc.X,s_loc.Y,s_loc.Z)
|
|
551
|
+
if isinstance(e_loc,Node):
|
|
552
|
+
e_loc = (e_loc.X,e_loc.Y,e_loc.Z)
|
|
553
|
+
|
|
554
|
+
beam_nodes =[]
|
|
555
|
+
beam_obj = []
|
|
556
|
+
i_loc = np.linspace(s_loc,e_loc,n+1)
|
|
557
|
+
for i in range(n+1):
|
|
558
|
+
Enode=Node(i_loc[i][0].item(),i_loc[i][1].item(),i_loc[i][2].item())
|
|
559
|
+
beam_nodes.append(Enode.ID)
|
|
560
|
+
for i in range(n):
|
|
561
|
+
if id == 0 : id_new = 0
|
|
562
|
+
else: id_new = id+i
|
|
563
|
+
beam_obj.append(Element.Beam(beam_nodes[i],beam_nodes[i+1],mat,sect,angle,group,id_new,bLocalAxis))
|
|
564
|
+
|
|
565
|
+
return beam_obj
|
|
566
|
+
|
|
567
|
+
@staticmethod
|
|
568
|
+
def PLine(points_loc:list,n_div:int=0,deg:int=1,includePoint:bool=False,mat:int=1,sect:int=1,angle:float=0, group:str = "" , id: int = None,bLocalAxis:bool=False,div_axis:Literal['X','Y','Z','L']="L"):
|
|
569
|
+
'''
|
|
570
|
+
angle : float of list(float)
|
|
571
|
+
'''
|
|
572
|
+
if id == None: id =0
|
|
573
|
+
beam_nodes =[]
|
|
574
|
+
beam_obj = []
|
|
575
|
+
if n_div == 0 :
|
|
576
|
+
i_loc = points_loc
|
|
577
|
+
else:
|
|
578
|
+
i_loc = _interpolateAlignment(points_loc,n_div,deg,0,includePoint,div_axis)
|
|
579
|
+
|
|
580
|
+
num_points = len(i_loc)
|
|
581
|
+
angle_intrp_finalY = _SInterp(angle,num_points-1) #Beta Angle to be applied to Elements So, n-1
|
|
582
|
+
|
|
583
|
+
for i in i_loc:
|
|
584
|
+
Enode=Node(i[0],i[1],i[2])
|
|
585
|
+
beam_nodes.append(Enode.ID)
|
|
586
|
+
for i in range(len(i_loc)-1):
|
|
587
|
+
if id == 0 : id_new = 0
|
|
588
|
+
else: id_new = id+i
|
|
589
|
+
beam_obj.append(Element.Beam(beam_nodes[i],beam_nodes[i+1],mat,sect,angle_intrp_finalY[i].item(),group,id_new,bLocalAxis))
|
|
590
|
+
|
|
591
|
+
return beam_obj
|
|
592
|
+
|
|
593
|
+
@staticmethod
|
|
594
|
+
def PLine2(points_loc:list,n_div:int=0,deg:int=1,includePoint:bool=False,mat:int=1,sect:int=1,angle:list[float]=0, group:str = "" , id: int = None,bLocalAxis:bool=False,div_axis:Literal['X','Y','Z','L']="L",yEcc:list[float]=0,zEcc:list[float]=0,bAngleInEcc:bool=True):
|
|
595
|
+
'''
|
|
596
|
+
Creates a polyline with Eccentricity considering the beta angle provided
|
|
597
|
+
angle , yEcc , zEcc : float or list(float)
|
|
598
|
+
[0,10] -> Angle at start = 0 | Angle at end = 10
|
|
599
|
+
[0,10,0] -> Angle at start = 0 | Angle at mid = 10 | Angle at end = 0
|
|
600
|
+
Inbetween values are **MAKIMA 1D** interpolated. (not cubic)
|
|
601
|
+
'''
|
|
602
|
+
from ._utils import _matchArray
|
|
603
|
+
if id == None: id =0
|
|
604
|
+
beam_nodes =[]
|
|
605
|
+
beam_obj = []
|
|
606
|
+
if n_div == 0 :
|
|
607
|
+
i_loc = points_loc
|
|
608
|
+
else:
|
|
609
|
+
i_loc = _interpolateAlignment(points_loc,n_div,deg,0,includePoint,div_axis)
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
num_points = len(i_loc)
|
|
613
|
+
if bAngleInEcc:
|
|
614
|
+
angle_intrp_Ecc = _SInterp(angle,num_points)
|
|
615
|
+
else:
|
|
616
|
+
angle_intrp_Ecc = _matchArray(i_loc,[0])
|
|
617
|
+
angle_intrp_finalY = _SInterp(angle,num_points-1) #Beta Angle to be applied to Elements So, n-1
|
|
618
|
+
|
|
619
|
+
yEcc_intrp = _SInterp(yEcc,num_points)
|
|
620
|
+
zEcc_intrp = _SInterp(zEcc,num_points)
|
|
621
|
+
|
|
622
|
+
i_loc2 = _pointOffset(i_loc,yEcc_intrp,zEcc_intrp,angle_intrp_Ecc)
|
|
623
|
+
for i in i_loc2:
|
|
624
|
+
Enode=Node(i[0],i[1],i[2])
|
|
625
|
+
beam_nodes.append(Enode.ID)
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
for i in range(len(i_loc2)-1):
|
|
629
|
+
if id == 0 : id_new = 0
|
|
630
|
+
else: id_new = id+i
|
|
631
|
+
beam_obj.append(Element.Beam(beam_nodes[i],beam_nodes[i+1],mat,sect,angle_intrp_finalY[i].item(),group,id_new,bLocalAxis))
|
|
632
|
+
|
|
633
|
+
return beam_obj
|
|
634
|
+
|
|
635
|
+
class Truss(_common):
|
|
636
|
+
def __init__(self, i: int, j: int, mat: int = 1, sect: int = 1, angle: float = 0, group = "" , id: int = None):
|
|
637
|
+
"""
|
|
638
|
+
Creates a TRUSS element
|
|
639
|
+
|
|
640
|
+
Parameters:
|
|
641
|
+
i: Start node ID
|
|
642
|
+
j: End node ID
|
|
643
|
+
mat: Material property number (default 1)
|
|
644
|
+
sect: Section property number (default 1)
|
|
645
|
+
angle: Beta angle for section orientation in degrees (default 0.0)
|
|
646
|
+
group: Structure group of the element (str or list; 'SG1' or ['SG1','SG2'])
|
|
647
|
+
id: Element ID (default 0 for auto-increment)
|
|
648
|
+
|
|
649
|
+
Examples:
|
|
650
|
+
```python
|
|
651
|
+
# Simple truss member
|
|
652
|
+
Element.Truss(1, 2)
|
|
653
|
+
|
|
654
|
+
# Truss with specific material and section
|
|
655
|
+
Element.Truss(1, 2, mat=3, sect=2)
|
|
656
|
+
|
|
657
|
+
# Diagonal truss member
|
|
658
|
+
Element.Truss(3, 4, mat=1, sect=1, id=50)
|
|
659
|
+
```
|
|
660
|
+
"""
|
|
661
|
+
if id == None: id =0
|
|
662
|
+
self.ID = id
|
|
663
|
+
self.TYPE = 'TRUSS'
|
|
664
|
+
self.MATL = mat
|
|
665
|
+
self.SECT = sect
|
|
666
|
+
self.NODE = [i, j]
|
|
667
|
+
self.ANGLE = angle
|
|
668
|
+
self._GROUP = group
|
|
669
|
+
_n1 = nodeByID(i)
|
|
670
|
+
_n2 = nodeByID(j)
|
|
671
|
+
self.LENGTH = _nodeDIST(_n1,_n2)
|
|
672
|
+
self.CENTER = np.average([_n1.LOC,_n2.LOC],0)
|
|
673
|
+
_dirVect = np.subtract(_n2.LOC,_n1.LOC)
|
|
674
|
+
self.LOCALX = _dirVect/(np.linalg.norm(_dirVect))
|
|
675
|
+
|
|
676
|
+
Element.lastLoc = (_n2.X,_n2.Y,_n2.Z)
|
|
677
|
+
_ADD(self)
|
|
678
|
+
|
|
679
|
+
@staticmethod
|
|
680
|
+
def SDL(s_loc:list,dir:list,l:float,n:int=1,mat:int=1,sect:int=1,angle:float=0, group = "" , id: int = None):
|
|
681
|
+
if id == None: id =0
|
|
682
|
+
if isinstance(s_loc,Node):
|
|
683
|
+
s_loc = (s_loc.X,s_loc.Y,s_loc.Z)
|
|
684
|
+
|
|
685
|
+
beam_nodes =[]
|
|
686
|
+
beam_obj =[]
|
|
687
|
+
s_locc = np.array(s_loc)
|
|
688
|
+
unit_vec = np.array(dir)/np.linalg.norm(dir)
|
|
689
|
+
|
|
690
|
+
for i in range(n+1):
|
|
691
|
+
locc = s_locc+i*l*unit_vec/n
|
|
692
|
+
Enode=Node(locc[0].item(),locc[1].item(),locc[2].item())
|
|
693
|
+
beam_nodes.append(Enode.ID)
|
|
694
|
+
|
|
695
|
+
for i in range(n):
|
|
696
|
+
if id == 0 : id_new = 0
|
|
697
|
+
else: id_new = id+i
|
|
698
|
+
beam_obj.append(Element.Truss(beam_nodes[i],beam_nodes[i+1],mat,sect,angle,group,id_new))
|
|
699
|
+
|
|
700
|
+
return beam_obj
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
@staticmethod
|
|
704
|
+
def SE(s_loc:list,e_loc:list,n:int=1,mat:int=1,sect:int=1,angle:float=0, group = "" , id: int = None):
|
|
705
|
+
if id == None: id =0
|
|
706
|
+
if isinstance(s_loc,Node):
|
|
707
|
+
s_loc = (s_loc.X,s_loc.Y,s_loc.Z)
|
|
708
|
+
if isinstance(e_loc,Node):
|
|
709
|
+
s_loc = (e_loc.X,e_loc.Y,e_loc.Z)
|
|
710
|
+
|
|
711
|
+
beam_nodes =[]
|
|
712
|
+
beam_obj = []
|
|
713
|
+
i_loc = np.linspace(s_loc,e_loc,n+1)
|
|
714
|
+
for i in range(n+1):
|
|
715
|
+
Enode=Node(i_loc[i][0].item(),i_loc[i][1].item(),i_loc[i][2].item())
|
|
716
|
+
beam_nodes.append(Enode.ID)
|
|
717
|
+
|
|
718
|
+
for i in range(n):
|
|
719
|
+
if id == 0 : id_new = 0
|
|
720
|
+
else: id_new = id+i
|
|
721
|
+
beam_obj.append(Element.Truss(beam_nodes[i],beam_nodes[i+1],mat,sect,angle,group,id_new))
|
|
722
|
+
|
|
723
|
+
return beam_obj
|
|
724
|
+
|
|
725
|
+
class Plate(_common):
|
|
726
|
+
def __init__(self, nodes: list, stype: int = 1, mat: int = 1, sect: int = 1, angle: float = 0, group = "" , id: int = None):
|
|
727
|
+
"""
|
|
728
|
+
Creates a PLATE element.
|
|
729
|
+
|
|
730
|
+
Parameters:
|
|
731
|
+
nodes: List of node IDs [n1, n2, n3] for triangular or [n1, n2, n3, n4] for quadrilateral
|
|
732
|
+
stype: Plate subtype (1=Thick plate, 2=Thin plate, 3=With drilling DOF) (default 1)
|
|
733
|
+
mat: Material property number (default 1)
|
|
734
|
+
sect: Section (thickness) property number (default 1)
|
|
735
|
+
angle: Material angle for orthotropic materials in degrees (default 0.0)
|
|
736
|
+
group: Structure group of the element (str or list; 'SG1' or ['SG1','SG2'])
|
|
737
|
+
id: Element ID (default 0 for auto-increment)
|
|
738
|
+
|
|
739
|
+
Examples:
|
|
740
|
+
```python
|
|
741
|
+
# Triangular thick plate
|
|
742
|
+
Element.Plate([1, 2, 3], stype=1, mat=1, sect=1)
|
|
743
|
+
|
|
744
|
+
# Quadrilateral thin plate
|
|
745
|
+
Element.Plate([1, 2, 3, 4], stype=2, mat=2, sect=1)
|
|
746
|
+
|
|
747
|
+
# Plate with drilling DOF for shell analysis
|
|
748
|
+
Element.Plate([5, 6, 7, 8], stype=3, mat=1, sect=2, angle=45.0)
|
|
749
|
+
```
|
|
750
|
+
"""
|
|
751
|
+
if id == None: id =0
|
|
752
|
+
self.ID = id
|
|
753
|
+
self.TYPE = 'PLATE'
|
|
754
|
+
self.MATL = mat
|
|
755
|
+
self.SECT = sect
|
|
756
|
+
|
|
757
|
+
self.ANGLE = angle
|
|
758
|
+
self.STYPE = stype
|
|
759
|
+
self._GROUP = group
|
|
760
|
+
|
|
761
|
+
uniq_nodes = list(dict.fromkeys(nodes))
|
|
762
|
+
self._NPOINT=len(uniq_nodes)
|
|
763
|
+
if len(uniq_nodes)==3:
|
|
764
|
+
self.NODE = uniq_nodes
|
|
765
|
+
_n1 = nodeByID(uniq_nodes[0])
|
|
766
|
+
_n2 = nodeByID(uniq_nodes[1])
|
|
767
|
+
_n3 = nodeByID(uniq_nodes[2])
|
|
768
|
+
self.CENTER = np.average([_n1.LOC,_n2.LOC,_n3.LOC],0)
|
|
769
|
+
self.AREA,self.NORMAL = _triangleAREA(_n1,_n2,_n3)
|
|
770
|
+
elif len(uniq_nodes)==4:
|
|
771
|
+
self.NODE = nodes
|
|
772
|
+
_n1 = nodeByID(uniq_nodes[0])
|
|
773
|
+
_n2 = nodeByID(uniq_nodes[1])
|
|
774
|
+
_n3 = nodeByID(uniq_nodes[2])
|
|
775
|
+
_n4 = nodeByID(uniq_nodes[3])
|
|
776
|
+
a1 , n1 = _triangleAREA(_n1,_n2,_n3)
|
|
777
|
+
a2 , n2 = _triangleAREA(_n3,_n4,_n1)
|
|
778
|
+
self.AREA = a1+a2
|
|
779
|
+
self.NORMAL = (n1+n2)/np.linalg.norm((n1+n2+0.000001))
|
|
780
|
+
self.CENTER = np.average([_n1.LOC,_n2.LOC,_n3.LOC,_n4.LOC],0)
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
|
|
784
|
+
_ADD(self)
|
|
785
|
+
|
|
786
|
+
@staticmethod
|
|
787
|
+
def fromPoints(points: list, meshSize:float=1.0,meshType:_meshType='Tri', innerPoints:list=None,stype: int = 1, mat: int = 1, sect: int = 1, angle: float = 0, group = "" , id: int = None): #CHANGE TO TUPLE
|
|
788
|
+
# INPUTS POINTS and create a triangular/quad meshing with given mesh size | If meshSize = 0 , half of shortest length will be taken as mesh size
|
|
789
|
+
|
|
790
|
+
bHole = False
|
|
791
|
+
import gmsh
|
|
792
|
+
gmsh.initialize()
|
|
793
|
+
gmsh.option.setNumber("General.Terminal", 0)
|
|
794
|
+
|
|
795
|
+
surface_Main = _createSurface(points,meshSize,1)
|
|
796
|
+
if innerPoints:
|
|
797
|
+
surface_Hole = _createSurface(innerPoints,meshSize,2)
|
|
798
|
+
surface_Final = gmsh.model.occ.cut([(2,1)], [(2,2)], removeObject=True, removeTool=True)
|
|
799
|
+
bHole = True
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
gmsh.model.occ.synchronize()
|
|
803
|
+
|
|
804
|
+
if meshType == 'Quad':
|
|
805
|
+
if not bHole:
|
|
806
|
+
gmsh.option.setNumber("Mesh.Algorithm", 11) # WITHOUT HOLE
|
|
807
|
+
gmsh.option.setNumber("Mesh.MeshSizeMin", 2*meshSize)
|
|
808
|
+
|
|
809
|
+
else:
|
|
810
|
+
gmsh.option.setNumber("Mesh.Algorithm", 1) # WITH HOLE
|
|
811
|
+
gmsh.option.setNumber("Mesh.RecombinationAlgorithm", 2)
|
|
812
|
+
gmsh.option.setNumber("Mesh.RecombineAll", 1)
|
|
813
|
+
gmsh.option.setNumber("Mesh.MeshSizeMin", 2*meshSize)
|
|
814
|
+
else:
|
|
815
|
+
gmsh.option.setNumber("Mesh.Algorithm", 1)
|
|
816
|
+
gmsh.option.setNumber("Mesh.MeshSizeMin", 1.5*meshSize)
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
gmsh.option.setNumber("Mesh.Smoothing", 3)
|
|
820
|
+
gmsh.model.mesh.generate(2)
|
|
821
|
+
|
|
822
|
+
_, node_coords, _ = gmsh.model.mesh.getNodes()
|
|
823
|
+
nodes = np.array(node_coords).reshape(-1, 3) # (N, 3) array
|
|
824
|
+
|
|
825
|
+
_, _, elemNodeTags = gmsh.model.mesh.getElements(2)
|
|
826
|
+
if meshType == 'Quad':
|
|
827
|
+
elemNODE = np.array(elemNodeTags).reshape(-1, 4)
|
|
828
|
+
else:
|
|
829
|
+
elemNODE = np.array(elemNodeTags).reshape(-1, 3)
|
|
830
|
+
|
|
831
|
+
# gmsh.fltk.run()
|
|
832
|
+
gmsh.finalize()
|
|
833
|
+
|
|
834
|
+
nID_list = []
|
|
835
|
+
for nd in nodes:
|
|
836
|
+
nID_list.append(Node(nd[0],nd[1],nd[2]).ID)
|
|
837
|
+
|
|
838
|
+
plate_obj = []
|
|
839
|
+
for elmNd in elemNODE:
|
|
840
|
+
plate_obj.append(Element.Plate([nID_list[int(x)-1] for x in elmNd],stype,mat,sect,angle,group,id))
|
|
841
|
+
|
|
842
|
+
return plate_obj
|
|
843
|
+
|
|
844
|
+
@staticmethod
|
|
845
|
+
def loftGroups(strGroups: list, stype: int = 1, mat: int = 1, sect: int = 1, angle: float = 0, group = "" , id: int = None,nDiv:int=1,bClose:bool=False): #CHANGE TO TUPLE
|
|
846
|
+
# INPUTS 2 or more structure groups to create rectangular plates between the nodes | No. of nodes should be same in the Str Group
|
|
847
|
+
"""
|
|
848
|
+
INPUTS 2 or more structure groups to create rectangular plates between the nodes
|
|
849
|
+
No. of nodes should be same in the Str Group
|
|
850
|
+
"""
|
|
851
|
+
if id == None: id =0
|
|
852
|
+
n_groups = len(strGroups)
|
|
853
|
+
if n_groups < 2 :
|
|
854
|
+
print("⚠️ No. of structure groups in Plate.loftGroups in less than 2")
|
|
855
|
+
return False
|
|
856
|
+
plate_obj = []
|
|
857
|
+
for ng in range(n_groups-1):
|
|
858
|
+
nID_A = nodesInGroup(strGroups[ng])
|
|
859
|
+
nID_B = nodesInGroup(strGroups[ng+1])
|
|
860
|
+
if bClose:
|
|
861
|
+
nID_A.append(nID_A[0])
|
|
862
|
+
nID_B.append(nID_B[0])
|
|
863
|
+
|
|
864
|
+
max_len = max(len(nID_A),len(nID_B))
|
|
865
|
+
if max_len < 2 :
|
|
866
|
+
print("⚠️ No. of nodes in Plate.loftGroups in less than 2")
|
|
867
|
+
return False
|
|
868
|
+
|
|
869
|
+
nID_A , nID_B = _longestList(nID_A , nID_B)
|
|
870
|
+
|
|
871
|
+
if nDiv == 1 :
|
|
872
|
+
for i in range(max_len-1):
|
|
873
|
+
pt_array = [nID_A[i],nID_B[i],nID_B[i+1],nID_A[i+1]]
|
|
874
|
+
plate_obj.append(Element.Plate(pt_array,stype,mat,sect,angle,group,id))
|
|
875
|
+
if nDiv > 1 :
|
|
876
|
+
nID_dic = {}
|
|
877
|
+
for j in range(nDiv+1):
|
|
878
|
+
nID_dic[j] = []
|
|
879
|
+
nID_dic[0] = nID_A
|
|
880
|
+
nID_dic[nDiv] = nID_B
|
|
881
|
+
for i in range(max_len):
|
|
882
|
+
loc0= nodeByID(nID_A[i]).LOC
|
|
883
|
+
loc1 = nodeByID(nID_B[i]).LOC
|
|
884
|
+
int_points = np.linspace(loc0,loc1,nDiv+1)
|
|
885
|
+
|
|
886
|
+
for j in range(nDiv-1):
|
|
887
|
+
nID_dic[j+1].append(Node(int_points[j+1][0],int_points[j+1][1],int_points[j+1][2]).ID)
|
|
888
|
+
|
|
889
|
+
for q in range(nDiv):
|
|
890
|
+
for i in range(max_len-1):
|
|
891
|
+
pt_array = [nID_dic[q][i],nID_dic[q+1][i],nID_dic[q+1][i+1],nID_dic[q][i+1]]
|
|
892
|
+
plate_obj.append(Element.Plate(pt_array,stype,mat,sect,angle,group,id))
|
|
893
|
+
|
|
894
|
+
return plate_obj
|
|
895
|
+
|
|
896
|
+
@staticmethod
|
|
897
|
+
def extrude(points: list,dir:list,nDiv:int=1,bClose:bool=False,inpType='XYZ', stype: int = 1, mat: int = 1, sect: int = 1, angle: float = 0, group = "" , id: int = None): #CHANGE TO TUPLE
|
|
898
|
+
# INPUTS 2 or more structure groups to create rectangular plates between the nodes | No. of nodes should be same in the Str Group
|
|
899
|
+
"""
|
|
900
|
+
INPUTS 2 or more structure groups to create rectangular plates between the nodes
|
|
901
|
+
No. of nodes should be same in the Str Group
|
|
902
|
+
"""
|
|
903
|
+
if id == None: id =0
|
|
904
|
+
nID_A = []
|
|
905
|
+
nID_B = []
|
|
906
|
+
|
|
907
|
+
if inpType == 'XYZ':
|
|
908
|
+
|
|
909
|
+
f_pt = np.add(points,dir)
|
|
910
|
+
|
|
911
|
+
for i,pt in enumerate(points):
|
|
912
|
+
nID_A.append(Node(pt[0],pt[1],pt[2]).ID)
|
|
913
|
+
nID_B.append(Node(f_pt[i][0],f_pt[i][1],f_pt[i][2]).ID)
|
|
914
|
+
if inpType == 'ID':
|
|
915
|
+
nID_A = points
|
|
916
|
+
pts_loc = [nodeByID(pt).LOC for pt in points]
|
|
917
|
+
|
|
918
|
+
f_pt = np.add(pts_loc,dir)
|
|
919
|
+
|
|
920
|
+
for i in range(len(points)):
|
|
921
|
+
nID_B.append(Node(f_pt[i][0],f_pt[i][1],f_pt[i][2]).ID)
|
|
922
|
+
|
|
923
|
+
if inpType == 'NODE':
|
|
924
|
+
nID_A = [pt.ID for pt in points]
|
|
925
|
+
pts_loc = [pt.LOC for pt in points]
|
|
926
|
+
|
|
927
|
+
f_pt = np.add(pts_loc,dir)
|
|
928
|
+
|
|
929
|
+
for i in range(len(points)):
|
|
930
|
+
nID_B.append(Node(f_pt[i][0],f_pt[i][1],f_pt[i][2]).ID)
|
|
931
|
+
|
|
932
|
+
|
|
933
|
+
if bClose:
|
|
934
|
+
nID_A.append(nID_A[0])
|
|
935
|
+
nID_B.append(nID_B[0])
|
|
936
|
+
|
|
937
|
+
max_len = len(nID_B)
|
|
938
|
+
|
|
939
|
+
plate_obj = []
|
|
940
|
+
if nDiv == 1 :
|
|
941
|
+
for i in range(max_len-1):
|
|
942
|
+
pt_array = [nID_A[i],nID_B[i],nID_B[i+1],nID_A[i+1]]
|
|
943
|
+
plate_obj.append(Element.Plate(pt_array,stype,mat,sect,angle,group,id))
|
|
944
|
+
if nDiv > 1 :
|
|
945
|
+
nID_dic = {}
|
|
946
|
+
for j in range(nDiv+1):
|
|
947
|
+
nID_dic[j] = []
|
|
948
|
+
nID_dic[0] = nID_A
|
|
949
|
+
nID_dic[nDiv] = nID_B
|
|
950
|
+
for i in range(max_len):
|
|
951
|
+
loc0= nodeByID(nID_A[i]).LOC
|
|
952
|
+
loc1 = nodeByID(nID_B[i]).LOC
|
|
953
|
+
int_points = np.linspace(loc0,loc1,nDiv+1)
|
|
954
|
+
|
|
955
|
+
for j in range(nDiv-1):
|
|
956
|
+
nID_dic[j+1].append(Node(int_points[j+1][0],int_points[j+1][1],int_points[j+1][2]).ID)
|
|
957
|
+
|
|
958
|
+
for q in range(nDiv):
|
|
959
|
+
for i in range(max_len-1):
|
|
960
|
+
pt_array = [nID_dic[q][i],nID_dic[q+1][i],nID_dic[q+1][i+1],nID_dic[q][i+1]]
|
|
961
|
+
plate_obj.append(Element.Plate(pt_array,stype,mat,sect,angle,group,id))
|
|
962
|
+
|
|
963
|
+
return plate_obj
|
|
964
|
+
|
|
965
|
+
class Tension(_common):
|
|
966
|
+
def __init__(self, i: int, j: int, stype: int, mat: int = 1, sect: int = 1, angle: float = 0, group = "" , id: int = None, non_len: float = None, cable_type: int = None, tens: float = None, t_limit: float = None):
|
|
967
|
+
"""
|
|
968
|
+
Creates a TENSTR (Tension-only) element.
|
|
969
|
+
|
|
970
|
+
Parameters:
|
|
971
|
+
i: Start node ID
|
|
972
|
+
j: End node ID
|
|
973
|
+
stype: Tension element subtype (1=Tension-only, 2=Hook, 3=Cable)
|
|
974
|
+
mat: Material property number (default 1)
|
|
975
|
+
sect: Section property number (default 1)
|
|
976
|
+
angle: Beta angle for section orientation in degrees (default 0.0)
|
|
977
|
+
group: Structure group of the element (str or list; 'SG1' or ['SG1','SG2'])
|
|
978
|
+
id: Element ID (default 0 for auto-increment)
|
|
979
|
+
non_len: Non-linear length parameter for Hook/Cable (default None)
|
|
980
|
+
cable_type: Cable type for stype=3 (1=Pretension, 2=Horizontal, 3=Lu) (default None)
|
|
981
|
+
tens: Initial tension force or allowable compression (default None)
|
|
982
|
+
t_limit: Tension limit value. If provided, the tension limit flag is set to True. (default None)
|
|
983
|
+
|
|
984
|
+
Examples:
|
|
985
|
+
```python
|
|
986
|
+
# Simple tension-only member
|
|
987
|
+
Element.Tension(1, 2, stype=1)
|
|
988
|
+
|
|
989
|
+
# Tension-only with allowable compression and a tension limit
|
|
990
|
+
Element.Tension(1, 2, stype=1, tens=0.5, t_limit=-15)
|
|
991
|
+
|
|
992
|
+
# Hook element with slack length
|
|
993
|
+
Element.Tension(3, 4, stype=2, non_len=0.5)
|
|
994
|
+
|
|
995
|
+
# Cable with initial tension and catenary effects
|
|
996
|
+
Element.Tension(5, 6, stype=3, cable_type=3, tens=1000.0, non_len=0.1)
|
|
997
|
+
```
|
|
998
|
+
"""
|
|
999
|
+
if id == None: id =0
|
|
1000
|
+
self.ID = id
|
|
1001
|
+
self.TYPE = 'TENSTR'
|
|
1002
|
+
self.MATL = mat
|
|
1003
|
+
self.SECT = sect
|
|
1004
|
+
self.NODE = [i, j]
|
|
1005
|
+
self.ANGLE = angle
|
|
1006
|
+
self.STYPE = stype
|
|
1007
|
+
self._GROUP = group
|
|
1008
|
+
_n1 = nodeByID(i)
|
|
1009
|
+
_n2 = nodeByID(j)
|
|
1010
|
+
self.LENGTH = _nodeDIST(_n1,_n2)
|
|
1011
|
+
_dirVect = np.subtract(_n2.LOC,_n1.LOC)
|
|
1012
|
+
self.LOCALX = _dirVect/(np.linalg.norm(_dirVect))
|
|
1013
|
+
Element.lastLoc = (_n2.X,_n2.Y,_n2.Z)
|
|
1014
|
+
|
|
1015
|
+
# Handle subtype-specific parameters
|
|
1016
|
+
if stype == 1: # Tension-only specific
|
|
1017
|
+
if tens is not None:
|
|
1018
|
+
self.TENS = tens
|
|
1019
|
+
if t_limit is not None:
|
|
1020
|
+
self.T_LIMIT = t_limit
|
|
1021
|
+
self.T_bLMT = True
|
|
1022
|
+
|
|
1023
|
+
elif stype == 2: # Hook specific
|
|
1024
|
+
if non_len is not None:
|
|
1025
|
+
self.NON_LEN = non_len
|
|
1026
|
+
|
|
1027
|
+
elif stype == 3: # Cable specific
|
|
1028
|
+
if cable_type is not None:
|
|
1029
|
+
self.CABLE = cable_type
|
|
1030
|
+
if non_len is not None:
|
|
1031
|
+
self.NON_LEN = non_len
|
|
1032
|
+
if tens is not None:
|
|
1033
|
+
self.TENS = tens
|
|
1034
|
+
_ADD(self)
|
|
1035
|
+
|
|
1036
|
+
class Compression(_common):
|
|
1037
|
+
def __init__(self, i: int, j: int, stype: int, mat: int = 1, sect: int = 1, angle: float = 0, group = "" , id: int = None, tens: float = None, t_limit: float = None, non_len: float = None):
|
|
1038
|
+
"""
|
|
1039
|
+
Creates a COMPTR (Compression-only) element.
|
|
1040
|
+
|
|
1041
|
+
Parameters:
|
|
1042
|
+
i: Start node ID
|
|
1043
|
+
j: End node ID
|
|
1044
|
+
stype: Compression element subtype (1=Compression-only, 2=Gap)
|
|
1045
|
+
mat: Material property number (default 1)
|
|
1046
|
+
sect: Section property number (default 1)
|
|
1047
|
+
angle: Beta angle for section orientation in degrees (default 0.0)
|
|
1048
|
+
group: Structure group of the element (str or list; 'SG1' or ['SG1','SG2'])
|
|
1049
|
+
id: Element ID (default 0 for auto-increment)
|
|
1050
|
+
tens: Allowable tension or initial compression force (default None)
|
|
1051
|
+
t_limit: Compression limit value. If provided, the compression limit flag is set to True. (default None)
|
|
1052
|
+
non_len: Non-linear length parameter for gap (default None)
|
|
1053
|
+
|
|
1054
|
+
Examples:
|
|
1055
|
+
```python
|
|
1056
|
+
# Simple compression-only member
|
|
1057
|
+
Element.Compression(1, 2, stype=1)
|
|
1058
|
+
|
|
1059
|
+
# Compression-only with tension limit and buckling limit
|
|
1060
|
+
Element.Compression(1, 2, stype=1, tens=27, t_limit=-15)
|
|
1061
|
+
|
|
1062
|
+
# Gap element with initial gap
|
|
1063
|
+
Element.Compression(3, 4, stype=2, non_len=0.25)
|
|
1064
|
+
```
|
|
1065
|
+
"""
|
|
1066
|
+
if id == None: id =0
|
|
1067
|
+
self.ID = id
|
|
1068
|
+
self.TYPE = 'COMPTR'
|
|
1069
|
+
self.MATL = mat
|
|
1070
|
+
self.SECT = sect
|
|
1071
|
+
self.NODE = [i, j]
|
|
1072
|
+
self.ANGLE = angle
|
|
1073
|
+
self.STYPE = stype
|
|
1074
|
+
self._GROUP = group
|
|
1075
|
+
_n1 = nodeByID(i)
|
|
1076
|
+
_n2 = nodeByID(j)
|
|
1077
|
+
self.LENGTH = _nodeDIST(_n1,_n2)
|
|
1078
|
+
_dirVect = np.subtract(_n2.LOC,_n1.LOC)
|
|
1079
|
+
self.LOCALX = _dirVect/(np.linalg.norm(_dirVect))
|
|
1080
|
+
Element.lastLoc = (_n2.X,_n2.Y,_n2.Z)
|
|
1081
|
+
|
|
1082
|
+
# Handle subtype-specific parameters
|
|
1083
|
+
if stype == 1: # Compression-only specific
|
|
1084
|
+
if tens is not None:
|
|
1085
|
+
self.TENS = tens
|
|
1086
|
+
if t_limit is not None:
|
|
1087
|
+
self.T_LIMIT = t_limit
|
|
1088
|
+
self.T_bLMT = True
|
|
1089
|
+
|
|
1090
|
+
elif stype == 2: # Gap specific
|
|
1091
|
+
if non_len is not None:
|
|
1092
|
+
self.NON_LEN = non_len
|
|
1093
|
+
_ADD(self)
|
|
1094
|
+
|
|
1095
|
+
class Solid(_common):
|
|
1096
|
+
def __init__(self, nodes: list, mat: int = 1, sect: int = 0, group = "" , id: int = None):
|
|
1097
|
+
"""
|
|
1098
|
+
Creates a SOLID element for 3D analysis.
|
|
1099
|
+
|
|
1100
|
+
Parameters:
|
|
1101
|
+
nodes: List of node IDs defining the solid element
|
|
1102
|
+
- 4 nodes: Tetrahedral element
|
|
1103
|
+
- 6 nodes: Pentahedral element
|
|
1104
|
+
- 8 nodes: Hexahedral element
|
|
1105
|
+
mat: Material property number (default 1)
|
|
1106
|
+
group: Structure group of the element (str or list; 'SG1' or ['SG1','SG2'])
|
|
1107
|
+
id: Element ID (default 0 for auto-increment)
|
|
1108
|
+
|
|
1109
|
+
Examples:
|
|
1110
|
+
```python
|
|
1111
|
+
# Tetrahedral solid element
|
|
1112
|
+
Element.Solid([1, 2, 3, 4], mat=1)
|
|
1113
|
+
|
|
1114
|
+
# Wedge solid element
|
|
1115
|
+
Element.Solid([1, 2, 3, 4, 5, 6], mat=2)
|
|
1116
|
+
|
|
1117
|
+
# Hexahedral solid element
|
|
1118
|
+
Element.Solid([1, 2, 3, 4, 5, 6, 7, 8], mat=1, id=200)
|
|
1119
|
+
```
|
|
1120
|
+
"""
|
|
1121
|
+
if id == None: id =0
|
|
1122
|
+
if len(nodes) not in [4, 6, 8]:
|
|
1123
|
+
raise ValueError("Solid element must have 4, 6, or 8 nodes.")
|
|
1124
|
+
self.ID = id
|
|
1125
|
+
self.TYPE = 'SOLID'
|
|
1126
|
+
self.MATL = mat
|
|
1127
|
+
self.SECT = sect # Solid elements don't use section properties
|
|
1128
|
+
self.NODE = nodes
|
|
1129
|
+
self._GROUP = group
|
|
1130
|
+
_ADD(self)
|
|
1131
|
+
|
|
1132
|
+
|
|
1133
|
+
#-----------------------------------------------Stiffness Scale Factor------------------------------
|
|
1134
|
+
|
|
1135
|
+
class StiffnessScaleFactor:
|
|
1136
|
+
|
|
1137
|
+
data = []
|
|
1138
|
+
|
|
1139
|
+
def __init__(self,
|
|
1140
|
+
element_id,
|
|
1141
|
+
area_sf: float = 1.0,
|
|
1142
|
+
asy_sf: float = 1.0,
|
|
1143
|
+
asz_sf: float = 1.0,
|
|
1144
|
+
ixx_sf: float = 1.0,
|
|
1145
|
+
iyy_sf: float = 1.0,
|
|
1146
|
+
izz_sf: float = 1.0,
|
|
1147
|
+
wgt_sf: float = 1.0,
|
|
1148
|
+
group: str = "",
|
|
1149
|
+
id: int = None):
|
|
1150
|
+
"""
|
|
1151
|
+
element_id: Element ID(s) where scale factor is applied (can be int or list)
|
|
1152
|
+
area_sf: Cross-sectional area scale factor
|
|
1153
|
+
asy_sf: Effective Shear Area scale factor (y-axis)
|
|
1154
|
+
asz_sf: Effective Shear Area scale factor (z-axis)
|
|
1155
|
+
ixx_sf: Torsional Resistance scale factor (x-axis)
|
|
1156
|
+
iyy_sf: Area Moment of Inertia scale factor (y-axis)
|
|
1157
|
+
izz_sf: Area Moment of Inertia scale factor (z-axis)
|
|
1158
|
+
wgt_sf: Weight scale factor
|
|
1159
|
+
group: Group name (default "")
|
|
1160
|
+
id: Scale factor ID (optional, auto-assigned if None)
|
|
1161
|
+
|
|
1162
|
+
Examples:
|
|
1163
|
+
StiffnessScaleFactor(908, area_sf=0.5, asy_sf=0.6, asz_sf=0.7,
|
|
1164
|
+
ixx_sf=0.8, iyy_sf=0.8, izz_sf=0.9, wgt_sf=0.95)
|
|
1165
|
+
|
|
1166
|
+
"""
|
|
1167
|
+
|
|
1168
|
+
# Check if group exists, create if not
|
|
1169
|
+
if group != "":
|
|
1170
|
+
chk = 0
|
|
1171
|
+
a = [v['NAME'] for v in Group.Boundary.json()["Assign"].values()]
|
|
1172
|
+
if group in a:
|
|
1173
|
+
chk = 1
|
|
1174
|
+
if chk == 0:
|
|
1175
|
+
Group.Boundary(group)
|
|
1176
|
+
|
|
1177
|
+
# Handle element_id as single int or list
|
|
1178
|
+
if isinstance(element_id, (list, tuple)):
|
|
1179
|
+
self.ELEMENT_IDS = list(element_id)
|
|
1180
|
+
else:
|
|
1181
|
+
self.ELEMENT_IDS = [element_id]
|
|
1182
|
+
|
|
1183
|
+
self.AREA_SF = area_sf
|
|
1184
|
+
self.ASY_SF = asy_sf
|
|
1185
|
+
self.ASZ_SF = asz_sf
|
|
1186
|
+
self.IXX_SF = ixx_sf
|
|
1187
|
+
self.IYY_SF = iyy_sf
|
|
1188
|
+
self.IZZ_SF = izz_sf
|
|
1189
|
+
self.WGT_SF = wgt_sf
|
|
1190
|
+
self.GROUP_NAME = group
|
|
1191
|
+
|
|
1192
|
+
# Auto-assign ID if not provided
|
|
1193
|
+
if id is None:
|
|
1194
|
+
self.ID = len(Element.StiffnessScaleFactor.data) + 1
|
|
1195
|
+
else:
|
|
1196
|
+
self.ID = id
|
|
1197
|
+
|
|
1198
|
+
# Add to static list
|
|
1199
|
+
Element.StiffnessScaleFactor.data.append(self)
|
|
1200
|
+
|
|
1201
|
+
@classmethod
|
|
1202
|
+
def json(cls):
|
|
1203
|
+
"""
|
|
1204
|
+
Converts StiffnessScaleFactor data to JSON format
|
|
1205
|
+
"""
|
|
1206
|
+
json_data = {"Assign": {}}
|
|
1207
|
+
|
|
1208
|
+
for scale_factor in cls.data:
|
|
1209
|
+
# Create scale factor item
|
|
1210
|
+
scale_factor_item = {
|
|
1211
|
+
"ID": scale_factor.ID,
|
|
1212
|
+
"AREA_SF": scale_factor.AREA_SF,
|
|
1213
|
+
"ASY_SF": scale_factor.ASY_SF,
|
|
1214
|
+
"ASZ_SF": scale_factor.ASZ_SF,
|
|
1215
|
+
"IXX_SF": scale_factor.IXX_SF,
|
|
1216
|
+
"IYY_SF": scale_factor.IYY_SF,
|
|
1217
|
+
"IZZ_SF": scale_factor.IZZ_SF,
|
|
1218
|
+
"WGT_SF": scale_factor.WGT_SF,
|
|
1219
|
+
"GROUP_NAME": scale_factor.GROUP_NAME
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
# Assign to each element ID
|
|
1223
|
+
for element_id in scale_factor.ELEMENT_IDS:
|
|
1224
|
+
if str(element_id) not in json_data["Assign"]:
|
|
1225
|
+
json_data["Assign"][str(element_id)] = {"ITEMS": []}
|
|
1226
|
+
|
|
1227
|
+
json_data["Assign"][str(element_id)]["ITEMS"].append(scale_factor_item)
|
|
1228
|
+
|
|
1229
|
+
return json_data
|
|
1230
|
+
|
|
1231
|
+
@classmethod
|
|
1232
|
+
def create(cls):
|
|
1233
|
+
"""
|
|
1234
|
+
Sends all StiffnessScaleFactor data to the API
|
|
1235
|
+
"""
|
|
1236
|
+
MidasAPI("PUT", "/db/essf", cls.json())
|
|
1237
|
+
|
|
1238
|
+
@classmethod
|
|
1239
|
+
def get(cls):
|
|
1240
|
+
"""
|
|
1241
|
+
Retrieves StiffnessScaleFactor data from the API
|
|
1242
|
+
"""
|
|
1243
|
+
return MidasAPI("GET", "/db/essf")
|
|
1244
|
+
|
|
1245
|
+
@classmethod
|
|
1246
|
+
def sync(cls):
|
|
1247
|
+
"""
|
|
1248
|
+
Updates the StiffnessScaleFactor class with data from the API
|
|
1249
|
+
"""
|
|
1250
|
+
cls.data = []
|
|
1251
|
+
response = cls.get()
|
|
1252
|
+
|
|
1253
|
+
if response != {'message': ''}:
|
|
1254
|
+
processed_ids = set() # To avoid duplicate processing
|
|
1255
|
+
|
|
1256
|
+
for element_data in response.get("ESSF", {}).items():
|
|
1257
|
+
for item in element_data.get("ITEMS", []):
|
|
1258
|
+
scale_factor_id = item.get("ID", 1)
|
|
1259
|
+
|
|
1260
|
+
# Skip if already processed (for multi-element scale factors)
|
|
1261
|
+
if scale_factor_id in processed_ids:
|
|
1262
|
+
continue
|
|
1263
|
+
|
|
1264
|
+
# Find all elements with the same scale factor ID
|
|
1265
|
+
element_ids = []
|
|
1266
|
+
for eid, edata in response.get("ESSF", {}).items():
|
|
1267
|
+
for eitem in edata.get("ITEMS", []):
|
|
1268
|
+
if eitem.get("ID") == scale_factor_id:
|
|
1269
|
+
element_ids.append(int(eid))
|
|
1270
|
+
|
|
1271
|
+
# Create StiffnessScaleFactor object
|
|
1272
|
+
Element.StiffnessScaleFactor(
|
|
1273
|
+
element_id=element_ids if len(element_ids) > 1 else element_ids[0],
|
|
1274
|
+
area_sf=item.get("AREA_SF", 1.0),
|
|
1275
|
+
asy_sf=item.get("ASY_SF", 1.0),
|
|
1276
|
+
asz_sf=item.get("ASZ_SF", 1.0),
|
|
1277
|
+
ixx_sf=item.get("IXX_SF", 1.0),
|
|
1278
|
+
iyy_sf=item.get("IYY_SF", 1.0),
|
|
1279
|
+
izz_sf=item.get("IZZ_SF", 1.0),
|
|
1280
|
+
wgt_sf=item.get("WGT_SF", 1.0),
|
|
1281
|
+
group=item.get("GROUP_NAME", ""),
|
|
1282
|
+
id=scale_factor_id
|
|
1283
|
+
)
|
|
1284
|
+
|
|
1285
|
+
processed_ids.add(scale_factor_id)
|
|
1286
|
+
|
|
1287
|
+
@classmethod
|
|
1288
|
+
def delete(cls):
|
|
1289
|
+
"""
|
|
1290
|
+
Deletes all stiffness scale factors from the database and resets the class.
|
|
1291
|
+
"""
|
|
1292
|
+
cls.data = []
|
|
1293
|
+
return MidasAPI("DELETE", "/db/essf")
|
|
1294
|
+
|
|
1295
|
+
|
|
1296
|
+
|
|
1297
|
+
|
|
1298
|
+
# ---- GET ELEMENT OBJECT FROM ID ----------
|
|
1299
|
+
|
|
1300
|
+
# def elemByID2(elemID:int) -> Element:
|
|
1301
|
+
# ''' Return Element object with the input ID '''
|
|
1302
|
+
# for elem in Element.elements:
|
|
1303
|
+
# if elem.ID == elemID:
|
|
1304
|
+
# return elem
|
|
1305
|
+
|
|
1306
|
+
# print(f'There is no element with ID {elemID}')
|
|
1307
|
+
# return None
|
|
1308
|
+
|
|
1309
|
+
def elemByID(elemID:int) -> _helperELEM:
|
|
1310
|
+
''' Return Element object with the input ID '''
|
|
1311
|
+
try:
|
|
1312
|
+
return (Element.__elemDIC__[str(elemID)])
|
|
1313
|
+
except:
|
|
1314
|
+
print(Fore.RED +f'There is no element with ID {elemID}'+Style.RESET_ALL)
|
|
1315
|
+
return None
|
|
1316
|
+
|
|
1317
|
+
def elemsInGroup(groupName:str,unique:bool=True,reverse:bool=False,output:Literal['ID','ELEM']='ID') -> list[_helperELEM]:
|
|
1318
|
+
''' Returns Element ID list or Element object list in a Structure Group '''
|
|
1319
|
+
groupNames = _convItem2List(groupName)
|
|
1320
|
+
elist = []
|
|
1321
|
+
for gName in groupNames:
|
|
1322
|
+
chk=1
|
|
1323
|
+
rev = reverse
|
|
1324
|
+
if gName[0] == '!':
|
|
1325
|
+
gName = gName[1:]
|
|
1326
|
+
rev = not rev
|
|
1327
|
+
for i in Group.Structure.Groups:
|
|
1328
|
+
if i.NAME == gName:
|
|
1329
|
+
chk=0
|
|
1330
|
+
eIDlist = i.ELIST
|
|
1331
|
+
if rev: eIDlist = list(reversed(eIDlist))
|
|
1332
|
+
elist.append(eIDlist)
|
|
1333
|
+
if chk:
|
|
1334
|
+
print(f'⚠️ "{gName}" - Structure group not found !')
|
|
1335
|
+
if unique:
|
|
1336
|
+
finalElist = list(dict.fromkeys(sFlatten(elist)))
|
|
1337
|
+
else:
|
|
1338
|
+
finalElist = sFlatten(elist)
|
|
1339
|
+
|
|
1340
|
+
if output == 'ELEM':
|
|
1341
|
+
finoutput = []
|
|
1342
|
+
for elm in finalElist:
|
|
1343
|
+
finoutput.append(elemByID(elm))
|
|
1344
|
+
finalElist:Element = finoutput
|
|
1345
|
+
|
|
1346
|
+
return finalElist
|