From 7c34c1421655f2bdabb85bd65ef7c9ca0496bf9a Mon Sep 17 00:00:00 2001 From: Marius Peter Date: Wed, 12 Jun 2019 12:46:35 -0700 Subject: begin work on evaluator --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 5d2e90a..2a8fdbe 100644 --- a/main.py +++ b/main.py @@ -21,7 +21,7 @@ import random import time start_time = time.time() -CHORD_LENGTH = 100 +CHORD_LENGTH = 140 SEMI_SPAN = 200 POP_SIZE = 1 -- cgit v1.2.3 From 47b6bcca3455ea0d0e3996d31188ac34dcaa1f49 Mon Sep 17 00:00:00 2001 From: Marius Peter Date: Wed, 12 Jun 2019 14:52:49 -0700 Subject: proportional graph plotting --- creator.py | 7 +++---- main.py | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/creator.py b/creator.py index eac3918..aa8001f 100644 --- a/creator.py +++ b/creator.py @@ -372,8 +372,8 @@ def plot(airfoil, spar, stringer): airfoil.y_c, '-.', color='r', - linewidth='2', - label='mean camber line') + linewidth='2') + # label='mean camber line') # Plot upper surface plt.plot(airfoil.x_u, airfoil.y_u, '', color='b', linewidth='1') # Plot lower surface @@ -405,10 +405,9 @@ def plot(airfoil, spar, stringer): print('Unable to plot stringers. Were they created?') # Graph formatting - plt.gcf().set_size_inches(9, 2.2) + plt.gca().set_aspect('equal', adjustable='box') 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 diff --git a/main.py b/main.py index 2a8fdbe..9278eeb 100644 --- a/main.py +++ b/main.py @@ -21,7 +21,7 @@ import random import time start_time = time.time() -CHORD_LENGTH = 140 +CHORD_LENGTH = 1000 SEMI_SPAN = 200 POP_SIZE = 1 -- cgit v1.2.3 From 09fd81eac3f5861b043e00d4865fa205deba5319 Mon Sep 17 00:00:00 2001 From: Marius Peter Date: Wed, 12 Jun 2019 15:13:23 -0700 Subject: debug messages for plotting and saving coordinates --- creator.py | 18 +++++++++++------- main.py | 9 +++++---- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/creator.py b/creator.py index aa8001f..a2626d0 100644 --- a/creator.py +++ b/creator.py @@ -92,12 +92,16 @@ class Coordinates: file_name = '{}_{}.txt'.format(self, number) full_path = os.path.join(save_dir_path, file_name) - # sys.stdout = open(full_path, 'w') - # self.print_coord(2) - with open(full_path, 'w') as sys.stdout: - self.print_coord(2) - # Following line required to reset value of sys.stdout - sys.stdout = sys.__stdout__ + try: + with open(full_path, 'w') as sys.stdout: + self.print_coord(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 @@ -385,7 +389,7 @@ def plot(airfoil, spar, stringer): x = (spar.x_u[_], spar.x_l[_]) y = (spar.y_u[_], spar.y_l[_]) plt.plot(x, y, '.-', color='b') - plt.legend() + # plt.legend() except: print('Did not plot spars. Were they added?') diff --git a/main.py b/main.py index 9278eeb..b842e9e 100644 --- a/main.py +++ b/main.py @@ -34,28 +34,29 @@ def main(): # Interate through all wings in population. for _ in range(1, POP_SIZE + 1): + # Create airfoil instance af = creator.Airfoil() # Define NACA airfoil coordinates af.add_naca(2412) - af.print_coord(2) + # af.print_coord(2) # Create spar instance af.spar = creator.Spar() # Define the spar coordinates, stored in single spar object af.spar.add(af.coord, 0.15) af.spar.add(af.coord, 0.55) - af.spar.print_coord(2) + # af.spar.print_coord(2) # Create stringer instance af.stringer = creator.Stringer() # Define the stringer coordinates from their amount af.stringer.add(af.coord, af.spar.coord, 4, 7, 5, 6) # Print coordinates of af.stringer to terminal - af.stringer.print_coord(2) + # af.stringer.print_coord(2) # Plot components with matplotlib - creator.plot(af, af.spar, af.stringer) + # creator.plot(af, af.spar, af.stringer) # Save component coordinates af.save_coord(SAVE_PATH, _) -- cgit v1.2.3 From b7a4c9728438b075e741f856fe3285bada26bb44 Mon Sep 17 00:00:00 2001 From: Marius Peter Date: Thu, 13 Jun 2019 13:14:04 -0700 Subject: add_mass method in class Coordinates --- creator.py | 12 +++++++++--- main.py | 29 ++++++++++++++++++----------- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/creator.py b/creator.py index a2626d0..8b6b8ae 100644 --- a/creator.py +++ b/creator.py @@ -50,6 +50,8 @@ class Coordinates: if chord < 10: self.chord = 10 self.semi_span = semi_span + # mass + self.mass = float() # Component material self.material = str() # Upper coordinates @@ -60,6 +62,7 @@ class Coordinates: self.y_l = [] # Coordinates x_u, y_u, x_l, y_l packed in single list self.coord = [] + # The airfoil components know the Coordinates instance's coords global parent parent = self @@ -67,6 +70,9 @@ class Coordinates: def __str__(self): return type(self).__name__ + def add_mass(self, mass): + self.mass = len(self.x_u) * mass + def print_coord(self, round): """ Print all the component's coordinates to the terminal. @@ -249,7 +255,7 @@ class Spar(Coordinates): def __init__(self): super().__init__(parent.chord, parent.semi_span) - def add(self, airfoil_coord, spar_x): + def add_coord(self, airfoil_coord, spar_x): """ Add a single spar at the % chord location given to function. @@ -290,8 +296,8 @@ class Stringer(Coordinates): def __init__(self): super().__init__(parent.chord, parent.semi_span) - def add(self, airfoil_coord, spar_coord, stringer_u_1, stringer_u_2, - stringer_l_1, stringer_l_2): + 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). diff --git a/main.py b/main.py index b842e9e..06dd233 100644 --- a/main.py +++ b/main.py @@ -21,9 +21,14 @@ import random import time start_time = time.time() -CHORD_LENGTH = 1000 +CHORD_LENGTH = 10 SEMI_SPAN = 200 +# masss +AIRFOIL_MASS = 100 # lbs +SPAR_MASS = 10 # lbs +STRINGER_MASS = 5 # lbs + POP_SIZE = 1 SAVE_PATH = 'C:/Users/blend/github/UCLA_MAE_154B/save' @@ -37,23 +42,25 @@ def main(): # Create airfoil instance af = creator.Airfoil() - # Define NACA airfoil coordinates + # Define NACA airfoil coordinates and mass af.add_naca(2412) - # af.print_coord(2) + af.add_mass(AIRFOIL_MASS) + af.print_coord(2) # Create spar instance af.spar = creator.Spar() - # Define the spar coordinates, stored in single spar object - af.spar.add(af.coord, 0.15) - af.spar.add(af.coord, 0.55) - # af.spar.print_coord(2) + # Define the spar coordinates and mass, stored in single spar object + af.spar.add_coord(af.coord, 0.15) + af.spar.add_coord(af.coord, 0.55) + af.spar.add_mass(SPAR_MASS) + af.spar.print_coord(2) # Create stringer instance af.stringer = creator.Stringer() - # Define the stringer coordinates from their amount - af.stringer.add(af.coord, af.spar.coord, 4, 7, 5, 6) - # Print coordinates of af.stringer to terminal - # af.stringer.print_coord(2) + # Compute the stringer coordinates from their quantity in each zone + af.stringer.add_coord(af.coord, af.spar.coord, 4, 7, 5, 6) + af.stringer.add_mass(STRINGER_MASS) + af.stringer.print_coord(2) # Plot components with matplotlib # creator.plot(af, af.spar, af.stringer) -- cgit v1.2.3 From 4bee0b332ed9c305ba40854fdecf2d7ab8a574ef Mon Sep 17 00:00:00 2001 From: Marius Peter Date: Thu, 13 Jun 2019 14:41:59 -0700 Subject: add_mass for each component class --- __init__.py | 4 ++-- creator.py | 32 +++++++++++++++++++------------- main.py | 16 ++++++++-------- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/__init__.py b/__init__.py index 5e916af..dc031d0 100644 --- a/__init__.py +++ b/__init__.py @@ -13,5 +13,5 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . __author__ = "Marius Peter" -__version__ = "2.3" -__revision__ = "2.3.1" +# __version__ = "2.3" +# __revision__ = "2.3.1" diff --git a/creator.py b/creator.py index 8b6b8ae..3cc10d1 100644 --- a/creator.py +++ b/creator.py @@ -70,10 +70,7 @@ class Coordinates: def __str__(self): return type(self).__name__ - def add_mass(self, mass): - self.mass = len(self.x_u) * mass - - def print_coord(self, round): + def print_info(self, round): """ Print all the component's coordinates to the terminal. @@ -83,15 +80,15 @@ class Coordinates: 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('y_u the upper y-coordinates:\n', np.around(self.y_u, round)) print('x_l the lower x-coordinates:\n', np.around(self.x_l, round)) print('y_l the lower y-coordinates:\n', np.around(self.y_l, round)) - # print('\n') return None - def save_coord(self, save_dir_path, number): + def save_info(self, save_dir_path, number): """ Save all the object's coordinates (must be full path). """ @@ -100,7 +97,7 @@ class Coordinates: full_path = os.path.join(save_dir_path, file_name) try: with open(full_path, 'w') as sys.stdout: - self.print_coord(2) + 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)) @@ -112,7 +109,7 @@ class Coordinates: return None - def pack_coord(self): + def pack_info(self): self.coord.append(self.x_u) self.coord.append(self.y_u) self.coord.append(self.x_l) @@ -244,9 +241,12 @@ class Airfoil(Coordinates): self.x_l.append(get_lower_coordinates(x)[0]) self.y_l.append(get_lower_coordinates(x)[1]) - super().pack_coord() + super().pack_info() return None + def add_mass(self, mass): + self.mass = mass + class Spar(Coordinates): """Contains a single spar's location.""" @@ -285,9 +285,12 @@ class Spar(Coordinates): self.x_l.append(x_l[spar_x_l]) self.y_l.append(y_l[spar_x_l]) - super().pack_coord() + 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.""" @@ -296,8 +299,8 @@ class Stringer(Coordinates): def __init__(self): super().__init__(parent.chord, parent.semi_span) - def add_coord(self, airfoil_coord, spar_coord, stringer_u_1, stringer_u_2, - stringer_l_1, stringer_l_2): + 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). @@ -365,9 +368,12 @@ class Stringer(Coordinates): self.y_l.append(airfoil_y_l[index]) x += interval - super().pack_coord() + super().pack_info() return None + def add_mass(self, mass): + self.mass = len(self.x_u) * mass + len(self.x_l) * mass + def plot(airfoil, spar, stringer): """This function plots the elements passed as arguments.""" diff --git a/main.py b/main.py index 06dd233..a530730 100644 --- a/main.py +++ b/main.py @@ -21,7 +21,7 @@ import random import time start_time = time.time() -CHORD_LENGTH = 10 +CHORD_LENGTH = 100 SEMI_SPAN = 200 # masss @@ -45,7 +45,7 @@ def main(): # Define NACA airfoil coordinates and mass af.add_naca(2412) af.add_mass(AIRFOIL_MASS) - af.print_coord(2) + af.print_info(2) # Create spar instance af.spar = creator.Spar() @@ -53,22 +53,22 @@ def main(): af.spar.add_coord(af.coord, 0.15) af.spar.add_coord(af.coord, 0.55) af.spar.add_mass(SPAR_MASS) - af.spar.print_coord(2) + af.spar.print_info(2) # Create stringer instance af.stringer = creator.Stringer() # Compute the stringer coordinates from their quantity in each zone af.stringer.add_coord(af.coord, af.spar.coord, 4, 7, 5, 6) af.stringer.add_mass(STRINGER_MASS) - af.stringer.print_coord(2) + af.stringer.print_info(2) # Plot components with matplotlib # creator.plot(af, af.spar, af.stringer) - # Save component coordinates - af.save_coord(SAVE_PATH, _) - af.spar.save_coord(SAVE_PATH, _) - af.stringer.save_coord(SAVE_PATH, _) + # Save component info + af.save_info(SAVE_PATH, _) + af.spar.save_info(SAVE_PATH, _) + af.stringer.save_info(SAVE_PATH, _) # Print final execution time print("--- %s seconds ---" % (time.time() - start_time)) -- cgit v1.2.3 From 99362c09681f5382470f36fed4a6caf6948a84e1 Mon Sep 17 00:00:00 2001 From: Marius Peter Date: Thu, 13 Jun 2019 16:34:46 -0700 Subject: start work on evaluator for real though --- creator.py | 83 +++++++++++++++++++++++++++++++----------------------------- evaluator.py | 6 ++++- main.py | 3 ++- 3 files changed, 50 insertions(+), 42 deletions(-) diff --git a/creator.py b/creator.py index 3cc10d1..138fffb 100644 --- a/creator.py +++ b/creator.py @@ -19,8 +19,6 @@ 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 # This variable is required for main.py constant wing dimensions # to be passed to inheriting classes (Airfoil, Spar, Stringer, Rib). @@ -50,17 +48,18 @@ class Coordinates: if chord < 10: self.chord = 10 self.semi_span = semi_span - # mass + # mass and area self.mass = float() + self.area = float() # Component material self.material = str() # Upper coordinates self.x_u = [] - self.y_u = [] + self.z_u = [] # Lower coordinates self.x_l = [] - self.y_l = [] - # Coordinates x_u, y_u, x_l, y_l packed in single list + 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 @@ -70,6 +69,10 @@ class Coordinates: def __str__(self): return type(self).__name__ + def add_area(self, area): + self.area = area + return None + def print_info(self, round): """ Print all the component's coordinates to the terminal. @@ -83,9 +86,9 @@ class Coordinates: print('Mass:', self.mass) print('============================') print('x_u the upper x-coordinates:\n', np.around(self.x_u, round)) - print('y_u the upper y-coordinates:\n', np.around(self.y_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('y_l the lower y-coordinates:\n', np.around(self.y_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): @@ -106,14 +109,14 @@ class Coordinates: .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.y_u) + self.coord.append(self.z_u) self.coord.append(self.x_l) - self.coord.append(self.y_l) + self.coord.append(self.z_l) + return None class Airfoil(Coordinates): @@ -209,25 +212,25 @@ class Airfoil(Coordinates): def get_upper_coordinates(x): x_u = float() - y_u = float() + z_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]) + 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]) - y_u = 0 # Make upper curve finish at y = 0 - return (x_u, y_u) + z_u = 0 # Make upper curve finish at y = 0 + return (x_u, z_u) def get_lower_coordinates(x): x_l = float() - y_l = float() + z_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])) + 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])) - y_l = 0 # Make lower curve finish at y = 0 - return (x_l, y_l) + 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): @@ -237,9 +240,9 @@ class Airfoil(Coordinates): 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.z_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.z_l.append(get_lower_coordinates(x)[1]) super().pack_info() return None @@ -260,7 +263,7 @@ class Spar(Coordinates): 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]. + 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. @@ -270,9 +273,9 @@ class Spar(Coordinates): # Airfoil surface coordinates # unpacked from 'coordinates' (list of lists in 'Coordinates'). x_u = airfoil_coord[0] - y_u = airfoil_coord[1] + z_u = airfoil_coord[1] x_l = airfoil_coord[2] - y_l = airfoil_coord[3] + 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. @@ -281,9 +284,9 @@ class Spar(Coordinates): 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.z_u.append(z_u[spar_x_u]) self.x_l.append(x_l[spar_x_l]) - self.y_l.append(y_l[spar_x_l]) + self.z_l.append(z_l[spar_x_l]) super().pack_info() return None @@ -318,16 +321,16 @@ class Stringer(Coordinates): # Airfoil surface coordinates # unpacked from 'coordinates' (list of lists in 'Coordinates'). airfoil_x_u = airfoil_coord[0] - airfoil_y_u = airfoil_coord[1] + airfoil_z_u = airfoil_coord[1] airfoil_x_l = airfoil_coord[2] - airfoil_y_l = airfoil_coord[3] + airfoil_z_l = airfoil_coord[3] # Spar coordinates # unpacked from 'coordinates' (list of lists in 'Coordinates'). try: spar_x_u = spar_coord[0] - spar_y_u = spar_coord[1] + spar_z_u = spar_coord[1] spar_x_l = spar_coord[2] - spar_y_l = spar_coord[3] + spar_z_l = spar_coord[3] except: print('Unable to initialize stringers. Were spars created?') # Find distance between leading edge and first upper stringer @@ -339,7 +342,7 @@ class Stringer(Coordinates): # 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.y_u.append(airfoil_y_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) @@ -347,7 +350,7 @@ class Stringer(Coordinates): for _ in range(0, stringer_u_2): index = bi.bisect_left(airfoil_x_u, x) self.x_u.append(airfoil_x_u[index]) - self.y_u.append(airfoil_y_u[index]) + self.z_u.append(airfoil_z_u[index]) x += interval # Find distance between leading edge and first lower stringer @@ -357,7 +360,7 @@ class Stringer(Coordinates): for _ in range(0, stringer_l_1): index = bi.bisect_left(airfoil_x_l, x) self.x_l.append(airfoil_x_l[index]) - self.y_l.append(airfoil_y_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) @@ -365,7 +368,7 @@ class Stringer(Coordinates): for _ in range(0, stringer_l_2): index = bi.bisect_left(airfoil_x_l, x) self.x_l.append(airfoil_x_l[index]) - self.y_l.append(airfoil_y_l[index]) + self.z_l.append(airfoil_z_l[index]) x += interval super().pack_info() @@ -391,15 +394,15 @@ def plot(airfoil, spar, stringer): linewidth='2') # label='mean camber line') # Plot upper surface - plt.plot(airfoil.x_u, airfoil.y_u, '', color='b', linewidth='1') + plt.plot(airfoil.x_u, airfoil.z_u, '', color='b', linewidth='1') # Plot lower surface - plt.plot(airfoil.x_l, airfoil.y_l, '', color='b', linewidth='1') + 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.y_u[_], spar.y_l[_]) + y = (spar.z_u[_], spar.z_l[_]) plt.plot(x, y, '.-', color='b') # plt.legend() except: @@ -410,12 +413,12 @@ def plot(airfoil, spar, stringer): # Upper stringers for _ in range(0, len(stringer.x_u)): x = stringer.x_u[_] - y = stringer.y_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.y_l[_] + y = stringer.z_l[_] plt.plot(x, y, '.', color='y') except: print('Unable to plot stringers. Were they created?') @@ -423,7 +426,7 @@ def plot(airfoil, spar, stringer): # Graph formatting plt.gca().set_aspect('equal', adjustable='box') plt.xlabel('X axis') - plt.ylabel('Y axis') + plt.ylabel('Z axis') plt.grid(axis='both', linestyle=':', linewidth=1) plt.show() return None diff --git a/evaluator.py b/evaluator.py index 6a87b7e..180d49a 100644 --- a/evaluator.py +++ b/evaluator.py @@ -13,4 +13,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import creator +# F_z = + + +def get_centroid(airfoil): + pass diff --git a/main.py b/main.py index a530730..e0921c2 100644 --- a/main.py +++ b/main.py @@ -35,6 +35,7 @@ SAVE_PATH = 'C:/Users/blend/github/UCLA_MAE_154B/save' def main(): # Create coordinate system specific to our airfoil dimensions. + # TODO: imperial + metric unit setting creator.Coordinates(CHORD_LENGTH, SEMI_SPAN) # Interate through all wings in population. @@ -63,7 +64,7 @@ def main(): af.stringer.print_info(2) # Plot components with matplotlib - # creator.plot(af, af.spar, af.stringer) + creator.plot(af, af.spar, af.stringer) # Save component info af.save_info(SAVE_PATH, _) -- cgit v1.2.3 From ab880773c095e7e9249f14435a9e03f4af328364 Mon Sep 17 00:00:00 2001 From: Marius Peter Date: Thu, 13 Jun 2019 16:49:56 -0700 Subject: main.py function docstrings --- evaluator.py | 5 +++++ main.py | 25 ++++++++++++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/evaluator.py b/evaluator.py index 180d49a..f3a05f4 100644 --- a/evaluator.py +++ b/evaluator.py @@ -18,3 +18,8 @@ def get_centroid(airfoil): pass + + +def get_mass(*component): + for _ in len(component): + mass += component.mass diff --git a/main.py b/main.py index e0921c2..1e591fd 100644 --- a/main.py +++ b/main.py @@ -15,7 +15,7 @@ import creator # Create geometry import evaluator # Evaluate geometry -import generator # Iteratevely evaluate instances of geometry +import generator # Iteratevely evaluate instances of geometry and optimize import random import time @@ -24,16 +24,19 @@ start_time = time.time() CHORD_LENGTH = 100 SEMI_SPAN = 200 -# masss +# mass AIRFOIL_MASS = 100 # lbs SPAR_MASS = 10 # lbs STRINGER_MASS = 5 # lbs +# population information POP_SIZE = 1 SAVE_PATH = 'C:/Users/blend/github/UCLA_MAE_154B/save' -def main(): +def create(): + '''Create an airfoil.''' + # Create coordinate system specific to our airfoil dimensions. # TODO: imperial + metric unit setting creator.Coordinates(CHORD_LENGTH, SEMI_SPAN) @@ -75,5 +78,21 @@ def main(): print("--- %s seconds ---" % (time.time() - start_time)) +def evaluate(): + '''Evaluate previously created airfoil(s).''' + pass + + +def generate(): + '''Iteratively evaluate airfoils by defining genetic generations.''' + pass + + +def main(): + create() + evaluate() + generate() + + if __name__ == '__main__': main() -- cgit v1.2.3 From 18fd5385ec0ff1f303b38802d22cfec357f58b62 Mon Sep 17 00:00:00 2001 From: Marius Peter Date: Thu, 13 Jun 2019 17:40:44 -0700 Subject: stringer areas --- creator.py | 17 ++++++++++++----- evaluator.py | 8 +++++--- main.py | 48 +++++++++++++++++++++++------------------------- 3 files changed, 40 insertions(+), 33 deletions(-) diff --git a/creator.py b/creator.py index 138fffb..c7bbf03 100644 --- a/creator.py +++ b/creator.py @@ -69,10 +69,6 @@ class Coordinates: def __str__(self): return type(self).__name__ - def add_area(self, area): - self.area = area - return None - def print_info(self, round): """ Print all the component's coordinates to the terminal. @@ -301,6 +297,7 @@ class Stringer(Coordinates): 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): @@ -333,6 +330,7 @@ class Stringer(Coordinates): 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 @@ -370,12 +368,21 @@ class Stringer(Coordinates): 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): diff --git a/evaluator.py b/evaluator.py index f3a05f4..4a65a4e 100644 --- a/evaluator.py +++ b/evaluator.py @@ -20,6 +20,8 @@ def get_centroid(airfoil): pass -def get_mass(*component): - for _ in len(component): - mass += component.mass +def get_total_mass(self, *component): + total_mass = float() + for _ in component: + total_mass += _.mass + return total_mass diff --git a/main.py b/main.py index 1e591fd..be49d2f 100644 --- a/main.py +++ b/main.py @@ -24,18 +24,25 @@ start_time = time.time() CHORD_LENGTH = 100 SEMI_SPAN = 200 -# mass +# m=Mass AIRFOIL_MASS = 100 # lbs SPAR_MASS = 10 # lbs STRINGER_MASS = 5 # lbs +# Area +STRINGER_AREA = 0.1 # sqin + # population information POP_SIZE = 1 SAVE_PATH = 'C:/Users/blend/github/UCLA_MAE_154B/save' -def create(): - '''Create an airfoil.''' +def main(): + ''' + Create an airfoil; + Evaluate an airfoil; + Generate a population of airfoils & optimize. + ''' # Create coordinate system specific to our airfoil dimensions. # TODO: imperial + metric unit setting @@ -49,7 +56,7 @@ def create(): # Define NACA airfoil coordinates and mass af.add_naca(2412) af.add_mass(AIRFOIL_MASS) - af.print_info(2) + # af.print_info(2) # Create spar instance af.spar = creator.Spar() @@ -57,41 +64,32 @@ def create(): af.spar.add_coord(af.coord, 0.15) af.spar.add_coord(af.coord, 0.55) af.spar.add_mass(SPAR_MASS) - af.spar.print_info(2) + # af.spar.print_info(2) # Create stringer instance af.stringer = creator.Stringer() # Compute the stringer coordinates from their quantity in each zone af.stringer.add_coord(af.coord, af.spar.coord, 4, 7, 5, 6) + af.stringer.add_area(STRINGER_AREA) af.stringer.add_mass(STRINGER_MASS) af.stringer.print_info(2) # Plot components with matplotlib - creator.plot(af, af.spar, af.stringer) + # creator.plot(af, af.spar, af.stringer) # Save component info - af.save_info(SAVE_PATH, _) - af.spar.save_info(SAVE_PATH, _) - af.stringer.save_info(SAVE_PATH, _) - - # Print final execution time - print("--- %s seconds ---" % (time.time() - start_time)) - + # af.save_info(SAVE_PATH, _) + # af.spar.save_info(SAVE_PATH, _) + # af.stringer.save_info(SAVE_PATH, _) -def evaluate(): - '''Evaluate previously created airfoil(s).''' - pass + # Evaluate previously created airfoil(s). + total_mass = evaluator.get_total_mass(af, af.spar, af.stringer) + # Iteratively evaluate airfoils by defining genetic generations. + # pass -def generate(): - '''Iteratively evaluate airfoils by defining genetic generations.''' - pass - - -def main(): - create() - evaluate() - generate() + # Print final execution time + print("--- %s seconds ---" % (time.time() - start_time)) if __name__ == '__main__': -- cgit v1.2.3 From 58c8a564e87f6325d66972d6c971eea8c465ba2e Mon Sep 17 00:00:00 2001 From: Marius Peter Date: Mon, 17 Jun 2019 18:07:25 -0700 Subject: commence work on centroid --- creator.py | 894 +++++++++++++++++++++++++++++------------------------------ evaluator.py | 9 +- main.py | 12 +- 3 files changed, 461 insertions(+), 454 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 . - -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 . + +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() diff --git a/evaluator.py b/evaluator.py index 4a65a4e..69a589a 100644 --- a/evaluator.py +++ b/evaluator.py @@ -17,7 +17,14 @@ def get_centroid(airfoil): - pass + area = airfoil.stringer.area + numerator = float() + for _ in airfoil.stringer.x_u: + numerator += _ * area + for _ in airfoil.stringer.x_l: + numerator += _ * area + denominator + # z_c = def get_total_mass(self, *component): diff --git a/main.py b/main.py index be49d2f..ce69f6b 100644 --- a/main.py +++ b/main.py @@ -56,7 +56,7 @@ def main(): # Define NACA airfoil coordinates and mass af.add_naca(2412) af.add_mass(AIRFOIL_MASS) - # af.print_info(2) + af.print_info(2) # Create spar instance af.spar = creator.Spar() @@ -64,7 +64,7 @@ def main(): af.spar.add_coord(af.coord, 0.15) af.spar.add_coord(af.coord, 0.55) af.spar.add_mass(SPAR_MASS) - # af.spar.print_info(2) + af.spar.print_info(2) # Create stringer instance af.stringer = creator.Stringer() @@ -75,12 +75,12 @@ def main(): af.stringer.print_info(2) # Plot components with matplotlib - # creator.plot(af, af.spar, af.stringer) + creator.plot(af, af.spar, af.stringer) # Save component info - # af.save_info(SAVE_PATH, _) - # af.spar.save_info(SAVE_PATH, _) - # af.stringer.save_info(SAVE_PATH, _) + af.save_info(SAVE_PATH, _) + af.spar.save_info(SAVE_PATH, _) + af.stringer.save_info(SAVE_PATH, _) # Evaluate previously created airfoil(s). total_mass = evaluator.get_total_mass(af, af.spar, af.stringer) -- cgit v1.2.3