Source code for RCAIDE.Framework.External_Interfaces.OpenVSP.vsp_wing

# 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 # ----------------------------------------------------------------------------------------------------------------------
[docs] def write_wing_conformal_fuel_tank(vehicle,wing, wing_id,fuel_tank,fuel_tank_set_ind): """This writes a conformal fuel tank in a wing. Assumptions: None Source: N/A Inputs: vehicle [-] vehicle data structure wing.segments.*.percent_span_location [-] wing.spans.projected [m] wind_id <str> fuel_tank. inward_offset [m] start_chord_percent [-] .1 is 10% end_chord_percent [-] start_span_percent [-] end_span_percent [-] fuel_type.density [kg/m^3] fuel_tank_set_ind <int> Outputs: Operates on the active OpenVSP model, no direct output Properties Used: N/A """ # Unpack try: offset = fuel_tank.inward_offset chord_trim_max = 1.-fuel_tank.start_chord_percent chord_trim_min = 1.-fuel_tank.end_chord_percent span_trim_max = fuel_tank.end_span_percent span_trim_min = fuel_tank.start_span_percent density = fuel_tank.fuel_type.density except: print('Fuel tank does not contain parameters needed for OpenVSP geometry. Tag: '+fuel_tank.tag) return tank_id = vsp.AddGeom('CONFORMAL',wing_id) vsp.SetGeomName(tank_id, fuel_tank.tag) n_segments = len(wing.segments.keys()) if n_segments > 0.: seg_span_percents = np.array([v['percent_span_location'] for (k,v)\ in wing.segments.iteritems()]) vsp_segment_breaks = np.linspace(0.,1.,n_segments) else: seg_span_percents = np.array([0.,1.]) span = wing.spans.projected # Offset vsp.SetParmVal(tank_id,'Offset','Design',offset) for key, fuselage in vehicle.fuselages.items(): width = fuselage.width length = fuselage.lengths.total hmax = fuselage.heights.maximum height1 = fuselage.heights.at_quarter_length height2 = fuselage.heights.at_wing_root_quarter_chord height3 = fuselage.heights.at_three_quarters_length effdia = fuselage.effective_diameter n_fine = fuselage.fineness.nose t_fine = fuselage.fineness.tail w_ac = wing.aerodynamic_center w_origin = vehicle.wings.main_wing.origin w_c_4 = vehicle.wings.main_wing.chords.root/4. # Figure out the location x location of each section, 3 sections, end of nose, wing origin, and start of tail x1 = 0.25 x2 = (w_origin[0]+w_c_4)/length x3 = 0.75 fuse_id = vsp.AddGeom("FUSELAGE") vsp.SetGeomName(fuse_id, fuselage.tag) wing_id[fuselage.tag] = ['fuselages',fuselage.tag] # Set the origins: x = fuselage.origin[0][0] y = fuselage.origin[0][1] z = fuselage.origin[0][2] vsp.SetParmVal(fuse_id,'X_Location','XForm',x) vsp.SetParmVal(fuse_id,'Y_Location','XForm',y) vsp.SetParmVal(fuse_id,'Z_Location','XForm',z) vsp.SetParmVal(fuse_id,'Abs_Or_Relitive_flag','XForm',vsp.ABS) # misspelling from OpenVSP vsp.SetParmVal(fuse_id,'Origin','XForm',0.0) # Fuel tank chord bounds vsp.SetParmVal(tank_id,'ChordTrimFlag','Design',1.) vsp.SetParmVal(tank_id,'ChordTrimMax','Design',chord_trim_max) vsp.SetParmVal(tank_id,'ChordTrimMin','Design',chord_trim_min) # Fuel tank span bounds if n_segments>0: span_trim_max = get_vsp_trim_from_RCAIDE_trim(seg_span_percents, vsp_segment_breaks, span_trim_max) span_trim_min = get_vsp_trim_from_RCAIDE_trim(seg_span_percents, vsp_segment_breaks, span_trim_min) else: pass # no change to span_trim vsp.SetParmVal(tank_id,'UTrimFlag','Design',1.) vsp.SetParmVal(tank_id,'UTrimMax','Design',span_trim_max) vsp.SetParmVal(tank_id,'UTrimMin','Design',span_trim_min) # Set density vsp.SetParmVal(tank_id,'Density','Mass_Props',density) # Add to the full fuel tank set vsp.SetSetFlag(tank_id, fuel_tank_set_ind, True) return
# ---------------------------------------------------------------------------------------------------------------------- # 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