RCAIDE-GUI 1.0.0__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 (170) hide show
  1. common_widgets/__init__.py +6 -0
  2. common_widgets/animated_toggle.py +141 -0
  3. common_widgets/color.py +17 -0
  4. common_widgets/data_entry_widget.py +337 -0
  5. common_widgets/image_widget.py +21 -0
  6. common_widgets/unit_picker_widget.py +48 -0
  7. main.py +168 -0
  8. rcaide_gui-1.0.0.dist-info/METADATA +96 -0
  9. rcaide_gui-1.0.0.dist-info/RECORD +170 -0
  10. rcaide_gui-1.0.0.dist-info/WHEEL +5 -0
  11. rcaide_gui-1.0.0.dist-info/entry_points.txt +2 -0
  12. rcaide_gui-1.0.0.dist-info/top_level.txt +5 -0
  13. rcaide_io.py +818 -0
  14. tabs/__init__.py +10 -0
  15. tabs/aircraft_configs/__init__.py +3 -0
  16. tabs/aircraft_configs/aircraft_configs.py +301 -0
  17. tabs/analysis/__init__.py +4 -0
  18. tabs/analysis/analysis.py +92 -0
  19. tabs/analysis/widgets/__init__.py +13 -0
  20. tabs/analysis/widgets/aeroacoustics_widget.py +158 -0
  21. tabs/analysis/widgets/aerodynamics_widget.py +108 -0
  22. tabs/analysis/widgets/analysis_data_widget.py +29 -0
  23. tabs/analysis/widgets/atmosphere_widget.py +49 -0
  24. tabs/analysis/widgets/costs_widget.py +35 -0
  25. tabs/analysis/widgets/energy_widget.py +32 -0
  26. tabs/analysis/widgets/geometry_widget.py +55 -0
  27. tabs/analysis/widgets/planets_widget.py +34 -0
  28. tabs/analysis/widgets/propulsion_widget.py +53 -0
  29. tabs/analysis/widgets/stability_widget.py +34 -0
  30. tabs/analysis/widgets/weights_widget.py +94 -0
  31. tabs/geometry/__init__.py +6 -0
  32. tabs/geometry/aircraft_configs.py +232 -0
  33. tabs/geometry/frames/__init__.py +15 -0
  34. tabs/geometry/frames/booms/__init__.py +5 -0
  35. tabs/geometry/frames/booms/boom_frame.py +238 -0
  36. tabs/geometry/frames/cargo_bays/__init__.py +5 -0
  37. tabs/geometry/frames/cargo_bays/cargo_bay_frame.py +159 -0
  38. tabs/geometry/frames/default_frame.py +23 -0
  39. tabs/geometry/frames/energy_network/__init__.py +2 -0
  40. tabs/geometry/frames/energy_network/turbofan_network/__init__.py +3 -0
  41. tabs/geometry/frames/energy_network/turbofan_network/frames/__init__.py +1 -0
  42. tabs/geometry/frames/energy_network/turbofan_network/widgets/__init__.py +1 -0
  43. tabs/geometry/frames/fuselages/__init__.py +5 -0
  44. tabs/geometry/frames/fuselages/fuselage_frame.py +294 -0
  45. tabs/geometry/frames/geometry_frame.py +91 -0
  46. tabs/geometry/frames/landing_gears/__init__.py +5 -0
  47. tabs/geometry/frames/landing_gears/landing_gear_frame.py +189 -0
  48. tabs/geometry/frames/nacelles/__init__.py +1 -0
  49. tabs/geometry/frames/powertrain/__init__.py +6 -0
  50. tabs/geometry/frames/powertrain/converters/__init__.py +5 -0
  51. tabs/geometry/frames/powertrain/converters/converter_frame.py +137 -0
  52. tabs/geometry/frames/powertrain/distributors/__init__.py +5 -0
  53. tabs/geometry/frames/powertrain/distributors/distributor_frame.py +143 -0
  54. tabs/geometry/frames/powertrain/nacelles/__init__.py +5 -0
  55. tabs/geometry/frames/powertrain/nacelles/nacelle_frame.py +283 -0
  56. tabs/geometry/frames/powertrain/powertrain_frame.py +259 -0
  57. tabs/geometry/frames/powertrain/propulsors/__init__.py +5 -0
  58. tabs/geometry/frames/powertrain/propulsors/propulsor_frame.py +146 -0
  59. tabs/geometry/frames/powertrain/sources/__init__.py +6 -0
  60. tabs/geometry/frames/powertrain/sources/energy_source_frame.py +147 -0
  61. tabs/geometry/frames/vehicle_frame.py +171 -0
  62. tabs/geometry/frames/wings/__init__.py +5 -0
  63. tabs/geometry/frames/wings/wings_frame.py +457 -0
  64. tabs/geometry/geometry.py +513 -0
  65. tabs/geometry/widgets/__init__.py +12 -0
  66. tabs/geometry/widgets/booms/__init__.py +6 -0
  67. tabs/geometry/widgets/booms/boom_section_widget.py +109 -0
  68. tabs/geometry/widgets/cargo_bays/__init__.py +1 -0
  69. tabs/geometry/widgets/fuselages/__init__.py +8 -0
  70. tabs/geometry/widgets/fuselages/cabin_class_widget.py +188 -0
  71. tabs/geometry/widgets/fuselages/cabin_widget.py +217 -0
  72. tabs/geometry/widgets/fuselages/fuselage_section_widget.py +190 -0
  73. tabs/geometry/widgets/geometry_data_widget.py +25 -0
  74. tabs/geometry/widgets/landing_gears/__init__.py +6 -0
  75. tabs/geometry/widgets/nacelles/__init__.py +1 -0
  76. tabs/geometry/widgets/powertrain/__init__.py +9 -0
  77. tabs/geometry/widgets/powertrain/converters/__init__.py +5 -0
  78. tabs/geometry/widgets/powertrain/converters/turboelectric_generator_widget.py +74 -0
  79. tabs/geometry/widgets/powertrain/distributors/__init__.py +5 -0
  80. tabs/geometry/widgets/powertrain/distributors/fuel_line_widget.py +81 -0
  81. tabs/geometry/widgets/powertrain/modulators/__init__.py +5 -0
  82. tabs/geometry/widgets/powertrain/modulators/esc_widget.py +58 -0
  83. tabs/geometry/widgets/powertrain/nacelles/__init__.py +5 -0
  84. tabs/geometry/widgets/powertrain/nacelles/nacelle_section_widget.py +115 -0
  85. tabs/geometry/widgets/powertrain/powertrain_connector_widget.py +136 -0
  86. tabs/geometry/widgets/powertrain/powertrain_widget.py +206 -0
  87. tabs/geometry/widgets/powertrain/propulsors/__init__.py +5 -0
  88. tabs/geometry/widgets/powertrain/propulsors/turbofan_widget.py +462 -0
  89. tabs/geometry/widgets/powertrain/sources/__init__.py +6 -0
  90. tabs/geometry/widgets/powertrain/sources/fuel_tank_widget.py +103 -0
  91. tabs/geometry/widgets/powertrain/sources/source_selector_widget.py +35 -0
  92. tabs/geometry/widgets/wings/__init__.py +7 -0
  93. tabs/geometry/widgets/wings/wing_cs_widget.py +198 -0
  94. tabs/geometry/widgets/wings/wing_section_widget.py +298 -0
  95. tabs/home/__init__.py +3 -0
  96. tabs/home/home.py +585 -0
  97. tabs/mission/__init__.py +4 -0
  98. tabs/mission/mission.py +1124 -0
  99. tabs/mission/widgets/__init__.py +3 -0
  100. tabs/mission/widgets/flight_controls_widget.py +186 -0
  101. tabs/mission/widgets/mission_analysis_widget.py +166 -0
  102. tabs/mission/widgets/mission_segment_helper.py +358 -0
  103. tabs/mission/widgets/mission_segment_widget.py +358 -0
  104. tabs/solve/__init__.py +4 -0
  105. tabs/solve/plots/__init__.py +12 -0
  106. tabs/solve/plots/aeroacoustics/__init__.py +6 -0
  107. tabs/solve/plots/aeroacoustics/plot_2D_noise_contour.py +184 -0
  108. tabs/solve/plots/aeroacoustics/plot_3D_noise_contour.py +219 -0
  109. tabs/solve/plots/aeroacoustics/plot_noise_certification_contour.py +115 -0
  110. tabs/solve/plots/aeroacoustics/plot_noise_level.py +118 -0
  111. tabs/solve/plots/aerodynamics/__init__.py +10 -0
  112. tabs/solve/plots/aerodynamics/plot_aerodynamic_coefficients.py +156 -0
  113. tabs/solve/plots/aerodynamics/plot_aerodynamic_forces.py +155 -0
  114. tabs/solve/plots/aerodynamics/plot_aircraft_aerodynamics.py +136 -0
  115. tabs/solve/plots/aerodynamics/plot_drag_components.py +157 -0
  116. tabs/solve/plots/aerodynamics/plot_lift_distribution.py +107 -0
  117. tabs/solve/plots/aerodynamics/plot_rotor_conditions.py +255 -0
  118. tabs/solve/plots/aerodynamics/plot_rotor_disc_performance.py +144 -0
  119. tabs/solve/plots/aerodynamics/plot_rotor_performance.py +124 -0
  120. tabs/solve/plots/common/__init__.py +4 -0
  121. tabs/solve/plots/common/plot_style.py +71 -0
  122. tabs/solve/plots/common/set_axes.py +55 -0
  123. tabs/solve/plots/create_plot_widgets.py +34 -0
  124. tabs/solve/plots/emissions/__init__.py +3 -0
  125. tabs/solve/plots/emissions/plot_emissions.py +167 -0
  126. tabs/solve/plots/energy/__init__.py +12 -0
  127. tabs/solve/plots/energy/plot_altitude_sfc_weight.py +134 -0
  128. tabs/solve/plots/energy/plot_battery_cell_conditions.py +154 -0
  129. tabs/solve/plots/energy/plot_battery_degradation.py +157 -0
  130. tabs/solve/plots/energy/plot_battery_module_C_rates.py +139 -0
  131. tabs/solve/plots/energy/plot_battery_module_conditions.py +157 -0
  132. tabs/solve/plots/energy/plot_battery_pack_conditions.py +164 -0
  133. tabs/solve/plots/energy/plot_battery_ragone_diagram.py +97 -0
  134. tabs/solve/plots/energy/plot_battery_temperature.py +136 -0
  135. tabs/solve/plots/energy/plot_electric_propulsor_efficiencies.py +136 -0
  136. tabs/solve/plots/energy/plot_propulsor_throttles.py +119 -0
  137. tabs/solve/plots/mission/__init__.py +5 -0
  138. tabs/solve/plots/mission/plot_aircraft_velocities.py +145 -0
  139. tabs/solve/plots/mission/plot_flight_conditions.py +236 -0
  140. tabs/solve/plots/mission/plot_flight_trajectory.py +172 -0
  141. tabs/solve/plots/stability/__init__.py +5 -0
  142. tabs/solve/plots/stability/plot_flight_forces_and_moments.py +163 -0
  143. tabs/solve/plots/stability/plot_lateral_stability.py +151 -0
  144. tabs/solve/plots/stability/plot_longitudinal_stability.py +173 -0
  145. tabs/solve/plots/thermal_management/__init__.py +7 -0
  146. tabs/solve/plots/thermal_management/plot_air_cooled_conditions.py +152 -0
  147. tabs/solve/plots/thermal_management/plot_cross_flow_heat_exchanger_conditions.py +185 -0
  148. tabs/solve/plots/thermal_management/plot_reservoir_conditions.py +141 -0
  149. tabs/solve/plots/thermal_management/plot_thermal_management_performance.py +105 -0
  150. tabs/solve/plots/thermal_management/plot_wavy_channel_conditions.py +161 -0
  151. tabs/solve/plots/weights/__init__.py +4 -0
  152. tabs/solve/plots/weights/plot_load_diagram.py +163 -0
  153. tabs/solve/plots/weights/plot_weight_breakdown.py +164 -0
  154. tabs/solve/solve.py +1498 -0
  155. tabs/style_sheet.py +114 -0
  156. tabs/tab_widget.py +14 -0
  157. tabs/visualize_geometry/__init__.py +4 -0
  158. tabs/visualize_geometry/core_3d_viewer.py +253 -0
  159. tabs/visualize_geometry/features/__init__.py +10 -0
  160. tabs/visualize_geometry/features/axes_gizmo.py +173 -0
  161. tabs/visualize_geometry/features/background.py +161 -0
  162. tabs/visualize_geometry/features/blueprint.py +685 -0
  163. tabs/visualize_geometry/features/camera.py +364 -0
  164. tabs/visualize_geometry/features/drag_aircraft.py +219 -0
  165. tabs/visualize_geometry/features/grid.py +194 -0
  166. tabs/visualize_geometry/features/measurement.py +367 -0
  167. tabs/visualize_geometry/features/screenshot.py +91 -0
  168. tabs/visualize_geometry/geometry_helper_functions.py +3 -0
  169. tabs/visualize_geometry/visualize_geometry.py +731 -0
  170. utilities.py +257 -0
rcaide_io.py ADDED
@@ -0,0 +1,818 @@
1
+ import RCAIDE
2
+ import json
3
+ import os
4
+ import importlib
5
+ import numpy as np
6
+ import types as _types_module
7
+ from collections import OrderedDict
8
+ from RCAIDE.load import read_RCAIDE_json_dict
9
+ from RCAIDE.Framework.Core import Data, DataOrdered
10
+
11
+ _ROOT = os.path.dirname(os.path.abspath(__file__))
12
+ _AIRCRAFT_DIR = os.path.join(_ROOT, "app_data", "aircraft")
13
+ _AIRFOIL_DIR = os.path.join(_ROOT, "app_data", "airfoils")
14
+
15
+
16
+ # ----------------------------------------------------------------------------------------------------------------------
17
+ # Utility helpers
18
+ # ----------------------------------------------------------------------------------------------------------------------
19
+
20
+ def is_mapping(value):
21
+ return hasattr(value, "items") and hasattr(value, "__setitem__")
22
+
23
+
24
+ def has_key(value, key):
25
+ return hasattr(value, "keys") and key in value.keys()
26
+
27
+
28
+ def is_unit_argument_pair(value):
29
+ return (
30
+ isinstance(value, list)
31
+ and len(value) == 2
32
+ and isinstance(value[1], int)
33
+ and not isinstance(value[1], bool)
34
+ )
35
+
36
+
37
+ def make_json_safe(value):
38
+ if is_mapping(value):
39
+ safe = OrderedDict()
40
+ for key, item in value.items():
41
+ if isinstance(key, type):
42
+ key = key.__name__
43
+ safe[str(key)] = make_json_safe(item)
44
+ return safe
45
+ if isinstance(value, list):
46
+ return [make_json_safe(item) for item in value]
47
+ if isinstance(value, tuple):
48
+ return [make_json_safe(item) for item in value]
49
+ return value
50
+
51
+
52
+ def strip_unit_arguments(value):
53
+ if is_mapping(value):
54
+ clean = OrderedDict()
55
+ for key, item in value.items():
56
+ clean[key] = strip_unit_arguments(item)
57
+ return clean
58
+ if is_unit_argument_pair(value):
59
+ return strip_unit_arguments(value[0])
60
+ if isinstance(value, list):
61
+ return [strip_unit_arguments(item) for item in value]
62
+ return value
63
+
64
+
65
+ def add_default_unit_arguments(value):
66
+ if is_mapping(value):
67
+ wrapped = OrderedDict()
68
+ for key, item in value.items():
69
+ wrapped[key] = add_default_unit_arguments(item)
70
+ return wrapped
71
+ if is_unit_argument_pair(value):
72
+ return value
73
+ if isinstance(value, str):
74
+ return value # strings stay plain to match export_rcaide_data format
75
+ if isinstance(value, list):
76
+ safe_items = [make_json_safe(item) for item in value]
77
+ if all(is_mapping(item) for item in safe_items):
78
+ return [add_default_unit_arguments(item) for item in safe_items]
79
+ return [safe_items, 0]
80
+ if isinstance(value, tuple):
81
+ return [make_json_safe(value), 0]
82
+ return [value, 0]
83
+
84
+
85
+ # ----------------------------------------------------------------------------------------------------------------------
86
+ # Local file-path repair (airfoil coordinate files)
87
+ # ----------------------------------------------------------------------------------------------------------------------
88
+
89
+ def repair_local_file_paths(value, source_dir=None):
90
+ if is_mapping(value):
91
+ for key, item in value.items():
92
+ if key == "coordinate_file" and isinstance(item, str):
93
+ value[key] = repair_airfoil_path(item, source_dir)
94
+ else:
95
+ repair_local_file_paths(item, source_dir)
96
+ elif isinstance(value, list):
97
+ for item in value:
98
+ repair_local_file_paths(item, source_dir)
99
+
100
+
101
+ def repair_airfoil_path(path, source_dir=None):
102
+ if not path or os.path.exists(path):
103
+ return path
104
+ basename = os.path.basename(path)
105
+ if source_dir:
106
+ candidate = os.path.join(source_dir, basename)
107
+ if os.path.exists(candidate):
108
+ return candidate
109
+ for search_dir in (_AIRCRAFT_DIR, _AIRFOIL_DIR):
110
+ candidate = os.path.join(search_dir, basename)
111
+ if os.path.exists(candidate):
112
+ return candidate
113
+ return path
114
+
115
+
116
+ # ----------------------------------------------------------------------------------------------------------------------
117
+ # Type-aware class resolution
118
+ # ----------------------------------------------------------------------------------------------------------------------
119
+
120
+ def _class_for_type_string(type_str):
121
+ """
122
+ Return the Python class for a fully-qualified '__type__' string.
123
+
124
+ Handles nested classes (e.g. 'RCAIDE.Library.Components.Wings.Wing.Container')
125
+ by trying progressively shorter module prefixes until the import succeeds and
126
+ the remaining parts resolve as class attributes on the module.
127
+ """
128
+ if not type_str or not isinstance(type_str, str):
129
+ return None
130
+ parts = type_str.split('.')
131
+ for i in range(len(parts) - 1, 0, -1):
132
+ module_path = '.'.join(parts[:i])
133
+ attr_chain = parts[i:]
134
+ try:
135
+ mod = importlib.import_module(module_path)
136
+ obj = mod
137
+ for attr in attr_chain:
138
+ obj = getattr(obj, attr)
139
+ if isinstance(obj, type):
140
+ return obj
141
+ except Exception:
142
+ continue
143
+ return None
144
+
145
+
146
+ def _type_str(data):
147
+ """Extract the __type__ string from a Data object or plain dict, if present."""
148
+ if hasattr(data, 'get'):
149
+ return data.get('__type__') or ''
150
+ return getattr(data, '__type__', '') or ''
151
+
152
+
153
+ # ----------------------------------------------------------------------------------------------------------------------
154
+ # Generic typed-component maker
155
+ # ----------------------------------------------------------------------------------------------------------------------
156
+
157
+ def _make_typed_component(data, fallback_cls=None):
158
+ """
159
+ Instantiate the correct RCAIDE class from *data*.
160
+
161
+ Resolution order
162
+ ----------------
163
+ 1. Already the right type → return as-is (if fallback_cls given).
164
+ 2. '__type__' field present → import class and call cls(); update from data.
165
+ 3. fallback_cls given → call fallback_cls(); update from data.
166
+ 4. Return data unchanged.
167
+ """
168
+ if fallback_cls is not None and isinstance(data, fallback_cls):
169
+ return data
170
+
171
+ ts = _type_str(data)
172
+ if ts:
173
+ cls = _class_for_type_string(ts)
174
+ if cls is not None and cls is not Data and cls is not DataOrdered:
175
+ if not (getattr(cls, '__module__', '') or '').startswith('RCAIDE.Framework.Core'):
176
+ try:
177
+ obj = cls()
178
+ obj.update(data)
179
+ return obj
180
+ except Exception:
181
+ pass
182
+
183
+ if fallback_cls is not None:
184
+ try:
185
+ obj = fallback_cls()
186
+ obj.update(data)
187
+ return obj
188
+ except Exception:
189
+ pass
190
+
191
+ return data
192
+
193
+
194
+
195
+ # ----------------------------------------------------------------------------------------------------------------------
196
+ # Vehicle-container restoration (single table-driven function)
197
+ # ----------------------------------------------------------------------------------------------------------------------
198
+
199
+ # Each entry: (container_key, base_class, root_map_attr, _unused)
200
+ # Class selection is driven by __type__; base_class is the graceful fallback.
201
+ # To add a new top-level component type, append one row here.
202
+ _VEHICLE_CONTAINER_TABLE = [
203
+ ('fuselages', RCAIDE.Library.Components.Fuselages.Fuselage, '_component_root_map', None),
204
+ ('booms', RCAIDE.Library.Components.Booms.Boom, '_component_root_map', None),
205
+ ('landing_gears', RCAIDE.Library.Components.Landing_Gear.Landing_Gear, '_component_root_map', None),
206
+ ('cargo_bays', RCAIDE.Library.Components.Cargo_Bays.Cargo_Bay, '_component_root_map', None),
207
+ ('wings', RCAIDE.Library.Components.Wings.Wing, '_component_root_map', None),
208
+ ('nacelles', RCAIDE.Library.Components.Nacelles.Nacelle, '_component_root_map', None),
209
+ ('networks', RCAIDE.Framework.Networks.Network, '_energy_network_root_map', None),
210
+ ]
211
+
212
+
213
+ def restore_vehicle_components(vehicle_obj):
214
+ """
215
+ Single-pass restoration of all typed vehicle-level component containers.
216
+
217
+ Iterates _VEHICLE_CONTAINER_TABLE so that every top-level container
218
+ (fuselages, wings, networks, etc.) is rebuilt with correctly-typed RCAIDE
219
+ instances. Class selection is driven entirely by the __type__ field written
220
+ by export_rcaide_data / write_to_json. If __type__ is absent or cannot be
221
+ resolved, the entry's base class is used as a graceful fallback.
222
+ """
223
+ for container_key, base_cls, map_attr, _ in _VEHICLE_CONTAINER_TABLE:
224
+ if not has_key(vehicle_obj, container_key) or not is_mapping(vehicle_obj[container_key]):
225
+ continue
226
+
227
+ container_cls = getattr(base_cls, 'Container', None)
228
+ if container_cls is None:
229
+ continue
230
+
231
+ restored = container_cls()
232
+ for _key, item in list(vehicle_obj[container_key].items()):
233
+ if not is_mapping(item):
234
+ continue
235
+ restored.append(_make_typed_component(item, base_cls))
236
+
237
+ vehicle_obj[container_key] = restored
238
+ root_map = getattr(vehicle_obj, map_attr, None)
239
+ if root_map is not None:
240
+ root_map[base_cls] = restored
241
+
242
+
243
+ # ----------------------------------------------------------------------------------------------------------------------
244
+ # Airfoil restoration (walks the full tree — kept separate because airfoils
245
+ # are embedded as attributes of wings/nacelles, not top-level containers)
246
+ # ----------------------------------------------------------------------------------------------------------------------
247
+
248
+ def restore_airfoil_components(value):
249
+ if is_mapping(value):
250
+ for key, item in list(value.items()):
251
+ if key == "airfoil" and _is_serialized_airfoil(item):
252
+ value[key] = _make_typed_component(item, RCAIDE.Library.Components.Airfoils.Airfoil)
253
+ else:
254
+ restore_airfoil_components(item)
255
+ elif isinstance(value, list):
256
+ for item in value:
257
+ restore_airfoil_components(item)
258
+
259
+
260
+ def _is_serialized_airfoil(value):
261
+ return is_mapping(value) and (
262
+ has_key(value, "coordinate_file") or has_key(value, "NACA_4_Series_code")
263
+ )
264
+
265
+
266
+ # ----------------------------------------------------------------------------------------------------------------------
267
+ # Generic sub-component restoration (Fan, Combustor, Fuel_Line, etc.)
268
+ # ----------------------------------------------------------------------------------------------------------------------
269
+
270
+ _SKIP_KEYS = frozenset({'__type__', '_component_root_map', '_energy_network_root_map'})
271
+
272
+
273
+ def restore_typed_subcomponents(obj):
274
+ """
275
+ Recursively replace raw DataOrdered objects with properly-typed RCAIDE instances.
276
+
277
+ Runs after restore_vehicle_components so that all nested sub-components
278
+ (Fan, Combustor, Fuel_Line, Compressor, …) whose types are recorded in the
279
+ __type__ field are also correctly typed. Only objects that are still the
280
+ raw DataOrdered produced by read_RCAIDE_json_dict are touched; already-typed
281
+ objects are left unchanged.
282
+ """
283
+ if not is_mapping(obj):
284
+ return
285
+
286
+ for key in list(obj.keys()):
287
+ if key in _SKIP_KEYS:
288
+ continue
289
+ child = obj[key]
290
+ if not is_mapping(child):
291
+ continue
292
+
293
+ restore_typed_subcomponents(child) # depth-first
294
+
295
+ if type(child) is not DataOrdered: # already typed → skip
296
+ continue
297
+
298
+ ts = child.get('__type__') if hasattr(child, 'get') else None
299
+ if not ts or not isinstance(ts, str):
300
+ continue
301
+
302
+ cls = _class_for_type_string(ts)
303
+ if cls is None or cls is Data or cls is DataOrdered:
304
+ continue
305
+ if (getattr(cls, '__module__', '') or '').startswith('RCAIDE.Framework.Core'):
306
+ continue
307
+
308
+ try:
309
+ new_obj = cls()
310
+ for k, v in child.items():
311
+ if k != '__type__':
312
+ new_obj[k] = v
313
+ obj[key] = new_obj
314
+ except Exception:
315
+ pass
316
+
317
+
318
+ # ----------------------------------------------------------------------------------------------------------------------
319
+ # JSON serialisation (write side)
320
+ # ----------------------------------------------------------------------------------------------------------------------
321
+
322
+ def _build_dict_r_with_types(v):
323
+ """Serialise a RCAIDE value to a plain Python structure, recording __type__ for containers."""
324
+ tv = type(v)
325
+ if tv is type or tv is _types_module.FunctionType:
326
+ return None
327
+ if tv in (str, bool) or tv is type(None):
328
+ return v
329
+ if tv in (float, int):
330
+ return v
331
+ if tv in (np.ndarray, np.float64):
332
+ return v.tolist()
333
+ if tv is list:
334
+ return v
335
+
336
+ try:
337
+ keys = v.keys()
338
+ except AttributeError:
339
+ return None if callable(tv) else None
340
+
341
+ ret = {}
342
+ module = getattr(tv, '__module__', '') or ''
343
+ qualname = getattr(tv, '__qualname__', '') or ''
344
+ if module and qualname and not qualname.startswith('<'):
345
+ ret['__type__'] = f"{module}.{qualname}"
346
+
347
+ _skip = ('_component_root_map', '_energy_network_root_map', '_base', '_diff')
348
+ for k in keys:
349
+ if k in _skip:
350
+ continue
351
+ ret[k] = _build_dict_r_with_types(v[k])
352
+ return ret
353
+
354
+
355
+ def _build_dict_base_with_types(base):
356
+ """Top-level serialisation: like RCAIDE.save.build_dict_base but includes __type__."""
357
+ _skip = ('_component_root_map', '_energy_network_root_map', '_base', '_diff')
358
+ base_dict = {}
359
+ for k in base.keys():
360
+ if k in _skip:
361
+ continue
362
+ base_dict[k] = _build_dict_r_with_types(base[k])
363
+ return base_dict
364
+
365
+
366
+ _WING_TYPE_LABELS = {
367
+ 'Main_Wing': 'Main Wing',
368
+ 'Horizontal_Tail': 'Horizontal Tail',
369
+ 'Vertical_Tail': 'Vertical Tail',
370
+ }
371
+
372
+ _FUSELAGE_SEGMENT_TYPE_LABELS = {
373
+ 'Circle_Segment': 'Circle Segment',
374
+ 'Ellipse_Segment': 'Ellipse Segment',
375
+ 'Rounded_Rectangle_Segment': 'Rounded Rectangle Segment',
376
+ 'Super_Ellipse_Segment': 'Super Ellipse Segment',
377
+ 'Segment': 'Segment',
378
+ }
379
+
380
+ def wing_type_label_for_ui(component_dict):
381
+ """Derive the GUI wing-type label from __type__, falling back to 'Wing'."""
382
+ type_str = component_dict.get('__type__', '')
383
+ class_name = type_str.rsplit('.', 1)[-1] if type_str else ''
384
+ return _WING_TYPE_LABELS.get(class_name, 'Wing')
385
+
386
+
387
+ def fuselage_segment_type_label_for_ui(segment_dict):
388
+ """Derive the GUI fuselage segment label from __type__, falling back to Segment."""
389
+ type_str = segment_dict.get('__type__', '')
390
+ class_name = type_str.rsplit('.', 1)[-1] if type_str else ''
391
+ return _FUSELAGE_SEGMENT_TYPE_LABELS.get(class_name, 'Segment')
392
+
393
+
394
+ # ----------------------------------------------------------------------------------------------------------------------
395
+ # Global state
396
+ # ----------------------------------------------------------------------------------------------------------------------
397
+
398
+ def new_rcaide_vehicle_data():
399
+ return [[] for _ in range(7)]
400
+
401
+
402
+ rcaide_vehicle = new_rcaide_vehicle_data()
403
+ propulsor_names = [[]]
404
+ vehicle = RCAIDE.Vehicle()
405
+ current_file_path = "" # path of the last loaded or saved JSON file
406
+
407
+ config_data = []
408
+ rcaide_configs = RCAIDE.Library.Components.Configs.Config.Container() # type: ignore
409
+
410
+ analysis_data = []
411
+ rcaide_analyses = RCAIDE.Framework.Analyses.Analysis.Container() # type: ignore
412
+
413
+ mission_data = []
414
+ rcaide_mission = RCAIDE.Framework.Mission.Sequential_Segments()
415
+
416
+
417
+ # ----------------------------------------------------------------------------------------------------------------------
418
+ # Vehicle → UI conversion helpers
419
+ # ----------------------------------------------------------------------------------------------------------------------
420
+
421
+ def vehicle_to_ui_format(vehicle_obj):
422
+ """Convert a RCAIDE vehicle object to UI format for display in frames."""
423
+ from RCAIDE.save import build_dict_base
424
+ from tabs.geometry.frames import VehicleFrame
425
+
426
+ vehicle_dict = make_json_safe(build_dict_base(vehicle_obj))
427
+ ui_dict = {"name": getattr(vehicle_obj, "tag", "")}
428
+
429
+ for ui_label, units, rcaide_path in VehicleFrame.data_units_labels:
430
+ try:
431
+ value = vehicle_dict
432
+ for key in rcaide_path.split("."):
433
+ value = value[key]
434
+ ui_dict[ui_label] = [value, 0]
435
+ except (KeyError, TypeError):
436
+ ui_dict[ui_label] = [0, 0]
437
+
438
+ return ui_dict
439
+
440
+
441
+ def vehicle_dict_to_ui_list_structure(vehicle_dict):
442
+ """Convert a stripped rcaide_vehicle dict to the 7-slot UI structure."""
443
+ from tabs.geometry.frames.booms.boom_frame import BoomFrame
444
+ from tabs.geometry.frames.cargo_bays.cargo_bay_frame import CargoBayFrame
445
+ from tabs.geometry.frames.fuselages.fuselage_frame import FuselageFrame
446
+ from tabs.geometry.frames.landing_gears.landing_gear_frame import LandingGearFrame
447
+ from tabs.geometry.frames.powertrain.powertrain_frame import PowertrainFrame
448
+ from tabs.geometry.frames.wings.wings_frame import WingsFrame
449
+
450
+ def wing_segment_to_ui(segment):
451
+ return {
452
+ "Segment Name": segment.get("tag", ""),
453
+ "Percent Span Location": [segment.get("percent_span_location", 0), 0],
454
+ "Twist": [segment.get("twist", 0), 0],
455
+ "Root Chord Percent": [segment.get("root_chord_percent", 0), 0],
456
+ "Thickness to Chord": [segment.get("thickness_to_chord", 0), 0],
457
+ "Dihedral Outboard": [segment.get("dihedral_outboard", 0), 0],
458
+ "Quarter Chord Sweep": [segment.get("sweeps", {}).get("quarter_chord", 0), 0],
459
+ "Has Fuel Tank": [bool(segment.get("Fuel_Tank")), 0],
460
+ "Has Aft Fuel Tank": [bool(segment.get("Aft_Fuel_Tank")), 0],
461
+ "Airfoil Type": None,
462
+ }
463
+
464
+ def wing_control_surface_to_ui(cs):
465
+ cs_type = {
466
+ "aileron": 0, "slat": 1, "flap": 2,
467
+ "elevator": 3, "rudder": 4, "spoiler": 5,
468
+ }.get(cs.get("tag", "").lower(), 0)
469
+ return {
470
+ "CS name": cs.get("tag", ""),
471
+ "CS type": cs_type,
472
+ "Span Fraction Start":[cs.get("span_fraction_start", 0), 0],
473
+ "Span Fraction End": [cs.get("span_fraction_end", 0), 0],
474
+ "Deflection": [cs.get("deflection", 0), 0],
475
+ "Chord Fraction": [cs.get("chord_fraction", 0), 0],
476
+ "Number of Slots": [1, 0],
477
+ }
478
+
479
+ def fuselage_segment_to_ui(segment):
480
+ return {
481
+ "Segment Name": segment.get("tag", ""),
482
+ "Percent X Location": [segment.get("percent_x_location", 0), 0],
483
+ "Percent Z Location": [segment.get("percent_z_location", 0), 0],
484
+ "Height": [segment.get("height", 0), 0],
485
+ "Width": [segment.get("width", 0), 0],
486
+ "segment_type": fuselage_segment_type_label_for_ui(segment),
487
+ }
488
+
489
+ # Preserve nested cabin/class data through GUI load/save.
490
+ def cabin_class_type_label_for_ui(class_dict):
491
+ # Get class type from saved RCAIDE type.
492
+ type_str = class_dict.get('__type__', '')
493
+ class_name = type_str.rsplit('.', 1)[-1] if type_str else ''
494
+ return class_name if class_name in {"Economy", "Business", "First"} else "Economy"
495
+
496
+ def cabin_class_to_ui(class_dict):
497
+ # Convert one cabin class to GUI data.
498
+ # RCAIDE may use either spelling.
499
+ seats_abreast = class_dict.get("number_of_seats_abrest", class_dict.get("number_of_seats_abreast", 0))
500
+ number_of_rows = class_dict.get("number_of_rows", 0)
501
+ number_of_seats = class_dict.get("number_of_seats", 0)
502
+ # Fill missing seat count.
503
+ if not number_of_seats and seats_abreast and number_of_rows:
504
+ number_of_seats = seats_abreast * number_of_rows
505
+
506
+ ui_class = {
507
+ "class_type": cabin_class_type_label_for_ui(class_dict),
508
+ "Number of Passengers": [class_dict.get("number_of_passengers", 0), 0],
509
+ "Number of Seats Abreast": [seats_abreast, 0],
510
+ "Number of Rows": [number_of_rows, 0],
511
+ "Number of Seats": [number_of_seats, 0],
512
+ "Seat Width": [class_dict.get("seat_width", 0), 0],
513
+ "Seat Arm Rest Width": [class_dict.get("seat_arm_rest_width", 0), 0],
514
+ "Seat Length": [class_dict.get("seat_length", 0), 0],
515
+ "Seat Pitch": [class_dict.get("seat_pitch", 0), 0],
516
+ "Aisle Width": [class_dict.get("aisle_width", 0), 0],
517
+ }
518
+
519
+ # Keep hidden RCAIDE fields.
520
+ for key, value in class_dict.items():
521
+ if key not in ui_class and key != "__type__":
522
+ ui_class[key] = make_json_safe(value)
523
+ return ui_class
524
+
525
+ def cabin_type_label_for_ui(cabin_dict):
526
+ # Get cabin type from saved RCAIDE type.
527
+ type_str = cabin_dict.get('__type__', '')
528
+ class_name = type_str.rsplit('.', 1)[-1] if type_str else ''
529
+ return "Side" if class_name == "Side_Cabin" else "Regular"
530
+
531
+ def cabin_to_ui(cabin_dict):
532
+ # Convert one cabin to GUI data.
533
+ classes = cabin_dict.get("classes", {})
534
+ if isinstance(classes, dict):
535
+ class_list = [cabin_class_to_ui(c) for c in classes.values() if is_mapping(c)]
536
+ elif isinstance(classes, list):
537
+ class_list = [cabin_class_to_ui(c) for c in classes if is_mapping(c)]
538
+ else:
539
+ class_list = []
540
+
541
+ ui_cabin = {
542
+ "Cabin Name": cabin_dict.get("tag", ""),
543
+ "cabin_type": cabin_type_label_for_ui(cabin_dict),
544
+ "Number of Passengers": [cabin_dict.get("number_of_passengers", 0), 0],
545
+ "Number of Seats": [cabin_dict.get("number_of_seats", 0), 0],
546
+ "Type A Door Length": [cabin_dict.get("type_a_door_length", 0), 0],
547
+ "Galley Lavatory Length": [cabin_dict.get("galley_lavatory_length", 0), 0],
548
+ "Emergency Exit Seat Pitch": [cabin_dict.get("emergency_exit_seat_pitch", 0), 0],
549
+ "Length": [cabin_dict.get("length", 0), 0],
550
+ "Width": [cabin_dict.get("width", 0), 0],
551
+ "Height": [cabin_dict.get("height", 0), 0],
552
+ "Wide Body": [cabin_dict.get("wide_body", False), 0],
553
+ "classes": class_list,
554
+ }
555
+
556
+ for key, value in cabin_dict.items():
557
+ if key not in ui_cabin and key not in {"__type__", "classes"}:
558
+ ui_cabin[key] = make_json_safe(value)
559
+ return ui_cabin
560
+
561
+ def cabins_to_ui(cabins):
562
+ # Convert all cabins to GUI data.
563
+ if isinstance(cabins, dict):
564
+ return [cabin_to_ui(c) for c in cabins.values() if is_mapping(c)]
565
+ if isinstance(cabins, list):
566
+ return [cabin_to_ui(c) for c in cabins if is_mapping(c)]
567
+ return []
568
+
569
+ def to_ui_format(component_dict, frame_class):
570
+ ui_dict = {"name": component_dict.get("tag", component_dict.get("name", ""))}
571
+ if hasattr(frame_class, 'data_units_labels'):
572
+ for ui_label, units, rcaide_path in frame_class.data_units_labels:
573
+ try:
574
+ value = component_dict
575
+ for key in rcaide_path.split("."):
576
+ value = value[key]
577
+ ui_dict[ui_label] = [value, 0]
578
+ except (KeyError, TypeError):
579
+ ui_dict[ui_label] = [0, 0]
580
+ else:
581
+ ui_dict.update(component_dict)
582
+
583
+ if frame_class.__name__ == 'WingsFrame':
584
+ if 'Spans Projected' not in ui_dict:
585
+ ui_dict["Spans Projected"] = [component_dict.get("spans", {}).get("projected", 0), 0]
586
+ ui_dict["Reference Area"] = [component_dict.get("areas", {}).get("reference", 0), 0]
587
+ ui_dict["Wetted Area"] = [component_dict.get("areas", {}).get("wetted", 0), 0]
588
+ ui_dict["Root Chord"] = [component_dict.get("chords", {}).get("root", 0), 0]
589
+ ui_dict["Tip Chord"] = [component_dict.get("chords", {}).get("tip", 0), 0]
590
+ ui_dict["Mean Aerodynamic Chord"] = [component_dict.get("chords", {}).get("mean_aerodynamic", 0), 0]
591
+ ui_dict["Quarter Chord Sweep Angle"] = [component_dict.get("sweeps", {}).get("quarter_chord", 0), 0]
592
+ ui_dict["Leading Edge Sweep Angle"] = [component_dict.get("sweeps", {}).get("leading_edge", 0), 0]
593
+ ui_dict["Root Chord Twist Angle"] = [component_dict.get("twists", {}).get("root", 0), 0]
594
+ ui_dict["Tip Chord Twist Angle"] = [component_dict.get("twists", {}).get("tip", 0), 0]
595
+ ui_dict["Taper"] = [component_dict.get("taper", 0), 0]
596
+ ui_dict["Dihedral"] = [component_dict.get("dihedral", 0), 0]
597
+ ui_dict["Aspect Ratio"] = [component_dict.get("aspect_ratio", 0), 0]
598
+ ui_dict["Thickness to Chord"] = [component_dict.get("thickness_to_chord", 0), 0]
599
+ ui_dict["Aerodynamic Center"] = [component_dict.get("aerodynamic_center", [0, 0, 0]), 0]
600
+ ui_dict["Origin"] = [component_dict.get("origin", [0, 0, 0]), 0]
601
+ ui_dict["Vertical"] = [component_dict.get("vertical", False), 0]
602
+ ui_dict["X-Y Plane Symmetric"] = [component_dict.get("xy_plane_symmetric", False), 0]
603
+ ui_dict["High Lift"] = [component_dict.get("high_lift", False), 0]
604
+ ui_dict["X-Z Plane Symmetric"] = [component_dict.get("xz_plane_symmetric", False), 0]
605
+ ui_dict["T-Tail"] = [component_dict.get("t_tail", False), 0]
606
+ ui_dict["Y-Z Plane Symmetric"] = [component_dict.get("yz_plane_symmetric", False), 0]
607
+ ui_dict["Dynamic Pressure Ratio"] = [component_dict.get("dynamic_pressure_ratio", 0), 0]
608
+ ui_dict["Exposed Root Chord Offset"] = [component_dict.get("exposed_root_chord_offset", 0), 0]
609
+
610
+ ui_dict.setdefault("wing_type", wing_type_label_for_ui(component_dict))
611
+
612
+ segments = ui_dict.pop("segments", {})
613
+ if isinstance(segments, dict):
614
+ ui_dict["sections"] = [wing_segment_to_ui(s) for s in segments.values() if is_mapping(s)]
615
+ elif isinstance(segments, list):
616
+ ui_dict["sections"] = [wing_segment_to_ui(s) for s in segments if is_mapping(s)]
617
+ else:
618
+ ui_dict["sections"] = []
619
+
620
+ control_surfaces = ui_dict.get("control_surfaces", [])
621
+ if isinstance(control_surfaces, dict):
622
+ ui_dict["control_surfaces"] = [wing_control_surface_to_ui(cs) for cs in control_surfaces.values() if is_mapping(cs)]
623
+ elif isinstance(control_surfaces, list):
624
+ ui_dict["control_surfaces"] = [wing_control_surface_to_ui(cs) for cs in control_surfaces if is_mapping(cs)]
625
+ else:
626
+ ui_dict["control_surfaces"] = []
627
+
628
+ ui_dict["cabins"] = cabins_to_ui(component_dict.get("cabins", {}))
629
+ ui_dict["side_cabins"] = cabins_to_ui(component_dict.get("side_cabins", {}))
630
+
631
+ if frame_class.__name__ == 'FuselageFrame':
632
+ segments = component_dict.get("segments", {})
633
+ if isinstance(segments, dict):
634
+ ui_dict["segments"] = [fuselage_segment_to_ui(s) for s in segments.values() if is_mapping(s)]
635
+ elif isinstance(segments, list):
636
+ ui_dict["segments"] = [fuselage_segment_to_ui(s) for s in segments if is_mapping(s)]
637
+ else:
638
+ ui_dict["segments"] = []
639
+
640
+ # Keep loaded cabins attached.
641
+ ui_dict["cabins"] = cabins_to_ui(component_dict.get("cabins", {}))
642
+
643
+ return ui_dict
644
+
645
+ ui_structure = [[] for _ in range(7)]
646
+ ui_structure[0] = None
647
+
648
+ if "booms" in vehicle_dict and vehicle_dict["booms"]:
649
+ ui_structure[1] = [to_ui_format(b, BoomFrame) for b in vehicle_dict["booms"].values() if is_mapping(b)]
650
+
651
+ if "cargo_bays" in vehicle_dict and vehicle_dict["cargo_bays"]:
652
+ ui_structure[2] = [to_ui_format(c, CargoBayFrame) for c in vehicle_dict["cargo_bays"].values() if is_mapping(c)]
653
+
654
+ if "fuselages" in vehicle_dict and vehicle_dict["fuselages"]:
655
+ ui_structure[3] = [to_ui_format(f, FuselageFrame) for f in vehicle_dict["fuselages"].values() if is_mapping(f)]
656
+
657
+ if "landing_gears" in vehicle_dict and vehicle_dict["landing_gears"]:
658
+ ui_structure[4] = [to_ui_format(l, LandingGearFrame) for l in vehicle_dict["landing_gears"].values() if is_mapping(l)]
659
+
660
+ if "powertrains" in vehicle_dict and vehicle_dict["powertrains"]:
661
+ ui_structure[5] = [to_ui_format(p, PowertrainFrame) for p in vehicle_dict["powertrains"].values() if is_mapping(p)]
662
+ elif "networks" in vehicle_dict and vehicle_dict["networks"]:
663
+ for network in vehicle_dict["networks"].values():
664
+ if not is_mapping(network):
665
+ continue
666
+ network_tag = network.get("tag", "Fuel").title()
667
+ propulsors = network.get("propulsors", {})
668
+ if propulsors:
669
+ for propulsor in propulsors.values():
670
+ if not is_mapping(propulsor):
671
+ continue
672
+ ui_structure[5].append({
673
+ "name": propulsor.get("tag", network_tag),
674
+ "energy network selected": network_tag,
675
+ "powertrain": {
676
+ "distributor data": [], "source data": [],
677
+ "propulsor data": [], "converter data": [],
678
+ "connections": [],
679
+ }
680
+ })
681
+ else:
682
+ ui_structure[5].append({
683
+ "name": network_tag,
684
+ "energy network selected": network_tag,
685
+ "powertrain": {
686
+ "distributor data": [], "source data": [],
687
+ "propulsor data": [], "converter data": [],
688
+ "connections": [],
689
+ }
690
+ })
691
+
692
+ if "wings" in vehicle_dict and vehicle_dict["wings"]:
693
+ ui_structure[6] = [to_ui_format(w, WingsFrame) for w in vehicle_dict["wings"].values() if is_mapping(w)]
694
+
695
+ return ui_structure
696
+
697
+
698
+ # ----------------------------------------------------------------------------------------------------------------------
699
+ # Config serialisation helpers
700
+ # ----------------------------------------------------------------------------------------------------------------------
701
+
702
+ _DIFF_SKIP = frozenset({'_component_root_map', '_energy_network_root_map', '_base', '_diff'})
703
+
704
+
705
+ def _apply_diff_to_obj(obj, diff_dict):
706
+ """Walk a stripped diff dict and apply every leaf value to obj via key navigation."""
707
+ for key, value in diff_dict.items():
708
+ if key == '__type__':
709
+ continue
710
+ if isinstance(value, (dict, OrderedDict)):
711
+ try:
712
+ child = obj[key]
713
+ _apply_diff_to_obj(child, value)
714
+ except (KeyError, TypeError, AttributeError):
715
+ pass
716
+ else:
717
+ try:
718
+ obj[key] = value
719
+ except (KeyError, TypeError, AttributeError):
720
+ pass
721
+
722
+
723
+ def _serialise_config_entry(config):
724
+ """Serialize one Config object via store_diff() into a JSON-safe diff entry."""
725
+ config.store_diff()
726
+ diff = config._diff
727
+
728
+ diff_raw = {}
729
+ for k in diff.keys():
730
+ if k in _DIFF_SKIP:
731
+ continue
732
+ diff_raw[k] = _build_dict_r_with_types(diff[k])
733
+
734
+ diff_serialised = add_default_unit_arguments(make_json_safe(diff_raw))
735
+
736
+ tv = type(config)
737
+ module = getattr(tv, '__module__', '') or ''
738
+ qualname = getattr(tv, '__qualname__', '') or ''
739
+
740
+ return {
741
+ "__type__": f"{module}.{qualname}",
742
+ "tag": config.tag,
743
+ "diff": diff_serialised,
744
+ }
745
+
746
+
747
+ # ----------------------------------------------------------------------------------------------------------------------
748
+ # Public read / write API
749
+ # ----------------------------------------------------------------------------------------------------------------------
750
+
751
+ def write_to_json():
752
+ config_entries = []
753
+ for _, config in rcaide_configs.items():
754
+ try:
755
+ config_entries.append(_serialise_config_entry(config))
756
+ except Exception:
757
+ pass
758
+
759
+ data = {
760
+ "rcaide_vehicle": add_default_unit_arguments(
761
+ make_json_safe(_build_dict_base_with_types(vehicle))
762
+ ),
763
+ "config_data": config_entries,
764
+ "analysis_data": analysis_data,
765
+ "mission_data": mission_data,
766
+ }
767
+ return json.dumps(data, indent=4)
768
+
769
+
770
+ def read_from_json(data_str, source_dir=None):
771
+ global rcaide_vehicle, vehicle, rcaide_configs, config_data, analysis_data, mission_data, propulsor_names, rcaide_analyses
772
+ from RCAIDE.Library.Components.Configs.Config import Config
773
+ from RCAIDE.import_rcaide_data import analyses_setup as _analyses_setup
774
+
775
+ data = json.loads(data_str, object_pairs_hook=OrderedDict)
776
+ rcaide_vehicle_dict = data["rcaide_vehicle"]
777
+ rcaide_vehicle_clean = strip_unit_arguments(rcaide_vehicle_dict)
778
+ repair_local_file_paths(rcaide_vehicle_clean, source_dir)
779
+
780
+ vehicle = RCAIDE.Vehicle()
781
+ if isinstance(rcaide_vehicle_clean, OrderedDict):
782
+ vehicle.update(read_RCAIDE_json_dict(rcaide_vehicle_clean))
783
+ restore_vehicle_components(vehicle) # rebuilds all top-level containers
784
+ restore_airfoil_components(vehicle) # fixes embedded airfoil objects
785
+ restore_typed_subcomponents(vehicle) # fixes Fan, Combustor, Fuel_Line, etc.
786
+
787
+ if "geometry_data" in data and data["geometry_data"]:
788
+ rcaide_vehicle = data["geometry_data"]
789
+ else:
790
+ rcaide_vehicle = vehicle_dict_to_ui_list_structure(rcaide_vehicle_clean)
791
+ rcaide_vehicle[0] = vehicle_to_ui_format(vehicle)
792
+
793
+ rcaide_configs = Config.Container()
794
+ for entry in data.get("config_data", []):
795
+ if not isinstance(entry, dict):
796
+ continue
797
+ name = entry.get("tag", "")
798
+ if not name:
799
+ continue
800
+ config = Config(vehicle)
801
+ config.tag = name
802
+ diff_clean = strip_unit_arguments(entry.get("diff", {}))
803
+ _apply_diff_to_obj(config, diff_clean)
804
+ rcaide_configs.append(config)
805
+
806
+ config_data = []
807
+ analysis_data = data.get("analysis_data", [])
808
+ mission_data = data.get("mission_data", [])
809
+
810
+ rcaide_analyses = _analyses_setup(analysis_data, rcaide_configs)
811
+
812
+ propulsor_names = [[]]
813
+ try:
814
+ for network in vehicle.networks:
815
+ for prop in network.propulsors:
816
+ propulsor_names[0].append(prop.tag)
817
+ except Exception:
818
+ pass