RCAIDE-GUI 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. common_widgets/__init__.py +6 -0
  2. common_widgets/animated_toggle.py +141 -0
  3. common_widgets/color.py +17 -0
  4. common_widgets/data_entry_widget.py +337 -0
  5. common_widgets/image_widget.py +21 -0
  6. common_widgets/unit_picker_widget.py +48 -0
  7. main.py +168 -0
  8. rcaide_gui-1.0.0.dist-info/METADATA +96 -0
  9. rcaide_gui-1.0.0.dist-info/RECORD +170 -0
  10. rcaide_gui-1.0.0.dist-info/WHEEL +5 -0
  11. rcaide_gui-1.0.0.dist-info/entry_points.txt +2 -0
  12. rcaide_gui-1.0.0.dist-info/top_level.txt +5 -0
  13. rcaide_io.py +818 -0
  14. tabs/__init__.py +10 -0
  15. tabs/aircraft_configs/__init__.py +3 -0
  16. tabs/aircraft_configs/aircraft_configs.py +301 -0
  17. tabs/analysis/__init__.py +4 -0
  18. tabs/analysis/analysis.py +92 -0
  19. tabs/analysis/widgets/__init__.py +13 -0
  20. tabs/analysis/widgets/aeroacoustics_widget.py +158 -0
  21. tabs/analysis/widgets/aerodynamics_widget.py +108 -0
  22. tabs/analysis/widgets/analysis_data_widget.py +29 -0
  23. tabs/analysis/widgets/atmosphere_widget.py +49 -0
  24. tabs/analysis/widgets/costs_widget.py +35 -0
  25. tabs/analysis/widgets/energy_widget.py +32 -0
  26. tabs/analysis/widgets/geometry_widget.py +55 -0
  27. tabs/analysis/widgets/planets_widget.py +34 -0
  28. tabs/analysis/widgets/propulsion_widget.py +53 -0
  29. tabs/analysis/widgets/stability_widget.py +34 -0
  30. tabs/analysis/widgets/weights_widget.py +94 -0
  31. tabs/geometry/__init__.py +6 -0
  32. tabs/geometry/aircraft_configs.py +232 -0
  33. tabs/geometry/frames/__init__.py +15 -0
  34. tabs/geometry/frames/booms/__init__.py +5 -0
  35. tabs/geometry/frames/booms/boom_frame.py +238 -0
  36. tabs/geometry/frames/cargo_bays/__init__.py +5 -0
  37. tabs/geometry/frames/cargo_bays/cargo_bay_frame.py +159 -0
  38. tabs/geometry/frames/default_frame.py +23 -0
  39. tabs/geometry/frames/energy_network/__init__.py +2 -0
  40. tabs/geometry/frames/energy_network/turbofan_network/__init__.py +3 -0
  41. tabs/geometry/frames/energy_network/turbofan_network/frames/__init__.py +1 -0
  42. tabs/geometry/frames/energy_network/turbofan_network/widgets/__init__.py +1 -0
  43. tabs/geometry/frames/fuselages/__init__.py +5 -0
  44. tabs/geometry/frames/fuselages/fuselage_frame.py +294 -0
  45. tabs/geometry/frames/geometry_frame.py +91 -0
  46. tabs/geometry/frames/landing_gears/__init__.py +5 -0
  47. tabs/geometry/frames/landing_gears/landing_gear_frame.py +189 -0
  48. tabs/geometry/frames/nacelles/__init__.py +1 -0
  49. tabs/geometry/frames/powertrain/__init__.py +6 -0
  50. tabs/geometry/frames/powertrain/converters/__init__.py +5 -0
  51. tabs/geometry/frames/powertrain/converters/converter_frame.py +137 -0
  52. tabs/geometry/frames/powertrain/distributors/__init__.py +5 -0
  53. tabs/geometry/frames/powertrain/distributors/distributor_frame.py +143 -0
  54. tabs/geometry/frames/powertrain/nacelles/__init__.py +5 -0
  55. tabs/geometry/frames/powertrain/nacelles/nacelle_frame.py +283 -0
  56. tabs/geometry/frames/powertrain/powertrain_frame.py +259 -0
  57. tabs/geometry/frames/powertrain/propulsors/__init__.py +5 -0
  58. tabs/geometry/frames/powertrain/propulsors/propulsor_frame.py +146 -0
  59. tabs/geometry/frames/powertrain/sources/__init__.py +6 -0
  60. tabs/geometry/frames/powertrain/sources/energy_source_frame.py +147 -0
  61. tabs/geometry/frames/vehicle_frame.py +171 -0
  62. tabs/geometry/frames/wings/__init__.py +5 -0
  63. tabs/geometry/frames/wings/wings_frame.py +457 -0
  64. tabs/geometry/geometry.py +513 -0
  65. tabs/geometry/widgets/__init__.py +12 -0
  66. tabs/geometry/widgets/booms/__init__.py +6 -0
  67. tabs/geometry/widgets/booms/boom_section_widget.py +109 -0
  68. tabs/geometry/widgets/cargo_bays/__init__.py +1 -0
  69. tabs/geometry/widgets/fuselages/__init__.py +8 -0
  70. tabs/geometry/widgets/fuselages/cabin_class_widget.py +188 -0
  71. tabs/geometry/widgets/fuselages/cabin_widget.py +217 -0
  72. tabs/geometry/widgets/fuselages/fuselage_section_widget.py +190 -0
  73. tabs/geometry/widgets/geometry_data_widget.py +25 -0
  74. tabs/geometry/widgets/landing_gears/__init__.py +6 -0
  75. tabs/geometry/widgets/nacelles/__init__.py +1 -0
  76. tabs/geometry/widgets/powertrain/__init__.py +9 -0
  77. tabs/geometry/widgets/powertrain/converters/__init__.py +5 -0
  78. tabs/geometry/widgets/powertrain/converters/turboelectric_generator_widget.py +74 -0
  79. tabs/geometry/widgets/powertrain/distributors/__init__.py +5 -0
  80. tabs/geometry/widgets/powertrain/distributors/fuel_line_widget.py +81 -0
  81. tabs/geometry/widgets/powertrain/modulators/__init__.py +5 -0
  82. tabs/geometry/widgets/powertrain/modulators/esc_widget.py +58 -0
  83. tabs/geometry/widgets/powertrain/nacelles/__init__.py +5 -0
  84. tabs/geometry/widgets/powertrain/nacelles/nacelle_section_widget.py +115 -0
  85. tabs/geometry/widgets/powertrain/powertrain_connector_widget.py +136 -0
  86. tabs/geometry/widgets/powertrain/powertrain_widget.py +206 -0
  87. tabs/geometry/widgets/powertrain/propulsors/__init__.py +5 -0
  88. tabs/geometry/widgets/powertrain/propulsors/turbofan_widget.py +462 -0
  89. tabs/geometry/widgets/powertrain/sources/__init__.py +6 -0
  90. tabs/geometry/widgets/powertrain/sources/fuel_tank_widget.py +103 -0
  91. tabs/geometry/widgets/powertrain/sources/source_selector_widget.py +35 -0
  92. tabs/geometry/widgets/wings/__init__.py +7 -0
  93. tabs/geometry/widgets/wings/wing_cs_widget.py +198 -0
  94. tabs/geometry/widgets/wings/wing_section_widget.py +298 -0
  95. tabs/home/__init__.py +3 -0
  96. tabs/home/home.py +585 -0
  97. tabs/mission/__init__.py +4 -0
  98. tabs/mission/mission.py +1124 -0
  99. tabs/mission/widgets/__init__.py +3 -0
  100. tabs/mission/widgets/flight_controls_widget.py +186 -0
  101. tabs/mission/widgets/mission_analysis_widget.py +166 -0
  102. tabs/mission/widgets/mission_segment_helper.py +358 -0
  103. tabs/mission/widgets/mission_segment_widget.py +358 -0
  104. tabs/solve/__init__.py +4 -0
  105. tabs/solve/plots/__init__.py +12 -0
  106. tabs/solve/plots/aeroacoustics/__init__.py +6 -0
  107. tabs/solve/plots/aeroacoustics/plot_2D_noise_contour.py +184 -0
  108. tabs/solve/plots/aeroacoustics/plot_3D_noise_contour.py +219 -0
  109. tabs/solve/plots/aeroacoustics/plot_noise_certification_contour.py +115 -0
  110. tabs/solve/plots/aeroacoustics/plot_noise_level.py +118 -0
  111. tabs/solve/plots/aerodynamics/__init__.py +10 -0
  112. tabs/solve/plots/aerodynamics/plot_aerodynamic_coefficients.py +156 -0
  113. tabs/solve/plots/aerodynamics/plot_aerodynamic_forces.py +155 -0
  114. tabs/solve/plots/aerodynamics/plot_aircraft_aerodynamics.py +136 -0
  115. tabs/solve/plots/aerodynamics/plot_drag_components.py +157 -0
  116. tabs/solve/plots/aerodynamics/plot_lift_distribution.py +107 -0
  117. tabs/solve/plots/aerodynamics/plot_rotor_conditions.py +255 -0
  118. tabs/solve/plots/aerodynamics/plot_rotor_disc_performance.py +144 -0
  119. tabs/solve/plots/aerodynamics/plot_rotor_performance.py +124 -0
  120. tabs/solve/plots/common/__init__.py +4 -0
  121. tabs/solve/plots/common/plot_style.py +71 -0
  122. tabs/solve/plots/common/set_axes.py +55 -0
  123. tabs/solve/plots/create_plot_widgets.py +34 -0
  124. tabs/solve/plots/emissions/__init__.py +3 -0
  125. tabs/solve/plots/emissions/plot_emissions.py +167 -0
  126. tabs/solve/plots/energy/__init__.py +12 -0
  127. tabs/solve/plots/energy/plot_altitude_sfc_weight.py +134 -0
  128. tabs/solve/plots/energy/plot_battery_cell_conditions.py +154 -0
  129. tabs/solve/plots/energy/plot_battery_degradation.py +157 -0
  130. tabs/solve/plots/energy/plot_battery_module_C_rates.py +139 -0
  131. tabs/solve/plots/energy/plot_battery_module_conditions.py +157 -0
  132. tabs/solve/plots/energy/plot_battery_pack_conditions.py +164 -0
  133. tabs/solve/plots/energy/plot_battery_ragone_diagram.py +97 -0
  134. tabs/solve/plots/energy/plot_battery_temperature.py +136 -0
  135. tabs/solve/plots/energy/plot_electric_propulsor_efficiencies.py +136 -0
  136. tabs/solve/plots/energy/plot_propulsor_throttles.py +119 -0
  137. tabs/solve/plots/mission/__init__.py +5 -0
  138. tabs/solve/plots/mission/plot_aircraft_velocities.py +145 -0
  139. tabs/solve/plots/mission/plot_flight_conditions.py +236 -0
  140. tabs/solve/plots/mission/plot_flight_trajectory.py +172 -0
  141. tabs/solve/plots/stability/__init__.py +5 -0
  142. tabs/solve/plots/stability/plot_flight_forces_and_moments.py +163 -0
  143. tabs/solve/plots/stability/plot_lateral_stability.py +151 -0
  144. tabs/solve/plots/stability/plot_longitudinal_stability.py +173 -0
  145. tabs/solve/plots/thermal_management/__init__.py +7 -0
  146. tabs/solve/plots/thermal_management/plot_air_cooled_conditions.py +152 -0
  147. tabs/solve/plots/thermal_management/plot_cross_flow_heat_exchanger_conditions.py +185 -0
  148. tabs/solve/plots/thermal_management/plot_reservoir_conditions.py +141 -0
  149. tabs/solve/plots/thermal_management/plot_thermal_management_performance.py +105 -0
  150. tabs/solve/plots/thermal_management/plot_wavy_channel_conditions.py +161 -0
  151. tabs/solve/plots/weights/__init__.py +4 -0
  152. tabs/solve/plots/weights/plot_load_diagram.py +163 -0
  153. tabs/solve/plots/weights/plot_weight_breakdown.py +164 -0
  154. tabs/solve/solve.py +1498 -0
  155. tabs/style_sheet.py +114 -0
  156. tabs/tab_widget.py +14 -0
  157. tabs/visualize_geometry/__init__.py +4 -0
  158. tabs/visualize_geometry/core_3d_viewer.py +253 -0
  159. tabs/visualize_geometry/features/__init__.py +10 -0
  160. tabs/visualize_geometry/features/axes_gizmo.py +173 -0
  161. tabs/visualize_geometry/features/background.py +161 -0
  162. tabs/visualize_geometry/features/blueprint.py +685 -0
  163. tabs/visualize_geometry/features/camera.py +364 -0
  164. tabs/visualize_geometry/features/drag_aircraft.py +219 -0
  165. tabs/visualize_geometry/features/grid.py +194 -0
  166. tabs/visualize_geometry/features/measurement.py +367 -0
  167. tabs/visualize_geometry/features/screenshot.py +91 -0
  168. tabs/visualize_geometry/geometry_helper_functions.py +3 -0
  169. tabs/visualize_geometry/visualize_geometry.py +731 -0
  170. utilities.py +257 -0
@@ -0,0 +1,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