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.
Files changed (40) hide show
  1. midas_civil/_BoundaryChangeAssignment.py +278 -0
  2. midas_civil/__init__.py +51 -0
  3. midas_civil/_analysiscontrol.py +585 -0
  4. midas_civil/_boundary.py +888 -0
  5. midas_civil/_construction.py +1004 -0
  6. midas_civil/_element.py +1346 -0
  7. midas_civil/_group.py +337 -0
  8. midas_civil/_load.py +967 -0
  9. midas_civil/_loadcomb.py +159 -0
  10. midas_civil/_mapi.py +249 -0
  11. midas_civil/_material.py +1692 -0
  12. midas_civil/_model.py +522 -0
  13. midas_civil/_movingload.py +1479 -0
  14. midas_civil/_node.py +532 -0
  15. midas_civil/_result_table.py +929 -0
  16. midas_civil/_result_test.py +5455 -0
  17. midas_civil/_section/_TapdbSecSS.py +175 -0
  18. midas_civil/_section/__init__.py +413 -0
  19. midas_civil/_section/_compositeSS.py +283 -0
  20. midas_civil/_section/_dbSecSS.py +164 -0
  21. midas_civil/_section/_offsetSS.py +53 -0
  22. midas_civil/_section/_pscSS copy.py +455 -0
  23. midas_civil/_section/_pscSS.py +822 -0
  24. midas_civil/_section/_tapPSC12CellSS.py +565 -0
  25. midas_civil/_section/_unSupp.py +58 -0
  26. midas_civil/_settlement.py +161 -0
  27. midas_civil/_temperature.py +677 -0
  28. midas_civil/_tendon.py +1016 -0
  29. midas_civil/_thickness.py +147 -0
  30. midas_civil/_utils.py +529 -0
  31. midas_civil/_utilsFunc/__init__.py +0 -0
  32. midas_civil/_utilsFunc/_line2plate.py +636 -0
  33. midas_civil/_view.py +891 -0
  34. midas_civil/_view_trial.py +430 -0
  35. midas_civil/_visualise.py +347 -0
  36. midas_civil-1.4.1.dist-info/METADATA +74 -0
  37. midas_civil-1.4.1.dist-info/RECORD +40 -0
  38. midas_civil-1.4.1.dist-info/WHEEL +5 -0
  39. midas_civil-1.4.1.dist-info/licenses/LICENSE +21 -0
  40. midas_civil-1.4.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,888 @@
1
+ from ._mapi import MidasAPI
2
+ # from ._model import *
3
+ from ._node import Node
4
+ from ._group import Group
5
+ from typing import Union
6
+
7
+
8
+
9
+ def convList(item):
10
+ if type(item)!=list:
11
+ return [item]
12
+ else:
13
+ return item
14
+
15
+
16
+ # ----- Extend for list of nodes/elems -----
17
+
18
+ def _ADD_Support(self):
19
+ if isinstance(self.NODE,int):
20
+ Boundary.Support.sups.append(self)
21
+ elif isinstance(self.NODE,list):
22
+ for nID in self.NODE:
23
+ Boundary.Support(nID,self.CONST,self.GROUP)
24
+
25
+
26
+ class Boundary:
27
+
28
+ @classmethod
29
+ def create(cls):
30
+ """Creates Boundary elements in MIDAS Civil NX"""
31
+ if cls.Support.sups!=[]: cls.Support.create()
32
+ if cls.ElasticLink.links!=[]: cls.ElasticLink.create()
33
+ if cls.RigidLink.links!=[]: cls.RigidLink.create()
34
+ if cls.MLFC.func!=[]: cls.RigidLink.create()
35
+ if cls.PointSpring.springs!=[]: cls.PointSpring.create()
36
+
37
+
38
+ @classmethod
39
+ def delete(cls):
40
+ """Delets Boundary elements from MIDAS Civil NX and Python"""
41
+ cls.Support.delete()
42
+ cls.ElasticLink.delete()
43
+ cls.RigidLink.delete()
44
+ cls.MLFC.delete()
45
+ cls.PointSpring.delete()
46
+
47
+ @classmethod
48
+ def clear(cls):
49
+ """Clear Boundary elements from Python"""
50
+ cls.Support.clear()
51
+ cls.ElasticLink.clear()
52
+ cls.RigidLink.clear()
53
+ cls.MLFC.clear()
54
+ cls.PointSpring.clear()
55
+
56
+ @classmethod
57
+ def sync(cls):
58
+ """Sync Boundary elements from MIDAS Civil NX to Python"""
59
+ cls.Support.sync()
60
+ cls.ElasticLink.sync()
61
+ cls.RigidLink.sync()
62
+ cls.MLFC.sync()
63
+ cls.PointSpring.sync()
64
+
65
+
66
+ class Support:
67
+ """Create Support Object in Python \n\nNode ID, Constraint, Boundary Group. Sample: Support(3, "1110000") or Support(3, "pin"). \nValid inputs for DOF are 1s and 0s or "pin", "fix", "free" (no capital letters).
68
+ \nIf more than 7 characters are entered, then only first 7 characters will be considered to define constraint."""
69
+ sups = []
70
+ def __init__(self, nodeID:int, constraint:str, group:str = ""):
71
+ if not isinstance(constraint, str): constraint = str(constraint)
72
+ if constraint == "pin": constraint = "111"
73
+ if constraint == "fix": constraint = "1111111"
74
+ if constraint == "roller": constraint = "001"
75
+ if len(constraint) < 7: constraint = constraint + '0' * (7-len(constraint))
76
+ if len(constraint) > 7: constraint = constraint[:7]
77
+ string = ''.join(['1' if char != '0' else '0' for char in constraint])
78
+
79
+ # Check if group exists, create if not
80
+ if group != "":
81
+ chk = 0
82
+ a = [v['NAME'] for v in Group.Boundary.json()["Assign"].values()]
83
+ if group in a:
84
+ chk = 1
85
+ if chk == 0:
86
+ Group.Boundary(group)
87
+
88
+ self.NODE = nodeID
89
+ self.CONST = string
90
+ self.GROUP = group
91
+ self.ID = len(Boundary.Support.sups) + 1
92
+ _ADD_Support(self)
93
+
94
+ @classmethod
95
+ def json(cls):
96
+ """Creates JSON from Supports objects defined in Python"""
97
+ json = {"Assign":{}}
98
+ ng = []
99
+ for i in Boundary.Support.sups:
100
+ if i.NODE in Node.ids:
101
+ json["Assign"][i.NODE] = {"ITEMS":
102
+ [{"ID": i.ID,
103
+ "CONSTRAINT":i.CONST,
104
+ "GROUP_NAME": i.GROUP}]
105
+ }
106
+ if i.NODE not in Node.ids: ng.append(i.NODE)
107
+ if len(ng) > 0: print("These nodes are not defined: ", ng)
108
+ return json
109
+
110
+ @staticmethod
111
+ def create():
112
+ """Creates Supports in MIDAS Civil NX"""
113
+ MidasAPI("PUT","/db/cons",Boundary.Support.json())
114
+
115
+ @staticmethod
116
+ def get():
117
+ """Get the JSON of Supports from MIDAS Civil NX"""
118
+ return MidasAPI("GET","/db/cons")
119
+
120
+ @staticmethod
121
+ def sync():
122
+ """Sync Supports from MIDAS Civil NX to Python"""
123
+ a = Boundary.Support.get()
124
+ if a != {'message': ''}:
125
+ if list(a['CONS'].keys()) != []:
126
+ Boundary.Support.sups = []
127
+ for j in a['CONS'].keys():
128
+ Boundary.Support(int(j),a['CONS'][j]['ITEMS'][0]['CONSTRAINT'])
129
+
130
+ @staticmethod
131
+ def delete():
132
+ """Delete Supports from MIDAS Civil NX and Python"""
133
+ Boundary.Support.clear()
134
+ return MidasAPI("DELETE","/db/cons")
135
+
136
+ @staticmethod
137
+ def clear():
138
+ """Delete Supports from Python"""
139
+ Boundary.Support.sups=[]
140
+
141
+
142
+
143
+
144
+ #---------------------------------------------------------------------------------------------------------------
145
+ #Class to define Elastic Links:
146
+ class ElasticLink:
147
+
148
+ # list to store all link instances
149
+ links = []
150
+
151
+ def __init__(self,
152
+ i_node: int,
153
+ j_node: int,
154
+ group: str = "",
155
+ link_type: str = "GEN",
156
+ sdx: float = 0,
157
+ sdy: float = 0,
158
+ sdz: float = 0,
159
+ srx: float = 0,
160
+ sry: float = 0,
161
+ srz: float = 0,
162
+ shear: bool = False,
163
+ dr_y: float = 0.5,
164
+ dr_z: float = 0.5,
165
+ beta_angle: float = 0,
166
+ dir: str = "Dy",
167
+ func_id: int = 1,
168
+ distance_ratio: float = 0,
169
+ id: int = None, ):
170
+ """
171
+ Elastic link.
172
+ Parameters:
173
+ i_node: The first node ID
174
+ j_node: The second node ID
175
+ group: The group name (default "")
176
+ link_type: Type of link (GEN, RIGID, TENS, COMP, MULTI LINEAR, SADDLE, RAIL INTERACT) (default "GEN")
177
+ sdx: Spring stiffness in X direction (default 0)
178
+ sdy: Spring stiffness in Y direction (default 0)
179
+ sdz: Spring stiffness in Z direction (default 0)
180
+ srx: Rotational stiffness around X axis (default 0)
181
+ sry: Rotational stiffness around Y axis (default 0)
182
+ srz: Rotational stiffness around Z axis (default 0)
183
+ shear: Consider shear effects (default False)
184
+ dr_y: Distance ratio for Y direction (default 0.5)
185
+ dr_z: Distance ratio for Z direction (default 0.5)
186
+ beta_angle: Rotation angle in degrees (default 0)
187
+ dir: Direction for MULTI LINEAR or RAIL INTERACT links (default "Dy")
188
+ func_id: Function ID for MULTI LINEAR or RAIL INTERACT links (default 1)
189
+ distance_ratio: Distance ratio for MULTI LINEAR or RAIL INTERACT links (default 0)
190
+ id: The link ID (optional)
191
+
192
+ Examples:
193
+ ```python
194
+ # General link with all stiffness parameters
195
+ ElasticLink(1, 2, "Group1", "GEN", 1000, 1000, 1000, 100, 100, 100)
196
+ # Rigid link
197
+ ElasticLink(3, 4, "Group2", "RIGID")
198
+ # Tension-only link
199
+ ElasticLink(5, 6, "Group3", "TENS", 500)
200
+ # Compression-only link
201
+ ElasticLink(7, 8, "Group4", "COMP", 500)
202
+ # Rail Track Type link
203
+ ElasticLink(9, 10, "Group5", "RAIL INTERACT", dir="Dy", func_id=1)
204
+ # Multi Linear Link
205
+ ElasticLink(11, 12, "Group6", "MULTI LINEAR", dir="Dy", func_id=1)
206
+ # Saddle type link
207
+ ElasticLink(13, 14, "Group7", "SADDLE")
208
+ ```
209
+ """
210
+ # Check if group exists, create if not
211
+ if group != "":
212
+ chk = 0
213
+ a = [v['NAME'] for v in Group.Boundary.json()["Assign"].values()]
214
+ if group in a:
215
+ chk = 1
216
+ if chk == 0:
217
+ Group.Boundary(group)
218
+
219
+ # Validate link type
220
+ valid_types = ["GEN", "RIGID", "TENS", "COMP", "MULTI LINEAR", "SADDLE", "RAIL INTERACT"]
221
+ if link_type not in valid_types:
222
+ link_type = "GEN"
223
+
224
+ # Validate direction for MULTI LINEAR
225
+ if link_type == "MULTI LINEAR":
226
+ valid_directions = ["Dx", "Dy", "Dz", "Rx", "Ry", "Rz"]
227
+ if dir not in valid_directions:
228
+ dir = "Dy"
229
+
230
+ # Validate direction for RAIL INTERACT
231
+ if link_type == "RAIL INTERACT":
232
+ valid_directions = ["Dy", "Dz"]
233
+ if dir not in valid_directions:
234
+ dir = "Dy"
235
+
236
+ self.I_NODE = i_node
237
+ self.J_NODE = j_node
238
+ self.GROUP_NAME = group
239
+ self.LINK_TYPE = link_type
240
+ self.ANGLE = beta_angle
241
+
242
+ # Parameters for all link types
243
+ self.SDx = sdx
244
+ self.SDy = sdy
245
+ self.SDz = sdz
246
+ self.SRx = srx
247
+ self.SRy = sry
248
+ self.SRz = srz
249
+ self.bSHEAR = shear
250
+ self.DR_Y = dr_y
251
+ self.DR_Z = dr_z
252
+
253
+ # Parameters for MULTI LINEAR and RAIL INTERACT
254
+ self.Direction = dir
255
+ self.Function_ID = func_id
256
+ self.Distance_ratio = distance_ratio
257
+
258
+ # Auto-assign ID if not provided
259
+ if id is None:
260
+ self.ID = len(Boundary.ElasticLink.links) + 1
261
+ else:
262
+ self.ID = id
263
+
264
+ # Add to static list
265
+ Boundary.ElasticLink.links.append(self)
266
+
267
+ @classmethod
268
+ def json(cls):
269
+ """
270
+ Converts ElasticLink data to JSON format for API submission.
271
+ Example:
272
+ # Get the JSON data for all links
273
+ json_data = ElasticLink.json()
274
+ print(json_data)
275
+ """
276
+ data = {}
277
+
278
+ for link in cls.links:
279
+ link_data = {
280
+ "NODE": [link.I_NODE, link.J_NODE],
281
+ "LINK": link.LINK_TYPE,
282
+ "ANGLE": link.ANGLE,
283
+ "BNGR_NAME": link.GROUP_NAME
284
+ }
285
+
286
+ # Add type-specific parameters
287
+ if link.LINK_TYPE == "GEN":
288
+ link_data["R_S"] = [False] * 6
289
+ link_data["SDR"] = [
290
+ link.SDx,
291
+ link.SDy,
292
+ link.SDz,
293
+ link.SRx,
294
+ link.SRy,
295
+ link.SRz
296
+ ]
297
+ link_data["bSHEAR"] = link.bSHEAR
298
+ if link.bSHEAR:
299
+ link_data["DR"] = [link.DR_Y, link.DR_Z]
300
+ else:
301
+ link_data["DR"] = [0.5, 0.5]
302
+
303
+ elif link.LINK_TYPE in ["TENS", "COMP"]:
304
+ link_data["SDR"] = [link.SDx, 0, 0, 0, 0, 0]
305
+ link_data["bSHEAR"] = link.bSHEAR
306
+ if link.bSHEAR:
307
+ link_data["DR"] = [link.DR_Y, link.DR_Z]
308
+ else:
309
+ link_data["DR"] = [0.5, 0.5]
310
+
311
+ elif link.LINK_TYPE == "MULTI LINEAR":
312
+ direction_mapping = {
313
+ "Dx": 0, "Dy": 1, "Dz": 2, "Rx": 3, "Ry": 4, "Rz": 5
314
+ }
315
+ link_data["DIR"] = direction_mapping.get(link.Direction, 0)
316
+ link_data["MLFC"] = link.Function_ID
317
+ link_data["DRENDI"] = link.Distance_ratio
318
+
319
+ elif link.LINK_TYPE == "RAIL INTERACT":
320
+ direction_mapping = {"Dy": 1, "Dz": 2}
321
+ link_data["DIR"] = direction_mapping.get(link.Direction, 0)
322
+ link_data["RLFC"] = link.Function_ID
323
+ link_data["bSHEAR"] = link.bSHEAR
324
+ if link.bSHEAR:
325
+ link_data["DEENDI"] = link.Distance_ratio
326
+ else:
327
+ link_data["DR"] = [0.5, 0.5]
328
+
329
+ data[link.ID] = link_data
330
+
331
+ return {"Assign": data}
332
+
333
+ @classmethod
334
+ def create(cls):
335
+ """
336
+ Sends all ElasticLink data to Midas API.
337
+ Example:
338
+ ElasticLink(1, 2, "Group1", "GEN", 1000, 1000, 1000, 100, 100, 100)
339
+ # Send to the API
340
+ ElasticLink.create()
341
+ """
342
+ MidasAPI("PUT", "/db/elnk", cls.json())
343
+
344
+ @classmethod
345
+ def get(cls):
346
+ """
347
+ Retrieves ElasticLink data from Midas API.
348
+ Example:
349
+ api_data = ElasticLink.get()
350
+ print(api_data)
351
+ """
352
+ return MidasAPI("GET", "/db/elnk")
353
+
354
+ @classmethod
355
+ def sync(cls):
356
+ """
357
+ Updates the ElasticLink class with data from the Midas API.
358
+ Example:
359
+ ElasticLink.sync()
360
+ """
361
+ cls.links = []
362
+ a = cls.get()
363
+
364
+ if a != {'message': ''}:
365
+ for link_id, link_data in a.get("ELNK", {}).items():
366
+ sdx = sdy = sdz = srx = sry = srz = 0
367
+ shear = False
368
+ dr_y = dr_z = 0.5
369
+ direction = "Dy"
370
+ func_id = 1
371
+ distance_ratio = 0
372
+
373
+ if link_data["LINK"] == "GEN" and "SDR" in link_data:
374
+ sdx, sdy, sdz, srx, sry, srz = link_data["SDR"]
375
+ shear = link_data.get("bSHEAR")
376
+ if shear and "DR" in link_data:
377
+ dr_y, dr_z = link_data["DR"]
378
+
379
+ elif link_data["LINK"] in ["TENS", "COMP"] and "SDR" in link_data:
380
+ sdx = link_data["SDR"][0]
381
+ shear = link_data.get("bSHEAR")
382
+ if shear and "DR" in link_data:
383
+ dr_y, dr_z = link_data["DR"]
384
+
385
+ elif link_data["LINK"] == "MULTI LINEAR":
386
+ dir_mapping = {0: "Dx", 1: "Dy", 2: "Dz", 3: "Rx", 4: "Ry", 5: "Rz"}
387
+ direction = dir_mapping.get(link_data.get("DIR"), "Dy")
388
+ func_id = link_data.get("MLFC")
389
+ distance_ratio = link_data.get("DRENDI")
390
+
391
+ elif link_data["LINK"] == "RAIL INTERACT":
392
+ dir_mapping = {1: "Dy", 2: "Dz"}
393
+ direction = dir_mapping.get(link_data.get("DIR"), "Dy")
394
+ func_id = link_data.get("RLFC")
395
+ shear = link_data.get("bSHEAR")
396
+ if shear and "DEENDI" in link_data:
397
+ distance_ratio = link_data["DEENDI"]
398
+
399
+ Boundary.ElasticLink(
400
+ link_data["NODE"][0],
401
+ link_data["NODE"][1],
402
+ link_data.get("BNGR_NAME"),
403
+ link_data["LINK"],
404
+ sdx, sdy, sdz, srx, sry, srz,
405
+ shear, dr_y, dr_z,
406
+ link_data.get("ANGLE"),
407
+ direction, func_id, distance_ratio,
408
+ int(link_id)
409
+ )
410
+
411
+ @classmethod
412
+ def delete(cls):
413
+ """
414
+ Deletes all elastic links from the database and resets the class.
415
+ Example:sss
416
+ ElasticLink.delete()
417
+ """
418
+ cls.clear()
419
+
420
+ @classmethod
421
+ def clear(cls):
422
+ """
423
+ Deletes all elastic links from the database and resets the class.
424
+ Example:sss
425
+ ElasticLink.delete()
426
+ """
427
+ cls.links = []
428
+ #---------------------------------------------------------------------------------------------------------------
429
+
430
+
431
+ #Class to define Rigid Links:
432
+ class RigidLink:
433
+
434
+ links = []
435
+ ids = [0]
436
+
437
+ def __init__(self,
438
+ master_node: int,
439
+ slave_nodes: list,
440
+ group: str = "",
441
+ dof: int = 111111,
442
+ id: int = None):
443
+ """
444
+ Rigid link.
445
+ Parameters:
446
+ master_node: The first node ID
447
+ slave_nodes: The second node ID
448
+ group: The group name (default "")
449
+ dof: Fixity of link (default 111111)
450
+ id: The link ID (optional)
451
+
452
+ Examples:
453
+ ```python
454
+ # General link with all stiffness parameters
455
+ RigidLink(1, [2,3], "Group1", 111000,1)
456
+ ```
457
+ """
458
+
459
+ # Check if group exists, create if not
460
+ if group != "":
461
+ chk = 0
462
+ a = [v['NAME'] for v in Group.Boundary.json()["Assign"].values()]
463
+ if group in a:
464
+ chk = 1
465
+ if chk == 0:
466
+ Group.Boundary(group)
467
+
468
+
469
+ self.M_NODE = master_node
470
+ self.S_NODE = convList(slave_nodes)
471
+ self.GROUP_NAME = group
472
+ self.DOF = dof
473
+
474
+ # Auto-assign ID if not provided
475
+ if id is None:
476
+ self.ID = max(Boundary.RigidLink.ids) + 1
477
+ else:
478
+ self.ID = id
479
+
480
+ # Add to static list
481
+ Boundary.RigidLink.links.append(self)
482
+ Boundary.RigidLink.ids.append(self.ID)
483
+
484
+
485
+ @classmethod
486
+ def json(cls):
487
+ """
488
+ Converts RigidLink data to JSON format for API submission.
489
+ Example:
490
+ # Get the JSON data for all links
491
+ json_data = RigidLink.json()
492
+ print(json_data)
493
+ """
494
+ json = {"Assign": {}}
495
+ for link in cls.links:
496
+ if link.M_NODE not in list(json["Assign"].keys()):
497
+ json["Assign"][link.M_NODE] = {"ITEMS": []}
498
+
499
+ json["Assign"][link.M_NODE]["ITEMS"].append({
500
+ "ID": link.ID,
501
+ "GROUP_NAME": link.GROUP_NAME,
502
+ "DOF": link.DOF,
503
+ "S_NODE": convList(link.S_NODE),
504
+ })
505
+ return json
506
+
507
+ @classmethod
508
+ def create(cls):
509
+ """
510
+ Sends all RigidLink data to Midas API.
511
+ RigidLink.create()
512
+ """
513
+ MidasAPI("PUT", "/db/RIGD", cls.json())
514
+
515
+ @classmethod
516
+ def get(cls):
517
+ """
518
+ Retrieves Rigid Link data from Midas API.
519
+ Example:
520
+ api_data = RigidLink.get()
521
+ print(api_data)
522
+ """
523
+ return MidasAPI("GET", "/db/RIGD")
524
+
525
+ @classmethod
526
+ def sync(cls):
527
+ """
528
+ Updates the RigidLink class with data from the Midas API.
529
+ Example:
530
+ RigidLink.sync()
531
+ """
532
+ cls.links = []
533
+ a = cls.get()
534
+ if a != {'message': ''}:
535
+ for i in a['RIGD'].keys():
536
+ for j in range(len(a['RIGD'][i]['ITEMS'])):
537
+ itm = a['RIGD'][i]['ITEMS'][j]
538
+ Boundary.RigidLink(int(i),itm['S_NODE'],itm['GROUP_NAME'],itm['DOF'],itm['ID'])
539
+
540
+ @classmethod
541
+ def delete(cls):
542
+ """
543
+ Deletes all rigid links from the database and resets the class.
544
+ Example:
545
+ ElasticLink.delete()
546
+ """
547
+ cls.clear()
548
+ return MidasAPI("DELETE", "/db/RIGD")
549
+
550
+ @classmethod
551
+ def clear(cls):
552
+ """
553
+ Deletes all rigid links from the database and resets the class.
554
+ Example:
555
+ ElasticLink.delete()
556
+ """
557
+ cls.links = []
558
+ #---------------------------------------------------------------------------------------------------------------
559
+
560
+ class MLFC:
561
+
562
+ func = []
563
+ _id = []
564
+
565
+ def __init__(self, name:str, type:str='FORCE', symm:bool=True, data:list=[[0,0],[1,1]], id:int=None):
566
+ """
567
+ Force-Deformation Function constructor.
568
+
569
+ Parameters:
570
+ name (str): The name for the Force-Deformation Function.
571
+ type (str, optional): Type of function, either "FORCE" or "MOMENT". Defaults to "FORCE".
572
+ symm (bool, optional): Defines if the function is symmetric (True) or unsymmetric (False). Defaults to True.
573
+ data (list[list[float]], optional): A list of [X, Y] coordinate pairs defining the function curve. Required.
574
+ - For "FORCE" type: X is displacement, Y is force.
575
+ - For "MOMENT" type: X is rotation in radians, Y is moment.
576
+ Defaults to [[0,0],[1,1]].
577
+ id (int, optional): The function ID. If not provided, it will be auto-assigned.
578
+
579
+ Examples:
580
+ ```python
581
+ # Create a symmetric force vs. displacement function
582
+ Boundary.MLFC(name="MyForceFunction", type="FORCE", symm=True, data=[[0,0], [0.1, 100], [0.2, 150]])
583
+
584
+ # Create an unsymmetric moment vs. rotation function with a specific ID
585
+ Boundary.MLFC(name="MyMomentFunction", type="MOMENT", symm=False, data=[[0,0], [0.01, 500], [0.02, 750]], id=5)
586
+ ```
587
+ """
588
+ self.NAME = name
589
+ self.TYPE = type
590
+ self.SYMM = symm
591
+ self.DATA = data
592
+
593
+ self.X = [dat[0] for dat in self.DATA]
594
+ self.Y = [dat[1] for dat in self.DATA]
595
+
596
+ # Auto-assign ID if not provided
597
+ if id is None:
598
+ if __class__._id == []:
599
+ self.ID = 1
600
+ else:
601
+ self.ID = max(__class__._id) + 1
602
+ else:
603
+ self.ID = id
604
+
605
+ __class__._id.append(self.ID)
606
+ __class__.func.append(self)
607
+
608
+
609
+
610
+ @classmethod
611
+ def json(cls):
612
+
613
+ json = {"Assign": {}}
614
+ for fn in cls.func:
615
+ json["Assign"][fn.ID]={
616
+ "NAME": fn.NAME,
617
+ "TYPE": fn.TYPE,
618
+ "SYMM": fn.SYMM,
619
+ "FUNC_ID": 0,
620
+ "ITEMS": []
621
+ }
622
+ for i in range(len(fn.X)):
623
+ json["Assign"][fn.ID]["ITEMS"].append({"X":fn.X[i],"Y":fn.Y[i]})
624
+ return json
625
+
626
+ @classmethod
627
+ def create(cls):
628
+ """
629
+ Sends all FUNC data.
630
+ """
631
+ MidasAPI("PUT", "/db/MLFC", cls.json())
632
+
633
+ @classmethod
634
+ def get(cls):
635
+ """
636
+ Retrieves data.
637
+ """
638
+ return MidasAPI("GET", "/db/MLFC")
639
+
640
+ @classmethod
641
+ def sync(cls):
642
+
643
+ cls.links = []
644
+ a = cls.get()
645
+ if a != {'message': ''}:
646
+ for i in a['MLFC'].keys():
647
+ name = a['MLFC'][i]["NAME"]
648
+ type = a['MLFC'][i]["TYPE"]
649
+ symm = a['MLFC'][i]["SYMM"]
650
+ data = []
651
+ for j in (a['MLFC'][i]['ITEMS']):
652
+ data.append([j["X"],j["Y"]])
653
+ Boundary.MLFC(name,type,symm,data,int(i))
654
+
655
+ @classmethod
656
+ def delete(cls):
657
+ """
658
+ Deletes all func from the database and resets the class.
659
+ """
660
+ cls.clear()
661
+ return MidasAPI("DELETE", "/db/MLFC")
662
+
663
+ @classmethod
664
+ def clear(cls):
665
+ """
666
+ Deletes all func from the database and resets the class.
667
+ """
668
+ cls.links = []
669
+
670
+ #--------------------------------------------------------------------------------------------------------------------------------------
671
+
672
+
673
+ class PointSpring:
674
+ """Create Point Spring Object in Python"""
675
+ springs = []
676
+
677
+ def __init__(self,
678
+ node: int,
679
+ spring_type: str = "LINEAR",
680
+ group: str = "",
681
+ stiffness: list = None,
682
+ fixed_option: list = None,
683
+ damping: list = None,
684
+ direction: str = "Dx+",
685
+ normal_vector: list = None,
686
+ function_id: int = 1,
687
+ id: int = None):
688
+ """
689
+ Point Spring constructor.
690
+
691
+ Parameters:
692
+ node: Node ID where spring is applied
693
+ spring_type: Type of spring ("LINEAR", "COMP", "TENS", "MULTI")
694
+ group: Group name (default "")
695
+ stiffness: Spring stiffness values [SDx, SDy, SDz, SRx, SRy, SRz] or single value for COMP/TENS
696
+ fixed_option: Fixed option array [Boolean, 6] for LINEAR type
697
+ damping: Damping values [Cx, Cy, Cz, CRx, CRy, CRz] (if provided, damping is enabled)
698
+ direction: Direction string ("Dx+", "Dx-", "Dy+", "Dy-", "Dz+", "Dz-", "Vector")
699
+ normal_vector: Normal vector [x, y, z] when direction is "Vector"
700
+ function_id: Function ID for MULTI type
701
+ id: Spring ID (optional, auto-assigned if None)
702
+
703
+ Examples:
704
+ # Linear spring
705
+ PointSpring(1, "LINEAR", "Group1", stiffness=[1000, 1000, 1000, 100, 100, 100])
706
+
707
+ # Compression only spring
708
+ PointSpring(2, "COMP", "Group2", stiffness=5000, direction="Dz+")
709
+
710
+ # Tension only spring with vector direction
711
+ PointSpring(3, "TENS", "Group3", stiffness=3000, direction="Vector", normal_vector=[0, -1, -1])
712
+
713
+ # Multi-linear spring
714
+ PointSpring(4, "MULTI", "Group4", direction="Dz+", function_id=1)
715
+ """
716
+
717
+ # Check if group exists, create if not
718
+ if group != "":
719
+ chk = 0
720
+ a = [v['NAME'] for v in Group.Boundary.json()["Assign"].values()]
721
+ if group in a:
722
+ chk = 1
723
+ if chk == 0:
724
+ Group.Boundary(group)
725
+
726
+ # Validate spring type
727
+ valid_types = ["LINEAR", "COMP", "TENS", "MULTI"]
728
+ if spring_type not in valid_types:
729
+ spring_type = "LINEAR"
730
+
731
+ self.NODE = node
732
+ self.TYPE = spring_type
733
+ self.GROUP_NAME = group
734
+
735
+ # Convert direction string to integer
736
+ direction_map = {
737
+ "Dx+": 0, "Dx-": 1, "Dy+": 2, "Dy-": 3,
738
+ "Dz+": 4, "Dz-": 5, "Vector": 6
739
+ }
740
+ self.DIR = direction_map.get(direction, 0)
741
+
742
+ # Auto-assign ID if not provided
743
+ if id is None:
744
+ self.ID = len(Boundary.PointSpring.springs) + 1
745
+ else:
746
+ self.ID = id
747
+
748
+ # Type-specific parameters
749
+ if spring_type == "LINEAR":
750
+ self.SDR = stiffness if stiffness else [0, 0, 0, 0, 0, 0]
751
+ self.F_S = fixed_option if fixed_option else [False] * 6
752
+ # Damping logic: if damping values provided, enable damping
753
+ self.DAMPING = damping is not None and any(d != 0 for d in damping) if damping else False
754
+ self.Cr = damping if damping else [0, 0, 0, 0, 0, 0]
755
+
756
+ elif spring_type in ["COMP", "TENS"]:
757
+ self.STIFF = stiffness if stiffness else 0
758
+ self.DV = normal_vector if normal_vector else [0, 0, 0]
759
+
760
+ elif spring_type == "MULTI":
761
+ self.DV = normal_vector if normal_vector else [0, 0, 0]
762
+ self.FUNCTION = function_id
763
+
764
+ # Add to static list
765
+ Boundary.PointSpring.springs.append(self)
766
+
767
+ @classmethod
768
+ def json(cls):
769
+ """
770
+ Converts PointSpring data to JSON format for API submission.
771
+ """
772
+ json_data = {"Assign": {}}
773
+
774
+ for spring in cls.springs:
775
+ spring_data = {
776
+ "ID": spring.ID,
777
+ "TYPE": spring.TYPE,
778
+ "GROUP_NAME": spring.GROUP_NAME
779
+ }
780
+
781
+ # Add type-specific parameters
782
+ if spring.TYPE == "LINEAR":
783
+ spring_data["SDR"] = spring.SDR
784
+ spring_data["F_S"] = spring.F_S
785
+ spring_data["DAMPING"] = spring.DAMPING
786
+ if spring.DAMPING:
787
+ spring_data["Cr"] = spring.Cr
788
+
789
+ elif spring.TYPE in ["COMP", "TENS"]:
790
+ spring_data["STIFF"] = spring.STIFF
791
+ spring_data["DIR"] = spring.DIR
792
+ spring_data["DV"] = spring.DV
793
+
794
+ elif spring.TYPE == "MULTI":
795
+ spring_data["DIR"] = spring.DIR
796
+ spring_data["DV"] = spring.DV
797
+ spring_data["FUNCTION"] = spring.FUNCTION
798
+
799
+ json_data["Assign"][spring.NODE] = {"ITEMS": [spring_data]}
800
+
801
+ return json_data
802
+
803
+ @classmethod
804
+ def create(cls):
805
+ """
806
+ Sends all PointSpring data
807
+ """
808
+ MidasAPI("PUT", "/db/nspr", cls.json())
809
+
810
+ @classmethod
811
+ def get(cls):
812
+ """
813
+ Retrieves PointSpring data
814
+ """
815
+ return MidasAPI("GET", "/db/nspr")
816
+
817
+ @classmethod
818
+ def sync(cls):
819
+ """
820
+ Updates the PointSpring class with data
821
+ """
822
+ cls.springs = []
823
+ a = cls.get()
824
+
825
+ if a != {'message': ''}:
826
+ for node_id, node_data in a.get("NSPR", {}).items():
827
+ for item in node_data.get("ITEMS"):
828
+ spring_type = item.get("TYPE")
829
+ group_name = item.get("GROUP_NAME")
830
+ spring_id = item.get("ID", 1)
831
+
832
+ # Extract type-specific parameters
833
+ if spring_type == "LINEAR":
834
+ stiffness = item.get("SDR")
835
+ fixed_option = item.get("F_S")
836
+ damping = item.get("Cr") if item.get("DAMPING", False) else None
837
+
838
+ # Convert direction back to string
839
+ dir_map = {0: "Dx+", 1: "Dx-", 2: "Dy+", 3: "Dy-", 4: "Dz+", 5: "Dz-", 6: "Vector"}
840
+ direction_str = dir_map.get(0, "Dx+") # Default for LINEAR
841
+
842
+ Boundary.PointSpring(
843
+ int(node_id), spring_type, group_name,
844
+ stiffness, fixed_option, damping, direction_str,spring_id
845
+ )
846
+
847
+ elif spring_type in ["COMP", "TENS"]:
848
+ stiffness = item.get("STIFF")
849
+ direction_int = item.get("DIR")
850
+ normal_vector = item.get("DV")
851
+
852
+ # Convert direction back to string
853
+ dir_map = {0: "Dx+", 1: "Dx-", 2: "Dy+", 3: "Dy-", 4: "Dz+", 5: "Dz-", 6: "Vector"}
854
+ direction_str = dir_map.get(direction_int, "Dx+")
855
+
856
+ Boundary.PointSpring(
857
+ int(node_id), spring_type, group_name,
858
+ stiffness, None, None, direction_str, normal_vector,spring_id
859
+ )
860
+
861
+ elif spring_type == "MULTI":
862
+ direction_int = item.get("DIR")
863
+ normal_vector = item.get("DV")
864
+ function_id = item.get("FUNCTION")
865
+
866
+ # Convert direction back to string
867
+ dir_map = {0: "Dx+", 1: "Dx-", 2: "Dy+", 3: "Dy-", 4: "Dz+", 5: "Dz-", 6: "Vector"}
868
+ direction_str = dir_map.get(direction_int, "Dx+")
869
+
870
+ Boundary.PointSpring(
871
+ int(node_id), spring_type, group_name,
872
+ None, None, None, direction_str, normal_vector, function_id,spring_id
873
+ )
874
+
875
+ @classmethod
876
+ def delete(cls):
877
+ """
878
+ Deletes all point springs from the database and resets the class.
879
+ """
880
+ cls.clear()
881
+ return MidasAPI("DELETE", "/db/nspr")
882
+
883
+ @classmethod
884
+ def clear(cls):
885
+ """
886
+ Deletes all point springs from the database and resets the class.
887
+ """
888
+ cls.springs = []