# RCAIDE/Framework/External_Interfaces/OpenVSP/vsp_wing.py
# Created: Jun 2018, T. St Francis
# Modified: Aug 2018, T. St Francis
# Jan 2020, T. MacDonald
# Jul 2020, E. Botero
# May 2021, E. Botero
# Feb 2022, M. Cunningham
# ----------------------------------------------------------------------------------------------------------------------
# IMPORT
# ----------------------------------------------------------------------------------------------------------------------
# RCAIDE imports
import RCAIDE
from RCAIDE.Framework.Core import Units , Data
from RCAIDE.Library.Components.Airfoils.Airfoil import Airfoil
from RCAIDE.Library.Methods.Geometry.Planform import wing_planform, wing_segmented_planform
import numpy as np
import string
import os
import sys
try:
import vsp as vsp
except ImportError:
try:
import openvsp as vsp
except ImportError:
# This allows RCAIDE to build without OpenVSP
pass
# This enforces lowercase names
chars = string.punctuation + string.whitespace
t_table = str.maketrans( chars + string.ascii_uppercase ,
'_'*len(chars) + string.ascii_lowercase )
# ----------------------------------------------------------------------------------------------------------------------
# vsp read wing
# ----------------------------------------------------------------------------------------------------------------------
[docs]
def read_vsp_wing(wing_id, main_wing_tag = None, units_type='SI', write_airfoil_file=True, use_scaling=True):
"""This reads an OpenVSP wing vehicle geometry and writes it into a RCAIDE wing format.
Assumptions:
1. OpenVSP wing is divided into segments ("XSecs" in VSP).
2. Written for OpenVSP 3.21.1
Source:
N/A
Inputs:
1. VSP 10-digit geom ID for wing.
2. units_type set to 'SI' (default) or 'Imperial'.
3. Boolean for whether or not to write an airfoil file(default = True).
4. Boolean for whether or not to use the scaling from OpenVSP (default = True).
Outputs:
Writes RCAIDE wing object, with these geometries, from VSP:
Wings.Wing. (* is all keys)
origin [m] in all three dimensions
spans.projected [m]
chords.root [m]
chords.tip [m]
aspect_ratio [-]
sweeps.quarter_chord [radians]
twists.root [radians]
twists.tip [radians]
thickness_to_chord [-]
dihedral [radians]
symmetric <boolean>
tag <string>
areas.reference [m^2]
areas.wetted [m^2]
Segments.
tag <string>
twist [radians]
percent_span_location [-] .1 is 10%
root_chord_percent [-] .1 is 10%
dihedral_outboard [radians]
sweeps.quarter_chord [radians]
thickness_to_chord [-]
airfoil <NACA 4-series, 6 series, or airfoil file>
Properties Used:
N/A
"""
# Check if this is vertical tail, this seems like a weird first step but it's necessary
# Get the initial rotation to get the dihedral angles
x_rot = vsp.GetParmVal( wing_id,'X_Rotation','XForm')
y_rot = vsp.GetParmVal( wing_id,'Y_Rotation','XForm')
if abs(x_rot) >=70:
wing = RCAIDE.Library.Components.Wings.Vertical_Tail()
wing.vertical = True
sign = (np.sign(x_rot))
x_rot = (sign*90 - sign*x_rot) * Units.deg
else:
# Instantiate a wing
wing = RCAIDE.Library.Components.Wings.Wing()
x_rot = x_rot * Units.deg
y_rot = y_rot * Units.deg
# Set the units
if units_type == 'SI':
units_factor = Units.meter * 1.
elif units_type == 'imperial':
units_factor = Units.foot * 1.
elif units_type == 'inches':
units_factor = Units.inch * 1.
# Apply a tag to the wing
if vsp.GetGeomName(wing_id):
tag = vsp.GetGeomName(wing_id)
tag = tag.translate(t_table)
if main_wing_tag == tag:
wing = RCAIDE.Library.Components.Wings.Main_Wing()
save_filename = os.path.join(sys.path[0], tag )
wing.tag = tag
else:
wing.tag = 'winggeom'
if use_scaling:
scaling = vsp.GetParmVal(wing_id, 'Scale', 'XForm')
else:
scaling = 1.
units_factor = units_factor*scaling
# Top level wing parameters
# Wing origin
wing.origin[0][0] = vsp.GetParmVal(wing_id, 'X_Location', 'XForm') * units_factor
wing.origin[0][1] = vsp.GetParmVal(wing_id, 'Y_Location', 'XForm') * units_factor
wing.origin[0][2] = vsp.GetParmVal(wing_id, 'Z_Location', 'XForm') * units_factor
# Wing Symmetry
sym_planar = vsp.GetParmVal(wing_id, 'Sym_Planar_Flag', 'Sym')
sym_origin = vsp.GetParmVal(wing_id, 'Sym_Ancestor_Origin_Flag', 'Sym')
# Check for symmetry
if sym_planar == 2. and sym_origin == 1.: #origin at wing, not vehicle
wing.symmetric = True
else:
wing.symmetric = False
#More top level parameters
total_proj_span = vsp.GetParmVal(wing_id, 'TotalProjectedSpan', 'WingGeom') * units_factor
wing.aspect_ratio = vsp.GetParmVal(wing_id, 'TotalAR', 'WingGeom')
wing.areas.reference = vsp.GetParmVal(wing_id, 'TotalArea', 'WingGeom') * units_factor**2
wing.spans.projected = total_proj_span
# Check if this is a single segment wing
xsec_surf_id = vsp.GetXSecSurf(wing_id, 0) # This is how VSP stores surfaces.
x_sec_1 = vsp.GetXSec(xsec_surf_id, 1)
if vsp.GetNumXSec(xsec_surf_id) == 2:
single_seg = True
else:
single_seg = False
segment_num = vsp.GetNumXSec(xsec_surf_id) # Get number of segments
span_sum = 0. # Non-projected.
proj_span_sum = 0. # Projected.
segment_spans = [None] * (segment_num) # Non-projected.
segment_dihedral = [None] * (segment_num)
segment_sweeps_quarter_chord = [None] * (segment_num)
# Necessary wing segment definitions start at XSec_1 (XSec_0 exists mainly to hold the root airfoil)
xsec_surf_id = vsp.GetXSecSurf(wing_id, 0)
x_sec = vsp.GetXSec(xsec_surf_id, 1)
chord_parm = vsp.GetXSecParm(x_sec,'Root_Chord')
root_chord = vsp.GetParmVal(chord_parm) * units_factor
# -------------
# Wing segments
# -------------
if single_seg == False:
# Convert VSP XSecs to RCAIDE segments. (Wing segments are defined by outboard sections in VSP, but inboard sections in RCAIDE.)
for i in range(1, segment_num+1):
# XSec airfoil
jj = i-1 # Airfoil index i-1 because VSP airfoils and sections are one index off relative to RCAIDE.
segment = RCAIDE.Library.Components.Wings.Segments.Segment()
segment.tag = 'Section_' + str(i)
thick_cord = vsp.GetParmVal(wing_id, 'ThickChord', 'XSecCurve_' + str(jj))
segment.thickness_to_chord = thick_cord # Thick_cord stored for use in airfoil, below.
if i!=segment_num:
segment_root_chord = vsp.GetParmVal(wing_id, 'Root_Chord', 'XSec_' + str(i)) * units_factor
else:
segment_root_chord = 0.0
segment.root_chord_percent = segment_root_chord / root_chord
segment.percent_span_location = proj_span_sum / (total_proj_span/(1+wing.symmetric))
segment.twist = vsp.GetParmVal(wing_id, 'Twist', 'XSec_' + str(jj)) * Units.deg + y_rot
if i==1:
wing.thickness_to_chord = thick_cord
if i < segment_num: # This excludes the tip xsec, but we need a segment in RCAIDE to store airfoil.
sweep = vsp.GetParmVal(wing_id, 'Sweep', 'XSec_' + str(i)) * Units.deg
sweep_loc = vsp.GetParmVal(wing_id, 'Sweep_Location', 'XSec_' + str(i))
AR = 2*vsp.GetParmVal(wing_id, 'Aspect', 'XSec_' + str(i))
taper = vsp.GetParmVal(wing_id, 'Taper', 'XSec_' + str(i))
segment_sweeps_quarter_chord[i] = convert_sweep(sweep,sweep_loc,0.25,AR,taper)
segment.sweeps.quarter_chord = segment_sweeps_quarter_chord[i] # Used again, below
# Used for dihedral computation, below.
segment_dihedral[i] = vsp.GetParmVal(wing_id, 'Dihedral', 'XSec_' + str(i)) * Units.deg + x_rot
segment.dihedral_outboard = segment_dihedral[i]
segment_spans[i] = vsp.GetParmVal(wing_id, 'Span', 'XSec_' + str(i)) * units_factor
proj_span_sum += segment_spans[i] * np.cos(segment_dihedral[i])
span_sum += segment_spans[i]
else:
segment.root_chord_percent = (vsp.GetParmVal(wing_id, 'Tip_Chord', 'XSec_' + str(i-1))) * units_factor /root_chord
xsec_id = str(vsp.GetXSec(xsec_surf_id, jj))
airfoil = Airfoil() # TO DO : ADD MORE AIRFOILS
airfoil.geometry = Data()
if vsp.GetXSecShape(xsec_id) == vsp.XS_FOUR_SERIES: # XSec shape: NACA 4-series
camber = vsp.GetParmVal(wing_id, 'Camber', 'XSecCurve_' + str(jj))
if camber == 0.:
camber_loc = 0.
else:
camber_loc = vsp.GetParmVal(wing_id, 'CamberLoc', 'XSecCurve_' + str(jj))
airfoil.geometry.thickness_to_chord = thick_cord
camber_round = int(np.around(camber*100))
camber_loc_round = int(np.around(camber_loc*10))
thick_cord_round = int(np.around(thick_cord*100))
airfoil.tag = 'NACA ' + str(camber_round) + str(camber_loc_round) + str(thick_cord_round)
elif vsp.GetXSecShape(xsec_id) == vsp.XS_SIX_SERIES: # XSec shape: NACA 6-series
thick_cord_round = int(np.around(thick_cord*100))
a_value = vsp.GetParmVal(wing_id, 'A', 'XSecCurve_' + str(jj))
ideal_CL = int(np.around(vsp.GetParmVal(wing_id, 'IdealCl', 'XSecCurve_' + str(jj))*10))
series_vsp = int(vsp.GetParmVal(wing_id, 'Series', 'XSecCurve_' + str(jj)))
series_dict = {0:'63',1:'64',2:'65',3:'66',4:'67',5:'63A',6:'64A',7:'65A'} # VSP series values.
series = series_dict[series_vsp]
airfoil.tag = 'NACA ' + series + str(ideal_CL) + str(thick_cord_round) + ' a=' + str(np.around(a_value,1))
elif vsp.GetXSecShape(xsec_id) == vsp.XS_FILE_AIRFOIL: # XSec shape: 12 is type AF_FILE
airfoil.geometry.thickness_to_chord = thick_cord
# VSP airfoil API calls get coordinates and write files with the final argument being the fraction of segment position, regardless of relative spans.
# (Write the root airfoil with final arg = 0. Write 4th airfoil of 5 segments with final arg = .8)
if write_airfoil_file==True:
vsp.WriteSeligAirfoil(str(save_filename) + '_airfoil_XSec_' + str(jj) +'.dat', wing_id, float(jj/segment_num))
airfoil.coordinate_file = str(save_filename) + '_airfoil_XSec_' + str(jj) +'.dat'
airfoil.tag = 'airfoil'
segment.append_airfoil(airfoil)
wing.segments.append(segment)
# Wing dihedral
proj_span_sum_alt = 0.
span_sum_alt = 0.
sweeps_sum = 0.
for ii in range(1, segment_num):
span_sum_alt += segment_spans[ii]
proj_span_sum_alt += segment_spans[ii] * np.cos(segment_dihedral[ii]) # Use projected span to find total wing dihedral.
sweeps_sum += segment_spans[ii] * np.tan(segment_sweeps_quarter_chord[ii])
wing.dihedral = np.arccos(proj_span_sum_alt / span_sum_alt)
wing.sweeps.quarter_chord = -np.arctan(sweeps_sum / span_sum_alt) # Minus sign makes it positive sweep.
# Add a tip segment, all values are zero except the tip chord
tc = vsp.GetParmVal(wing_id, 'Tip_Chord', 'XSec_' + str(segment_num-1)) * units_factor
# Chords
wing.chords.root = vsp.GetParmVal(wing_id, 'Tip_Chord', 'XSec_0') * units_factor
wing.chords.tip = tc
wing.chords.mean_geometric = wing.areas.reference / wing.spans.projected
# Just double calculate and fix things:
wing = wing_segmented_planform(wing)
else:
# Single segment
# Get ID's
x_sec_1_dih_parm = vsp.GetXSecParm(x_sec_1,'Dihedral')
x_sec_1_sweep_parm = vsp.GetXSecParm(x_sec_1,'Sweep')
x_sec_1_sweep_loc_parm = vsp.GetXSecParm(x_sec_1,'Sweep_Location')
x_sec_1_taper_parm = vsp.GetXSecParm(x_sec_1,'Taper')
x_sec_1_rc_parm = vsp.GetXSecParm(x_sec_1,'Root_Chord')
x_sec_1_tc_parm = vsp.GetXSecParm(x_sec_1,'Tip_Chord')
x_sec_1_t_parm = vsp.GetXSecParm(x_sec_1,'ThickChord')
# Calcs
sweep = vsp.GetParmVal(x_sec_1_sweep_parm) * Units.deg
sweep_loc = vsp.GetParmVal(x_sec_1_sweep_loc_parm)
taper = vsp.GetParmVal(x_sec_1_taper_parm)
c_4_sweep = convert_sweep(sweep,sweep_loc,0.25,wing.aspect_ratio,taper)
# Pull and pack
wing.sweeps.quarter_chord = c_4_sweep
wing.taper = taper
wing.dihedral = vsp.GetParmVal(x_sec_1_dih_parm) * Units.deg + x_rot
wing.chords.root = vsp.GetParmVal(x_sec_1_rc_parm)* units_factor
wing.chords.tip = vsp.GetParmVal(x_sec_1_tc_parm) * units_factor
wing.chords.mean_geometric = wing.areas.reference / wing.spans.projected
wing.thickness_to_chord = vsp.GetParmVal(x_sec_1_t_parm)
# Just double calculate and fix things:
wing = wing_planform(wing)
# Twists
wing.twists.root = vsp.GetParmVal(wing_id, 'Twist', 'XSec_0') * Units.deg + y_rot
wing.twists.tip = vsp.GetParmVal(wing_id, 'Twist', 'XSec_' + str(segment_num-1)) * Units.deg + y_rot
# check if control surface (sub surfaces) are defined
tags = []
LE_flags = []
U_starts = []
U_ends = []
chord_fractions = []
# determine the number of segments and where the breaks are
if len(wing.segments.keys())>0:
N = len(wing.segments.keys())-1
# Do a for loop to find the location of breaks
breaks = []
for seg in wing.segments:
breaks.append(seg.percent_span_location)
else:
N = 1
# Location of breaks
breaks = [0.,1.]
num_cs = vsp.GetNumSubSurf(wing_id)
# loop through wing and get all control surface parameters
for cs_idx in range(num_cs):
cs_id = vsp.GetSubSurf(wing_id,cs_idx)
param_names = vsp.GetSubSurfParmIDs(cs_id)
tags.append(vsp.GetSubSurfName(cs_id))
for p_idx in range(len(param_names)):
if 'LE_Flag' == vsp.GetParmName(param_names[p_idx]):
LE_flags.append(vsp.GetParmVal(param_names[p_idx]))
if 'UStart' == vsp.GetParmName(param_names[p_idx]):
U_starts.append(vsp.GetParmVal(param_names[p_idx]))
if 'UEnd' == vsp.GetParmName(param_names[p_idx]):
U_ends.append(vsp.GetParmVal(param_names[p_idx]))
if 'Length_C_Start' == vsp.GetParmName(param_names[p_idx]):
chord_fractions.append(vsp.GetParmVal(param_names[p_idx]))
# assign control surface parameters to wings. Outer most control surface on main/horizontal wing is assigned a aileron
for cs_idx in range(num_cs):
aileron_present = False
if num_cs > 1:
aileron_loc = np.argmax(np.array(U_starts))
if cs_idx == aileron_loc:
aileron_present = True
if LE_flags[cs_idx] == 1.0:
CS = RCAIDE.Library.Components.Wings.Control_Surfaces.Slat()
else:
if wing.vertical == True:
CS = RCAIDE.Library.Components.Wings.Control_Surfaces.Rudder()
else:
if aileron_present:
CS = RCAIDE.Library.Components.Wings.Control_Surfaces.Aileron()
else:
CS = RCAIDE.Library.Components.Wings.Control_Surfaces.Flap()
CS.tag = tags[cs_idx]
# Do some math to get U into a span fraction
U_scale = 1/(N+2) # U scaling
# Determine which segment the control surfaces begin and end, use floor
segment_start = int(np.floor(U_starts[cs_idx]/U_scale)) - 1
segment_end = int(np.floor(U_ends[cs_idx]/U_scale)) - 1
segment_normalized_start = U_starts[cs_idx]/U_scale - (segment_start+1)
segment_normalized_end = U_ends[cs_idx]/U_scale - (segment_end+1)
# calculate segment spans
start_span = breaks[segment_start+1] - breaks[segment_start]
end_span = breaks[segment_end+1] - breaks[segment_end]
# Calculate the offsets
start_offset = breaks[segment_start]
end_offset = breaks[segment_end]
# Calculate the end points
span_start = segment_normalized_start * start_span + start_offset
span_end = segment_normalized_end * end_span + end_offset
CS.span_fraction_start = np.max([span_start, 0])
CS.span_fraction_end = np.min([span_end,1])
if CS.span_fraction_start > 1 or CS.span_fraction_end < 0:
raise AssertionError("RCAIDE import of VSP files does not allow control surfaces defined for the wing caps.")
CS.chord_fraction = chord_fractions[cs_idx]
CS.span = (CS.span_fraction_end - CS.span_fraction_start)*wing.spans.projected
wing.append_control_surface(CS)
return wing
# ----------------------------------------------------------------------------------------------------------------------
# write_vsp_wing
# ----------------------------------------------------------------------------------------------------------------------
[docs]
def write_vsp_wing(vehicle,wing, area_tags, fuel_tank_set_ind, OML_set_ind):
"""This write a given wing into OpenVSP format
Assumptions:
If wing segments are defined, they must cover the full span.
(may work in some other cases, but functionality will not be maintained)
Source:
N/A
Inputs:
vehicle [-] vehicle data structure
wing.
origin [m] in all three dimensions
spans.projected [m]
chords.root [m]
chords.tip [m]
sweeps.quarter_chord [radians]
twists.root [radians]
twists.tip [radians]
thickness_to_chord [-]
dihedral [radians]
tag <string>
Segments.*. (optional)
twist [radians]
percent_span_location [-] .1 is 10%
root_chord_percent [-] .1 is 10%
dihedral_outboard [radians]
sweeps.quarter_chord [radians]
thickness_to_chord [-]
area_tags <dict> used to keep track of all tags needed in wetted area computation
fuel_tank_set_index <int> OpenVSP object set containing the fuel tanks
Outputs:
area_tags <dict> used to keep track of all tags needed in wetted area computation
wing_id <str> OpenVSP ID for given wing
Properties Used:
N/A
"""
wing_x = wing.origin[0][0]
wing_y = wing.origin[0][1]
wing_z = wing.origin[0][2]
if wing.symmetric == True:
span = wing.spans.projected/2. # span of one side
else:
span = wing.spans.projected
root_chord = wing.chords.root
tip_chord = wing.chords.tip
sweep = wing.sweeps.quarter_chord / Units.deg
sweep_loc = 0.25
root_twist = wing.twists.root / Units.deg
tip_twist = wing.twists.tip / Units.deg
root_tc = wing.thickness_to_chord
tip_tc = wing.thickness_to_chord
dihedral = wing.dihedral / Units.deg
# Check to see if segments are defined. Get count
if len(wing.segments.keys())>0:
n_segments = len(wing.segments.keys())
else:
n_segments = 0
# Create the wing
wing_id = vsp.AddGeom( "WING" )
vsp.SetGeomName(wing_id, wing.tag)
area_tags[wing.tag] = ['wings',wing.tag]
# Make names for each section and insert them into the wing if necessary
x_secs = []
x_sec_curves = []
# n_segments + 2 will create an extra segment if the root segment is
# included in the list of segments. This is not used and the tag is
# removed when the segments are checked for this case.
for i_segs in range(0,n_segments+2):
x_secs.append('XSec_' + str(i_segs))
x_sec_curves.append('XSecCurve_' + str(i_segs))
# Apply the basic characteristics of the wing to root and tip
if wing.symmetric == False:
vsp.SetParmVal( wing_id,'Sym_Planar_Flag','Sym',0)
if wing.vertical == True:
vsp.SetParmVal( wing_id,'X_Rel_Rotation','XForm',90)
dihedral = -dihedral # check for vertical tail, direction reverses from RCAIDE/AVL
vsp.SetParmVal( wing_id,'X_Rel_Location','XForm',wing_x)
vsp.SetParmVal( wing_id,'Y_Rel_Location','XForm',wing_y)
vsp.SetParmVal( wing_id,'Z_Rel_Location','XForm',wing_z)
# This ensures that the other VSP parameters are driven properly
vsp.SetDriverGroup( wing_id, 1, vsp.SPAN_WSECT_DRIVER, vsp.ROOTC_WSECT_DRIVER, vsp.TIPC_WSECT_DRIVER )
# Root chord
vsp.SetParmVal( wing_id,'Root_Chord',x_secs[1],root_chord)
# Sweep of the first section
vsp.SetParmVal( wing_id,'Sweep',x_secs[1],sweep)
vsp.SetParmVal( wing_id,'Sweep_Location',x_secs[1],sweep_loc)
# Twists
if n_segments != 0:
segment_keys = list(wing.segments.keys())
if np.isclose(wing.segments[segment_keys[0]].percent_span_location,0.):
vsp.SetParmVal( wing_id,'Twist',x_secs[0],wing.segments[segment_keys[0]].twist / Units.deg) # root
else:
vsp.SetParmVal( wing_id,'Twist',x_secs[0],root_twist) # root
# The tips should write themselves
else:
vsp.SetParmVal( wing_id,'Twist',x_secs[0],root_twist) # root
vsp.SetParmVal( wing_id,'Twist',x_secs[1],tip_twist) # tip
# Figure out if there is an airfoil provided
# Airfoils should be in Lednicer format
# i.e. :
#
#EXAMPLE AIRFOIL
# 3. 3.
#
# 0.0 0.0
# 0.5 0.1
# 1.0 0.0
#
# 0.0 0.0
# 0.5 -0.1
# 1.0 0.0
# Note this will fail silently if airfoil is not in correct format
# check geometry output
airfoil_vsp_types = []
if n_segments > 0:
for i in range(n_segments):
if wing.segments[segment_keys[i]].airfoil != None:
if type(wing.segments[segment_keys[i]].airfoil) == RCAIDE.Library.Components.Airfoils.Biconvex_Airfoil:
airfoil_vsp_types.append(vsp.XS_BICONVEX)
else:
airfoil_vsp_types.append(vsp.XS_FILE_AIRFOIL)
else:
airfoil_vsp_types.append(vsp.XS_FILE_AIRFOIL)
elif wing.airfoil != None:
if type(wing.airfoil) == RCAIDE.Library.Components.Airfoils.Biconvex_Airfoil:
airfoil_vsp_types.append(vsp.XS_BICONVEX)
else:
airfoil_vsp_types.append(vsp.XS_FILE_AIRFOIL)
else:
airfoil_vsp_types = [vsp.XS_FILE_AIRFOIL]
if n_segments==0:
if wing.airfoil != None:
xsecsurf = vsp.GetXSecSurf(wing_id,0)
vsp.ChangeXSecShape(xsecsurf,0,airfoil_vsp_types[0])
vsp.ChangeXSecShape(xsecsurf,1,airfoil_vsp_types[0])
xsec1 = vsp.GetXSec(xsecsurf,0)
xsec2 = vsp.GetXSec(xsecsurf,1)
vsp.ReadFileAirfoil(xsec1,wing.airfoil.coordinate_file)
vsp.ReadFileAirfoil(xsec2,wing.airfoil.coordinate_file)
vsp.Update()
else:
if wing.segments[segment_keys[0]].airfoil != None:
xsecsurf = vsp.GetXSecSurf(wing_id,0)
vsp.ChangeXSecShape(xsecsurf,0,airfoil_vsp_types[0])
vsp.ChangeXSecShape(xsecsurf,1,airfoil_vsp_types[0])
xsec1 = vsp.GetXSec(xsecsurf,0)
xsec2 = vsp.GetXSec(xsecsurf,1)
vsp.ReadFileAirfoil(xsec1,wing.segments[segment_keys[0]].airfoil.coordinate_file)
vsp.ReadFileAirfoil(xsec2,wing.segments[segment_keys[0]].airfoil.coordinate_file)
vsp.Update()
# Thickness to chords
vsp.SetParmVal( wing_id,'ThickChord','XSecCurve_0',root_tc)
vsp.SetParmVal( wing_id,'ThickChord','XSecCurve_1',tip_tc)
# Dihedral
vsp.SetParmVal( wing_id,'Dihedral',x_secs[1],dihedral)
# Span and tip of the section
if n_segments>1:
local_span = span*wing.segments[segment_keys[-1]].percent_span_location
sec_tip_chord = root_chord*wing.segments[segment_keys[-1]].root_chord_percent
vsp.SetParmVal( wing_id,'Span',x_secs[1],local_span)
vsp.SetParmVal( wing_id,'Tip_Chord',x_secs[1],sec_tip_chord)
else:
vsp.SetParmVal( wing_id,'Span',x_secs[1],span/np.cos(dihedral*Units.degrees))
vsp.Update()
if n_segments>0:
if wing.segments[segment_keys[0]].percent_span_location==0.:
x_secs[-1] = [] # remove extra section tag (for clarity)
adjust = 0 # used for indexing
else:
adjust = 1
else:
adjust = 1
# Loop for the number of segments left over
for i_segs in range(1,n_segments+1):
if (wing.segments[segment_keys[i_segs-1]] == wing.segments[segment_keys[-1]]) and (wing.segments[segment_keys[-1]].percent_span_location == 1.):
break
# Unpack
dihedral_i = wing.segments[segment_keys[i_segs-1]].dihedral_outboard / Units.deg
chord_i = root_chord*wing.segments[segment_keys[i_segs-1]].root_chord_percent
try:
twist_i = wing.segments[segment_keys[i_segs]].twist / Units.deg
no_twist_flag = False
except:
no_twist_flag = True
sweep_i = wing.segments[segment_keys[i_segs-1]].sweeps.quarter_chord / Units.deg
tc_i = wing.segments[segment_keys[i_segs-1]].thickness_to_chord
# Calculate the local span
if i_segs == n_segments:
span_i = span*(1 - wing.segments[segment_keys[i_segs-1]].percent_span_location)/np.cos(dihedral_i*Units.deg)
else:
span_i = span*(wing.segments[segment_keys[i_segs]].percent_span_location-wing.segments[segment_keys[i_segs-1]].percent_span_location)/np.cos(dihedral_i*Units.deg)
# Insert the new wing section with specified airfoil if available
if wing.segments[segment_keys[i_segs-1]].airfoil != None:
vsp.InsertXSec(wing_id,i_segs-1+adjust,airfoil_vsp_types[i_segs-1])
xsecsurf = vsp.GetXSecSurf(wing_id,0)
xsec = vsp.GetXSec(xsecsurf,i_segs+adjust)
vsp.ReadFileAirfoil(xsec, wing.segments[segment_keys[i_segs-1]].airfoil.coordinate_file)
else:
vsp.InsertXSec(wing_id,i_segs-1+adjust,vsp.XS_FOUR_SERIES)
# Set the parms
# Find the id
x_sec_id = vsp.GetXSec(vsp.GetXSecSurf(wing_id, 0),i_segs+adjust)
# Find the parm strings
span_parm = vsp.GetXSecParm(x_sec_id, 'Span')
dih_parm = vsp.GetXSecParm(x_sec_id, 'Dihedral')
sweep_parm = vsp.GetXSecParm(x_sec_id, 'Sweep')
swp_loc_parm = vsp.GetXSecParm(x_sec_id, 'Sweep_Location')
rt_ch_parm = vsp.GetXSecParm(x_sec_id, 'Root_Chord')
tc_parm = vsp.GetXSecParm(x_sec_id, 'ThickChord')
# Set the parm values
vsp.SetParmVal(span_parm, span_i)
vsp.SetParmVal(sweep_parm, sweep_i)
vsp.SetParmVal(swp_loc_parm, sweep_loc)
vsp.SetParmVal(rt_ch_parm, chord_i)
vsp.SetParmVal(tc_parm, tc_i)
# OpenVSP's rotation on vertical segmented wings is opposite of RCAIDE's
if not wing.vertical:
vsp.SetParmVal(dih_parm, dihedral_i)
else:
vsp.SetParmVal(dih_parm, -dihedral_i)
if not no_twist_flag:
twist_parm = vsp.GetXSecParm(x_sec_id, 'Twist')
vsp.SetParmVal(twist_parm,twist_i)
if adjust and (i_segs == 1):
vsp.Update()
x_sec_id = vsp.GetXSec(vsp.GetXSecSurf(wing_id, 0),1)
twist_parm = vsp.GetXSecParm(x_sec_id, 'Twist')
vsp.SetParmVal(twist_parm,wing.segments[segment_keys[i_segs-1]].twist / Units.deg)
vsp.Update()
if (n_segments != 0) and (wing.segments[segment_keys[-1]].percent_span_location == 1.):
tip_chord = root_chord*wing.segments[segment_keys[-1]].root_chord_percent
vsp.SetParmVal( wing_id,'Tip_Chord',x_secs[n_segments-1+adjust],tip_chord)
vsp.SetParmVal( wing_id,'ThickChord',x_sec_curves[n_segments-1+adjust],wing.segments[segment_keys[-1]].thickness_to_chord)
# twist is set in the normal loop
else:
vsp.SetParmVal( wing_id,'Tip_Chord',x_secs[-1-(1-adjust)],tip_chord)
vsp.SetParmVal( wing_id,'Twist',x_secs[-1-(1-adjust)],tip_twist)
# a single trapezoidal wing is assumed to have constant thickness to chord
vsp.Update()
vsp.SetParmVal(wing_id,'CapUMaxOption','EndCap',2.)
vsp.SetParmVal(wing_id,'CapUMaxStrength','EndCap',1.)
vsp.Update()
if 'control_surfaces' in wing:
for ctrl_surf in wing.control_surfaces:
write_vsp_control_surface(wing,wing_id,ctrl_surf)
if 'fuel_tanks' in wing:
for tank in wing.fuel_tanks:
write_wing_conformal_fuel_tank(vehicle,wing, wing_id, tank, fuel_tank_set_ind)
vsp.SetSetFlag(wing_id, OML_set_ind, True)
return area_tags, wing_id
# ----------------------------------------------------------------------------------------------------------------------
# write_vsp_control_surface
# ----------------------------------------------------------------------------------------------------------------------
[docs]
def write_vsp_control_surface(wing,wing_id,ctrl_surf):
"""This writes a control surface in a wing.
Assumptions:
None
Source:
N/A
Inputs:
wind_id <str>
ctrl_surf [-]
n_segments int, number of wing segments
Outputs:
Operates on the active OpenVSP model, no direct output
Properties Used:
N/A
"""
# determine the number of segments and where the breaks are
if len(wing.segments.keys())>0:
N = len(wing.segments.keys())-1
# Do a for loop to find the location of breaks
breaks = []
for seg in wing.segments:
breaks.append(seg.percent_span_location)
else:
N = 1
# Location of breaks
breaks = [0.,1.]
breaks = np.array(breaks)
span_start = ctrl_surf.span_fraction_start
span_end = ctrl_surf.span_fraction_end
cs_id = vsp.AddSubSurf( wing_id, vsp.SS_CONTROL)
param_names = vsp.GetSubSurfParmIDs(cs_id)
for p_idx in range(len(param_names)):
if 'LE_Flag' == vsp.GetParmName(param_names[p_idx]):
if type(ctrl_surf) == RCAIDE.Library.Components.Wings.Control_Surfaces.Slat:
vsp.SetParmVal(param_names[p_idx], 1.0)
else:
vsp.SetParmVal( param_names[p_idx], 0.0)
# U values are only linear about the section in which they are defined
# Identify the segments they start and end with, if there are no segments there is one segment
segment_start = np.where(breaks<span_start)[0][-1]
segment_end = np.where(breaks>=span_end)[0][0] -1
# Find where in the segment it starts and ends (normalized)
start_span = breaks[segment_start+1] - breaks[segment_start]
end_span = breaks[segment_end+1] - breaks[segment_end]
start_offset = breaks[segment_start]
end_offset = breaks[segment_end]
segment_normalized_start = (span_start - start_offset)/start_span
segment_normalized_end = (span_end - end_offset )/end_span
# Find the scaling for those segments
U_scale = 1/(N + 2)
# Used the normalized positions and the U_scaling to find the U location
U_start = U_scale*segment_normalized_start + (segment_start + 1) * U_scale
U_end = U_scale*segment_normalized_end + (segment_end + 1) * U_scale
# Voila!
if 'UStart' == vsp.GetParmName(param_names[p_idx]):
vsp.SetParmVal(param_names[p_idx], U_start)
if 'UEnd' ==vsp.GetParmName(param_names[p_idx]):
vsp.SetParmVal(param_names[p_idx], U_end)
if 'Length_C_Start' == vsp.GetParmName(param_names[p_idx]):
vsp.SetParmVal(param_names[p_idx], ctrl_surf.chord_fraction)
if 'Length_C_End' == vsp.GetParmName(param_names[p_idx]):
vsp.SetParmVal(param_names[p_idx], ctrl_surf.chord_fraction)
if 'SE_Const_Flag' == vsp.GetParmName(param_names[p_idx]):
vsp.SetParmVal(param_names[p_idx], 1.0)
return
# ----------------------------------------------------------------------------------------------------------------------
# write_wing_conformal_fuel_tank
# ----------------------------------------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------------------------------
# get_vsp_trim_from_RCAIDE_trim
# ----------------------------------------------------------------------------------------------------------------------
[docs]
def get_vsp_trim_from_RCAIDE_trim(seg_span_percents,vsp_segment_breaks,trim):
"""Compute OpenVSP span trim coordinates based on RCAIDE coordinates
Assumptions:
Wing does not have end caps
Source:
N/A
Inputs:
seg_span_percents [-] range of 0 to 1
vsp_segment_breaks [-] range of 0 to 1
trim [-] range of 0 to 1 (RCAIDE value)
Outputs:
trim [-] OpenVSP trim value
Properties Used:
N/A
"""
# Determine max chord trim correction
y_seg_ind = next(i for i,per_y in enumerate(seg_span_percents) if per_y > trim)
segment_percent_of_total_span = seg_span_percents[y_seg_ind] - seg_span_percents[y_seg_ind-1]
remaining_percent_within_segment = trim - seg_span_percents[y_seg_ind-1]
percent_of_segment = remaining_percent_within_segment/segment_percent_of_total_span
trim = vsp_segment_breaks[y_seg_ind-1] + (vsp_segment_breaks[y_seg_ind]-vsp_segment_breaks[y_seg_ind-1])*percent_of_segment
return trim
# ----------------------------------------------------------------------------------------------------------------------
# convert_sweep
# ----------------------------------------------------------------------------------------------------------------------
[docs]
def convert_sweep(sweep,sweep_loc,new_sweep_loc,AR,taper):
"""This converts arbitrary sweep into a desired sweep given
wing geometry.
Assumptions:
None
Source:
N/A
Inputs:
sweep [degrees]
sweep_loc [unitless]
new_sweep_loc [unitless]
AR [unitless]
taper [unitless]
Outputs:
quarter chord sweep
Properties Used:
N/A
"""
sweep_LE = np.arctan(np.tan(sweep)+4*sweep_loc* (1-taper)/(AR*(1+taper)))
new_sweep = np.arctan(np.tan(sweep_LE)-4*new_sweep_loc* (1-taper)/(AR*(1+taper)))
return new_sweep