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
@@ -0,0 +1,358 @@
1
+ # RCAIDE_GUI/tabs/mission/widgets/mission_segment_widget.py
2
+
3
+ # Created: May 2023, M. Clarke
4
+ # ------------------------------------------------------------------------------
5
+ # Imports
6
+ # ------------------------------------------------------------------------------
7
+ from PyQt6.QtCore import QEvent, QObject, Qt
8
+ from PyQt6.QtWidgets import (
9
+ QWidget, QVBoxLayout, QHBoxLayout, QComboBox, QLabel, QLineEdit,
10
+ QGroupBox, QRadioButton
11
+ )
12
+
13
+ from tabs.mission.widgets.flight_controls_widget import FlightControlsWidget
14
+ from tabs.mission.widgets.mission_segment_helper import (
15
+ segment_data_fields,
16
+ segment_rcaide_classes,
17
+ )
18
+ from utilities import Units, set_data, convert_name
19
+ import rcaide_io
20
+ import RCAIDE
21
+ from common_widgets import DataEntryWidget
22
+
23
+
24
+ class _MissionComboWheelGuard(QObject):
25
+ def eventFilter(self, obj, event):
26
+ # Prevent hover-scrolling from changing combo values unless the user
27
+ # has explicitly opened the combo box popup.
28
+ if event.type() == QEvent.Type.Wheel and isinstance(obj, QComboBox):
29
+ if not obj.view().isVisible():
30
+ event.ignore()
31
+ return True
32
+ return super().eventFilter(obj, event)
33
+
34
+
35
+ class MissionSegmentWidget(QWidget):
36
+ def __init__(self):
37
+ super().__init__()
38
+ self._combo_wheel_guard = _MissionComboWheelGuard(self)
39
+
40
+ # ============================================================
41
+ # Root Layout
42
+ # ============================================================
43
+ self.segment_layout = QVBoxLayout()
44
+ self.segment_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
45
+ self.setLayout(self.segment_layout)
46
+
47
+ # Runtime widgets
48
+ self.subsegment_entry_widget = None
49
+ self.dof_entry_widget = None
50
+ self.flight_controls_widget = None
51
+ self._suppress_defaults = False
52
+
53
+ # Dropdowns
54
+ self.top_dropdown = QComboBox()
55
+ self.nested_dropdown = QComboBox()
56
+ self.config_selector = QComboBox()
57
+
58
+ # ============================================================
59
+ # Group 1 — Specify Segment Settings
60
+ # ============================================================
61
+ self.settings_group = QGroupBox("Segment Settings")
62
+ self.settings_group.setStyleSheet(self._box_style())
63
+ self.settings_layout = QVBoxLayout(self.settings_group)
64
+
65
+ cp_row = QHBoxLayout()
66
+ cp_row.addWidget(QLabel("Number of Control Points:"))
67
+ self.ctrl_points = QLineEdit("16")
68
+ self.ctrl_points.setFixedWidth(70)
69
+ self.ctrl_points.setAlignment(Qt.AlignmentFlag.AlignCenter)
70
+ cp_row.addWidget(self.ctrl_points)
71
+ cp_row.addStretch(1)
72
+ self.settings_layout.addLayout(cp_row)
73
+
74
+ solver_row = QHBoxLayout()
75
+ solver_row.addWidget(QLabel("Mission Solver:"))
76
+ self.solver_root = QRadioButton("Root Solver")
77
+ self.solver_opt = QRadioButton("Optimize Solver")
78
+ self.solver_root.setChecked(True)
79
+ solver_row.addWidget(self.solver_root)
80
+ solver_row.addWidget(self.solver_opt)
81
+ solver_row.addStretch(1)
82
+ self.settings_layout.addLayout(solver_row)
83
+
84
+ self.segment_layout.addWidget(self.settings_group)
85
+
86
+ # ============================================================
87
+ # Group 2 — Specify Segment Details
88
+ # ============================================================
89
+ self.details_group = QGroupBox("Mission Profile")
90
+ self.details_group.setStyleSheet(self._box_style())
91
+ self.details_layout = QVBoxLayout(self.details_group)
92
+
93
+ self.details_layout.addWidget(QLabel("Segment Name:"))
94
+ self.segment_name_input = QLineEdit()
95
+ self.details_layout.addWidget(self.segment_name_input)
96
+
97
+ self.details_layout.addWidget(QLabel("Segment Classification:"))
98
+ self.top_dropdown.addItems([
99
+ "Climb", "Cruise", "Descent", "Ground",
100
+ "Single_Point", "Vertical Flight"
101
+ ])
102
+ self.details_layout.addWidget(self.top_dropdown)
103
+
104
+ self.details_layout.addWidget(QLabel("Segment Type:"))
105
+ self.populate_nested_dropdown(0)
106
+ self.details_layout.addWidget(self.nested_dropdown)
107
+
108
+ self.details_layout.addWidget(QLabel("Vehicle Configuration:"))
109
+ cfg_container = getattr(rcaide_io, "rcaide_configs", None)
110
+ config_names = list(cfg_container.keys()) if hasattr(cfg_container, 'keys') else []
111
+ self.details_layout.addWidget(QLabel("Segment Details:"))
112
+ self.config_selector.addItems([n for n in config_names if n])
113
+ self.details_layout.addWidget(self.config_selector)
114
+
115
+ self.segment_layout.addWidget(self.details_group)
116
+
117
+ # ============================================================
118
+ # Group 3 — Degrees of Freedom
119
+ # ============================================================
120
+ self.dof_group = QGroupBox("Select Degrees of Freedom")
121
+ self.dof_group.setStyleSheet(self._box_style())
122
+ dof_layout = QVBoxLayout(self.dof_group)
123
+
124
+ dof_fields = [
125
+ ("Forces in X axis", Units.Boolean, "flight_dynamics.force_x"),
126
+ ("Moments about X axis", Units.Boolean, "flight_dynamics.moment_x"),
127
+ ("Forces in Y axis", Units.Boolean, "flight_dynamics.force_y"),
128
+ ("Moments about Y axis", Units.Boolean, "flight_dynamics.moment_y"),
129
+ ("Forces in Z axis", Units.Boolean, "flight_dynamics.force_z"),
130
+ ("Moments about Z axis", Units.Boolean, "flight_dynamics.moment_z"),
131
+ ]
132
+
133
+ self.dof_entry_widget = DataEntryWidget(dof_fields)
134
+ dof_layout.addWidget(self.dof_entry_widget)
135
+ self.segment_layout.addWidget(self.dof_group)
136
+
137
+ # ============================================================
138
+ # Group 4 — Flight Controls
139
+ # ============================================================
140
+ self.fc_group = QGroupBox("Select Flight Controls")
141
+ self.fc_group.setStyleSheet(self._box_style())
142
+ fc_layout = QVBoxLayout(self.fc_group)
143
+
144
+ self.flight_controls_widget = FlightControlsWidget()
145
+ fc_layout.addWidget(self.flight_controls_widget)
146
+ self.segment_layout.addWidget(self.fc_group)
147
+
148
+ # ============================================================
149
+ # Signals
150
+ # ============================================================
151
+ self.top_dropdown.currentIndexChanged.connect(
152
+ self._on_top_dropdown_change
153
+ )
154
+ self.nested_dropdown.currentIndexChanged.connect(
155
+ self.update_subsegment_fields
156
+ )
157
+ self.segment_name_input.textChanged.connect(
158
+ self._on_segment_name_change
159
+ )
160
+
161
+ #self._apply_defaults()
162
+ self._install_combo_wheel_guards()
163
+
164
+ # ============================================================
165
+ # Styling
166
+ # ============================================================
167
+ def _box_style(self):
168
+ return """
169
+ QGroupBox {
170
+ color:#cfe0ff;
171
+ font-weight:600;
172
+ border:1px solid #2d3a4e;
173
+ border-radius:6px;
174
+ padding:20px;
175
+ margin-bottom:20px;
176
+ }
177
+ """
178
+
179
+ # ============================================================
180
+ # Dropdown Logic
181
+ # ============================================================
182
+ def populate_nested_dropdown(self, index):
183
+ # Clear old subsegment options
184
+ self.nested_dropdown.clear()
185
+
186
+ # Populate subsegments for the selected segment type
187
+ self.nested_dropdown.addItems(
188
+ list(segment_data_fields[index].keys())
189
+ )
190
+
191
+ def _on_top_dropdown_change(self, idx):
192
+ # Update available subsegments when segment type changes
193
+ self.populate_nested_dropdown(idx)
194
+
195
+ # Rebuild the subsegment-specific input fields
196
+ self.update_subsegment_fields()
197
+
198
+ # ============================================================
199
+ # Subsegment Handling
200
+ # ============================================================
201
+ def update_subsegment_fields(self):
202
+ # Remove the previous subsegment input widget if it exists
203
+ if self.subsegment_entry_widget:
204
+ self.subsegment_entry_widget.setParent(None)
205
+ self.subsegment_entry_widget = None
206
+
207
+ # Get the selected segment type and subsegment
208
+ top = self.top_dropdown.currentIndex()
209
+ sub = self.nested_dropdown.currentText()
210
+
211
+ # Do nothing if no subsegment is selected
212
+ if not sub:
213
+ return
214
+
215
+ # Create input fields for the selected subsegment
216
+ self.subsegment_entry_widget = DataEntryWidget(
217
+ segment_data_fields[top][sub]
218
+ )
219
+
220
+ # Add the subsegment fields to the UI
221
+ self.details_layout.addWidget(self.subsegment_entry_widget)
222
+ self._install_combo_wheel_guards(self.subsegment_entry_widget)
223
+
224
+ def _apply_config_default(self, config_key):
225
+ cfg_container = getattr(rcaide_io, "rcaide_configs", None)
226
+ if not hasattr(cfg_container, 'keys') or not cfg_container:
227
+ return
228
+ for key in cfg_container.keys():
229
+ if convert_name(key) == config_key:
230
+ self.config_selector.setCurrentText(key)
231
+ break
232
+
233
+ def _on_segment_name_change(self, _):
234
+ # Avoid changing defaults while loading saved data
235
+ if self._suppress_defaults:
236
+ return
237
+
238
+ # Recompute the correct aircraft configuration
239
+ config_key = self._default_config_key(
240
+ self.top_dropdown.currentText(),
241
+ self.nested_dropdown.currentText(),
242
+ self.segment_name_input.text(),
243
+ )
244
+
245
+ # Apply the inferred configuration
246
+ self._apply_config_default(config_key)
247
+
248
+ def _install_combo_wheel_guards(self, root=None):
249
+ root = root or self
250
+ # Apply the guard to existing mission-tab combo boxes, including any
251
+ # detail widgets rebuilt when the segment type changes.
252
+ for combo in root.findChildren(QComboBox):
253
+ combo.installEventFilter(self._combo_wheel_guard)
254
+
255
+ # ============================================================
256
+ # Save / Load
257
+ # ============================================================
258
+ def get_form_data(self):
259
+ # Save only the UI-entered mission fields; do not build RCAIDE objects here.
260
+ data = {
261
+ "Segment Name": self.segment_name_input.text(),
262
+ "top dropdown": self.top_dropdown.currentIndex(),
263
+ "nested dropdown": self.nested_dropdown.currentText(),
264
+ "config": self.config_selector.currentText(),
265
+ "Control Points": int(self.ctrl_points.text()),
266
+ "Solver": "root" if self.solver_root.isChecked() else "optimize",
267
+ "flight forces": self.dof_entry_widget.get_values(),
268
+ "flight controls": self.flight_controls_widget.get_data(),
269
+ }
270
+ data.update(self.subsegment_entry_widget.get_values())
271
+ return data
272
+
273
+ def get_data(self):
274
+ # RCAIDE segment creation is kept separate so plain mission saves cannot fail on missing analyses.
275
+ data = self.get_form_data()
276
+ return data, self.create_rcaide_segment()
277
+
278
+ def load_data(self, data):
279
+ self._suppress_defaults = True
280
+
281
+ self.segment_name_input.setText(data["Segment Name"])
282
+ self.top_dropdown.setCurrentIndex(data["top dropdown"])
283
+ self.populate_nested_dropdown(data["top dropdown"])
284
+ self.nested_dropdown.setCurrentText(data["nested dropdown"])
285
+ config_val = data.get("config", "")
286
+ if isinstance(config_val, int):
287
+ self.config_selector.setCurrentIndex(config_val)
288
+ else:
289
+ self.config_selector.setCurrentText(str(config_val))
290
+ self.ctrl_points.setText(str(data.get("Control Points", 16)))
291
+
292
+ if data.get("Solver", "root") == "root":
293
+ self.solver_root.setChecked(True)
294
+ else:
295
+ self.solver_opt.setChecked(True)
296
+
297
+ self.update_subsegment_fields()
298
+ self.subsegment_entry_widget.load_data(data)
299
+ self.dof_entry_widget.load_data(data["flight forces"])
300
+ self.flight_controls_widget.load_data(data["flight controls"])
301
+
302
+ self._suppress_defaults = False
303
+
304
+ # ============================================================
305
+ # RCAIDE Segment Creation
306
+ # ============================================================
307
+ def create_rcaide_segment(self):
308
+ top = self.top_dropdown.currentIndex()
309
+ sub = self.nested_dropdown.currentText()
310
+
311
+ seg = segment_rcaide_classes[top][sub]()
312
+ seg.tag = self.segment_name_input.text()
313
+
314
+ if hasattr(seg, "state") and hasattr(seg.state, "numerics"):
315
+ solver = (
316
+ "root_finder"
317
+ if self.solver_root.isChecked()
318
+ else "optimize"
319
+ )
320
+ # Force cruise to use root_finder for better trim convergence
321
+ if top == 1:
322
+ solver = "root_finder"
323
+ if hasattr(seg.state.numerics, "solver"):
324
+ seg.state.numerics.solver.type = solver
325
+
326
+ vals_si = self.subsegment_entry_widget.get_values_si()
327
+ for label, _, rcaide_label in (
328
+ self.subsegment_entry_widget.data_units_labels
329
+ ):
330
+ set_data(seg, rcaide_label, vals_si[label][0])
331
+
332
+ dof_vals = self.dof_entry_widget.get_values()
333
+ for label, _, rcaide_label in (
334
+ self.dof_entry_widget.data_units_labels
335
+ ):
336
+ set_data(seg, rcaide_label, dof_vals[label][0])
337
+
338
+ cfg = convert_name(self.config_selector.currentText())
339
+ analyses = rcaide_io.rcaide_analyses.get(cfg)
340
+ if analyses is None or (hasattr(analyses, "__len__") and len(analyses) == 0):
341
+ fallback = None
342
+ for candidate in rcaide_io.rcaide_analyses.values():
343
+ if hasattr(candidate, "__len__") and len(candidate) > 0:
344
+ fallback = candidate
345
+ break
346
+ if fallback is not None:
347
+ analyses = fallback
348
+ else:
349
+ raise RuntimeError(
350
+ "No RCAIDE analyses available. "
351
+ "Go to Mission tab and press 'Save Analyses'."
352
+ )
353
+
354
+ seg.analyses.extend(analyses)
355
+ self.flight_controls_widget.set_control_variables(seg)
356
+ seg.control_points = int(self.ctrl_points.text())
357
+
358
+ return seg
tabs/solve/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ # RCAIDE_GUI/tabs/solve/__init__.py
2
+
3
+ from . import plots
4
+ from .solve import *
@@ -0,0 +1,12 @@
1
+ # RCAIDE_GUI/tabs/solve/plots/__init__.py
2
+
3
+ from .create_plot_widgets import create_plot_widgets
4
+ from .aerodynamics import *
5
+ from .common import *
6
+ from .emissions import *
7
+ from .energy import *
8
+ from .mission import *
9
+ from .aeroacoustics import *
10
+ from .thermal_management import *
11
+ from .stability import *
12
+ from .weights import *
@@ -0,0 +1,6 @@
1
+ # RCAIDE_GUI/tabs/solve/plots/aeroacoustics/__init__.py
2
+
3
+ from .plot_noise_level import plot_noise_level
4
+ from .plot_3D_noise_contour import plot_3D_noise_contour
5
+ from .plot_2D_noise_contour import plot_2D_noise_contour
6
+ from .plot_noise_certification_contour import plot_noise_certification_contour
@@ -0,0 +1,184 @@
1
+ # RCAIDE_GUI/tabs/solve/plots/aeroacoustics/plot_2D_noise_contour.py
2
+
3
+ # RCAIDE imports
4
+ from RCAIDE.Framework.Core import Units
5
+
6
+ # Python imports
7
+ import numpy as np
8
+ import matplotlib.pyplot as plt
9
+ import matplotlib.colors
10
+ import matplotlib.colors as colors
11
+
12
+ # ------------------------------------------------------------------------------
13
+ # Plot 2D Noise Contour
14
+ # ------------------------------------------------------------------------------
15
+ def plot_2D_noise_contour(noise_data,
16
+ noise_level = None,
17
+ min_noise_level = 35,
18
+ max_noise_level = 90,
19
+ noise_scale_label = None,
20
+ save_figure = False,
21
+ show_figure = True,
22
+ save_filename = "2D_Noise_Contour",
23
+ show_elevation = False,
24
+ use_lat_long_coordinates = False,
25
+ colormap = 'jet',
26
+ file_type = ".png",
27
+ width = 10,
28
+ height = 7):
29
+ """
30
+ Creates a 2D contour plot of noise levels with optional terrain elevation.
31
+
32
+ Parameters
33
+ ----------
34
+ noise_data : NoiseData
35
+ RCAIDE noise data structure containing:
36
+ - microphone_locations[:,:,0:3]
37
+ 3D array of microphone positions
38
+ - microphone_coordinates[:,:,0:2]
39
+ Latitude and longitude of measurement points
40
+ - topography_file
41
+ Optional path to terrain elevation data
42
+
43
+ noise_level : ndarray, optional
44
+ 2D array of noise levels at measurement points
45
+
46
+ min_noise_level : float, optional
47
+ Minimum noise level for contour scale (default: 35 dB)
48
+
49
+ max_noise_level : float, optional
50
+ Maximum noise level for contour scale (default: 90 dB)
51
+
52
+ noise_scale_label : str, optional
53
+ Label for noise metric (e.g., "dBA", "EPNL", etc.)
54
+
55
+ save_figure : bool, optional
56
+ Flag for saving the figure (default: False)
57
+
58
+ show_figure : bool, optional
59
+ Flag to display the figure (default: True)
60
+
61
+ save_filename : str, optional
62
+ Name of file for saved figure (default: "2D_Noise_Contour")
63
+
64
+ show_elevation : bool, optional
65
+ Flag to display terrain elevation contours (default: False)
66
+
67
+ use_lat_long_coordinates : bool, optional
68
+ Flag to use geographic coordinates (default: True)
69
+
70
+ colormap : str, optional
71
+ Colormap specification for noise contours (default: 'jet')
72
+
73
+ file_type : str, optional
74
+ File extension for saved figure (default: ".png")
75
+
76
+ width : float, optional
77
+ Figure width in inches (default: 10)
78
+
79
+ height : float, optional
80
+ Figure height in inches (default: 7)
81
+
82
+ Returns
83
+ -------
84
+ fig : plotly.graph_objects.Figure
85
+ Handle to the generated figure
86
+
87
+ Notes
88
+ -----
89
+ Creates visualization showing:
90
+ - Noise level contours
91
+ - Optional terrain elevation
92
+ - Geographic or Cartesian coordinates
93
+ - Customizable color scales
94
+
95
+ **Definitions**
96
+
97
+ 'Noise Level'
98
+ Sound pressure level or derived metric
99
+ 'Terrain Elevation'
100
+ Height above sea level
101
+ 'Geographic Coordinates'
102
+ Latitude and longitude positions
103
+
104
+ See Also
105
+ --------
106
+ RCAIDE.Library.Plots.Noise.plot_3D_noise_contour : 3D visualization of noise field
107
+ RCAIDE.Library.Plots.Noise.post_process_noise_data : Data preparation for plotting
108
+ """
109
+
110
+ elevation = noise_data.microphone_locations[:,:,2]/Units.ft
111
+ colors_undersea = plt.cm.terrain(np.linspace(0, 0.17, 56))
112
+ colors_land = plt.cm.terrain(np.linspace(0.25, 1, 200))
113
+ colors = np.vstack((colors_undersea, colors_land))
114
+ cut_terrain_map = matplotlib.colors.LinearSegmentedColormap.from_list('cut_terrain', colors)
115
+ norm = FixPointNormalize(sealevel=0,vmax=np.max(elevation),vmin=np.min(elevation))
116
+
117
+ fig = plt.figure(save_filename)
118
+ fig.set_size_inches(width,height)
119
+
120
+ axis = fig.add_subplot(1,1,1)
121
+
122
+ noise_levels = np.linspace(min_noise_level,max_noise_level,10)
123
+ noise_cmap = plt.get_cmap('turbo')
124
+ noise_new_cmap = truncate_colormap(noise_cmap,0.0, 1.0)
125
+
126
+ if use_lat_long_coordinates:
127
+ LAT = noise_data.microphone_coordinates[:,:,0]
128
+ LONG = noise_data.microphone_coordinates[:,:,1]
129
+ axis.set_xlabel('Longitude [°]')
130
+ axis.set_ylabel('Latitude [°]')
131
+ else:
132
+ LAT = noise_data.microphone_locations[:,:,0]
133
+ LONG = noise_data.microphone_locations[:,:,1]
134
+ axis.set_xlabel('x [m]')
135
+ axis.set_ylabel('y [m]')
136
+
137
+ if show_elevation:
138
+ CS_1 = axis.contourf(LONG,LAT,elevation,cmap =cut_terrain_map,norm=norm,levels = 20, alpha=0.5)
139
+ cbar = fig.colorbar(CS_1, ax=axis)
140
+ cbar.ax.set_ylabel('Elevation above sea level [ft]', rotation = 90)
141
+
142
+ # plot aircraft noise levels
143
+ CS_2 = axis.contourf(LONG,LAT,noise_level ,noise_levels,cmap = noise_new_cmap)
144
+ cbar = fig.colorbar(CS_2, ax=axis)
145
+ cbar.ax.set_ylabel(noise_scale_label, rotation = 90)
146
+
147
+ fig.tight_layout()
148
+ if save_figure:
149
+ figure_title = save_filename
150
+ plt.savefig(figure_title + file_type )
151
+
152
+ return fig
153
+
154
+ # ------------------------------------------------------------------
155
+ # Truncate colormaps
156
+ # ------------------------------------------------------------------
157
+ def truncate_colormap(cmap, minval=0.0, maxval=1.0, n=100):
158
+ new_cmap = colors.LinearSegmentedColormap.from_list(
159
+ 'trunc({n},{a:.2f},{b:.2f})'.format(n=cmap.name, a=minval, b=maxval),
160
+ cmap(np.linspace(minval, maxval, n)))
161
+ return new_cmap
162
+
163
+
164
+ class FixPointNormalize(matplotlib.colors.Normalize):
165
+ """
166
+ Inspired by https://stackoverflow.com/questions/20144529/shifted-colorbar-matplotlib
167
+ Subclassing Normalize to obtain a colormap with a fixpoint
168
+ somewhere in the middle of the colormap.
169
+ This may be useful for a `terrain` map, to set the "sea level"
170
+ to a color in the blue/turquise range.
171
+ """
172
+ def __init__(self, vmin=None, vmax=None, sealevel=0, col_val = 0.21875, clip=False):
173
+ # sealevel is the fix point of the colormap (in data units)
174
+ self.sealevel = sealevel
175
+ # col_val is the color value in the range [0,1] that should represent the sealevel.
176
+ self.col_val = col_val
177
+ matplotlib.colors.Normalize.__init__(self, vmin, vmax, clip)
178
+
179
+ def __call__(self, value, clip=None):
180
+ x, y = [self.vmin, self.sealevel, self.vmax], [0, self.col_val, 1]
181
+ return np.ma.masked_array(np.interp(value, x, y))
182
+
183
+ def colorax(vmin, vmax):
184
+ return dict(cmin=vmin,cmax=vmax)