# make_VLM_wings.py
# Created: Jun 2021, A. Blaufox
# ----------------------------------------------------------------------------------------------------------------------
# Imports
# ----------------------------------------------------------------------------------------------------------------------
# package imports
import numpy as np
from copy import deepcopy
import RCAIDE
from RCAIDE.Framework.Core import Data
from RCAIDE.Library.Components.Wings import All_Moving_Surface
from RCAIDE.Library.Components.Wings.Control_Surfaces import Aileron , Elevator , Slat , Flap , Rudder
from RCAIDE.Library.Methods.Geometry.Planform import populate_control_sections
from RCAIDE.Library.Methods.Geometry.Planform.convert_sweep import convert_sweep_segments
# ----------------------------------------------------------------------------------------------------------------------
# make_VLM_wings()
# ----------------------------------------------------------------------------------------------------------------------
[docs]
def make_VLM_wings(geometry, settings):
""" This parses through geometry.wings to create a Container of Data objects.
Relevant VLM attributes are copied from geometry.wings to the Container.
After, the wing data objects are reformatted. All control surfaces are
also added to the Container as Data objects representing full wings.
Helper variables are then computed (most notably span_breaks) for later.
see make_span_break() for further details
Assumptions:
All control surfaces are appended directly to the wing, not wing segments.
If a given wing has no segments, it must have either .taper or .chords.root
and .chords.tip defined
Source:
None
Inputs:
geometry.
wings.wing.
twists.root
twists.tip
dihedral
sweeps.quarter_chord OR sweeps.leading_edge
thickness_to_chord
taper
chord.root
chords.tip
control_surface.
tag
span_fraction_start
span_fraction_end
deflection
chord_fraction
settings.discretize_control_surfaces --> set to True to generate control surface panels
Properties Used:
N/A
"""
# unpack inputs
discretize_cs = settings.discretize_control_surfaces
wings = copy_wings(geometry.wings)
# ------------------------------------------------------------------
# Reformat original wings to have at least 2 segments and additional values for processing later
# ------------------------------------------------------------------
for wing in wings:
wing.is_a_control_surface = False
n_segments = len(wing.segments.keys())
if n_segments==0:
# convert to preferred format for the panelization loop
wing = convert_to_segmented_wing(wing)
n_segments = 2
else:
# check for invalid/unsupported/conflicting geometry input
if issubclass(wing.wing_type, All_Moving_Surface): # these cases unsupported due to the way the panelization loop is structured at the moment
if not (wing.hinge_vector == np.array([0.,0.,0.])).all() and wing.use_constant_hinge_fraction:
raise ValueError("A hinge_vector is specified, but the surface is set to use a constant hinge fraction")
if len(wing.control_surfaces) > 0:
raise ValueError('Input: control surfaces are not supported on all-moving surfaces at this time')
for segment in wing.segments: #unsupported by convention
if 'control_surfaces' in segment.keys() and len(segment.control_surfaces) > 0:
raise ValueError('Input: control surfaces should be appended to the wing, not its segments. ' +
'This function will move the control surfaces to wing segments itself.')
#move wing control surfaces to from wing to its segments
wing = populate_control_sections(wing) if discretize_cs else wing
#ensure wing has attributes that will be needed later
wing_halfspan = wing.spans.projected * 0.5 if wing.symmetric else wing.spans.projected
segment_list = list(wing.segments.keys())
for i in range(n_segments):
(ia, ib) = (0, 0) if i==0 else (i-1, i)
seg_a = wing.segments[segment_list[ia]]
seg_b = wing.segments[segment_list[ib]]
seg_b.chord = seg_b.root_chord_percent *wing.chords.root ##may be worth implementing a self-calculating .chord attribute
#guarantee that all segments have leading edge sweep
if (i != 0) and (seg_a.sweeps.leading_edge is None):
old_sweep = seg_a.sweeps.quarter_chord
new_sweep = convert_sweep_segments(old_sweep, seg_a, seg_b, wing, old_ref_chord_fraction=0.25, new_ref_chord_fraction=0.0)
seg_a.sweeps.leading_edge = new_sweep
#give segments offsets for giving cs_wings an origin later
section_span = (seg_b.percent_span_location - seg_a.percent_span_location) * wing_halfspan
seg_b.x_offset = 0. if i==0 else seg_a.x_offset + section_span*np.tan(seg_a.sweeps.leading_edge)
seg_b.dih_offset = 0. if i==0 else seg_a.dih_offset + section_span*np.tan(seg_a.dihedral_outboard)
wing.segments[segment_list[-1]].sweeps.leading_edge = 1e-8
# each control_surface-turned-wing will have its own unique ID number
cs_ID = 0
# ------------------------------------------------------------------
# Build wing Data() objects and wing.span_breaks from control surfaces on segments
# ------------------------------------------------------------------
for wing in wings:
if wing.is_a_control_surface == True: #skip if this wing is actually a control surface
continue
#prepare to iterate across all segments and control surfaces
seg_breaks = RCAIDE.Framework.Core.ContainerOrdered()
LE_breaks = RCAIDE.Framework.Core.ContainerOrdered()
TE_breaks = RCAIDE.Framework.Core.ContainerOrdered()
n_segments = len(wing.segments.keys())
#process all control surfaces in each segment-------------------------------------
segment_list = list(wing.segments.keys())
for i in range(n_segments):
(ia, ib) = (0, 0) if i==0 else (i-1, i)
seg_a = wing.segments[segment_list[ia]]
seg_b = wing.segments[segment_list[ib]]
control_surfaces = seg_b.control_surfaces if 'control_surfaces' in seg_b.keys() else Data()
for cs in control_surfaces: #should be no control surfaces on root segment
# create and append a wing object from the control_surface object and relevant segments
cs_wing = make_cs_wing_from_cs(cs, seg_a, seg_b, wing, cs_ID)
wings.append(cs_wing)
# register cs start and end span breaks
cs_span_breaks = make_span_breaks_from_cs(cs, seg_a, seg_b, cs_wing, cs_ID)
if cs.cs_type==Slat:
LE_breaks.append(cs_span_breaks[0])
LE_breaks.append(cs_span_breaks[1])
else:
TE_breaks.append(cs_span_breaks[0])
TE_breaks.append(cs_span_breaks[1])
cs_ID += 1
# register segment span break
span_break = make_span_break_from_segment(seg_b)
seg_breaks.append(span_break)
#merge _breaks arrays into one span_breaks array----------------------------------
# 1. sort all span_breaks by their span_fraction
# 2. combine LE and TE breaks with the same span_fraction values (LE cuts from slats and TE cuts from others)
# 3. scan LE and TE to pick up cs cuts that cross over one or more span breaks
# 1:
LE_breaks = sorted(LE_breaks, key=lambda span_break: span_break.span_fraction)
TE_breaks = sorted(TE_breaks, key=lambda span_break: span_break.span_fraction)
seg_breaks = sorted(seg_breaks, key=lambda span_break: span_break.span_fraction)
# 2: similar to a 3-way merge sort
span_breaks = RCAIDE.Framework.Core.ContainerOrdered()
n_LE = len(LE_breaks)
n_TE = len(TE_breaks)
n_seg = len(seg_breaks)
i, j, k = 0,0,0
big_num = float('inf')
while True:
LE_span = LE_breaks[i].span_fraction if (i < n_LE) else big_num
TE_span = TE_breaks[j].span_fraction if (j < n_TE) else big_num
seg_span = seg_breaks[k].span_fraction if (k < n_seg) else big_num
if (LE_span==big_num) and (TE_span==big_num) and (seg_span==big_num):
break
if (LE_span <= TE_span) and (LE_span <= seg_span):
add_span_break(LE_breaks[i], span_breaks)
i += 1
elif (TE_span <= LE_span) and (TE_span <= seg_span):
add_span_break(TE_breaks[j], span_breaks)
j += 1
elif (seg_span <= LE_span) and (seg_span <= TE_span):
add_span_break(seg_breaks[k], span_breaks)
k += 1
else:
raise ValueError("No suitable span break") #should never occur
# 3:
ib, ob = 0, 1 #inboard, outboard indices
for edge, edge_str in enumerate(['LE','TE']):
for i in range(len(span_breaks)-1):
ID_i = span_breaks[i].cs_IDs[edge,ob]
cut = span_breaks[i].cuts[edge,ob]
if ID_i == -1:
continue
#copy the cs ID and its cut until the end of the control surface is found
for j in range(i+1,len(span_breaks)):
i += 1
ID_j = span_breaks[j].cs_IDs[edge,ib]
if ID_j == ID_i: #found control surface end
break
elif ID_j == -1: #found a span_break within control surface. copy values
span_breaks[j].cs_IDs[edge,:] = [ID_i, ID_i]
span_breaks[j].cuts[edge,:] = [cut, cut]
else:
raise ValueError('VLM does not support multiple control surfaces on the same edge at this time')
# pack span_breaks
wing.span_breaks = reprocess_span_breaks(span_breaks)
# ------------------------------------------------------------------
# Give cs_wings span_breaks arrays
# ------------------------------------------------------------------
for cs_wing in wings:
cs_w_segs = list(cs_wing.segments.keys())
if cs_wing.is_a_control_surface == False: #skip if this wing isn't actually a control surface
continue
span_breaks = RCAIDE.Framework.Core.ContainerOrdered()
span_break = make_span_break_from_segment(cs_wing.segments[cs_w_segs[0]])
span_breaks.append(span_break)
span_break = make_span_break_from_segment(cs_wing.segments[cs_w_segs[1]])
span_breaks.append(span_break)
cs_wing.span_breaks = span_breaks
return wings
# ------------------------------------------------------------------
# custom deepcopy(wings)
# --TO DO-- This is a stand-in for a more fleshed-out VLM_surface class
# ------------------------------------------------------------------
[docs]
def copy_wings(original_wings):
""" This copies VLM attributes for every wing object in original_wings into
a new wings container with new Data objects
Inputs:
original_wings - the original wings container
"""
return copy_large_container(original_wings, "wings")
[docs]
def copy_large_container(large_container, type_str):
""" This function helps avoid copying a container of large objects directly,
especially if those objects are Physical_Components
Inputs:
objects - a Container of large objects
"""
container = RCAIDE.Framework.Core.Container() if type_str != "Segments" else RCAIDE.Framework.Core.ContainerOrdered()
paths = get_paths(type_str)
for obj in large_container:
if type(obj) == RCAIDE.Library.Components.Wings.Control_Surfaces.Slat: # DO NOT COPY SLATS
continue
else:
#copy from paths
data = copy_data_from_paths(obj, paths)
#special case new attributes
if type_str == 'control_surfaces':
data.cs_type = type(obj) # needed to identify the class of a control surface
elif type_str == 'wings':
data.wing_type = type(obj)
if issubclass(data.wing_type, All_Moving_Surface):
data.sign_duplicate = obj.sign_duplicate
data.hinge_fraction = obj.hinge_fraction
data.deflection = obj.deflection
data.is_slat = False
data.use_constant_hinge_fraction = obj.use_constant_hinge_fraction
data.hinge_vector = obj.hinge_vector
data.deflection_last = 0.
container.append(data)
return container
[docs]
def copy_data_from_paths(old_object, paths):
""" This copies the attributes specified by 'paths' from old_object
into a new Data() object
Inputs:
old_object - an object to copy
"""
new_object = Data()
for path in paths:
val = old_object.deep_get(path)
recursive_set(new_object, path, val)
return new_object
[docs]
def recursive_set(data_obj, path, val):
""" This is similar to the deep_set function, but also creates
intermediate Data() objects for keys that do not yet exist. Special
copy cases are made for paths that lead to large class objects
"""
special_case_keys = ['control_surfaces', 'segments']
keys = path.split('.')
key = keys[0]
if len(keys) == 1:
if key in special_case_keys:
data_obj[key] = copy_large_container(val, key) # will eventually recurse back to this function
else:
data_obj[key] = deepcopy(val) # at this point, should only be copying primitive types or very small Data objects
return
has_key = key in data_obj.keys()
if not has_key:
data_obj[key] = Data()
new_path = '.'.join(keys[1:])
recursive_set(data_obj[key], new_path, val)
[docs]
def get_paths(type_str):
""" This returns a list of the paths to the attributes needed in VLM
for a given type of object.
Note that if any element in the paths array is the same as the array's correponding type_str,
this will cause copy_large_container() to recurse infinitely. It will also recurse infinitely
if any element in the current array is the same as a type_str that corresponds to a different array
which itself has an element that is the same as the current type_str.
Inputs:
type_str - "wings", "control_surfaces" or "Segments"
"""
if type_str == 'wings':
paths = ['tag',
'origin',
'symmetric',
'vertical',
'taper',
'dihedral',
'thickness_to_chord',
'spans.projected',
'chords.root',
'chords.tip',
'sweeps.quarter_chord',
'sweeps.leading_edge',
'twists.root',
'twists.tip',
'vortex_lift',
'airfoil',
'segments',
'control_surfaces',
]
elif type_str == 'control_surfaces':
paths = ['tag',
'span',
'span_fraction_start',
'span_fraction_end',
'hinge_fraction',
'chord_fraction',
'sign_duplicate',
'deflection',
'configuration_type',
'gain',
]
elif type_str == 'segments':
paths = ['tag',
'percent_span_location',
'twist',
'root_chord_percent',
'dihedral_outboard',
'thickness_to_chord',
'sweeps.quarter_chord',
'sweeps.leading_edge',
'airfoil',
]
return paths
# ------------------------------------------------------------------
# wing helper functions
# ------------------------------------------------------------------
[docs]
def make_cs_wing_from_cs(cs, seg_a, seg_b, wing, cs_ID):
""" This uses a control surface and the segment it lies between to create
an equilvalent wing object. The wing has a couple of non-standard attributes
that contain information about the control surface it came from
Assumptions:
None
Source:
None
Inputs:
cs - a control surface object
seg_a - the segment object inboard of the cs
seg_b - the segment object outboard of the cs. The cs is also attached to this
wing - the wing object which owns seg_a and seg_b
cs_ID - a unique identifier for the cs_wing
Outputs:
cs_wing - a Data object with relevant Wing and Control_Surface attributes
Properties Used:
N/A
"""
hspan = wing.spans.projected*0.5 if wing.symmetric else wing.spans.projected
cs_wing = copy_data_from_paths(RCAIDE.Library.Components.Wings.Wing(), get_paths("wings"))
#standard wing attributes--------------------------------------------------------------------------------------
cs_wing.tag = wing.tag + '__cs_id_{}'.format(cs_ID)
span_a = seg_a.percent_span_location
span_b = seg_b.percent_span_location
twist_a = seg_a.twist
twist_b = seg_b.twist
cs_wing.twists.root = np.interp(cs.span_fraction_start, [span_a, span_b], [twist_a, twist_b])
cs_wing.twists.tip = np.interp(cs.span_fraction_end, [span_a, span_b], [twist_a, twist_b])
cs_wing.dihedral = seg_a.dihedral_outboard
cs_wing.thickness_to_chord = (seg_a.thickness_to_chord + seg_b.thickness_to_chord)/2
cs_wing.origin = np.array(wing.origin) *1.
span_fraction_tot = cs.span_fraction_end - cs.span_fraction_start
cs_wing.spans.projected = wing.spans.projected * span_fraction_tot #includes 2x length if cs is on a symmetric wing
wing_chord_local_at_cs_root = np.interp(cs.span_fraction_start, [span_a, span_b], [seg_a.chord, seg_b.chord])
wing_chord_local_at_cs_tip = np.interp(cs.span_fraction_end, [span_a, span_b], [seg_a.chord, seg_b.chord])
cs_wing.chords.root = wing_chord_local_at_cs_root * cs.chord_fraction
cs_wing.chords.tip = wing_chord_local_at_cs_tip * cs.chord_fraction
cs_wing.taper = cs_wing.chords.tip / cs_wing.chords.root
cs_wing.sweeps.quarter_chord = 0. # leave at 0. VLM will use leading edge
cs_wing.symmetric = wing.symmetric
cs_wing.vertical = wing.vertical
cs_wing.vortex_lift = wing.vortex_lift
#non-standard wing attributes, mostly to do with cs_wing's identity as a control surface-----------------------
#metadata
cs_wing.is_a_control_surface = True
cs_wing.cs_ID = cs_ID
cs_wing.name = wing.tag + '__' + seg_b.tag + '__' + cs.tag + '__cs_ID_{}'.format(cs_ID)
cs_wing.is_slat = (cs.cs_type==Slat)
cs_wing.is_aileron = (cs.cs_type==Aileron)
cs_wing.pivot_edge = 'TE' if cs_wing.is_slat else 'LE'
#control surface attributes
cs_wing.chord_fraction = cs.chord_fraction
cs_wing.hinge_fraction = cs.hinge_fraction
cs_wing.sign_duplicate = cs.sign_duplicate
cs_wing.deflection = cs.deflection
cs_wing.deflection_last = 0.
#adjustments---------------------------------------------------------------------------------------------------
#adjust origin - may need to be adjusted later
wing_halfspan = wing.spans.projected * 0.5 if wing.symmetric else wing.spans.projected
LE_TE_cs_offset = 0. if cs_wing.is_slat else (1 - cs.chord_fraction)*wing_chord_local_at_cs_root
cs_wing.origin[0,0] += np.interp(cs.span_fraction_start, [span_a, span_b], [seg_a.x_offset, seg_b.x_offset]) + LE_TE_cs_offset
cs_wing.origin[0,1] += cs.span_fraction_start * wing_halfspan if not wing.vertical else np.interp(cs.span_fraction_start, [span_a, span_b], [seg_a.dih_offset, seg_b.dih_offset])
cs_wing.origin[0,2] += np.interp(cs.span_fraction_start, [span_a, span_b], [seg_a.dih_offset, seg_b.dih_offset]) if not wing.vertical else cs.span_fraction_start * wing_halfspan
# holds all required y-coords. Will be added to during discretization to ensure y-coords match up between wing and control surface.
rel_offset = cs_wing.origin[0,1] - wing.origin[0][1] if not cs_wing.vertical else cs_wing.origin[0,2] - wing.origin[0][2]
cs_wing.y_coords_required = [cs.span_fraction_end*hspan - rel_offset] #initialize with the tip y-coord. Other coords to be added in VLM
#find sweep of the 'outside' edge (LE for slats, TE for everything else)
use_le_sweep = not (seg_a.sweeps.leading_edge is None)
new_cf = 0. if cs_wing.is_slat else 1
old_cf = 0. if use_le_sweep else 0.25
old_sweep = seg_a.sweeps.leading_edge if use_le_sweep else seg_a.sweeps.quarter_chord
new_sweep = convert_sweep_segments(old_sweep, seg_a, seg_b, wing, old_ref_chord_fraction=old_cf, new_ref_chord_fraction=new_cf)
cs_wing.outside_sweep = new_sweep
#find leading edge sweep
if cs_wing.is_slat:
cs_wing.sweeps.leading_edge = new_sweep
else:
new_cf = 1 - cs_wing.chord_fraction
new_sweep = convert_sweep_segments(old_sweep, seg_a, seg_b, wing, old_ref_chord_fraction=old_cf, new_ref_chord_fraction=new_cf)
cs_wing.sweeps.leading_edge = new_sweep
#convert to segmented wing-------------------------------------------------------------------------------------
cs_wing = convert_to_segmented_wing(cs_wing)
# give segments offsets (in coordinates relative to the cs_wing)
cs_w_segs = list(cs_wing.segments.keys())
cs_wing.segments[cs_w_segs[0]].x_offset = 0.
cs_wing.segments[cs_w_segs[0]].dih_offset = 0.
cs_wing.segments[cs_w_segs[1]].x_offset = wing_halfspan * span_fraction_tot *np.tan(cs_wing.segments[cs_w_segs[0]].sweeps.leading_edge)
cs_wing.segments[cs_w_segs[1]].dih_offset = wing_halfspan * span_fraction_tot *np.tan(cs_wing.segments[cs_w_segs[0]].dihedral_outboard)
#add airfoil
cs_wing.segments[cs_w_segs[0]].airfoil = seg_a.airfoil
cs_wing.segments[cs_w_segs[1]].airfoil = seg_b.airfoil if cs.span_fraction_end==span_b else seg_a.airfoil
return cs_wing
[docs]
def convert_to_segmented_wing(wing):
""" This turns a non-segmented wing into a segmented wing
Assumptions:
If a given wing has no segments, it must have either .taper or .chords.tip defined
Source:
None
Inputs:
VD - vortex distribution
geometry.
wings.wing.
twists.root
twists.tip
dihedral
sweeps.quarter_chord
thickness_to_chord
taper
chord.root
chords.tip
Properties Used:
N/A
"""
if len(wing.segments.keys()) > 0:
return wing
# root segment
segment = RCAIDE.Library.Components.Wings.Segments.Segment()
segment.tag = 'root_segment'
segment.percent_span_location = 0.0
segment.twist = wing.twists.root
segment.root_chord_percent = 1.
segment.chord = wing.chords.root #non-standard attribute, needed for VLM
segment.dihedral_outboard = wing.dihedral
segment.sweeps.quarter_chord = wing.sweeps.quarter_chord
segment.sweeps.leading_edge = wing.sweeps.leading_edge
segment.thickness_to_chord = wing.thickness_to_chord
if wing.airfoil:
segment.append_airfoil(wing.airfoil)
wing.segments.append(segment)
# tip segment
if wing.taper==0:
wing.taper = wing.chords.tip / wing.chords.root
elif wing.chords.tip==0:
wing.chords.tip = wing.chords.root * wing.taper
segment = RCAIDE.Library.Components.Wings.Segments.Segment()
segment.tag = 'tip_segment'
segment.percent_span_location = 1.
segment.twist = wing.twists.tip
segment.root_chord_percent = wing.taper
segment.chord = wing.chords.tip #non-standard attribute, needed for VLM
segment.dihedral_outboard = 0.
segment.sweeps.quarter_chord = 0.
segment.sweeps.leading_edge = 1e-8
segment.thickness_to_chord = wing.thickness_to_chord
if wing.airfoil:
segment.append_airfoil(wing.airfoil)
wing.segments.append(segment)
return wing
# ------------------------------------------------------------------
# span_break processing helper functions
# ------------------------------------------------------------------
[docs]
def add_span_break(span_break, span_breaks):
""" This is a helper function that appends or superimposes a span_break
into span_breaks
Assumptions:
None
Source:
None
Inputs:
span_break
span_breaks
Properties Used:
N/A
"""
if len(span_breaks) == 0:
span_breaks.append(span_break)
else:
# if non-coincident, the space between the breaks is nominal wing: append the new span_break
if span_breaks[-1].span_fraction < span_break.span_fraction:
span_breaks.append(span_break)
# else coincident: need to superimpose cs_IDs and cuts, not append
else:
boolean = span_breaks[-1].cs_IDs==-1
span_breaks[-1].cs_IDs[boolean] = span_break.cs_IDs[boolean]
span_breaks[-1].cuts[boolean] = span_break.cuts[boolean]
return
[docs]
def reprocess_span_breaks(span_breaks):
""" This reprocesses the tags in a newly superimposed set of
span_breaks and creates a new object so that the new keys match
the new tags
Inputs:
span_breaks
"""
sbs = RCAIDE.Framework.Core.ContainerOrdered()
for i,span_break in enumerate(span_breaks):
span_break.tag = make_span_break_tag(span_break)
sbs.append(span_break)
return sbs
# ------------------------------------------------------------------
# span_break creation helper functions
# ------------------------------------------------------------------
[docs]
def make_span_break_from_segment(seg):
""" This creates a span_break Data() object from a segment
Assumptions:
None
Source:
None
Inputs:
seg - a segment object with standard attributes except for:
.chord
Properties Used:
N/A
"""
span_frac = seg.percent_span_location
airfoil = seg.airfoil
dihedral_ob = seg.dihedral_outboard
sweep_ob_QC = seg.sweeps.quarter_chord
sweep_ob_LE = seg.sweeps.leading_edge
twist = seg.twist
local_chord = seg.chord #non-standard attribute
x_offset = seg.x_offset
dih_offset = seg.dih_offset
span_break = make_span_break(-1, 0, 0, span_frac, 0., airfoil,
dihedral_ob, sweep_ob_QC, sweep_ob_LE, twist, local_chord,
x_offset, dih_offset)
span_break.cuts = np.array([[0.,0.],
[1.,1.]])
return span_break
[docs]
def make_span_breaks_from_cs(cs, seg_a, seg_b, cs_wing, cs_ID):
""" This creates span_break Data() objects from a control surface, its
owning segments, and their owning cs_wing
Assumptions:
None
Source:
None
Inputs:
cs - a control surface object
seg_a - the segment object inboard of the cs
seg_b - the segment object outboard of the cs. The cs is also attached to this
cs_wing - the wing object which owns seg_a and seg_b
cs_ID - a unique identifier for the cs_wing
Properties Used:
N/A
"""
is_slat = (cs.cs_type==Slat)
LE_TE = 0 if is_slat else 1
span_a = seg_a.percent_span_location
span_b = seg_b.percent_span_location
#inboard span break
ib_ob = 1 #the inboard break of the cs is the outboard part of the span_break
span_frac = cs.span_fraction_start
ob_cut = cs.chord_fraction if is_slat else 1 - cs.chord_fraction
airfoil = seg_a.airfoil
dihedral_ob = seg_a.dihedral_outboard
sweep_ob_QC = seg_a.sweeps.quarter_chord
sweep_ob_LE = seg_a.sweeps.leading_edge
twist = cs_wing.twists.root
local_chord = cs_wing.chords.root / cs.chord_fraction
x_offset = np.interp(cs.span_fraction_start, [span_a, span_b], [seg_a.x_offset, seg_b.x_offset])
dih_offset = np.interp(cs.span_fraction_start, [span_a, span_b], [seg_a.dih_offset, seg_b.dih_offset])
inboard_span_break = make_span_break(cs_ID, LE_TE, ib_ob, span_frac, ob_cut, airfoil,
dihedral_ob, sweep_ob_QC, sweep_ob_LE, twist, local_chord,
x_offset, dih_offset)
#outboard span break
is_coincident = (cs.span_fraction_end==seg_b.percent_span_location)
ib_ob = 0 #the outboard break of the cs is the inboard part of the span_break
span_frac = cs.span_fraction_end
ib_cut = cs.chord_fraction if is_slat else 1 - cs.chord_fraction
airfoil = seg_b.airfoil if is_coincident else seg_a.airfoil #take seg_b value if this outboard break is conicident with seg_b
dihedral_ob = seg_b.dihedral_outboard if is_coincident else seg_a.dihedral_outboard
sweep_ob_QC = seg_b.sweeps.quarter_chord if is_coincident else seg_a.sweeps.quarter_chord
sweep_ob_LE = seg_b.sweeps.leading_edge if is_coincident else seg_a.sweeps.leading_edge
twist = cs_wing.twists.tip
local_chord = cs_wing.chords.tip / cs.chord_fraction
x_offset = np.interp(cs.span_fraction_end, [span_a, span_b], [seg_a.x_offset, seg_b.x_offset])
dih_offset = np.interp(cs.span_fraction_end, [span_a, span_b], [seg_a.dih_offset, seg_b.dih_offset])
outboard_span_break = make_span_break(cs_ID, LE_TE, ib_ob, span_frac, ib_cut, airfoil,
dihedral_ob, sweep_ob_QC, sweep_ob_LE, twist, local_chord,
x_offset, dih_offset)
return inboard_span_break, outboard_span_break
[docs]
def make_span_break(cs_ID, LE_TE, ib_ob, span_frac, chord_cut, airfoil,
dihedral_ob, sweep_ob_QC, sweep_ob_LE, twist, local_chord,
x_offset, dih_offset):
""" This gathers information related to a span break into one Data() object.
A span break is the spanwise location of a discontinuity in the discretization
of the panels. These can be caused by segments and by the inboard and outboard
edges of a control surface. The inboard and outboard sides of a span break can
have different chords due to cuts made by control surfaces. Ultimately, the
attributes of the span_breaks of the wing will provide the discretization function
generate_wing_vortex_distribution() with the necessary values to make VLM panels
as well as reshape those panels to make the control surface cuts dipicted below.
A diagram is given below:
nominal local chord
fuselage inboard LE | | . outboard LE
<--- | | .
| | .
| | . <-- cut from a slat
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | . <-- cut from a non-slat control surface with a different chord
cut from a non-slat control surface | | . fraction than the control surface on the inboard side
--> . | .
. | .
inboard TE . | . outboard TE
|_______________________|
|
there is 0 spanwise
distance between inboard
and outboard sides
Outputs:
span_break
Properties Used:
N/A
"""
span_break = Data()
span_break.cs_IDs = np.array([[-1,-1], # [[inboard LE cs, outboard LE cs],
[-1,-1]]) # [inboard TE cs, outboard TE cs]]
span_break.cs_IDs[LE_TE,ib_ob] = cs_ID
span_break.span_fraction = span_frac
# The following 'cut' attributes are in terms of the local total chord and represent positions.
# (an aileron with chord fraction 0.2 would have a cut value of 0.8)
# For inboard_cut, -1 takes value of previous outboard cut value in a later function
# For outboard_cut, -1 takes value of next inboard cut value.
# If no break directly touching this one, cut becomes 0 (LE) or 1 (TE).
span_break.cuts = np.array([[0.,0.], # [[inboard LE cut, outboard LE cut],
[1.,1.]]) # [inboard TE cut, outboard TE cut]]
span_break.cuts[LE_TE,ib_ob] = chord_cut
span_break.airfoil = airfoil
span_break.dihedral_outboard = dihedral_ob
span_break.sweep_outboard_QC = sweep_ob_QC
span_break.sweep_outboard_LE = sweep_ob_LE
span_break.twist = twist
span_break.local_chord = local_chord #this is the local chord BEFORE cuts are made
span_break.x_offset = x_offset
span_break.dih_offset = dih_offset #dih_offset is the y or z accumulated offset from dihedral
span_break.tag = make_span_break_tag(span_break)
return span_break
[docs]
def make_span_break_tag(span_break):
location = round(span_break.span_fraction, 3)
cs_IDs_arr = span_break.cs_IDs.flatten()
cs_IDs_str = '{}'.format(cs_IDs_arr).replace('[','').replace(']','').replace('-1', 'na').replace(' ', '_')
return "{}___{}".format(location, cs_IDs_str)