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
|
@@ -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,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)
|