midas-civil 0.1.4__py3-none-any.whl → 0.1.5__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.

Potentially problematic release.


This version of midas-civil might be problematic. Click here for more details.

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