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,188 @@
1
+ # RCAIDE_GUI/tabs/geometry/widgets/fuselages/cabin_class_widget.py
2
+
3
+ # Created: Dec 2025, M. Clarke
4
+ # ------------------------------------------------------------------------------
5
+ # Imports
6
+ # ------------------------------------------------------------------------------
7
+ import RCAIDE
8
+ import math
9
+ from PyQt6.QtWidgets import (QHBoxLayout, QLabel,
10
+ QLineEdit, QPushButton, QSizePolicy, QSpacerItem,
11
+ QVBoxLayout, QWidget, QFrame, QComboBox)
12
+
13
+ from utilities import Units
14
+ from common_widgets import DataEntryWidget
15
+
16
+
17
+ class CabinClassWidget(QWidget):
18
+ # Visible fields; preserve everything else.
19
+ visible_data_keys = {
20
+ "class_type",
21
+ "Number of Passengers",
22
+ "Number of Seats Abreast",
23
+ "Number of Rows",
24
+ "Number of Seats",
25
+ "Seat Width",
26
+ "Seat Arm Rest Width",
27
+ "Seat Length",
28
+ "Seat Pitch",
29
+ "Aisle Width",
30
+ "number_of_passengers",
31
+ "number_of_seats_abrest",
32
+ "number_of_seats_abreast",
33
+ "number_of_rows",
34
+ "number_of_seats",
35
+ "seat_width",
36
+ "seat_arm_rest_width",
37
+ "seat_length",
38
+ "seat_pitch",
39
+ "aisle_width",
40
+ }
41
+
42
+ def __init__(self, index, on_delete, section_data=None):
43
+ # Initializing the CabinClassWidget
44
+ super().__init__()
45
+ self.index = index
46
+ self.on_delete = on_delete
47
+ self.data_entry_widget: DataEntryWidget | None = None
48
+ self.header_layout = QHBoxLayout()
49
+ # Hidden fields restored on save.
50
+ self.preserved_data = {}
51
+ self.init_ui(section_data)
52
+
53
+ # noinspection DuplicatedCode
54
+ def init_ui(self, section_data):
55
+ main_layout = QVBoxLayout()
56
+ # add spacing, buttons and drop down to header layout
57
+ spacer_left = QSpacerItem(80, 5, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
58
+ self.header_layout.addItem(spacer_left)
59
+ self.header_layout.addWidget(QLabel("Class Type: "))
60
+ self.class_type_combo = QComboBox()
61
+ #list of types of "classes" for cabins
62
+ self.class_type_combo.addItems(["Economy", "Business", "First"])
63
+ self.header_layout.addWidget(self.class_type_combo)
64
+ self.header_layout.addItem(QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum))
65
+ #adding delete button
66
+ delete_btn = QPushButton("Delete Class")
67
+ delete_btn.setStyleSheet("color:#dbe7ff; font-weight:500; margin:0; padding:0;")
68
+ delete_btn.clicked.connect(self.delete_clicked)
69
+ self.header_layout.addWidget(delete_btn)
70
+ main_layout.addLayout(self.header_layout)
71
+
72
+ # List of data labels
73
+ self.data_units_labels = [
74
+ ("Number of Passengers", Units.Count),
75
+ ("Number of Seats Abreast", Units.Count),
76
+ ("Number of Rows", Units.Count),
77
+ ("Number of Seats", Units.Count),
78
+ ("Seat Width", Units.Length),
79
+ ("Seat Arm Rest Width", Units.Length),
80
+ ("Seat Length", Units.Length),
81
+ ("Seat Pitch", Units.Length),
82
+ ("Aisle Width", Units.Length)
83
+ ]
84
+
85
+ self.data_entry_widget = DataEntryWidget(self.data_units_labels)
86
+
87
+ #adding data entry widget (labels) to main layout
88
+ main_layout.addWidget(self.data_entry_widget)
89
+
90
+ # Add horizontal bar
91
+ line_bar = QFrame()
92
+ line_bar.setFrameShape(QFrame.Shape.HLine)
93
+ line_bar.setFrameShadow(QFrame.Shadow.Sunken)
94
+ main_layout.addWidget(line_bar)
95
+ #loading data if provided
96
+ if section_data:
97
+ self.load_data_values(section_data)
98
+
99
+ self.setLayout(main_layout)
100
+
101
+ def create_rcaide_structure(self, data):
102
+ #creating the correct class type based on user selection
103
+ class_type = data.get("class_type", self.class_type_combo.currentText())
104
+ if class_type == "Economy":
105
+ classtype = RCAIDE.Library.Components.Fuselages.Cabins.Classes.Economy()
106
+ elif class_type == "Business":
107
+ classtype = RCAIDE.Library.Components.Fuselages.Cabins.Classes.Business()
108
+ elif class_type == "First":
109
+ classtype = RCAIDE.Library.Components.Fuselages.Cabins.Classes.First()
110
+ else:
111
+ classtype = RCAIDE.Library.Components.Fuselages.Cabins.Classes.Economy()
112
+ #economy is default
113
+
114
+ #setting all values from data entry to the rcaide structure
115
+ classtype.number_of_passengers = int(data["Number of Passengers"][0])
116
+ seats_abreast = int(data["Number of Seats Abreast"][0])
117
+ number_of_rows = int(data["Number of Rows"][0])
118
+ number_of_seats = int(data["Number of Seats"][0])
119
+ # Infer missing seat/row counts for LOPA.
120
+ if number_of_seats <= 0 and seats_abreast > 0 and number_of_rows > 0:
121
+ number_of_seats = seats_abreast * number_of_rows
122
+ if number_of_rows <= 0 and seats_abreast > 0 and number_of_seats > 0:
123
+ number_of_rows = math.ceil(number_of_seats / seats_abreast)
124
+ if seats_abreast > 0 and number_of_seats > seats_abreast * number_of_rows:
125
+ number_of_rows = math.ceil(number_of_seats / seats_abreast)
126
+ # RCAIDE/LOPA reads `abrest`; keep both spellings.
127
+ classtype.number_of_seats_abrest = seats_abreast
128
+ classtype.number_of_seats_abreast = seats_abreast
129
+ classtype.number_of_rows = number_of_rows
130
+ classtype.number_of_seats = number_of_seats
131
+ classtype.seat_width = data["Seat Width"][0]
132
+ classtype.seat_arm_rest_width = data["Seat Arm Rest Width"][0]
133
+ classtype.seat_length = data["Seat Length"][0]
134
+ classtype.seat_pitch = data["Seat Pitch"][0]
135
+ classtype.aisle_width = data["Aisle Width"][0]
136
+
137
+ # Restore hidden RCAIDE fields.
138
+ for key, value in data.items():
139
+ if key not in self.visible_data_keys:
140
+ setattr(classtype, key, self._unwrap_value(value))
141
+
142
+ return classtype
143
+
144
+ @staticmethod
145
+ def _unwrap_value(value):
146
+ if (
147
+ isinstance(value, list)
148
+ and len(value) == 2
149
+ and isinstance(value[1], int)
150
+ and not isinstance(value[1], bool)
151
+ ):
152
+ return value[0]
153
+ return value
154
+
155
+ def get_data_values(self):
156
+ #getting data from data entry widget
157
+ data = self.data_entry_widget.get_values()
158
+ data_si = self.data_entry_widget.get_values_si()
159
+ selected_type = self.class_type_combo.currentText()
160
+ #setting class type in data
161
+ data_si["class_type"] = selected_type
162
+ data["class_type"] = selected_type
163
+ # Preserve hidden fields in both data forms.
164
+ data.update(self.preserved_data)
165
+ data_si.update(self.preserved_data)
166
+ classtype = self.create_rcaide_structure(data_si)
167
+ data["Number of Rows"] = [classtype.number_of_rows, 0]
168
+ data["Number of Seats"] = [classtype.number_of_seats, 0]
169
+ data_si["Number of Rows"] = [classtype.number_of_rows, 0]
170
+ data_si["Number of Seats"] = [classtype.number_of_seats, 0]
171
+ return data, classtype
172
+
173
+ def load_data_values(self, section_data):
174
+ #loading data into data entry widget and setting class type
175
+ # Save hidden fields for round-trip.
176
+ self.preserved_data = {
177
+ key: value
178
+ for key, value in section_data.items()
179
+ if key not in self.visible_data_keys
180
+ }
181
+ self.data_entry_widget.load_data(section_data)
182
+ if "class_type" in section_data:
183
+ self.class_type_combo.setCurrentText(section_data["class_type"])
184
+
185
+ def delete_clicked(self):
186
+ #calling on_delete callback when delete button is pressed
187
+ if self.on_delete:
188
+ self.on_delete(self.index)
@@ -0,0 +1,217 @@
1
+ # RCAIDE_GUI/tabs/geometry/widgets/fuselages/cabin_widget.py
2
+
3
+ # Created: Dec 2025, M. Clarke
4
+ # ------------------------------------------------------------------------------
5
+ # Imports
6
+ # ------------------------------------------------------------------------------
7
+ import RCAIDE
8
+ from PyQt6.QtWidgets import (QHBoxLayout, QLabel,
9
+ QLineEdit, QPushButton, QSizePolicy, QSpacerItem,
10
+ QVBoxLayout, QWidget, QFrame)
11
+
12
+ from utilities import Units, clear_layout
13
+ from common_widgets import DataEntryWidget
14
+ from tabs.geometry.widgets.fuselages.cabin_class_widget import CabinClassWidget
15
+
16
+
17
+ class CabinWidget(QWidget):
18
+ # Visible fields; preserve everything else.
19
+ visible_data_keys = {
20
+ "Cabin Name",
21
+ "cabin_type",
22
+ "Number of Passengers",
23
+ "Number of Seats",
24
+ "Type A Door Length",
25
+ "Galley Lavatory Length",
26
+ "Emergency Exit Seat Pitch",
27
+ "Length",
28
+ "Width",
29
+ "Height",
30
+ "Wide Body",
31
+ "classes",
32
+ "tag",
33
+ "number_of_passengers",
34
+ "number_of_seats",
35
+ "type_a_door_length",
36
+ "galley_lavatory_length",
37
+ "emergency_exit_seat_pitch",
38
+ "length",
39
+ "width",
40
+ "height",
41
+ "wide_body",
42
+ }
43
+
44
+ def __init__(self, index, on_delete, section_data=None, cabin_type="Regular"):
45
+ super(CabinWidget, self).__init__()
46
+ self.index = index
47
+ self.on_delete = on_delete
48
+ self.cabin_type = cabin_type
49
+ self.data_entry_widget: DataEntryWidget | None = None
50
+ self.name_layout = QHBoxLayout()
51
+ self.classes_layout = QVBoxLayout()
52
+ # Hidden fields restored on save.
53
+ self.preserved_data = {}
54
+ self.init_ui(section_data)
55
+
56
+ # noinspection DuplicatedCode
57
+ def init_ui(self, section_data):
58
+ main_layout = QVBoxLayout()
59
+ type_label = QLabel(f"<b>Type: {self.cabin_type} Cabin</b>")
60
+ main_layout.addWidget(type_label)
61
+ # add spacing
62
+ spacer_left = QSpacerItem(80, 5, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
63
+ spacer_right = QSpacerItem(300, 5, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
64
+ self.name_layout.addItem(spacer_left)
65
+ self.name_layout.addWidget(QLabel("Cabin Name: "))
66
+ self.name_layout.addWidget(QLineEdit(self))
67
+ self.name_layout.addItem(spacer_right)
68
+
69
+ main_layout.addLayout(self.name_layout)
70
+
71
+ # List of data labels
72
+ data_units_labels = [
73
+ ("Number of Passengers", Units.Count),
74
+ ("Number of Seats", Units.Count),
75
+ ("Type A Door Length", Units.Length),
76
+ ("Galley Lavatory Length", Units.Length),
77
+ ("Emergency Exit Seat Pitch", Units.Length),
78
+ ("Length", Units.Length),
79
+ ("Width", Units.Length),
80
+ ("Height", Units.Length),
81
+ ("Wide Body", Units.Boolean)
82
+ #potentially add the "filled seats arrangement" option later
83
+ ]
84
+
85
+ self.data_entry_widget = DataEntryWidget(data_units_labels)
86
+ main_layout.addWidget(self.data_entry_widget)
87
+
88
+ main_layout.addWidget(QLabel("<b>Cabin Classes</b>"))
89
+ add_class_button = QPushButton("Add Cabin Class")
90
+ add_class_button.setStyleSheet("color:#dbe7ff; font-weight:500; margin:0; padding:0;")
91
+ add_class_button.clicked.connect(self.add_cabin_class)
92
+ main_layout.addWidget(add_class_button)
93
+ main_layout.addLayout(self.classes_layout)
94
+
95
+ # Add horizontal bar
96
+ line_bar = QFrame()
97
+ line_bar.setFrameShape(QFrame.Shape.HLine)
98
+ line_bar.setFrameShadow(QFrame.Shadow.Sunken)
99
+ main_layout.addWidget(line_bar)
100
+
101
+ delete_button = QPushButton("Delete Cabin", self)
102
+ delete_button.setStyleSheet("color:#dbe7ff; font-weight:500; margin:0; padding:0;")
103
+ delete_button.clicked.connect(self.delete_button_pressed)
104
+ # center delete button
105
+ delete_button_layout = QHBoxLayout()
106
+ delete_button_layout.addItem(QSpacerItem(50, 5, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum))
107
+ delete_button_layout.addWidget(delete_button)
108
+ delete_button_layout.addItem(QSpacerItem(50, 5, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum))
109
+ main_layout.addLayout(delete_button_layout)
110
+
111
+ self.setLayout(main_layout)
112
+
113
+ if section_data:
114
+ self.load_data_values(section_data)
115
+
116
+
117
+
118
+ def add_cabin_class(self):
119
+ widget = CabinClassWidget(self.classes_layout.count(), self.delete_cabin_class)
120
+ self.classes_layout.addWidget(widget)
121
+
122
+ def delete_cabin_class(self, index):
123
+ item = self.classes_layout.itemAt(index)
124
+ if item:
125
+ widget = item.widget()
126
+ if widget:
127
+ widget.deleteLater()
128
+ self.classes_layout.removeWidget(widget)
129
+ self.classes_layout.update()
130
+ for i in range(self.classes_layout.count()):
131
+ item = self.classes_layout.itemAt(i)
132
+ if item and item.widget():
133
+ item.widget().index = i
134
+
135
+ def create_rcaide_structure(self, data):
136
+ if self.cabin_type == "Side":
137
+ cabin = RCAIDE.Library.Components.Fuselages.Cabins.Side_Cabin()
138
+
139
+ else:
140
+ cabin = RCAIDE.Library.Components.Fuselages.Cabins.Cabin()
141
+
142
+ cabin.tag = data["Cabin Name"]
143
+ cabin.number_of_passengers = int(data["Number of Passengers"][0])
144
+ cabin.number_of_seats = int(data["Number of Seats"][0])
145
+ cabin.type_a_door_length = data["Type A Door Length"][0]
146
+ cabin.galley_lavatory_length = data["Galley Lavatory Length"][0]
147
+ cabin.emergency_exit_seat_pitch = data["Emergency Exit Seat Pitch"][0]
148
+ cabin.length = data["Length"][0]
149
+ cabin.width = data["Width"][0]
150
+ cabin.height = data["Height"][0]
151
+ cabin.wide_body = data["Wide Body"][0]
152
+
153
+ # Restore hidden RCAIDE fields.
154
+ for key, value in data.items():
155
+ if key not in self.visible_data_keys:
156
+ setattr(cabin, key, self._unwrap_value(value))
157
+
158
+ return cabin
159
+
160
+ @staticmethod
161
+ def _unwrap_value(value):
162
+ if (
163
+ isinstance(value, list)
164
+ and len(value) == 2
165
+ and isinstance(value[1], int)
166
+ and not isinstance(value[1], bool)
167
+ ):
168
+ return value[0]
169
+ return value
170
+
171
+ def get_data_values(self):
172
+ data = self.data_entry_widget.get_values()
173
+ data_si = self.data_entry_widget.get_values_si()
174
+ data["Cabin Name"] = self.name_layout.itemAt(2).widget().text()
175
+ data["cabin_type"] = self.cabin_type
176
+ data_si["Cabin Name"] = self.name_layout.itemAt(2).widget().text()
177
+ data_si["cabin_type"] = self.cabin_type
178
+ # Preserve hidden fields in both data forms.
179
+ data.update(self.preserved_data)
180
+ data_si.update(self.preserved_data)
181
+ cabin_object = self.create_rcaide_structure(data_si)
182
+
183
+ data["classes"] = []
184
+ for i in range(self.classes_layout.count()):
185
+ item = self.classes_layout.itemAt(i)
186
+ if item and item.widget():
187
+ class_widget = item.widget()
188
+ class_data, class_object = class_widget.get_data_values()
189
+ data["classes"].append(class_data)
190
+ if hasattr(cabin_object, "append_cabin_class"):
191
+ cabin_object.append_cabin_class(class_object)
192
+ else:
193
+ cabin_object.classes.append(class_object)
194
+
195
+ return data, cabin_object
196
+
197
+ def load_data_values(self, section_data):
198
+ # Save hidden fields for round-trip.
199
+ self.preserved_data = {
200
+ key: value
201
+ for key, value in section_data.items()
202
+ if key not in self.visible_data_keys
203
+ }
204
+ self.data_entry_widget.load_data(section_data)
205
+ self.name_layout.itemAt(2).widget().setText(section_data["Cabin Name"])
206
+ if "cabin_type" in section_data:
207
+ self.cabin_type = section_data["cabin_type"]
208
+ self.layout().itemAt(0).widget().setText(f"<b>Type: {self.cabin_type} Cabin</b>")
209
+ clear_layout(self.classes_layout)
210
+ if "classes" in section_data:
211
+ for class_data in section_data["classes"]:
212
+ class_widget = CabinClassWidget(self.classes_layout.count(), self.delete_cabin_class, class_data)
213
+ self.classes_layout.addWidget(class_widget)
214
+
215
+ def delete_button_pressed(self):
216
+ if self.on_delete:
217
+ self.on_delete(self.index)
@@ -0,0 +1,190 @@
1
+ # RCAIDE_GUI/tabs/geometry/widgets/fuselages/fuselage_section_widget.py
2
+
3
+ # Created: Dec 2025, M. Clarke
4
+ # ------------------------------------------------------------------------------
5
+ # Imports
6
+ # ------------------------------------------------------------------------------
7
+ # RCAIDE imports
8
+ import RCAIDE
9
+
10
+ # RCAIDE GUI imports
11
+ from utilities import Units
12
+ from common_widgets import DataEntryWidget
13
+
14
+ # PyQT imports
15
+ from PyQt6.QtWidgets import QHBoxLayout, QLabel,QLineEdit, QPushButton, QSizePolicy, QSpacerItem,QVBoxLayout, QWidget, QFrame, QComboBox
16
+ from PyQt6.QtCore import Qt
17
+ # ----------------------------------------------------------------------------------------------------------------------
18
+ # Fuselage Segment Widget
19
+ # ----------------------------------------------------------------------------------------------------------------------
20
+ class FuselageSectionWidget(QWidget):
21
+ def __init__(self, index, on_delete, section_data=None):
22
+ super(FuselageSectionWidget, self).__init__()
23
+
24
+ # self.data_fields = {}
25
+ self.coordinate_filename = ""
26
+ self.index = index
27
+ self.on_delete = on_delete
28
+ self.data_entry_widget: DataEntryWidget | None = None
29
+
30
+ self.name_layout = QHBoxLayout()
31
+ self.init_ui(section_data)
32
+
33
+ # noinspection DuplicatedCode
34
+ def init_ui(self, section_data):
35
+ main_layout = QVBoxLayout()
36
+ # add spacing
37
+ spacer_left = QSpacerItem(80, 5, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
38
+ spacer_right = QSpacerItem(300, 5, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
39
+ self.name_layout.addItem(spacer_left)
40
+ self.name_layout.addWidget(QLabel("Segment Name: "))
41
+ self.segment_name_input = QLineEdit(self)
42
+ self.segment_name_input.setFixedWidth(200)
43
+ self.name_layout.addWidget(self.segment_name_input)
44
+ self.name_layout.addItem(spacer_right)
45
+
46
+ ## Energy Network
47
+ segment_label = QLabel("Segment Type:")
48
+ segment_label.setFixedWidth(100) # Adjust width of label
49
+ self.name_layout.addWidget(segment_label)
50
+ self.fuselage_segment_combo = QComboBox()
51
+ self.fuselage_segment_combo.addItems(["Circle Segment", "Ellipse Segment", "Rounded Rectangle Segment", "Super Ellipse Segment" , "Segment"])
52
+ self.fuselage_segment_combo.setFixedWidth(400) # Fix width of combo box
53
+ self.name_layout.addWidget(self.fuselage_segment_combo, alignment=Qt.AlignmentFlag.AlignLeft)
54
+ # Connect signal
55
+ # self.fuselage_segment_combo.currentIndexChanged.connect(self.display_selected_segment)
56
+
57
+
58
+ main_layout.addLayout(self.name_layout)
59
+
60
+ # List of data labels
61
+ data_units_labels = [
62
+ ("Percent X Location", Units.Unitless),
63
+ ("Percent Z Location", Units.Unitless),
64
+ ("Height", Units.Length),
65
+ ("Width", Units.Length),
66
+ ]
67
+
68
+ self.data_entry_widget = DataEntryWidget(data_units_labels)
69
+ delete_button = QPushButton("Delete Segment", self)
70
+ delete_button.setStyleSheet("color:#dbe7ff; font-weight:500; margin:0; padding:0;")
71
+ # delete_button.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
72
+ # delete_button.setFixedWidth(150)
73
+ delete_button.clicked.connect(self.delete_button_pressed)
74
+ # center delete button
75
+ delete_button_layout = QHBoxLayout()
76
+ delete_button_layout.addItem(QSpacerItem(50, 5, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum))
77
+ delete_button_layout.addWidget(delete_button)
78
+ delete_button_layout.addItem(QSpacerItem(50, 5, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum))
79
+
80
+ main_layout.addWidget(self.data_entry_widget)
81
+ main_layout.addLayout(delete_button_layout)
82
+
83
+ # Add horizontal bar
84
+ line_bar = QFrame()
85
+ line_bar.setFrameShape(QFrame.Shape.HLine)
86
+ line_bar.setFrameShadow(QFrame.Shadow.Sunken)
87
+ main_layout.addWidget(line_bar)
88
+
89
+ self.setLayout(main_layout)
90
+
91
+ if section_data:
92
+ self.load_data_values(section_data)
93
+
94
+
95
+
96
+ def create_rcaide_structure(self, data):
97
+
98
+ selected_segment = self.fuselage_segment_combo.currentText()
99
+ segment = None
100
+
101
+ #if selected_segment == "Ellipse Segment":
102
+ #item = self.powertrain_layout.itemAt(0)
103
+ #assert item is not None
104
+ #widget = item.widget()
105
+ #assert widget is not None and isinstance(widget, PowertrainWidget)
106
+ #_, lines, propulsors,converters = widget.get_data_values()
107
+
108
+ if selected_segment == "Circle Segment":
109
+ segment = RCAIDE.Library.Components.Fuselages.Segments.Circle_Segment()
110
+ segment.percent_x_location = data["Percent X Location"][0]
111
+ segment.percent_z_location = data["Percent Z Location"][0]
112
+ segment.height = data["Height"][0]
113
+ segment.width = data["Width"][0]
114
+ segment.tag = data["Segment Name"]
115
+ elif selected_segment == "Ellipse Segment":
116
+ segment = RCAIDE.Library.Components.Fuselages.Segments.Ellipse_Segment()
117
+ segment.percent_x_location = data["Percent X Location"][0]
118
+ segment.percent_z_location = data["Percent Z Location"][0]
119
+ segment.height = data["Height"][0]
120
+ segment.width = data["Width"][0]
121
+ segment.tag = data["Segment Name"]
122
+ elif selected_segment == "Rounded Rectangle Segment":
123
+ segment = RCAIDE.Library.Components.Fuselages.Segments.Rounded_Rectangle_Segment()
124
+ segment.percent_x_location = data["Percent X Location"][0]
125
+ segment.percent_z_location = data["Percent Z Location"][0]
126
+ segment.height = data["Height"][0]
127
+ segment.width = data["Width"][0]
128
+ segment.tag = data["Segment Name"]
129
+ elif selected_segment == "Super Ellipse Segment":
130
+ segment = RCAIDE.Library.Components.Fuselages.Segments.Super_Ellipse_Segment()
131
+ segment.percent_x_location = data["Percent X Location"][0]
132
+ segment.percent_z_location = data["Percent Z Location"][0]
133
+ segment.height = data["Height"][0]
134
+ segment.width = data["Width"][0]
135
+ segment.tag = data["Segment Name"]
136
+ elif selected_segment == "Segment":
137
+ segment = RCAIDE.Library.Components.Fuselages.Segments.Segment()
138
+ segment.percent_x_location = data["Percent X Location"][0]
139
+ segment.percent_z_location = data["Percent Z Location"][0]
140
+ segment.height = data["Height"][0]
141
+ segment.width = data["Width"][0]
142
+ segment.tag = data["Segment Name"]
143
+ else:
144
+ segment = None
145
+
146
+
147
+ return segment
148
+
149
+ def get_data_values(self):
150
+ data = self.data_entry_widget.get_values()
151
+ data_si = self.data_entry_widget.get_values_si()
152
+ data["Segment Name"] = self.name_layout.itemAt(2).widget().text()
153
+ data_si["Segment Name"] = self.name_layout.itemAt(2).widget().text()
154
+ selected_type = self.fuselage_segment_combo.currentText()
155
+ data["segment_type"] = selected_type
156
+ data_si["segment_type"] = selected_type
157
+ segment = self.create_rcaide_structure(data_si)
158
+ return data, segment
159
+
160
+ def load_data_values(self, section_data):
161
+ self.data_entry_widget.load_data(section_data)
162
+ self.name_layout.itemAt(2).widget().setText(section_data["Segment Name"])
163
+
164
+ if "segment_type" in section_data:
165
+ index = self.fuselage_segment_combo.findText(section_data["segment_type"])
166
+ if index>=0:
167
+ self.fuselage_segment_combo.setCurrentIndex(index)
168
+
169
+ def delete_button_pressed(self):
170
+ if self.on_delete is None:
171
+ print("on_delete is None")
172
+ return
173
+
174
+ self.on_delete(self.index)
175
+
176
+
177
+ def display_selected_segment(self, index):
178
+ selected_fuselage_segmemt = self.fuselage_segment_combo.currentText()
179
+ # Clear the layout first
180
+ clear_layout(self.powertrain_layout)
181
+
182
+ if selected_fuselage_segmemt == "Fuel":
183
+ self.main_powertrain_widget = PowertrainWidget()
184
+ self.powertrain_layout.addWidget(self.main_powertrain_widget)
185
+ elif selected_fuselage_segmemt == "None Selected":
186
+ # Do nothing or add blank widget
187
+ pass
188
+ else:
189
+ # Handle other energy network options here
190
+ pass
@@ -0,0 +1,25 @@
1
+ # RCAIDE_GUI/tabs/geometry/widgets/geometry_data_widget.py
2
+
3
+ # Created: Dec 2025, M. Clarke
4
+ # ------------------------------------------------------------------------------
5
+ # Imports
6
+ # ------------------------------------------------------------------------------
7
+ from PyQt6.QtWidgets import QWidget
8
+
9
+
10
+ class GeometryDataWidget(QWidget):
11
+ def __init__(self):
12
+ super(GeometryDataWidget, self).__init__()
13
+
14
+ def create_rcaide_structure(self, data):
15
+ pass
16
+
17
+ # Should return the data array AND the RCAIDE structure for the widget
18
+ def get_data_values(self):
19
+ pass
20
+
21
+ def load_data_values(self, data):
22
+ pass
23
+
24
+ def delete_button_pressed(self):
25
+ pass
@@ -0,0 +1,6 @@
1
+ # RCAIDE_GUI/tabs/geometry/widgets/fuselages/__init__.py
2
+ # ----------------------------------------------------------------------------------------------------------------------
3
+ # IMPORT
4
+ # ----------------------------------------------------------------------------------------------------------------------
5
+
6
+
@@ -0,0 +1 @@
1
+ # RCAIDE_GUI/tabs/geometry/widgets/nacelles/__init__.py
@@ -0,0 +1,9 @@
1
+ # RCAIDE_GUI/tabs/geometry/widgets/powertrain/__init__.py
2
+ # ----------------------------------------------------------------------------------------------------------------------
3
+ # IMPORT
4
+ # ----------------------------------------------------------------------------------------------------------------------
5
+
6
+ from tabs.geometry.frames.powertrain.converters import *
7
+ from tabs.geometry.frames.powertrain.sources import *
8
+ from tabs.geometry.frames.powertrain.propulsors import *
9
+ from tabs.geometry.frames.powertrain.distributors import *
@@ -0,0 +1,5 @@
1
+ # RCAIDE_GUI/tabs/geometry/widgets/powertrain/converters/__init__.py
2
+ # ----------------------------------------------------------------------------------------------------------------------
3
+ # IMPORT
4
+ # ----------------------------------------------------------------------------------------------------------------------
5
+ from tabs.geometry.widgets.powertrain.converters.turboelectric_generator_widget import TurboelectricGeneratorWidget