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/style_sheet.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# RCAIDE_GUI/tabs/style_sheet.py
|
|
2
|
+
|
|
3
|
+
# PyQt imports
|
|
4
|
+
from PyQt6.QtCore import Qt
|
|
5
|
+
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QTreeWidget, QTreeWidgetItem, QFrame, QMainWindow, QApplication, QMainWindow, QTabWidget, QFileDialog, QMenu
|
|
6
|
+
|
|
7
|
+
# ------------------------------------------------------------------------------
|
|
8
|
+
# Style Sheet Utilities
|
|
9
|
+
# ------------------------------------------------------------------------------
|
|
10
|
+
def add_line(shape = "HLine", shadow = "Sunken", color = "gray", height = "20"):
|
|
11
|
+
line = QFrame()
|
|
12
|
+
shape_dict = {
|
|
13
|
+
'NoFrame': QFrame.Shape.NoFrame,
|
|
14
|
+
'Box': QFrame.Shape.Box,
|
|
15
|
+
'Panel': QFrame.Shape.Panel,
|
|
16
|
+
'StyledPanel': QFrame.Shape.StyledPanel,
|
|
17
|
+
'HLine': QFrame.Shape.HLine,
|
|
18
|
+
'VLine': QFrame.Shape.VLine
|
|
19
|
+
}
|
|
20
|
+
shadow_dict = {
|
|
21
|
+
'Plain': QFrame.Shadow.Plain,
|
|
22
|
+
'Raised': QFrame.Shadow.Raised,
|
|
23
|
+
'Sunken': QFrame.Shadow.Sunken
|
|
24
|
+
}
|
|
25
|
+
if shape not in shape_dict:
|
|
26
|
+
raise TypeError("Have problem in shape format, shape should be 'NoFrame , Box , Panel , StyledPanel , HLine, VLine'" )
|
|
27
|
+
#shape:NoFrame , Box , Panel , StyledPanel , HLine, VLine
|
|
28
|
+
line.setFrameShape(shape_dict[shape])
|
|
29
|
+
if shadow not in shadow_dict:
|
|
30
|
+
raise TypeError("Have problem in shadow format, shadow should be 'Plain , Raised and Sunken'" )
|
|
31
|
+
#shadow:Plain , Raised and Sunken
|
|
32
|
+
line.setFrameShadow(shadow_dict[shadow])
|
|
33
|
+
try:
|
|
34
|
+
line.setStyleSheet(f"background-color: {color}; height: {height}px;")
|
|
35
|
+
except:
|
|
36
|
+
raise TypeError("Color format:1.Specific color like:red, blue, lightblue. 2.Hex Code like: #FF5733. 3. RGB like: rgb(255, 87, 51)." )
|
|
37
|
+
return line
|
|
38
|
+
|
|
39
|
+
def add_title(text, font_weight="bold", color = "black", font_size="20", Alignment="AlignLeft"):
|
|
40
|
+
title = QLabel(text)
|
|
41
|
+
Alignment_dict={
|
|
42
|
+
"AlignLeft":Qt.AlignmentFlag.AlignLeft,
|
|
43
|
+
"AlignRight": Qt.AlignmentFlag.AlignRight,
|
|
44
|
+
"AlignHCenter":Qt.AlignmentFlag.AlignHCenter,
|
|
45
|
+
"AlignJustify":Qt.AlignmentFlag.AlignJustify,
|
|
46
|
+
"AlignTop":Qt.AlignmentFlag.AlignTop,
|
|
47
|
+
"AlignBottom":Qt.AlignmentFlag.AlignBottom,
|
|
48
|
+
"AlignVCenter":Qt.AlignmentFlag.AlignCenter,
|
|
49
|
+
"AlignBaseline":Qt.AlignmentFlag.AlignVCenter
|
|
50
|
+
}
|
|
51
|
+
if Alignment not in Alignment_dict:
|
|
52
|
+
raise TypeError("Have problem in Alignment format, Alignment should be 'AlignLeft, AlignRight, AlignHCenter, AlignJustify, AlignTop, AlignBottom, AlignVCenter, AlignBaseline'" )
|
|
53
|
+
else:
|
|
54
|
+
title.setAlignment(Alignment_dict[Alignment])
|
|
55
|
+
try:
|
|
56
|
+
title.setStyleSheet(f"font-size: {font_size}px; font-weight: {font_weight}; color: {color};")
|
|
57
|
+
except:
|
|
58
|
+
raise TypeError("font-size format:integer, font-weight format:'bold',Makes the text bold, Color format:1.Specific color like:red, blue, lightblue. 2.Hex Code like: #FF5733. 3. RGB like: rgb(255, 87, 51)." )
|
|
59
|
+
return title
|
|
60
|
+
|
|
61
|
+
def add_subtitle(text, font_weight="bold", color = "black", font_size="15", Alignment="AlignLeft"):
|
|
62
|
+
subtitle = QLabel(text)
|
|
63
|
+
Alignment_dict={
|
|
64
|
+
"AlignLeft":Qt.AlignmentFlag.AlignLeft,
|
|
65
|
+
"AlignRight": Qt.AlignmentFlag.AlignRight,
|
|
66
|
+
"AlignHCenter":Qt.AlignmentFlag.AlignHCenter,
|
|
67
|
+
"AlignJustify":Qt.AlignmentFlag.AlignJustify,
|
|
68
|
+
"AlignTop":Qt.AlignmentFlag.AlignTop,
|
|
69
|
+
"AlignBottom":Qt.AlignmentFlag.AlignBottom,
|
|
70
|
+
"AlignVCenter":Qt.AlignmentFlag.AlignCenter,
|
|
71
|
+
"AlignBaseline":Qt.AlignmentFlag.AlignVCenter
|
|
72
|
+
}
|
|
73
|
+
if Alignment not in Alignment_dict:
|
|
74
|
+
raise TypeError("Have problem in Alignment format, Alignment should be 'AlignLeft, AlignRight, AlignHCenter, AlignJustify, AlignTop, AlignBottom, AlignVCenter, AlignBaseline'" )
|
|
75
|
+
else:
|
|
76
|
+
subtitle.setAlignment(Alignment_dict[Alignment])
|
|
77
|
+
try:
|
|
78
|
+
subtitle.setStyleSheet(f"font-size: {font_size}px; font-weight: {font_weight}; color: {color};")
|
|
79
|
+
except:
|
|
80
|
+
raise TypeError("font-size format:integer, font-weight format:'bold',Makes the text bold, Color format:1.Specific color like:red, blue, lightblue. 2.Hex Code like: #FF5733. 3. RGB like: rgb(255, 87, 51)." )
|
|
81
|
+
return subtitle
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
#This is a function help build header.It has three inputs. The first input is from QMainWindow(), usually it is "self".
|
|
85
|
+
#The second input is the name for this window. The third input is menu_list. like["File","Documentation"].
|
|
86
|
+
def add_header(window, window_name, menu_list = None):
|
|
87
|
+
window.setWindowTitle(window_name)
|
|
88
|
+
menubar = window.menuBar()
|
|
89
|
+
if menubar is None:
|
|
90
|
+
return
|
|
91
|
+
if menu_list:
|
|
92
|
+
for i in menu_list:
|
|
93
|
+
menubar.addMenu(i)
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
#After use this function,programmer can find exact menu.
|
|
97
|
+
def find_menu_variable(window, target_menu):
|
|
98
|
+
menubar = window.menuBar()
|
|
99
|
+
if menubar is None:
|
|
100
|
+
print("No menu bar found.")
|
|
101
|
+
return
|
|
102
|
+
menus = [child for child in menubar.children() if isinstance(child, QMenu)]
|
|
103
|
+
for menu in menus:
|
|
104
|
+
if menu.title() == target_menu:
|
|
105
|
+
return menu
|
|
106
|
+
|
|
107
|
+
if __name__ == '__main__':
|
|
108
|
+
app = QApplication([])
|
|
109
|
+
window = QMainWindow()
|
|
110
|
+
add_header(window, "My Application", menu_list=["File", "Edit", "View"])
|
|
111
|
+
file_mune = find_menu_variable(window,"File")
|
|
112
|
+
file_mune.addAction("Quit")
|
|
113
|
+
window.show()
|
|
114
|
+
app.exec()
|
tabs/tab_widget.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# RCAIDE_GUI/tabs/tab_widget.py
|
|
2
|
+
|
|
3
|
+
# PyQt imports
|
|
4
|
+
from PyQt6.QtWidgets import QWidget
|
|
5
|
+
|
|
6
|
+
# ------------------------------------------------------------------------------
|
|
7
|
+
# Tab Widget
|
|
8
|
+
# ------------------------------------------------------------------------------
|
|
9
|
+
class TabWidget(QWidget):
|
|
10
|
+
def update_layout(self):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
def load_from_values(self):
|
|
14
|
+
pass
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# RCAIDE_GUI/tabs/visualize_geometry/core_3d_viewer.py
|
|
2
|
+
|
|
3
|
+
# RCAIDE imports
|
|
4
|
+
import RCAIDE
|
|
5
|
+
from RCAIDE.Framework.Core import Units
|
|
6
|
+
from RCAIDE.Library.Components.Airfoils import Airfoil
|
|
7
|
+
from RCAIDE.Library.Plots.Geometry.generate_3d_wing_points import *
|
|
8
|
+
from RCAIDE.Library.Plots.Geometry.generate_3d_fuselage_points import *
|
|
9
|
+
from RCAIDE.Library.Plots.Geometry.plot_3d_rotor import generate_3d_blade_points
|
|
10
|
+
from RCAIDE.Library.Plots.Geometry.generate_3d_nacelle_points import *
|
|
11
|
+
from RCAIDE.Library.Methods.Geometry.Planform import fuselage_planform, wing_planform
|
|
12
|
+
|
|
13
|
+
# RCAIDE-GUI imports
|
|
14
|
+
from tabs import TabWidget
|
|
15
|
+
from vtkmodules.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
|
|
16
|
+
from tabs.visualize_geometry import geometry_helper_functions
|
|
17
|
+
|
|
18
|
+
# PyQt imports
|
|
19
|
+
from PyQt6.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QTreeWidget, QPushButton, QTreeWidgetItem, QHeaderView, QLabel, QToolBar, QColorDialog, QSpacerItem, QSizePolicy, QFrame, QLineEdit
|
|
20
|
+
from PyQt6.QtCore import Qt
|
|
21
|
+
from PyQt6.QtGui import QIcon
|
|
22
|
+
|
|
23
|
+
# python imports
|
|
24
|
+
import matplotlib.colors as mcolors
|
|
25
|
+
import vtk
|
|
26
|
+
import rcaide_io
|
|
27
|
+
import os
|
|
28
|
+
from copy import deepcopy
|
|
29
|
+
|
|
30
|
+
# ------------------------------------------------------------------------------
|
|
31
|
+
# make_object
|
|
32
|
+
# ------------------------------------------------------------------------------
|
|
33
|
+
def make_object(renderer, actor_group, GEOM, rgb_color, opacity):
|
|
34
|
+
|
|
35
|
+
actor = geometry_helper_functions.generate_vtk_object(GEOM.PTS)
|
|
36
|
+
|
|
37
|
+
# Set color of fuselage
|
|
38
|
+
mapper = actor.GetMapper()
|
|
39
|
+
mapper.ScalarVisibilityOff()
|
|
40
|
+
prop = actor.GetProperty()
|
|
41
|
+
prop.SetColor(rgb_color[0] * 1.2, rgb_color[1] * 1.2, rgb_color[2] * 1.2) # slightly brighter
|
|
42
|
+
prop.SetDiffuse(0.8)
|
|
43
|
+
prop.SetAmbient(0.4) # adds base light even in dark areas
|
|
44
|
+
prop.SetSpecular(0.3) # gives a soft highlight
|
|
45
|
+
prop.SetSpecularPower(20)
|
|
46
|
+
prop.SetOpacity(opacity)
|
|
47
|
+
renderer.AddActor(actor)
|
|
48
|
+
actor_group.append(actor)
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
def make_actuator_disc(renderer, inner_radius, outer_radius, origin, rot_x,rot_y,rot_z, rgb_color, opacity):
|
|
52
|
+
|
|
53
|
+
disk_source = vtk.vtkDiskSource()
|
|
54
|
+
disk_source.SetInnerRadius(inner_radius)
|
|
55
|
+
disk_source.SetOuterRadius(outer_radius)
|
|
56
|
+
disk_source.SetRadialResolution(50)
|
|
57
|
+
disk_source.SetCircumferentialResolution(50)
|
|
58
|
+
|
|
59
|
+
transform = vtk.vtkTransform()
|
|
60
|
+
transform.RotateX(rot_x/Units.degrees)
|
|
61
|
+
transform.RotateY(rot_y/Units.degrees)
|
|
62
|
+
transform.RotateZ(rot_z/Units.degrees)
|
|
63
|
+
transformFilter = vtk.vtkTransformPolyDataFilter()
|
|
64
|
+
transformFilter.SetTransform(transform)
|
|
65
|
+
transformFilter.SetInputConnection(disk_source.GetOutputPort())
|
|
66
|
+
|
|
67
|
+
mapper = vtk.vtkPolyDataMapper()
|
|
68
|
+
mapper.SetInputConnection(transformFilter.GetOutputPort())
|
|
69
|
+
|
|
70
|
+
actor = vtk.vtkActor()
|
|
71
|
+
actor.SetMapper(mapper)
|
|
72
|
+
actor.GetProperty().SetColor(rgb_color[0], rgb_color[1], rgb_color[2])
|
|
73
|
+
actor.GetProperty().SetDiffuse(1.0)
|
|
74
|
+
actor.GetProperty().SetSpecular(0.0)
|
|
75
|
+
actor.GetProperty().SetOpacity(opacity)
|
|
76
|
+
actor.SetPosition( origin[0][0], origin[0][1], origin[0][2])
|
|
77
|
+
renderer.AddActor(actor)
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
class Core3DViewer(QWidget):
|
|
81
|
+
def __init__(self, parent=None):
|
|
82
|
+
super().__init__(parent)
|
|
83
|
+
|
|
84
|
+
# Setup Layout
|
|
85
|
+
layout = QVBoxLayout(self)
|
|
86
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
87
|
+
|
|
88
|
+
# Setup VTK Window
|
|
89
|
+
self.vtkWidget = QVTKRenderWindowInteractor(self)
|
|
90
|
+
layout.addWidget(self.vtkWidget)
|
|
91
|
+
|
|
92
|
+
self.renderer = vtk.vtkRenderer()
|
|
93
|
+
self.vtkWidget.GetRenderWindow().AddRenderer(self.renderer)
|
|
94
|
+
self.render_window_interactor = self.vtkWidget.GetRenderWindow().GetInteractor()
|
|
95
|
+
style = vtk.vtkInteractorStyleTrackballCamera()
|
|
96
|
+
self.render_window_interactor.SetInteractorStyle(style)
|
|
97
|
+
self.wing_actors = []
|
|
98
|
+
self.fuselage_actors = []
|
|
99
|
+
self.boom_actors = []
|
|
100
|
+
self.nacelle_actors = []
|
|
101
|
+
self.rotor_actors = []
|
|
102
|
+
self.fuel_tank_actors = []
|
|
103
|
+
self.part_actors = [] # General list if needed
|
|
104
|
+
|
|
105
|
+
self.render_window_interactor.Initialize()
|
|
106
|
+
|
|
107
|
+
def clear_scene(self):
|
|
108
|
+
"""Wipes the 3D canvas clean before drawing a new airplane."""
|
|
109
|
+
self.renderer.RemoveAllViewProps()
|
|
110
|
+
self.wing_actors.clear()
|
|
111
|
+
self.fuselage_actors.clear()
|
|
112
|
+
self.boom_actors.clear()
|
|
113
|
+
self.nacelle_actors.clear()
|
|
114
|
+
self.rotor_actors.clear()
|
|
115
|
+
self.fuel_tank_actors.clear()
|
|
116
|
+
self.part_actors.clear()
|
|
117
|
+
|
|
118
|
+
def run_solve(self):
|
|
119
|
+
"""Calculates and draws the geometry based on rcaide_io.vehicle"""
|
|
120
|
+
|
|
121
|
+
self.clear_scene()
|
|
122
|
+
|
|
123
|
+
wing_color = 'grey'
|
|
124
|
+
fuselage_color = 'grey'
|
|
125
|
+
nacelle_color = 'grey'
|
|
126
|
+
boom_color = 'grey'
|
|
127
|
+
rotor_color = 'black'
|
|
128
|
+
wing_opacity = 0.5
|
|
129
|
+
fuselage_opacity = 0.5
|
|
130
|
+
nacelle_opacity = 1.0
|
|
131
|
+
rotor_opacity = 0.6
|
|
132
|
+
number_of_airfoil_points = 101
|
|
133
|
+
tessellation = 96
|
|
134
|
+
boom_opacity = 1.0
|
|
135
|
+
camera_eye_x = -1
|
|
136
|
+
camera_eye_y = -1
|
|
137
|
+
camera_eye_z = 0.35
|
|
138
|
+
|
|
139
|
+
wing_rgb_color = mcolors.to_rgb(wing_color)
|
|
140
|
+
fuselage_rgb_color = mcolors.to_rgb(fuselage_color)
|
|
141
|
+
nacelle_rgb_color = mcolors.to_rgb(nacelle_color)
|
|
142
|
+
rotor_rgb_color = mcolors.to_rgb(rotor_color)
|
|
143
|
+
boom_rgb_color = mcolors.to_rgb(boom_color)
|
|
144
|
+
|
|
145
|
+
if not rcaide_io.vehicle:
|
|
146
|
+
return # Don't try to draw if there's no vehicle
|
|
147
|
+
|
|
148
|
+
geometry = deepcopy(rcaide_io.vehicle)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
for wing in geometry.wings:
|
|
152
|
+
try: wing_planform(wing)
|
|
153
|
+
except: pass # Bypass if not fully defined yet in GUI
|
|
154
|
+
|
|
155
|
+
for fuselage in geometry.fuselages:
|
|
156
|
+
try:
|
|
157
|
+
fuselage_planform(fuselage)
|
|
158
|
+
except: pass
|
|
159
|
+
|
|
160
|
+
for wing in geometry.wings:
|
|
161
|
+
n_segments = len(wing.segments)
|
|
162
|
+
dim = n_segments if n_segments > 0 else 2
|
|
163
|
+
try:
|
|
164
|
+
GEOM = generate_3d_wing_points(wing, number_of_airfoil_points, dim)
|
|
165
|
+
make_object(self.renderer, self.wing_actors, GEOM, wing_rgb_color, wing_opacity)
|
|
166
|
+
if wing.yz_plane_symmetric:
|
|
167
|
+
GEOM.PTS[:, :, 0] = -GEOM.PTS[:, :, 0]
|
|
168
|
+
make_object(self.renderer, self.wing_actors,GEOM,wing_rgb_color,wing_opacity)
|
|
169
|
+
if wing.xz_plane_symmetric:
|
|
170
|
+
GEOM.PTS[:, :, 1] = -GEOM.PTS[:, :, 1]
|
|
171
|
+
make_object(self.renderer,self.wing_actors, GEOM,wing_rgb_color,wing_opacity)
|
|
172
|
+
if wing.xy_plane_symmetric:
|
|
173
|
+
GEOM.PTS[:, :, 2] = -GEOM.PTS[:, :, 2]
|
|
174
|
+
make_object(self.renderer,self.wing_actors, GEOM,wing_rgb_color,wing_opacity)
|
|
175
|
+
except Exception as e:
|
|
176
|
+
print(f"Skipping a wing due to incomplete data: {e}")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
for fuselage in geometry.fuselages:
|
|
180
|
+
try:
|
|
181
|
+
GEOM = generate_3d_fuselage_points(fuselage, tessellation)
|
|
182
|
+
make_object(self.renderer, self.fuselage_actors,GEOM, fuselage_rgb_color,fuselage_opacity)
|
|
183
|
+
except: pass
|
|
184
|
+
|
|
185
|
+
for boom in geometry.booms:
|
|
186
|
+
try:
|
|
187
|
+
GEOM = generate_3d_fuselage_points(boom, tessellation)
|
|
188
|
+
make_object(self.renderer, self.boom_actors, GEOM, boom_rgb_color,boom_opacity)
|
|
189
|
+
except: pass
|
|
190
|
+
|
|
191
|
+
for nacelle in geometry.nacelles:
|
|
192
|
+
try:
|
|
193
|
+
GEOM = generate_3d_BOR_nacelle_points(nacelle, tessellation=tessellation, number_of_airfoil_points=number_of_airfoil_points)
|
|
194
|
+
make_object(self.renderer, self.nacelle_actors, GEOM, nacelle_rgb_color, nacelle_opacity)
|
|
195
|
+
except: pass
|
|
196
|
+
|
|
197
|
+
for network in geometry.networks:
|
|
198
|
+
for propulsor in network.propulsors:
|
|
199
|
+
if 'nacelle' in propulsor and propulsor.nacelle != None:
|
|
200
|
+
try:
|
|
201
|
+
if type(propulsor.nacelle) == RCAIDE.Library.Components.Nacelles.Stack_Nacelle:
|
|
202
|
+
GEOM = generate_3d_stack_nacelle_points(propulsor.nacelle,tessellation = tessellation,number_of_airfoil_points = number_of_airfoil_points)
|
|
203
|
+
elif type(propulsor.nacelle) == RCAIDE.Library.Components.Nacelles.Body_of_Revolution_Nacelle:
|
|
204
|
+
GEOM = generate_3d_BOR_nacelle_points(propulsor.nacelle,tessellation = tessellation,number_of_airfoil_points = number_of_airfoil_points)
|
|
205
|
+
else:
|
|
206
|
+
GEOM= generate_3d_basic_nacelle_points(propulsor.nacelle,tessellation = tessellation,number_of_airfoil_points = number_of_airfoil_points)
|
|
207
|
+
make_object(self.renderer,self.nacelle_actors, GEOM, nacelle_rgb_color,nacelle_opacity)
|
|
208
|
+
except: pass
|
|
209
|
+
|
|
210
|
+
if 'rotor' in propulsor:
|
|
211
|
+
try:
|
|
212
|
+
rot = propulsor.rotor
|
|
213
|
+
rot_x = rot.orientation_euler_angles[0]
|
|
214
|
+
rot_y = rot.orientation_euler_angles[1]
|
|
215
|
+
rot_z = rot.orientation_euler_angles[2]
|
|
216
|
+
num_B = int(rot.number_of_blades)
|
|
217
|
+
if rot.radius_distribution is None:
|
|
218
|
+
make_actuator_disc(self.renderer, rot.hub_radius, rot.tip_radius, rot.origin, rot_x,rot_y,rot_z, rotor_rgb_color,rotor_opacity)
|
|
219
|
+
else:
|
|
220
|
+
dim = len(rot.radius_distribution)
|
|
221
|
+
for i in range(num_B):
|
|
222
|
+
GEOM = generate_3d_blade_points(rot,number_of_airfoil_points,dim,i)
|
|
223
|
+
make_object(self.renderer,self.rotor_actors, GEOM, rotor_rgb_color,rotor_opacity)
|
|
224
|
+
except: pass
|
|
225
|
+
|
|
226
|
+
if 'propeller' in propulsor:
|
|
227
|
+
try:
|
|
228
|
+
prop = propulsor.propeller
|
|
229
|
+
rot_x = prop.orientation_euler_angles[0]
|
|
230
|
+
rot_y = np.pi / 2 + prop.orientation_euler_angles[1]
|
|
231
|
+
rot_z = prop.orientation_euler_angles[2]
|
|
232
|
+
num_B = int(prop.number_of_blades)
|
|
233
|
+
if prop.radius_distribution is None:
|
|
234
|
+
make_actuator_disc(self.renderer, prop.hub_radius, prop.tip_radius, prop.origin, rot_x,rot_y,rot_z,rotor_rgb_color,rotor_opacity)
|
|
235
|
+
else:
|
|
236
|
+
dim = len(prop.radius_distribution)
|
|
237
|
+
for i in range(num_B):
|
|
238
|
+
GEOM = generate_3d_blade_points(prop,number_of_airfoil_points,dim,i)
|
|
239
|
+
make_object(self.renderer,self.rotor_actors, GEOM, rotor_rgb_color,rotor_opacity)
|
|
240
|
+
except: pass
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
# Set camera and background
|
|
244
|
+
camera = vtk.vtkCamera()
|
|
245
|
+
camera.SetPosition(camera_eye_x, camera_eye_y, camera_eye_z)
|
|
246
|
+
camera.SetFocalPoint(0, 0, 0)
|
|
247
|
+
camera.SetViewUp(0, 0, 1)
|
|
248
|
+
|
|
249
|
+
self.renderer.SetActiveCamera(camera)
|
|
250
|
+
self.renderer.ResetCamera()
|
|
251
|
+
self.renderer.SetBackground(0.1, 0.1, 0.15) # A nice dark theme background
|
|
252
|
+
|
|
253
|
+
self.vtkWidget.GetRenderWindow().Render()
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# RCAIDE_GUI/tabs/visualize_geometry/features/__init__.py
|
|
2
|
+
|
|
3
|
+
from .axes_gizmo import *
|
|
4
|
+
from .background import *
|
|
5
|
+
from .blueprint import *
|
|
6
|
+
from .camera import *
|
|
7
|
+
from .drag_aircraft import *
|
|
8
|
+
from .grid import *
|
|
9
|
+
from .measurement import *
|
|
10
|
+
from .screenshot import *
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# RCAIDE_GUI/tabs/visualize_geometry/features/axes_gizmo.py
|
|
2
|
+
|
|
3
|
+
# Created: M Clarke, LEADS, 2024
|
|
4
|
+
# RCAIDE-GUI imports
|
|
5
|
+
# RCAIDE-GUI Imports
|
|
6
|
+
from tabs.visualize_geometry.visualize_geometry import VisualizeGeometryWidget
|
|
7
|
+
|
|
8
|
+
# Python imports
|
|
9
|
+
import vtkmodules.all as vtk
|
|
10
|
+
# ------------------------------------------------------------------------------
|
|
11
|
+
# Gizmo functions
|
|
12
|
+
# ------------------------------------------------------------------------------
|
|
13
|
+
def _bind_gizmo_renderer(widget, renderer):
|
|
14
|
+
# VTK uses different method names depending on the version
|
|
15
|
+
try:
|
|
16
|
+
widget.SetCurrentRenderer(renderer)
|
|
17
|
+
except Exception:
|
|
18
|
+
try:
|
|
19
|
+
widget.SetDefaultRenderer(renderer)
|
|
20
|
+
except Exception:
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
def add_orientation_gizmo(self):
|
|
24
|
+
# Need renderer + render window + interactor
|
|
25
|
+
r = getattr(self, "renderer", None)
|
|
26
|
+
if r is None:
|
|
27
|
+
return
|
|
28
|
+
try:
|
|
29
|
+
rw = self.vtkWidget.GetRenderWindow()
|
|
30
|
+
except Exception:
|
|
31
|
+
return
|
|
32
|
+
if rw is None:
|
|
33
|
+
return
|
|
34
|
+
iren = rw.GetInteractor()
|
|
35
|
+
if iren is None:
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
# Reuse existing widget so we don’t create duplicates
|
|
39
|
+
widget = getattr(self, "_axes_widget", None) or getattr(iren, "_axes_widget", None)
|
|
40
|
+
|
|
41
|
+
if widget is None:
|
|
42
|
+
# Axes + label styling
|
|
43
|
+
axes = vtk.vtkAxesActor()
|
|
44
|
+
for cap in (
|
|
45
|
+
axes.GetXAxisCaptionActor2D(),
|
|
46
|
+
axes.GetYAxisCaptionActor2D(),
|
|
47
|
+
axes.GetZAxisCaptionActor2D(),
|
|
48
|
+
):
|
|
49
|
+
tp = cap.GetTextActor().GetTextProperty()
|
|
50
|
+
tp.SetFontSize(14)
|
|
51
|
+
tp.SetColor(1.0, 1.0, 1.0)
|
|
52
|
+
|
|
53
|
+
# Light wireframe border around the gizmo
|
|
54
|
+
border = vtk.vtkCubeSource()
|
|
55
|
+
border.SetXLength(1.2)
|
|
56
|
+
border.SetYLength(1.2)
|
|
57
|
+
border.SetZLength(1.2)
|
|
58
|
+
|
|
59
|
+
border_mapper = vtk.vtkPolyDataMapper()
|
|
60
|
+
border_mapper.SetInputConnection(border.GetOutputPort())
|
|
61
|
+
|
|
62
|
+
border_actor = vtk.vtkActor()
|
|
63
|
+
border_actor.SetMapper(border_mapper)
|
|
64
|
+
p = border_actor.GetProperty()
|
|
65
|
+
p.SetColor(0.8, 0.8, 0.8)
|
|
66
|
+
p.SetOpacity(0.1)
|
|
67
|
+
p.SetRepresentationToWireframe()
|
|
68
|
+
p.LightingOff()
|
|
69
|
+
|
|
70
|
+
# Combine axes + border into one marker
|
|
71
|
+
assembly = vtk.vtkPropAssembly()
|
|
72
|
+
assembly.AddPart(border_actor)
|
|
73
|
+
assembly.AddPart(axes)
|
|
74
|
+
|
|
75
|
+
# Small overlay widget in the corner
|
|
76
|
+
widget = vtk.vtkOrientationMarkerWidget()
|
|
77
|
+
widget.SetOrientationMarker(assembly)
|
|
78
|
+
widget.SetViewport(0.0, 0.0, 0.18, 0.28)
|
|
79
|
+
widget.SetInteractor(iren)
|
|
80
|
+
|
|
81
|
+
# Attach to the current renderer (important if renderer was rebuilt)
|
|
82
|
+
_bind_gizmo_renderer(widget, r)
|
|
83
|
+
|
|
84
|
+
# Some VTK builds need this before enabling the widget
|
|
85
|
+
try:
|
|
86
|
+
if not iren.GetInitialized():
|
|
87
|
+
iren.Initialize()
|
|
88
|
+
except Exception:
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
# Keep it visible but not clickable
|
|
92
|
+
widget.EnabledOn()
|
|
93
|
+
widget.InteractiveOff()
|
|
94
|
+
|
|
95
|
+
# Store in both places so it survives tab switches
|
|
96
|
+
self._axes_widget = widget
|
|
97
|
+
iren._axes_widget = widget
|
|
98
|
+
|
|
99
|
+
# Redraw now
|
|
100
|
+
try:
|
|
101
|
+
rw.Render()
|
|
102
|
+
except Exception:
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
def _rebind_axes_gizmo_on_show(self):
|
|
106
|
+
# Tab switching can recreate the renderer/interactor, so we reattach the gizmo
|
|
107
|
+
r = getattr(self, "renderer", None)
|
|
108
|
+
if r is None:
|
|
109
|
+
return
|
|
110
|
+
try:
|
|
111
|
+
rw = self.vtkWidget.GetRenderWindow()
|
|
112
|
+
except Exception:
|
|
113
|
+
return
|
|
114
|
+
if rw is None:
|
|
115
|
+
return
|
|
116
|
+
iren = rw.GetInteractor()
|
|
117
|
+
if iren is None:
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
# Find existing gizmo widget
|
|
121
|
+
w = getattr(self, "_axes_widget", None) or getattr(iren, "_axes_widget", None)
|
|
122
|
+
if w is None:
|
|
123
|
+
return
|
|
124
|
+
|
|
125
|
+
# Reattach to the current interactor/renderer
|
|
126
|
+
try:
|
|
127
|
+
w.SetInteractor(iren)
|
|
128
|
+
except Exception:
|
|
129
|
+
pass
|
|
130
|
+
_bind_gizmo_renderer(w, r)
|
|
131
|
+
|
|
132
|
+
# Turn it back on and redraw
|
|
133
|
+
try:
|
|
134
|
+
w.EnabledOn()
|
|
135
|
+
w.InteractiveOff()
|
|
136
|
+
rw.Render()
|
|
137
|
+
except Exception:
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
# Activate Patch
|
|
141
|
+
def _patch_run_solve_for_axes_gizmo():
|
|
142
|
+
# After run_solve builds the scene, make sure the gizmo exists
|
|
143
|
+
if getattr(VisualizeGeometryWidget, "_axes_gizmo_patched", False):
|
|
144
|
+
return
|
|
145
|
+
base = VisualizeGeometryWidget.run_solve
|
|
146
|
+
|
|
147
|
+
def wrapped(self, *args, **kwargs):
|
|
148
|
+
base(self, *args, **kwargs)
|
|
149
|
+
add_orientation_gizmo(self)
|
|
150
|
+
|
|
151
|
+
VisualizeGeometryWidget.run_solve = wrapped
|
|
152
|
+
VisualizeGeometryWidget._axes_gizmo_patched = True
|
|
153
|
+
|
|
154
|
+
# Activate Patch
|
|
155
|
+
def _patch_showEvent_for_axes_gizmo_rebind():
|
|
156
|
+
# When the widget becomes visible again, rebind the gizmo
|
|
157
|
+
if getattr(VisualizeGeometryWidget, "_axes_showevent_patched", False):
|
|
158
|
+
return
|
|
159
|
+
old = getattr(VisualizeGeometryWidget, "showEvent", None)
|
|
160
|
+
|
|
161
|
+
def wrapped(self, event):
|
|
162
|
+
if old is not None:
|
|
163
|
+
try:
|
|
164
|
+
old(self, event)
|
|
165
|
+
except Exception:
|
|
166
|
+
pass
|
|
167
|
+
_rebind_axes_gizmo_on_show(self)
|
|
168
|
+
|
|
169
|
+
VisualizeGeometryWidget.showEvent = wrapped
|
|
170
|
+
VisualizeGeometryWidget._axes_showevent_patched = True
|
|
171
|
+
|
|
172
|
+
_patch_run_solve_for_axes_gizmo()
|
|
173
|
+
_patch_showEvent_for_axes_gizmo_rebind()
|