Source code for RCAIDE.Library.Methods.Geometry.Planform.wing_planform
# RCAIDE/Library/Methods/Geometry/Planform/wing_planform.py
#
#
# Created: Jul 2024, M. Clarke
# ----------------------------------------------------------------------------------------------------------------------
# IMPORT
# ----------------------------------------------------------------------------------------------------------------------
import RCAIDE
from RCAIDE.Library.Methods.Geometry.Planform.convert_sweep import convert_sweep_segments, convert_sweep
from RCAIDE.Library.Methods.Geometry.Airfoil import compute_naca_4series, import_airfoil_geometry
from RCAIDE.Library.Methods.Geometry.Planform.compute_segment_centroid import compute_segment_centroid
# package imports
import numpy as np
# ----------------------------------------------------------------------------------------------------------------------
# Wing Segmented Planform
# ----------------------------------------------------------------------------------------------------------------------
[docs]
def wing_planform(wing):
"""Computes standard wing planform values.
Assumptions:
Multisegmented wing. There is no unexposed wetted area, ie wing area that
intersects inside a fuselage. Aerodynamic center is at 25% mean aerodynamic chord.
Source:
None
Inputs:
wing.
chords.root [m]
spans.projected [m]
symmetric <boolean> Determines if wing is symmetric
Outputs:
wing.
spans.total [m]
chords.tip [m]
chords.mean_aerodynamics [m]
wing.chords.mean_geometric [m]
areas.reference [m^2]
taper [-]
sweeps.quarter_chord [radians]
aspect_ratio [-]
thickness_to_chord [-]
dihedral [radians]
aerodynamic_center [m] x, y, and z location
Properties Used:
N/A
"""
sym = wing.xz_plane_symmetric
if len(wing.segments) > 1:
# Unpack
RC = wing.chords.root
vertical = wing.vertical
span = wing.spans.projected
# Pull all the segment data into array format
span_locs = []
twists = []
sweeps = []
dihedrals = []
chords = []
t_cs = []
seg_keys = list(wing.segments.keys())
for i in range(len(wing.segments)):
seg = wing.segments[seg_keys[i]]
span_locs.append(seg.percent_span_location)
twists.append(seg.twist)
chords.append(seg.root_chord_percent)
if i == (len(wing.segments) - 1):
sweeps.append(sweeps[-1])
seg.sweeps.quarter_chord = sweeps[-1]
seg.sweeps.leading_edge = sweeps[-1]
else:
if seg.sweeps.quarter_chord != None:
sweeps.append(seg.sweeps.quarter_chord)
elif seg.sweeps.quarter_chord == None and seg.sweeps.leading_edge != None:
# covert leading edge to quarter chord
if i == len(wing.segments) - 1:
sweeps.append(0)
seg.sweeps.quarter_chord = 0
else:
next_seg = wing.segments[seg_keys[i+1]]
quarter_chord_sweep = convert_sweep_segments(seg.sweeps.leading_edge, seg, next_seg, wing, old_ref_chord_fraction=0.0, new_ref_chord_fraction=0.25)
sweeps.append(quarter_chord_sweep)
seg.sweeps.quarter_chord = quarter_chord_sweep
else:
raise AssertionError("Quarter chord or leading edge sweep must be defined")
if seg.airfoil != None:
if type(seg.airfoil) == RCAIDE.Library.Components.Airfoils.NACA_4_Series_Airfoil: # check if naca 4 series of airfoil from datafile
seg.airfoil.geometry = compute_naca_4series(seg.airfoil.NACA_4_Series_code)
seg.thickness_to_chord = seg.airfoil.geometry.thickness_to_chord
else:
seg.airfoil.geometry = import_airfoil_geometry(seg.airfoil.coordinate_file)
seg.thickness_to_chord = seg.airfoil.geometry.thickness_to_chord
t_cs.append(seg.thickness_to_chord)
else:
t_cs.append(seg.thickness_to_chord)
dihedrals.append(seg.dihedral_outboard)
# Convert to arrays
chords = np.array(chords)
span_locs = np.array(span_locs)
sweeps = np.array(sweeps)
t_cs = np.array(t_cs)
# Basic calcs:
semispan = span/(1+sym)
lengths_ndim = span_locs[1:]-span_locs[:-1]
lengths_dim = lengths_ndim*semispan
chords_dim = RC*chords
tapers = chords[1:]/chords[:-1]
# Calculate the areas of each segment
As = (lengths_dim*chords_dim[:-1]-(chords_dim[:-1]-chords_dim[1:])*(lengths_dim/2))
Af = lengths_dim*((chords_dim[:-1]*t_cs[:-1]+chords_dim[1:]*t_cs[1:])/2)
# Calculate the wing area
ref_area = np.sum(As)*(1+sym)
frontal_area = np.sum(Af)*(1+sym)
# Calculate the Aspect Ratio
AR = (span**2)/ref_area
# Calculate the total span
lens = lengths_dim/np.cos(dihedrals[:-1])
total_len = np.sum(np.array(lens))*(1+sym)
# Calculate the mean geometric chord
mgc = ref_area/span
# Calculate the mean aerodynamic chord
A = chords_dim[:-1]
B = (A-chords_dim[1:])/(-lengths_ndim)
C = span_locs[:-1]
integral = ((A+B*(span_locs[1:]-C))**3-(A+B*(span_locs[:-1]-C))**3)/(3*B)
# For the cases when the wing doesn't taper in a spot
integral[np.isnan(integral)] = (A[np.isnan(integral)]**2)*((lengths_ndim)[np.isnan(integral)])
MAC = (semispan*(1+sym)/(ref_area))*np.sum(integral)
# Calculate the taper ratio
lamda = chords[-1]/chords[0]
# the tip chord
ct = chords_dim[-1]
# Calculate an average t/c weighted by area
t_c = np.sum(As*t_cs[:-1])/(ref_area/2)
# Calculate the segment leading edge sweeps
r_offsets = chords_dim[:-1]/4
t_offsets = chords_dim[1:]/4
le_sweeps = np.arctan((r_offsets+np.tan(sweeps[:-1])*(lengths_dim)-t_offsets)/(lengths_dim))
le_sweeps = np.append(le_sweeps, 0)
# Calculate the effective sweeps
c_4_sweep = np.arctan(np.sum(lengths_ndim*np.tan(sweeps[:-1])))
le_sweep_total= np.arctan(np.sum(lengths_ndim*np.tan(le_sweeps[:-1])))
# Calculate the aerodynamic center, but first the centroid
dxs = np.cumsum(np.concatenate([np.array([0]),np.tan(le_sweeps[:-1])*lengths_dim]))
dys = np.cumsum(np.concatenate([np.array([0]),lengths_dim]))
dzs = np.cumsum(np.concatenate([np.array([0]),np.tan(dihedrals[:-1])*lengths_dim]))
Cxys = []
for i in range(len(lengths_dim)):
Cxys.append(compute_segment_centroid(le_sweeps[i],lengths_dim[i],dxs[i],dys[i],dzs[i], tapers[i],
dihedrals[i], chords_dim[i], chords_dim[i+1]))
aerodynamic_center = (np.dot(np.transpose(Cxys),As)/(ref_area/(1+sym)))
single_side_aerodynamic_center = (np.array(aerodynamic_center)*1.)
single_side_aerodynamic_center[0] = single_side_aerodynamic_center[0] - MAC*.25
if sym== True:
aerodynamic_center[1] = 0
aerodynamic_center[0] = single_side_aerodynamic_center[0]
# Total length for supersonics
total_length = np.tan(le_sweep_total)*semispan + chords[-1]*RC
if vertical:
for i in range(len(wing.segments)):
wing.segments[seg_keys[i]].sweeps.leading_edge = le_sweeps[i]
wing.segments[seg_keys[i]].origin = [[dxs[i],dzs[i],dys[i]]]
else:
for i in range(len(wing.segments)):
wing.segments[seg_keys[i]].sweeps.leading_edge = le_sweeps[i]
wing.segments[seg_keys[i]].origin = [[dxs[i],dys[i],dzs[i]]]
wing.spans.total = total_len
wing.chords.mean_geometric = mgc
wing.chords.mean_aerodynamic = MAC
wing.chords.tip = ct
wing.taper = lamda
wing.areas.projected = ref_area
wing.areas.reference = ref_area
wing.areas.front_projected = frontal_area
wing.sweeps.quarter_chord = c_4_sweep
wing.sweeps.leading_edge = le_sweep_total
wing.thickness_to_chord = t_c
wing.aerodynamic_center = aerodynamic_center
wing.total_length = total_length
wing.aspect_ratio = AR
# update remainder segment properties
segment_properties(wing)
# compute trap area
seg_keys = list(wing.segments.keys())
for tag, segment in enumerate(wing.segments):
if segment.chords.reference_area_root:
segment_root_chord = wing.segments[seg_keys[tag]].root_chord_percent * wing.chords.root
segment_tip_chord = wing.segments[seg_keys[tag+1]].root_chord_percent * wing.chords.root
segnent_start_span = wing.segments[seg_keys[tag]].percent_span_location * wing.spans.projected
reference_wing_span = wing.segments[seg_keys[tag+1]].percent_span_location * wing.spans.projected
next_seg = wing.segments[seg_keys[tag+1]]
trailing_edge_sweep = convert_sweep_segments(segment.sweeps.quarter_chord, segment, next_seg, wing, old_ref_chord_fraction=0.25, new_ref_chord_fraction=1.0)
leading_edge_sweep = convert_sweep_segments(segment.sweeps.quarter_chord, segment, next_seg, wing, old_ref_chord_fraction=0.25, new_ref_chord_fraction=0.0)
projected_root_chord = segment_root_chord + segnent_start_span * (np.tan(leading_edge_sweep) - np.tan(trailing_edge_sweep))
wing.areas.reference = (projected_root_chord + segment_tip_chord)/2 * reference_wing_span
else:
# unpack
sref = wing.areas.reference
taper = wing.taper
sweep = wing.sweeps.quarter_chord
ar = wing.aspect_ratio
dihedral = wing.dihedral
vertical = wing.vertical
symmetric = wing.xz_plane_symmetric
if wing.airfoil != None:
if type(wing.airfoil) == RCAIDE.Library.Components.Airfoils.NACA_4_Series_Airfoil: # check if naca 4 series of airfoil from datafile
wing.airfoil.geometry = compute_naca_4series(wing.airfoil.NACA_4_Series_code)
wing.thickness_to_chord = wing.airfoil.geometry.thickness_to_chord
else:
wing.airfoil.geometry = import_airfoil_geometry(wing.airfoil.coordinate_file)
wing.thickness_to_chord = wing.airfoil.geometry.thickness_to_chord
t_c_w = wing.thickness_to_chord
# calculate
span = (ar*sref)**.5
semispan = span/(1+sym)
span_total = span/np.cos(dihedral)
chord_root = 2*sref/span/(1+taper)
chord_tip = taper * chord_root
mgc = (chord_root+chord_tip)/2
swet = 2.*span/2.*(chord_root+chord_tip) * (1.0 + 0.2*t_c_w)
mac = 2./3.*( chord_root+chord_tip - chord_root*chord_tip/(chord_root+chord_tip) )
# calculate leading edge sweep
if wing.sweeps.leading_edge == None:
le_sweep = np.arctan( np.tan(sweep) - (4./ar)*(0.-0.25)*(1.-taper)/(1.+taper) )
wing.sweeps.leading_edge = le_sweep
else:
le_sweep = wing.sweeps.leading_edge
wing.sweeps.quarter_chord = convert_sweep(wing,old_ref_chord_fraction = 0.0,new_ref_chord_fraction = 0.25)
# estimating aerodynamic center coordinates
y_coord = span / 6. * (( 1. + 2. * taper ) / (1. + taper))
x_coord = mac * 0.25 + y_coord * np.tan(le_sweep)
z_coord = y_coord * np.tan(dihedral)
if vertical:
temp = y_coord * 1.
y_coord = z_coord * 1.
z_coord = temp
if symmetric:
y_coord = 0
# Total length calculation
total_length = np.tan(le_sweep)*span/2. + chord_tip
# update
wing.chords.root = chord_root
wing.chords.tip = chord_tip
wing.chords.mean_aerodynamic = mac
wing.chords.mean_geometric = mgc
wing.sweeps.leading_edge = le_sweep
wing.areas.wetted = swet
wing.areas.projected = sref
wing.areas.front_projected = span*mgc
wing.spans.projected = span
wing.spans.total = span_total
wing.aerodynamic_center = [x_coord , y_coord, z_coord]
wing.total_length = total_length
# estimate LEMAC
wing.LEMAC = wing.origin[0][0] + np.tan(wing.sweeps.leading_edge) * y_coord
segment = RCAIDE.Library.Components.Wings.Segments.Segment()
segment.tag = 'root'
segment.percent_span_location = 0.0
segment.root_chord_percent = 1.0
segment.sweeps.leading_edge = le_sweep
segment.sweeps.quarter_chord = wing.sweeps.quarter_chord
segment.dihedral_outboard = dihedral
segment.thickness_to_chord = t_c_w
wing.append_segment(segment)
segment = RCAIDE.Library.Components.Wings.Segments.Segment()
segment.tag = 'tip'
segment.percent_span_location = 1.0
segment.root_chord_percent = taper
segment.thickness_to_chord = t_c_w
if vertical:
dx = np.tan(le_sweep) * semispan
dy = np.tan(dihedral) * semispan
dz = semispan
segment.origin = [[dx, dy , semispan ]]
else:
dx = np.tan(le_sweep) * semispan
dz = np.tan(dihedral) * semispan
segment.origin = [[dx, semispan , dz ]]
wing.append_segment(segment)
segment_properties(wing)
# control surface
taper = wing.taper
Sw = wing.areas.reference
bw = wing.spans.projected
for cs in wing.control_surfaces:
cs_span = (cs.span_fraction_end - cs.span_fraction_start) * bw
chord_root = 2*Sw/bw/(1+taper)
chord_tip = taper * chord_root
delta_chord = chord_tip - chord_root
wing_chord_cs_start = chord_root + delta_chord * cs.span_fraction_start
wing_chord_cs_end = chord_root + delta_chord * cs.span_fraction_end
cs_chord_start = wing_chord_cs_start* cs.chord_fraction
cs_chord_end = wing_chord_cs_end* cs.chord_fraction
cf = (cs_chord_start +cs_chord_end) /2
Sf = cs_span * cf
cs.area = Sf
cs.span = cs_span
cs.root_chord = cs_chord_start
cs.tip_chord = cs_chord_end
seg_keys = list(wing.segments.keys())
for tag, segment in enumerate(wing.segments):
if segment.chords.reference_area_root:
segment_root_chord = wing.segments[seg_keys[tag]].root_chord_percent * wing.chords.root
segment_tip_chord = wing.segments[seg_keys[tag+1]].root_chord_percent * wing.chords.root
segnent_start_span = wing.segments[seg_keys[tag]].percent_span_location * wing.spans.projected
reference_wing_span = wing.segments[seg_keys[tag+1]].percent_span_location * wing.spans.projected
next_seg = wing.segments[seg_keys[tag+1]]
trailing_edge_sweep = convert_sweep_segments(segment.sweeps.quarter_chord, segment, next_seg, wing, old_ref_chord_fraction=0.25, new_ref_chord_fraction=1.0)
leading_edge_sweep = convert_sweep_segments(segment.sweeps.quarter_chord, segment, next_seg, wing, old_ref_chord_fraction=0.25, new_ref_chord_fraction=0.0)
projected_root_chord = segment_root_chord + segnent_start_span/2 * (np.tan(leading_edge_sweep) - np.tan(trailing_edge_sweep))
wing.areas.reference = (projected_root_chord + segment_tip_chord)/2 * reference_wing_span
wing.chords.mean_aerodynamic = 2./3.*( projected_root_chord+segment_tip_chord - projected_root_chord*segment_tip_chord/(projected_root_chord+segment_tip_chord) )
# estimating aerodynamic center coordinates
outboard_segment_origin = wing.segments[seg_keys[tag+1]].origin
span = wing.spans.projected
taper = segment_tip_chord/projected_root_chord
y_coord = span / 6. * (( 1. + 2. * taper ) / (1. + taper))
x_coord = wing.chords.mean_aerodynamic * 0.25 + y_coord * np.tan(leading_edge_sweep)
LEMAC = outboard_segment_origin[0][0] + np.tan(leading_edge_sweep)*(y_coord - wing.segments[seg_keys[tag+1]].percent_span_location * wing.spans.projected/2)
# estimate LEMAC
wing.LEMAC = LEMAC
return wing
[docs]
def segment_properties(wing):
"""Computes detailed segment properties. These are currently used for parasite drag calculations.
Assumptions:
Segments are trapezoids
Source:
http://aerodesign.stanford.edu/aircraftdesign/aircraftdesign.html (Stanford AA241 A/B Course Notes)
Inputs:
wing.
percent_span_unexposed [m]
symmetric [-]
spans.projected [m]
thickness_to_chord [-]
areas.wetted [m^2]
chords.root [m]
Segments.
percent_span_location [-]
root_chord_percent [-]
Outputs:
wing.areas.wetted [m^2]
wing.areas.reference [m^2]
wing.segments.
taper [-]
chords.mean_aerodynamic [m]
areas.
reference [m^2]
exposed [m^2]
wetted [m^2]
Properties Used:
N/A
"""
# Unpack wing
percent_span_unexposed = wing.percent_span_unexposed
symm = wing.xz_plane_symmetric
semispan = wing.spans.projected*0.5 * (2 - symm)
segments = wing.segments
wing_root_chord = wing.chords.root
segment_names = list(segments.keys())
num_segments = len(segment_names)
# initialize areas to 0
total_wetted_area = 0.0
center_body_area = 0.0
total_reference_area = 0.0
aft_center_body_area = 0.0
for seg_idx in range(num_segments):
if seg_idx == num_segments-1:
continue
else:
# unpack segment
inboard_segment = segments[segment_names[seg_idx]]
outboard_segment = segments[segment_names[seg_idx+1]]
# segment thickness to chord ratio
t_c_w = inboard_segment.thickness_to_chord
# segment span
span_seg = semispan*(outboard_segment.percent_span_location - inboard_segment.percent_span_location )
# compute the exposed wing segment area
chord_root = wing_root_chord*inboard_segment.root_chord_percent
chord_tip = wing_root_chord*outboard_segment.root_chord_percent
taper = chord_tip/chord_root
mac_seg = chord_root * 2/3 * (( 1 + taper + taper**2 )/( 1 + taper))
Sref_seg = span_seg*(chord_root+chord_tip)*0.5
if seg_idx == 0:
wing_root = chord_root + percent_span_unexposed*((chord_tip - chord_root)/span_seg)
S_exposed_seg = (span_seg-percent_span_unexposed)*(wing_root+chord_tip)*0.5
else:
S_exposed_seg = Sref_seg
# multiply if wing is symmetric
if wing.xz_plane_symmetric:
Sref_seg = Sref_seg*2
S_exposed_seg = S_exposed_seg*2
# compute wetted area of segment
if t_c_w < 0.05:
Swet_seg = 2.003* S_exposed_seg
else:
Swet_seg = (1.977 + 0.52*t_c_w) * S_exposed_seg
# store segment properties
inboard_segment.taper = taper
inboard_segment.chords.mean_aerodynamic = mac_seg
inboard_segment.areas.reference = Sref_seg
inboard_segment.aspect_ratio = (span_seg **2) / Sref_seg
inboard_segment.areas.exposed = S_exposed_seg
inboard_segment.areas.wetted = Swet_seg
total_wetted_area += Swet_seg
# compute wing mean aerodynamic chord
MAC = wing.chords.mean_aerodynamic
if (MAC < chord_root) and (MAC > chord_tip):
x_0 = segments[segment_names[seg_idx]].origin[0][0] + wing.origin[0][0]
dy = ( MAC - chord_root) / ( (chord_tip - chord_root) / span_seg)
LEMAC = x_0 + np.tan(segments[segment_names[seg_idx]].sweeps.leading_edge) *dy
wing.LEMAC = LEMAC
elif (MAC == chord_root) and (MAC == chord_tip):
x_0 = segments[segment_names[seg_idx]].origin[0][0] + wing.origin[0][0]
wing.LEMAC = x_0
if isinstance(outboard_segment, RCAIDE.Library.Components.Wings.Segments.Blended_Wing_Body_Fuselage_Segment):
# center body
center_body_chord_root = wing_root_chord*inboard_segment.root_chord_percent - wing.aft_center_body.length
center_body_chord_tip = wing_root_chord*outboard_segment.root_chord_percent - wing.aft_center_body.length
center_body_Sref_seg = span_seg*(center_body_chord_root+center_body_chord_tip)*0.5
# aft center body
aft_center_body_chord_root = wing.aft_center_body.length
aft_center_body_chord_tip = wing.aft_center_body.length
aft_center_body_Sref_seg = span_seg*(aft_center_body_chord_root+aft_center_body_chord_tip)*0.5
# double area if symmetric
if wing.xz_plane_symmetric:
center_body_Sref_seg = center_body_Sref_seg*2
aft_center_body_Sref_seg = aft_center_body_Sref_seg*2
center_body_area += center_body_Sref_seg
aft_center_body_area += aft_center_body_Sref_seg
total_reference_area += Sref_seg
wing.areas.reference = total_reference_area
wing.areas.projected = total_reference_area
if isinstance(wing,RCAIDE.Library.Components.Wings.Blended_Wing_Body):
wing.center_body.area = center_body_area
wing.aft_center_body.area = aft_center_body_area
wing.areas.wetted = total_wetted_area
return wing