summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--creator.py217
-rw-r--r--evaluator.py95
-rw-r--r--main.py26
3 files changed, 174 insertions, 164 deletions
diff --git a/creator.py b/creator.py
index 78c537d..6dd3c1c 100644
--- a/creator.py
+++ b/creator.py
@@ -53,14 +53,9 @@ class Coordinates:
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 = []
+ # Coordinates
+ self.x = []
+ self.z = []
# The airfoil components know the Coordinates instance's coords
global parent
@@ -85,10 +80,8 @@ class Coordinates:
print('Semi-span:', self.semi_span)
print('Mass:', self.mass)
print(num_of_dashes * '-')
- print('x_u the upper x-coordinates:\n', np.around(self.x_u, round))
- print('z_u the upper z-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 z-coordinates:\n', np.around(self.z_l, round))
+ print('x-coordinates:\n', np.around(self.x, round))
+ print('z-coordinates:\n', np.around(self.z, round))
return None
def info_save(self, save_path, number):
@@ -112,7 +105,18 @@ class Coordinates:
class Airfoil(Coordinates):
- '''This class enables the creation of a single NACA airfoil.'''
+ '''
+ This class enables the creation of a single NACA airfoil.
+
+ Please note: the coordinates are saved as two lists
+ for the x- and z-coordinates. The coordinates start at
+ the leading edge, travel over the airfoil's upper edge,
+ then loop back to the leading edge via the lower edge.
+
+ This method was chosen for easier future exports
+ to 3D CAD packages like SolidWorks, which can import such
+ geometry as coordinates written in a CSV file.
+ '''
def __init__(self):
global parent
@@ -122,7 +126,7 @@ class Airfoil(Coordinates):
self.naca_num = int()
# Mean camber line
self.x_c = []
- self.y_c = []
+ self.z_c = []
def add_naca(self, naca_num):
'''
@@ -148,22 +152,22 @@ class Airfoil(Coordinates):
def get_camber(x):
'''
- Returns camber y-coordinate from 1 'x' along the airfoil chord.
+ Returns camber z-coordinate from 1 'x' along the airfoil chord.
'''
- y_c = float()
+ z_c = float()
if 0 <= x < p_c:
- y_c = (m / (p ** 2)) * (2 * p * (x / self.chord)
+ z_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)
+ z_c = (m / ((1 - p) ** 2)) * ((1 - 2 * p)
+ 2 * p * (x / self.chord)
- (x / self.chord) ** 2)
- return (y_c * self.chord)
+ return (z_c * self.chord)
def get_thickness(x):
- '''
- Returns thickness from 1 'x' along the airfoil chord.
- '''
+ '''Returns thickness from 1 'x' along the airfoil chord.'''
+
+ x = 0 if x < 0 else x
y_t = 5 * t * self.chord * (
+ 0.2969 * sqrt(x / self.chord)
- 0.1260 * (x / self.chord)
@@ -173,37 +177,43 @@ class Airfoil(Coordinates):
return y_t
def get_theta(x):
- dy_c = float()
+ dz_c = float()
if 0 <= x < p_c:
- dy_c = ((2 * m) / p ** 2) * (p - x / self.chord)
+ dz_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)
- theta = atan(dy_c)
+ dz_c = (2 * m) / ((1 - p) ** 2) * (p - x / self.chord)
+ theta = atan(dz_c)
return theta
def get_upper_coord(x):
- x_u = x - get_thickness(x) * sin(get_theta(x))
- z_u = get_camber(x) + get_thickness(x) * cos(get_theta(x))
- return (x_u, z_u)
+ x = x - get_thickness(x) * sin(get_theta(x))
+ z = get_camber(x) + get_thickness(x) * cos(get_theta(x))
+ return (x, z)
def get_lower_coord(x):
- x_l = x + get_thickness(x) * sin(get_theta(x))
- z_l = get_camber(x) - get_thickness(x) * cos(get_theta(x))
- return (x_l, z_l)
+ x = x + get_thickness(x) * sin(get_theta(x))
+ z = get_camber(x) - get_thickness(x) * cos(get_theta(x))
+ return (x, z)
# Densify x-coordinates 10 times for first 1/4 chord length
x_chord_25_percent = round(self.chord / 4)
- x_chord = [x / 10 for x in range(x_chord_25_percent * 10)]
- x_chord.extend([x for x in range(x_chord_25_percent, self.chord + 1)])
+
+ x_chord = [i / 10 for i in range(x_chord_25_percent * 10)]
+ x_chord.extend(i for i in range(x_chord_25_percent, self.chord + 1))
+ # Reversed list for our lower airfoil coordinate densification
+ x_chord_rev = [i for i in range(self.chord, x_chord_25_percent, -1)]
+ extend = [i / 10 for i in range(x_chord_25_percent * 10, -1, -1)]
+ x_chord_rev.extend(extend)
# Generate our airfoil geometry from previous sub-functions.
for x in x_chord:
self.x_c.append(x)
- self.y_c.append(get_camber(x))
- self.x_u.append(get_upper_coord(x)[0])
- self.z_u.append(get_upper_coord(x)[1])
- self.x_l.append(get_lower_coord(x)[0])
- self.z_l.append(get_lower_coord(x)[1])
+ self.z_c.append(get_camber(x))
+ self.x.append(get_upper_coord(x)[0])
+ self.z.append(get_upper_coord(x)[1])
+ for x in x_chord_rev:
+ self.x.append(get_lower_coord(x)[0])
+ self.z.append(get_lower_coord(x)[1])
return None
def add_mass(self, mass):
@@ -211,8 +221,8 @@ class Airfoil(Coordinates):
def info_print(self, round):
super().info_print(round)
- print('x_c the camber x-coordinates:\n', np.around(self.x_u, round))
- print('z_c the camber z-coordinates:\n', np.around(self.x_u, round))
+ print('x_c the camber x-coordinates:\n', np.around(self.x, round))
+ print('z_c the camber z-coordinates:\n', np.around(self.x, round))
return None
@@ -223,35 +233,33 @@ class Spar(Coordinates):
def __init__(self):
super().__init__(parent.chord, parent.semi_span)
- def add_coord(self, airfoil, spar_x):
+ def add_coord(self, airfoil, x_loc_percent):
'''
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].
+ coordinates: provided by Airfoil.coordinates[x, z, x, z].
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.x_u
- z_u = airfoil.z_u
- x_l = airfoil.x_l
- z_l = airfoil.z_l
+
# Scaled spar location with regards to chord
- loc = spar_x * self.chord
- # bisect_left: returns index of first value in x_u > loc.
- # 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])
+ loc = x_loc_percent * self.chord
+ # bi.bisect_left: returns index of first value in airfoil.x > loc
+ # This ensures that spar geom intersects with airfoil geom.
+ # Spar upper coordinates
+ spar_x = bi.bisect_left(airfoil.x, loc) - 1
+ x = [airfoil.x[spar_x]]
+ z = [airfoil.z[spar_x]]
+ # Spar lower coordinates
+ spar_x = bi.bisect_left(airfoil.x[::-1], loc) - 1
+ x += [airfoil.x[-spar_x]]
+ z += [airfoil.z[-spar_x]]
+ self.x.append(x)
+ self.z.append(z)
return None
def add_spar_caps(self, spar_cap_area):
@@ -259,7 +267,7 @@ class Spar(Coordinates):
return None
def add_mass(self, mass):
- self.mass = len(self.x_u) * mass
+ self.mass = len(self.x) * mass
return None
@@ -291,44 +299,44 @@ class Stringer(Coordinates):
'''
# Find distance between leading edge and first upper stringer
- interval = airfoil.spar.x_u[0] / (stringer_u_1 + 1)
- # initialise first self.stringer_x_u at first interval
+ interval = airfoil.spar.x[0][0] / (stringer_u_1 + 1)
+ # initialise first self.stringer_x 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])
+ # Index of the first value of airfoil.x > x
+ i = bi.bisect_left(airfoil.x, x)
+ self.x.append(airfoil.x[i])
+ self.z.append(airfoil.z[i])
x += interval
# Add upper stringers from first spar until last spar
# TODO: stringer placement if only one spar is created
- interval = (airfoil.spar.x_u[-1]
- - airfoil.spar.x_u[0]) / (stringer_u_2 + 1)
- x = interval + airfoil.spar.x_u[0]
+ interval = (airfoil.spar.x[-1][0]
+ - airfoil.spar.x[0][0]) / (stringer_u_2 + 1)
+ x = interval + airfoil.spar.x[0][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])
+ i = bi.bisect_left(airfoil.x, x)
+ self.x.append(airfoil.x[i])
+ self.z.append(airfoil.z[i])
x += interval
# Find distance between leading edge and first lower stringer
- interval = airfoil.spar.x_l[0] / (stringer_l_1 + 1)
+ interval = airfoil.spar.x[0][1] / (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])
+ i = bi.bisect_left(airfoil.x[::-1], x)
+ self.x.append(airfoil.x[-i])
+ self.z.append(airfoil.z[-i])
x += interval
# Add lower stringers from first spar until last spar
- interval = (airfoil.spar.x_l[-1]
- - airfoil.spar.x_l[0]) / (stringer_l_2 + 1)
- x = interval + airfoil.spar.x_l[0]
+ interval = (airfoil.spar.x[-1][1]
+ - airfoil.spar.x[0][1]) / (stringer_l_2 + 1)
+ x = interval + airfoil.spar.x[0][1]
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])
+ i = bi.bisect_left(airfoil.x[::-1], x)
+ self.x.append(airfoil.x[-i])
+ self.z.append(airfoil.z[-i])
x += interval
return None
@@ -337,7 +345,7 @@ class Stringer(Coordinates):
return None
def add_mass(self, mass):
- self.mass = len(self.x_u) * mass + len(self.x_l) * mass
+ self.mass = len(self.x) * mass + len(self.x) * mass
return None
def info_print(self, round):
@@ -357,38 +365,33 @@ def plot_geom(airfoil):
plt.plot(airfoil.chord / 4, 0, '.', color='g',
markersize=24, label='Quarter-chord')
# Plot mean camber line
- plt.plot(airfoil.x_c, airfoil.y_c,
- '-.', color='r', linewidth='2',
+ plt.plot(airfoil.x_c, airfoil.z_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 airfoil surfaces
+ plt.fill(airfoil.x, airfoil.z, color='b', linewidth='1', fill=False)
# Plot spars
- for _ in range(0, len(airfoil.spar.x_u)):
- x = (airfoil.spar.x_u[_], airfoil.spar.x_l[_])
- y = (airfoil.spar.z_u[_], airfoil.spar.z_l[_])
- plt.plot(x, y, '.-', color='b')
-
- # Plot upper stringers
- for _ in range(0, len(airfoil.stringer.x_u)):
- x = airfoil.stringer.x_u[_]
- y = airfoil.stringer.z_u[_]
- plt.plot(x, y, '.', color='y', markersize=12)
- # Plot lower stringers
- for _ in range(0, len(airfoil.stringer.x_l)):
- x = airfoil.stringer.x_l[_]
- y = airfoil.stringer.z_l[_]
- plt.plot(x, y, '.', color='y', markersize=12)
+ try:
+ for _ in range(len(airfoil.spar.x)):
+ x = (airfoil.spar.x[_])
+ y = (airfoil.spar.z[_])
+ plt.plot(x, y, '-', color='b')
+ except AttributeError:
+ print('No spars to plot.')
+ # Plot stringers
+ try:
+ for _ in range(0, len(airfoil.stringer.x)):
+ x = airfoil.stringer.x[_]
+ y = airfoil.stringer.z[_]
+ plt.plot(x, y, '.', color='y', markersize=12)
+ except AttributeError:
+ print('No stringers to plot.')
# Graph formatting
plt.xlabel('X axis')
plt.ylabel('Z axis')
- plot_bound = airfoil.x_u[-1]
+ plot_bound = max(airfoil.x)
plt.xlim(- 0.10 * plot_bound, 1.10 * plot_bound)
plt.ylim(- (1.10 * plot_bound / 2), (1.10 * plot_bound / 2))
plt.gca().set_aspect('equal', adjustable='box')
diff --git a/evaluator.py b/evaluator.py
index 5710522..046d4dd 100644
--- a/evaluator.py
+++ b/evaluator.py
@@ -38,17 +38,17 @@ class Evaluator:
+ airfoil.spar.mass
+ airfoil.stringer.mass)
self.mass_dist = []
- # Upper coordinates
- self.x_u = airfoil.x_u
- self.z_u = airfoil.z_u
- # Lower coordinates
- self.x_l = airfoil.x_l
- self.z_l = airfoil.z_l
+ # Coordinates
+ self.x = airfoil.x
+ self.z = airfoil.z
+ # Lift
self.lift_rectangular = []
self.lift_elliptical = []
self.lift_total = []
# Drag
self.drag = []
+ # centroid
+ self.centroid = []
# Inertia terms:
# I_x = self.I_[0]
# I_z = self.I_[1]
@@ -137,20 +137,26 @@ class Evaluator:
def get_centroid(self):
'''Return the coordinates of the centroid.'''
+
stringer_area = self.stringer.area
caps_area = self.spar.cap_area
- x_spars = self.spar.x_u + self.spar.x_l
- x_stringers = self.stringer.x_u + self.stringer.x_l
- z_stringers = self.stringer.z_u + self.stringer.z_l
- denom = float(len(x_spars) * caps_area
- + len(x_stringers) * stringer_area)
+ caps_x = [value for spar in self.spar.x for value in spar]
+ caps_z = [value for spar in self.spar.z for value in spar]
+ stringers_x = self.stringer.x
+ stringers_z = self.stringer.z
+
+ denominator = float(len(caps_x) * caps_area
+ + len(stringers_x) * stringer_area)
- x_ctr = (sum([i * caps_area for i in self.spar.x_u])
- + sum([i * stringer_area for i in x_stringers])) / denom
- z_ctr = (sum([i * caps_area for i in self.spar.z_u])
- + sum([i * stringer_area for i in z_stringers])) / denom
- return(x_ctr, z_ctr)
+ centroid_x = float(sum([x * caps_area for x in caps_x])
+ + sum([x * stringer_area for x in stringers_x]))
+ centroid_x = centroid_x / denominator
+
+ centroid_z = float(sum([z * caps_area for z in caps_z])
+ + sum([z * stringer_area for z in stringers_z]))
+ centroid_z = centroid_z / denominator
+ return(centroid_x, centroid_z)
def get_inertia_terms(self):
'''Obtain all inertia terms.'''
@@ -159,12 +165,12 @@ class Evaluator:
caps_area = self.spar.cap_area
# Adds upper and lower components' coordinates to list
- x_stringers = self.stringer.x_u + self.stringer.x_l
- z_stringers = self.stringer.z_u + self.stringer.z_l
- x_spars = self.spar.x_u + self.spar.x_l
- z_spars = self.spar.z_u + self.spar.z_l
+ x_stringers = self.stringer.x
+ z_stringers = self.stringer.z
+ x_spars = self.spar.x[:][0] + self.spar.x[:][1]
+ z_spars = self.spar.z[:][0] + self.spar.z[:][1]
stringer_count = range(len(x_stringers))
- spar_count = range(len(self.spar.x_u))
+ spar_count = range(len(self.spar.x))
# I_x is the sum of the contributions of the spar caps and stringers
I_x = (sum([caps_area * (z_spars[i] - self.centroid[1]) ** 2
@@ -209,31 +215,32 @@ def plot_geom(evaluator):
y_chord = [0, 0]
plt.plot(x_chord, y_chord, linewidth='1')
# Plot quarter chord
- q = evaluator.chord / 4
- plt.plot(q, 0, '.', color='g', markersize=24, label='Quarter-chord')
- # Plot upper surface
- plt.plot(evaluator.x_u, evaluator.z_u,
- '', color='b', linewidth='1')
- # Plot lower surface
- plt.plot(evaluator.x_l, evaluator.z_l,
- '', color='b', linewidth='1')
+ plt.plot(evaluator.chord / 4, 0, '.', color='g',
+ markersize=24, label='Quarter-chord')
+ # Plot airfoil surfaces
+ plt.fill(evaluator.x, evaluator.z, color='b', linewidth='1', fill=False)
# Plot spars
- for _ in range(0, len(evaluator.spar.x_u)):
- x = (evaluator.spar.x_u[_], evaluator.spar.x_l[_])
- y = (evaluator.spar.z_u[_], evaluator.spar.z_l[_])
- plt.plot(x, y, '.-', color='b')
-
+ try:
+ for _ in range(len(evaluator.spar.x)):
+ x = (evaluator.spar.x[_])
+ y = (evaluator.spar.z[_])
+ plt.plot(x, y, '-', color='b')
+ except AttributeError:
+ print('No spars to plot.')
# Plot upper stringers
- for _ in range(0, len(evaluator.stringer.x_u)):
- x = evaluator.stringer.x_u[_]
- y = evaluator.stringer.z_u[_]
- plt.plot(x, y, '.', color='y', markersize=12)
- # Plot lower stringers
- for _ in range(0, len(evaluator.stringer.x_l)):
- x = evaluator.stringer.x_l[_]
- y = evaluator.stringer.z_l[_]
- plt.plot(x, y, '.', color='y', markersize=12)
+ try:
+ for _ in range(0, len(evaluator.stringer.x)):
+ x = evaluator.stringer.x[_]
+ y = evaluator.stringer.z[_]
+ plt.plot(x, y, '.', color='y', markersize=12)
+ except AttributeError:
+ print('No stringers to plot.')
+ # # Plot lower stringers
+ # for _ in range(0, len(evaluator.stringer.x)):
+ # x = evaluator.stringer.x[_]
+ # y = evaluator.stringer.z[_]
+ # plt.plot(x, y, '.', color='y', markersize=12)
# Plot centroid
x = evaluator.centroid[0]
@@ -244,7 +251,7 @@ def plot_geom(evaluator):
plt.xlabel('X axis')
plt.ylabel('Z axis')
- plot_bound = evaluator.x_u[-1]
+ plot_bound = max(evaluator.x)
plt.xlim(- 0.10 * plot_bound, 1.10 * plot_bound)
plt.ylim(- (1.10 * plot_bound / 2), (1.10 * plot_bound / 2))
plt.gca().set_aspect('equal', adjustable='box')
diff --git a/main.py b/main.py
index fd8855f..f11bd0f 100644
--- a/main.py
+++ b/main.py
@@ -24,8 +24,8 @@ start_time = time.time()
# Airfoil dimensions
NACA_NUM = 2412
-CHORD_LENGTH = 40
-SEMI_SPAN = 50
+CHORD_LENGTH = 101
+SEMI_SPAN = 40
# Airfoil thickness
T_UPPER = 0.1
@@ -37,14 +37,14 @@ SPAR_MASS = 10 # lbs
STRINGER_MASS = 5 # lbs
# Area
-SPAR_CAP_AREA = 0.3 # sqin
+SPAR_CAP_AREA = 0.0 # sqin
STRINGER_AREA = 0.1 # sqin
# Amount of stringers
-TOP_STRINGERS = 0
-BOTTOM_STRINGERS = 18
-NOSE_TOP_STRINGERS = 0
-NOSE_BOTTOM_STRINGERS = 5
+TOP_STRINGERS = 3
+BOTTOM_STRINGERS = 4
+NOSE_TOP_STRINGERS = 3
+NOSE_BOTTOM_STRINGERS = 6
# population information & save path
POP_SIZE = 1
@@ -71,18 +71,18 @@ def main():
af.add_naca(NACA_NUM)
af.add_mass(AIRFOIL_MASS)
# af.info_print(2)
- # af.info_save(SAVE_PATH, _)
+ af.info_save(SAVE_PATH, _)
# Create spar instance
af.spar = creator.Spar()
# Define the spar coordinates and mass, stored in single spar object
- af.spar.add_coord(af, 0.15)
- af.spar.add_coord(af, 0.55)
+ af.spar.add_coord(af, 0.20)
+ af.spar.add_coord(af, 0.65)
# Automatically adds spar caps for all spars previously defined
af.spar.add_spar_caps(SPAR_CAP_AREA)
af.spar.add_mass(SPAR_MASS)
# af.spar.info_print(2)
- # af.spar.info_save(SAVE_PATH, _)
+ af.spar.info_save(SAVE_PATH, _)
# Create stringer instance
af.stringer = creator.Stringer()
@@ -95,10 +95,10 @@ def main():
af.stringer.add_area(STRINGER_AREA)
af.stringer.add_mass(STRINGER_MASS)
# af.stringer.info_print(2)
- # af.stringer.info_save(SAVE_PATH, _)
+ af.stringer.info_save(SAVE_PATH, _)
# Plot components with matplotlib
- # creator.plot_geom(af)
+ creator.plot_geom(af)
# Evaluator object contains airfoil analysis results.
eval = evaluator.Evaluator(af)
Copyright 2019--2024 Marius PETER