# RCAIDE/Library/Methods/Geometry/Airfoil/import_airfoil_geometry.py
# ----------------------------------------------------------------------
# Imports
# ----------------------------------------------------------------------
from RCAIDE.Framework.Core import Data
import numpy as np
from scipy import interpolate
# ----------------------------------------------------------------------------------------------------------------------
# import_airfoil_geometry
# ----------------------------------------------------------------------------------------------------------------------
[docs]
def import_airfoil_geometry(airfoil_geometry_file, npoints = 201,surface_interpolation = 'cubic'):
"""This imports an airfoil geometry from a text file and store
the coordinates of upper and lower surfaces as well as the mean
camberline
Assumptions:
Works for Selig and Lednicer airfoil formats. Automatically detects which format based off first line of data. Assumes it is one of those two.
Source:
airfoiltools.com/airfoil/index - method for determining format and basic error checking
Inputs:
airfoil_geometry_files <list of strings>
surface_interpolation - type of interpolation used in the SciPy function. Preferable options are linear, quardratic and cubic.
Full list of options can be found here :
https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.interp1d.html#scipy.interpolate.interp1d
Outputs:
airfoil_data.
thickness_to_chord
x_coordinates
y_coordinates
x_upper_surface
x_lower_surface
y_upper_surface
y_lower_surface
camber_coordinates
Properties Used:
N/A
"""
if npoints%2 != 1:
npoints+= 1
print('Number of points must be odd, changing to ' + str(npoints) + ' points')
geometry = Data()
half_npoints = npoints//2
# Open file and read column names and data block
f = open(airfoil_geometry_file)
# Extract data
data_block = f.readlines()
try:
# Check for header block
first_element = float(data_block[0][0])
if first_element == 1.:
lednicer_format = False
except:
# Check for format line and remove header block
format_line = data_block[1]
# Check if it's a Selig or Lednicer file
try:
format_flag = float(format_line.strip().split()[0])
except:
format_flag = float(format_line.strip().split(',')[0])
if format_flag > 1.01: # Amount of wiggle room per airfoil tools
lednicer_format = True
# Remove header block
data_block = data_block[3:]
else:
lednicer_format = False
# Remove header block
data_block = data_block[1:]
# Close the file
f.close()
if lednicer_format:
x_up_surf = []
y_up_surf = []
x_lo_surf = []
y_lo_surf = []
# Loop through each value: append to each column
upper_surface_flag = True
for line_count , line in enumerate(data_block):
#check for blank line which signifies the upper/lower surface division
line_check = data_block[line_count].strip()
if line_check == '':
upper_surface_flag = False
continue
if upper_surface_flag:
x_up_surf.append(float(data_block[line_count].strip().split()[0]))
y_up_surf.append(float(data_block[line_count].strip().split()[1]))
else:
x_lo_surf.append(float(data_block[line_count].strip().split()[0]))
y_lo_surf.append(float(data_block[line_count].strip().split()[1]))
else:
x_up_surf_rev = []
y_up_surf_rev = []
x_lo_surf = []
y_lo_surf = []
# Loop through each value: append to each column
upper_surface_flag = True
for line_count , line in enumerate(data_block):
#check for line which starts with 0., which should be the split between upper and lower in selig
line_check = data_block[line_count].strip()
# Remove any commas
line_check = line_check.replace(',','')
if float(line_check.split()[0]) == 0.:
x_up_surf_rev.append(float(data_block[line_count].strip().replace(',','').split()[0]))
y_up_surf_rev.append(float(data_block[line_count].strip().replace(',','').split()[1]))
x_lo_surf.append(float(data_block[line_count].strip().replace(',','').split()[0]))
y_lo_surf.append(float(data_block[line_count].strip().replace(',','').split()[1]))
upper_surface_flag = False
continue
if upper_surface_flag:
x_up_surf_rev.append(float(data_block[line_count].strip().replace(',','').split()[0]))
y_up_surf_rev.append(float(data_block[line_count].strip().replace(',','').split()[1]))
else:
x_lo_surf.append(float(data_block[line_count].strip().replace(',','').split()[0]))
y_lo_surf.append(float(data_block[line_count].strip().replace(',','').split()[1]))
if upper_surface_flag ==True:
# check if next line flips without x-coordinate going to 0
next_line = data_block[line_count+1].strip()
next_line = next_line.replace(',','')
if next_line.split()[0]>line_check.split()[0] and float(next_line.split()[0]) >0.:
upper_surface_flag = False
# Upper surface values in Selig format are reversed from Lednicer format, so fix that
x_up_surf_rev.reverse()
y_up_surf_rev.reverse()
x_up_surf = x_up_surf_rev
y_up_surf = y_up_surf_rev
x_up_surf = np.array(x_up_surf)
x_lo_surf = np.array(x_lo_surf)
y_up_surf = np.array(y_up_surf)
y_lo_surf = np.array(y_lo_surf)
# Check for extra zeros (OpenVSP exports extra zeros)
if len(np.unique(x_up_surf))!=len(x_up_surf):
x_up_surf = x_up_surf[1:]
x_lo_surf = x_lo_surf[1:]
y_up_surf = y_up_surf[1:]
y_lo_surf = y_lo_surf[1:]
# create custom spacing for more points and leading and trailing edge
t = np.linspace(0,4,npoints-1)
delta = 0.25
A = 5
f = 0.25
smoothsq = 5 + (2*A/np.pi) *np.arctan(np.sin(2*np.pi*t*f + np.pi/2)/delta)
dim_spacing = np.append(0,np.cumsum(smoothsq)/sum(smoothsq))
# compute thickness, camber and concatenate coodinates
x_data = np.hstack((x_lo_surf[::-1], x_up_surf[1:]))
y_data = np.hstack((y_lo_surf[::-1], y_up_surf[1:]))
tck,u = interpolate.splprep([x_data,y_data],k=3,s=0)
out = interpolate.splev(dim_spacing,tck)
x_data = out[0]
y_data = out[1]
# shift points to leading edge (x = 0, y = 0)
x_delta = min(x_data)
x_data = x_data - x_delta
arg_min = np.argmin(x_data)
y_delta = y_data[arg_min]
y_data = y_data - y_delta
if (x_data[arg_min] == 0) and (y_data[arg_min] == 0):
x_data[arg_min] = 0
y_data[arg_min] = 0
# make sure points start and end at x = 1.0
x_data[0] = 1.0
x_data[-1] = 1.0
# make sure a small gap at trailing edge
if (y_data[0] == y_data[-1]):
y_data[0] = y_data[0] - 1E-4
y_data[-1] = y_data[-1] + 1E-4
# thicknes and camber distributions require equal points
x_up_surf_old = np.array(x_up_surf)
arrx_up_interp = interpolate.interp1d(np.arange(x_up_surf_old.size),x_up_surf_old, kind=surface_interpolation)
x_up_surf_new = arrx_up_interp(np.linspace(0,x_up_surf_old.size-1,half_npoints))
x_lo_surf_old = np.array(x_lo_surf)
arrx_lo_interp = interpolate.interp1d(np.arange(x_lo_surf_old.size),x_lo_surf_old, kind=surface_interpolation )
x_lo_surf_new = arrx_lo_interp(np.linspace(0,x_lo_surf_old.size-1,half_npoints))
# y coordinate s
y_up_surf_old = np.array(y_up_surf)
arry_up_interp = interpolate.interp1d(np.arange(y_up_surf_old.size),y_up_surf_old, kind=surface_interpolation)
y_up_surf_new = arry_up_interp(np.linspace(0,y_up_surf_old.size-1,half_npoints))
y_lo_surf_old = np.array(y_lo_surf)
arry_lo_interp = interpolate.interp1d(np.arange(y_lo_surf_old.size),y_lo_surf_old, kind=surface_interpolation)
y_lo_surf_new = arry_lo_interp(np.linspace(0,y_lo_surf_old.size-1,half_npoints))
# compute thickness, camber and concatenate coodinates
thickness = y_up_surf_new - y_lo_surf_new
camber = y_lo_surf_new + thickness/2
max_t = np.max(thickness)
max_c = max(x_data) - min(x_data)
t_c = max_t/max_c
geometry.thickness_to_chord = t_c
geometry.max_thickness = max_t
geometry.x_coordinates = x_data
geometry.y_coordinates = y_data
geometry.x_upper_surface = x_up_surf_new
geometry.x_lower_surface = x_lo_surf_new
geometry.y_upper_surface = y_up_surf_new
geometry.y_lower_surface = y_lo_surf_new
geometry.camber_coordinates = camber
return geometry