RCAIDE-GUI 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- common_widgets/__init__.py +6 -0
- common_widgets/animated_toggle.py +141 -0
- common_widgets/color.py +17 -0
- common_widgets/data_entry_widget.py +337 -0
- common_widgets/image_widget.py +21 -0
- common_widgets/unit_picker_widget.py +48 -0
- main.py +168 -0
- rcaide_gui-1.0.0.dist-info/METADATA +96 -0
- rcaide_gui-1.0.0.dist-info/RECORD +170 -0
- rcaide_gui-1.0.0.dist-info/WHEEL +5 -0
- rcaide_gui-1.0.0.dist-info/entry_points.txt +2 -0
- rcaide_gui-1.0.0.dist-info/top_level.txt +5 -0
- rcaide_io.py +818 -0
- tabs/__init__.py +10 -0
- tabs/aircraft_configs/__init__.py +3 -0
- tabs/aircraft_configs/aircraft_configs.py +301 -0
- tabs/analysis/__init__.py +4 -0
- tabs/analysis/analysis.py +92 -0
- tabs/analysis/widgets/__init__.py +13 -0
- tabs/analysis/widgets/aeroacoustics_widget.py +158 -0
- tabs/analysis/widgets/aerodynamics_widget.py +108 -0
- tabs/analysis/widgets/analysis_data_widget.py +29 -0
- tabs/analysis/widgets/atmosphere_widget.py +49 -0
- tabs/analysis/widgets/costs_widget.py +35 -0
- tabs/analysis/widgets/energy_widget.py +32 -0
- tabs/analysis/widgets/geometry_widget.py +55 -0
- tabs/analysis/widgets/planets_widget.py +34 -0
- tabs/analysis/widgets/propulsion_widget.py +53 -0
- tabs/analysis/widgets/stability_widget.py +34 -0
- tabs/analysis/widgets/weights_widget.py +94 -0
- tabs/geometry/__init__.py +6 -0
- tabs/geometry/aircraft_configs.py +232 -0
- tabs/geometry/frames/__init__.py +15 -0
- tabs/geometry/frames/booms/__init__.py +5 -0
- tabs/geometry/frames/booms/boom_frame.py +238 -0
- tabs/geometry/frames/cargo_bays/__init__.py +5 -0
- tabs/geometry/frames/cargo_bays/cargo_bay_frame.py +159 -0
- tabs/geometry/frames/default_frame.py +23 -0
- tabs/geometry/frames/energy_network/__init__.py +2 -0
- tabs/geometry/frames/energy_network/turbofan_network/__init__.py +3 -0
- tabs/geometry/frames/energy_network/turbofan_network/frames/__init__.py +1 -0
- tabs/geometry/frames/energy_network/turbofan_network/widgets/__init__.py +1 -0
- tabs/geometry/frames/fuselages/__init__.py +5 -0
- tabs/geometry/frames/fuselages/fuselage_frame.py +294 -0
- tabs/geometry/frames/geometry_frame.py +91 -0
- tabs/geometry/frames/landing_gears/__init__.py +5 -0
- tabs/geometry/frames/landing_gears/landing_gear_frame.py +189 -0
- tabs/geometry/frames/nacelles/__init__.py +1 -0
- tabs/geometry/frames/powertrain/__init__.py +6 -0
- tabs/geometry/frames/powertrain/converters/__init__.py +5 -0
- tabs/geometry/frames/powertrain/converters/converter_frame.py +137 -0
- tabs/geometry/frames/powertrain/distributors/__init__.py +5 -0
- tabs/geometry/frames/powertrain/distributors/distributor_frame.py +143 -0
- tabs/geometry/frames/powertrain/nacelles/__init__.py +5 -0
- tabs/geometry/frames/powertrain/nacelles/nacelle_frame.py +283 -0
- tabs/geometry/frames/powertrain/powertrain_frame.py +259 -0
- tabs/geometry/frames/powertrain/propulsors/__init__.py +5 -0
- tabs/geometry/frames/powertrain/propulsors/propulsor_frame.py +146 -0
- tabs/geometry/frames/powertrain/sources/__init__.py +6 -0
- tabs/geometry/frames/powertrain/sources/energy_source_frame.py +147 -0
- tabs/geometry/frames/vehicle_frame.py +171 -0
- tabs/geometry/frames/wings/__init__.py +5 -0
- tabs/geometry/frames/wings/wings_frame.py +457 -0
- tabs/geometry/geometry.py +513 -0
- tabs/geometry/widgets/__init__.py +12 -0
- tabs/geometry/widgets/booms/__init__.py +6 -0
- tabs/geometry/widgets/booms/boom_section_widget.py +109 -0
- tabs/geometry/widgets/cargo_bays/__init__.py +1 -0
- tabs/geometry/widgets/fuselages/__init__.py +8 -0
- tabs/geometry/widgets/fuselages/cabin_class_widget.py +188 -0
- tabs/geometry/widgets/fuselages/cabin_widget.py +217 -0
- tabs/geometry/widgets/fuselages/fuselage_section_widget.py +190 -0
- tabs/geometry/widgets/geometry_data_widget.py +25 -0
- tabs/geometry/widgets/landing_gears/__init__.py +6 -0
- tabs/geometry/widgets/nacelles/__init__.py +1 -0
- tabs/geometry/widgets/powertrain/__init__.py +9 -0
- tabs/geometry/widgets/powertrain/converters/__init__.py +5 -0
- tabs/geometry/widgets/powertrain/converters/turboelectric_generator_widget.py +74 -0
- tabs/geometry/widgets/powertrain/distributors/__init__.py +5 -0
- tabs/geometry/widgets/powertrain/distributors/fuel_line_widget.py +81 -0
- tabs/geometry/widgets/powertrain/modulators/__init__.py +5 -0
- tabs/geometry/widgets/powertrain/modulators/esc_widget.py +58 -0
- tabs/geometry/widgets/powertrain/nacelles/__init__.py +5 -0
- tabs/geometry/widgets/powertrain/nacelles/nacelle_section_widget.py +115 -0
- tabs/geometry/widgets/powertrain/powertrain_connector_widget.py +136 -0
- tabs/geometry/widgets/powertrain/powertrain_widget.py +206 -0
- tabs/geometry/widgets/powertrain/propulsors/__init__.py +5 -0
- tabs/geometry/widgets/powertrain/propulsors/turbofan_widget.py +462 -0
- tabs/geometry/widgets/powertrain/sources/__init__.py +6 -0
- tabs/geometry/widgets/powertrain/sources/fuel_tank_widget.py +103 -0
- tabs/geometry/widgets/powertrain/sources/source_selector_widget.py +35 -0
- tabs/geometry/widgets/wings/__init__.py +7 -0
- tabs/geometry/widgets/wings/wing_cs_widget.py +198 -0
- tabs/geometry/widgets/wings/wing_section_widget.py +298 -0
- tabs/home/__init__.py +3 -0
- tabs/home/home.py +585 -0
- tabs/mission/__init__.py +4 -0
- tabs/mission/mission.py +1124 -0
- tabs/mission/widgets/__init__.py +3 -0
- tabs/mission/widgets/flight_controls_widget.py +186 -0
- tabs/mission/widgets/mission_analysis_widget.py +166 -0
- tabs/mission/widgets/mission_segment_helper.py +358 -0
- tabs/mission/widgets/mission_segment_widget.py +358 -0
- tabs/solve/__init__.py +4 -0
- tabs/solve/plots/__init__.py +12 -0
- tabs/solve/plots/aeroacoustics/__init__.py +6 -0
- tabs/solve/plots/aeroacoustics/plot_2D_noise_contour.py +184 -0
- tabs/solve/plots/aeroacoustics/plot_3D_noise_contour.py +219 -0
- tabs/solve/plots/aeroacoustics/plot_noise_certification_contour.py +115 -0
- tabs/solve/plots/aeroacoustics/plot_noise_level.py +118 -0
- tabs/solve/plots/aerodynamics/__init__.py +10 -0
- tabs/solve/plots/aerodynamics/plot_aerodynamic_coefficients.py +156 -0
- tabs/solve/plots/aerodynamics/plot_aerodynamic_forces.py +155 -0
- tabs/solve/plots/aerodynamics/plot_aircraft_aerodynamics.py +136 -0
- tabs/solve/plots/aerodynamics/plot_drag_components.py +157 -0
- tabs/solve/plots/aerodynamics/plot_lift_distribution.py +107 -0
- tabs/solve/plots/aerodynamics/plot_rotor_conditions.py +255 -0
- tabs/solve/plots/aerodynamics/plot_rotor_disc_performance.py +144 -0
- tabs/solve/plots/aerodynamics/plot_rotor_performance.py +124 -0
- tabs/solve/plots/common/__init__.py +4 -0
- tabs/solve/plots/common/plot_style.py +71 -0
- tabs/solve/plots/common/set_axes.py +55 -0
- tabs/solve/plots/create_plot_widgets.py +34 -0
- tabs/solve/plots/emissions/__init__.py +3 -0
- tabs/solve/plots/emissions/plot_emissions.py +167 -0
- tabs/solve/plots/energy/__init__.py +12 -0
- tabs/solve/plots/energy/plot_altitude_sfc_weight.py +134 -0
- tabs/solve/plots/energy/plot_battery_cell_conditions.py +154 -0
- tabs/solve/plots/energy/plot_battery_degradation.py +157 -0
- tabs/solve/plots/energy/plot_battery_module_C_rates.py +139 -0
- tabs/solve/plots/energy/plot_battery_module_conditions.py +157 -0
- tabs/solve/plots/energy/plot_battery_pack_conditions.py +164 -0
- tabs/solve/plots/energy/plot_battery_ragone_diagram.py +97 -0
- tabs/solve/plots/energy/plot_battery_temperature.py +136 -0
- tabs/solve/plots/energy/plot_electric_propulsor_efficiencies.py +136 -0
- tabs/solve/plots/energy/plot_propulsor_throttles.py +119 -0
- tabs/solve/plots/mission/__init__.py +5 -0
- tabs/solve/plots/mission/plot_aircraft_velocities.py +145 -0
- tabs/solve/plots/mission/plot_flight_conditions.py +236 -0
- tabs/solve/plots/mission/plot_flight_trajectory.py +172 -0
- tabs/solve/plots/stability/__init__.py +5 -0
- tabs/solve/plots/stability/plot_flight_forces_and_moments.py +163 -0
- tabs/solve/plots/stability/plot_lateral_stability.py +151 -0
- tabs/solve/plots/stability/plot_longitudinal_stability.py +173 -0
- tabs/solve/plots/thermal_management/__init__.py +7 -0
- tabs/solve/plots/thermal_management/plot_air_cooled_conditions.py +152 -0
- tabs/solve/plots/thermal_management/plot_cross_flow_heat_exchanger_conditions.py +185 -0
- tabs/solve/plots/thermal_management/plot_reservoir_conditions.py +141 -0
- tabs/solve/plots/thermal_management/plot_thermal_management_performance.py +105 -0
- tabs/solve/plots/thermal_management/plot_wavy_channel_conditions.py +161 -0
- tabs/solve/plots/weights/__init__.py +4 -0
- tabs/solve/plots/weights/plot_load_diagram.py +163 -0
- tabs/solve/plots/weights/plot_weight_breakdown.py +164 -0
- tabs/solve/solve.py +1498 -0
- tabs/style_sheet.py +114 -0
- tabs/tab_widget.py +14 -0
- tabs/visualize_geometry/__init__.py +4 -0
- tabs/visualize_geometry/core_3d_viewer.py +253 -0
- tabs/visualize_geometry/features/__init__.py +10 -0
- tabs/visualize_geometry/features/axes_gizmo.py +173 -0
- tabs/visualize_geometry/features/background.py +161 -0
- tabs/visualize_geometry/features/blueprint.py +685 -0
- tabs/visualize_geometry/features/camera.py +364 -0
- tabs/visualize_geometry/features/drag_aircraft.py +219 -0
- tabs/visualize_geometry/features/grid.py +194 -0
- tabs/visualize_geometry/features/measurement.py +367 -0
- tabs/visualize_geometry/features/screenshot.py +91 -0
- tabs/visualize_geometry/geometry_helper_functions.py +3 -0
- tabs/visualize_geometry/visualize_geometry.py +731 -0
- utilities.py +257 -0
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()
|
tabs/mission/__init__.py
ADDED