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