summaryrefslogtreecommitdiff
path: root/creator.py
diff options
context:
space:
mode:
Diffstat (limited to 'creator.py')
-rw-r--r--creator.py894
1 files changed, 447 insertions, 447 deletions
diff --git a/creator.py b/creator.py
index c7bbf03..634277e 100644
--- a/creator.py
+++ b/creator.py
@@ -1,447 +1,447 @@
-# 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
-
-# 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 airfoil components need the following:
-
- Parameters:
- * Component material
- * Coordinates relative to the chord & semi-span.
-
- Methods:
- * Print component coordinates
- * Save component coordinates to file specified in main.py
-
- So, all component classes inherit from class Coordinates.
- """
-
- def __init__(self, chord, semi_span):
- # Global dimensions
- self.chord = chord
- if chord < 10:
- self.chord = 10
- self.semi_span = semi_span
- # mass and area
- self.mass = float()
- self.area = float()
- # Component material
- self.material = str()
- # Upper coordinates
- self.x_u = []
- self.z_u = []
- # Lower coordinates
- self.x_l = []
- self.z_l = []
- # Coordinates x_u, z_u, x_l, z_l packed in single list
- self.coord = []
-
- # The airfoil components know the Coordinates instance's coords
- global parent
- parent = self
-
- def __str__(self):
- return type(self).__name__
-
- def print_info(self, round):
- """
- Print all the component's coordinates to the terminal.
-
- This function's output is piped to the 'save_coord' function below.
- """
- print('============================')
- print('Component:', str(self))
- print('Chord length:', self.chord)
- print('Semi-span:', self.semi_span)
- print('Mass:', self.mass)
- print('============================')
- print('x_u the upper x-coordinates:\n', np.around(self.x_u, round))
- print('z_u the upper y-coordinates:\n', np.around(self.z_u, round))
- print('x_l the lower x-coordinates:\n', np.around(self.x_l, round))
- print('z_l the lower y-coordinates:\n', np.around(self.z_l, round))
- return None
-
- def save_info(self, save_dir_path, number):
- """
- Save all the object's coordinates (must be full path).
- """
-
- file_name = '{}_{}.txt'.format(self, number)
- full_path = os.path.join(save_dir_path, file_name)
- try:
- with open(full_path, 'w') as sys.stdout:
- self.print_info(2)
- # This line required to reset behavior of sys.stdout
- sys.stdout = sys.__stdout__
- print('Successfully wrote to file {}'.format(full_path))
- except:
- print('Unable to write {} to specified directory.\n'
- .format(file_name),
- 'Was the full path passed to the function?')
- # It is cleaner to use this context guard to ensure file is closed
- return None
-
- def pack_info(self):
- self.coord.append(self.x_u)
- self.coord.append(self.z_u)
- self.coord.append(self.x_l)
- self.coord.append(self.z_l)
- return None
-
-
-class Airfoil(Coordinates):
- """This class enables the creation of a single 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 = [] # Contains only integers from 0 to self.chord
- self.y_c = [] # Contains floats
- # Thickness
- self.y_t = []
- # dy_c / d_x
- self.dy_c = []
- # Theta
- self.theta = []
-
- def add_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
-
- 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
- # 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()
- z_u = float()
- if 0 <= x < self.chord:
- x_u = x - self.y_t[x] * sin(self.theta[x])
- z_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])
- z_u = 0 # Make upper curve finish at y = 0
- return (x_u, z_u)
-
- def get_lower_coordinates(x):
- x_l = float()
- z_l = float()
- if 0 <= x < self.chord:
- x_l = (x + self.y_t[x] * sin(self.theta[x]))
- z_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]))
- z_l = 0 # Make lower curve finish at y = 0
- return (x_l, z_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.z_u.append(get_upper_coordinates(x)[1])
- self.x_l.append(get_lower_coordinates(x)[0])
- self.z_l.append(get_lower_coordinates(x)[1])
-
- super().pack_info()
- return None
-
- def add_mass(self, mass):
- self.mass = mass
-
-
-class Spar(Coordinates):
- """Contains a single spar's location."""
- global parent
-
- def __init__(self):
- super().__init__(parent.chord, parent.semi_span)
-
- def add_coord(self, airfoil_coord, spar_x):
- """
- Add a single spar at the % chord location given to function.
-
- Parameters:
- coordinates: provided by Airfoil.coordinates[x_u, z_u, x_l, z_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 'Coordinates').
- x_u = airfoil_coord[0]
- z_u = airfoil_coord[1]
- x_l = airfoil_coord[2]
- z_l = airfoil_coord[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.z_u.append(z_u[spar_x_u])
- self.x_l.append(x_l[spar_x_l])
- self.z_l.append(z_l[spar_x_l])
-
- super().pack_info()
- return None
-
- def add_mass(self, mass):
- self.mass = len(self.x_u) * mass
-
-
-class Stringer(Coordinates):
- """Contains the coordinates of all stringers."""
- global parent
-
- def __init__(self):
- super().__init__(parent.chord, parent.semi_span)
- self.area = float()
-
- def add_coord(self, airfoil_coord, spar_coord,
- stringer_u_1, stringer_u_2, stringer_l_1, stringer_l_2):
- """
- Add equally distributed stringers to four airfoil locations
- (upper nose, lower nose, upper surface, lower surface).
-
- Parameters:
- stringer_u_1: upper nose number of stringers
- stringer_u_2: upper surface number of stringers
- stringer_l_1: lower nose number of stringers
- stringer_l_2: lower surface number of stringers
-
- Returns:
- None
- """
-
- # Airfoil surface coordinates
- # unpacked from 'coordinates' (list of lists in 'Coordinates').
- airfoil_x_u = airfoil_coord[0]
- airfoil_z_u = airfoil_coord[1]
- airfoil_x_l = airfoil_coord[2]
- airfoil_z_l = airfoil_coord[3]
- # Spar coordinates
- # unpacked from 'coordinates' (list of lists in 'Coordinates').
- try:
- spar_x_u = spar_coord[0]
- spar_z_u = spar_coord[1]
- spar_x_l = spar_coord[2]
- spar_z_l = spar_coord[3]
- except:
- print('Unable to initialize stringers. Were spars created?')
-
- # Find distance between leading edge and first upper stringer
- interval = spar_x_u[0] / (stringer_u_1 + 1)
- # initialise first self.stringer_x_u at first interval
- x = interval
- # Add upper stringers from leading edge until first spar.
- for _ in range(0, stringer_u_1):
- # Index of the first value of airfoil_x_u > x
- index = bi.bisect_left(airfoil_x_u, x)
- self.x_u.append(airfoil_x_u[index])
- self.z_u.append(airfoil_z_u[index])
- x += interval
- # Add upper stringers from first spar until last spar
- interval = (spar_x_u[-1] - spar_x_u[0]) / (stringer_u_2 + 1)
- x = interval + spar_x_u[0]
- for _ in range(0, stringer_u_2):
- index = bi.bisect_left(airfoil_x_u, x)
- self.x_u.append(airfoil_x_u[index])
- self.z_u.append(airfoil_z_u[index])
- x += interval
-
- # Find distance between leading edge and first lower stringer
- interval = spar_x_l[0] / (stringer_l_1 + 1)
- x = interval
- # Add lower stringers from leading edge until first spar.
- for _ in range(0, stringer_l_1):
- index = bi.bisect_left(airfoil_x_l, x)
- self.x_l.append(airfoil_x_l[index])
- self.z_l.append(airfoil_z_l[index])
- x += interval
- # Add lower stringers from first spar until last spar
- interval = (spar_x_l[-1] - spar_x_l[0]) / (stringer_l_2 + 1)
- x = interval + spar_x_l[0]
- for _ in range(0, stringer_l_2):
- index = bi.bisect_left(airfoil_x_l, x)
- self.x_l.append(airfoil_x_l[index])
- self.z_l.append(airfoil_z_l[index])
- x += interval
- super().pack_info()
- return None
-
- def add_area(self, area):
- self.area = area
- return None
-
- def add_mass(self, mass):
- self.mass = len(self.x_u) * mass + len(self.x_l) * mass
- return None
-
- def print_info(self, round):
- super().print_info(round)
- print('Stringer Area:\n', np.around(self.area, round))
- return None
-
-
-def plot(airfoil, spar, stringer):
- """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.z_u, '', color='b', linewidth='1')
- # Plot lower surface
- plt.plot(airfoil.x_l, airfoil.z_l, '', color='b', linewidth='1')
-
- # Plot spars
- try:
- for _ in range(0, len(spar.x_u)):
- x = (spar.x_u[_], spar.x_l[_])
- y = (spar.z_u[_], spar.z_l[_])
- plt.plot(x, y, '.-', color='b')
- # plt.legend()
- except:
- print('Did not plot spars. Were they added?')
-
- # Plot stringers
- try:
- # Upper stringers
- for _ in range(0, len(stringer.x_u)):
- x = stringer.x_u[_]
- y = stringer.z_u[_]
- plt.plot(x, y, '.', color='y')
- # Lower stringers
- for _ in range(0, len(stringer.x_l)):
- x = stringer.x_l[_]
- y = stringer.z_l[_]
- plt.plot(x, y, '.', color='y')
- except:
- print('Unable to plot stringers. Were they created?')
-
- # Graph formatting
- plt.gca().set_aspect('equal', adjustable='box')
- plt.xlabel('X axis')
- plt.ylabel('Z axis')
- plt.grid(axis='both', linestyle=':', linewidth=1)
- plt.show()
- return None
-
-
-def main():
- return None
-
-
-if __name__ == '__main__':
- main()
+# 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
+
+# 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 airfoil components need the following:
+
+ Parameters:
+ * Component material
+ * Coordinates relative to the chord & semi-span.
+
+ Methods:
+ * Print component coordinates
+ * Save component coordinates to file specified in main.py
+
+ So, all component classes inherit from class Coordinates.
+ """
+
+ def __init__(self, chord, semi_span):
+ # Global dimensions
+ self.chord = chord
+ if chord < 10:
+ self.chord = 10
+ self.semi_span = semi_span
+ # mass and area
+ self.mass = float()
+ self.area = float()
+ # Component material
+ self.material = str()
+ # Upper coordinates
+ self.x_u = []
+ self.z_u = []
+ # Lower coordinates
+ self.x_l = []
+ self.z_l = []
+ # Coordinates x_u, z_u, x_l, z_l packed in single list
+ self.coord = []
+
+ # The airfoil components know the Coordinates instance's coords
+ global parent
+ parent = self
+
+ def __str__(self):
+ return type(self).__name__
+
+ def print_info(self, round):
+ """
+ Print all the component's coordinates to the terminal.
+
+ This function's output is piped to the 'save_coord' function below.
+ """
+ print('============================')
+ print('Component:', str(self))
+ print('Chord length:', self.chord)
+ print('Semi-span:', self.semi_span)
+ print('Mass:', self.mass)
+ print('============================')
+ print('x_u the upper x-coordinates:\n', np.around(self.x_u, round))
+ print('z_u the upper y-coordinates:\n', np.around(self.z_u, round))
+ print('x_l the lower x-coordinates:\n', np.around(self.x_l, round))
+ print('z_l the lower y-coordinates:\n', np.around(self.z_l, round))
+ return None
+
+ def save_info(self, save_dir_path, number):
+ """
+ Save all the object's coordinates (must be full path).
+ """
+
+ file_name = '{}_{}.txt'.format(self, number)
+ full_path = os.path.join(save_dir_path, file_name)
+ try:
+ with open(full_path, 'w') as sys.stdout:
+ self.print_info(2)
+ # This line required to reset behavior of sys.stdout
+ sys.stdout = sys.__stdout__
+ print('Successfully wrote to file {}'.format(full_path))
+ except:
+ print('Unable to write {} to specified directory.\n'
+ .format(file_name),
+ 'Was the full path passed to the function?')
+ # It is cleaner to use this context guard to ensure file is closed
+ return None
+
+ def pack_info(self):
+ self.coord.append(self.x_u)
+ self.coord.append(self.z_u)
+ self.coord.append(self.x_l)
+ self.coord.append(self.z_l)
+ return None
+
+
+class Airfoil(Coordinates):
+ """This class enables the creation of a single 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 = [] # Contains only integers from 0 to self.chord
+ self.y_c = [] # Contains floats
+ # Thickness
+ self.y_t = []
+ # dy_c / d_x
+ self.dy_c = []
+ # Theta
+ self.theta = []
+
+ def add_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
+
+ 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
+ # 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()
+ z_u = float()
+ if 0 <= x < self.chord:
+ x_u = x - self.y_t[x] * sin(self.theta[x])
+ z_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])
+ z_u = 0 # Make upper curve finish at y = 0
+ return (x_u, z_u)
+
+ def get_lower_coordinates(x):
+ x_l = float()
+ z_l = float()
+ if 0 <= x < self.chord:
+ x_l = (x + self.y_t[x] * sin(self.theta[x]))
+ z_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]))
+ z_l = 0 # Make lower curve finish at y = 0
+ return (x_l, z_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.z_u.append(get_upper_coordinates(x)[1])
+ self.x_l.append(get_lower_coordinates(x)[0])
+ self.z_l.append(get_lower_coordinates(x)[1])
+
+ super().pack_info()
+ return None
+
+ def add_mass(self, mass):
+ self.mass = mass
+
+
+class Spar(Coordinates):
+ """Contains a single spar's location."""
+ global parent
+
+ def __init__(self):
+ super().__init__(parent.chord, parent.semi_span)
+
+ def add_coord(self, airfoil_coord, spar_x):
+ """
+ Add a single spar at the % chord location given to function.
+
+ Parameters:
+ coordinates: provided by Airfoil.coordinates[x_u, z_u, x_l, z_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 'Coordinates').
+ x_u = airfoil_coord[0]
+ z_u = airfoil_coord[1]
+ x_l = airfoil_coord[2]
+ z_l = airfoil_coord[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.z_u.append(z_u[spar_x_u])
+ self.x_l.append(x_l[spar_x_l])
+ self.z_l.append(z_l[spar_x_l])
+
+ super().pack_info()
+ return None
+
+ def add_mass(self, mass):
+ self.mass = len(self.x_u) * mass
+
+
+class Stringer(Coordinates):
+ """Contains the coordinates of all stringers."""
+ global parent
+
+ def __init__(self):
+ super().__init__(parent.chord, parent.semi_span)
+ self.area = float()
+
+ def add_coord(self, airfoil_coord, spar_coord,
+ stringer_u_1, stringer_u_2, stringer_l_1, stringer_l_2):
+ """
+ Add equally distributed stringers to four airfoil locations
+ (upper nose, lower nose, upper surface, lower surface).
+
+ Parameters:
+ stringer_u_1: upper nose number of stringers
+ stringer_u_2: upper surface number of stringers
+ stringer_l_1: lower nose number of stringers
+ stringer_l_2: lower surface number of stringers
+
+ Returns:
+ None
+ """
+
+ # Airfoil surface coordinates
+ # unpacked from 'coordinates' (list of lists in 'Coordinates').
+ airfoil_x_u = airfoil_coord[0]
+ airfoil_z_u = airfoil_coord[1]
+ airfoil_x_l = airfoil_coord[2]
+ airfoil_z_l = airfoil_coord[3]
+ # Spar coordinates
+ # unpacked from 'coordinates' (list of lists in 'Coordinates').
+ try:
+ spar_x_u = spar_coord[0]
+ spar_z_u = spar_coord[1]
+ spar_x_l = spar_coord[2]
+ spar_z_l = spar_coord[3]
+ except:
+ print('Unable to initialize stringers. Were spars created?')
+
+ # Find distance between leading edge and first upper stringer
+ interval = spar_x_u[0] / (stringer_u_1 + 1)
+ # initialise first self.stringer_x_u at first interval
+ x = interval
+ # Add upper stringers from leading edge until first spar.
+ for _ in range(0, stringer_u_1):
+ # Index of the first value of airfoil_x_u > x
+ index = bi.bisect_left(airfoil_x_u, x)
+ self.x_u.append(airfoil_x_u[index])
+ self.z_u.append(airfoil_z_u[index])
+ x += interval
+ # Add upper stringers from first spar until last spar
+ interval = (spar_x_u[-1] - spar_x_u[0]) / (stringer_u_2 + 1)
+ x = interval + spar_x_u[0]
+ for _ in range(0, stringer_u_2):
+ index = bi.bisect_left(airfoil_x_u, x)
+ self.x_u.append(airfoil_x_u[index])
+ self.z_u.append(airfoil_z_u[index])
+ x += interval
+
+ # Find distance between leading edge and first lower stringer
+ interval = spar_x_l[0] / (stringer_l_1 + 1)
+ x = interval
+ # Add lower stringers from leading edge until first spar.
+ for _ in range(0, stringer_l_1):
+ index = bi.bisect_left(airfoil_x_l, x)
+ self.x_l.append(airfoil_x_l[index])
+ self.z_l.append(airfoil_z_l[index])
+ x += interval
+ # Add lower stringers from first spar until last spar
+ interval = (spar_x_l[-1] - spar_x_l[0]) / (stringer_l_2 + 1)
+ x = interval + spar_x_l[0]
+ for _ in range(0, stringer_l_2):
+ index = bi.bisect_left(airfoil_x_l, x)
+ self.x_l.append(airfoil_x_l[index])
+ self.z_l.append(airfoil_z_l[index])
+ x += interval
+ super().pack_info()
+ return None
+
+ def add_area(self, area):
+ self.area = area
+ return None
+
+ def add_mass(self, mass):
+ self.mass = len(self.x_u) * mass + len(self.x_l) * mass
+ return None
+
+ def print_info(self, round):
+ super().print_info(round)
+ print('Stringer Area:\n', np.around(self.area, round))
+ return None
+
+
+def plot(airfoil, spar, stringer):
+ """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.z_u, '', color='b', linewidth='1')
+ # Plot lower surface
+ plt.plot(airfoil.x_l, airfoil.z_l, '', color='b', linewidth='1')
+
+ # Plot spars
+ try:
+ for _ in range(0, len(spar.x_u)):
+ x = (spar.x_u[_], spar.x_l[_])
+ y = (spar.z_u[_], spar.z_l[_])
+ plt.plot(x, y, '.-', color='b')
+ # plt.legend()
+ except:
+ print('Did not plot spars. Were they added?')
+
+ # Plot stringers
+ try:
+ # Upper stringers
+ for _ in range(0, len(stringer.x_u)):
+ x = stringer.x_u[_]
+ y = stringer.z_u[_]
+ plt.plot(x, y, '.', color='y')
+ # Lower stringers
+ for _ in range(0, len(stringer.x_l)):
+ x = stringer.x_l[_]
+ y = stringer.z_l[_]
+ plt.plot(x, y, '.', color='y')
+ except:
+ print('Unable to plot stringers. Were they created?')
+
+ # Graph formatting
+ plt.gca().set_aspect('equal', adjustable='box')
+ plt.xlabel('X axis')
+ plt.ylabel('Z axis')
+ plt.grid(axis='both', linestyle=':', linewidth=1)
+ plt.show()
+ return None
+
+
+def main():
+ return None
+
+
+if __name__ == '__main__':
+ main()
Copyright 2019--2024 Marius PETER