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,1479 @@
1
+
2
+ from ._mapi import MidasAPI
3
+
4
+ # ----------------------------------------------------------------------------------------------------------------
5
+
6
+ def _El_list(Start_id: int, End_id: int) -> list:
7
+
8
+ return list(range(Start_id, End_id + 1))
9
+
10
+ # --------------------------------------------------------------------------------------------------------------------------
11
+
12
+ class MovingLoad:
13
+
14
+ @classmethod
15
+ def create(cls):
16
+
17
+ if cls.LineLane.lanes:
18
+ cls.LineLane.create()
19
+
20
+ # Assuming Vehicle and Case classes exist elsewhere or will be added later
21
+ if hasattr(cls, 'Vehicle') and cls.Vehicle.vehicles:
22
+ cls.Vehicle.create()
23
+
24
+ if hasattr(cls, 'Case') and cls.Case.cases:
25
+ cls.Case.create()
26
+
27
+ @classmethod
28
+ def delete(cls):
29
+ cls.Code.delete()
30
+
31
+ @classmethod
32
+ def sync(cls):
33
+ cls.LineLane.sync()
34
+ if hasattr(cls, 'Vehicle'):
35
+ cls.Vehicle.sync()
36
+ if hasattr(cls, 'Case'):
37
+ cls.Case.sync()
38
+
39
+
40
+ class Code:
41
+
42
+ def __init__(self, code_name: str):
43
+ """
44
+ code_name (str): The name of the moving load code to be used.
45
+
46
+ Available Moving Load Codes:
47
+ - "KSCE-LSD15", "KOREA", "AASHTO STANDARD", "AASHTO LRFD",
48
+ - "AASHTO LRFD(PENNDOT)", "CHINA", "INDIA", "TAIWAN", "CANADA",
49
+ - "BS", "EUROCODE", "AUSTRALIA", "POLAND", "RUSSIA",
50
+ - "SOUTH AFRICA"
51
+ """
52
+ valid_codes = {
53
+ "KSCE-LSD15", "KOREA", "AASHTO STANDARD", "AASHTO LRFD", "AASHTO LRFD(PENDOT)",
54
+ "CHINA", "INDIA", "TAIWAN", "CANADA", "BS", "EUROCODE", "AUSTRALIA",
55
+ "POLAND", "RUSSIA", "SOUTH AFRICA"
56
+ }
57
+
58
+ if code_name not in valid_codes:
59
+ raise ValueError(f"Invalid code_name. Choose from: {', '.join(valid_codes)}")
60
+
61
+ self.code_name = code_name
62
+ json_data = {
63
+ "Assign": {
64
+ "1": {
65
+ "CODE": code_name
66
+ }
67
+ }
68
+ }
69
+ MidasAPI("PUT", "/db/mvcd", json_data)
70
+
71
+ @classmethod
72
+ def get(cls):
73
+ """Gets the currently set moving load code from the Midas model."""
74
+ return MidasAPI("GET", "/db/mvcd")
75
+
76
+ @classmethod
77
+ def delete(cls):
78
+ return MidasAPI("DELETE", "/db/mvcd")
79
+
80
+ class LineLane:
81
+
82
+ lanes = []
83
+
84
+ def __init__(
85
+ self,
86
+ code: str,
87
+ Lane_name: str,
88
+ Ecc: float,
89
+ Wheel_space: float,
90
+ elem_list: list[int],
91
+ IF: float = 0,
92
+ Span: float = 0,
93
+ id: int = None,
94
+ width: float = 0,
95
+ opt_width: float = 0,
96
+ Group_Name: str = "",
97
+ Moving_Direction: str = "BOTH",
98
+ Skew_start: float = 0,
99
+ Skew_end: float = 0
100
+ ):
101
+ """
102
+ code (str): Country code for traffic lane standards (e.g., "INDIA", "CHINA").
103
+ Lane_name (str): A unique name for the lane.
104
+ Ecc (float): Lateral eccentricity of the lane's centerline from the reference element path.
105
+ A positive value indicates an offset in the +Y direction of the element's local axis.
106
+ Wheel_space (float): The center-to-center distance between the wheels of the vehicle.
107
+ (e.g., a standard value is often around 1.8m or 6ft).
108
+ elem_list (list[int]): A list of element IDs defining the continuous path of the lane.
109
+ IF (float, optional): Impact Factor or Scale Factor, as defined by the selected design code. Defaults to 0.(For LRFD code Add Centerifugal force Input)
110
+ Span (float, optional): The span length of the lane, used by some codes for impact factor calculation. Defaults to 0.
111
+ id (int, optional): A unique integer ID for the lane. If None, it will be auto-assigned. Defaults to None.
112
+ width (float, optional): The width of the traffic lane. Key name "WIDTH". Defaults to 0.
113
+ opt_width (float, optional): The allowable width of the traffic lane for auto-positioning. Key name "ALLOW_WIDTH". Defaults to 0.
114
+ Group_Name (str, optional): The group name for cross-beam load distribution. If provided, distribution is "CROSS". Defaults to "".
115
+ Moving_Direction (str, optional): The allowed direction of vehicle movement ("FORWARD", "BACKWARD", "BOTH"). Defaults to "BOTH".
116
+ Skew_start (float, optional): The skew angle of the bridge at the start of the lane (in degrees). Defaults to 0.
117
+ Skew_end (float, optional): The skew angle of the bridge at the end of the lane (in degrees). Defaults to 0.
118
+ """
119
+ self.code = code
120
+ self.Lane_name = Lane_name
121
+ self.Ecc = Ecc
122
+ self.Wheel_space = Wheel_space
123
+ self.elem_list = elem_list
124
+ self.IF = IF
125
+ self.Span = Span
126
+ self.id = len(MovingLoad.LineLane.lanes) + 1 if id is None else id
127
+ self.width = width
128
+ self.opt_width = opt_width
129
+ self.Group_Name = Group_Name
130
+ self.Moving_Direction = Moving_Direction
131
+ self.Skew_start = Skew_start
132
+ self.Skew_end = Skew_end
133
+
134
+ # Ensure the correct moving load code is active in the model
135
+ MovingLoad.Code(code)
136
+
137
+ # Avoid duplicating lanes if syncing
138
+ if not any(lane.id == self.id for lane in MovingLoad.LineLane.lanes):
139
+ MovingLoad.LineLane.lanes.append(self)
140
+
141
+ # Definition of country-specific subclasses
142
+ class India:
143
+ def __init__(self, Lane_name: str, Ecc: float, Wheel_space: float, elem_list: list[int],
144
+ IF: float = 0, Span: float = 0, id: int = None,
145
+ width: float = 0, opt_width: float = 0, Group_Name: str = "", Moving_Direction: str = "BOTH",
146
+ Skew_start: float = 0, Skew_end: float = 0):
147
+ """Defines a traffic lane according to Indian standards."""
148
+ MovingLoad.LineLane("INDIA", Lane_name, Ecc, Wheel_space, elem_list,
149
+ IF, Span, id, width, opt_width, Group_Name, Moving_Direction, Skew_start, Skew_end)
150
+
151
+ class China:
152
+ def __init__(self, Lane_name: str, Ecc: float, Wheel_space: float, elem_list: list[int],
153
+ IF: float = 0, Span: float = 0, id: int = None,
154
+ width: float = 0, opt_width: float = 0, Group_Name: str = "", Moving_Direction: str = "BOTH",
155
+ Skew_start: float = 0, Skew_end: float = 0):
156
+ """Defines a traffic lane according to Chinese standards."""
157
+ MovingLoad.LineLane("CHINA", Lane_name, Ecc, Wheel_space, elem_list,
158
+ IF, Span, id, width, opt_width, Group_Name, Moving_Direction, Skew_start, Skew_end)
159
+
160
+ class Korea:
161
+ def __init__(self, Lane_name: str, Ecc: float, Wheel_space: float, elem_list: list[int],
162
+ IF: float = 0, id: int = None, opt_width: float = 0,
163
+ Group_Name: str = "", Moving_Direction: str = "BOTH",
164
+ Skew_start: float = 0, Skew_end: float = 0):
165
+ """Defines a traffic lane according to Korean standards."""
166
+ MovingLoad.LineLane("KOREA", Lane_name, Ecc, Wheel_space, elem_list,
167
+ IF, 0, id, 3, opt_width, Group_Name, Moving_Direction, Skew_start, Skew_end)
168
+
169
+ class Taiwan:
170
+ def __init__(self, Lane_name: str, Ecc: float, Wheel_space: float, elem_list: list[int],
171
+ IF: float = 0, id: int = None, width: float = 0, opt_width: float = 0,
172
+ Group_Name: str = "", Moving_Direction: str = "BOTH",
173
+ Skew_start: float = 0, Skew_end: float = 0):
174
+ """Defines a traffic lane according to Taiwanese standards."""
175
+ MovingLoad.LineLane("TAIWAN", Lane_name, Ecc, Wheel_space, elem_list,
176
+ IF, 0, id, width, opt_width, Group_Name, Moving_Direction, Skew_start, Skew_end)
177
+
178
+ class AASHTOStandard:
179
+ def __init__(self, Lane_name: str, Ecc: float, Wheel_space: float, elem_list: list[int],
180
+ IF: float = 0, id: int = None, opt_width: float = 0,
181
+ Group_Name: str = "", Moving_Direction: str = "BOTH",
182
+ Skew_start: float = 0, Skew_end: float = 0):
183
+ """Defines a traffic lane according to AASHTO Standard."""
184
+ MovingLoad.LineLane("AASHTO STANDARD", Lane_name, Ecc, Wheel_space, elem_list,
185
+ IF, 0, id, 3, opt_width, Group_Name, Moving_Direction, Skew_start, Skew_end)
186
+
187
+ class AASHTOLRFD:
188
+ def __init__(self, Lane_name: str, Ecc: float, Wheel_space: float, elem_list: list[int],
189
+ IF: float = 0, id: int = None, opt_width: float = 0,
190
+ Group_Name: str = "", Moving_Direction: str = "BOTH",
191
+ Skew_start: float = 0, Skew_end: float = 0):
192
+ """Defines a traffic lane according to AASHTO LRFD."""
193
+ MovingLoad.LineLane("AASHTO LRFD", Lane_name, Ecc, Wheel_space, elem_list,
194
+ IF, 0, id, 3, opt_width, Group_Name, Moving_Direction, Skew_start, Skew_end)
195
+
196
+ class PENNDOT:
197
+ def __init__(self, Lane_name: str, Ecc: float, Wheel_space: float, elem_list: list[int],
198
+ id: int = None, opt_width: float = 0,
199
+ Group_Name: str = "", Moving_Direction: str = "BOTH",
200
+ Skew_start: float = 0, Skew_end: float = 0):
201
+ """Defines a traffic lane according to AASHTO LRFD (PENNDOT)."""
202
+ MovingLoad.LineLane("AASHTO LRFD(PENDOT)", Lane_name, Ecc, Wheel_space, elem_list,
203
+ 0, 0, id, 3, opt_width, Group_Name, Moving_Direction, Skew_start, Skew_end)
204
+
205
+ class Canada:
206
+ def __init__(self, Lane_name: str, Ecc: float, Wheel_space: float, elem_list: list[int],
207
+ id: int = None, opt_width: float = 0,
208
+ Group_Name: str = "", Moving_Direction: str = "BOTH",
209
+ Skew_start: float = 0, Skew_end: float = 0):
210
+ """Defines a traffic lane according to Canadian standards."""
211
+ MovingLoad.LineLane("CANADA", Lane_name, Ecc, Wheel_space, elem_list,
212
+ 0, 0, id, 3, opt_width, Group_Name, Moving_Direction, Skew_start, Skew_end)
213
+
214
+ class BS:
215
+ def __init__(self, Lane_name: str, Ecc: float, Wheel_space: float, elem_list: list[int],
216
+ id: int = None, width: float = 0, opt_width: float = 0,
217
+ Group_Name: str = "", Moving_Direction: str = "BOTH",
218
+ Skew_start: float = 0, Skew_end: float = 0):
219
+ """Defines a traffic lane according to British Standards (BS)."""
220
+ MovingLoad.LineLane("BS", Lane_name, Ecc, Wheel_space, elem_list,
221
+ 0, 0, id, width, opt_width, Group_Name, Moving_Direction, Skew_start, Skew_end)
222
+
223
+ class Eurocode:
224
+ def __init__(self, Lane_name: str, Ecc: float, Wheel_space: float, elem_list: list[int],
225
+ IF: float = 0, id: int = None, width: float = 0, opt_width: float = 0,
226
+ Group_Name: str = "", Moving_Direction: str = "BOTH",
227
+ Skew_start: float = 0, Skew_end: float = 0):
228
+ """Defines a traffic lane according to Eurocode."""
229
+ MovingLoad.LineLane("EUROCODE", Lane_name, Ecc, Wheel_space, elem_list,
230
+ IF, 0, id, width, opt_width, Group_Name, Moving_Direction, Skew_start, Skew_end)
231
+
232
+ class Australia:
233
+ def __init__(self, Lane_name: str, Ecc: float, Wheel_space: float, elem_list: list[int],
234
+ id: int = None, width: float = 0, opt_width: float = 0,
235
+ Group_Name: str = "", Moving_Direction: str = "BOTH",
236
+ Skew_start: float = 0, Skew_end: float = 0):
237
+ """Defines a traffic lane according to Australian standards."""
238
+ MovingLoad.LineLane("AUSTRALIA", Lane_name, Ecc, Wheel_space, elem_list,
239
+ 0, 0, id, width, opt_width, Group_Name, Moving_Direction, Skew_start, Skew_end)
240
+
241
+ class Poland:
242
+ def __init__(self, Lane_name: str, Ecc: float, Wheel_space: float, elem_list: list[int],
243
+ id: int = None, width: float = 0, opt_width: float = 0,
244
+ Group_Name: str = "", Moving_Direction: str = "BOTH",
245
+ Skew_start: float = 0, Skew_end: float = 0):
246
+ """Defines a traffic lane according to Polish standards."""
247
+ MovingLoad.LineLane("POLAND", Lane_name, Ecc, Wheel_space, elem_list,
248
+ 0, 0, id, width, opt_width, Group_Name, Moving_Direction, Skew_start, Skew_end)
249
+
250
+ class Russia:
251
+ def __init__(self, Lane_name: str, Ecc: float, Wheel_space: float, elem_list: list[int],
252
+ id: int = None, width: float = 0, opt_width: float = 0,
253
+ Group_Name: str = "", Moving_Direction: str = "BOTH",
254
+ Skew_start: float = 0, Skew_end: float = 0):
255
+ """Defines a traffic lane according to Russian standards."""
256
+ MovingLoad.LineLane("RUSSIA", Lane_name, Ecc, Wheel_space, elem_list,
257
+ 0, 0, id, width, opt_width, Group_Name, Moving_Direction, Skew_start, Skew_end)
258
+
259
+ class SouthAfrica:
260
+ def __init__(self, Lane_name: str, Ecc: float, Wheel_space: float, elem_list: list[int],
261
+ id: int = None, width: float = 0, opt_width: float = 0,
262
+ Group_Name: str = "", Moving_Direction: str = "BOTH",
263
+ Skew_start: float = 0, Skew_end: float = 0):
264
+ """Defines a traffic lane according to South African standards."""
265
+ MovingLoad.LineLane("SOUTH AFRICA", Lane_name, Ecc, Wheel_space, elem_list,
266
+ 0, 0, id, width, opt_width, Group_Name, Moving_Direction, Skew_start, Skew_end)
267
+
268
+ class KSCELSD15:
269
+ def __init__(self, Lane_name: str, Ecc: float, Wheel_space: float, elem_list: list[int],
270
+ id: int = None, opt_width: float = 0,
271
+ Group_Name: str = "", Moving_Direction: str = "BOTH",
272
+ Skew_start: float = 0, Skew_end: float = 0):
273
+ """Defines a traffic lane according to KSCE-LSD15."""
274
+ MovingLoad.LineLane("KSCE-LSD15", Lane_name, Ecc, Wheel_space, elem_list,
275
+ 0, 0, id, 3, opt_width, Group_Name, Moving_Direction, Skew_start, Skew_end)
276
+
277
+ @staticmethod
278
+ def _get_lane_item_details(code, lane, is_start_span):
279
+ """
280
+ Internal helper to get code-specific keys for a LANE_ITEM.
281
+ This centralizes the logic for different country standards.
282
+ """
283
+ details = {}
284
+ if code == "INDIA":
285
+ details = {
286
+ "SPAN": lane.Span,
287
+ "IMPACT_SPAN": 1 if lane.Span > 0 else 0,
288
+ "IMPACT_FACTOR": lane.IF
289
+ }
290
+ elif code == "CHINA":
291
+ details = {
292
+ "SPAN": lane.Span,
293
+ "SPAN_START": is_start_span,
294
+ "SCALE_FACTOR": lane.IF
295
+ }
296
+ elif code in ["KOREA", "TAIWAN", "AASHTO STANDARD"]:
297
+ details = {
298
+ "FACT": lane.IF,
299
+ "SPAN_START": is_start_span
300
+ }
301
+ elif code in ["AASHTO LRFD(PENDOT)", "AUSTRALIA", "POLAND"]:
302
+ details = {
303
+ "SPAN_START": is_start_span
304
+ }
305
+ elif code == "AASHTO LRFD":
306
+ details = {
307
+ "CENT_F": lane.IF,
308
+ "SPAN_START": is_start_span
309
+ }
310
+ elif code == "EUROCODE":
311
+ details = {
312
+ "ECCEN_VERT_LOAD": lane.IF
313
+ }
314
+ # Codes like "KSCE-LSD15", "BS", "CANADA" etc., don't need extra keys
315
+
316
+ return details
317
+
318
+ @classmethod
319
+ def json(cls, lanes_list=None):
320
+ """
321
+ Generates the JSON
322
+ """
323
+ if lanes_list is None:
324
+ lanes_list = cls.lanes
325
+
326
+ data = {"Assign": {}}
327
+ for lane in lanes_list:
328
+ # Use the user-provided list directly
329
+ E_list = lane.elem_list
330
+ Load_Dist = "CROSS" if lane.Group_Name else "LANE"
331
+ opt_auto_lane = lane.opt_width > 0
332
+
333
+ common_data = {
334
+ "LL_NAME": lane.Lane_name,
335
+ "LOAD_DIST": Load_Dist,
336
+ "GROUP_NAME": lane.Group_Name if Load_Dist == "CROSS" else "",
337
+ "SKEW_START": lane.Skew_start,
338
+ "SKEW_END": lane.Skew_end,
339
+ "MOVING": lane.Moving_Direction,
340
+ "WHEEL_SPACE": lane.Wheel_space,
341
+ "WIDTH": lane.width,
342
+ "OPT_AUTO_LANE": opt_auto_lane,
343
+ "ALLOW_WIDTH": lane.opt_width
344
+ }
345
+
346
+ lane_items = []
347
+ for i, e in enumerate(E_list):
348
+ is_start_span = (i == 0)
349
+ # Get code-specific details from the helper function
350
+ item_details = cls._get_lane_item_details(lane.code, lane, is_start_span)
351
+
352
+ # Start with the base item
353
+ lane_item = {"ELEM": e, "ECC": lane.Ecc}
354
+ # Add the code-specific details
355
+ lane_item.update(item_details)
356
+ lane_items.append(lane_item)
357
+
358
+ data["Assign"][str(lane.id)] = {
359
+ "COMMON": common_data,
360
+ "LANE_ITEMS": lane_items
361
+ }
362
+ return data
363
+
364
+ @classmethod
365
+ def create(cls):
366
+ """Sends all defined traffic lane data to the Midas Civil API """
367
+ if not cls.lanes:
368
+ print("No lanes to create.")
369
+ return
370
+
371
+ # Group lanes by their country code to use the correct API endpoint
372
+ lanes_by_code = {"INDIA": [], "CHINA": [], "OTHER": []}
373
+ for lane in cls.lanes:
374
+ if lane.code == "INDIA":
375
+ lanes_by_code["INDIA"].append(lane)
376
+ elif lane.code == "CHINA":
377
+ lanes_by_code["CHINA"].append(lane)
378
+ else:
379
+ lanes_by_code["OTHER"].append(lane)
380
+
381
+ # Create JSON and send data for each group
382
+ if lanes_by_code["INDIA"]:
383
+ india_data = cls.json(lanes_by_code["INDIA"])
384
+ MidasAPI("PUT", "/db/llanid", india_data)
385
+
386
+ if lanes_by_code["CHINA"]:
387
+ china_data = cls.json(lanes_by_code["CHINA"])
388
+ MidasAPI("PUT", "/db/llanch", china_data)
389
+
390
+ if lanes_by_code["OTHER"]:
391
+ other_data = cls.json(lanes_by_code["OTHER"])
392
+ MidasAPI("PUT", "/db/llan", other_data)
393
+
394
+ @classmethod
395
+ def get(cls):
396
+ """Retrieves lane data from the Midas model based on the current active code.
397
+ Returns pure JSON output like {"LLAN": {...}} or {"LLANID": {...}} or {"LLANCH": {...}}"""
398
+
399
+ # First get the current active code
400
+ try:
401
+ code_response = MovingLoad.Code.get()
402
+ if not code_response or 'MVCD' not in code_response:
403
+ print("Warning: No moving load code found in model.")
404
+ return {}
405
+
406
+ # Extract the active code name
407
+ current_code = None
408
+ mvcd_data = code_response.get('MVCD', {})
409
+ for code_id, code_info in mvcd_data.items():
410
+ if isinstance(code_info, dict) and 'CODE' in code_info:
411
+ current_code = code_info['CODE']
412
+ break
413
+
414
+ if not current_code:
415
+ print("Warning: Could not determine active moving load code.")
416
+ return {}
417
+
418
+ except Exception as e:
419
+ print(f"Error getting current code: {e}")
420
+ return {}
421
+
422
+ # Based on the code, use the appropriate endpoint
423
+ if current_code == "INDIA":
424
+ return MidasAPI("GET", "/db/llanid")
425
+ elif current_code == "CHINA":
426
+ return MidasAPI("GET", "/db/llanch")
427
+ else:
428
+ return MidasAPI("GET", "/db/llan")
429
+
430
+ @classmethod
431
+ def delete(cls):
432
+ """Deletes all traffic lanes from the Midas model using simple deletion."""
433
+
434
+ # Get the current active code to determine endpoint
435
+ try:
436
+ code_response = MovingLoad.Code.get()
437
+ if not code_response or 'MVCD' not in code_response:
438
+ print("Warning: No moving load code found in model.")
439
+ return
440
+
441
+ current_code = None
442
+ mvcd_data = code_response.get('MVCD', {})
443
+ for code_id, code_info in mvcd_data.items():
444
+ if isinstance(code_info, dict) and 'CODE' in code_info:
445
+ current_code = code_info['CODE']
446
+ break
447
+
448
+ if not current_code:
449
+ print("Warning: Could not determine active moving load code.")
450
+ return
451
+
452
+ except Exception as e:
453
+ print(f"Error getting current code: {e}")
454
+ return
455
+
456
+ # Get lane data based on current code
457
+ api_data = cls.get()
458
+ if not api_data:
459
+ print("No lanes found in the model. Nothing to delete.")
460
+ return
461
+
462
+ # Determine the response key based on current code
463
+ if current_code == "INDIA":
464
+ response_key = "LLANID"
465
+ endpoint = "/db/llanid"
466
+ elif current_code == "CHINA":
467
+ response_key = "LLANCH"
468
+ endpoint = "/db/llanch"
469
+ else:
470
+ response_key = "LLAN"
471
+ endpoint = "/db/llan"
472
+
473
+ # Extract lane IDs
474
+ if response_key not in api_data:
475
+ print("No lanes found in the model. Nothing to delete.")
476
+ return
477
+
478
+ lane_ids = [int(id_str) for id_str in api_data[response_key].keys()]
479
+
480
+ if not lane_ids:
481
+ print("No target lanes to delete.")
482
+ return
483
+
484
+ # Simple deletion
485
+ MidasAPI("DELETE", endpoint, {"Remove": lane_ids})
486
+
487
+ @classmethod
488
+ def sync(cls):
489
+ """
490
+ Synchronizes the lane data from the Midas model
491
+ """
492
+ # Clear existing lanes
493
+ cls.lanes = []
494
+
495
+ # Get the current active code from the model
496
+ try:
497
+ code_response = MovingLoad.Code.get()
498
+ if not code_response or 'MVCD' not in code_response:
499
+ print("Warning: No moving load code found in model. Cannot sync lanes.")
500
+ return
501
+
502
+ # Extract the active code name
503
+ current_code = None
504
+ mvcd_data = code_response.get('MVCD', {})
505
+ for code_id, code_info in mvcd_data.items():
506
+ if isinstance(code_info, dict) and 'CODE' in code_info:
507
+ current_code = code_info['CODE']
508
+ break
509
+
510
+ if not current_code:
511
+ print("Warning: Could not determine active moving load code. Cannot sync lanes.")
512
+ return
513
+
514
+ except Exception as e:
515
+ print(f"Error getting current code: {e}")
516
+ return
517
+
518
+ # Get lane data using the new get method
519
+ response = cls.get()
520
+
521
+ if not response:
522
+ print("No lane data found in model to sync.")
523
+ return
524
+
525
+ # Determine the response key based on current code
526
+ if current_code == "INDIA":
527
+ response_key = "LLANID"
528
+ elif current_code == "CHINA":
529
+ response_key = "LLANCH"
530
+ else:
531
+ response_key = "LLAN"
532
+
533
+ # Extract lane data from the response
534
+ all_lanes_data = response.get(response_key, {})
535
+
536
+ if not all_lanes_data:
537
+ print("No lane data found in model to sync.")
538
+ return
539
+
540
+ # Process each lane from the model
541
+ for lane_id, lane_data in all_lanes_data.items():
542
+ common = lane_data.get("COMMON", {})
543
+ items = lane_data.get("LANE_ITEMS", [])
544
+
545
+ if not common or not items:
546
+ continue
547
+
548
+ # Extract element list directly
549
+ element_ids = [item['ELEM'] for item in items]
550
+ if not element_ids:
551
+ continue
552
+
553
+ # Extract common properties
554
+ lane_name = common.get("LL_NAME", f"Lane_{lane_id}")
555
+ ecc = items[0].get("ECC", 0) if items else 0
556
+ wheel_space = common.get("WHEEL_SPACE", 0)
557
+ width = common.get("WIDTH", 0)
558
+ opt_width = common.get("ALLOW_WIDTH", 0)
559
+ group_name = common.get("GROUP_NAME", "")
560
+ moving_dir = common.get("MOVING", "BOTH")
561
+ skew_start = common.get("SKEW_START", 0)
562
+ skew_end = common.get("SKEW_END", 0)
563
+
564
+ # Extract code-specific parameters based on the current active code
565
+ if_val = 0
566
+ span_val = 0
567
+
568
+ # Use the first item to extract code-specific parameters
569
+ first_item = items[0] if items else {}
570
+
571
+ if current_code == "INDIA":
572
+ if_val = first_item.get("IMPACT_FACTOR", 0)
573
+ span_val = first_item.get("SPAN", 0)
574
+ elif current_code == "CHINA":
575
+ if_val = first_item.get("SCALE_FACTOR", 0)
576
+ span_val = first_item.get("SPAN", 0)
577
+ elif current_code in ["KOREA", "TAIWAN", "AASHTO STANDARD"]:
578
+ if_val = first_item.get("FACT", 0)
579
+ elif current_code == "AASHTO LRFD":
580
+ if_val = first_item.get("CENT_F", 0)
581
+ elif current_code == "EUROCODE":
582
+ if_val = first_item.get("ECCEN_VERT_LOAD", 0)
583
+
584
+ # Create the LineLane object with the current active code
585
+ try:
586
+ lane_obj = MovingLoad.LineLane(
587
+ code=current_code,
588
+ Lane_name=lane_name,
589
+ Ecc=ecc,
590
+ Wheel_space=wheel_space,
591
+ elem_list=element_ids,
592
+ IF=if_val,
593
+ Span=span_val,
594
+ id=int(lane_id),
595
+ width=width,
596
+ opt_width=opt_width,
597
+ Group_Name=group_name,
598
+ Moving_Direction=moving_dir,
599
+ Skew_start=skew_start,
600
+ Skew_end=skew_end
601
+ )
602
+
603
+ except Exception as e:
604
+ print(f"Error creating lane {lane_id}: {e}")
605
+ continue
606
+ # ============================================= VEHICLE CLASS =================================================================
607
+
608
+
609
+ class Vehicle:
610
+
611
+ vehicles = []
612
+
613
+ # --- Data mapping for Indian (IRS) vehicle codes ---
614
+ _irs_vehicle_map = {
615
+ "BG-1676": {
616
+ "full_name": "Broad Gauge-1676mm",
617
+ "vehicles": [
618
+ "Modified B.G. Loading 1987-1", "Modified B.G. Loading 1987-2", "B.G. Standard Loading 1926-M.L.",
619
+ "B.G. Standard Loading 1926-B.L.", "Revised B.G. Loading 1975-WG1+WG1", "Revised B.G. Loading 1975-WAM4A+WAM4A",
620
+ "Revised B.G. Loading 1975-Bo-Bo+Bo-Bo", "Revised B.G. Loading 1975-WAM4A", "Revised B.G. Loading 1975-WAM4A+WAM4",
621
+ "Revised B.G. Loading 1975-WAM4A+WDM2", "25t Loading-2008 Combination 1", "25t Loading-2008 Combination 2",
622
+ "25t Loading-2008 Combination 3", "25t Loading-2008 Combination 4", "25t Loading-2008 Combination 5",
623
+ "DFC Loading Combination 1", "DFC Loading Combination 2", "DFC Loading Combination 3",
624
+ "DFC Loading Combination 4", "DFC Loading Combination 5"
625
+ ]
626
+ },
627
+ "MG-1000": {
628
+ "full_name": "Metre Gauge-1000mm",
629
+ "vehicles": ["2 Co-Co Locomotives", "2 Bo-Bo Locomotives", "MGML Loading of 1929", "M.L.", "B.L.", "C."]
630
+ },
631
+ "NG-762": {
632
+ "full_name": "Narrow Gauge-762mm",
633
+ "vehicles": [
634
+ "Class H: B-B or Bo-Bo Type", "Class H: C-C or Co-Co Type", "Class H: Steam (Zf/1)", "Class H: Diesel Electric",
635
+ "Class A: B-B or Bo-Bo Type", "Class A: C-C or Co-Co Type", "Class A: Diesel Mech./Elec.",
636
+ "Class A: Diesel Mech./Elec.(Articulated)", "Class A: DRG No. CSO/C-873", "Class B: B-B or Bo-Bo Type",
637
+ "Class B: Steam Engine (Tank)", "Class B: Steam Engine (Tender)", "Class B: Diesel Electric"
638
+ ]
639
+ },
640
+ "HML": {
641
+ "full_name": "Heavy Mineral Loadings",
642
+ "vehicles": [f"Train Formation No.{i}" for i in range(1, 18)]
643
+ },
644
+ "FTB": {
645
+ "full_name": "Footbridge & Footpath",
646
+ "vehicles": ["Footbridge & Footpath"]
647
+ }
648
+ }
649
+
650
+ # --- Default parameter mapping for Indian vehicle codes ---
651
+ _india_defaults_map = {
652
+ "IRC": {
653
+ "Footway": {"VEH_IN": {"FOOTWAY": 4.903325, "FOOTWAY_WIDTH": 3}}
654
+ },
655
+ "IRS": {
656
+ "BG-1676": {
657
+ "Modified B.G. Loading 1987-1": {"VEH_IN": {"TRACTIVE": 490.3325, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
658
+ "Modified B.G. Loading 1987-2": {"VEH_IN": {"TRACTIVE": 490.3325, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
659
+ "B.G. Standard Loading 1926-M.L.": {"VEH_IN": {"TRACTIVE": 0, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
660
+ "B.G. Standard Loading 1926-B.L.": {"VEH_IN": {"TRACTIVE": 0, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
661
+ "Revised B.G. Loading 1975-WG1+WG1": {"VEH_IN": {"TRACTIVE": 0, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
662
+ "Revised B.G. Loading 1975-WAM4A+WAM4A": {"VEH_IN": {"TRACTIVE": 0, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
663
+ "Revised B.G. Loading 1975-Bo-Bo+Bo-Bo": {"VEH_IN": {"TRACTIVE": 0, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
664
+ "Revised B.G. Loading 1975-WAM4A": {"VEH_IN": {"TRACTIVE": 0, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
665
+ "Revised B.G. Loading 1975-WAM4A+WAM4": {"VEH_IN": {"TRACTIVE": 0, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
666
+ "Revised B.G. Loading 1975-WAM4A+WDM2": {"VEH_IN": {"TRACTIVE": 0, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
667
+ "25t Loading-2008 Combination 1": {"VEH_IN": {"TRACTIVE": 617.81895, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
668
+ "25t Loading-2008 Combination 2": {"VEH_IN": {"TRACTIVE": 509.9458, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
669
+ "25t Loading-2008 Combination 3": {"VEH_IN": {"TRACTIVE": 823.7586, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
670
+ "25t Loading-2008 Combination 4": {"VEH_IN": {"TRACTIVE": 490.3325, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
671
+ "25t Loading-2008 Combination 5": {"VEH_IN": {"TRACTIVE": 490.3325, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
672
+ "DFC Loading Combination 1": {"VEH_IN": {"TRACTIVE": 617.81895, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
673
+ "DFC Loading Combination 2": {"VEH_IN": {"TRACTIVE": 509.9458, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
674
+ "DFC Loading Combination 3": {"VEH_IN": {"TRACTIVE": 823.7586, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
675
+ "DFC Loading Combination 4": {"VEH_IN": {"TRACTIVE": 490.3325, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
676
+ "DFC Loading Combination 5": {"VEH_IN": {"TRACTIVE": 490.3325, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
677
+ },
678
+ "MG-1000": {
679
+ "2 Co-Co Locomotives": {"VEH_IN": {"TRACTIVE": 313.8128, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
680
+ "2 Bo-Bo Locomotives": {"VEH_IN": {"TRACTIVE": 235.3596, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
681
+ "MGML Loading of 1929": {"VEH_IN": {"TRACTIVE": 0, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
682
+ "M.L.": {"VEH_IN": {"TRACTIVE": 0, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
683
+ "B.L.": {"VEH_IN": {"TRACTIVE": 0, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
684
+ "C.": {"VEH_IN": {"TRACTIVE": 0, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
685
+ },
686
+ "NG-762": {
687
+ "Class H: B-B or Bo-Bo Type": {"VEH_IN": {"TRACTIVE": 0, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
688
+ "Class H: C-C or Co-Co Type": {"VEH_IN": {"TRACTIVE": 0, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
689
+ "Class H: Steam (Zf/1)": {"VEH_IN": {"TRACTIVE": 0, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
690
+ "Class H: Diesel Electric": {"VEH_IN": {"TRACTIVE": 0, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
691
+ "Class A: B-B or Bo-Bo Type": {"VEH_IN": {"TRACTIVE": 0, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
692
+ "Class A: C-C or Co-Co Type": {"VEH_IN": {"TRACTIVE": 0, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
693
+ "Class A: Diesel Mech./Elec.": {"VEH_IN": {"TRACTIVE": 0, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
694
+ "Class A: Diesel Mech./Elec.(Articulated)": {"VEH_IN": {"TRACTIVE": 0, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
695
+ "Class A: DRG No. CSO/C-873": {"VEH_IN": {"TRACTIVE": 0, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
696
+ "Class B: B-B or Bo-Bo Type": {"VEH_IN": {"TRACTIVE": 0, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
697
+ "Class B: Steam Engine (Tank)": {"VEH_IN": {"TRACTIVE": 0, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
698
+ "Class B: Steam Engine (Tender)": {"VEH_IN": {"TRACTIVE": 0, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
699
+ "Class B: Diesel Electric": {"VEH_IN": {"TRACTIVE": 0, "BRAKE_LOCO_RATIO": 25, "BRAKE_TRAIN_RATIO": 13.4}},
700
+ },
701
+ "HML": {
702
+ "Train Formation No.1": {"VEH_IN": {"TRACTIVE": 588.399, "BRAKE_LOCO": 245.16625}},
703
+ "Train Formation No.2": {"VEH_IN": {"TRACTIVE": 588.399, "BRAKE_LOCO": 245.16625}},
704
+ "Train Formation No.3": {"VEH_IN": {"TRACTIVE": 588.399, "BRAKE_LOCO": 245.16625}},
705
+ "Train Formation No.4": {"VEH_IN": {"TRACTIVE": 588.399, "BRAKE_LOCO": 245.16625}},
706
+ "Train Formation No.5": {"VEH_IN": {"TRACTIVE": 441.29925, "BRAKE_LOCO": 245.16625}},
707
+ "Train Formation No.6": {"VEH_IN": {"TRACTIVE": 441.29925, "BRAKE_LOCO": 245.16625}},
708
+ "Train Formation No.7": {"VEH_IN": {"TRACTIVE": 441.29925, "BRAKE_LOCO": 245.16625}},
709
+ "Train Formation No.8": {"VEH_IN": {"TRACTIVE": 298.61249, "BRAKE_LOCO": 215.7463}},
710
+ "Train Formation No.9": {"VEH_IN": {"TRACTIVE": 397.169325, "BRAKE_LOCO": 114.737805}},
711
+ "Train Formation No.10": {"VEH_IN": {"TRACTIVE": 397.169325, "BRAKE_LOCO": 114.737805}},
712
+ "Train Formation No.11": {"VEH_IN": {"TRACTIVE": 397.169325, "BRAKE_LOCO": 114.737805}},
713
+ "Train Formation No.12": {"VEH_IN": {"TRACTIVE": 441.29925, "BRAKE_LOCO": 245.16625}},
714
+ "Train Formation No.13": {"VEH_IN": {"TRACTIVE": 441.29925, "BRAKE_LOCO": 245.16625}},
715
+ "Train Formation No.14": {"VEH_IN": {"TRACTIVE": 441.29925, "BRAKE_LOCO": 245.16625}},
716
+ "Train Formation No.15": {"VEH_IN": {"TRACTIVE": 441.29925, "BRAKE_LOCO": 245.16625}},
717
+ "Train Formation No.16": {"VEH_IN": {"TRACTIVE": 441.29925, "BRAKE_LOCO": 245.16625}},
718
+ "Train Formation No.17": {"VEH_IN": {"TRACTIVE": 441.29925, "BRAKE_LOCO": 245.16625}},
719
+ },
720
+ "FTB": {
721
+ "Footbridge & Footpath": {"VEH_IN": {"FOOTWAY_WIDTH": 3, "SPAN_LENGTH": 7.5}}
722
+ }
723
+ }
724
+ }
725
+
726
+ # --- Data mapping for Eurocode vehicle codes ---
727
+ _euro_vehicle_map = {
728
+ "RoadBridge": {
729
+ "full_name": "EN 1991-2:2003 - Road Bridge", "sub_type": 19,
730
+ "vehicle_types": [
731
+ {"name": "Load Model 1", "defaults": {"AMP_VALUES": [0.75, 0.4], "TANDEM_ADJUST_VALUES": [1,1,1], "UDL_ADJUST_VALUES": [1,1,1,1]}},
732
+ {"name": "Load Model 2", "defaults": {"ADJUSTMENT": 0.75, "ADJUSTMENT2": 1}},
733
+ {"name": "Load Model 4", "defaults": {"ADJUSTMENT": 0.75}},
734
+ {"name": "Load Model 3", "selectable_vehicles": ["600/150", "900/150", "1200/150/200", "1500/150/200", "1800/150/200", "2400/200", "3000/200", "3600/200"], "defaults": {"LM3_LOADCASE1": True, "LM3_LOADCASE2": False, "DYNAMIC_FACTOR": True, "USER_INPUT": False}},
735
+ {"name": "Load Model 3 (UK NA)", "selectable_vehicles": ["SV 80", "SV 100", "SV 196", "SOV 250", "SOV 350", "SOV 450", "SOV 600"], "defaults": {"DYNAMIC_FACTOR": True, "USER_INPUT": False}}
736
+ ]
737
+ },
738
+ "FTB": {
739
+ "full_name": "EN 1991-2:2003 - Footway and FootBridge", "sub_type": 20,
740
+ "vehicle_types": [
741
+ {"name": "Uniform load (Road bridge footway)", "defaults": {"ADJUSTMENT": 0.4, "FOOTWAY": 5}},
742
+ {"name": "Uniform load (Footbridge)", "defaults": {"ADJUSTMENT": 0.4}},
743
+ {"name": "Concentrated Load", "defaults": {}},
744
+ {"name": "Uniform load (Road bridge footway) UK NA", "defaults": {"ADJUSTMENT": 0.4}}
745
+ ]
746
+ },
747
+ "RoadBridgeFatigue": {
748
+ "full_name": "EN 1991-2:2003 - RoadBridge Fatigue", "sub_type": 21,
749
+ "vehicle_types": [
750
+ {"name": "Fatigue Load Model 1", "defaults": {"AMP": 1, "TANDEM_ADJUST_VALUES": [1,1,1], "UDL_ADJUST_VALUES": [1,1,1,1]}},
751
+ {"name": "Fatigue Load Model 2 (280)", "defaults": {"AMP": 1}},
752
+ {"name": "Fatigue Load Model 2 (360)", "defaults": {"AMP": 1}},
753
+ {"name": "Fatigue Load Model 2 (630)", "defaults": {"AMP": 1}},
754
+ {"name": "Fatigue Load Model 2 (560)", "defaults": {"AMP": 1}},
755
+ {"name": "Fatigue Load Model 2 (610)", "defaults": {"AMP": 1}},
756
+ {"name": "Fatigue Load Model 3 (One Vehicle)", "defaults": {"AMP": 1}},
757
+ {"name": "Fatigue Load Model 3 (Two Vehicle)", "defaults": {"AMP": 1, "INTERVAL": 31.6}},
758
+ {"name": "Fatigue Load Model 4 (200)", "defaults": {"AMP": 1}},
759
+ {"name": "Fatigue Load Model 4 (310)", "defaults": {"AMP": 1}},
760
+ {"name": "Fatigue Load Model 4 (490)", "defaults": {"AMP": 1}},
761
+ {"name": "Fatigue Load Model 4 (390)", "defaults": {"AMP": 1}},
762
+ {"name": "Fatigue Load Model 4 (450)", "defaults": {"AMP": 1}}
763
+ ]
764
+ },
765
+ "RailTraffic": {
766
+ "full_name": "EN 1991-2:2003-Rail Traffic Load", "sub_type": 23,
767
+ "vehicle_types": [
768
+ {"name": "Load Model 71", "defaults": {"W1": 80, "DD1": 0, "D1": 0.8, "W2": 80, "DD2": 0, "D2": 0.8, "V_LOAD_FACTOR": 1, "LONGI_DIST": False, "ECCEN_VERT_LOAD": False}},
769
+ {"name": "Load Model SW/0", "defaults": {"W1": 133, "DD1": 15, "D1": 5.3, "W2": 133, "DD2": 15, "D2": 0, "V_LOAD_FACTOR": 1, "LONGI_DIST": False, "ECCEN_VERT_LOAD": False}},
770
+ {"name": "Load Model SW/2", "defaults": {"W1": 150, "DD1": 25, "D1": 7, "W2": 150, "DD2": 25, "D2": 0, "V_LOAD_FACTOR": 1, "LONGI_DIST": False, "ECCEN_VERT_LOAD": False}},
771
+ {"name": "Unloaded Train", "defaults": {"W1": 10, "DD1": 0, "D1": 0, "W2": 0, "DD2": 0, "D2": 0, "V_LOAD_FACTOR": 1, "LONGI_DIST": False, "ECCEN_VERT_LOAD": False}},
772
+ {"name": "HSLM B", "defaults": {"V_LOAD_FACTOR": 1, "LONGI_DIST": False, "ECCEN_VERT_LOAD": False, "HSLMB_NUM": 10, "HSLMB_FORCE": 170, "HSLMB_DIST": 3.5, "PHI_DYN_EFF1": 0, "PHI_DYN_EFF2": 0}},
773
+ {"name": "HSLM A1 ~ HSLM A10", "selectable_vehicles": [f"A{i}" for i in range(1, 11)], "defaults": {"V_LOAD_FACTOR": 1, "LONGI_DIST": False, "ECCEN_VERT_LOAD": False, "PHI_DYN_EFF1": 0, "PHI_DYN_EFF2": 0}}
774
+ ]
775
+ }
776
+ }
777
+
778
+ def __init__(self, code: str, v_type: str, name: str, id: int = None, **kwargs):
779
+ """Base class for vehicle definition"""
780
+ self.code, self.v_type, self.name = code, v_type, name
781
+ self.id = len(MovingLoad.Vehicle.vehicles) + 1 if id is None else id
782
+ self.params = kwargs
783
+ if not any(v.id == self.id for v in MovingLoad.Vehicle.vehicles):
784
+ MovingLoad.Vehicle.vehicles.append(self)
785
+ #--------------------------------------------------------------- INDIA----------------------------------------------------
786
+ class India:
787
+ """
788
+ Defines a Standard Indian Vehicle
789
+ """
790
+ def __init__(self,
791
+ name: str,
792
+ standard_code: str,
793
+ vehicle_type: str,
794
+ vehicle_name: int = None,
795
+ id: int = None):
796
+ """
797
+ name (str): A unique name for the vehicle load.
798
+ standard_code (str): Abbreviation for the standard code ("IRC", "IRS", "Footway", "Fatigue").
799
+ vehicle_type (str): The specific type of vehicle.
800
+ - For "IRC": "Class A", "Class B", "Class 70R", "Class 40R", "Class AA","Footway".
801
+ - For "IRS": Use short codes: "BG-1676", "MG-1000", "NG-762", "HML", "FTB".
802
+ vehicle_name (int, optional): The numeric identifier (1-based) for the vehicle, required for "IRS" code.
803
+ id (int, optional): A unique ID for the vehicle. Auto-assigned if None.
804
+ """
805
+ code_map = {"IRC": "IRC:6-2000", "Footway": "IRC:6-2000", "IRS": "IRS: BRIDGE RULES", "Fatigue": "IRC:6-2014"}
806
+ full_standard_code = code_map.get(standard_code)
807
+ if not full_standard_code:
808
+ raise ValueError(f"Invalid standard_code. Use 'IRC', 'IRS', 'Footway', or 'Fatigue'.")
809
+
810
+ all_params = { "standard_code": full_standard_code }
811
+ defaults = {}
812
+
813
+ if standard_code == "IRS":
814
+ if not vehicle_name:
815
+ raise ValueError("'vehicle_name' is required for IRS standard code.")
816
+
817
+ irs_map = MovingLoad.Vehicle._irs_vehicle_map
818
+ if vehicle_type not in irs_map:
819
+ raise ValueError(f"Invalid IRS vehicle_type '{vehicle_type}'. Choose from {list(irs_map.keys())}")
820
+
821
+ vehicle_info = irs_map[vehicle_type]
822
+ all_params["vehicle_type_name"] = vehicle_info["full_name"]
823
+
824
+ if not (1 <= vehicle_name <= len(vehicle_info["vehicles"])):
825
+ raise ValueError(f"Invalid 'vehicle_name' {vehicle_name} for type '{vehicle_type}'. "
826
+ f"Must be between 1 and {len(vehicle_info['vehicles'])}.")
827
+
828
+ select_vehicle_name = vehicle_info["vehicles"][vehicle_name - 1]
829
+ all_params["select_vehicle"] = select_vehicle_name
830
+
831
+ # Get defaults for the specific IRS vehicle
832
+ if vehicle_type in MovingLoad.Vehicle._india_defaults_map["IRS"]:
833
+ if select_vehicle_name in MovingLoad.Vehicle._india_defaults_map["IRS"][vehicle_type]:
834
+ defaults = MovingLoad.Vehicle._india_defaults_map["IRS"][vehicle_type][select_vehicle_name]
835
+
836
+ else: # For IRC, Footway, Fatigue
837
+ all_params["vehicle_type_name"] = vehicle_type
838
+ # Get defaults if any exist for this type
839
+ if standard_code in MovingLoad.Vehicle._india_defaults_map:
840
+ if vehicle_type in MovingLoad.Vehicle._india_defaults_map[standard_code]:
841
+ defaults = MovingLoad.Vehicle._india_defaults_map[standard_code][vehicle_type]
842
+
843
+ all_params.update(defaults)
844
+ MovingLoad.Vehicle("INDIA", "Standard", name, id, **all_params)
845
+
846
+ class Eurocode:
847
+ """
848
+ Defines a Standard Eurocode Vehicle
849
+ """
850
+ def __init__(self,
851
+ name: str,
852
+ standard_code: str,
853
+ vehicle_type: str,
854
+ vehicle_name: int = None,
855
+ id: int = None):
856
+ """
857
+ name (str): A unique name for the vehicle load.
858
+ standard_code (str): Abbreviation for the standard code.
859
+ - "RoadBridge", "FTB", "RoadBridgeFatigue", "RailTraffic"
860
+ vehicle_type (str): The specific type of vehicle (e.g., "Load Model 1", "Load Model 3").
861
+ vehicle_name (int, optional): The numeric ID (1-based) for a selectable vehicle (e.g., for "Load Model 3").
862
+ id (int, optional): A unique ID for the vehicle. Auto-assigned if None.
863
+ """
864
+ euro_map = MovingLoad.Vehicle._euro_vehicle_map
865
+ if standard_code not in euro_map:
866
+ raise ValueError(f"Invalid standard_code. Choose from {list(euro_map.keys())}")
867
+
868
+ std_info = euro_map[standard_code]
869
+ v_type_info = next((vt for vt in std_info["vehicle_types"] if vt["name"] == vehicle_type), None)
870
+ if not v_type_info:
871
+ available_types = [vt['name'] for vt in std_info['vehicle_types']]
872
+ raise ValueError(f"Invalid vehicle_type '{vehicle_type}' for '{standard_code}'. Choose from: {available_types}")
873
+
874
+ # Start with the default parameters for the vehicle type
875
+ all_params = v_type_info.get("defaults", {}).copy()
876
+ all_params["SUB_TYPE"] = std_info["sub_type"]
877
+ all_params["vehicle_type_name"] = vehicle_type
878
+
879
+ # Handle selectable vehicles
880
+ if "selectable_vehicles" in v_type_info:
881
+ if not vehicle_name:
882
+ raise ValueError(f"'vehicle_name' is required for vehicle_type '{vehicle_type}'.")
883
+ selectable_list = v_type_info["selectable_vehicles"]
884
+ if not (1 <= vehicle_name <= len(selectable_list)):
885
+ raise ValueError(f"Invalid 'vehicle_name' {vehicle_name} for type '{vehicle_type}'. Must be between 1 and {len(selectable_list)}.")
886
+
887
+ select_vehicle_name = selectable_list[vehicle_name - 1]
888
+ all_params["SEL_VEHICLE"] = select_vehicle_name
889
+
890
+ MovingLoad.Vehicle("EUROCODE", "Standard", name, id, **all_params)
891
+
892
+ @classmethod
893
+ def create(cls):
894
+ """Sends all defined vehicle data to the Midas Civil"""
895
+ if not cls.vehicles:
896
+ print("No vehicles defined to create.")
897
+ return
898
+
899
+ json_data = cls.json(cls.vehicles)
900
+ MidasAPI("PUT", "/db/mvhl", json_data)
901
+
902
+
903
+ @classmethod
904
+ def json(cls, vehicle_list=None):
905
+ """
906
+ Generates the JSON Data
907
+ """
908
+ if vehicle_list is None:
909
+ vehicle_list = cls.vehicles
910
+
911
+ data = {"Assign": {}}
912
+ for v in vehicle_list:
913
+ # Creates a copy to avoid modifying the original stored parameters
914
+ params = v.params.copy()
915
+
916
+ if v.code == "INDIA":
917
+ vehicle_data = {
918
+ "MVLD_CODE": 7, # Static code for INDIA
919
+ "VEHICLE_LOAD_NAME": v.name,
920
+ "VEHICLE_LOAD_NUM": 1, # Always Standard for this implementation
921
+ "STANDARD_CODE": params.pop("standard_code"),
922
+ "VEHICLE_TYPE_NAME": params.pop("vehicle_type_name")
923
+ }
924
+
925
+ # Extract the selected vehicle name if it exists
926
+ select_vehicle_name = params.pop("select_vehicle", None)
927
+
928
+ # The remaining parameters in 'params' should be the content for VEH_IN
929
+ # If VEH_IN was added from defaults, use it
930
+ if "VEH_IN" in params:
931
+ # For IRS vehicles, SEL_VEHICLE key is required inside the VEH_IN block
932
+ if select_vehicle_name:
933
+ params["VEH_IN"]["SEL_VEHICLE"] = select_vehicle_name
934
+ vehicle_data["VEH_IN"] = params["VEH_IN"]
935
+ # If there's no default VEH_IN but a vehicle name is selected (e.g., basic IRC class)
936
+ elif select_vehicle_name:
937
+ vehicle_data["VEH_IN"] = {"SEL_VEHICLE": select_vehicle_name}
938
+
939
+ data["Assign"][str(v.id)] = vehicle_data
940
+
941
+ elif v.code == "EUROCODE":
942
+ vehicle_data = {
943
+ "MVLD_CODE": 11, # Static code for EUROCODE
944
+ "VEHICLE_LOAD_NAME": v.name,
945
+ "VEHICLE_LOAD_NUM": 1, # Always Standard for this implementation
946
+ "VEHICLE_TYPE_NAME": params.pop("vehicle_type_name"),
947
+ # All other parameters are nested under VEH_EUROCODE
948
+ "VEH_EUROCODE": params
949
+ }
950
+ data["Assign"][str(v.id)] = vehicle_data
951
+
952
+ return data
953
+
954
+ @classmethod
955
+ def sync(cls):
956
+ """
957
+ Synchronizes the vehicle data from the Midas model
958
+ """
959
+ cls.vehicles = [] # Clear the local list before syncing
960
+ response = cls.get()
961
+
962
+ if not response or "MVHL" not in response:
963
+ print("No vehicle data found in the model to sync.")
964
+ return
965
+
966
+ all_vehicles_data = response["MVHL"]
967
+
968
+ # --- Reverse Maps for Lookups ---
969
+ # India Standard Code (e.g., "IRC:6-2000" -> "IRC")
970
+ india_std_code_rev_map = {
971
+ "IRC:6-2000": "IRC",
972
+ "IRS: BRIDGE RULES": "IRS",
973
+ "IRC:6-2014": "Fatigue"
974
+ }
975
+ # India Vehicle Type (e.g., "Broad Gauge-1676mm" -> "BG-1676")
976
+ india_type_rev_map = {v['full_name']: k for k, v in cls._irs_vehicle_map.items()}
977
+
978
+ # Eurocode Standard Code (e.g., 19 -> "RoadBridge")
979
+ euro_std_code_rev_map = {v['sub_type']: k for k, v in cls._euro_vehicle_map.items()}
980
+
981
+
982
+ for v_id, v_data in all_vehicles_data.items():
983
+ vehicle_id = int(v_id)
984
+ mvld_code = v_data.get("MVLD_CODE")
985
+ vehicle_load_name = v_data.get("VEHICLE_LOAD_NAME")
986
+
987
+ # --- Sync logic for INDIA vehicles ---
988
+ if mvld_code == 7:
989
+ standard_code_full = v_data.get("STANDARD_CODE")
990
+ vehicle_type_full = v_data.get("VEHICLE_TYPE_NAME")
991
+
992
+ # Handle special case for Footway which uses IRC standard
993
+ if vehicle_type_full == "Footway":
994
+ standard_code = "Footway"
995
+ vehicle_type = "Footway"
996
+ vehicle_name_id = None
997
+ else:
998
+ standard_code = india_std_code_rev_map.get(standard_code_full)
999
+ vehicle_type = india_type_rev_map.get(vehicle_type_full)
1000
+ vehicle_name_id = None
1001
+
1002
+ if standard_code == "IRS" and "VEH_IN" in v_data:
1003
+ sel_vehicle_name = v_data["VEH_IN"].get("SEL_VEHICLE")
1004
+ if vehicle_type in cls._irs_vehicle_map and sel_vehicle_name:
1005
+ try:
1006
+ # Find the 1-based index of the vehicle name
1007
+ vehicle_name_id = cls._irs_vehicle_map[vehicle_type]["vehicles"].index(sel_vehicle_name) + 1
1008
+ except ValueError:
1009
+ print(f"Warning: Could not find vehicle name '{sel_vehicle_name}' for type '{vehicle_type}' during sync.")
1010
+ continue
1011
+
1012
+ # Instantiate the vehicle to add it to the local list
1013
+ MovingLoad.Vehicle.India(
1014
+ name=vehicle_load_name,
1015
+ standard_code=standard_code,
1016
+ vehicle_type=vehicle_type or vehicle_type_full, # Fallback to full name if no short code
1017
+ vehicle_name=vehicle_name_id,
1018
+ id=vehicle_id
1019
+ )
1020
+
1021
+ # --- Sync logic for EUROCODE vehicles ---
1022
+ elif mvld_code == 11 and "VEH_EUROCODE" in v_data:
1023
+ euro_params = v_data["VEH_EUROCODE"]
1024
+ sub_type = euro_params.get("SUB_TYPE")
1025
+ vehicle_type_name = v_data.get("VEHICLE_TYPE_NAME")
1026
+
1027
+ standard_code = euro_std_code_rev_map.get(sub_type)
1028
+ if not standard_code:
1029
+ print(f"Warning: Unknown Eurocode SUB_TYPE '{sub_type}' for vehicle ID {vehicle_id}. Skipping.")
1030
+ continue
1031
+
1032
+ # Check if this vehicle type has selectable options
1033
+ std_info = cls._euro_vehicle_map.get(standard_code, {})
1034
+ v_type_info = next((vt for vt in std_info.get("vehicle_types", []) if vt["name"] == vehicle_type_name), None)
1035
+
1036
+ vehicle_name_id = None
1037
+ if v_type_info and "selectable_vehicles" in v_type_info:
1038
+ sel_vehicle_name = euro_params.get("SEL_VEHICLE")
1039
+ if sel_vehicle_name:
1040
+ try:
1041
+ vehicle_name_id = v_type_info["selectable_vehicles"].index(sel_vehicle_name) + 1
1042
+ except ValueError:
1043
+ print(f"Warning: Could not find Eurocode vehicle name '{sel_vehicle_name}' for type '{vehicle_type_name}' during sync.")
1044
+ continue
1045
+
1046
+ MovingLoad.Vehicle.Eurocode(
1047
+ name=vehicle_load_name,
1048
+ standard_code=standard_code,
1049
+ vehicle_type=vehicle_type_name,
1050
+ vehicle_name=vehicle_name_id,
1051
+ id=vehicle_id
1052
+ )
1053
+
1054
+ @classmethod
1055
+ def get(cls):
1056
+ """Gets all vehicle load definitions from the Midas model."""
1057
+ return MidasAPI("GET", "/db/mvhl")
1058
+
1059
+
1060
+
1061
+ @classmethod
1062
+ def delete(cls):
1063
+ """Deletes all vehicles from the Midas model."""
1064
+ return MidasAPI("DELETE", "/db/mvhl")
1065
+
1066
+
1067
+ #--------------------------------------------------Load Case--------------------------------------------
1068
+
1069
+ class Case:
1070
+
1071
+ cases = []
1072
+
1073
+ def __init__(self, code: str, case_id: int, params: dict):
1074
+ """
1075
+ Internal constructor for the Case class. User should use country-specific subclasses.
1076
+ """
1077
+ self.code = code
1078
+ self.id = case_id
1079
+ self.params = params
1080
+
1081
+ # Add the new case instance to the class-level list, avoiding duplicates by ID
1082
+ if not any(c.id == self.id for c in self.__class__.cases):
1083
+ self.__class__.cases.append(self)
1084
+
1085
+ @classmethod
1086
+ def create(cls):
1087
+ """
1088
+ Creates moving load cases in the Midas model for all defined country codes.
1089
+ """
1090
+ if not cls.cases:
1091
+ print("No moving load cases to create.")
1092
+ return
1093
+
1094
+ # Separate cases by country code and send them to the appropriate API endpoint
1095
+ country_codes = set(c.code for c in cls.cases)
1096
+ for code in country_codes:
1097
+ cases_to_create = [c for c in cls.cases if c.code == code]
1098
+ if cases_to_create:
1099
+ json_data = cls.json(cases_to_create)
1100
+ endpoint = ""
1101
+ if code == "INDIA":
1102
+ endpoint = "/db/MVLDid"
1103
+ elif code == "EUROCODE":
1104
+ endpoint = "/db/MVLDeu"
1105
+
1106
+ if endpoint:
1107
+ MidasAPI("PUT", endpoint, json_data)
1108
+
1109
+ # Clear the list after creation
1110
+ cls.cases.clear()
1111
+
1112
+ @classmethod
1113
+ def json(cls, case_list=None):
1114
+ """Generates the JSON for a list of cases. Uses all stored cases if list is not provided."""
1115
+ if case_list is None:
1116
+ case_list = cls.cases
1117
+ data = {"Assign": {}}
1118
+ for case in case_list:
1119
+ data["Assign"][str(case.id)] = case.params
1120
+ return data
1121
+
1122
+ @classmethod
1123
+ def get(cls):
1124
+ """
1125
+ Retrieves all moving load cases from the Midas model
1126
+ """
1127
+ all_cases_data = {}
1128
+
1129
+ # Define endpoints and their expected response keys.
1130
+ endpoints = {
1131
+ "/db/MVLDid": "MVLDID",
1132
+ "/db/MVLDeu": "MVLDEU"
1133
+ }
1134
+
1135
+ for endpoint, response_key in endpoints.items():
1136
+ api_data = MidasAPI("GET", endpoint)
1137
+
1138
+ # Check if the response is valid and contains the expected key
1139
+ if api_data and response_key in api_data:
1140
+ # Add the data under its original API key (e.g., "MVLDID")
1141
+ all_cases_data[response_key] = api_data[response_key]
1142
+
1143
+ return all_cases_data
1144
+
1145
+ @classmethod
1146
+ def delete(cls):
1147
+ """Deletes all moving load cases from the Midas model."""
1148
+ all_cases_in_model = cls.get()
1149
+ if not all_cases_in_model:
1150
+ return
1151
+
1152
+ if "MVLDID" in all_cases_in_model:
1153
+ MidasAPI("DELETE", "/db/MVLDid")
1154
+ if "MVLDEU" in all_cases_in_model:
1155
+ MidasAPI("DELETE", "/db/MVLDeu")
1156
+
1157
+ @classmethod
1158
+ def sync(cls):
1159
+ """
1160
+ Synchronizes the load case data from the Midas model
1161
+ """
1162
+ cls.cases = []
1163
+ response = cls.get()
1164
+
1165
+ # Sync India Cases
1166
+ if "MVLDID" in response:
1167
+ for case_id, case_data in response["MVLDID"].items():
1168
+ name = case_data.get("LCNAME")
1169
+ num_lanes = case_data.get("NUM_LOADED_LANES")
1170
+ scale_factor = case_data.get("SCALE_FACTOR")
1171
+
1172
+ # Case 1: Permit Vehicle Load
1173
+ if case_data.get("OPT_LC_FOR_PERMIT_LOAD"):
1174
+ MovingLoad.Case.India(
1175
+ id=int(case_id),
1176
+ name=name,
1177
+ num_loaded_lanes=num_lanes,
1178
+ scale_factor=scale_factor,
1179
+ opt_lc_for_permit=True,
1180
+ permit_vehicle_id=case_data.get("PERMIT_VEHICLE"),
1181
+ ref_lane_id=case_data.get("REF_LANE"),
1182
+ eccentricity=case_data.get("ECCEN"),
1183
+ permit_scale_factor=case_data.get("PERMIT_SCALE_FACTOR")
1184
+ )
1185
+ # Case 2: Auto Live Load Combination
1186
+ elif case_data.get("OPT_AUTO_LL"):
1187
+ sub_items = []
1188
+ for item in case_data.get("SUB_LOAD_ITEMS", []):
1189
+ sub_items.append([
1190
+ item.get("SCALE_FACTOR"),
1191
+ item.get("VEHICLE_CLASS_1"),
1192
+ item.get("VEHICLE_CLASS_2"),
1193
+ item.get("FOOTWAY"),
1194
+ item.get("SELECTED_LANES"),
1195
+ item.get("SELECTED_FOOTWAY_LANES") # Will be None if not present
1196
+ ])
1197
+ MovingLoad.Case.India(
1198
+ id=int(case_id),
1199
+ name=name,
1200
+ num_loaded_lanes=num_lanes,
1201
+ scale_factor=scale_factor,
1202
+ opt_auto_ll=True,
1203
+ sub_load_items=sub_items
1204
+ )
1205
+ # Case 3: General Load
1206
+ else:
1207
+ sub_items = []
1208
+ for item in case_data.get("SUB_LOAD_ITEMS", []):
1209
+ sub_items.append([
1210
+ item.get("SCALE_FACTOR"),
1211
+ item.get("MIN_NUM_LOADED_LANES"),
1212
+ item.get("MAX_NUM_LOADED_LANES"),
1213
+ item.get("VEHICLE_CLASS_1"),
1214
+ item.get("SELECTED_LANES")
1215
+ ])
1216
+ MovingLoad.Case.India(
1217
+ id=int(case_id),
1218
+ name=name,
1219
+ num_loaded_lanes=num_lanes,
1220
+ scale_factor=scale_factor,
1221
+ opt_auto_ll=False,
1222
+ sub_load_items=sub_items
1223
+ )
1224
+
1225
+ # Sync Eurocode Cases
1226
+ if "MVLDEU" in response:
1227
+ for case_id, case_data in response["MVLDEU"].items():
1228
+ # The Eurocode constructor can handle the raw dictionary via **kwargs
1229
+ # by leveraging its fallback mechanism when sub_load_items is not provided.
1230
+ name = case_data.pop("LCNAME")
1231
+ load_model = case_data.pop("TYPE_LOADMODEL")
1232
+ use_optimization = case_data.pop("OPT_AUTO_OPTIMIZE")
1233
+
1234
+ MovingLoad.Case.Eurocode(
1235
+ id=int(case_id),
1236
+ name=name,
1237
+ load_model=load_model,
1238
+ use_optimization=use_optimization,
1239
+ **case_data # Pass the rest of the data as keyword arguments
1240
+ )
1241
+
1242
+ class India:
1243
+ """
1244
+ Defines a Moving Load Case according to Indian standards.
1245
+
1246
+ """
1247
+ def __init__(self,
1248
+ name: str,
1249
+ num_loaded_lanes: int,
1250
+ id: int = None,
1251
+ # --- Switches to select the type of load case ---
1252
+ opt_auto_ll: bool = False,
1253
+ opt_lc_for_permit: bool = False,
1254
+
1255
+ # --- Common and General Load Parameters ---
1256
+ sub_load_items: list = None,
1257
+ scale_factor: list = None,
1258
+
1259
+ # --- Permit Vehicle Specific Parameters ---
1260
+ permit_vehicle_id: int = None,
1261
+ ref_lane_id: int = None,
1262
+ eccentricity: float = None,
1263
+ permit_scale_factor: float = None):
1264
+ """
1265
+
1266
+ name (str): The name of the load case (LCNAME).
1267
+ num_loaded_lanes (int): The number of loaded lanes.
1268
+ id (int, optional): A unique integer ID for the case.
1269
+ opt_auto_ll (bool, optional): Set to True for "Auto Live Load Combinations".
1270
+ opt_lc_for_permit (bool, optional): Set to True for "Load Cases for Permit Vehicle".
1271
+ sub_load_items (list, optional): A list of lists defining sub-loads. The format depends on the selected options:
1272
+
1273
+ *** Case 1: General Load Format (opt_auto_ll=False) ***
1274
+ Each inner list must contain 5 items in this order:
1275
+ 1. Scale Factor (Number)
1276
+ 2. Min. Number of Loaded Lanes (Integer)
1277
+ 3. Max. Number of Loaded Lanes (Integer)
1278
+ 4. Vehicle Name (String, e.g., "Class A")
1279
+ 5. Selected Lanes (list[str], e.g., ["T1", "T2"])
1280
+
1281
+ *** Case 2: Auto Live Load Format (opt_auto_ll=True) ***
1282
+ Each inner list must contain 5-6 items in this order:
1283
+ 1. Scale Factor (Number)
1284
+ 2. Vehicle Class I (String)
1285
+ 3. Vehicle Class II (String)
1286
+ 4. Vehicle Footway (String, "" for none)
1287
+ 5. Selected Carriageway Lanes (list[str])
1288
+ 6. Selected Footway Lanes (list[str]) - Optional, omit or set to None if no footway.
1289
+
1290
+ *** Case 3: Permit Vehicle Format (opt_lc_for_permit=True) ***
1291
+ sub_load_items is not used. Use permit_vehicle_id, ref_lane_id, etc. instead.
1292
+
1293
+ scale_factor (list, optional): A list of 4 numbers for the Multiple Presence Factor. Defaults to [1, 0.9, 0.8, 0.8].
1294
+ permit_vehicle_id (int, optional): The ID of the permit vehicle. Required for permit cases.
1295
+ ref_lane_id (int, optional): The reference lane ID. Required for permit cases.
1296
+ eccentricity (float, optional): Eccentricity for the permit vehicle. Required for permit cases.
1297
+ permit_scale_factor (float, optional): Scale factor for the permit vehicle. Required for permit cases.
1298
+ """
1299
+ if id is None:
1300
+ # Correctly reference the 'cases' list through its full path
1301
+ case_id = (max(c.id for c in MovingLoad.Case.cases) + 1) if MovingLoad.Case.cases else 1
1302
+ else:
1303
+ case_id = id
1304
+ final_scale_factor = scale_factor if scale_factor is not None else [1, 0.9, 0.8, 0.8]
1305
+
1306
+ params = {
1307
+ "LCNAME": name,
1308
+ "DESC": "",
1309
+ "SCALE_FACTOR": final_scale_factor,
1310
+ "NUM_LOADED_LANES": num_loaded_lanes
1311
+ }
1312
+
1313
+ formatted_sub_loads = []
1314
+
1315
+ if opt_lc_for_permit:
1316
+ if any(p is None for p in [permit_vehicle_id, ref_lane_id, eccentricity, permit_scale_factor]):
1317
+ raise ValueError("For Permit Vehicle cases, 'permit_vehicle_id', 'ref_lane_id', 'eccentricity', and 'permit_scale_factor' are required.")
1318
+
1319
+ params.update({
1320
+ "OPT_AUTO_LL": True,
1321
+ "OPT_LC_FOR_PERMIT_LOAD": True,
1322
+ "PERMIT_VEHICLE": permit_vehicle_id,
1323
+ "REF_LANE": ref_lane_id,
1324
+ "ECCEN": eccentricity,
1325
+ "PERMIT_SCALE_FACTOR": permit_scale_factor
1326
+ })
1327
+
1328
+ elif opt_auto_ll:
1329
+ if sub_load_items is None:
1330
+ raise ValueError("For Auto Live Load cases, 'sub_load_items' is required.")
1331
+
1332
+ carriage_way_width = 2.3 if num_loaded_lanes == 1 else 0
1333
+ carriage_way_loading = 4.903325 if num_loaded_lanes == 1 else 0
1334
+
1335
+ for item_list in sub_load_items:
1336
+ sub_load_dict = {
1337
+ "SCALE_FACTOR": item_list[0],
1338
+ "VEHICLE_CLASS_1": item_list[1],
1339
+ "VEHICLE_CLASS_2": item_list[2],
1340
+ "FOOTWAY": item_list[3],
1341
+ "CARRIAGE_WAY_WIDTH": carriage_way_width,
1342
+ "CARRIAGE_WAY_LOADING": carriage_way_loading,
1343
+ "SELECTED_LANES": item_list[4]
1344
+ }
1345
+ if len(item_list) > 5 and item_list[5] is not None:
1346
+ sub_load_dict["SELECTED_FOOTWAY_LANES"] = item_list[5]
1347
+ formatted_sub_loads.append(sub_load_dict)
1348
+
1349
+ params.update({
1350
+ "OPT_AUTO_LL": True,
1351
+ "OPT_LC_FOR_PERMIT_LOAD": False,
1352
+ "SUB_LOAD_ITEMS": formatted_sub_loads
1353
+ })
1354
+
1355
+ else: # General Load
1356
+ if sub_load_items is None:
1357
+ raise ValueError("For General Load cases, 'sub_load_items' is required.")
1358
+
1359
+ for item_list in sub_load_items:
1360
+ formatted_sub_loads.append({
1361
+ "SCALE_FACTOR": item_list[0],
1362
+ "MIN_NUM_LOADED_LANES": item_list[1],
1363
+ "MAX_NUM_LOADED_LANES": item_list[2],
1364
+ "VEHICLE_CLASS_1": item_list[3],
1365
+ "SELECTED_LANES": item_list[4]
1366
+ })
1367
+
1368
+ params.update({
1369
+ "OPT_AUTO_LL": False,
1370
+ "OPT_LC_FOR_PERMIT_LOAD": False,
1371
+ "SUB_LOAD_ITEMS": formatted_sub_loads
1372
+ })
1373
+
1374
+ MovingLoad.Case("INDIA", case_id, params)
1375
+
1376
+ class Eurocode:
1377
+
1378
+ def __init__(self,
1379
+ name: str,
1380
+ load_model: int,
1381
+ use_optimization: bool = False,
1382
+ id: int = None,
1383
+ sub_load_items: list = None,
1384
+ **kwargs):
1385
+ """
1386
+
1387
+ name (str): The name of the load case (LCNAME).
1388
+ load_model (int): The Eurocode Load Model type (1-5).
1389
+ use_optimization (bool, optional): Set to True for "Moving Load Optimization". Defaults to False.
1390
+ id (int, optional): A unique integer ID for the case. Auto-assigned if None.
1391
+ sub_load_items (list, optional): Simplified list input. Format depends on the load_model and use_optimization.
1392
+ **kwargs: Additional individual parameters (for backward compatibility).
1393
+
1394
+ * General Load (use_optimization=False) *
1395
+
1396
+ - load_model = 1: [opt_leading, vhl_name1, vhl_name2, selected_lanes, remaining_area, footway_lanes]
1397
+ Example: [False, "V1", "V2", ["L1"], ["L2"], ["F1"]]
1398
+
1399
+ - load_model = 2: [opt_leading, opt_comb, [(name, sf, min_L, max_L, [lanes]), ...]]
1400
+ Example: [True, 1, [("V_Permit", 1.0, 1, 4, ["L1", "L2"])]]
1401
+
1402
+ - load_model = 3: [opt_leading, vhl_name1, vhl_name2, selected_lanes, remaining_area]
1403
+ Example: [False, "V1", "V3", ["L1", "L2"], ["L3"]]
1404
+
1405
+ - load_model = 4: [opt_leading, vhl_name1, vhl_name2, selected_lanes, remaining_area, straddling_lanes]
1406
+ Where straddling_lanes is a list of dicts: [{'NAME1': 'start', 'NAME2': 'end'}, ...]
1407
+ Example: [False, "V1", "V4", ["L1"], ["L2"], [{"NAME1": "L3", "NAME2": "L4"}]]
1408
+
1409
+ - load_model = 5 (Railway): [opt_psi, opt_comb, [sf1,sf2,sf3], [mf1,mf2,mf3], [(name, sf, min_L, max_L, [lanes]), ...]]
1410
+ Example: [False, 1, [0.8,0.7,0.6], [1,1,0.75], [("Rail-V", 1, 1, 1, ["T1"])]]
1411
+
1412
+ * Moving Load Optimization (use_optimization=True) *
1413
+
1414
+ - load_model = 1: [opt_leading, vhl_name1, vhl_name2, min_dist, opt_lane, loaded_lanes, [selected_lanes]]
1415
+ Example: [False, "V1", "V2", 10, "L1", 2, ["L1", "L2"]]
1416
+
1417
+ - load_model = 2: [opt_leading, opt_comb, min_dist, opt_lane, min_v, max_v, [(name, sf), ...]]
1418
+ Example: [False, 1, 10, "L1", 1, 2, [("V_Permit", 1.0), ("V_Other", 0.8)]]
1419
+
1420
+ - load_model = 3: [opt_leading, vhl_name1, vhl_name2, min_dist, opt_lane, loaded_lanes, [selected_lanes]]
1421
+ Example: [True, "V1", "V3_auto", 10, "L1", 3, ["L1", "L2", "L3"]]
1422
+
1423
+ - load_model = 4: [opt_leading, vhl_name1, vhl_name2, min_dist, opt_lane, loaded_lanes, [selected_lanes], [straddling_lanes]]
1424
+ Example: [True, "V1", "V4_auto", 10, "L2", 3, ["L1", "L3"], [{"NAME1": "L3", "NAME2": "L4"}]]
1425
+
1426
+ - load_model = 5 (Railway): [opt_psi, opt_comb, [sf1,sf2,sf3], [mf1,mf2,mf3], min_dist, opt_lane, min_v, max_v, [(name, sf), ...]]
1427
+ Example: [False, 1, [0.8,0.7,0.6], [1,1,0.75], 20, "T1", 1, 1, [("Rail-V", 1)]]
1428
+ """
1429
+ if id is None:
1430
+ # Correctly reference the 'cases' list through its full path
1431
+ case_id = (max(c.id for c in MovingLoad.Case.cases) + 1) if MovingLoad.Case.cases else 1
1432
+ else:
1433
+ case_id = id
1434
+ params = {
1435
+ "LCNAME": name,
1436
+ "DESC": kwargs.get("DESC", ""),
1437
+ "TYPE_LOADMODEL": load_model,
1438
+ "OPT_AUTO_OPTIMIZE": use_optimization
1439
+ }
1440
+
1441
+ if sub_load_items is None:
1442
+ # Fallback to kwargs for backward compatibility if sub_load_items is not used
1443
+ params.update(kwargs)
1444
+ MovingLoad.Case("EUROCODE", case_id, params)
1445
+ return
1446
+
1447
+ if not use_optimization: # General Load
1448
+ if load_model == 1:
1449
+ params.update({"OPT_LEADING": sub_load_items[0], "VHLNAME1": sub_load_items[1], "VHLNAME2": sub_load_items[2], "SLN_LIST": sub_load_items[3], "SRA_LIST": sub_load_items[4], "FLN_LIST": sub_load_items[5]})
1450
+ elif load_model == 2:
1451
+ sub_loads = [{"TYPE": 2, "NAME": item[0], "SCALE_FACTOR": item[1], "MIN_LOAD_LANE_TYPE": item[2], "MAX_LOAD_LANE_TYPE": item[3], "SLN_LIST": item[4]} for item in sub_load_items[2]]
1452
+ params.update({"OPT_LEADING": sub_load_items[0], "OPT_COMB": sub_load_items[1], "SUB_LOAD_LIST": sub_loads})
1453
+ elif load_model == 3:
1454
+ params.update({"OPT_LEADING": sub_load_items[0], "VHLNAME1": sub_load_items[1], "VHLNAME2": sub_load_items[2], "SLN_LIST": sub_load_items[3], "SRA_LIST": sub_load_items[4]})
1455
+ elif load_model == 4:
1456
+ params.update({"OPT_LEADING": sub_load_items[0], "VHLNAME1": sub_load_items[1], "VHLNAME2": sub_load_items[2], "SLN_LIST": sub_load_items[3], "SRA_LIST": sub_load_items[4], "STL_LIST": sub_load_items[5]})
1457
+ elif load_model == 5:
1458
+ sub_loads = [{"TYPE": 2, "NAME": item[0], "SCALE_FACTOR": item[1], "MIN_LOAD_LANE_TYPE": item[2], "MAX_LOAD_LANE_TYPE": item[3], "SLN_LIST": item[4]} for item in sub_load_items[4]]
1459
+ params.update({"OPT_PSI_FACTOR": sub_load_items[0], "OPT_COMB": sub_load_items[1], "SCALE_FACTOR1": sub_load_items[2][0], "SCALE_FACTOR2": sub_load_items[2][1], "SCALE_FACTOR3": sub_load_items[2][2], "MULTI_FACTOR1": sub_load_items[3][0], "MULTI_FACTOR2": sub_load_items[3][1], "MULTI_FACTOR3": sub_load_items[3][2], "SUB_LOAD_LIST": sub_loads})
1460
+
1461
+ else: # Moving Load Optimization
1462
+ if load_model == 1:
1463
+ params.update({"OPT_LEADING": sub_load_items[0], "VHLNAME1": sub_load_items[1], "VHLNAME2": sub_load_items[2], "MINVHLDIST": sub_load_items[3], "OPTIMIZE_LANE_NAME": sub_load_items[4], "LOADEDLANE": sub_load_items[5], "SLN_LIST": sub_load_items[6]})
1464
+ elif load_model == 2:
1465
+ opt_list = [{"TYPE": 2, "NAME": item[0], "SCALE_FACTOR": item[1]} for item in sub_load_items[6]]
1466
+ params.update({"OPT_LEADING": sub_load_items[0], "OPT_COMB": sub_load_items[1], "MINVHLDIST": sub_load_items[2], "OPTIMIZE_LANE_NAME": sub_load_items[3], "MIN_NUM_VHL": sub_load_items[4], "MAX_NUM_VHL": sub_load_items[5], "OPTIMIZE_LIST": opt_list})
1467
+ elif load_model == 3:
1468
+ params.update({"OPT_LEADING": sub_load_items[0], "VHLNAME1": sub_load_items[1], "VHLNAME2": sub_load_items[2], "MINVHLDIST": sub_load_items[3], "OPTIMIZE_LANE_NAME": sub_load_items[4], "LOADEDLANE": sub_load_items[5], "SLN_LIST": sub_load_items[6]})
1469
+ elif load_model == 4:
1470
+ params.update({"OPT_LEADING": sub_load_items[0], "VHLNAME1": sub_load_items[1], "VHLNAME2": sub_load_items[2], "MINVHLDIST": sub_load_items[3], "OPTIMIZE_LANE_NAME": sub_load_items[4], "LOADEDLANE": sub_load_items[5], "SLN_LIST": sub_load_items[6], "STL_LIST": sub_load_items[7]})
1471
+ elif load_model == 5:
1472
+ opt_list = [{"TYPE": 2, "NAME": item[0], "SCALE_FACTOR": item[1]} for item in sub_load_items[8]]
1473
+ params.update({"OPT_PSI_FACTOR": sub_load_items[0], "OPT_COMB": sub_load_items[1], "SCALE_FACTOR1": sub_load_items[2][0], "SCALE_FACTOR2": sub_load_items[2][1], "SCALE_FACTOR3": sub_load_items[2][2], "MULTI_FACTOR1": sub_load_items[3][0], "MULTI_FACTOR2": sub_load_items[3][1], "MULTI_FACTOR3": sub_load_items[3][2], "MINVHLDIST": sub_load_items[4], "OPTIMIZE_LANE_NAME": sub_load_items[5], "MIN_NUM_VHL": sub_load_items[6], "MAX_NUM_VHL": sub_load_items[7], "OPTIMIZE_LIST": opt_list})
1474
+
1475
+ MovingLoad.Case("EUROCODE", case_id, params)
1476
+
1477
+
1478
+
1479
+ #--------------------------------------Test---------------------------------------------