Add files via upload

Commit
50557a765e812beca9b9ef7d92af0828bd7bd0d4
Author
Blendoit <51464356+Blendoit@users.noreply.github.com>
Author date
Committer
GitHub <noreply@github.com>
Committer date
Changed files
__init__.py
index 00000000..a7e70a3d 000000..100644
@@ -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
index 00000000..59c84364 000000..100644
@@ -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
index 00000000..4065c5ae 000000..100644
@@ -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
index 00000000..7a5a20b3 000000..100644
@@ -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()