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
tabs/__init__.py ADDED
@@ -0,0 +1,10 @@
1
+ # RCAIDE_GUI/tabs/__init__.py
2
+ from .tab_widget import *
3
+ from . import aircraft_configs
4
+ from . import analysis
5
+ from . import geometry
6
+ from . import home
7
+ from . import mission
8
+ from . import solve
9
+ from . import visualize_geometry
10
+
@@ -0,0 +1,3 @@
1
+ # RCAIDE_GUI/tabs/aircraft_configs/__init__.py
2
+
3
+ from .aircraft_configs import *
@@ -0,0 +1,301 @@
1
+ # RCAIDE_GUI/tabs/aircraft_configs/aircraft_configs.py
2
+
3
+ import RCAIDE
4
+ from RCAIDE.Library.Components.Configs.Config import Config
5
+
6
+ from tabs import TabWidget
7
+ from utilities import Units
8
+ from common_widgets import DataEntryWidget
9
+ import rcaide_io
10
+
11
+ from PyQt6.QtCore import QTimer
12
+ from PyQt6.QtWidgets import (
13
+ QTreeWidgetItem, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QTreeWidget,
14
+ QLabel, QLineEdit, QCheckBox, QSpacerItem, QSizePolicy, QScrollArea, QGroupBox,
15
+ QMessageBox, QInputDialog
16
+ )
17
+
18
+
19
+ class AircraftConfigsWidget(TabWidget):
20
+ def __init__(self):
21
+ super().__init__()
22
+ self._cfg_widgets = {} # config tag → {name_edit, cs_block, prop_block, gear_check}
23
+ self._selected_tag = None
24
+
25
+ base_layout = QHBoxLayout()
26
+
27
+ tree_layout = QVBoxLayout()
28
+ self.tree = QTreeWidget()
29
+ self.tree.setHeaderLabels(["Configuration Tree"])
30
+ self.root_item = QTreeWidgetItem(["Aircraft Configurations"])
31
+ self.tree.addTopLevelItem(self.root_item)
32
+ self.tree.itemClicked.connect(self.on_tree_item_clicked)
33
+ tree_layout.addWidget(self.tree)
34
+
35
+ self.main_layout = QVBoxLayout()
36
+ self.main_layout.addWidget(QLabel("<b>All Configurations</b>"))
37
+
38
+ self.scroll = QScrollArea()
39
+ self.scroll.setWidgetResizable(True)
40
+ self.scroll_container = QWidget()
41
+ self.scroll_layout = QVBoxLayout(self.scroll_container)
42
+ self.scroll.setWidget(self.scroll_container)
43
+ self.main_layout.addWidget(self.scroll)
44
+
45
+ button_layout = QHBoxLayout()
46
+ new_btn = QPushButton("New Config")
47
+ new_btn.clicked.connect(self.new_configuration)
48
+ button_layout.addWidget(new_btn)
49
+ save_btn = QPushButton("Save Config")
50
+ save_btn.clicked.connect(self.save_data)
51
+ button_layout.addWidget(save_btn)
52
+ delete_btn = QPushButton("Delete Config")
53
+ delete_btn.clicked.connect(self.delete_data)
54
+ button_layout.addWidget(delete_btn)
55
+ self.main_layout.addLayout(button_layout)
56
+
57
+ base_layout.addLayout(tree_layout, 3)
58
+ base_layout.addLayout(self.main_layout, 7)
59
+ self.setLayout(base_layout)
60
+
61
+ QTimer.singleShot(0, self.update_layout)
62
+
63
+ # ------------------------------------------------------------------
64
+ # Vehicle traversal helpers
65
+ # ------------------------------------------------------------------
66
+
67
+ def _cs_names(self):
68
+ names = []
69
+ for wing in getattr(rcaide_io.vehicle, 'wings', []):
70
+ for cs in getattr(wing, 'control_surfaces', []):
71
+ tag = getattr(cs, 'tag', None)
72
+ if tag and tag not in names:
73
+ names.append(tag)
74
+ return sorted(names)
75
+
76
+ def _prop_names(self):
77
+ names = []
78
+ for network in getattr(rcaide_io.vehicle, 'networks', []):
79
+ for prop in getattr(network, 'propulsors', []):
80
+ tag = getattr(prop, 'tag', None)
81
+ if tag and tag not in names:
82
+ names.append(tag)
83
+ return sorted(names)
84
+
85
+ def _get_cs_deflection(self, config, cs_name):
86
+ for wing in getattr(config, 'wings', []):
87
+ for cs in getattr(wing, 'control_surfaces', []):
88
+ if getattr(cs, 'tag', None) == cs_name:
89
+ return getattr(cs, 'deflection', 0.0)
90
+ return 0.0
91
+
92
+ def _set_cs_deflection(self, config, cs_name, angle):
93
+ for wing in getattr(config, 'wings', []):
94
+ for cs in getattr(wing, 'control_surfaces', []):
95
+ if getattr(cs, 'tag', None) == cs_name:
96
+ cs.deflection = angle
97
+ return
98
+
99
+ def _get_gear_extended(self, config):
100
+ for gear in getattr(config, 'landing_gears', []):
101
+ if getattr(gear, 'gear_extended', False):
102
+ return True
103
+ return False
104
+
105
+ def _set_gear_extended(self, config, value):
106
+ for gear in getattr(config, 'landing_gears', []):
107
+ gear.gear_extended = value
108
+
109
+ def _get_prop_active(self, config, prop_name):
110
+ for network in getattr(config, 'networks', []):
111
+ for prop in getattr(network, 'propulsors', []):
112
+ if getattr(prop, 'tag', None) == prop_name:
113
+ return getattr(prop, 'active', True)
114
+ return True
115
+
116
+ def _set_prop_active(self, config, prop_name, value):
117
+ for network in getattr(config, 'networks', []):
118
+ for prop in getattr(network, 'propulsors', []):
119
+ if getattr(prop, 'tag', None) == prop_name:
120
+ if hasattr(prop, 'active'):
121
+ prop.active = bool(value)
122
+ return
123
+
124
+ # ------------------------------------------------------------------
125
+ # Layout
126
+ # ------------------------------------------------------------------
127
+
128
+ def update_layout(self):
129
+ cs_names = self._cs_names()
130
+ prop_names = self._prop_names()
131
+ cs_labels = [(f"{n} Deflection", Units.Angle) for n in cs_names]
132
+ prop_labels = [(f"{n} Enabled", Units.Boolean) for n in prop_names]
133
+
134
+ self._cfg_widgets.clear()
135
+ while self.scroll_layout.count():
136
+ w = self.scroll_layout.takeAt(0).widget()
137
+ if w:
138
+ w.deleteLater()
139
+ self.root_item.takeChildren()
140
+
141
+ configs = rcaide_io.rcaide_configs
142
+ for cfg_tag, config in configs.items():
143
+ if not hasattr(config, 'tag'):
144
+ continue
145
+ name = config.tag
146
+
147
+ group = QGroupBox(name)
148
+ layout = QVBoxLayout(group)
149
+
150
+ name_row = QHBoxLayout()
151
+ name_row.addWidget(QLabel("Config Name:"))
152
+ name_edit = QLineEdit(name)
153
+ name_row.addWidget(name_edit)
154
+ layout.addLayout(name_row)
155
+
156
+ layout.addWidget(QLabel("<b>Control Surfaces</b>"))
157
+ cs_block = None
158
+ if cs_labels:
159
+ cs_block = DataEntryWidget(cs_labels)
160
+ cs_block.load_data({
161
+ f"{n} Deflection": [self._get_cs_deflection(config, n), 0]
162
+ for n in cs_names
163
+ })
164
+ layout.addWidget(cs_block)
165
+ else:
166
+ layout.addWidget(QLabel("<i>No control surfaces defined.</i>"))
167
+
168
+ layout.addWidget(QLabel("<b>Propulsors</b>"))
169
+ prop_block = None
170
+ if prop_labels:
171
+ prop_block = DataEntryWidget(prop_labels)
172
+ prop_block.load_data({
173
+ f"{n} Enabled": [self._get_prop_active(config, n), 0]
174
+ for n in prop_names
175
+ })
176
+ layout.addWidget(prop_block)
177
+ else:
178
+ layout.addWidget(QLabel("<i>No propulsors defined.</i>"))
179
+
180
+ gear_check = QCheckBox("Landing Gear Deployed")
181
+ gear_check.setChecked(self._get_gear_extended(config))
182
+ layout.addWidget(gear_check)
183
+
184
+ self._cfg_widgets[name] = {
185
+ "name": name_edit,
186
+ "cs": cs_block,
187
+ "prop": prop_block,
188
+ "gear": gear_check,
189
+ "orig_tag": cfg_tag,
190
+ }
191
+
192
+ self.scroll_layout.addWidget(group)
193
+ self.root_item.addChild(QTreeWidgetItem([name]))
194
+
195
+ self.scroll_layout.addSpacerItem(
196
+ QSpacerItem(0, 0, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
197
+ )
198
+ self.tree.expandAll()
199
+
200
+ def on_tree_item_clicked(self, item, _):
201
+ idx = self.root_item.indexOfChild(item)
202
+ tags = list(self._cfg_widgets.keys())
203
+ self._selected_tag = tags[idx] if 0 <= idx < len(tags) else None
204
+
205
+ # ------------------------------------------------------------------
206
+ # CRUD
207
+ # ------------------------------------------------------------------
208
+
209
+ def new_configuration(self):
210
+ name, ok = QInputDialog.getText(self, "New Configuration", "Configuration name:")
211
+ if not ok:
212
+ return
213
+ name = name.strip()
214
+ if not name:
215
+ QMessageBox.warning(self, "Invalid Name", "Configuration name cannot be empty.")
216
+ return
217
+ if any(getattr(c, 'tag', '') == name for _, c in rcaide_io.rcaide_configs.items()):
218
+ QMessageBox.warning(self, "Duplicate Name", "Configuration name already exists.")
219
+ return
220
+
221
+ config = Config(rcaide_io.vehicle)
222
+ config.tag = name
223
+ rcaide_io.rcaide_configs.append(config)
224
+ self._selected_tag = name
225
+ self.update_layout()
226
+
227
+ def save_data(self):
228
+ new_configs = Config.Container()
229
+ for name, w in self._cfg_widgets.items():
230
+ orig_tag = w["orig_tag"]
231
+ try:
232
+ config = rcaide_io.rcaide_configs[orig_tag]
233
+ except (KeyError, TypeError):
234
+ continue
235
+
236
+ new_name = w["name"].text().strip() or name
237
+ config.tag = new_name
238
+
239
+ if w["cs"]:
240
+ for label, val in w["cs"].get_values().items():
241
+ if label.endswith(" Deflection"):
242
+ cs_name = label[: -len(" Deflection")]
243
+ angle = val[0] if isinstance(val, (list, tuple)) else val
244
+ self._set_cs_deflection(config, cs_name, angle)
245
+
246
+ if w["prop"]:
247
+ for label, val in w["prop"].get_values().items():
248
+ if label.endswith(" Enabled"):
249
+ prop_name = label[: -len(" Enabled")]
250
+ active = val[0] if isinstance(val, (list, tuple)) else val
251
+ self._set_prop_active(config, prop_name, active)
252
+
253
+ self._set_gear_extended(config, w["gear"].isChecked())
254
+ new_configs.append(config)
255
+
256
+ rcaide_io.rcaide_configs = new_configs
257
+ rcaide_io.propulsor_names = _collect_propulsor_names(rcaide_io.vehicle)
258
+ self.update_layout()
259
+ QMessageBox.information(self, "Saved", "Aircraft configurations saved.")
260
+
261
+ def delete_data(self):
262
+ if self._selected_tag is None:
263
+ return
264
+ orig_tag = (self._cfg_widgets.get(self._selected_tag) or {}).get("orig_tag")
265
+ key = orig_tag or self._selected_tag
266
+ try:
267
+ del rcaide_io.rcaide_configs[key]
268
+ except (KeyError, TypeError, AttributeError):
269
+ pass
270
+ self._selected_tag = None
271
+ self.update_layout()
272
+
273
+
274
+ # ------------------------------------------------------------------------------
275
+ # Module-level helpers
276
+ # ------------------------------------------------------------------------------
277
+
278
+ def _collect_propulsor_names(vehicle):
279
+ names = [[]]
280
+ for network in getattr(vehicle, 'networks', []):
281
+ for prop in getattr(network, 'propulsors', []):
282
+ names[0].append(prop.tag)
283
+ return names
284
+
285
+
286
+ def build_rcaide_configs_from_geometry():
287
+ """
288
+ Return the current RCAIDE Config.Container, collecting propulsor names as a
289
+ side effect. Raises RuntimeError if no vehicle or configs are available.
290
+ """
291
+ if rcaide_io.vehicle is None:
292
+ raise RuntimeError("No geometry data available")
293
+ if not rcaide_io.rcaide_configs:
294
+ raise RuntimeError("No aircraft configurations available")
295
+
296
+ rcaide_io.propulsor_names = _collect_propulsor_names(rcaide_io.vehicle)
297
+ return rcaide_io.rcaide_configs
298
+
299
+
300
+ def get_widget() -> QWidget:
301
+ return AircraftConfigsWidget()
@@ -0,0 +1,4 @@
1
+ # RCAIDE_GUI/tabs/analysis/__init__.py
2
+
3
+ from .analysis import *
4
+ from . import widgets
@@ -0,0 +1,92 @@
1
+ # RCAIDE_GUI/tabs/analysis/analysis.py
2
+
3
+ # RCAIDE imports
4
+ import RCAIDE
5
+
6
+ # RCAIDE-GUI imports
7
+ from tabs.mission.widgets import MissionAnalysisWidget
8
+ from tabs.analysis.widgets import *
9
+ from tabs import TabWidget
10
+ from utilities import create_scroll_area
11
+ import rcaide_io
12
+
13
+ # PyQt imports
14
+ from PyQt6.QtCore import Qt, QTimer
15
+ from PyQt6.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QTreeWidget, \
16
+ QTreeWidgetItem, QHeaderView, QPushButton, QLabel, QGroupBox
17
+
18
+ # ------------------------------------------------------------------------------
19
+ # Analysis Widget
20
+ # ------------------------------------------------------------------------------
21
+ class AnalysisWidget(TabWidget):
22
+ def __init__(self):
23
+ super().__init__()
24
+
25
+ layout = QVBoxLayout(self)
26
+ layout.setContentsMargins(10, 10, 10, 10)
27
+
28
+ analyses_box = QGroupBox("Analyses Setup")
29
+ analyses_v = QVBoxLayout(analyses_box)
30
+
31
+ self.save_notice = QLabel("")
32
+ self.save_notice.setVisible(False)
33
+ self.save_notice.setStyleSheet(
34
+ "color:#10351e; background:#9fe3b5; border:1px solid #2d8a57; "
35
+ "border-radius:4px; padding:6px 10px; font-weight:600;"
36
+ )
37
+ analyses_v.addWidget(self.save_notice)
38
+
39
+ self.analysis_content = MissionAnalysisWidget()
40
+ analyses_v.addWidget(self.analysis_content)
41
+
42
+ layout.addWidget(analyses_box)
43
+
44
+ btn_row = QHBoxLayout()
45
+ self.save_btn = QPushButton("Save Analyses")
46
+ self.save_btn.setStyleSheet("background-color:#141b29; color:#e5f0ff; padding: 8px;")
47
+ self.save_btn.clicked.connect(self.save_analyses_to_values)
48
+
49
+ btn_row.addStretch()
50
+ btn_row.addWidget(self.save_btn)
51
+
52
+ layout.addLayout(btn_row)
53
+
54
+ def save_analyses_to_values(self):
55
+ # Build the RCAIDE analyses from the current UI state and store them in `values`.
56
+ self.analysis_content.save_analyses()
57
+ saved_configs = list(getattr(values, "rcaide_analyses", {}).keys())
58
+ # Show a short in-app confirmation so the user does not have to rely on terminal output.
59
+ self.save_notice.setText(
60
+ f"Analyses saved successfully for {len(saved_configs)} configuration(s)."
61
+ )
62
+ self.save_notice.setVisible(True)
63
+ QTimer.singleShot(2500, lambda: self.save_notice.setVisible(False))
64
+
65
+ # Print a readable summary of what was written into `rcaide_io.rcaide_analyses`.
66
+ print("\n[Analyses Saved]")
67
+ print(f"Configs: {len(saved_configs)}")
68
+ print(f"Names: {', '.join(saved_configs) if saved_configs else 'None'}")
69
+
70
+ for config_name, analysis in getattr(values, "rcaide_analyses", {}).items():
71
+ try:
72
+ entries = list(analysis)
73
+ except TypeError:
74
+ entries = []
75
+
76
+ entry_labels = [
77
+ getattr(entry, "tag", entry.__class__.__name__)
78
+ for entry in entries
79
+ ]
80
+ print(f"- {config_name}")
81
+ print(f" Entries: {len(entry_labels)}")
82
+ print(f" Tags: {', '.join(entry_labels) if entry_labels else 'None'}")
83
+
84
+ def load_from_values(self):
85
+ if hasattr(self.analysis_content, "load_values"):
86
+ self.analysis_content.load_values()
87
+
88
+ def get_data(self):
89
+ return self.analysis_content.get_data()
90
+
91
+ def get_widget() -> QWidget:
92
+ return AnalysisWidget()
@@ -0,0 +1,13 @@
1
+ # RCAIDE_GUI/tabs/analysis/widgets/__init__.py
2
+
3
+ from tabs.analysis.widgets.analysis_data_widget import AnalysisDataWidget
4
+ from tabs.analysis.widgets.aerodynamics_widget import AerodynamicsWidget
5
+ from tabs.analysis.widgets.atmosphere_widget import AtmosphereWidget
6
+ from tabs.analysis.widgets.costs_widget import CostsWidget
7
+ from tabs.analysis.widgets.energy_widget import EnergyWidget
8
+ from tabs.analysis.widgets.aeroacoustics_widget import AeroacousticsWidget
9
+ from tabs.analysis.widgets.planets_widget import PlanetsWidget
10
+ from tabs.analysis.widgets.propulsion_widget import PropulsionWidget
11
+ from tabs.analysis.widgets.stability_widget import StabilityWidget
12
+ from tabs.analysis.widgets.weights_widget import WeightsWidget
13
+ from tabs.analysis.widgets.geometry_widget import GeometryWidget
@@ -0,0 +1,158 @@
1
+ # RCAIDE_GUI/tabs/analysis/widgets/aeroacoustics_widget.py
2
+
3
+ # Created: May 2023, M. Clarke
4
+ # ------------------------------------------------------------------------------
5
+ # Imports
6
+ # ------------------------------------------------------------------------------
7
+ # RCAIDE imports
8
+ import RCAIDE
9
+
10
+ # RCAIDE-GUI imports
11
+ from utilities import create_line_bar, Units, set_data
12
+ from tabs.analysis.widgets import AnalysisDataWidget
13
+ from common_widgets import DataEntryWidget
14
+
15
+ # PyQt imports
16
+ from PyQt6.QtWidgets import QWidget, QLabel, QVBoxLayout, QComboBox
17
+
18
+ # Python imports
19
+ import json
20
+ import os
21
+
22
+ # ------------------------------------------------------------------------------
23
+ # Aeroacoustics Widget
24
+ # ------------------------------------------------------------------------------
25
+ class AeroacousticsWidget(AnalysisDataWidget):
26
+ def __init__(self):
27
+ super(AeroacousticsWidget, self).__init__()
28
+ self.main_layout = QVBoxLayout()
29
+
30
+ self.main_layout.addWidget(QLabel("<b>Acoustics</b>"))
31
+ self.main_layout.addWidget(create_line_bar())
32
+
33
+ self.analysis_selector = QComboBox()
34
+ self.analysis_selector.addItems(self.analyses)
35
+ self.analysis_selector.currentIndexChanged.connect(
36
+ self.on_analysis_change)
37
+ self.main_layout.addWidget(self.analysis_selector)
38
+
39
+ self.data_entry_widget = DataEntryWidget(self.data_units_labels[0])
40
+ self.main_layout.addWidget(self.data_entry_widget)
41
+
42
+ _root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
43
+ _defaults_path = os.path.join(_root, "app_data", "defaults", "aeroacoustics_analysis.json")
44
+ with open(_defaults_path, "r") as defaults:
45
+ self.defaults = json.load(defaults)
46
+
47
+ self.data_entry_widget.load_data(self.defaults[0])
48
+ self.main_layout.addWidget(create_line_bar())
49
+ self.setLayout(self.main_layout)
50
+
51
+ def on_analysis_change(self, index):
52
+ assert self.main_layout is not None
53
+
54
+ self.main_layout.removeWidget(self.data_entry_widget)
55
+ self.data_entry_widget = DataEntryWidget(self.data_units_labels[index])
56
+ self.data_entry_widget.load_data(self.defaults[index])
57
+ # self.main_layout.addWidget(self.data_entry_widget)
58
+ self.main_layout.insertWidget(
59
+ self.main_layout.count() - 1, self.data_entry_widget)
60
+
61
+ def create_analysis(self, vehicle):
62
+ analysis_num = self.analysis_selector.currentIndex()
63
+ if analysis_num == 0:
64
+ aeroacoustics = RCAIDE.Framework.Analyses.Aeroacoustics.Semi_Empirical()
65
+ else:
66
+ aeroacoustics = RCAIDE.Framework.Analyses.Aeroacoustics.Physics_Based()
67
+
68
+ settings = aeroacoustics.settings
69
+ values_si = self.data_entry_widget.get_values_si()
70
+
71
+ for data_unit_label in self.data_units_labels[analysis_num]:
72
+ if len(data_unit_label) < 3:
73
+ continue
74
+
75
+ rcaide_label = data_unit_label[-1]
76
+ user_label = data_unit_label[0]
77
+ set_data(settings, rcaide_label, values_si[user_label])
78
+
79
+ phi_upper_bound = values_si["Noise Hemisphere Phi Upper Bound"][0]
80
+ phi_lower_bound = values_si["Noise Hemisphere Phi Lower Bound"][0]
81
+
82
+ settings.noise_hemisphere_phi_angle_bounds = [
83
+ phi_lower_bound, phi_upper_bound]
84
+
85
+ theta_upper_bound = values_si["Noise Hemisphere Theta Upper Bound"][0]
86
+ theta_lower_bound = values_si["Noise Hemisphere Theta Lower Bound"][0]
87
+
88
+ settings.noise_hemisphere_theta_angle_bounds = [
89
+ theta_lower_bound, theta_upper_bound]
90
+
91
+ return aeroacoustics
92
+
93
+ def get_values(self):
94
+ data = self.data_entry_widget.get_values()
95
+ data["analysis_num"] = self.analysis_selector.currentIndex()
96
+ return data
97
+
98
+ def load_values(self, values):
99
+ super().load_values(values)
100
+ self.analysis_selector.setCurrentIndex(values["analysis_num"])
101
+ self.on_analysis_change(values["analysis_num"])
102
+ self.data_entry_widget.load_data(values)
103
+
104
+ data_units_labels = [
105
+ [
106
+ ("Print Noise Output", Units.Boolean, "print_noise_output"),
107
+ ("Mean Sea Level Altitude", Units.Boolean, "mean_sea_level_altitude"),
108
+ ("Aircraft Departure Location", Units.Position,
109
+ "aircraft_departure_location"),
110
+ ("Aircraft Destination Location", Units.Position,
111
+ "aircraft_destination_location"),
112
+ ("Microphone X Resolution", Units.Count, "microphone_x_resolution"),
113
+ ("Microphone Y Resolution", Units.Count, "microphone_y_resolution"),
114
+ # TODO ("Microphone X Stencil", Units.Length),
115
+ # TODO ("Microphone Y Stencil", Units.Length),
116
+ ("Microphone Min X", Units.Length, "microphone_min_x"),
117
+ ("Microphone Max X", Units.Length, "microphone_max_x"),
118
+ ("Microphone Min Y", Units.Length, "microphone_min_y"),
119
+ ("Microphone Max Y", Units.Length, "microphone_max_y"),
120
+ ("Noise Hemisphere", Units.Boolean, "noise_hemisphere"),
121
+ ("Noise Hemisphere Radius", Units.Length, "noise_hemisphere_radius"),
122
+ ("Noise Hemisphere Microphone Resolution", Units.Count,
123
+ "noise_hemisphere_microphone_resolution"),
124
+ ("Noise Hemisphere Phi Upper Bound", Units.Angle),
125
+ ("Noise Hemisphere Phi Lower Bound", Units.Angle),
126
+ ("Noise Hemisphere Theta Upper Bound", Units.Angle),
127
+ ("Noise Hemisphere Theta Lower Bound", Units.Angle),
128
+ ],
129
+ [
130
+ ("Flyover", Units.Boolean, "flyover"),
131
+ ("Approach", Units.Boolean, "approach"),
132
+ ("Sideline", Units.Boolean, "sideline"),
133
+ ("Sideline X Position", Units.Boolean, "sideline_x_position"),
134
+ ("Print Noise Output", Units.Boolean, "print_noise_output"),
135
+ ("Mean Sea Level Altitude", Units.Boolean, "mean_sea_level_altitude"),
136
+ ("Aircraft Departure Location", Units.Position,
137
+ "aircraft_departure_location"),
138
+ ("Aircraft Destination Location", Units.Position,
139
+ "aircraft_destination_location"),
140
+ ("Microphone X Resolution", Units.Count, "microphone_x_resolution"),
141
+ ("Microphone Y Resolution", Units.Count, "microphone_y_resolution"),
142
+ # TODO ("Microphone X Stencil", Units.Length),
143
+ # TODO ("Microphone Y Stencil", Units.Length),
144
+ ("Microphone Min X", Units.Length, "microphone_min_x"),
145
+ ("Microphone Max X", Units.Length, "microphone_max_x"),
146
+ ("Microphone Min Y", Units.Length, "microphone_min_y"),
147
+ ("Microphone Max Y", Units.Length, "microphone_max_y"),
148
+ ("Noise Hemisphere", Units.Boolean, "noise_hemisphere"),
149
+ ("Noise Hemisphere Radius", Units.Length, "noise_hemisphere_radius"),
150
+ ("Noise Hemisphere Microphone Resolution", Units.Count,
151
+ "noise_hemisphere_microphone_resolution"),
152
+ ("Noise Hemisphere Phi Upper Bound", Units.Angle),
153
+ ("Noise Hemisphere Phi Lower Bound", Units.Angle),
154
+ ("Noise Hemisphere Theta Upper Bound", Units.Angle),
155
+ ("Noise Hemisphere Theta Lower Bound", Units.Angle),
156
+ ]
157
+ ]
158
+ analyses = ["Semi Empirical", "Physics-Based"]