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