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 @@
1
+ # RCAIDE_GUI/tabs/geometry/frames/energy_network/turbofan_network/frames/__init__.py
@@ -0,0 +1 @@
1
+ # RCAIDE_GUI/tabs/geometry/frames/energy_network/turbofan_network/widgets/__init__.py
@@ -0,0 +1,5 @@
1
+ # RCAIDE_GUI/tabs/geometry/frames/fuselages/__init__.py
2
+ # ----------------------------------------------------------------------------------------------------------------------
3
+ # IMPORT
4
+ # ----------------------------------------------------------------------------------------------------------------------
5
+ from tabs.geometry.frames.fuselages.fuselage_frame import FuselageFrame
@@ -0,0 +1,294 @@
1
+ # RCAIDE_GUI/tabs/geometry/frames/fuselages/fuselage_frame.py
2
+ #
3
+ # Created: Dec 2025, M. Clarke
4
+
5
+ # ----------------------------------------------------------------------------------------------------------------------
6
+ # IMPORT
7
+ # ----------------------------------------------------------------------------------------------------------------------
8
+ import RCAIDE
9
+ from PyQt6.QtWidgets import QWidget, QLabel, QVBoxLayout, QPushButton, QLineEdit, QHBoxLayout, \
10
+ QSpacerItem, QSizePolicy, QScrollArea
11
+
12
+ from tabs.geometry.frames import GeometryFrame
13
+ from tabs.geometry.widgets import FuselageSectionWidget, CabinWidget, CabinClassWidget
14
+
15
+ from utilities import show_popup, create_line_bar, set_data, Units, create_scroll_area, clear_layout
16
+ from common_widgets import DataEntryWidget
17
+
18
+
19
+ class FuselageFrame(GeometryFrame):
20
+ # List of data labels
21
+ data_units_labels = [
22
+ ("Fineness Nose", Units.Unitless, "fineness.nose"),
23
+ ("Fineness Tail", Units.Unitless, "fineness.tail"),
24
+ ("Lengths Nose", Units.Length, "lengths.nose"),
25
+ ("Lengths Tail", Units.Length, "lengths.tail"),
26
+ ("Lengths Cabin", Units.Length, "lengths.cabin"),
27
+ ("Lengths Total", Units.Length, "lengths.total"),
28
+ ("Lengths Forespace", Units.Length, "lengths.fore_space"),
29
+ ("Lengths Aftspace", Units.Length, "lengths.aft_space"),
30
+ ("Width", Units.Length, "width"),
31
+ ("Heights Maximum", Units.Length, "heights.maximum"),
32
+ ("Height at Quarter", Units.Length, "heights.at_quarter_length"),
33
+ ("Height at Three Quarters", Units.Length, "heights.at_three_quarters_length"),
34
+ ("Height at Wing Root Quarter Chord", Units.Length, "heights.at_wing_root_quarter_chord"),
35
+ ("Areas Side Projected", Units.Area, "areas.side_projected"),
36
+ ("Area Wetted", Units.Area, "areas.wetted"),
37
+ ("Area Front Projected", Units.Area, "areas.front_projected"),
38
+ ("Differential Pressure", Units.Pressure, "differential_pressure"),
39
+ ("Effective Diameter", Units.Length, "effective_diameter"),
40
+ ]
41
+
42
+ def __init__(self):
43
+ """Create a frame for entering fuselage data."""
44
+ super(FuselageFrame, self).__init__()
45
+ self.data_entry_widget: DataEntryWidget | None = None
46
+
47
+ create_scroll_area(self)
48
+
49
+ assert self.main_layout is not None
50
+ self.main_layout.addWidget(QLabel("<b>Fuselages</b>"))
51
+ self.main_layout.addWidget(create_line_bar())
52
+ self.index = -1
53
+
54
+ self.add_name_layout()
55
+
56
+ # Add the data entry widget to the main layout
57
+ self.data_entry_widget = DataEntryWidget(self.data_units_labels)
58
+ self.wire_auto_save(self.data_entry_widget)
59
+ self.main_layout.addWidget(self.data_entry_widget)
60
+ self.main_layout.addWidget(create_line_bar())
61
+
62
+ # Add the sections layout to the main layout
63
+ self.fuselage_sections_layout = QVBoxLayout()
64
+ self.main_layout.addLayout(self.fuselage_sections_layout)
65
+
66
+ self.main_layout.addWidget(QLabel("<b>Cabins</b>"))
67
+ self.cabins_layout = QVBoxLayout()
68
+ self.main_layout.addLayout(self.cabins_layout)
69
+
70
+ self.add_buttons_layout()
71
+
72
+ # Adds scroll function
73
+ self.main_layout.addItem(QSpacerItem(
74
+ 20, 40, QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.Expanding))
75
+
76
+ def add_name_layout(self):
77
+ name_layout = QHBoxLayout()
78
+ spacer_left = QSpacerItem(
79
+ 50, 5, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
80
+ spacer_right = QSpacerItem(
81
+ 200, 5, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
82
+ self.name_line_edit = QLineEdit(self)
83
+ name_layout.addItem(spacer_left)
84
+ name_layout.addWidget(QLabel("Name: "))
85
+ name_layout.addWidget(self.name_line_edit)
86
+ name_layout.addItem(spacer_right)
87
+
88
+ assert self.main_layout is not None
89
+ self.main_layout.addLayout(name_layout)
90
+
91
+ # noinspection PyUnresolvedReferences
92
+ def add_buttons_layout(self):
93
+ """Add the save, delete, and new buttons to the layout."""
94
+ new_section_button = QPushButton("New Fuselage Section", self)
95
+ new_section_button.setStyleSheet("color:#dbe7ff; font-weight:500; margin:0; padding:0;")
96
+ save_button = QPushButton("Save Data", self)
97
+ save_button.setStyleSheet("color:#dbe7ff; font-weight:500; margin:0; padding:0;")
98
+ delete_button = QPushButton("Delete Data", self)
99
+ delete_button.setStyleSheet("color:#dbe7ff; font-weight:500; margin:0; padding:0;")
100
+ new_button = QPushButton("New Fuselage Structure", self)
101
+ new_button.setStyleSheet("color:#dbe7ff; font-weight:500; margin:0; padding:0;")
102
+
103
+ new_section_button.clicked.connect(self.add_fuselage_section)
104
+ save_button.clicked.connect(self.save_data)
105
+ delete_button.clicked.connect(self.delete_data)
106
+ new_button.clicked.connect(self.create_new_structure)
107
+
108
+ new_cabin_button = QPushButton("Add Cabin", self)
109
+ new_cabin_button.setStyleSheet("color:#dbe7ff; font-weight:500; margin:0; padding:0;")
110
+ new_cabin_button.clicked.connect(self.add_cabin)
111
+
112
+ buttons_layout = QHBoxLayout()
113
+ buttons_layout.addWidget(new_section_button)
114
+ buttons_layout.addWidget(new_cabin_button)
115
+ buttons_layout.addWidget(save_button)
116
+ buttons_layout.addWidget(delete_button)
117
+ buttons_layout.addWidget(new_button)
118
+
119
+ assert self.main_layout is not None
120
+ self.main_layout.addLayout(buttons_layout)
121
+
122
+ # noinspection DuplicatedCode
123
+ def save_data(self):
124
+ """Call the save function and pass the entered data to it."""
125
+ entered_data, fuselage = self.get_data_values()
126
+ if self.save_function:
127
+ if self.index >= 0:
128
+ # Update the live RCAIDE fuselage, including cabins.
129
+ self.index = self.save_function(
130
+ tab_index=self.tab_index, index=self.index,
131
+ vehicle_component=fuselage, data=entered_data, persist=True)
132
+ return
133
+ else:
134
+ self.index = self.save_function(
135
+ tab_index=self.tab_index, vehicle_component=fuselage, data=entered_data, new=True, persist=True)
136
+
137
+ show_popup("Data Saved!", self)
138
+ else:
139
+ print("No save function set.")
140
+
141
+ def add_fuselage_section(self):
142
+ self.fuselage_sections_layout.addWidget(FuselageSectionWidget(
143
+ self.fuselage_sections_layout.count(), self.delete_fuselage_section))
144
+
145
+ def delete_fuselage_section(self, index):
146
+ item = self.fuselage_sections_layout.itemAt(index)
147
+ assert item is not None
148
+ widget = item.widget()
149
+ assert widget is not None
150
+
151
+ widget.deleteLater()
152
+ self.fuselage_sections_layout.removeWidget(widget)
153
+ self.fuselage_sections_layout.update()
154
+
155
+ for i in range(index, self.fuselage_sections_layout.count()):
156
+ item = self.fuselage_sections_layout.itemAt(i)
157
+ if item is None:
158
+ continue
159
+
160
+ widget = item.widget()
161
+ if widget is not None and isinstance(widget, FuselageSectionWidget):
162
+ widget.index = i
163
+
164
+ # TODO: Implement proper deletion of data
165
+ def delete_data(self):
166
+ pass
167
+
168
+ def create_new_structure(self):
169
+ """Create a new fuselage structure."""
170
+ # Clear the main data values
171
+ assert self.data_entry_widget is not None and self.name_line_edit is not None
172
+ self.data_entry_widget.clear_values()
173
+ self.name_line_edit.clear()
174
+ clear_layout(self.cabins_layout)
175
+ clear_layout(self.fuselage_sections_layout)
176
+ self.index = -1
177
+
178
+ def create_rcaide_structure(self):
179
+ assert self.data_entry_widget is not None
180
+ data = self.data_entry_widget.get_values_si()
181
+ fuselage = RCAIDE.Library.Components.Fuselages.Fuselage()
182
+ fuselage.tag = self.name_line_edit.text()
183
+ for data_unit_label in self.data_units_labels:
184
+ rcaide_label = data_unit_label[-1]
185
+ user_label = data_unit_label[0]
186
+ set_data(fuselage, rcaide_label, data[user_label][0])
187
+
188
+ # Infer missing cabin length for LOPA.
189
+ if fuselage.lengths.cabin <= 0 and fuselage.lengths.total > fuselage.lengths.nose + fuselage.lengths.tail:
190
+ fuselage.lengths.cabin = fuselage.lengths.total - fuselage.lengths.nose - fuselage.lengths.tail
191
+
192
+ for i in range(self.fuselage_sections_layout.count()):
193
+ item = self.fuselage_sections_layout.itemAt(i)
194
+ if item is None:
195
+ continue
196
+
197
+ fuselage_section = item.widget()
198
+ if fuselage_section is not None and isinstance(fuselage_section, FuselageSectionWidget):
199
+ _, segment = fuselage_section.get_data_values()
200
+ fuselage.segments.append(segment)
201
+
202
+ return fuselage
203
+
204
+ def add_cabin(self):
205
+ self.cabins_layout.addWidget(CabinWidget(
206
+ self.cabins_layout.count(), self.delete_cabin, cabin_type="Regular"))
207
+
208
+ def delete_cabin(self, index):
209
+ item = self.cabins_layout.itemAt(index)
210
+ assert item is not None
211
+ widget = item.widget()
212
+ assert widget is not None
213
+
214
+ widget.deleteLater()
215
+ self.cabins_layout.removeWidget(widget)
216
+ self.cabins_layout.update()
217
+
218
+ for i in range(index, self.cabins_layout.count()):
219
+ item = self.cabins_layout.itemAt(i)
220
+ if item is None:
221
+ continue
222
+
223
+ widget = item.widget()
224
+ if widget is not None and isinstance(widget, CabinWidget):
225
+ widget.index = i
226
+
227
+ def get_data_values(self):
228
+ """Retrieve the entered data values from the text fields."""
229
+ assert self.data_entry_widget is not None
230
+ data = self.data_entry_widget.get_values()
231
+ fuselage = self.create_rcaide_structure()
232
+
233
+ data["segments"] = []
234
+ for i in range(self.fuselage_sections_layout.count()):
235
+ item = self.fuselage_sections_layout.itemAt(i)
236
+ if item is None:
237
+ continue
238
+
239
+ fuselage_section = item.widget()
240
+ if fuselage_section is not None and isinstance(fuselage_section, FuselageSectionWidget):
241
+ # Segment object was already appended.
242
+ segment_data, _ = fuselage_section.get_data_values()
243
+ data["segments"].append(segment_data)
244
+
245
+ assert self.name_line_edit is not None
246
+ data["name"] = self.name_line_edit.text()
247
+ if fuselage.lengths.cabin > 0:
248
+ data["Lengths Cabin"] = [fuselage.lengths.cabin, 0]
249
+
250
+ data["cabins"] = []
251
+ if not hasattr(fuselage, "cabins"):
252
+ fuselage.cabins = RCAIDE.Library.Components.Fuselages.Cabins.Cabin.Container()
253
+ for i in range(self.cabins_layout.count()):
254
+ item = self.cabins_layout.itemAt(i)
255
+ if item is None:
256
+ continue
257
+
258
+ cabin_widget = item.widget()
259
+ if cabin_widget is not None and isinstance(cabin_widget, CabinWidget):
260
+ cabin_data, cabin = cabin_widget.get_data_values()
261
+ data["cabins"].append(cabin_data)
262
+ # Support both RCAIDE container APIs.
263
+ if hasattr(fuselage.cabins, "append_cabin"):
264
+ fuselage.cabins.append_cabin(cabin)
265
+ else:
266
+ fuselage.cabins.append(cabin)
267
+ return data, fuselage
268
+
269
+ def load_data(self, data, index):
270
+ """Load the data into the widgets.
271
+
272
+ Args:
273
+ data: The data to be loaded into the widgets.
274
+ index: The index of the data in the list.
275
+ """
276
+ assert self.data_entry_widget is not None
277
+ self.data_entry_widget.load_data(data)
278
+
279
+ clear_layout(self.fuselage_sections_layout)
280
+
281
+ # New files use RCAIDE's "segments"; older GUI data may still say "sections".
282
+ for section in data.get("segments", data.get("sections", [])):
283
+ self.fuselage_sections_layout.addWidget(FuselageSectionWidget(
284
+ self.fuselage_sections_layout.count(), self.delete_fuselage_section, section))
285
+
286
+ clear_layout(self.cabins_layout)
287
+ if "cabins" in data:
288
+ for cabin in data["cabins"]:
289
+ self.cabins_layout.addWidget(CabinWidget(
290
+ self.cabins_layout.count(), self.delete_cabin, cabin))
291
+
292
+ assert self.name_line_edit is not None
293
+ self.name_line_edit.setText(data["name"])
294
+ self.index = index
@@ -0,0 +1,91 @@
1
+ # RCAIDE_GUI/tabs/geometry/frames/geometry_frame.py
2
+ #
3
+ # Created: Dec 2025, M. Clarke
4
+
5
+ # ----------------------------------------------------------------------------------------------------------------------
6
+ # IMPORT
7
+ # ----------------------------------------------------------------------------------------------------------------------
8
+ from PyQt6.QtWidgets import QVBoxLayout, QLineEdit, QWidget
9
+
10
+ import RCAIDE
11
+
12
+
13
+ class GeometryFrame(QWidget):
14
+ def __init__(self):
15
+ super().__init__()
16
+ self.tab_index = -1
17
+ self.index = -1
18
+ self.save_function = None
19
+ self.main_layout: QVBoxLayout | None = None
20
+ self.name_line_edit: QLineEdit = QLineEdit()
21
+
22
+ def load_data(self, data, index):
23
+ """Load the given data into the frame.
24
+
25
+ Args:
26
+ data: The data to load.
27
+ index: The index of the data."""
28
+ pass
29
+
30
+ def set_save_function(self, function):
31
+ """Set the save function to be called when the save button is pressed.
32
+
33
+ Args:
34
+ function: The function to be called.
35
+ """
36
+ self.save_function = function
37
+
38
+ def set_tab_index(self, tab_index):
39
+ """Set the tab index for the frame.
40
+
41
+ Args:
42
+ tab_index: The index of the tab."""
43
+ self.tab_index = tab_index
44
+
45
+ def create_new_structure(self):
46
+ """Create a new structure for the frame: clear all fields and set the index to -1."""
47
+ pass
48
+
49
+ def create_rcaide_structure(self):
50
+ """Create an RCAIDE structure from the given data and return it.
51
+
52
+ Args:
53
+ data: The data to create the structure from.
54
+ """
55
+ return RCAIDE.Library.Components.Component()
56
+
57
+ def get_data_values(self):
58
+ """Get the data values from the frame's widgets and return it."""
59
+ return {}, RCAIDE.Library.Components.Component()
60
+
61
+ def save_data(self):
62
+ """Save the data from the frame into geometry.py, which subsequently saves it in values."""
63
+ pass
64
+
65
+ def update_layout(self):
66
+ """Update the layout of the frame. Called when the frame is shown."""
67
+ pass
68
+
69
+ def wire_auto_save(self, data_entry_widget):
70
+ """Connect data_entry_widget.data_changed to a silent save that refreshes the preview.
71
+
72
+ Call this once per DataEntryWidget after it is constructed in a subclass __init__.
73
+ The auto-save skips the user-facing popup and only fires for already-saved components
74
+ (index >= 0), so it never accidentally creates new tree entries.
75
+ """
76
+ data_entry_widget.data_changed.connect(self._on_auto_save)
77
+
78
+ def _on_auto_save(self):
79
+ """Silently persist field changes and trigger a preview refresh."""
80
+ if self.save_function is None:
81
+ return
82
+ # tab_index 0 is the vehicle (always present); others require an existing component.
83
+ if self.tab_index != 0 and self.index < 0:
84
+ return
85
+ try:
86
+ result = self.get_data_values()
87
+ data = result[0] if isinstance(result, tuple) else result
88
+ except Exception:
89
+ return
90
+ self.save_function(tab_index=self.tab_index, index=self.index, data=data)
91
+
@@ -0,0 +1,5 @@
1
+ # RCAIDE_GUI/tabs/geometry/frames/landing_gears/__init__.py
2
+ # ----------------------------------------------------------------------------------------------------------------------
3
+ # IMPORT
4
+ # ----------------------------------------------------------------------------------------------------------------------
5
+ from tabs.geometry.frames.landing_gears.landing_gear_frame import LandingGearFrame
@@ -0,0 +1,189 @@
1
+ # RCAIDE_GUI/tabs/geometry/frames/landing_gears/landing_gear_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 utilities import set_data, show_popup, create_line_bar, Units, create_scroll_area, clear_layout
19
+ from common_widgets import DataEntryWidget
20
+
21
+ # ----------------------------------------------------------------------------------------------------------------------
22
+ # Landing Gear Frame
23
+ # ----------------------------------------------------------------------------------------------------------------------
24
+ class LandingGearFrame(GeometryFrame):
25
+
26
+ # List of data labels
27
+ data_units_labels = [
28
+ ("Tire Diameter", Units.Length, "tire_diameter"),
29
+ ("Rim Diameter", Units.Length, "rim_diameter"),
30
+ ("Tire Width", Units.Length, "tire_width"),
31
+ ("Strut Length", Units.Length, "strut_length"),
32
+ ("Origin", Units.Position, "origin"),
33
+ ("Wheels", Units.Count, "wheels"),
34
+ ("Number of Gear Types in Tandem", Units.Count, "number_of_gear_types_in_tandem"),
35
+ ("Number of Wheels in Gear Type", Units.Count, "number_of_wheels_in_gear_type"),
36
+ ("X-Z Plane Symmetric", Units.Boolean, "xz_plane_symmetric"),
37
+ ]
38
+
39
+ def __init__(self):
40
+ """Create a frame for entering landing gear data."""
41
+ super(LandingGearFrame, self).__init__()
42
+ self.data_entry_widget: DataEntryWidget | None = None
43
+
44
+ create_scroll_area(self)
45
+
46
+ assert self.main_layout is not None
47
+ self.main_layout.addWidget(QLabel("<b>Landing Gears</b>"))
48
+ self.main_layout.addWidget(create_line_bar())
49
+
50
+ self.add_name_layout()
51
+ self.add_landing_gear_type()
52
+
53
+ # Add the data entry widget to the home layout
54
+ self.data_entry_widget = DataEntryWidget(self.data_units_labels)
55
+ self.wire_auto_save(self.data_entry_widget)
56
+ self.main_layout.addWidget(self.data_entry_widget)
57
+ self.main_layout.addWidget(create_line_bar())
58
+
59
+ self.add_buttons_layout()
60
+
61
+ # Adds scroll function
62
+ self.main_layout.addItem(QSpacerItem(
63
+ 20, 40, QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.Expanding))
64
+
65
+ def add_name_layout(self):
66
+ name_layout = QHBoxLayout()
67
+ spacer_left = QSpacerItem(
68
+ 50, 5, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
69
+ spacer_right = QSpacerItem(
70
+ 200, 5, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
71
+ self.name_line_edit = QLineEdit(self)
72
+ name_layout.addItem(spacer_left)
73
+ name_layout.addWidget(QLabel("Name: "))
74
+ name_layout.addWidget(self.name_line_edit)
75
+ name_layout.addItem(spacer_right)
76
+
77
+ assert self.main_layout is not None
78
+ self.main_layout.addLayout(name_layout)
79
+
80
+ def add_landing_gear_type(self):
81
+ landing_gear_type_label = QLabel("Landing Gear Type: ")
82
+ gear_type_layout = QHBoxLayout()
83
+ spacer_left = QSpacerItem(
84
+ 50, 5, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
85
+ spacer_right = QSpacerItem(
86
+ 200, 5, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
87
+ gear_type_layout.addItem(spacer_left)
88
+ gear_type_layout.addWidget(landing_gear_type_label)
89
+ self.landing_gear_type_combo = QComboBox()
90
+ self.landing_gear_type_combo.addItems(["General Gear","Nose Gear", "Main Gear"])
91
+ self.landing_gear_type_combo.setFixedWidth(600)
92
+ gear_type_layout.addWidget(self.landing_gear_type_combo, alignment=Qt.AlignmentFlag.AlignCenter)
93
+ gear_type_layout.addItem(spacer_right)
94
+
95
+ assert self.main_layout is not None
96
+ self.main_layout.addLayout(gear_type_layout)
97
+
98
+ # noinspection PyUnresolvedReferences
99
+ def add_buttons_layout(self):
100
+ """Add the save, delete, and new buttons to the layout."""
101
+ save_button = QPushButton("Save Data", self)
102
+ save_button.setStyleSheet("color:#dbe7ff; font-weight:500; margin:0; padding:0;")
103
+ delete_button = QPushButton("Delete Data", self)
104
+ delete_button.setStyleSheet("color:#dbe7ff; font-weight:500; margin:0; padding:0;")
105
+ new_button = QPushButton("New Landing Gear Structure", self)
106
+ new_button.setStyleSheet("color:#dbe7ff; font-weight:500; margin:0; padding:0;")
107
+
108
+ save_button.clicked.connect(self.save_data)
109
+ delete_button.clicked.connect(self.delete_data)
110
+ new_button.clicked.connect(self.create_new_structure)
111
+
112
+ buttons_layout = QHBoxLayout()
113
+ buttons_layout.addWidget(save_button)
114
+ buttons_layout.addWidget(delete_button)
115
+ buttons_layout.addWidget(new_button)
116
+
117
+ assert self.main_layout is not None
118
+ self.main_layout.addLayout(buttons_layout)
119
+
120
+ # noinspection DuplicatedCode
121
+ def save_data(self):
122
+ """Call the save function and pass the entered data to it."""
123
+ entered_data, landing_gear = self.get_data_values()
124
+ if self.save_function:
125
+ if self.index >= 0:
126
+ self.index = self.save_function(
127
+ tab_index=self.tab_index, index=self.index, data=entered_data, persist=True)
128
+ return
129
+ else:
130
+ self.index = self.save_function(
131
+ tab_index=self.tab_index, vehicle_component=landing_gear, data=entered_data, new=True, persist=True)
132
+
133
+ show_popup("Data Saved!", self)
134
+
135
+ # TODO: Implement proper deletion of data
136
+ def delete_data(self):
137
+ pass
138
+
139
+ def create_new_structure(self):
140
+ """Create a new Landing Gear structure."""
141
+ # Clear the main data values
142
+ assert self.data_entry_widget is not None and self.name_line_edit is not None
143
+ self.data_entry_widget.clear_values()
144
+ self.name_line_edit.clear()
145
+ self.index = -1
146
+
147
+ def create_rcaide_structure(self):
148
+ data = self.data_entry_widget.get_values_si()
149
+ data["name"] = self.name_line_edit.text()
150
+ selected_landing_gear_type = self.landing_gear_type_combo.currentText()
151
+ if selected_landing_gear_type == "General Gear":
152
+ landing_gear = RCAIDE.Library.Components.Landing_Gear.Landing_Gear()
153
+ elif selected_landing_gear_type == "Nose Gear":
154
+ landing_gear = RCAIDE.Library.Components.Landing_Gear.Nose_Landing_Gear()
155
+ elif selected_landing_gear_type == "Main Gear":
156
+ landing_gear = RCAIDE.Library.Components.Landing_Gear.Main_Landing_Gear()
157
+ landing_gear.tag = data["name"]
158
+ for data_unit_label in self.data_units_labels:
159
+ rcaide_label = data_unit_label[-1]
160
+ user_label = data_unit_label[0]
161
+ set_data(landing_gear, rcaide_label, data[user_label][0])
162
+ return landing_gear
163
+
164
+ def get_data_values(self):
165
+ """Retrieve the entered data values from the text fields."""
166
+ assert self.data_entry_widget is not None and self.name_line_edit is not None
167
+ data = self.data_entry_widget.get_values()
168
+ data["name"] = self.name_line_edit.text()
169
+ data["landing_gear_type"] = self.landing_gear_type_combo.currentText()
170
+ landing_gear = self.create_rcaide_structure()
171
+ return data, landing_gear
172
+
173
+ def load_data(self, data, index):
174
+ """Load the data into the widgets.
175
+
176
+ Args:
177
+ data: The data to be loaded into the widgets.
178
+ index: The index of the data in the list.
179
+ """
180
+ assert self.data_entry_widget is not None and self.name_line_edit is not None
181
+ self.data_entry_widget.load_data(data)
182
+
183
+ if "landing_gear_type" in data:
184
+ self.landing_gear_type_combo.setCurrentText(data["landing_gear_type"])
185
+ else:
186
+ self.landing_gear_type_combo.setCurrentIndex(0)
187
+
188
+ self.name_line_edit.setText(data["name"])
189
+ self.index = index
@@ -0,0 +1 @@
1
+ # RCAIDE_GUI/tabs/geometry/frames/nacelles/__init__.py
@@ -0,0 +1,6 @@
1
+ # RCAIDE_GUI/tabs/geometry/frames/powertrain/__init__.py
2
+ # ----------------------------------------------------------------------------------------------------------------------
3
+ # IMPORT
4
+ # ----------------------------------------------------------------------------------------------------------------------
5
+
6
+ from tabs.geometry.frames.powertrain.powertrain_frame import PowertrainFrame
@@ -0,0 +1,5 @@
1
+ # RCAIDE_GUI/tabs/geometry/frames/powertrain/converters/__init__.py
2
+ # ----------------------------------------------------------------------------------------------------------------------
3
+ # IMPORT
4
+ # ----------------------------------------------------------------------------------------------------------------------
5
+ from tabs.geometry.frames.powertrain.converters.converter_frame import ConverterFrame