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
tabs/home/home.py ADDED
@@ -0,0 +1,585 @@
1
+ # RCAIDE_GUI/tabs/home/home.py
2
+
3
+ # RCAIDE-GUI imports
4
+ from utilities import create_line_bar
5
+ from tabs import TabWidget
6
+ from common_widgets.image_widget import ImageWidget
7
+
8
+ # PyQt imports
9
+ from PyQt6.QtCore import QSize, Qt, QPropertyAnimation, QEasingCurve, QTimer, QUrl
10
+ from PyQt6.QtCore import qInstallMessageHandler
11
+ from PyQt6.QtGui import QPixmap, QDesktopServices
12
+ from PyQt6.QtWidgets import (
13
+ QFrame, QGridLayout, QPushButton, QSizePolicy,
14
+ QWidget, QHBoxLayout, QVBoxLayout, QLabel, QGraphicsOpacityEffect
15
+ )
16
+ from PyQt6.QtWidgets import QApplication
17
+
18
+ # Python imports
19
+ import os
20
+
21
+ _ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
22
+ _IMG = os.path.join(_ROOT, "app_data", "images")
23
+
24
+ # ------------------------------------------------------------------------------
25
+ # Home Widget
26
+ # ------------------------------------------------------------------------------
27
+ def suppress_qt_warnings(mode, context, message):
28
+ if "Unknown property" in message: # hides CSS warnings (Qt Does not support CSS propertieis within Load/Scratch Aircraft (RH column) - Harmless warning, functionality not affected)
29
+ return
30
+ print(message)
31
+ qInstallMessageHandler(suppress_qt_warnings)
32
+
33
+ BANNER_HEIGHT = 320 # Default banner image height in pixels
34
+ VERTICAL_FOCUS = 0.3 # Vertical offset ratio (0 = top, 1 = bottom crop focus)
35
+
36
+
37
+ class BannerImage(QLabel):
38
+ # Displays a banner image that maintains aspect ratio and crops vertically when resized
39
+ def __init__(self, path: str, height: int = BANNER_HEIGHT):
40
+ super().__init__()
41
+ self._pix = QPixmap(path) # Load the image from file
42
+ self._fixed_h = height # Store the banner height
43
+ self.setFixedHeight(height) # Fix the widget height
44
+ self.setAlignment(Qt.AlignmentFlag.AlignCenter)
45
+ self.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Fixed)
46
+
47
+ def resizeEvent(self, e):
48
+ # Scales and crops the banner image to fill the available width
49
+ if not self._pix.isNull():
50
+ # Scale image to fill width while keeping aspect ratio
51
+ scaled = self._pix.scaled(
52
+ self.width(), self._fixed_h,
53
+ Qt.AspectRatioMode.KeepAspectRatioByExpanding,
54
+ Qt.TransformationMode.SmoothTransformation
55
+ )
56
+
57
+ # Compute how much to crop vertically based on focus ratio
58
+ y_offset = int(max(0, scaled.height() - self._fixed_h) * VERTICAL_FOCUS)
59
+
60
+ # Crop the scaled image to fit the fixed height and apply
61
+ cropped = scaled.copy(0, y_offset, self.width(), self._fixed_h)
62
+ self.setPixmap(cropped)
63
+
64
+ # Call parent method to ensure normal event behavior
65
+ super().resizeEvent(e)
66
+
67
+ class HomeWidget(TabWidget):
68
+ def __init__(self):
69
+ super(HomeWidget, self).__init__()
70
+
71
+ # --- Splash logo screen setup ---
72
+ self.splash_label = QLabel()
73
+ self.splash_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
74
+ self.splash_label.setStyleSheet("background-color: #02101d;")
75
+
76
+ # Load and scale RCAIDE logo
77
+ pixmap = QPixmap(os.path.join(_IMG, "logo.png"))
78
+ self.splash_label.setPixmap(
79
+ pixmap.scaled(260, 260, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)
80
+ )
81
+
82
+ # Add opacity effect for fade animations
83
+ self.splash_opacity = QGraphicsOpacityEffect()
84
+ self.splash_label.setGraphicsEffect(self.splash_opacity)
85
+
86
+ # Fade-in animation
87
+ self.fade_in = QPropertyAnimation(self.splash_opacity, b"opacity")
88
+ self.fade_in.setDuration(1800)
89
+ self.fade_in.setStartValue(0)
90
+ self.fade_in.setEndValue(1)
91
+ self.fade_in.setEasingCurve(QEasingCurve.Type.InOutQuad)
92
+
93
+ # Fade-out animation
94
+ self.fade_out = QPropertyAnimation(self.splash_opacity, b"opacity")
95
+ self.fade_out.setDuration(1300)
96
+ self.fade_out.setStartValue(1)
97
+ self.fade_out.setEndValue(0)
98
+ self.fade_out.setEasingCurve(QEasingCurve.Type.InOutQuad)
99
+ self.fade_out.finished.connect(self._show_homepage_after_splash)
100
+
101
+ # --- Main layout container ---
102
+ self.layout = QVBoxLayout(self)
103
+ self.layout.setContentsMargins(0, 0, 0, 0)
104
+ self.layout.setSpacing(0)
105
+
106
+ # Main home widget (hidden until splash finishes)
107
+ self.main_widget = QWidget()
108
+ self.main_widget.setVisible(False)
109
+
110
+ # Add both widgets to layout
111
+ self.layout.addWidget(self.splash_label)
112
+ self.layout.addWidget(self.main_widget)
113
+
114
+ # Build main UI components
115
+ self._build_main_ui()
116
+
117
+ # Start splash animation with short delay
118
+ QTimer.singleShot(200, self._start_splash)
119
+
120
+ def _start_splash(self):
121
+ """Starts the splash logo fade-in, then triggers fade-out."""
122
+ self.fade_in.start()
123
+ self.fade_in.finished.connect(lambda: QTimer.singleShot(800, self.fade_out.start))
124
+
125
+ def _show_homepage_after_splash(self):
126
+ """Switches from splash logo to main home screen with fade-in."""
127
+ self.splash_label.hide()
128
+ self.main_widget.setVisible(True)
129
+
130
+ # Apply fade-in effect to main content
131
+ home_opacity = QGraphicsOpacityEffect()
132
+ self.main_widget.setGraphicsEffect(home_opacity)
133
+ home_opacity.setOpacity(0.0)
134
+
135
+ self.home_fade_in = QPropertyAnimation(home_opacity, b"opacity")
136
+ self.home_fade_in.setDuration(1000)
137
+ self.home_fade_in.setStartValue(0.0)
138
+ self.home_fade_in.setEndValue(1.0)
139
+ self.home_fade_in.setEasingCurve(QEasingCurve.Type.InOutQuad)
140
+ self.home_fade_in.finished.connect(lambda: self.main_widget.setGraphicsEffect(None))
141
+ self.home_fade_in.start()
142
+
143
+ # Play fade sequence for banner and flowchart
144
+ self.play_fade_sequence()
145
+
146
+ # Adjust layout for current screen or fullscreen mode
147
+ self.adjust_for_screen(force=True)
148
+
149
+ # --- Main UI ----
150
+ def _build_main_ui(self):
151
+ base_layout = QVBoxLayout(self.main_widget)
152
+ base_layout.setSpacing(0)
153
+ base_layout.setContentsMargins(0, 0, 0, 0)
154
+
155
+ # Header
156
+ header_layout = QGridLayout()
157
+ website_btn = QPushButton("Website")
158
+ github_btn = QPushButton("GitHub")
159
+ docs_btn = QPushButton("Documentation")
160
+ contribute_btn = QPushButton("Contribute")
161
+ contact_btn = QPushButton("Contact")
162
+
163
+ header_layout.addWidget(website_btn, 0, 0)
164
+ header_layout.addWidget(github_btn, 0, 1)
165
+ header_layout.addWidget(docs_btn, 0, 2)
166
+ header_layout.addWidget(contribute_btn, 0, 3)
167
+ header_layout.addWidget(contact_btn, 0, 4)
168
+ header_layout.setContentsMargins(12, 12, 12, 0)
169
+ header_layout.setSpacing(10)
170
+
171
+ # --- Tab URLs ---
172
+ website_btn.clicked.connect(lambda: QDesktopServices.openUrl(QUrl("https://www.rcaide.leadsresearchgroup.com")))
173
+ github_btn.clicked.connect(lambda: QDesktopServices.openUrl(QUrl("https://github.com/leadsgroup")))
174
+ docs_btn.clicked.connect(lambda: QDesktopServices.openUrl(QUrl("https://www.docs.rcaide.leadsresearchgroup.com")))
175
+ contribute_btn.clicked.connect(lambda: QDesktopServices.openUrl(QUrl("https://www.rcaide.leadsresearchgroup.com/community")))
176
+ contact_btn.clicked.connect(lambda: QDesktopServices.openUrl(QUrl("https://www.rcaide.leadsresearchgroup.com/community")))
177
+
178
+ # --- Banner Section ---
179
+ # Container for the banner image and overlay text
180
+ banner_container = QFrame()
181
+ banner_container.setFixedHeight(BANNER_HEIGHT)
182
+ banner_container.setStyleSheet("border:none; background:transparent;")
183
+
184
+ # Background banner image
185
+ self.banner = BannerImage(
186
+ os.path.join(_IMG, "background.jpg"),
187
+ height=BANNER_HEIGHT
188
+ )
189
+
190
+ # Main title and subtitle shown on top of the banner
191
+ self.rcaide_label = QLabel("RCAIDE")
192
+ self.leads_label = QLabel("by L.E.A.D.S (UIUC)")
193
+
194
+ # Center text and allow mouse events to pass through
195
+ for lbl in (self.rcaide_label, self.leads_label):
196
+ lbl.setAlignment(Qt.AlignmentFlag.AlignCenter)
197
+ lbl.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents)
198
+
199
+ # Styling for banner text
200
+ self.rcaide_label.setStyleSheet(
201
+ "color:#00BFFF; font-size:46px; font-weight:800; letter-spacing:2px;"
202
+ )
203
+ self.leads_label.setStyleSheet(
204
+ "color:white; font-size:20px; font-weight:500;"
205
+ )
206
+
207
+ # Layout that stacks title and subtitle vertically
208
+ overlay_layout = QVBoxLayout()
209
+ overlay_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
210
+ overlay_layout.addWidget(self.rcaide_label)
211
+ overlay_layout.addWidget(self.leads_label)
212
+
213
+ # Widget that holds the overlay text (transparent background)
214
+ overlay_widget = QWidget()
215
+ overlay_widget.setLayout(overlay_layout)
216
+ overlay_widget.setStyleSheet("background:transparent;")
217
+
218
+ # --- Layout for the banner container ---
219
+ # Banner image first, then text overlay on top
220
+ banner_layout = QVBoxLayout(banner_container)
221
+ banner_layout.setContentsMargins(0, 0, 0, 0)
222
+ banner_layout.addWidget(self.banner)
223
+ banner_layout.addWidget(
224
+ overlay_widget,
225
+ alignment=Qt.AlignmentFlag.AlignCenter
226
+ )
227
+
228
+ # Add banner section to the main layout
229
+ base_layout.addLayout(header_layout)
230
+ base_layout.addWidget(banner_container)
231
+ base_layout.addWidget(create_line_bar())
232
+
233
+ # Body Background
234
+ body_bg = QFrame()
235
+ body_bg.setObjectName("bodyBg")
236
+ body_bg.setStyleSheet("""
237
+ #bodyBg {
238
+ background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1,
239
+ stop:0 #0B1D2C, stop:1 #051018);
240
+ border-top: 1px solid #11293C;
241
+ }
242
+ """)
243
+ body_layout = QHBoxLayout(body_bg)
244
+ body_layout.setContentsMargins(60, 16, 100, 16)
245
+ body_layout.setSpacing(30)
246
+ body_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
247
+
248
+ # --- Left Column: How To Use GUI Flowchart Image ---
249
+ self.flowchart_frame = QFrame()
250
+ self.flowchart_frame.setStyleSheet("""
251
+ background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
252
+ stop:0 rgba(20, 40, 60, 0.7),
253
+ stop:1 rgba(10, 20, 30, 0.7));
254
+ border-radius: 16px;
255
+ border: 1px solid rgba(0, 170, 255, 0.3);
256
+ """)
257
+ flowchart_layout = QVBoxLayout(self.flowchart_frame)
258
+ flowchart_layout.setContentsMargins(0, 0, 0, 0)
259
+ flowchart_layout.setSpacing(0)
260
+ flowchart_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
261
+
262
+ # Load High-Resolution Flowchart Image
263
+ original_pix = QPixmap(os.path.join(_IMG, "flowchart.png"))
264
+ if not original_pix.isNull():
265
+ screen = QApplication.primaryScreen()
266
+ dpr = screen.devicePixelRatio() if screen else 1.0
267
+ highres_pix = original_pix.scaled(
268
+ int(620 * dpr),
269
+ int(320 * dpr),
270
+ Qt.AspectRatioMode.KeepAspectRatio,
271
+ Qt.TransformationMode.SmoothTransformation
272
+ )
273
+ highres_pix.setDevicePixelRatio(dpr)
274
+ else:
275
+ highres_pix = original_pix
276
+
277
+ # Center Flowchart Within Container
278
+ self.flowchart_image = QLabel()
279
+ self.flowchart_image.setPixmap(highres_pix)
280
+ self.flowchart_image.setAlignment(Qt.AlignmentFlag.AlignCenter)
281
+ self.flowchart_image.setFixedSize(620, 320)
282
+ self.flowchart_image.setStyleSheet("border:none; background:transparent;")
283
+
284
+ flowchart_layout.addStretch(1)
285
+ flowchart_layout.addWidget(self.flowchart_image, alignment=Qt.AlignmentFlag.AlignCenter)
286
+ flowchart_layout.addStretch(1)
287
+
288
+ # --- Right Column: Load Aircraft / Mission Or Start From Scratch ---
289
+ self.mission_frame = QFrame()
290
+ self.mission_frame.setObjectName("missionPanel")
291
+ self.mission_frame.setStyleSheet("""
292
+ #missionPanel {
293
+ background-color: rgba(20, 40, 60, 0.55);
294
+ border: 1px solid rgba(0, 170, 255, 0.35);
295
+ border-radius: 20px;
296
+ box-shadow: 0px 0px 25px rgba(0, 170, 255, 0.15);
297
+ backdrop-filter: blur(14px);
298
+ }
299
+ QLabel {
300
+ color: #ffffff;
301
+ font-size: 22px;
302
+ font-weight: bold;
303
+ }
304
+ QPushButton {
305
+ background-color: qlineargradient(x1:0,y1:0,x2:1,y2:1,
306
+ stop:0 #0099FF, stop:1 #0066CC);
307
+ color: white;
308
+ font-size: 18px;
309
+ font-weight: 600;
310
+ border-radius: 12px;
311
+ padding: 8px 22px;
312
+ border: 1px solid rgba(0,170,255,0.35);
313
+ }
314
+ QPushButton:hover {
315
+ background-color: qlineargradient(x1:0,y1:0,x2:1,y2:1,
316
+ stop:0 #33BBFF, stop:1 #0088FF);
317
+ box-shadow: 0 0 15px rgba(0,170,255,0.3);
318
+ }
319
+ """)
320
+ mission_layout = QVBoxLayout(self.mission_frame)
321
+ mission_layout.setContentsMargins(45, 16, 45, 16)
322
+ mission_layout.setSpacing(14)
323
+ mission_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
324
+
325
+ # --- Mission Panel Title Section ---
326
+ title_container = QVBoxLayout()
327
+ title_container.setSpacing(6)
328
+ title_container.setAlignment(Qt.AlignmentFlag.AlignCenter)
329
+
330
+ # Main section title text
331
+ mission_label = QLabel("Get Started")
332
+ mission_label.setFixedHeight(40)
333
+ mission_label.setStyleSheet("""
334
+ color: #00BFFF;
335
+ font-size: 28px;
336
+ font-weight: 800;
337
+ letter-spacing: 1px;
338
+ padding-top: 6px;
339
+ padding-bottom: 6px;
340
+ """)
341
+
342
+ # Decorative underline below the title
343
+ underline = QFrame()
344
+ underline.setFixedHeight(5)
345
+ underline.setFixedWidth(180)
346
+ underline.setStyleSheet("""
347
+ QFrame {
348
+ background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
349
+ stop:0 transparent,
350
+ stop:0.25 rgba(0,150,255,0.35),
351
+ stop:0.5 rgba(0,220,255,0.95),
352
+ stop:0.75 rgba(0,150,255,0.35),
353
+ stop:1 transparent);
354
+ border-radius: 2px;
355
+ margin-top: 6px;
356
+ }
357
+ """)
358
+
359
+ # Add title and underline to vertical layout
360
+ title_container.addWidget(mission_label, alignment=Qt.AlignmentFlag.AlignCenter)
361
+ title_container.addWidget(underline, alignment=Qt.AlignmentFlag.AlignCenter)
362
+ mission_layout.addLayout(title_container)
363
+
364
+ # Subtitle text under the title
365
+ subtitle = QLabel("Quickly set up or define your aircraft mission.")
366
+ subtitle.setStyleSheet("color: rgba(255,255,255,0.7); font-size: 18px; font-weight: 400;")
367
+ mission_layout.addWidget(subtitle, alignment=Qt.AlignmentFlag.AlignCenter)
368
+
369
+ # Buttons
370
+ # Horizontal layout for the load row
371
+ load_row = QHBoxLayout()
372
+ load_row.setAlignment(Qt.AlignmentFlag.AlignCenter)
373
+ load_row.setSpacing(15)
374
+
375
+ # Button to load a predefined aircraft and mission setup
376
+ load_btn = QPushButton("Load Aircraft and Mission")
377
+ load_btn.setMinimumWidth(230)
378
+ load_btn.setFixedHeight(42)
379
+
380
+ # Add button to the same row
381
+ load_row.addWidget(load_btn)
382
+ mission_layout.addLayout(load_row)
383
+
384
+ # Divider line between the load and scratch sections
385
+ divider = QFrame()
386
+ divider.setFixedHeight(2)
387
+ divider.setStyleSheet("""
388
+ QFrame {
389
+ border: none;
390
+ background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
391
+ stop:0 transparent,
392
+ stop:0.3 rgba(0,180,255,0.25),
393
+ stop:0.5 rgba(0,255,255,0.8),
394
+ stop:0.7 rgba(0,180,255,0.25),
395
+ stop:1 transparent);
396
+ border-radius: 1px;
397
+ margin-top: 18px;
398
+ margin-bottom: 10px;
399
+ }
400
+ """)
401
+ mission_layout.addWidget(divider)
402
+
403
+ # Button to start a new mission from scratch
404
+ scratch_btn = QPushButton("Start from Scratch")
405
+ scratch_btn.setMinimumWidth(250)
406
+ scratch_btn.setFixedHeight(42)
407
+
408
+ scratch_container = QVBoxLayout()
409
+ scratch_container.setContentsMargins(0, 0, 0, 0)
410
+ scratch_container.addWidget(scratch_btn, alignment=Qt.AlignmentFlag.AlignCenter)
411
+ mission_layout.addLayout(scratch_container)
412
+
413
+ # Navigation Handler
414
+ def go_to_geometry_tab():
415
+ # Find the TabWidget that contains this HomeWidget
416
+ parent = self.window()
417
+ tabs = None
418
+ # Try possible parent-paths
419
+ if hasattr(parent, "tabs"):
420
+ tabs = parent.tabs
421
+ elif hasattr(parent, "tab_widget"):
422
+ tabs = parent.tab_widget
423
+ from PyQt6.QtWidgets import QTabWidget
424
+ if not tabs:
425
+ w = self
426
+ while w is not None:
427
+ if isinstance(w, QTabWidget):
428
+ tabs = w
429
+ break
430
+ w = w.parentWidget()
431
+ if not tabs:
432
+ print("[Home] No TabWidget found in parent chain.")
433
+ return
434
+
435
+ # Locate Geometry tab by attribute or title
436
+ idx = -1
437
+ if hasattr(tabs, "geometry_tab"):
438
+ try:
439
+ idx = tabs.indexOf(tabs.geometry_tab)
440
+ except Exception:
441
+ idx = -1
442
+ if idx == -1:
443
+ geometry_names = {"geometry", "geometry parameterization", "aircraft geometry"}
444
+ for i in range(tabs.count()):
445
+ title = tabs.tabText(i).strip().lower()
446
+ if any(name in title for name in geometry_names):
447
+ idx = i
448
+ break
449
+ if idx == -1:
450
+ print("[Home] Geometry tab not found.")
451
+ return
452
+
453
+ # Switch and initialize
454
+ tabs.setCurrentIndex(idx)
455
+ w = tabs.widget(idx)
456
+ if hasattr(w, "update_layout"):
457
+ try:
458
+ w.update_layout()
459
+ except Exception as e:
460
+ print(f"[Home] Geometry tab update error: {e}")
461
+
462
+ # Button Logic
463
+ def handle_load_click():
464
+ parent = self.window()
465
+ if hasattr(parent, "load_all"):
466
+ parent.load_all()
467
+ return
468
+
469
+ def handle_scratch_click():
470
+ go_to_geometry_tab()
471
+
472
+ load_btn.clicked.connect(handle_load_click)
473
+ scratch_btn.clicked.connect(handle_scratch_click)
474
+
475
+ # Sizes
476
+ self._normal_flowchart_size = QSize(620, 320)
477
+ self._normal_mission_size = QSize(550, 310)
478
+ self._fullscreen_scale = 1.55
479
+
480
+ self.flowchart_frame.setFixedSize(self._normal_flowchart_size)
481
+ self.mission_frame.setFixedSize(self._normal_mission_size)
482
+
483
+ body_layout.addWidget(self.flowchart_frame)
484
+ body_layout.addWidget(self.mission_frame)
485
+ base_layout.addWidget(body_bg)
486
+
487
+ # Footer
488
+ footer = QFrame()
489
+ footer.setFixedHeight(12)
490
+ footer.setStyleSheet("background-color: qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 #004080, stop:1 #001A33);")
491
+ base_layout.addWidget(footer)
492
+ self.setup_fade_animations()
493
+
494
+ def resizeEvent(self, event):
495
+ # Called when the widget is resized
496
+ super().resizeEvent(event)
497
+
498
+ # Update sizes based on window state
499
+ self.adjust_for_screen()
500
+
501
+ def adjust_for_screen(self, force=False):
502
+ # Get the main application window
503
+ window = self.window()
504
+ if not window:
505
+ return
506
+
507
+ # Check if the window is fullscreen
508
+ is_fullscreen = window.isFullScreen()
509
+
510
+ # Pick scale factor (larger in fullscreen)
511
+ scale = self._fullscreen_scale if is_fullscreen else 1.0
512
+
513
+ # Scale flowchart and mission frame sizes
514
+ flow_w = int(self._normal_flowchart_size.width() * scale)
515
+ flow_h = int(self._normal_flowchart_size.height() * scale)
516
+ mission_w = int(self._normal_mission_size.width() * scale)
517
+ mission_h = int(self._normal_mission_size.height() * scale)
518
+
519
+ # Apply new sizes
520
+ self.flowchart_frame.setFixedSize(flow_w, flow_h)
521
+ self.mission_frame.setFixedSize(mission_w, mission_h)
522
+
523
+ # Refresh layout and redraw
524
+ self.updateGeometry()
525
+ self.repaint()
526
+
527
+ def setup_fade_animations(self):
528
+ # Create a simple fade-in animation for a widget
529
+ def make_fade(widget, duration=2000):
530
+ effect = QGraphicsOpacityEffect()
531
+ widget.setGraphicsEffect(effect)
532
+
533
+ anim = QPropertyAnimation(effect, b"opacity")
534
+ anim.setDuration(duration)
535
+ anim.setStartValue(0)
536
+ anim.setEndValue(1)
537
+ anim.setEasingCurve(QEasingCurve.Type.InOutCubic)
538
+
539
+ return effect, anim
540
+
541
+ # Fade animations for main UI elements
542
+ self.banner_effect, self.banner_anim = make_fade(self.banner)
543
+ self.flowchart_effect, self.flow_anim = make_fade(self.flowchart_frame)
544
+ self.mission_effect, self.mission_anim = make_fade(self.mission_frame)
545
+
546
+ # Fade animations for labels (slightly faster)
547
+ self.rcaide_effect, self.rcaide_anim = make_fade(self.rcaide_label, 1800)
548
+ self.leads_effect, self.leads_anim = make_fade(self.leads_label, 1800)
549
+
550
+ def play_fade_sequence(self):
551
+ # Start main content fade-in
552
+ def start_group1():
553
+ self.banner_anim.start()
554
+ self.flow_anim.start()
555
+ self.mission_anim.start()
556
+
557
+ # Start label fade-in after a short delay
558
+ QTimer.singleShot(1200, start_group2)
559
+
560
+ # Fade in branding labels
561
+ def start_group2():
562
+ self.rcaide_label.setVisible(True)
563
+ self.leads_label.setVisible(True)
564
+
565
+ self.rcaide_anim.start()
566
+ self.leads_anim.start()
567
+
568
+ # Clean up effects after animation
569
+ self.rcaide_anim.finished.connect(
570
+ lambda: self.rcaide_label.setGraphicsEffect(None)
571
+ )
572
+ self.leads_anim.finished.connect(
573
+ lambda: self.leads_label.setGraphicsEffect(None)
574
+ )
575
+
576
+ # Hide labels before animation starts
577
+ self.rcaide_label.setVisible(False)
578
+ self.leads_label.setVisible(False)
579
+
580
+ # Small delay before starting animations
581
+ QTimer.singleShot(100, start_group1)
582
+
583
+ def get_widget() -> QWidget:
584
+ # Return the home screen widget
585
+ return HomeWidget()
@@ -0,0 +1,4 @@
1
+ # RCAIDE_GUI/tabs/mission/__init__.py
2
+
3
+ from .mission import *
4
+ from . import widgets