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
tabs/__init__.py
ADDED
|
@@ -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,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"]
|