*ARCHIVED* development moved to aircraft-studio.
Add files via upload
Changed files
__init__.py
@@ -0,0 +1,17 @@
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:
__author__ = "Marius Peter"
16
Added:
__version__ = "2.1"
17
Added:
__revision__ = "2.1.1"
creator.py
@@ -0,0 +1,382 @@
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:
import sys
17
Added:
import os.path
18
Added:
import numpy as np
19
Added:
from math import sin, cos, tan, atan, sqrt, ceil
20
Added:
import bisect as bi
21
Added:
import matplotlib.pyplot as plt
22
Added:
import matplotlib as mpl
23
Added:
from mpl_toolkits.mplot3d import Axes3D
24
Added:
25
Added:
26
Added:
# This variable is required for main.py constant wing dimensions
27
Added:
# to be passed to inheriting classes (Airfoil, Spar, Stringer, Rib).
28
Added:
# This way, we don't have to redeclare our coordinates as parameters for
29
Added:
# our spars, stringers and ribs. This makes for more elegant code.
30
Added:
global parent
31
Added:
32
Added:
33
Added:
class Coordinates:
34
Added:
"""
35
Added:
All classes need coordinates relative to the chord & semi-span.
36
Added:
So, they all inherit from this class.
37
Added:
"""
38
Added:
39
Added:
def __init__(self, chord, semi_span):
40
Added:
# Global dimensions
41
Added:
self.chord = chord
42
Added:
self.semi_span = semi_span
43
Added:
# Upper coordinates
44
Added:
self.x_u = []
45
Added:
self.y_u = []
46
Added:
# Lower coordinates
47
Added:
self.x_l = []
48
Added:
self.y_l = []
49
Added:
# Upper coordinates
50
Added:
self.x_u = []
51
Added:
self.y_u = []
52
Added:
# Lower coordinates
53
Added:
self.x_l = []
54
Added:
self.y_l = []
55
Added:
# Coordinates x_u, y_u, x_l, y_l packed in single list
56
Added:
self.coordinates = []
57
Added:
global parent
58
Added:
parent = self
59
Added:
60
Added:
def create(self, chord, semi_span):
61
Added:
self.chord = chord
62
Added:
self.semi_span = semi_span
63
Added:
64
Added:
chord = self.chord
65
Added:
semi_span = self.semi_span
66
Added:
67
Added:
68
Added:
class Airfoil(Coordinates):
69
Added:
"""This class enables the creation of a NACA airfoil."""
70
Added:
71
Added:
def __init__(self):
72
Added:
global parent
73
Added:
# Run 'Coordinates' super class init method with same chord & 1/2 span.
74
Added:
super().__init__(parent.chord, parent.semi_span)
75
Added:
# NACA number
76
Added:
self.naca_num = int()
77
Added:
# Mean camber line
78
Added:
self.x_c = []
79
Added:
self.y_c = []
80
Added:
# Thickness
81
Added:
self.y_t = []
82
Added:
# dy_c / d_x
83
Added:
self.dy_c = []
84
Added:
# Theta
85
Added:
self.theta = []
86
Added:
87
Added:
def naca(self, naca_num):
88
Added:
"""
89
Added:
This function generates geometry for our chosen NACA airfoil shape.\
90
Added:
The nested functions perform the required steps to generate geometry,\
91
Added:
and can be called to solve the geometry y-coordinate for any 'x' input.\
92
Added:
Equation coefficients were retrieved from Wikipedia.org.
93
Added:
94
Added:
Parameters:
95
Added:
naca_num: 4-digit NACA wing
96
Added:
chord: wing chord length, in any unit
97
Added:
98
Added:
Return:
99
Added:
None
100
Added:
"""
101
Added:
102
Added:
# Variables extracted from 'naca_num' argument passed to the function
103
Added:
self.naca_num = naca_num
104
Added:
m = int(str(naca_num)[0]) / 100
105
Added:
p = int(str(naca_num)[1]) / 10
106
Added:
t = int(str(naca_num)[2:]) / 100
107
Added:
# Chord length. Should be higher than 10.
108
Added:
if self.chord < 10:
109
Added:
self.chord = 10
110
Added:
# x-coordinate of maximum camber
111
Added:
p_c = p * self.chord
112
Added:
113
Added:
def get_camber(x):
114
Added:
"""
115
Added:
Returns 1 camber y-coordinate from 1 'x' along the airfoil chord.
116
Added:
"""
117
Added:
x_c = x
118
Added:
y_c = float()
119
Added:
if 0 <= x < p_c:
120
Added:
y_c = (m / (p ** 2)) * (2 * p
121
Added:
* (x / self.chord)
122
Added:
- (x / self.chord) ** 2)
123
Added:
elif p_c <= x <= self.chord:
124
Added:
y_c = (m / ((1 - p) ** 2)) * ((1 - 2 * p)
125
Added:
+ 2 * p * (x / self.chord)
126
Added:
- (x / self.chord) ** 2)
127
Added:
else:
128
Added:
print('x-coordinate for camber is out of bounds. '
129
Added:
'Check that 0 < x <= chord.')
130
Added:
return (x_c, y_c * self.chord)
131
Added:
132
Added:
def get_thickness(x):
133
Added:
"""
134
Added:
Returns thickness from 1 'x' along the airfoil chord.
135
Added:
"""
136
Added:
y_t = float()
137
Added:
if 0 <= x <= self.chord:
138
Added:
y_t = 5 * t * self.chord * (0.2969 * sqrt(x / self.chord)
139
Added:
- 0.1260 * (x / self.chord)
140
Added:
- 0.3516 * (x / self.chord) ** 2
141
Added:
+ 0.2843 * (x / self.chord) ** 3
142
Added:
- 0.1015 * (x / self.chord) ** 4)
143
Added:
else:
144
Added:
print('x-coordinate for thickness is out of bounds. '
145
Added:
'Check that 0 < x <= chord.')
146
Added:
return y_t
147
Added:
148
Added:
def get_dy_c(x):
149
Added:
"""
150
Added:
Returns dy_c/dx from 1 'x' along the airfoil chord.
151
Added:
"""
152
Added:
dy_c = float()
153
Added:
if 0 <= x < p_c:
154
Added:
dy_c = ((2 * m)/p ** 2) * (p - x / self.chord)
155
Added:
elif p_c <= x <= self.chord:
156
Added:
dy_c = (2 * m) / ((1 - p) ** 2) * (p - x / self.chord)
157
Added:
return dy_c
158
Added:
159
Added:
def get_theta(dy_c):
160
Added:
theta = atan(dy_c)
161
Added:
return theta
162
Added:
163
Added:
def get_upper_coordinates(x):
164
Added:
x_u = float()
165
Added:
y_u = float()
166
Added:
if 0 <= x < self.chord:
167
Added:
x_u = x - self.y_t[x] * sin(self.theta[x])
168
Added:
y_u = self.y_c[x] + self.y_t[x] * cos(self.theta[x])
169
Added:
elif x == self.chord:
170
Added:
x_u = x - self.y_t[x] * sin(self.theta[x])
171
Added:
y_u = 0 # Make upper curve finish at y = 0
172
Added:
return(x_u, y_u)
173
Added:
174
Added:
def get_lower_coordinates(x):
175
Added:
x_l = float()
176
Added:
y_l = float()
177
Added:
if 0 <= x < self.chord:
178
Added:
x_l = (x + self.y_t[x] * sin(self.theta[x]))
179
Added:
y_l = (self.y_c[x] - self.y_t[x] * cos(self.theta[x]))
180
Added:
elif x == self.chord:
181
Added:
x_l = (x + self.y_t[x] * sin(self.theta[x]))
182
Added:
y_l = 0 # Make lower curve finish at y = 0
183
Added:
return(x_l, y_l)
184
Added:
185
Added:
# Generate all our wing geometries from previous sub-functions
186
Added:
for x in range(0, self.chord + 1):
187
Added:
self.x_c.append(get_camber(x)[0])
188
Added:
self.y_c.append(get_camber(x)[1])
189
Added:
self.y_t.append(get_thickness(x))
190
Added:
self.dy_c.append(get_dy_c(x))
191
Added:
self.theta.append(get_theta(self.dy_c[x]))
192
Added:
self.x_u.append(get_upper_coordinates(x)[0])
193
Added:
self.y_u.append(get_upper_coordinates(x)[1])
194
Added:
self.x_l.append(get_lower_coordinates(x)[0])
195
Added:
self.y_l.append(get_lower_coordinates(x)[1])
196
Added:
197
Added:
self.coordinates.append(self.x_u)
198
Added:
self.coordinates.append(self.y_u)
199
Added:
self.coordinates.append(self.x_l)
200
Added:
self.coordinates.append(self.x_l)
201
Added:
202
Added:
return None
203
Added:
204
Added:
def print_geometry(self, round):
205
Added:
"""
206
Added:
Print all the declared geometry to the terminal.
207
Added:
"""
208
Added:
# Print all our basic geometry, useful for debugging
209
Added:
print('Chord length')
210
Added:
print(self.chord)
211
Added:
print('x_c the x-coordinates of the mean camber line')
212
Added:
print(np.around(self.x_c, round))
213
Added:
print('y_c the y-coordinates of the mean camber line')
214
Added:
print(np.around(self.y_c, round))
215
Added:
print('y_t the y-coordinates of the airfoil thickness')
216
Added:
print(np.around(self.y_t, round))
217
Added:
print('dy_c the derivative of y_c with respect to dx')
218
Added:
print(np.around(self.dy_c, round))
219
Added:
print('theta is like an angle, idk')
220
Added:
print(np.around(self.theta, round))
221
Added:
print('x_u the x-coordinates of the upper airfoil surface')
222
Added:
print(np.around(self.x_u, round))
223
Added:
print('y_u the y-coordinates of the upper airfoil surface')
224
Added:
print(np.around(self.y_u, round))
225
Added:
print('x_l the x-coordinates of the lower airfoil surface')
226
Added:
print(np.around(self.x_l, round))
227
Added:
print('y_l the y-coordinates of lower airfoil surface')
228
Added:
print(np.around(self.y_l, round))
229
Added:
return None
230
Added:
231
Added:
def save_values(self, airfoil_number, save_dir_path):
232
Added:
"""
233
Added:
Save all the declared geometry to save_dir_path (must be full path).
234
Added:
"""
235
Added:
file_name = 'airfoil_%s' % airfoil_number
236
Added:
full_path = os.path.join(save_dir_path, file_name+'.txt')
237
Added:
file = open(full_path, 'w')
238
Added:
sys.stdout = file
239
Added:
self.print_geometry(4)
240
Added:
return None
241
Added:
242
Added:
243
Added:
class Spar(Coordinates):
244
Added:
"""Contains a single spar's location and material."""
245
Added:
global parent
246
Added:
247
Added:
def __init__(self):
248
Added:
super().__init__(parent.chord, parent.semi_span)
249
Added:
# Spar material
250
Added:
self.spar_material = []
251
Added:
252
Added:
def add_spar(self, coordinates, material, spar_x):
253
Added:
"""
254
Added:
Add a single spar at the % chord location given to function.
255
Added:
256
Added:
Parameters:
257
Added:
coordinates: provided by Airfoil.coordinates[x_u, y_u, x_l, y_l].
258
Added:
material: spar's material. Assumes homogeneous material.
259
Added:
spar_x: spar's location as a % of total chord length.
260
Added:
261
Added:
Return:
262
Added:
None
263
Added:
"""
264
Added:
# Airfoil surface coordinates
265
Added:
# unpacked from 'coordinates' (list of lists in 'Airfoil').
266
Added:
x_u = coordinates[0]
267
Added:
y_u = coordinates[1]
268
Added:
x_l = coordinates[2]
269
Added:
y_l = coordinates[3]
270
Added:
# Scaled spar location with regards to chord
271
Added:
loc = spar_x * self.chord
272
Added:
# bisect_left: returns index of first value in x_u > loc.
273
Added:
# This ensures that the spar coordinates intersect with airfoil surface.
274
Added:
spar_x_u = bi.bisect_left(x_u, loc) # index of spar's x_u
275
Added:
spar_x_l = bi.bisect_left(x_l, loc) # index of spar's x_l
276
Added:
# These x and y coordinates are assigned to the spar, NOT airfoil.
277
Added:
self.x_u.append(x_u[spar_x_u])
278
Added:
self.y_u.append(y_u[spar_x_u])
279
Added:
self.x_l.append(x_l[spar_x_l])
280
Added:
self.y_l.append(y_l[spar_x_l])
281
Added:
self.spar_material = material
282
Added:
return None
283
Added:
284
Added:
285
Added:
class Stringer():
286
Added:
"""Contains the coordinates of stringer(s) location and material."""
287
Added:
288
Added:
def __init__(self):
289
Added:
# Stringer attributes
290
Added:
self.stringer_x_u = []
291
Added:
self.stringer_y_u = []
292
Added:
self.stringer_x_l = []
293
Added:
self.stringer_y_l = []
294
Added:
self.stringer_mat = []
295
Added:
296
Added:
def add_stringers(self, material, *density):
297
Added:
"""
298
Added:
Add stringers to the wing from their distribution density between spars.
299
Added:
First half of density[] concerns stringer distribution on
300
Added:
301
Added:
Parameters:
302
Added:
material: stringer material
303
Added:
*density:
304
Added:
305
Added:
"""
306
Added:
307
Added:
# Find interval between leading edge and first upper stringer,
308
Added:
# from density parameter den_u_1.
309
Added:
interval = self.spar_x_u[0] / (den_u_1 * self.spar_x_u[0])
310
Added:
# initialise first self.stringer_x_u at first interval.
311
Added:
x = interval
312
Added:
# Add upper stringers until first spar.
313
Added:
while x < self.spar_x_u[0]:
314
Added:
# Index of the first value of self.x_u > x
315
Added:
x_u = bi.bisect_left(self.x_u, x)
316
Added:
self.stringer_x_u.append(self.x_u[x_u])
317
Added:
self.stringer_y_u.append(self.y_u[x_u])
318
Added:
x += interval
319
Added:
320
Added:
# Find interval between leading edge and first lower stringer,
321
Added:
# from density parameter den_l_1.
322
Added:
interval = self.spar_x_u[0] / (den_l_1 * self.spar_x_u[0])
323
Added:
# initialise first self.stringer_x_l at first interval.
324
Added:
x = interval
325
Added:
# Add lower stringers until first spar.
326
Added:
while x < self.spar_x_l[0]:
327
Added:
# Index of the first value of self.x_l > x
328
Added:
x_u = bi.bisect_left(self.x_l, x)
329
Added:
self.stringer_x_l.append(self.x_l[x_u])
330
Added:
self.stringer_y_l.append(self.y_l[x_u])
331
Added:
x += interval
332
Added:
return None
333
Added:
334
Added:
335
Added:
def plot(airfoil, spar):
336
Added:
"""This function plots the elements passed as arguments."""
337
Added:
338
Added:
print('Plotting airfoil.')
339
Added:
# Plot chord
340
Added:
x_chord = [0, airfoil.chord]
341
Added:
y_chord = [0, 0]
342
Added:
plt.plot(x_chord, y_chord, linewidth='1')
343
Added:
# Plot mean camber line
344
Added:
plt.plot(airfoil.x_c, airfoil.y_c, '-.', color='r', linewidth='2',
345
Added:
label='mean camber line')
346
Added:
# Plot upper surface
347
Added:
plt.plot(airfoil.x_u, airfoil.y_u, '', color='b', linewidth='1')
348
Added:
# Plot lower surface
349
Added:
plt.plot(airfoil.x_l, airfoil.y_l, '', color='b', linewidth='1')
350
Added:
# Plot spars
351
Added:
try:
352
Added:
for _ in range(0, len(spar.x_u)):
353
Added:
x = (spar.spar_x_u[_], spar.spar_x_l[_])
354
Added:
y = (spar.spar_y_u[_], spar.spar_y_l[_])
355
Added:
plt.plot(x, y, '.-', color='b', label='spar')
356
Added:
plt.legend()
357
Added:
except:
358
Added:
print('Did plot spars. Were they added?')
359
Added:
# Plot stringers
360
Added:
# if len(self.spar_x) != 0:
361
Added:
# for _ in range(0, len(self.stringer_x)):
362
Added:
# x = (self.stringer_x[_], self.stringer_x[_])
363
Added:
# y = (self.stringer_y_u[_], self.stringer_y_l[_])
364
Added:
# plt.scatter(x, y, color='y', linewidth='1',
365
Added:
# else:
366
Added:
# print('Unable to plot stringers. Were they created?')
367
Added:
# Graph formatting
368
Added:
plt.gcf().set_size_inches(9, 2.2)
369
Added:
plt.xlabel('X axis')
370
Added:
plt.ylabel('Y axis')
371
Added:
# plt.gcf().set_size_inches(self.chord, max(self.y_u) - min(self.y_l))
372
Added:
plt.grid(axis='both', linestyle=':', linewidth=1)
373
Added:
plt.show()
374
Added:
return None
375
Added:
376
Added:
377
Added:
def main():
378
Added:
return None
379
Added:
380
Added:
381
Added:
if __name__ == '__main__':
382
Added:
main()
evaluate.py
@@ -0,0 +1,16 @@
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:
import airfoil as af
main.py
@@ -0,0 +1,45 @@
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:
import creator
17
Added:
import random
18
Added:
19
Added:
import time
20
Added:
start_time = time.time()
21
Added:
22
Added:
CHORD_LENGTH = 100
23
Added:
SEMI_SPAN = 200
24
Added:
25
Added:
POP_SIZE = 1
26
Added:
SAVE_PATH = 'C:/Users/blend/Desktop/python/airfoils'
27
Added:
28
Added:
29
Added:
def main():
30
Added:
# Create coordinate system specific to airfoil dimensions.
31
Added:
creator.Coordinates(CHORD_LENGTH, SEMI_SPAN)
32
Added:
for airfoil_number in range(1, POP_SIZE + 1):
33
Added:
foo = creator.Airfoil()
34
Added:
foo.naca(2412)
35
Added:
# foo.print_geometry(4)
36
Added:
foo.spar = creator.Spar()
37
Added:
foo.spar.add_spar(foo.coordinates, 'aluminium', 0.15)
38
Added:
creator.plot(foo, foo.spar)
39
Added:
# foo.save_values(airfoil_number, SAVE_PATH)
40
Added:
41
Added:
print("--- %s seconds ---" % (time.time() - start_time))
42
Added:
43
Added:
44
Added:
if __name__ == '__main__':
45
Added:
main()