summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--__init__.py19
-rw-r--r--__pycache__/creator.cpython-37.pycbin0 -> 9785 bytes
-rw-r--r--creator.py386
3 files changed, 405 insertions, 0 deletions
diff --git a/__init__.py b/__init__.py
new file mode 100644
index 0000000..3e60a6f
--- /dev/null
+++ b/__init__.py
@@ -0,0 +1,19 @@
+# This file is part of Marius Peter's airfoil analysis package (this program).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distribguted in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+__author__ = "Marius Peter"
+__version__ = "2.1"
+__revision__ = "2.1.1"
+
+# Foobie foobar
diff --git a/__pycache__/creator.cpython-37.pyc b/__pycache__/creator.cpython-37.pyc
new file mode 100644
index 0000000..9933593
--- /dev/null
+++ b/__pycache__/creator.cpython-37.pyc
Binary files differ
diff --git a/creator.py b/creator.py
new file mode 100644
index 0000000..f826a14
--- /dev/null
+++ b/creator.py
@@ -0,0 +1,386 @@
+# This file is part of Marius Peter's airfoil analysis package (this program).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import sys
+import os.path
+import numpy as np
+from math import sin, cos, tan, atan, sqrt, ceil
+import bisect as bi
+import matplotlib.pyplot as plt
+import matplotlib as mpl
+from mpl_toolkits.mplot3d import Axes3D
+
+# hello im just dicking around
+# This variable is required for main.py constant wing dimensions
+# to be passed to inheriting classes (Airfoil, Spar, Stringer, Rib).
+# This way, we don't have to redeclare our coordinates as parameters for
+# our spars, stringers and ribs. This makes for more elegant code.
+global parent
+
+
+class Coordinates:
+ """
+ All classes need coordinates relative to the chord & semi-span.
+ So, they all inherit from this class.
+ """
+
+ def __init__(self, chord, semi_span):
+ # Global dimensions
+ self.chord = chord
+ self.semi_span = semi_span
+ # Upper coordinates
+ self.x_u = []
+ self.y_u = []
+ # Lower coordinates
+ self.x_l = []
+ self.y_l = []
+ # Upper coordinates
+ self.x_u = []
+ self.y_u = []
+ # Lower coordinates
+ self.x_l = []
+ self.y_l = []
+ # Coordinates x_u, y_u, x_l, y_l packed in single list
+ self.coordinates = []
+ global parent
+ parent = self
+
+ def create(self, chord, semi_span):
+ self.chord = chord
+ self.semi_span = semi_span
+
+ chord = self.chord
+ semi_span = self.semi_span
+
+
+class Airfoil(Coordinates):
+ """This class enables the creation of a NACA airfoil."""
+
+ def __init__(self):
+ global parent
+ # Run 'Coordinates' super class init method with same chord & 1/2 span.
+ super().__init__(parent.chord, parent.semi_span)
+ # NACA number
+ self.naca_num = int()
+ # Mean camber line
+ self.x_c = []
+ self.y_c = []
+ # Thickness
+ self.y_t = []
+ # dy_c / d_x
+ self.dy_c = []
+ # Theta
+ self.theta = []
+
+ def naca(self, naca_num):
+ """
+ This function generates geometry for our chosen NACA airfoil shape.\
+ The nested functions perform the required steps to generate geometry,\
+ and can be called to solve the geometry y-coordinate for any 'x' input.\
+ Equation coefficients were retrieved from Wikipedia.org.
+
+ Parameters:
+ naca_num: 4-digit NACA wing
+ chord: wing chord length, in any unit
+
+ Return:
+ None
+ """
+
+ # Variables extracted from 'naca_num' argument passed to the function
+ self.naca_num = naca_num
+ m = int(str(naca_num)[0]) / 100
+ p = int(str(naca_num)[1]) / 10
+ t = int(str(naca_num)[2:]) / 100
+ # Chord length. Should be higher than 10.
+ if self.chord < 10:
+ self.chord = 10
+ # x-coordinate of maximum camber
+ p_c = p * self.chord
+
+ def get_camber(x):
+ """
+ Returns 1 camber y-coordinate from 1 'x' along the airfoil chord.
+ """
+ x_c = x
+ y_c = float()
+ if 0 <= x < p_c:
+ y_c = (m / (p**2)) * (2 * p * (x / self.chord) -
+ (x / self.chord)**2)
+ elif p_c <= x <= self.chord:
+ y_c = (m /
+ ((1 - p)**2)) * ((1 - 2 * p) + 2 * p *
+ (x / self.chord) - (x / self.chord)**2)
+ else:
+ print('x-coordinate for camber is out of bounds. '
+ 'Check that 0 < x <= chord.')
+ return (x_c, y_c * self.chord)
+
+ def get_thickness(x):
+ """
+ Returns thickness from 1 'x' along the airfoil chord.
+ """
+ y_t = float()
+ if 0 <= x <= self.chord:
+ y_t = 5 * t * self.chord * (0.2969 * sqrt(x / self.chord) -
+ 0.1260 *
+ (x / self.chord) - 0.3516 *
+ (x / self.chord)**2 + 0.2843 *
+ (x / self.chord)**3 - 0.1015 *
+ (x / self.chord)**4)
+ else:
+ print('x-coordinate for thickness is out of bounds. '
+ 'Check that 0 < x <= chord.')
+ return y_t
+
+ def get_dy_c(x):
+ """
+ Returns dy_c/dx from 1 'x' along the airfoil chord.
+ """
+ dy_c = float()
+ if 0 <= x < p_c:
+ dy_c = ((2 * m) / p**2) * (p - x / self.chord)
+ elif p_c <= x <= self.chord:
+ dy_c = (2 * m) / ((1 - p)**2) * (p - x / self.chord)
+ return dy_c
+
+ def get_theta(dy_c):
+ theta = atan(dy_c)
+ return theta
+
+ def get_upper_coordinates(x):
+ x_u = float()
+ y_u = float()
+ if 0 <= x < self.chord:
+ x_u = x - self.y_t[x] * sin(self.theta[x])
+ y_u = self.y_c[x] + self.y_t[x] * cos(self.theta[x])
+ elif x == self.chord:
+ x_u = x - self.y_t[x] * sin(self.theta[x])
+ y_u = 0 # Make upper curve finish at y = 0
+ return (x_u, y_u)
+
+ def get_lower_coordinates(x):
+ x_l = float()
+ y_l = float()
+ if 0 <= x < self.chord:
+ x_l = (x + self.y_t[x] * sin(self.theta[x]))
+ y_l = (self.y_c[x] - self.y_t[x] * cos(self.theta[x]))
+ elif x == self.chord:
+ x_l = (x + self.y_t[x] * sin(self.theta[x]))
+ y_l = 0 # Make lower curve finish at y = 0
+ return (x_l, y_l)
+
+ # Generate all our wing geometries from previous sub-functions
+ for x in range(0, self.chord + 1):
+ self.x_c.append(get_camber(x)[0])
+ self.y_c.append(get_camber(x)[1])
+ self.y_t.append(get_thickness(x))
+ self.dy_c.append(get_dy_c(x))
+ self.theta.append(get_theta(self.dy_c[x]))
+ self.x_u.append(get_upper_coordinates(x)[0])
+ self.y_u.append(get_upper_coordinates(x)[1])
+ self.x_l.append(get_lower_coordinates(x)[0])
+ self.y_l.append(get_lower_coordinates(x)[1])
+
+ self.coordinates.append(self.x_u)
+ self.coordinates.append(self.y_u)
+ self.coordinates.append(self.x_l)
+ self.coordinates.append(self.x_l)
+
+ return None
+
+ def print_geometry(self, round):
+ """
+ Print all the declared geometry to the terminal.
+ """
+ # Print all our basic geometry, useful for debugging
+ print('Chord length')
+ print(self.chord)
+ print('x_c the x-coordinates of the mean camber line')
+ print(np.around(self.x_c, round))
+ print('y_c the y-coordinates of the mean camber line')
+ print(np.around(self.y_c, round))
+ print('y_t the y-coordinates of the airfoil thickness')
+ print(np.around(self.y_t, round))
+ print('dy_c the derivative of y_c with respect to dx')
+ print(np.around(self.dy_c, round))
+ print('theta is like an angle, idk')
+ print(np.around(self.theta, round))
+ print('x_u the x-coordinates of the upper airfoil surface')
+ print(np.around(self.x_u, round))
+ print('y_u the y-coordinates of the upper airfoil surface')
+ print(np.around(self.y_u, round))
+ print('x_l the x-coordinates of the lower airfoil surface')
+ print(np.around(self.x_l, round))
+ print('y_l the y-coordinates of lower airfoil surface')
+ print(np.around(self.y_l, round))
+ return None
+
+ def save_values(self, airfoil_number, save_dir_path):
+ """
+ Save all the declared geometry to save_dir_path (must be full path).
+ """
+ file_name = 'airfoil_%s' % airfoil_number
+ full_path = os.path.join(save_dir_path, file_name + '.txt')
+ file = open(full_path, 'w')
+ sys.stdout = file
+ self.print_geometry(4)
+ return None
+
+
+class Spar(Coordinates):
+ """Contains a single spar's location and material."""
+ global parent
+
+ def __init__(self):
+ super().__init__(parent.chord, parent.semi_span)
+ # Spar material
+ self.spar_material = []
+
+ def add_spar(self, coordinates, material, spar_x):
+ """
+ Add a single spar at the % chord location given to function.
+
+ Parameters:
+ coordinates: provided by Airfoil.coordinates[x_u, y_u, x_l, y_l].
+ material: spar's material. Assumes homogeneous material.
+ spar_x: spar's location as a % of total chord length.
+
+ Return:
+ None
+ """
+ # Airfoil surface coordinates
+ # unpacked from 'coordinates' (list of lists in 'Airfoil').
+ x_u = coordinates[0]
+ y_u = coordinates[1]
+ x_l = coordinates[2]
+ y_l = coordinates[3]
+ # Scaled spar location with regards to chord
+ loc = spar_x * self.chord
+ # bisect_left: returns index of first value in x_u > loc.
+ # This ensures that the spar coordinates intersect with airfoil surface.
+ spar_x_u = bi.bisect_left(x_u, loc) # index of spar's x_u
+ spar_x_l = bi.bisect_left(x_l, loc) # index of spar's x_l
+ # These x and y coordinates are assigned to the spar, NOT airfoil.
+ self.x_u.append(x_u[spar_x_u])
+ self.y_u.append(y_u[spar_x_u])
+ self.x_l.append(x_l[spar_x_l])
+ self.y_l.append(y_l[spar_x_l])
+ self.spar_material = material
+ return None
+
+
+class Stringer():
+ """Contains the coordinates of stringer(s) location and material."""
+
+ def __init__(self):
+ # Stringer attributes
+ self.stringer_x_u = []
+ self.stringer_y_u = []
+ self.stringer_x_l = []
+ self.stringer_y_l = []
+ self.stringer_mat = []
+
+ def add_stringers(self, material, *density):
+ """
+ Add stringers to the wing from their distribution density between spars.
+ First half of density[] concerns stringer distribution on
+
+ Parameters:
+ material: stringer material
+ *density:
+
+ """
+
+ # Find interval between leading edge and first upper stringer,
+ # from density parameter den_u_1.
+ interval = self.spar_x_u[0] / (den_u_1 * self.spar_x_u[0])
+ # initialise first self.stringer_x_u at first interval.
+ x = interval
+ # Add upper stringers until first spar.
+ while x < self.spar_x_u[0]:
+ # Index of the first value of self.x_u > x
+ x_u = bi.bisect_left(self.x_u, x)
+ self.stringer_x_u.append(self.x_u[x_u])
+ self.stringer_y_u.append(self.y_u[x_u])
+ x += interval
+
+ # Find interval between leading edge and first lower stringer,
+ # from density parameter den_l_1.
+ interval = self.spar_x_u[0] / (den_l_1 * self.spar_x_u[0])
+ # initialise first self.stringer_x_l at first interval.
+ x = interval
+ # Add lower stringers until first spar.
+ while x < self.spar_x_l[0]:
+ # Index of the first value of self.x_l > x
+ x_u = bi.bisect_left(self.x_l, x)
+ self.stringer_x_l.append(self.x_l[x_u])
+ self.stringer_y_l.append(self.y_l[x_u])
+ x += interval
+ return None
+
+
+def plot(airfoil, spar):
+ """This function plots the elements passed as arguments."""
+
+ print('Plotting airfoil.')
+ # Plot chord
+ x_chord = [0, airfoil.chord]
+ y_chord = [0, 0]
+ plt.plot(x_chord, y_chord, linewidth='1')
+ # Plot mean camber line
+ plt.plot(airfoil.x_c,
+ airfoil.y_c,
+ '-.',
+ color='r',
+ linewidth='2',
+ label='mean camber line')
+ # Plot upper surface
+ plt.plot(airfoil.x_u, airfoil.y_u, '', color='b', linewidth='1')
+ # Plot lower surface
+ plt.plot(airfoil.x_l, airfoil.y_l, '', color='b', linewidth='1')
+ # Plot spars
+ try:
+ for _ in range(0, len(spar.x_u)):
+ x = (spar.spar_x_u[_], spar.spar_x_l[_])
+ y = (spar.spar_y_u[_], spar.spar_y_l[_])
+ plt.plot(x, y, '.-', color='b', label='spar')
+ plt.legend()
+ except:
+ print('Did plot spars. Were they added?')
+ # Plot stringers
+ # if len(self.spar_x) != 0:
+ # for _ in range(0, len(self.stringer_x)):
+ # x = (self.stringer_x[_], self.stringer_x[_])
+ # y = (self.stringer_y_u[_], self.stringer_y_l[_])
+ # plt.scatter(x, y, color='y', linewidth='1',
+ # else:
+ # print('Unable to plot stringers. Were they created?')
+ # Graph formatting
+ plt.gcf().set_size_inches(9, 2.2)
+ plt.xlabel('X axis')
+ plt.ylabel('Y axis')
+ # plt.gcf().set_size_inches(self.chord, max(self.y_u) - min(self.y_l))
+ plt.grid(axis='both', linestyle=':', linewidth=1)
+ plt.show()
+ return None
+
+
+def main():
+ return None
+
+
+if __name__ == '__main__':
+ main()
Copyright 2019--2024 Marius PETER