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.
- common_widgets/__init__.py +6 -0
- common_widgets/animated_toggle.py +141 -0
- common_widgets/color.py +17 -0
- common_widgets/data_entry_widget.py +337 -0
- common_widgets/image_widget.py +21 -0
- common_widgets/unit_picker_widget.py +48 -0
- main.py +168 -0
- rcaide_gui-1.0.0.dist-info/METADATA +96 -0
- rcaide_gui-1.0.0.dist-info/RECORD +170 -0
- rcaide_gui-1.0.0.dist-info/WHEEL +5 -0
- rcaide_gui-1.0.0.dist-info/entry_points.txt +2 -0
- rcaide_gui-1.0.0.dist-info/top_level.txt +5 -0
- rcaide_io.py +818 -0
- tabs/__init__.py +10 -0
- tabs/aircraft_configs/__init__.py +3 -0
- tabs/aircraft_configs/aircraft_configs.py +301 -0
- tabs/analysis/__init__.py +4 -0
- tabs/analysis/analysis.py +92 -0
- tabs/analysis/widgets/__init__.py +13 -0
- tabs/analysis/widgets/aeroacoustics_widget.py +158 -0
- tabs/analysis/widgets/aerodynamics_widget.py +108 -0
- tabs/analysis/widgets/analysis_data_widget.py +29 -0
- tabs/analysis/widgets/atmosphere_widget.py +49 -0
- tabs/analysis/widgets/costs_widget.py +35 -0
- tabs/analysis/widgets/energy_widget.py +32 -0
- tabs/analysis/widgets/geometry_widget.py +55 -0
- tabs/analysis/widgets/planets_widget.py +34 -0
- tabs/analysis/widgets/propulsion_widget.py +53 -0
- tabs/analysis/widgets/stability_widget.py +34 -0
- tabs/analysis/widgets/weights_widget.py +94 -0
- tabs/geometry/__init__.py +6 -0
- tabs/geometry/aircraft_configs.py +232 -0
- tabs/geometry/frames/__init__.py +15 -0
- tabs/geometry/frames/booms/__init__.py +5 -0
- tabs/geometry/frames/booms/boom_frame.py +238 -0
- tabs/geometry/frames/cargo_bays/__init__.py +5 -0
- tabs/geometry/frames/cargo_bays/cargo_bay_frame.py +159 -0
- tabs/geometry/frames/default_frame.py +23 -0
- tabs/geometry/frames/energy_network/__init__.py +2 -0
- tabs/geometry/frames/energy_network/turbofan_network/__init__.py +3 -0
- tabs/geometry/frames/energy_network/turbofan_network/frames/__init__.py +1 -0
- tabs/geometry/frames/energy_network/turbofan_network/widgets/__init__.py +1 -0
- tabs/geometry/frames/fuselages/__init__.py +5 -0
- tabs/geometry/frames/fuselages/fuselage_frame.py +294 -0
- tabs/geometry/frames/geometry_frame.py +91 -0
- tabs/geometry/frames/landing_gears/__init__.py +5 -0
- tabs/geometry/frames/landing_gears/landing_gear_frame.py +189 -0
- tabs/geometry/frames/nacelles/__init__.py +1 -0
- tabs/geometry/frames/powertrain/__init__.py +6 -0
- tabs/geometry/frames/powertrain/converters/__init__.py +5 -0
- tabs/geometry/frames/powertrain/converters/converter_frame.py +137 -0
- tabs/geometry/frames/powertrain/distributors/__init__.py +5 -0
- tabs/geometry/frames/powertrain/distributors/distributor_frame.py +143 -0
- tabs/geometry/frames/powertrain/nacelles/__init__.py +5 -0
- tabs/geometry/frames/powertrain/nacelles/nacelle_frame.py +283 -0
- tabs/geometry/frames/powertrain/powertrain_frame.py +259 -0
- tabs/geometry/frames/powertrain/propulsors/__init__.py +5 -0
- tabs/geometry/frames/powertrain/propulsors/propulsor_frame.py +146 -0
- tabs/geometry/frames/powertrain/sources/__init__.py +6 -0
- tabs/geometry/frames/powertrain/sources/energy_source_frame.py +147 -0
- tabs/geometry/frames/vehicle_frame.py +171 -0
- tabs/geometry/frames/wings/__init__.py +5 -0
- tabs/geometry/frames/wings/wings_frame.py +457 -0
- tabs/geometry/geometry.py +513 -0
- tabs/geometry/widgets/__init__.py +12 -0
- tabs/geometry/widgets/booms/__init__.py +6 -0
- tabs/geometry/widgets/booms/boom_section_widget.py +109 -0
- tabs/geometry/widgets/cargo_bays/__init__.py +1 -0
- tabs/geometry/widgets/fuselages/__init__.py +8 -0
- tabs/geometry/widgets/fuselages/cabin_class_widget.py +188 -0
- tabs/geometry/widgets/fuselages/cabin_widget.py +217 -0
- tabs/geometry/widgets/fuselages/fuselage_section_widget.py +190 -0
- tabs/geometry/widgets/geometry_data_widget.py +25 -0
- tabs/geometry/widgets/landing_gears/__init__.py +6 -0
- tabs/geometry/widgets/nacelles/__init__.py +1 -0
- tabs/geometry/widgets/powertrain/__init__.py +9 -0
- tabs/geometry/widgets/powertrain/converters/__init__.py +5 -0
- tabs/geometry/widgets/powertrain/converters/turboelectric_generator_widget.py +74 -0
- tabs/geometry/widgets/powertrain/distributors/__init__.py +5 -0
- tabs/geometry/widgets/powertrain/distributors/fuel_line_widget.py +81 -0
- tabs/geometry/widgets/powertrain/modulators/__init__.py +5 -0
- tabs/geometry/widgets/powertrain/modulators/esc_widget.py +58 -0
- tabs/geometry/widgets/powertrain/nacelles/__init__.py +5 -0
- tabs/geometry/widgets/powertrain/nacelles/nacelle_section_widget.py +115 -0
- tabs/geometry/widgets/powertrain/powertrain_connector_widget.py +136 -0
- tabs/geometry/widgets/powertrain/powertrain_widget.py +206 -0
- tabs/geometry/widgets/powertrain/propulsors/__init__.py +5 -0
- tabs/geometry/widgets/powertrain/propulsors/turbofan_widget.py +462 -0
- tabs/geometry/widgets/powertrain/sources/__init__.py +6 -0
- tabs/geometry/widgets/powertrain/sources/fuel_tank_widget.py +103 -0
- tabs/geometry/widgets/powertrain/sources/source_selector_widget.py +35 -0
- tabs/geometry/widgets/wings/__init__.py +7 -0
- tabs/geometry/widgets/wings/wing_cs_widget.py +198 -0
- tabs/geometry/widgets/wings/wing_section_widget.py +298 -0
- tabs/home/__init__.py +3 -0
- tabs/home/home.py +585 -0
- tabs/mission/__init__.py +4 -0
- tabs/mission/mission.py +1124 -0
- tabs/mission/widgets/__init__.py +3 -0
- tabs/mission/widgets/flight_controls_widget.py +186 -0
- tabs/mission/widgets/mission_analysis_widget.py +166 -0
- tabs/mission/widgets/mission_segment_helper.py +358 -0
- tabs/mission/widgets/mission_segment_widget.py +358 -0
- tabs/solve/__init__.py +4 -0
- tabs/solve/plots/__init__.py +12 -0
- tabs/solve/plots/aeroacoustics/__init__.py +6 -0
- tabs/solve/plots/aeroacoustics/plot_2D_noise_contour.py +184 -0
- tabs/solve/plots/aeroacoustics/plot_3D_noise_contour.py +219 -0
- tabs/solve/plots/aeroacoustics/plot_noise_certification_contour.py +115 -0
- tabs/solve/plots/aeroacoustics/plot_noise_level.py +118 -0
- tabs/solve/plots/aerodynamics/__init__.py +10 -0
- tabs/solve/plots/aerodynamics/plot_aerodynamic_coefficients.py +156 -0
- tabs/solve/plots/aerodynamics/plot_aerodynamic_forces.py +155 -0
- tabs/solve/plots/aerodynamics/plot_aircraft_aerodynamics.py +136 -0
- tabs/solve/plots/aerodynamics/plot_drag_components.py +157 -0
- tabs/solve/plots/aerodynamics/plot_lift_distribution.py +107 -0
- tabs/solve/plots/aerodynamics/plot_rotor_conditions.py +255 -0
- tabs/solve/plots/aerodynamics/plot_rotor_disc_performance.py +144 -0
- tabs/solve/plots/aerodynamics/plot_rotor_performance.py +124 -0
- tabs/solve/plots/common/__init__.py +4 -0
- tabs/solve/plots/common/plot_style.py +71 -0
- tabs/solve/plots/common/set_axes.py +55 -0
- tabs/solve/plots/create_plot_widgets.py +34 -0
- tabs/solve/plots/emissions/__init__.py +3 -0
- tabs/solve/plots/emissions/plot_emissions.py +167 -0
- tabs/solve/plots/energy/__init__.py +12 -0
- tabs/solve/plots/energy/plot_altitude_sfc_weight.py +134 -0
- tabs/solve/plots/energy/plot_battery_cell_conditions.py +154 -0
- tabs/solve/plots/energy/plot_battery_degradation.py +157 -0
- tabs/solve/plots/energy/plot_battery_module_C_rates.py +139 -0
- tabs/solve/plots/energy/plot_battery_module_conditions.py +157 -0
- tabs/solve/plots/energy/plot_battery_pack_conditions.py +164 -0
- tabs/solve/plots/energy/plot_battery_ragone_diagram.py +97 -0
- tabs/solve/plots/energy/plot_battery_temperature.py +136 -0
- tabs/solve/plots/energy/plot_electric_propulsor_efficiencies.py +136 -0
- tabs/solve/plots/energy/plot_propulsor_throttles.py +119 -0
- tabs/solve/plots/mission/__init__.py +5 -0
- tabs/solve/plots/mission/plot_aircraft_velocities.py +145 -0
- tabs/solve/plots/mission/plot_flight_conditions.py +236 -0
- tabs/solve/plots/mission/plot_flight_trajectory.py +172 -0
- tabs/solve/plots/stability/__init__.py +5 -0
- tabs/solve/plots/stability/plot_flight_forces_and_moments.py +163 -0
- tabs/solve/plots/stability/plot_lateral_stability.py +151 -0
- tabs/solve/plots/stability/plot_longitudinal_stability.py +173 -0
- tabs/solve/plots/thermal_management/__init__.py +7 -0
- tabs/solve/plots/thermal_management/plot_air_cooled_conditions.py +152 -0
- tabs/solve/plots/thermal_management/plot_cross_flow_heat_exchanger_conditions.py +185 -0
- tabs/solve/plots/thermal_management/plot_reservoir_conditions.py +141 -0
- tabs/solve/plots/thermal_management/plot_thermal_management_performance.py +105 -0
- tabs/solve/plots/thermal_management/plot_wavy_channel_conditions.py +161 -0
- tabs/solve/plots/weights/__init__.py +4 -0
- tabs/solve/plots/weights/plot_load_diagram.py +163 -0
- tabs/solve/plots/weights/plot_weight_breakdown.py +164 -0
- tabs/solve/solve.py +1498 -0
- tabs/style_sheet.py +114 -0
- tabs/tab_widget.py +14 -0
- tabs/visualize_geometry/__init__.py +4 -0
- tabs/visualize_geometry/core_3d_viewer.py +253 -0
- tabs/visualize_geometry/features/__init__.py +10 -0
- tabs/visualize_geometry/features/axes_gizmo.py +173 -0
- tabs/visualize_geometry/features/background.py +161 -0
- tabs/visualize_geometry/features/blueprint.py +685 -0
- tabs/visualize_geometry/features/camera.py +364 -0
- tabs/visualize_geometry/features/drag_aircraft.py +219 -0
- tabs/visualize_geometry/features/grid.py +194 -0
- tabs/visualize_geometry/features/measurement.py +367 -0
- tabs/visualize_geometry/features/screenshot.py +91 -0
- tabs/visualize_geometry/geometry_helper_functions.py +3 -0
- tabs/visualize_geometry/visualize_geometry.py +731 -0
- 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
|