[Python] Design & build airplanes from your specifications.
attempt component tree
Changed files
creator/base.py
@@ -12,24 +12,23 @@
12
12
13
13
class Aircraft:
14
14
"""This class tracks all sub-components and is fed to the evaluator."""
15
Removed:
def __init__(self, parent, name):
16
Removed:
self.parent = parent
15
Added:
def __init__(self, evaluator, name):
16
Added:
evaluator.tree.update({"aircraft": self})
17
Added:
self.evaluator = evaluator
17
18
self.name = name
18
Removed:
parent.aircrafts.append(name)
19
Added:
self.tree = [] # Nested list of subcomponents
19
20
20
21
21
22
class Component:
22
Removed:
"""Basic component providing coordinates and tools."""
23
Added:
"""Basic component providing coordinates, tools and a component tree."""
23
24
def __init__(self, parent, name):
24
Removed:
self.parent = None
25
Added:
self.tree = [name]
26
Added:
parent.tree.append(self.tree)
25
27
self.name = name
26
28
self.x = np.array([])
27
29
self.z = np.array([])
28
30
self.material = None
29
31
self.mass = float()
30
Removed:
31
Removed:
def __str__(self):
32
Removed:
return self.name
33
32
34
33
def set_material(self, material):
35
34
"""Set the component bulk material."""
creator/wing.py
@@ -24,7 +24,7 @@
24
24
class Airfoil(base.Component):
25
25
"""This class represents a single NACA airfoil.
26
26
27
Removed:
The coordinates are saved as two lists
27
Added:
The coordinates are saved as two np.arrays
28
28
for the x- and z-coordinates. The coordinates start at
29
29
the leading edge, travel over the airfoil's upper edge,
30
30
then loop back to the leading edge via the lower edge.
@@ -141,9 +141,9 @@
141
141
super().__init__(parent, name)
142
142
super().set_material(material)
143
143
self.cap_area = float()
144
Removed:
loc = loc_percent * parent.chord
145
144
# bi.bisect_left: returns index of first value in parent.x > loc
146
145
# This ensures that spar geom intersects with airfoil geom.
146
Added:
loc = loc_percent * parent.chord
147
147
# Spar upper coordinates
148
148
spar_u = bi.bisect_left(parent.x, loc) - 1
149
149
self.x = np.append(self.x, parent.x[spar_u])
evaluator.py
@@ -16,7 +16,8 @@
16
16
"""Performs structural evaluations on aircrafts.
17
17
Individual aircrafts must claim an Evaluator object as parent."""
18
18
def __init__(self):
19
Removed:
self.aircrafts = []
19
Added:
self.tree = {} # Dictionary contains aircrafts and subcomponents
20
Added:
self.results = []
20
21
# # Evaluator knows all geometrical info from evaluated airfoil
21
22
# self.airfoil = self.get_airfoil(aircraft)
22
23
# self.spars = self.get_spars(aircraft)
@@ -32,87 +33,23 @@
32
33
# Inertia terms:
33
34
self.I_ = {'x': 0, 'z': 0, 'xz': 0}
34
35
35
Removed:
def __str__(self):
36
Removed:
return type(self).__name__
37
Removed:
38
Removed:
def update(self):
39
Removed:
"""Get all aircrafts' data."""
40
Removed:
try:
41
Removed:
print(self.aircrafts)
42
Removed:
except:
43
Removed:
print("failed!")
44
Removed:
45
Removed:
# def get_airfoil(self, aircraft):
46
Removed:
# """Get data of spars belonging to aircraft."""
47
Removed:
# try:
48
Removed:
# pass
49
Removed:
# except:
50
Removed:
# pass
51
Removed:
# pass
52
Removed:
53
Removed:
# def get_spars(self, aircraft):
54
Removed:
# """Get data of spars belonging to aircraft."""
55
Removed:
# try:
56
Removed:
# pass
57
Removed:
# except:
58
Removed:
# pass
59
Removed:
# pass
60
Removed:
61
Removed:
# def get_stringers(self, aircraft):
62
Removed:
# """Get data of spars belonging to aircraft."""
63
Removed:
# try:
64
Removed:
# pass
65
Removed:
# except:
66
Removed:
# pass
67
Removed:
# pass
68
Removed:
69
Removed:
def info_print(self, round):
70
Removed:
"""Print all the component's evaluated data to the terminal."""
71
Removed:
name = f' {print(self)} DATA FOR {str(self).upper()} '
72
Removed:
num_of_dashes = len(name)
73
Removed:
print(num_of_dashes * '-')
74
Removed:
print(name)
75
Removed:
for k, v in self.__dict__.items():
76
Removed:
if type(v) != list:
77
Removed:
print('{}:\n'.format(k), v)
78
Removed:
print(num_of_dashes * '-')
79
Removed:
for k, v in self.__dict__.items():
80
Removed:
if type(v) == list:
81
Removed:
print('{}:\n'.format(k), np.around(v, round))
82
Removed:
return None
83
Removed:
84
Removed:
def info_save(self, save_path, number):
85
Removed:
"""Save all the object's coordinates (must be full path)."""
86
Removed:
file_name = 'airfoil_{}_eval.txt'.format(number)
87
Removed:
full_path = os.path.join(save_path, file_name)
88
Removed:
try:
89
Removed:
with open(full_path, 'w') as sys.stdout:
90
Removed:
self.info_print(6)
91
Removed:
# This line required to reset behavior of sys.stdout
92
Removed:
sys.stdout = sys.__stdout__
93
Removed:
print('Successfully wrote to file {}'.format(full_path))
94
Removed:
except IOError:
95
Removed:
print(
96
Removed:
'Unable to write {} to specified directory.\n'.format(
97
Removed:
file_name), 'Was the full path passed to the function?')
98
Removed:
return None
99
Removed:
100
Removed:
# All these functions take integer arguments and return lists.
101
Removed:
102
Removed:
def get_lift_rectangular(self, lift):
103
Removed:
L_prime = [lift / (self.semi_span * 2) for x in range(self.semi_span)]
36
Added:
def get_lift_rectangular(aircraft, lift):
37
Added:
L_prime = [
38
Added:
lift / (aircraft.semi_span * 2) for x in range(aircraft.semi_span)
39
Added:
]
104
40
return L_prime
105
41
106
Removed:
def get_lift_elliptical(self, L_0):
42
Added:
def get_lift_elliptical(aircraft, L_0):
107
43
L_prime = [
108
Removed:
L_0 / (self.semi_span * 2) * sqrt(1 - (y / self.semi_span)**2)
109
Removed:
for y in range(self.semi_span)
44
Added:
L_0 / (aircraft.semi_span * 2) * sqrt(1 -
45
Added:
(y / aircraft.semi_span)**2)
46
Added:
for y in range(aircraft.semi_span)
110
47
]
111
48
return L_prime
112
49
113
Removed:
def get_lift_total(self):
114
Removed:
F_z = [(self.lift_rectangular[_] + self.lift_elliptical[_]) / 2
115
Removed:
for _ in range(len(self.lift_rectangular))]
50
Added:
def get_lift_total(aircraft):
51
Added:
F_z = [(aircraft.lift_rectangular[_] + aircraft.lift_elliptical[_]) / 2
52
Added:
for _ in range(len(aircraft.lift_rectangular))]
116
53
return F_z
117
54
118
55
def get_mass_distribution(self, total_mass):
@@ -131,15 +68,15 @@
131
68
F_x.extend([1.25 * drag for x in semi_span[cutoff:]])
132
69
return F_x
133
70
134
Removed:
def get_centroid(self):
71
Added:
def get_centroid(aircraft):
135
72
"""Return the coordinates of the centroid."""
136
Removed:
stringer_area = self.stringer.area
137
Removed:
cap_area = self.spar.cap_area
73
Added:
stringer_area = aircraft.stringer.area
74
Added:
cap_area = aircraft.spar.cap_area
138
75
139
Removed:
caps_x = [value for spar in self.spar.x for value in spar]
140
Removed:
caps_z = [value for spar in self.spar.z for value in spar]
141
Removed:
stringers_x = self.stringer.x
142
Removed:
stringers_z = self.stringer.z
76
Added:
caps_x = [value for spar in aircraft.spar.x for value in spar]
77
Added:
caps_z = [value for spar in aircraft.spar.z for value in spar]
78
Added:
stringers_x = aircraft.stringer.x
79
Added:
stringers_z = aircraft.stringer.z
143
80
144
81
denominator = float(
145
82
len(caps_x) * cap_area + len(stringers_x) * stringer_area)
@@ -214,23 +151,94 @@
214
151
area * zDist[_] * (I_z * V_z - I_xz * V_x) / denom)
215
152
return z
216
153
217
Removed:
def analysis(self, V_x, V_z):
154
Added:
# def analysis(self, V_x, V_z):
155
Added:
# """Perform all analysis calculations and store in class instance."""
156
Added:
157
Added:
# self.drag = self.get_drag(10)
158
Added:
# self.lift_rectangular = self.get_lift_rectangular(13.7)
159
Added:
# self.lift_elliptical = self.get_lift_elliptical(15)
160
Added:
# self.lift_total = self.get_lift_total()
161
Added:
# self.mass_dist = self.get_mass_distribution(self.mass_total)
162
Added:
# self.centroid = self.get_centroid()
163
Added:
# self.I_['x'] = self.get_inertia_terms()[0]
164
Added:
# self.I_['z'] = self.get_inertia_terms()[1]
165
Added:
# self.I_['xz'] = self.get_inertia_terms()[2]
166
Added:
# spar_dx = self.get_dx(self.spar)
167
Added:
# spar_dz = self.get_dz(self.spar)
168
Added:
# self.spar.dP_x = self.get_dP(spar_dx, spar_dz, V_x, 0,
169
Added:
# self.spar.cap_area)
170
Added:
# self.spar.dP_z = self.get_dP(spar_dx, spar_dz, 0, V_z,
171
Added:
# self.spar.cap_area)
172
Added:
# print("yayyyyy")
173
Added:
# return None
174
Added:
175
Added:
def update(self):
176
Added:
"""Get all aircrafts' data."""
177
Added:
for aircraft in self.tree:
178
Added:
# print(f"For {aircraft.name} tree is:")
179
Added:
# print("For", aircraft.name, " tree is:")
180
Added:
# print(self.tree)
181
Added:
self.results.append(self.analysis(aircraft))
182
Added:
183
Added:
def analysis(self, aircraft):
218
184
"""Perform all analysis calculations and store in class instance."""
219
Removed:
self.drag = self.get_drag(10)
220
Removed:
self.lift_rectangular = self.get_lift_rectangular(13.7)
221
Removed:
self.lift_elliptical = self.get_lift_elliptical(15)
222
Removed:
self.lift_total = self.get_lift_total()
223
Removed:
self.mass_dist = self.get_mass_distribution(self.mass_total)
224
Removed:
self.centroid = self.get_centroid()
225
Removed:
self.I_['x'] = self.get_inertia_terms()[0]
226
Removed:
self.I_['z'] = self.get_inertia_terms()[1]
227
Removed:
self.I_['xz'] = self.get_inertia_terms()[2]
228
Removed:
spar_dx = self.get_dx(self.spar)
229
Removed:
spar_dz = self.get_dz(self.spar)
230
Removed:
self.spar.dP_x = self.get_dP(spar_dx, spar_dz, V_x, 0,
231
Removed:
self.spar.cap_area)
232
Removed:
self.spar.dP_z = self.get_dP(spar_dx, spar_dz, 0, V_z,
233
Removed:
self.spar.cap_area)
185
Added:
186
Added:
results = {
187
Added:
"Lift": self.get_lift_total,
188
Added:
"Drag": self.get_drag,
189
Added:
"Centroid": self.get_centroid
190
Added:
}
191
Added:
192
Added:
# print(f"Analysis results for {aircraft.name}:\n", results)
193
Added:
return (results)
194
Added:
# self.results = self.get_lift_total(aircraft)
195
Added:
196
Added:
# self.drag = self.get_drag(10)
197
Added:
# self.lift_rectangular = self.get_lift_rectangular(13.7)
198
Added:
# self.lift_elliptical = self.get_lift_elliptical(15)
199
Added:
# self.lift_total = self.get_lift_total()
200
Added:
# self.mass_dist = self.get_mass_distribution(self.mass_total)
201
Added:
# self.centroid = self.get_centroid()
202
Added:
# self.I_['x'] = self.get_inertia_terms()[0]
203
Added:
# self.I_['z'] = self.get_inertia_terms()[1]
204
Added:
# self.I_['xz'] = self.get_inertia_terms()[2]
205
Added:
# spar_dx = self.get_dx(self.spar)
206
Added:
# spar_dz = self.get_dz(self.spar)
207
Added:
# self.spar.dP_x = self.get_dP(spar_dx, spar_dz, V_x, 0,
208
Added:
# self.spar.cap_area)
209
Added:
# self.spar.dP_z = self.get_dP(spar_dx, spar_dz, 0, V_z,
210
Added:
# self.spar.cap_area)
211
Added:
return None
212
Added:
213
Added:
def info_print(self, round):
214
Added:
"""Print all the component's evaluated data to the terminal."""
215
Added:
name = f' {print(self)} DATA FOR {str(self).upper()} '
216
Added:
num_of_dashes = len(name)
217
Added:
print(num_of_dashes * '-')
218
Added:
print(name)
219
Added:
for k, v in self.__dict__.items():
220
Added:
if type(v) != list:
221
Added:
print('{}:\n'.format(k), v)
222
Added:
print(num_of_dashes * '-')
223
Added:
for k, v in self.__dict__.items():
224
Added:
if type(v) == list:
225
Added:
print('{}:\n'.format(k), np.around(v, round))
226
Added:
return None
227
Added:
228
Added:
def info_save(self, save_path, number):
229
Added:
"""Save all the object's coordinates (must be full path)."""
230
Added:
file_name = 'airfoil_{}_eval.txt'.format(number)
231
Added:
full_path = os.path.join(save_path, file_name)
232
Added:
try:
233
Added:
with open(full_path, 'w') as sys.stdout:
234
Added:
self.info_print(6)
235
Added:
# This line required to reset behavior of sys.stdout
236
Added:
sys.stdout = sys.__stdout__
237
Added:
print('Successfully wrote to file {}'.format(full_path))
238
Added:
except IOError:
239
Added:
print(
240
Added:
'Unable to write {} to specified directory.\n'.format(
241
Added:
file_name), 'Was the full path passed to the function?')
234
242
return None
235
243
236
244
example_airfoil.py
@@ -43,23 +43,27 @@
43
43
SAVE_PATH = '/home/blendux/Projects/Aircraft_Studio/save'
44
44
45
45
eval = evaluator.Evaluator()
46
Removed:
# Create aircraft instance
46
Added:
47
47
ac = creator.base.Aircraft(eval, "ac")
48
Removed:
# Create airfoil instance
49
48
af = creator.wing.Airfoil(ac, 'af')
50
49
af.add_naca(NACA_NUM)
51
50
# af.info_print(2)
52
51
af.info_save()
53
Removed:
54
Removed:
# Create spar instances
55
Removed:
af.spar1 = creator.wing.Spar(af, 'spar1')
56
Removed:
af.spar2 = creator.wing.Spar(af, 'spar2', 0.57)
52
Added:
spar1 = creator.wing.Spar(af, 'spar1')
53
Added:
spar2 = creator.wing.Spar(af, 'spar2', 0.57)
57
54
# af.spar1.info_print(2)
58
55
# af.spar2.info_print(2)
59
Removed:
af.spar1.info_save()
60
Removed:
af.spar2.info_save()
56
Added:
spar1.info_save()
57
Added:
spar2.info_save()
61
58
59
Added:
ac2 = creator.base.Aircraft(eval, "ac2")
60
Added:
af2 = creator.wing.Airfoil(ac2, 'af2')
61
Added:
af2.add_naca(3412)
62
Added:
spar3 = creator.wing.Spar(af2, 'spar3', 0.23)
63
Added:
spar4 = creator.wing.Spar(af2, 'spar4', 0.67)
64
Added:
62
65
eval.update()
66
Added:
# print("spar2 parent is:", af.parent)
63
67
# eval.info_print(2)
64
68
65
69
# # Create stringer instance
gui.py
@@ -1,81 +1,12 @@
1
Removed:
from tools import creator, evaluator, generator
2
Removed:
# import creator
3
Removed:
# import evaluator
4
Removed:
# import generator
5
Removed:
import tkinter as tk
6
Removed:
import tkinter.ttk as ttk
1
Added:
from tkinter import ttk # Normal Tkinter.* widgets are not themed!
2
Added:
from ttkthemes import ThemedTk
7
3
8
Removed:
from matplotlib.backends.backend_tkagg import (
9
Removed:
FigureCanvasTkAgg, NavigationToolbar2Tk)
10
Removed:
11
Removed:
12
Removed:
class MainWindow(tk.Frame):
13
Removed:
"""Main editor window."""
14
Removed:
15
Removed:
def __init__(self, *args, **kwargs):
16
Removed:
tk.Frame.__init__(self, *args, **kwargs)
17
Removed:
root = tk.Tk()
18
Removed:
root.wm_title('MAE 154B - Airfoil Design, Evaluation, Optimization')
19
Removed:
20
Removed:
# self.button = tk.Button(self, text="Create new window",
21
Removed:
# command=self.create_window)
22
Removed:
# self.button.pack(side="top")
23
Removed:
frame_1 = ttk.Frame(root)
24
Removed:
l_naca, e_naca = new_field(frame_1, 'naca')
25
Removed:
l_chord, e_chord = new_field(frame_1, 'chord')
26
Removed:
l_semi_span, e_semi_span = new_field(frame_1, 'semi_span')
27
Removed:
af = generator.default_airfoil()
28
Removed:
# Graph window
29
Removed:
frame_2 = ttk.Frame(root)
30
Removed:
fig, ax = creator.plot_geom(af, False)
31
Removed:
plot = FigureCanvasTkAgg(fig, frame_2)
32
Removed:
# plot.draw()
33
Removed:
toolbar = NavigationToolbar2Tk(plot, frame_2)
34
Removed:
# toolbar.update()
35
Removed:
36
Removed:
l_naca.grid(row=0, column=0)
37
Removed:
e_naca.grid(row=0, column=1, padx=4)
38
Removed:
# b_naca.grid(row=0, column=2)
39
Removed:
l_chord.grid(row=1, column=0)
40
Removed:
e_chord.grid(row=1, column=1, padx=4)
41
Removed:
l_semi_span.grid(row=2, column=0, padx=4)
42
Removed:
e_semi_span.grid(row=2, column=1, padx=4)
43
Removed:
frame_1.pack(side=tk.LEFT)
44
Removed:
# Graph window
45
Removed:
plot.get_tk_widget().pack(expand=1, fill=tk.BOTH)
46
Removed:
toolbar.pack()
47
Removed:
frame_2.pack(side=tk.LEFT)
48
Removed:
49
Removed:
def create_window(self):
50
Removed:
self.counter += 1
51
Removed:
window = tk.Toplevel(self)
52
Removed:
window.wm_title("Window #%s" % self.counter)
53
Removed:
label = tk.Label(window, text="This is window #%s" % self.counter)
54
Removed:
label.pack(side="top", fill="both", expand=True, padx=100, pady=100)
55
Removed:
56
Removed:
57
Removed:
def new_field(parent, name):
58
Removed:
"""Add a new user input field."""
59
Removed:
60
Removed:
label = ttk.Label(parent, text=name)
61
Removed:
entry = ttk.Entry(parent)
62
Removed:
return label, entry
63
Removed:
64
Removed:
65
Removed:
def set_naca(name):
66
Removed:
naca_num = name.get()
67
Removed:
print(naca_num)
68
Removed:
69
Removed:
70
Removed:
def set_chord(name):
71
Removed:
chord = name.get()
72
Removed:
print(chord)
73
Removed:
74
Removed:
75
Removed:
def set_semi_span(name):
76
Removed:
semi_span = name.get()
77
Removed:
print(semi_span)
78
Removed:
79
Removed:
80
Removed:
# plot.get_tk_widget().pack()
81
Removed:
MainWindow().mainloop()
4
Added:
window = ThemedTk(theme="equilux")
5
Added:
frame1 = ttk.Frame(window)
6
Added:
but1 = ttk.Button(frame1, text="Quit", command=window.destroy).pack()
7
Added:
frame2 = ttk.Frame(window)
8
Added:
but2 = ttk.Button(frame2, text="Quit", command=window.destroy).pack()
9
Added:
but3 = ttk.Button(frame2, text="Quit", command=window.destroy).pack()
10
Added:
frame1.pack()
11
Added:
frame2.pack()
12
Added:
window.mainloop()
gui.py.bkp
@@ -0,0 +1,80 @@
1
Added:
import creator
2
Added:
import evaluator
3
Added:
import generator
4
Added:
import tkinter as tk
5
Added:
import tkinter.ttk as ttk
6
Added:
7
Added:
from matplotlib.backends.backend_tkagg import (
8
Added:
FigureCanvasTkAgg, NavigationToolbar2Tk)
9
Added:
10
Added:
11
Added:
class MainWindow(tk.Frame):
12
Added:
"""Main editor window."""
13
Added:
14
Added:
def __init__(self, *args, **kwargs):
15
Added:
tk.Frame.__init__(self, *args, **kwargs)
16
Added:
root = tk.Tk()
17
Added:
root.wm_title('MAE 154B - Airfoil Design, Evaluation, Optimization')
18
Added:
19
Added:
# self.button = tk.Button(self, text="Create new window",
20
Added:
# command=self.create_window)
21
Added:
# self.button.pack(side="top")
22
Added:
frame_1 = ttk.Frame(root)
23
Added:
l_naca, e_naca = new_field(frame_1, 'naca')
24
Added:
l_chord, e_chord = new_field(frame_1, 'chord')
25
Added:
l_semi_span, e_semi_span = new_field(frame_1, 'semi_span')
26
Added:
# af = creator.base.Aircraft.from_defaults()
27
Added:
# Graph window
28
Added:
frame_2 = ttk.Frame(root)
29
Added:
# fig, ax = creator.plot_geom(af, False)
30
Added:
# plot = FigureCanvasTkAgg(fig, frame_2)
31
Added:
# plot.draw()
32
Added:
# toolbar = NavigationToolbar2Tk(plot, frame_2)
33
Added:
# toolbar.update()
34
Added:
35
Added:
l_naca.grid(row=0, column=0)
36
Added:
e_naca.grid(row=0, column=1, padx=4)
37
Added:
# b_naca.grid(row=0, column=2)
38
Added:
l_chord.grid(row=1, column=0)
39
Added:
e_chord.grid(row=1, column=1, padx=4)
40
Added:
l_semi_span.grid(row=2, column=0, padx=4)
41
Added:
e_semi_span.grid(row=2, column=1, padx=4)
42
Added:
frame_1.pack(side=tk.LEFT)
43
Added:
# Graph window
44
Added:
# plot.get_tk_widget().pack(expand=1, fill=tk.BOTH)
45
Added:
# toolbar.pack()
46
Added:
frame_2.pack(side=tk.LEFT)
47
Added:
48
Added:
def create_window(self):
49
Added:
self.counter += 1
50
Added:
window = tk.Toplevel(self)
51
Added:
window.wm_title("Window #%s" % self.counter)
52
Added:
label = tk.Label(window, text="This is window #%s" % self.counter)
53
Added:
label.pack(side="top", fill="both", expand=True, padx=100, pady=100)
54
Added:
55
Added:
56
Added:
def new_field(parent, name):
57
Added:
"""Add a new user input field."""
58
Added:
59
Added:
label = ttk.Label(parent, text=name)
60
Added:
entry = ttk.Entry(parent)
61
Added:
return label, entry
62
Added:
63
Added:
64
Added:
def set_naca(name):
65
Added:
naca_num = name.get()
66
Added:
print(naca_num)
67
Added:
68
Added:
69
Added:
def set_chord(name):
70
Added:
chord = name.get()
71
Added:
print(chord)
72
Added:
73
Added:
74
Added:
def set_semi_span(name):
75
Added:
semi_span = name.get()
76
Added:
print(semi_span)
77
Added:
78
Added:
79
Added:
# plot.get_tk_widget().pack()
80
Added:
MainWindow().mainloop()