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,171 @@
|
|
|
1
|
+
# RCAIDE_GUI/tabs/geometry/frames/vehicle_frame.py
|
|
2
|
+
#
|
|
3
|
+
# Created: Dec 2025, M. Clarke
|
|
4
|
+
|
|
5
|
+
# ----------------------------------------------------------------------------------------------------------------------
|
|
6
|
+
# IMPORT
|
|
7
|
+
# ----------------------------------------------------------------------------------------------------------------------
|
|
8
|
+
from PyQt6.QtWidgets import QWidget, QLabel, QVBoxLayout, QPushButton, QLineEdit, QHBoxLayout, \
|
|
9
|
+
QSpacerItem, QSizePolicy, QComboBox
|
|
10
|
+
|
|
11
|
+
from tabs.geometry.frames import GeometryFrame
|
|
12
|
+
from utilities import create_line_bar, Units, create_scroll_area
|
|
13
|
+
from common_widgets import DataEntryWidget
|
|
14
|
+
import rcaide_io
|
|
15
|
+
|
|
16
|
+
_FAR_PARTS = ["None", "23", "25", "35", "91", "107", "135"]
|
|
17
|
+
_CATEGORIES = ["None", "normal", "utility", "acrobatic", "commuter"]
|
|
18
|
+
|
|
19
|
+
# Labels that are rendered as standalone QComboBoxes rather than in DataEntryWidget
|
|
20
|
+
_COMBO_LABELS = {"FAR Part Classification Number", "Aircraft Category"}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class VehicleFrame(GeometryFrame):
|
|
24
|
+
data_units_labels = [
|
|
25
|
+
# --- General ---
|
|
26
|
+
("Reference Area", Units.Area, "reference_area"),
|
|
27
|
+
("Passengers", Units.Count, "number_of_passengers"),
|
|
28
|
+
# --- Mass Properties ---
|
|
29
|
+
("Max Takeoff Weight", Units.Mass, "mass_properties.max_takeoff"),
|
|
30
|
+
("Takeoff Weight", Units.Mass, "mass_properties.takeoff"),
|
|
31
|
+
("Operating Empty Weight", Units.Mass, "mass_properties.operating_empty"),
|
|
32
|
+
("Max Fuel Weight", Units.Mass, "mass_properties.max_fuel"),
|
|
33
|
+
("Maximum Zero Fuel Weight", Units.Mass, "mass_properties.max_zero_fuel"),
|
|
34
|
+
("Fuel Weight", Units.Mass, "mass_properties.fuel"),
|
|
35
|
+
("Maximum Payload Weight", Units.Mass, "mass_properties.max_payload"),
|
|
36
|
+
("Payload Weight", Units.Mass, "mass_properties.payload"),
|
|
37
|
+
("Maximum Landing Weight", Units.Mass, "mass_properties.max_landing"),
|
|
38
|
+
("Landing Weight", Units.Mass, "mass_properties.landing"),
|
|
39
|
+
("Cargo Weight", Units.Mass, "mass_properties.cargo"),
|
|
40
|
+
("Center of Gravity", Units.Position, "mass_properties.center_of_gravity"),
|
|
41
|
+
("Moment of Intertia", Units.Intertia, "mass_properties.moments_of_inertia.tensor"),
|
|
42
|
+
# --- Flight Envelope ---
|
|
43
|
+
("Ultimate Load", Units.Unitless, "flight_envelope.ultimate_load"),
|
|
44
|
+
("Positive Limit Load", Units.Unitless, "flight_envelope.positive_limit_load"),
|
|
45
|
+
("Negative Limit Load", Units.Unitless, "flight_envelope.negative_limit_load"),
|
|
46
|
+
("Design Dynamic Pressure", Units.Pressure, "flight_envelope.design_dynamic_pressure"),
|
|
47
|
+
("Design Mach Number", Units.Unitless, "flight_envelope.design_mach_number"),
|
|
48
|
+
("Design Cruise Altitude", Units.Length, "flight_envelope.design_cruise_altitude"),
|
|
49
|
+
("Design Range", Units.Length, "flight_envelope.design_range"),
|
|
50
|
+
("V2/VS Ratio", Units.Unitless, "flight_envelope.V2_VS_ratio"),
|
|
51
|
+
("Maximum Lift Coefficient", Units.Unitless, "flight_envelope.maximum_lift_coefficient"),
|
|
52
|
+
("Minimum Lift Coefficient", Units.Unitless, "flight_envelope.minimum_lift_coefficient"),
|
|
53
|
+
# Aircraft Category and FAR Part rendered as QComboBoxes (excluded from DataEntryWidget)
|
|
54
|
+
("Aircraft Category", Units.Unitless, "flight_envelope.category"),
|
|
55
|
+
("FAR Part Classification Number", Units.Unitless, "flight_envelope.FAR_part_number"),
|
|
56
|
+
# --- Maneuver ---
|
|
57
|
+
("Maneuver Load Alleviation Factor", Units.Unitless, "flight_envelope.maneuver.load_alleviation_factor"),
|
|
58
|
+
("Maneuver Speed Max Gust", Units.Velocity, "flight_envelope.maneuver.equivalent_speed.velocity_max_gust"),
|
|
59
|
+
("Maneuver Speed Max Cruise", Units.Velocity, "flight_envelope.maneuver.equivalent_speed.velocity_max_cruise"),
|
|
60
|
+
("Maneuver Speed Max Dive", Units.Velocity, "flight_envelope.maneuver.equivalent_speed.velocity_max_dive"),
|
|
61
|
+
("Maneuver Load Factor Max Gust", Units.Unitless, "flight_envelope.maneuver.load_factor.velocity_max_gust"),
|
|
62
|
+
("Maneuver Load Factor Max Cruise", Units.Unitless, "flight_envelope.maneuver.load_factor.velocity_max_cruise"),
|
|
63
|
+
("Maneuver Load Factor Max Dive", Units.Unitless, "flight_envelope.maneuver.load_factor.velocity_max_dive"),
|
|
64
|
+
# --- Systems ---
|
|
65
|
+
("Control Systems", Units.Unitless, "systems.control"),
|
|
66
|
+
("Accessories", Units.Unitless, "systems.accessories"),
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
def __init__(self):
|
|
70
|
+
super().__init__()
|
|
71
|
+
self.data = []
|
|
72
|
+
self.data_entry_widget = None
|
|
73
|
+
self.main_layout = QVBoxLayout()
|
|
74
|
+
create_scroll_area(self)
|
|
75
|
+
|
|
76
|
+
self.name_line_edit = QLineEdit()
|
|
77
|
+
|
|
78
|
+
self.main_layout.addWidget(QLabel("<b>Vehicle</b>"))
|
|
79
|
+
self.main_layout.addWidget(create_line_bar())
|
|
80
|
+
|
|
81
|
+
name_layout = QHBoxLayout()
|
|
82
|
+
name_layout.addWidget(QLabel("Name:"))
|
|
83
|
+
name_layout.addWidget(self.name_line_edit)
|
|
84
|
+
self.main_layout.addLayout(name_layout)
|
|
85
|
+
|
|
86
|
+
# Numeric / position fields rendered by DataEntryWidget
|
|
87
|
+
widget_labels = [l for l in self.data_units_labels if l[0] not in _COMBO_LABELS]
|
|
88
|
+
self.data_entry_widget = DataEntryWidget(widget_labels)
|
|
89
|
+
self.wire_auto_save(self.data_entry_widget)
|
|
90
|
+
self.main_layout.addWidget(self.data_entry_widget)
|
|
91
|
+
|
|
92
|
+
# Inject the combo rows directly into DataEntryWidget's QGridLayout so
|
|
93
|
+
# they share its column widths and align perfectly with all other fields.
|
|
94
|
+
# DataEntryWidget uses 4 sub-columns per field: label | input(span 2) | unit-picker
|
|
95
|
+
grid = self.data_entry_widget.layout()
|
|
96
|
+
next_row = grid.rowCount()
|
|
97
|
+
|
|
98
|
+
self.category_combo = QComboBox()
|
|
99
|
+
self.category_combo.addItems(_CATEGORIES)
|
|
100
|
+
grid.addWidget(QLabel("Aircraft Category:"), next_row, 0)
|
|
101
|
+
grid.addWidget(self.category_combo, next_row, 1, 1, 2)
|
|
102
|
+
grid.addItem(
|
|
103
|
+
QSpacerItem(80, 0, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum), next_row, 3)
|
|
104
|
+
|
|
105
|
+
self.far_combo = QComboBox()
|
|
106
|
+
self.far_combo.addItems(_FAR_PARTS)
|
|
107
|
+
grid.addWidget(QLabel("FAR Part Classification Number:"), next_row, 4)
|
|
108
|
+
grid.addWidget(self.far_combo, next_row, 5, 1, 2)
|
|
109
|
+
grid.addItem(
|
|
110
|
+
QSpacerItem(80, 0, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum), next_row, 7)
|
|
111
|
+
|
|
112
|
+
save_button = QPushButton("Save")
|
|
113
|
+
save_button.clicked.connect(self.save_data)
|
|
114
|
+
self.main_layout.addWidget(save_button)
|
|
115
|
+
|
|
116
|
+
self.main_layout.addItem(QSpacerItem(
|
|
117
|
+
20, 40, QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.Expanding))
|
|
118
|
+
|
|
119
|
+
# ------------------------------------------------------------------
|
|
120
|
+
# Helpers for combo load / save
|
|
121
|
+
# ------------------------------------------------------------------
|
|
122
|
+
@staticmethod
|
|
123
|
+
def _load_combo(combo, raw, choices):
|
|
124
|
+
"""Set combo to the string value in raw, falling back to 'None'."""
|
|
125
|
+
val = raw[0] if isinstance(raw, (list, tuple)) else raw
|
|
126
|
+
text = str(val) if val is not None else "None"
|
|
127
|
+
if text in choices:
|
|
128
|
+
combo.setCurrentText(text)
|
|
129
|
+
else:
|
|
130
|
+
combo.setCurrentText("None")
|
|
131
|
+
|
|
132
|
+
# ------------------------------------------------------------------
|
|
133
|
+
# GeometryFrame interface
|
|
134
|
+
# ------------------------------------------------------------------
|
|
135
|
+
def load_data(self, data, index):
|
|
136
|
+
self.data = data
|
|
137
|
+
self.index = index
|
|
138
|
+
|
|
139
|
+
self.name_line_edit.setText(data["name"])
|
|
140
|
+
self.data_entry_widget.load_data(data)
|
|
141
|
+
|
|
142
|
+
self._load_combo(self.category_combo,
|
|
143
|
+
data.get("Aircraft Category", [None, 0]),
|
|
144
|
+
_CATEGORIES)
|
|
145
|
+
self._load_combo(self.far_combo,
|
|
146
|
+
data.get("FAR Part Classification Number", [None, 0]),
|
|
147
|
+
_FAR_PARTS)
|
|
148
|
+
|
|
149
|
+
def create_rcaide_structure(self):
|
|
150
|
+
raise NotImplementedError("This method should not be called")
|
|
151
|
+
|
|
152
|
+
def get_data_values(self):
|
|
153
|
+
data = self.data_entry_widget.get_values()
|
|
154
|
+
data["name"] = self.name_line_edit.text()
|
|
155
|
+
|
|
156
|
+
cat_text = self.category_combo.currentText()
|
|
157
|
+
data["Aircraft Category"] = [None if cat_text == "None" else cat_text, 0]
|
|
158
|
+
|
|
159
|
+
far_text = self.far_combo.currentText()
|
|
160
|
+
data["FAR Part Classification Number"] = [None if far_text == "None" else int(far_text), 0]
|
|
161
|
+
|
|
162
|
+
return data
|
|
163
|
+
|
|
164
|
+
def update_layout(self):
|
|
165
|
+
if isinstance(rcaide_io.rcaide_vehicle, list) and rcaide_io.rcaide_vehicle[0]:
|
|
166
|
+
self.load_data(rcaide_io.rcaide_vehicle[0], 0)
|
|
167
|
+
|
|
168
|
+
def save_data(self):
|
|
169
|
+
assert self.save_function is not None
|
|
170
|
+
self.save_function(self.tab_index, vehicle_component=None,
|
|
171
|
+
index=-1, data=self.get_data_values(), persist=True)
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# RCAIDE_GUI/tabs/geometry/frames/wings/__init__.py
|
|
2
|
+
# ----------------------------------------------------------------------------------------------------------------------
|
|
3
|
+
# IMPORT
|
|
4
|
+
# ----------------------------------------------------------------------------------------------------------------------
|
|
5
|
+
from tabs.geometry.frames.wings.wings_frame import WingsFrame
|
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
# RCAIDE_GUI/tabs/geometry/frames/wings/wings_frame.py
|
|
2
|
+
#
|
|
3
|
+
# Created: Dec 2025, M. Clarke
|
|
4
|
+
|
|
5
|
+
# ----------------------------------------------------------------------------------------------------------------------
|
|
6
|
+
# IMPORT
|
|
7
|
+
# ----------------------------------------------------------------------------------------------------------------------
|
|
8
|
+
# RCAIDE imports
|
|
9
|
+
import RCAIDE
|
|
10
|
+
|
|
11
|
+
# PyQT Imports
|
|
12
|
+
from PyQt6.QtCore import Qt
|
|
13
|
+
from PyQt6.QtWidgets import QWidget, QPushButton, QLineEdit, QSizePolicy, QVBoxLayout,\
|
|
14
|
+
QHBoxLayout, QLabel, QFrame, QScrollArea, QSpacerItem, QComboBox
|
|
15
|
+
|
|
16
|
+
# RCAIDE GUI imports
|
|
17
|
+
from tabs.geometry.frames import GeometryFrame
|
|
18
|
+
from tabs.geometry.widgets import WingCSWidget, WingSectionWidget, CabinWidget
|
|
19
|
+
from utilities import show_popup, create_line_bar, Units, create_scroll_area, set_data, clear_layout
|
|
20
|
+
from common_widgets import DataEntryWidget
|
|
21
|
+
|
|
22
|
+
# ----------------------------------------------------------------------------------------------------------------------
|
|
23
|
+
# Wing Frame
|
|
24
|
+
# ----------------------------------------------------------------------------------------------------------------------
|
|
25
|
+
class WingsFrame(GeometryFrame):
|
|
26
|
+
def __init__(self):
|
|
27
|
+
"""Create a frame for entering wing data."""
|
|
28
|
+
super(WingsFrame, self).__init__()
|
|
29
|
+
self.data_entry_widget: DataEntryWidget | None = None
|
|
30
|
+
|
|
31
|
+
self.cabin_widgets = []
|
|
32
|
+
self.side_cabin_widgets = []
|
|
33
|
+
|
|
34
|
+
create_scroll_area(self)
|
|
35
|
+
|
|
36
|
+
assert self.main_layout is not None
|
|
37
|
+
self.main_layout.addWidget(QLabel("<b>Wings</b>"))
|
|
38
|
+
self.main_layout.addWidget(create_line_bar())
|
|
39
|
+
|
|
40
|
+
self.add_name_layout()
|
|
41
|
+
self.add_wing_type()
|
|
42
|
+
|
|
43
|
+
# List of data labels
|
|
44
|
+
self.data_units_labels = [
|
|
45
|
+
("Spans Projected", Units.Length, "spans.projected"),
|
|
46
|
+
("Reference Area", Units.Area, "areas.reference"),
|
|
47
|
+
("Wetted Area", Units.Area, "areas.wetted"),
|
|
48
|
+
("Root Chord", Units.Length, "chords.root"),
|
|
49
|
+
("Tip Chord", Units.Length, "chords.tip"),
|
|
50
|
+
("Mean Aerodynamic Chord", Units.Length, "chords.mean_aerodynamic"),
|
|
51
|
+
("Quarter Chord Sweep Angle", Units.Angle, "sweeps.quarter_chord"),
|
|
52
|
+
("Leading Edge Sweep Angle", Units.Angle, "sweeps.leading_edge"),
|
|
53
|
+
("Root Chord Twist Angle", Units.Angle, "twists.root"),
|
|
54
|
+
("Tip Chord Twist Angle", Units.Angle, "twists.tip"),
|
|
55
|
+
("Taper", Units.Unitless, "taper"),
|
|
56
|
+
("Dihedral", Units.Angle, "dihedral"),
|
|
57
|
+
("Aspect Ratio", Units.Unitless, "aspect_ratio"),
|
|
58
|
+
("Thickness to Chord", Units.Unitless, "thickness_to_chord"),
|
|
59
|
+
("Aerodynamic Center", Units.Position, "aerodynamic_center"),
|
|
60
|
+
("Origin", Units.Position, "origin"),
|
|
61
|
+
("Vertical", Units.Boolean, "vertical"),
|
|
62
|
+
("X-Y Plane Symmetric", Units.Boolean, "xy_plane_symmetric"),
|
|
63
|
+
("High Lift", Units.Boolean, "high_lift"),
|
|
64
|
+
("X-Z Plane Symmetric", Units.Boolean, "xz_plane_symmetric"),
|
|
65
|
+
("T-Tail", Units.Boolean, "t_tail"),
|
|
66
|
+
("Y-Z Plane Symmetric", Units.Boolean, "yz_plane_symmetric"),
|
|
67
|
+
("Dynamic Pressure Ratio", Units.Unitless, "dynamic_pressure_ratio"),
|
|
68
|
+
("Exposed Root Chord Offset", Units.Unitless,
|
|
69
|
+
"exposed_root_chord_offset")
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
# Add the data entry widget to the main layout
|
|
73
|
+
self.data_entry_widget = DataEntryWidget(self.data_units_labels)
|
|
74
|
+
self.wire_auto_save(self.data_entry_widget)
|
|
75
|
+
self.main_layout.addWidget(self.data_entry_widget)
|
|
76
|
+
self.main_layout.addWidget(create_line_bar())
|
|
77
|
+
|
|
78
|
+
# Add the sections layout to the main layout
|
|
79
|
+
self.wing_sections_layout = QVBoxLayout()
|
|
80
|
+
self.main_layout.addLayout(self.wing_sections_layout)
|
|
81
|
+
|
|
82
|
+
self.wing_cs_layout = QVBoxLayout()
|
|
83
|
+
self.main_layout.addWidget(create_line_bar())
|
|
84
|
+
# self.main_layout.addWidget(QLabel("Control Surfaces"))
|
|
85
|
+
self.main_layout.addLayout(self.wing_cs_layout)
|
|
86
|
+
|
|
87
|
+
self.cabins_layout = QVBoxLayout()
|
|
88
|
+
self.main_layout.addLayout(self.cabins_layout)
|
|
89
|
+
|
|
90
|
+
self.side_cabins_layout = QVBoxLayout()
|
|
91
|
+
self.main_layout.addLayout(self.side_cabins_layout)
|
|
92
|
+
|
|
93
|
+
self.main_layout.addWidget(create_line_bar())
|
|
94
|
+
|
|
95
|
+
self.add_buttons_layout()
|
|
96
|
+
|
|
97
|
+
# Adds scroll function
|
|
98
|
+
self.main_layout.addItem(QSpacerItem(
|
|
99
|
+
20, 40, QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.Expanding))
|
|
100
|
+
|
|
101
|
+
def add_name_layout(self):
|
|
102
|
+
assert self.main_layout is not None
|
|
103
|
+
|
|
104
|
+
name_layout = QHBoxLayout()
|
|
105
|
+
spacer_left = QSpacerItem(
|
|
106
|
+
50, 5, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
|
|
107
|
+
spacer_right = QSpacerItem(
|
|
108
|
+
200, 5, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
|
|
109
|
+
self.name_line_edit = QLineEdit(self)
|
|
110
|
+
name_layout.addItem(spacer_left)
|
|
111
|
+
name_layout.addWidget(QLabel("Name: "))
|
|
112
|
+
name_layout.addWidget(self.name_line_edit)
|
|
113
|
+
name_layout.addItem(spacer_right)
|
|
114
|
+
self.main_layout.addLayout(name_layout)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def add_wing_type(self):
|
|
118
|
+
wing_type_label = QLabel("Wing Type: ")
|
|
119
|
+
wing_type_layout = QHBoxLayout()
|
|
120
|
+
spacer_left = QSpacerItem(
|
|
121
|
+
50, 5, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
|
|
122
|
+
spacer_right = QSpacerItem(
|
|
123
|
+
200, 5, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
|
|
124
|
+
wing_type_layout.addItem(spacer_left)
|
|
125
|
+
wing_type_layout.addWidget(wing_type_label)
|
|
126
|
+
self.wing_type_combo = QComboBox()
|
|
127
|
+
self.wing_type_combo.addItems(["Select Wing Type", "All Moving Surface", "Blended Wing Body", "Horizontal Tail", "Main Wing", "Stabilator", "Vertical Tail","Vertical Tail All Moving", "Wing"])
|
|
128
|
+
self.wing_type_combo.setFixedWidth(600)
|
|
129
|
+
wing_type_layout.addWidget(self.wing_type_combo, alignment=Qt.AlignmentFlag.AlignCenter)
|
|
130
|
+
wing_type_layout.addItem(spacer_right)
|
|
131
|
+
|
|
132
|
+
assert self.main_layout is not None
|
|
133
|
+
self.main_layout.addLayout(wing_type_layout)
|
|
134
|
+
|
|
135
|
+
# noinspection PyUnresolvedReferences
|
|
136
|
+
def add_buttons_layout(self):
|
|
137
|
+
"""Add the save, delete, and new buttons to the layout."""
|
|
138
|
+
new_section_button = QPushButton("Add Wing Segment", self)
|
|
139
|
+
new_section_button.setStyleSheet("color:#dbe7ff; font-weight:500; margin:0; padding:0;")
|
|
140
|
+
new_cs_button = QPushButton("Add Control Surface", self)
|
|
141
|
+
new_cs_button.setStyleSheet("color:#dbe7ff; font-weight:500; margin:0; padding:0;")
|
|
142
|
+
new_cabin_button = QPushButton("Add Cabin", self)
|
|
143
|
+
new_cabin_button.setStyleSheet("color:#dbe7ff; font-weight:500; margin:0; padding:0;")
|
|
144
|
+
new_side_cabin_button = QPushButton("Add Side Cabin", self)
|
|
145
|
+
new_side_cabin_button.setStyleSheet("color:#dbe7ff; font-weight:500; margin:0; padding:0;")
|
|
146
|
+
save_button = QPushButton("Save Data", self)
|
|
147
|
+
save_button.setStyleSheet("color:#dbe7ff; font-weight:500; margin:0; padding:0;")
|
|
148
|
+
delete_button = QPushButton("Delete Data", self)
|
|
149
|
+
delete_button.setStyleSheet("color:#dbe7ff; font-weight:500; margin:0; padding:0;")
|
|
150
|
+
new_button = QPushButton("New Wing Structure", self)
|
|
151
|
+
new_button.setStyleSheet("color:#dbe7ff; font-weight:500; margin:0; padding:0;")
|
|
152
|
+
|
|
153
|
+
new_section_button.clicked.connect(self.add_wing_section)
|
|
154
|
+
new_cs_button.clicked.connect(self.add_control_surface)
|
|
155
|
+
new_cabin_button.clicked.connect(self.add_regular_cabin)
|
|
156
|
+
new_side_cabin_button.clicked.connect(self.add_side_cabin)
|
|
157
|
+
save_button.clicked.connect(self.save_data)
|
|
158
|
+
delete_button.clicked.connect(self.delete_data)
|
|
159
|
+
new_button.clicked.connect(self.create_new_structure)
|
|
160
|
+
|
|
161
|
+
buttons_layout = QHBoxLayout()
|
|
162
|
+
buttons_layout.addWidget(new_section_button)
|
|
163
|
+
buttons_layout.addWidget(new_cs_button)
|
|
164
|
+
buttons_layout.addWidget(new_cabin_button)
|
|
165
|
+
buttons_layout.addWidget(new_side_cabin_button)
|
|
166
|
+
buttons_layout.addWidget(save_button)
|
|
167
|
+
buttons_layout.addWidget(delete_button)
|
|
168
|
+
buttons_layout.addWidget(new_button)
|
|
169
|
+
|
|
170
|
+
assert self.main_layout is not None
|
|
171
|
+
self.main_layout.addLayout(buttons_layout)
|
|
172
|
+
|
|
173
|
+
# noinspection DuplicatedCode
|
|
174
|
+
def save_data(self):
|
|
175
|
+
"""Call the save function and pass the entered data to it."""
|
|
176
|
+
entered_data, wing = self.get_data_values()
|
|
177
|
+
if self.save_function:
|
|
178
|
+
if self.index >= 0:
|
|
179
|
+
# Existing wing edits must replace the current RCAIDE wing,
|
|
180
|
+
# not append another one or rebuild through a stale frame.
|
|
181
|
+
self.index = self.save_function(
|
|
182
|
+
tab_index=self.tab_index, index=self.index,
|
|
183
|
+
vehicle_component=wing, data=entered_data, persist=True)
|
|
184
|
+
return
|
|
185
|
+
else:
|
|
186
|
+
self.index = self.save_function(
|
|
187
|
+
tab_index=self.tab_index, vehicle_component=wing, data=entered_data, new=True, persist=True)
|
|
188
|
+
|
|
189
|
+
show_popup("Data Saved!", self)
|
|
190
|
+
|
|
191
|
+
def add_wing_section(self):
|
|
192
|
+
self.wing_sections_layout.addWidget(WingSectionWidget(
|
|
193
|
+
self.wing_sections_layout.count(), self.delete_wing_section))
|
|
194
|
+
|
|
195
|
+
def delete_wing_section(self, index):
|
|
196
|
+
item = self.wing_sections_layout.itemAt(index)
|
|
197
|
+
assert item is not None
|
|
198
|
+
widget = item.widget()
|
|
199
|
+
assert widget is not None and isinstance(widget, WingSectionWidget)
|
|
200
|
+
|
|
201
|
+
widget.deleteLater()
|
|
202
|
+
self.wing_sections_layout.removeWidget(widget)
|
|
203
|
+
self.wing_sections_layout.update()
|
|
204
|
+
|
|
205
|
+
for i in range(index, self.wing_sections_layout.count()):
|
|
206
|
+
item = self.wing_sections_layout.itemAt(i)
|
|
207
|
+
if item is None:
|
|
208
|
+
continue
|
|
209
|
+
|
|
210
|
+
widget = item.widget()
|
|
211
|
+
if widget is not None and isinstance(widget, WingSectionWidget):
|
|
212
|
+
widget.index = i
|
|
213
|
+
|
|
214
|
+
def add_control_surface(self):
|
|
215
|
+
self.wing_cs_layout.addWidget(WingCSWidget(
|
|
216
|
+
self.wing_cs_layout.count(), self.delete_control_surface))
|
|
217
|
+
|
|
218
|
+
def delete_control_surface(self, index):
|
|
219
|
+
item = self.wing_cs_layout.itemAt(index)
|
|
220
|
+
assert item is not None
|
|
221
|
+
widget = item.widget()
|
|
222
|
+
assert widget is not None and isinstance(widget, WingCSWidget)
|
|
223
|
+
|
|
224
|
+
widget.deleteLater()
|
|
225
|
+
self.wing_cs_layout.removeWidget(widget)
|
|
226
|
+
self.wing_cs_layout.update()
|
|
227
|
+
|
|
228
|
+
for i in range(index, self.wing_cs_layout.count()):
|
|
229
|
+
item = self.wing_cs_layout.itemAt(i)
|
|
230
|
+
if item is None:
|
|
231
|
+
continue
|
|
232
|
+
|
|
233
|
+
widget = item.widget()
|
|
234
|
+
if widget is not None and isinstance(widget, WingCSWidget):
|
|
235
|
+
widget.index = i
|
|
236
|
+
|
|
237
|
+
def add_regular_cabin(self, data=None):
|
|
238
|
+
widget = CabinWidget(len(self.cabin_widgets), self.delete_regular_cabin, data, cabin_type="Regular")
|
|
239
|
+
self.cabin_widgets.append(widget)
|
|
240
|
+
self.cabins_layout.addWidget(widget)
|
|
241
|
+
|
|
242
|
+
def delete_regular_cabin(self, index):
|
|
243
|
+
if 0 <= index < len(self.cabin_widgets):
|
|
244
|
+
widget = self.cabin_widgets[index]
|
|
245
|
+
self.cabins_layout.removeWidget(widget)
|
|
246
|
+
widget.deleteLater()
|
|
247
|
+
self.cabin_widgets.pop(index)
|
|
248
|
+
for i in range(index, len(self.cabin_widgets)):
|
|
249
|
+
self.cabin_widgets[i].index = i
|
|
250
|
+
|
|
251
|
+
def add_side_cabin(self, data=None):
|
|
252
|
+
widget = CabinWidget(len(self.side_cabin_widgets), self.delete_side_cabin, data, cabin_type="Side")
|
|
253
|
+
self.side_cabin_widgets.append(widget)
|
|
254
|
+
self.side_cabins_layout.addWidget(widget)
|
|
255
|
+
|
|
256
|
+
def delete_side_cabin(self, index):
|
|
257
|
+
if 0 <= index < len(self.side_cabin_widgets):
|
|
258
|
+
widget = self.side_cabin_widgets[index]
|
|
259
|
+
self.side_cabins_layout.removeWidget(widget)
|
|
260
|
+
widget.deleteLater()
|
|
261
|
+
self.side_cabin_widgets.pop(index)
|
|
262
|
+
for i in range(index, len(self.side_cabin_widgets)):
|
|
263
|
+
self.side_cabin_widgets[i].index = i
|
|
264
|
+
|
|
265
|
+
# TODO: Implement proper deletion of data
|
|
266
|
+
def delete_data(self):
|
|
267
|
+
pass
|
|
268
|
+
|
|
269
|
+
def create_new_structure(self):
|
|
270
|
+
"""Create a new wing structure."""
|
|
271
|
+
# # Clear the main data values
|
|
272
|
+
# assert self.data_entry_widget is not None and self.name_line_edit is not None
|
|
273
|
+
self.data_entry_widget.clear_values()
|
|
274
|
+
self.name_line_edit.clear()
|
|
275
|
+
clear_layout(self.wing_sections_layout)
|
|
276
|
+
clear_layout(self.wing_cs_layout)
|
|
277
|
+
clear_layout(self.cabins_layout)
|
|
278
|
+
self.cabin_widgets.clear()
|
|
279
|
+
clear_layout(self.side_cabins_layout)
|
|
280
|
+
self.side_cabin_widgets.clear()
|
|
281
|
+
|
|
282
|
+
# # Clear the wing sections
|
|
283
|
+
# for i in range(self.wing_sections_layout.count()):
|
|
284
|
+
# item = self.wing_sections_layout.itemAt(i)
|
|
285
|
+
# assert item is not None
|
|
286
|
+
# widget = item.widget()
|
|
287
|
+
# assert widget is not None and isinstance(widget, WingSectionWidget)
|
|
288
|
+
# widget.deleteLater()
|
|
289
|
+
|
|
290
|
+
# self.wing_sections_layout.update()
|
|
291
|
+
|
|
292
|
+
# # Clear the control surfaces
|
|
293
|
+
# for i in range(self.wing_cs_layout.count()):
|
|
294
|
+
# item = self.wing_cs_layout.itemAt(i)
|
|
295
|
+
# assert item is not None
|
|
296
|
+
# widget = item.widget()
|
|
297
|
+
# assert widget is not None and isinstance(widget, WingCSWidget)
|
|
298
|
+
|
|
299
|
+
# widget.deleteLater()
|
|
300
|
+
# self.wing_cs_layout.update()
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
self.index = -1
|
|
304
|
+
|
|
305
|
+
def create_rcaide_structure(self):
|
|
306
|
+
assert self.data_entry_widget is not None
|
|
307
|
+
data = self.data_entry_widget.get_values_si()
|
|
308
|
+
data["name"] = self.name_line_edit.text()
|
|
309
|
+
|
|
310
|
+
selected_wing_type = self.wing_type_combo.currentText()
|
|
311
|
+
if selected_wing_type == "All Moving Surface":
|
|
312
|
+
wing = RCAIDE.Library.Components.Wings.All_Moving_Surface()
|
|
313
|
+
elif selected_wing_type == "Blended Wing Body":
|
|
314
|
+
wing = RCAIDE.Library.Components.Wings.Blended_Wing_Body()
|
|
315
|
+
elif selected_wing_type == "Horizontal Tail":
|
|
316
|
+
wing = RCAIDE.Library.Components.Wings.Horizontal_Tail()
|
|
317
|
+
elif selected_wing_type == "Main Wing":
|
|
318
|
+
wing = RCAIDE.Library.Components.Wings.Main_Wing()
|
|
319
|
+
elif selected_wing_type == "Stabilator":
|
|
320
|
+
wing = RCAIDE.Library.Components.Wings.Stabilator()
|
|
321
|
+
elif selected_wing_type == "Vertical Tail":
|
|
322
|
+
wing = RCAIDE.Library.Components.Wings.Vertical_Tail()
|
|
323
|
+
elif selected_wing_type == "Vertical Tail All Moving":
|
|
324
|
+
wing = RCAIDE.Library.Components.Wings.Vertical_Tail_All_Moving()
|
|
325
|
+
elif selected_wing_type == "Wing":
|
|
326
|
+
wing = RCAIDE.Library.Components.Wings.Wing()
|
|
327
|
+
else:
|
|
328
|
+
wing = RCAIDE.Library.Components.Wings.Main_Wing()
|
|
329
|
+
|
|
330
|
+
# assign wing name
|
|
331
|
+
|
|
332
|
+
wing.tag = data["name"]
|
|
333
|
+
|
|
334
|
+
# assign wing properties
|
|
335
|
+
for data_unit_label in self.data_units_labels:
|
|
336
|
+
rcaide_label = data_unit_label[-1]
|
|
337
|
+
user_label = data_unit_label[0]
|
|
338
|
+
set_data(wing, rcaide_label, data[user_label][0])
|
|
339
|
+
|
|
340
|
+
wing.aerodynamic_center = data["Aerodynamic Center"][0][0]
|
|
341
|
+
|
|
342
|
+
for i in range(self.wing_sections_layout.count()):
|
|
343
|
+
item = self.wing_sections_layout.itemAt(i)
|
|
344
|
+
if item is None:
|
|
345
|
+
continue
|
|
346
|
+
|
|
347
|
+
widget = item.widget()
|
|
348
|
+
if widget is not None and isinstance(widget, WingSectionWidget):
|
|
349
|
+
_, wing_section = widget.get_data_values()
|
|
350
|
+
wing.append_segment(wing_section)
|
|
351
|
+
|
|
352
|
+
for i in range(self.wing_cs_layout.count()):
|
|
353
|
+
item = self.wing_cs_layout.itemAt(i)
|
|
354
|
+
if item is None:
|
|
355
|
+
continue
|
|
356
|
+
|
|
357
|
+
widget = item.widget()
|
|
358
|
+
if widget is not None and isinstance(widget, WingCSWidget):
|
|
359
|
+
_, cs = widget.get_data_values()
|
|
360
|
+
if cs is not None:
|
|
361
|
+
wing.append_control_surface(cs)
|
|
362
|
+
|
|
363
|
+
return wing
|
|
364
|
+
|
|
365
|
+
def get_data_values(self):
|
|
366
|
+
"""Retrieve the entered data values from the text fields."""
|
|
367
|
+
assert self.data_entry_widget is not None and self.name_line_edit is not None
|
|
368
|
+
data = self.data_entry_widget.get_values()
|
|
369
|
+
data["name"] = self.name_line_edit.text()
|
|
370
|
+
data["wing_type"] = self.wing_type_combo.currentText()
|
|
371
|
+
wing = self.create_rcaide_structure()
|
|
372
|
+
|
|
373
|
+
data["sections"] = []
|
|
374
|
+
for i in range(self.wing_sections_layout.count()):
|
|
375
|
+
item = self.wing_sections_layout.itemAt(i)
|
|
376
|
+
if item is None:
|
|
377
|
+
continue
|
|
378
|
+
|
|
379
|
+
widget = item.widget()
|
|
380
|
+
if widget is not None and isinstance(widget, WingSectionWidget):
|
|
381
|
+
# create_rcaide_structure already added the segment to wing.
|
|
382
|
+
section_data, _ = widget.get_data_values()
|
|
383
|
+
data["sections"].append(section_data)
|
|
384
|
+
|
|
385
|
+
data["control_surfaces"] = []
|
|
386
|
+
for i in range(self.wing_cs_layout.count()):
|
|
387
|
+
item = self.wing_cs_layout.itemAt(i)
|
|
388
|
+
if item is None:
|
|
389
|
+
continue
|
|
390
|
+
|
|
391
|
+
widget = item.widget()
|
|
392
|
+
if widget is not None and isinstance(widget, WingCSWidget):
|
|
393
|
+
# create_rcaide_structure already added valid control surfaces.
|
|
394
|
+
cs_data, _ = widget.get_data_values()
|
|
395
|
+
data["control_surfaces"].append(cs_data)
|
|
396
|
+
|
|
397
|
+
data["cabins"] = []
|
|
398
|
+
if not hasattr(wing, "cabins"):
|
|
399
|
+
wing.cabins = RCAIDE.Library.Components.Fuselages.Cabins.Cabin.Container()
|
|
400
|
+
for widget in self.cabin_widgets:
|
|
401
|
+
cabin_data, cabin_object = widget.get_data_values()
|
|
402
|
+
data["cabins"].append(cabin_data)
|
|
403
|
+
wing.cabins.append_cabin(cabin_object)
|
|
404
|
+
|
|
405
|
+
data["side_cabins"] = []
|
|
406
|
+
if not hasattr(wing, "side_cabins"):
|
|
407
|
+
wing.side_cabins = RCAIDE.Library.Components.Fuselages.Cabins.Side_Cabin.Container()
|
|
408
|
+
for widget in self.side_cabin_widgets:
|
|
409
|
+
cabin_data, cabin_object = widget.get_data_values()
|
|
410
|
+
data["side_cabins"].append(cabin_data)
|
|
411
|
+
wing.side_cabins.append_side_cabin(cabin_object)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
return data, wing
|
|
415
|
+
|
|
416
|
+
def load_data(self, data, index):
|
|
417
|
+
"""Load the data into the widgets.
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
data: The data to be loaded into the widgets.py
|
|
421
|
+
index: The index of the data in the list.
|
|
422
|
+
"""
|
|
423
|
+
assert self.data_entry_widget is not None and self.name_line_edit is not None
|
|
424
|
+
self.data_entry_widget.load_data(data)
|
|
425
|
+
|
|
426
|
+
if "wing_type" in data:
|
|
427
|
+
self.wing_type_combo.setCurrentText(data["wing_type"])
|
|
428
|
+
else:
|
|
429
|
+
self.wing_type_combo.setCurrentIndex(0)
|
|
430
|
+
|
|
431
|
+
clear_layout(self.wing_sections_layout)
|
|
432
|
+
clear_layout(self.wing_cs_layout)
|
|
433
|
+
clear_layout(self.cabins_layout)
|
|
434
|
+
# The widget lists are separate from the Qt layouts; clear both so
|
|
435
|
+
# switching wings cannot carry stale sub-widgets into the next save.
|
|
436
|
+
self.cabin_widgets.clear()
|
|
437
|
+
clear_layout(self.side_cabins_layout)
|
|
438
|
+
self.side_cabin_widgets.clear()
|
|
439
|
+
|
|
440
|
+
for section in data["sections"]:
|
|
441
|
+
self.wing_sections_layout.addWidget(WingSectionWidget(
|
|
442
|
+
self.wing_sections_layout.count(), self.delete_wing_section, section))
|
|
443
|
+
|
|
444
|
+
for section in data["control_surfaces"]:
|
|
445
|
+
self.wing_cs_layout.addWidget(WingCSWidget(
|
|
446
|
+
self.wing_cs_layout.count(), self.delete_control_surface, section))
|
|
447
|
+
|
|
448
|
+
if isinstance(data.get("cabins"), list):
|
|
449
|
+
for cabin_data in data["cabins"]:
|
|
450
|
+
self.add_regular_cabin(cabin_data)
|
|
451
|
+
|
|
452
|
+
if isinstance(data.get("side_cabins"), list):
|
|
453
|
+
for cabin_data in data["side_cabins"]:
|
|
454
|
+
self.add_side_cabin(cabin_data)
|
|
455
|
+
|
|
456
|
+
self.name_line_edit.setText(data["name"])
|
|
457
|
+
self.index = index
|