diff options
| -rw-r--r-- | __init__.py | 19 | ||||
| -rw-r--r-- | __pycache__/creator.cpython-37.pyc | bin | 0 -> 9785 bytes | |||
| -rw-r--r-- | creator.py | 386 | 
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.pycBinary files differ new file mode 100644 index 0000000..9933593 --- /dev/null +++ b/__pycache__/creator.cpython-37.pyc 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()
 | 
