[Python] Design & build airplanes from your specifications.
Folder structure
Changed files
__init__.py
creator/__init__.py
@@ -1,1 +1,8 @@
1
Removed:
__all__ = ['base', 'fuselage', 'propulsion', 'wing']
1
Added:
from . import base
2
Added:
from . import fuselage
3
Added:
from . import propulsion
4
Added:
from . import wing
5
Added:
# import creator.base
6
Added:
# import creator.fuselage
7
Added:
# import creator.propulsion
8
Added:
# import creator.wing
creator/base.py
@@ -9,6 +9,11 @@
9
9
format='%(asctime)s - %(levelname)s - %(message)s')
10
10
11
11
12
Added:
class Aircraft:
13
Added:
"""This class tracks all sub-components and is fed to the evaluator."""
14
Added:
pass
15
Added:
16
Added:
12
17
class Component:
13
18
"""Basic component providing coordinates and tools."""
14
19
@@ -52,8 +57,3 @@
52
57
print(f'Unable to write {file_name} to specified directory.\n',
53
58
'Was the full path passed to the function?')
54
59
return None
55
Removed:
56
Removed:
57
Removed:
class Aircraft:
58
Removed:
"""This class tracks all sub-components and is fed to the evaluator."""
59
Removed:
pass
creator/wing.py
@@ -316,11 +316,3 @@
316
316
bbox_transform=plt.gcf().transFigure)
317
317
plt.show()
318
318
return fig, ax
319
Removed:
320
Removed:
321
Removed:
def main():
322
Removed:
return None
323
Removed:
324
Removed:
325
Removed:
if __name__ == '__main__':
326
Removed:
main()
evaluator.py
@@ -0,0 +1,301 @@
1
Added:
"""
2
Added:
The evaluator.py module contains a single Evaluator class,
3
Added:
which knows all the attributes of a specified Aircraft instance,
4
Added:
and contains functions to analyse the airfoil's geometrical
5
Added:
& structural properties.
6
Added:
"""
7
Added:
8
Added:
import sys
9
Added:
import os.path
10
Added:
import numpy as np
11
Added:
from math import sqrt
12
Added:
import matplotlib.pyplot as plt
13
Added:
14
Added:
15
Added:
class Evaluator:
16
Added:
"""Performs structural evaluations for the airfoil passed as argument."""
17
Added:
def __init__(self, aircraft):
18
Added:
# Evaluator knows all geometrical info from evaluated airfoil
19
Added:
self.airfoil = self.get_airfoil(aircraft)
20
Added:
self.spars = self.get_spars(aircraft)
21
Added:
self.stringers = self.get_stringers(aircraft)
22
Added:
# Lift
23
Added:
self.lift_rectangular = []
24
Added:
self.lift_elliptical = []
25
Added:
self.lift_total = []
26
Added:
# Drag
27
Added:
self.drag = []
28
Added:
# centroid
29
Added:
self.centroid = []
30
Added:
# Inertia terms:
31
Added:
self.I_ = {'x': 0, 'z': 0, 'xz': 0}
32
Added:
33
Added:
def __str__(self):
34
Added:
return type(self).__name__
35
Added:
36
Added:
def get_airfoil(self, aircraft):
37
Added:
"""Get data of spars belonging to aircraft."""
38
Added:
try:
39
Added:
pass
40
Added:
except:
41
Added:
pass
42
Added:
pass
43
Added:
44
Added:
def get_spars(self, aircraft):
45
Added:
"""Get data of spars belonging to aircraft."""
46
Added:
try:
47
Added:
pass
48
Added:
except:
49
Added:
pass
50
Added:
pass
51
Added:
52
Added:
def get_stringers(self, aircraft):
53
Added:
"""Get data of spars belonging to aircraft."""
54
Added:
try:
55
Added:
pass
56
Added:
except:
57
Added:
pass
58
Added:
pass
59
Added:
60
Added:
def info_print(self, round):
61
Added:
"""Print all the component's evaluated data to the terminal."""
62
Added:
name = f' {print(self)} DATA FOR {str(self).upper()} '
63
Added:
num_of_dashes = len(name)
64
Added:
print(num_of_dashes * '-')
65
Added:
print(name)
66
Added:
for k, v in self.__dict__.items():
67
Added:
if type(v) != list:
68
Added:
print('{}:\n'.format(k), v)
69
Added:
print(num_of_dashes * '-')
70
Added:
for k, v in self.__dict__.items():
71
Added:
if type(v) == list:
72
Added:
print('{}:\n'.format(k), np.around(v, round))
73
Added:
return None
74
Added:
75
Added:
def info_save(self, save_path, number):
76
Added:
"""Save all the object's coordinates (must be full path)."""
77
Added:
file_name = 'airfoil_{}_eval.txt'.format(number)
78
Added:
full_path = os.path.join(save_path, file_name)
79
Added:
try:
80
Added:
with open(full_path, 'w') as sys.stdout:
81
Added:
self.info_print(6)
82
Added:
# This line required to reset behavior of sys.stdout
83
Added:
sys.stdout = sys.__stdout__
84
Added:
print('Successfully wrote to file {}'.format(full_path))
85
Added:
except IOError:
86
Added:
print(
87
Added:
'Unable to write {} to specified directory.\n'.format(
88
Added:
file_name), 'Was the full path passed to the function?')
89
Added:
return None
90
Added:
91
Added:
# All these functions take integer arguments and return lists.
92
Added:
93
Added:
def get_lift_rectangular(self, lift):
94
Added:
L_prime = [lift / (self.semi_span * 2) for x in range(self.semi_span)]
95
Added:
return L_prime
96
Added:
97
Added:
def get_lift_elliptical(self, L_0):
98
Added:
L_prime = [
99
Added:
L_0 / (self.semi_span * 2) * sqrt(1 - (y / self.semi_span)**2)
100
Added:
for y in range(self.semi_span)
101
Added:
]
102
Added:
return L_prime
103
Added:
104
Added:
def get_lift_total(self):
105
Added:
F_z = [(self.lift_rectangular[_] + self.lift_elliptical[_]) / 2
106
Added:
for _ in range(len(self.lift_rectangular))]
107
Added:
return F_z
108
Added:
109
Added:
def get_mass_distribution(self, total_mass):
110
Added:
F_z = [total_mass / self.semi_span for x in range(0, self.semi_span)]
111
Added:
return F_z
112
Added:
113
Added:
def get_drag(self, drag):
114
Added:
# Transform semi-span integer into list
115
Added:
semi_span = [x for x in range(0, self.semi_span)]
116
Added:
117
Added:
# Drag increases after 80% of the semi_span
118
Added:
cutoff = round(0.8 * self.semi_span)
119
Added:
120
Added:
# Drag increases by 25% after 80% of the semi_span
121
Added:
F_x = [drag for x in semi_span[0:cutoff]]
122
Added:
F_x.extend([1.25 * drag for x in semi_span[cutoff:]])
123
Added:
return F_x
124
Added:
125
Added:
def get_centroid(self):
126
Added:
"""Return the coordinates of the centroid."""
127
Added:
stringer_area = self.stringer.area
128
Added:
cap_area = self.spar.cap_area
129
Added:
130
Added:
caps_x = [value for spar in self.spar.x for value in spar]
131
Added:
caps_z = [value for spar in self.spar.z for value in spar]
132
Added:
stringers_x = self.stringer.x
133
Added:
stringers_z = self.stringer.z
134
Added:
135
Added:
denominator = float(
136
Added:
len(caps_x) * cap_area + len(stringers_x) * stringer_area)
137
Added:
138
Added:
centroid_x = float(
139
Added:
sum([x * cap_area for x in caps_x]) +
140
Added:
sum([x * stringer_area for x in stringers_x]))
141
Added:
centroid_x = centroid_x / denominator
142
Added:
143
Added:
centroid_z = float(
144
Added:
sum([z * cap_area for z in caps_z]) +
145
Added:
sum([z * stringer_area for z in stringers_z]))
146
Added:
centroid_z = centroid_z / denominator
147
Added:
148
Added:
return (centroid_x, centroid_z)
149
Added:
150
Added:
def get_inertia_terms(self):
151
Added:
"""Obtain all inertia terms."""
152
Added:
stringer_area = self.stringer.area
153
Added:
cap_area = self.spar.cap_area
154
Added:
155
Added:
# Adds upper and lower components' coordinates to list
156
Added:
x_stringers = self.stringer.x
157
Added:
z_stringers = self.stringer.z
158
Added:
x_spars = self.spar.x[:][0] + self.spar.x[:][1]
159
Added:
z_spars = self.spar.z[:][0] + self.spar.z[:][1]
160
Added:
stringer_count = range(len(x_stringers))
161
Added:
spar_count = range(len(self.spar.x))
162
Added:
163
Added:
# I_x is the sum of the contributions of the spar caps and stringers
164
Added:
# TODO: replace list indices with dictionary value
165
Added:
I_x = sum([
166
Added:
cap_area * (z_spars[i] - self.centroid[1])**2 for i in spar_count
167
Added:
])
168
Added:
I_x += sum([
169
Added:
stringer_area * (z_stringers[i] - self.centroid[1])**2
170
Added:
for i in stringer_count
171
Added:
])
172
Added:
173
Added:
I_z = sum([
174
Added:
cap_area * (x_spars[i] - self.centroid[0])**2 for i in spar_count
175
Added:
])
176
Added:
I_z += sum([
177
Added:
stringer_area * (x_stringers[i] - self.centroid[0])**2
178
Added:
for i in stringer_count
179
Added:
])
180
Added:
181
Added:
I_xz = sum([
182
Added:
cap_area * (x_spars[i] - self.centroid[0]) *
183
Added:
(z_spars[i] - self.centroid[1]) for i in spar_count
184
Added:
])
185
Added:
I_xz += sum([
186
Added:
stringer_area * (x_stringers[i] - self.centroid[0]) *
187
Added:
(z_stringers[i] - self.centroid[1]) for i in stringer_count
188
Added:
])
189
Added:
return (I_x, I_z, I_xz)
190
Added:
191
Added:
def get_dx(self, component):
192
Added:
return [x - self.centroid[0] for x in component.x_start]
193
Added:
194
Added:
def get_dz(self, component):
195
Added:
return [x - self.centroid[1] for x in component.x_start]
196
Added:
197
Added:
def get_dP(self, xDist, zDist, V_x, V_z, area):
198
Added:
I_x = self.I_['x']
199
Added:
I_z = self.I_['z']
200
Added:
I_xz = self.I_['xz']
201
Added:
denom = float(I_x * I_z - I_xz**2)
202
Added:
z = float()
203
Added:
for _ in range(len(xDist)):
204
Added:
z += float(-area * xDist[_] * (I_x * V_x - I_xz * V_z) / denom -
205
Added:
area * zDist[_] * (I_z * V_z - I_xz * V_x) / denom)
206
Added:
return z
207
Added:
208
Added:
def analysis(self, V_x, V_z):
209
Added:
"""Perform all analysis calculations and store in class instance."""
210
Added:
self.drag = self.get_drag(10)
211
Added:
self.lift_rectangular = self.get_lift_rectangular(13.7)
212
Added:
self.lift_elliptical = self.get_lift_elliptical(15)
213
Added:
self.lift_total = self.get_lift_total()
214
Added:
self.mass_dist = self.get_mass_distribution(self.mass_total)
215
Added:
self.centroid = self.get_centroid()
216
Added:
self.I_['x'] = self.get_inertia_terms()[0]
217
Added:
self.I_['z'] = self.get_inertia_terms()[1]
218
Added:
self.I_['xz'] = self.get_inertia_terms()[2]
219
Added:
spar_dx = self.get_dx(self.spar)
220
Added:
spar_dz = self.get_dz(self.spar)
221
Added:
self.spar.dP_x = self.get_dP(spar_dx, spar_dz, V_x, 0,
222
Added:
self.spar.cap_area)
223
Added:
self.spar.dP_z = self.get_dP(spar_dx, spar_dz, 0, V_z,
224
Added:
self.spar.cap_area)
225
Added:
return None
226
Added:
227
Added:
228
Added:
def plot_geom(evaluator):
229
Added:
"""This function plots analysis results over the airfoil's geometry."""
230
Added:
# Plot chord
231
Added:
x_chord = [0, evaluator.chord]
232
Added:
y_chord = [0, 0]
233
Added:
plt.plot(x_chord, y_chord, linewidth='1')
234
Added:
# Plot quarter chord
235
Added:
plt.plot(evaluator.chord / 4,
236
Added:
0,
237
Added:
'.',
238
Added:
color='g',
239
Added:
markersize=24,
240
Added:
label='Quarter-chord')
241
Added:
# Plot airfoil surfaces
242
Added:
x = [0.98 * x for x in evaluator.airfoil.x]
243
Added:
y = [0.98 * z for z in evaluator.airfoil.z]
244
Added:
plt.fill(x, y, color='w', linewidth='1', fill=False)
245
Added:
x = [1.02 * x for x in evaluator.airfoil.x]
246
Added:
y = [1.02 * z for z in evaluator.airfoil.z]
247
Added:
plt.fill(x, y, color='b', linewidth='1', fill=False)
248
Added:
249
Added:
# Plot spars
250
Added:
try:
251
Added:
for _ in range(len(evaluator.spar.x)):
252
Added:
x = (evaluator.spar.x[_])
253
Added:
y = (evaluator.spar.z[_])
254
Added:
plt.plot(x, y, '-', color='b')
255
Added:
except AttributeError:
256
Added:
print('No spars to plot.')
257
Added:
# Plot stringers
258
Added:
try:
259
Added:
for _ in range(0, len(evaluator.stringer.x)):
260
Added:
x = evaluator.stringer.x[_]
261
Added:
y = evaluator.stringer.z[_]
262
Added:
plt.plot(x, y, '.', color='y', markersize=12)
263
Added:
except AttributeError:
264
Added:
print('No stringers to plot.')
265
Added:
266
Added:
# Plot centroid
267
Added:
x = evaluator.centroid[0]
268
Added:
y = evaluator.centroid[1]
269
Added:
plt.plot(x, y, '.', color='r', markersize=24, label='centroid')
270
Added:
271
Added:
# Graph formatting
272
Added:
plt.xlabel('X axis')
273
Added:
plt.ylabel('Z axis')
274
Added:
275
Added:
plot_bound = max(evaluator.airfoil.x)
276
Added:
plt.xlim(-0.10 * plot_bound, 1.10 * plot_bound)
277
Added:
plt.ylim(-(1.10 * plot_bound / 2), (1.10 * plot_bound / 2))
278
Added:
plt.gca().set_aspect('equal', adjustable='box')
279
Added:
plt.gca().legend()
280
Added:
plt.grid(axis='both', linestyle=':', linewidth=1)
281
Added:
plt.show()
282
Added:
return None
283
Added:
284
Added:
285
Added:
def plot_lift(evaluator):
286
Added:
x = range(evaluator.semi_span)
287
Added:
y_1 = evaluator.lift_rectangular
288
Added:
y_2 = evaluator.lift_elliptical
289
Added:
y_3 = evaluator.lift_total
290
Added:
plt.plot(x, y_1, '.', color='b', markersize=4, label='Rectangular lift')
291
Added:
plt.plot(x, y_2, '.', color='g', markersize=4, label='Elliptical lift')
292
Added:
plt.plot(x, y_3, '.', color='r', markersize=4, label='Total lift')
293
Added:
294
Added:
# Graph formatting
295
Added:
plt.xlabel('Semi-span location')
296
Added:
plt.ylabel('Lift')
297
Added:
298
Added:
plt.gca().legend()
299
Added:
plt.grid(axis='both', linestyle=':', linewidth=1)
300
Added:
plt.show()
301
Added:
return None
evaluator/__init__.py
evaluator/evaluator.py
@@ -1,301 +0,0 @@
1
Removed:
"""
2
Removed:
The evaluator.py module contains a single Evaluator class,
3
Removed:
which knows all the attributes of a specified Aircraft instance,
4
Removed:
and contains functions to analyse the airfoil's geometrical
5
Removed:
& structural properties.
6
Removed:
"""
7
Removed:
8
Removed:
import sys
9
Removed:
import os.path
10
Removed:
import numpy as np
11
Removed:
from math import sqrt
12
Removed:
import matplotlib.pyplot as plt
13
Removed:
14
Removed:
15
Removed:
class Evaluator:
16
Removed:
"""Performs structural evaluations for the airfoil passed as argument."""
17
Removed:
def __init__(self, aircraft):
18
Removed:
# Evaluator knows all geometrical info from evaluated airfoil
19
Removed:
self.airfoil = self.get_airfoil(aircraft)
20
Removed:
self.spars = self.get_spars(aircraft)
21
Removed:
self.stringers = self.get_stringers(aircraft)
22
Removed:
# Lift
23
Removed:
self.lift_rectangular = []
24
Removed:
self.lift_elliptical = []
25
Removed:
self.lift_total = []
26
Removed:
# Drag
27
Removed:
self.drag = []
28
Removed:
# centroid
29
Removed:
self.centroid = []
30
Removed:
# Inertia terms:
31
Removed:
self.I_ = {'x': 0, 'z': 0, 'xz': 0}
32
Removed:
33
Removed:
def __str__(self):
34
Removed:
return type(self).__name__
35
Removed:
36
Removed:
def get_airfoil(self, aircraft):
37
Removed:
"""Get data of spars belonging to aircraft."""
38
Removed:
try:
39
Removed:
pass
40
Removed:
except:
41
Removed:
pass
42
Removed:
pass
43
Removed:
44
Removed:
def get_spars(self, aircraft):
45
Removed:
"""Get data of spars belonging to aircraft."""
46
Removed:
try:
47
Removed:
pass
48
Removed:
except:
49
Removed:
pass
50
Removed:
pass
51
Removed:
52
Removed:
def get_stringers(self, aircraft):
53
Removed:
"""Get data of spars belonging to aircraft."""
54
Removed:
try:
55
Removed:
pass
56
Removed:
except:
57
Removed:
pass
58
Removed:
pass
59
Removed:
60
Removed:
def info_print(self, round):
61
Removed:
"""Print all the component's evaluated data to the terminal."""
62
Removed:
name = f' {print(self)} DATA FOR {str(self).upper()} '
63
Removed:
num_of_dashes = len(name)
64
Removed:
print(num_of_dashes * '-')
65
Removed:
print(name)
66
Removed:
for k, v in self.__dict__.items():
67
Removed:
if type(v) != list:
68
Removed:
print('{}:\n'.format(k), v)
69
Removed:
print(num_of_dashes * '-')
70
Removed:
for k, v in self.__dict__.items():
71
Removed:
if type(v) == list:
72
Removed:
print('{}:\n'.format(k), np.around(v, round))
73
Removed:
return None
74
Removed:
75
Removed:
def info_save(self, save_path, number):
76
Removed:
"""Save all the object's coordinates (must be full path)."""
77
Removed:
file_name = 'airfoil_{}_eval.txt'.format(number)
78
Removed:
full_path = os.path.join(save_path, file_name)
79
Removed:
try:
80
Removed:
with open(full_path, 'w') as sys.stdout:
81
Removed:
self.info_print(6)
82
Removed:
# This line required to reset behavior of sys.stdout
83
Removed:
sys.stdout = sys.__stdout__
84
Removed:
print('Successfully wrote to file {}'.format(full_path))
85
Removed:
except IOError:
86
Removed:
print(
87
Removed:
'Unable to write {} to specified directory.\n'.format(
88
Removed:
file_name), 'Was the full path passed to the function?')
89
Removed:
return None
90
Removed:
91
Removed:
# All these functions take integer arguments and return lists.
92
Removed:
93
Removed:
def get_lift_rectangular(self, lift):
94
Removed:
L_prime = [lift / (self.semi_span * 2) for x in range(self.semi_span)]
95
Removed:
return L_prime
96
Removed:
97
Removed:
def get_lift_elliptical(self, L_0):
98
Removed:
L_prime = [
99
Removed:
L_0 / (self.semi_span * 2) * sqrt(1 - (y / self.semi_span)**2)
100
Removed:
for y in range(self.semi_span)
101
Removed:
]
102
Removed:
return L_prime
103
Removed:
104
Removed:
def get_lift_total(self):
105
Removed:
F_z = [(self.lift_rectangular[_] + self.lift_elliptical[_]) / 2
106
Removed:
for _ in range(len(self.lift_rectangular))]
107
Removed:
return F_z
108
Removed:
109
Removed:
def get_mass_distribution(self, total_mass):
110
Removed:
F_z = [total_mass / self.semi_span for x in range(0, self.semi_span)]
111
Removed:
return F_z
112
Removed:
113
Removed:
def get_drag(self, drag):
114
Removed:
# Transform semi-span integer into list
115
Removed:
semi_span = [x for x in range(0, self.semi_span)]
116
Removed:
117
Removed:
# Drag increases after 80% of the semi_span
118
Removed:
cutoff = round(0.8 * self.semi_span)
119
Removed:
120
Removed:
# Drag increases by 25% after 80% of the semi_span
121
Removed:
F_x = [drag for x in semi_span[0:cutoff]]
122
Removed:
F_x.extend([1.25 * drag for x in semi_span[cutoff:]])
123
Removed:
return F_x
124
Removed:
125
Removed:
def get_centroid(self):
126
Removed:
"""Return the coordinates of the centroid."""
127
Removed:
stringer_area = self.stringer.area
128
Removed:
cap_area = self.spar.cap_area
129
Removed:
130
Removed:
caps_x = [value for spar in self.spar.x for value in spar]
131
Removed:
caps_z = [value for spar in self.spar.z for value in spar]
132
Removed:
stringers_x = self.stringer.x
133
Removed:
stringers_z = self.stringer.z
134
Removed:
135
Removed:
denominator = float(
136
Removed:
len(caps_x) * cap_area + len(stringers_x) * stringer_area)
137
Removed:
138
Removed:
centroid_x = float(
139
Removed:
sum([x * cap_area for x in caps_x]) +
140
Removed:
sum([x * stringer_area for x in stringers_x]))
141
Removed:
centroid_x = centroid_x / denominator
142
Removed:
143
Removed:
centroid_z = float(
144
Removed:
sum([z * cap_area for z in caps_z]) +
145
Removed:
sum([z * stringer_area for z in stringers_z]))
146
Removed:
centroid_z = centroid_z / denominator
147
Removed:
148
Removed:
return (centroid_x, centroid_z)
149
Removed:
150
Removed:
def get_inertia_terms(self):
151
Removed:
"""Obtain all inertia terms."""
152
Removed:
stringer_area = self.stringer.area
153
Removed:
cap_area = self.spar.cap_area
154
Removed:
155
Removed:
# Adds upper and lower components' coordinates to list
156
Removed:
x_stringers = self.stringer.x
157
Removed:
z_stringers = self.stringer.z
158
Removed:
x_spars = self.spar.x[:][0] + self.spar.x[:][1]
159
Removed:
z_spars = self.spar.z[:][0] + self.spar.z[:][1]
160
Removed:
stringer_count = range(len(x_stringers))
161
Removed:
spar_count = range(len(self.spar.x))
162
Removed:
163
Removed:
# I_x is the sum of the contributions of the spar caps and stringers
164
Removed:
# TODO: replace list indices with dictionary value
165
Removed:
I_x = sum([
166
Removed:
cap_area * (z_spars[i] - self.centroid[1])**2 for i in spar_count
167
Removed:
])
168
Removed:
I_x += sum([
169
Removed:
stringer_area * (z_stringers[i] - self.centroid[1])**2
170
Removed:
for i in stringer_count
171
Removed:
])
172
Removed:
173
Removed:
I_z = sum([
174
Removed:
cap_area * (x_spars[i] - self.centroid[0])**2 for i in spar_count
175
Removed:
])
176
Removed:
I_z += sum([
177
Removed:
stringer_area * (x_stringers[i] - self.centroid[0])**2
178
Removed:
for i in stringer_count
179
Removed:
])
180
Removed:
181
Removed:
I_xz = sum([
182
Removed:
cap_area * (x_spars[i] - self.centroid[0]) *
183
Removed:
(z_spars[i] - self.centroid[1]) for i in spar_count
184
Removed:
])
185
Removed:
I_xz += sum([
186
Removed:
stringer_area * (x_stringers[i] - self.centroid[0]) *
187
Removed:
(z_stringers[i] - self.centroid[1]) for i in stringer_count
188
Removed:
])
189
Removed:
return (I_x, I_z, I_xz)
190
Removed:
191
Removed:
def get_dx(self, component):
192
Removed:
return [x - self.centroid[0] for x in component.x_start]
193
Removed:
194
Removed:
def get_dz(self, component):
195
Removed:
return [x - self.centroid[1] for x in component.x_start]
196
Removed:
197
Removed:
def get_dP(self, xDist, zDist, V_x, V_z, area):
198
Removed:
I_x = self.I_['x']
199
Removed:
I_z = self.I_['z']
200
Removed:
I_xz = self.I_['xz']
201
Removed:
denom = float(I_x * I_z - I_xz**2)
202
Removed:
z = float()
203
Removed:
for _ in range(len(xDist)):
204
Removed:
z += float(-area * xDist[_] * (I_x * V_x - I_xz * V_z) / denom -
205
Removed:
area * zDist[_] * (I_z * V_z - I_xz * V_x) / denom)
206
Removed:
return z
207
Removed:
208
Removed:
def analysis(self, V_x, V_z):
209
Removed:
"""Perform all analysis calculations and store in class instance."""
210
Removed:
self.drag = self.get_drag(10)
211
Removed:
self.lift_rectangular = self.get_lift_rectangular(13.7)
212
Removed:
self.lift_elliptical = self.get_lift_elliptical(15)
213
Removed:
self.lift_total = self.get_lift_total()
214
Removed:
self.mass_dist = self.get_mass_distribution(self.mass_total)
215
Removed:
self.centroid = self.get_centroid()
216
Removed:
self.I_['x'] = self.get_inertia_terms()[0]
217
Removed:
self.I_['z'] = self.get_inertia_terms()[1]
218
Removed:
self.I_['xz'] = self.get_inertia_terms()[2]
219
Removed:
spar_dx = self.get_dx(self.spar)
220
Removed:
spar_dz = self.get_dz(self.spar)
221
Removed:
self.spar.dP_x = self.get_dP(spar_dx, spar_dz, V_x, 0,
222
Removed:
self.spar.cap_area)
223
Removed:
self.spar.dP_z = self.get_dP(spar_dx, spar_dz, 0, V_z,
224
Removed:
self.spar.cap_area)
225
Removed:
return None
226
Removed:
227
Removed:
228
Removed:
def plot_geom(evaluator):
229
Removed:
"""This function plots analysis results over the airfoil's geometry."""
230
Removed:
# Plot chord
231
Removed:
x_chord = [0, evaluator.chord]
232
Removed:
y_chord = [0, 0]
233
Removed:
plt.plot(x_chord, y_chord, linewidth='1')
234
Removed:
# Plot quarter chord
235
Removed:
plt.plot(evaluator.chord / 4,
236
Removed:
0,
237
Removed:
'.',
238
Removed:
color='g',
239
Removed:
markersize=24,
240
Removed:
label='Quarter-chord')
241
Removed:
# Plot airfoil surfaces
242
Removed:
x = [0.98 * x for x in evaluator.airfoil.x]
243
Removed:
y = [0.98 * z for z in evaluator.airfoil.z]
244
Removed:
plt.fill(x, y, color='w', linewidth='1', fill=False)
245
Removed:
x = [1.02 * x for x in evaluator.airfoil.x]
246
Removed:
y = [1.02 * z for z in evaluator.airfoil.z]
247
Removed:
plt.fill(x, y, color='b', linewidth='1', fill=False)
248
Removed:
249
Removed:
# Plot spars
250
Removed:
try:
251
Removed:
for _ in range(len(evaluator.spar.x)):
252
Removed:
x = (evaluator.spar.x[_])
253
Removed:
y = (evaluator.spar.z[_])
254
Removed:
plt.plot(x, y, '-', color='b')
255
Removed:
except AttributeError:
256
Removed:
print('No spars to plot.')
257
Removed:
# Plot stringers
258
Removed:
try:
259
Removed:
for _ in range(0, len(evaluator.stringer.x)):
260
Removed:
x = evaluator.stringer.x[_]
261
Removed:
y = evaluator.stringer.z[_]
262
Removed:
plt.plot(x, y, '.', color='y', markersize=12)
263
Removed:
except AttributeError:
264
Removed:
print('No stringers to plot.')
265
Removed:
266
Removed:
# Plot centroid
267
Removed:
x = evaluator.centroid[0]
268
Removed:
y = evaluator.centroid[1]
269
Removed:
plt.plot(x, y, '.', color='r', markersize=24, label='centroid')
270
Removed:
271
Removed:
# Graph formatting
272
Removed:
plt.xlabel('X axis')
273
Removed:
plt.ylabel('Z axis')
274
Removed:
275
Removed:
plot_bound = max(evaluator.airfoil.x)
276
Removed:
plt.xlim(-0.10 * plot_bound, 1.10 * plot_bound)
277
Removed:
plt.ylim(-(1.10 * plot_bound / 2), (1.10 * plot_bound / 2))
278
Removed:
plt.gca().set_aspect('equal', adjustable='box')
279
Removed:
plt.gca().legend()
280
Removed:
plt.grid(axis='both', linestyle=':', linewidth=1)
281
Removed:
plt.show()
282
Removed:
return None
283
Removed:
284
Removed:
285
Removed:
def plot_lift(evaluator):
286
Removed:
x = range(evaluator.semi_span)
287
Removed:
y_1 = evaluator.lift_rectangular
288
Removed:
y_2 = evaluator.lift_elliptical
289
Removed:
y_3 = evaluator.lift_total
290
Removed:
plt.plot(x, y_1, '.', color='b', markersize=4, label='Rectangular lift')
291
Removed:
plt.plot(x, y_2, '.', color='g', markersize=4, label='Elliptical lift')
292
Removed:
plt.plot(x, y_3, '.', color='r', markersize=4, label='Total lift')
293
Removed:
294
Removed:
# Graph formatting
295
Removed:
plt.xlabel('Semi-span location')
296
Removed:
plt.ylabel('Lift')
297
Removed:
298
Removed:
plt.gca().legend()
299
Removed:
plt.grid(axis='both', linestyle=':', linewidth=1)
300
Removed:
plt.show()
301
Removed:
return None
example_airfoil.py
@@ -9,9 +9,10 @@
9
9
Generate a population of airfoils & optimize.
10
10
"""
11
11
12
Removed:
from resources import materials as mt
13
Removed:
from creator import *
14
Removed:
from evaluator import evaluator
12
Added:
import resources.materials as mt
13
Added:
import creator
14
Added:
import evaluator
15
Added:
import generator
15
16
# from generator import
16
17
17
18
import time
@@ -42,16 +43,16 @@
42
43
SAVE_PATH = '/home/blendux/Projects/Aircraft_Studio/save'
43
44
44
45
# Create aircraft instance
45
Removed:
aircraft = base.Aircraft
46
Added:
aircraft = creator.base.Aircraft
46
47
# Create airfoil instance
47
Removed:
af = wing.Airfoil(20, 150, mt.aluminium)
48
Added:
af = creator.wing.Airfoil(68, 150, mt.aluminium)
48
49
af.add_naca(NACA_NUM)
49
50
af.info_print(2)
50
51
# af.info_save(SAVE_PATH, 'foo_name')
51
52
52
53
# Create spar instances
53
Removed:
af.spar1 = wing.Spar(af, 0.23, mt.aluminium)
54
Removed:
af.spar2 = wing.Spar(af, 0.57, mt.aluminium)
54
Added:
af.spar1 = creator.wing.Spar(af, 0.23, mt.aluminium)
55
Added:
af.spar2 = creator.wing.Spar(af, 0.57, mt.aluminium)
55
56
# af.spar1.info_print(2)
56
57
# af.spar2.info_print(2)
57
58
# af.spar1.info_save(SAVE_PATH, 'spar1')
generator.py
@@ -1,64 +0,0 @@
1
Removed:
# This file is part of Marius Peter's airfoil analysis package (this program).
2
Removed:
#
3
Removed:
# This program is free software: you can redistribute it and/or modify
4
Removed:
# it under the terms of the GNU General Public License as published by
5
Removed:
# the Free Software Foundation, either version 3 of the License, or
6
Removed:
# (at your option) any later version.
7
Removed:
#
8
Removed:
# This program is distributed in the hope that it will be useful,
9
Removed:
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
Removed:
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
Removed:
# GNU General Public License for more details.
12
Removed:
#
13
Removed:
# You should have received a copy of the GNU General Public License
14
Removed:
# along with this program. If not, see <https://www.gnu.org/licenses/>.
15
Removed:
"""
16
Removed:
The generator.py module contains a single Population class,
17
Removed:
which represents a collection of randomized airfoils.
18
Removed:
"""
19
Removed:
20
Removed:
from tools import creator
21
Removed:
22
Removed:
23
Removed:
def default_airfoil():
24
Removed:
"""Generate the default airfoil."""
25
Removed:
airfoil = creator.Airfoil.from_dimensions(100, 200)
26
Removed:
airfoil.add_naca(2412)
27
Removed:
airfoil.add_mass(10)
28
Removed:
29
Removed:
airfoil.spar = creator.Spar()
30
Removed:
airfoil.spar.add_coord(airfoil, 0.23)
31
Removed:
airfoil.spar.add_coord(airfoil, 0.57)
32
Removed:
airfoil.spar.add_spar_caps(0.3)
33
Removed:
airfoil.spar.add_mass(10)
34
Removed:
airfoil.spar.add_webs(0.4)
35
Removed:
36
Removed:
airfoil.stringer = creator.Stringer()
37
Removed:
airfoil.stringer.add_coord(airfoil, 3, 6, 5, 4)
38
Removed:
airfoil.stringer.add_area(0.1)
39
Removed:
airfoil.stringer.add_mass(5)
40
Removed:
airfoil.stringer.add_webs(0.1)
41
Removed:
42
Removed:
return airfoil
43
Removed:
44
Removed:
45
Removed:
class Population(creator.Airfoil):
46
Removed:
"""Collection of random airfoils."""
47
Removed:
48
Removed:
def __init__(self, size):
49
Removed:
af = creator.Airfoil
50
Removed:
# print(af)
51
Removed:
self.size = size
52
Removed:
self.gen_number = 0 # incremented for every generation
53
Removed:
54
Removed:
def mutate(self, prob_mt):
55
Removed:
"""Randomly mutate the genes of prob_mt % of the population."""
56
Removed:
57
Removed:
def crossover(self, prob_cx):
58
Removed:
"""Combine the genes of prob_cx % of the population."""
59
Removed:
60
Removed:
def reproduce(self, prob_rp):
61
Removed:
"""Pass on the genes of the fittest prob_rp % of the population."""
62
Removed:
63
Removed:
def fitness():
64
Removed:
"""Rate the fitness of an individual on a relative scale (0-100)"""
generator/generator.py
@@ -0,0 +1,64 @@
1
Added:
# This file is part of Marius Peter's airfoil analysis package (this program).
2
Added:
#
3
Added:
# This program is free software: you can redistribute it and/or modify
4
Added:
# it under the terms of the GNU General Public License as published by
5
Added:
# the Free Software Foundation, either version 3 of the License, or
6
Added:
# (at your option) any later version.
7
Added:
#
8
Added:
# This program is distributed in the hope that it will be useful,
9
Added:
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
Added:
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
Added:
# GNU General Public License for more details.
12
Added:
#
13
Added:
# You should have received a copy of the GNU General Public License
14
Added:
# along with this program. If not, see <https://www.gnu.org/licenses/>.
15
Added:
"""
16
Added:
The generator.py module contains a single Population class,
17
Added:
which represents a collection of randomized airfoils.
18
Added:
"""
19
Added:
20
Added:
from tools import creator
21
Added:
22
Added:
23
Added:
def default_airfoil():
24
Added:
"""Generate the default airfoil."""
25
Added:
airfoil = creator.Airfoil.from_dimensions(100, 200)
26
Added:
airfoil.add_naca(2412)
27
Added:
airfoil.add_mass(10)
28
Added:
29
Added:
airfoil.spar = creator.Spar()
30
Added:
airfoil.spar.add_coord(airfoil, 0.23)
31
Added:
airfoil.spar.add_coord(airfoil, 0.57)
32
Added:
airfoil.spar.add_spar_caps(0.3)
33
Added:
airfoil.spar.add_mass(10)
34
Added:
airfoil.spar.add_webs(0.4)
35
Added:
36
Added:
airfoil.stringer = creator.Stringer()
37
Added:
airfoil.stringer.add_coord(airfoil, 3, 6, 5, 4)
38
Added:
airfoil.stringer.add_area(0.1)
39
Added:
airfoil.stringer.add_mass(5)
40
Added:
airfoil.stringer.add_webs(0.1)
41
Added:
42
Added:
return airfoil
43
Added:
44
Added:
45
Added:
class Population(creator.Airfoil):
46
Added:
"""Collection of random airfoils."""
47
Added:
48
Added:
def __init__(self, size):
49
Added:
af = creator.Airfoil
50
Added:
# print(af)
51
Added:
self.size = size
52
Added:
self.gen_number = 0 # incremented for every generation
53
Added:
54
Added:
def mutate(self, prob_mt):
55
Added:
"""Randomly mutate the genes of prob_mt % of the population."""
56
Added:
57
Added:
def crossover(self, prob_cx):
58
Added:
"""Combine the genes of prob_cx % of the population."""
59
Added:
60
Added:
def reproduce(self, prob_rp):
61
Added:
"""Pass on the genes of the fittest prob_rp % of the population."""
62
Added:
63
Added:
def fitness():
64
Added:
"""Rate the fitness of an individual on a relative scale (0-100)"""