diff --git a/aerosandbox/tools/openvsp/vsp_read.py b/aerosandbox/tools/openvsp/vsp_read.py new file mode 100644 index 000000000..c71d3a6ee --- /dev/null +++ b/aerosandbox/tools/openvsp/vsp_read.py @@ -0,0 +1,91 @@ +# Created: Jun 2018, T. St Francis +# Modified: Aug 2021 Michael Shamberger +# Aug 2018, T. St Francis +# Jan 2020, T. MacDonald +# Jul 2020, E. Botero +# Original code taken from Suave project and modified for AeroSandbox + +import aerosandbox +from aerosandbox.geometry import Airplane +from aerosandbox.tools.openvsp.vsp_read_fuselage import vsp_read_fuselage +from aerosandbox.tools.openvsp.vsp_read_wing import vsp_read_wing +import aerosandbox.numpy as np + +import openvsp as vsp + + +# ---------------------------------------------------------------------- +# vsp read +# ---------------------------------------------------------------------- +def vsp_read(tag): + """This reads an OpenVSP vehicle geometry and writes it into a Aerosandbox vehicle format. + Includes wings, fuselages, and propellers. + + Assumptions: + 1. OpenVSP vehicle is composed of conventionally shaped fuselages, wings, and propellers. + 1a. OpenVSP fuselage: generally narrow at nose and tail, wider in center). + 1b. Fuselage is designed in VSP as it appears in real life. That is, the VSP model does not rely on + superficial elements such as canopies, stacks, or additional fuselages to cover up internal lofting oddities. + 1c. This program will NOT account for multiple geometries comprising the fuselage. For example: a wingbox mounted beneath + is a separate geometry and will NOT be processed. + 2. Fuselage origin is located at nose. VSP file origin can be located anywhere, preferably at the forward tip + of the vehicle or in front (to make all X-coordinates of vehicle positive). + 3. Written for OpenVSP 3.24 + + Source: + N/A + + Inputs: + 1. A tag for an XML file in format .vsp3. + + Outputs: + Writes Aerosandbox vehicle + + Properties Used: + N/A + """ + + vsp.ClearVSPModel() + vsp.ReadVSPFile(tag) + + vsp_fuselages = [] + vsp_wings = [] + vsp_props = [] + vsp_geoms = vsp.FindGeoms() + geom_names = [] + + # The two for-loops below are in anticipation of an OpenVSP API update with a call for GETGEOMTYPE. + # This print function allows user to enter VSP GeomID manually as first argument in vsp_read functions. + print("VSP geometry IDs: ") + + # Label each geom type by storing its VSP geom ID. + for geom in vsp_geoms: + geom_name = vsp.GetGeomName(geom) + geom_names.append(geom_name) + print(str(geom_name) + ': ' + geom) + + # -------------------------------- + # AUTOMATIC VSP ENTRY & PROCESSING + # -------------------------------- + for geom in vsp_geoms: + geom_name = vsp.GetGeomTypeName(str(geom)) + + if geom_name == 'Fuselage': + vsp_fuselages.append(geom) + if geom_name == 'Wing': + vsp_wings.append(geom) + # No aerosandbox propeller geometry class available + #if geom_name == 'Propeller': + # vsp_props.append(geom) + + #Read VSP geoms and store in Aerosandbox components + xyz_ref = np.array([0, 0, 0]) + fuselages = [] + for fuselage_id in vsp_fuselages: + fuselages.append(vsp_read_fuselage(fuselage_id)) + + wings = [] + for wing_id in vsp_wings: + wings.append(vsp_read_wing(wing_id)) + + return Airplane(tag, xyz_ref, wings, fuselages) diff --git a/aerosandbox/tools/openvsp/vsp_read_fuselage.py b/aerosandbox/tools/openvsp/vsp_read_fuselage.py new file mode 100644 index 000000000..34bae1d91 --- /dev/null +++ b/aerosandbox/tools/openvsp/vsp_read_fuselage.py @@ -0,0 +1,117 @@ +import aerosandbox +from aerosandbox.geometry import Fuselage +from aerosandbox.geometry import FuselageXSec +import openvsp as vsp +import aerosandbox.numpy as np +from ctypes import * + +# ---------------------------------------------------------------------- +# vsp read fuselage +# ---------------------------------------------------------------------- + +def vsp_read_fuselage(fuselage_id): + """This reads an OpenVSP fuselage geometry and writes it to a aerosandbox fuselage format. + + Assumptions: + 1. OpenVSP fuselage is "conventionally shaped" (generally narrow at nose and tail, wider in center). + 2. Fuselage is designed in VSP as it appears in real life. That is, the VSP model does not rely on + superficial elements such as canopies, stacks, or additional fuselages to cover up internal lofting oddities. + 3. This program will NOT account for multiple geometries comprising the fuselage. For example: a wingbox mounted beneath + is a separate geometry and will NOT be processed. + 4. Fuselage origin is located at nose. VSP file origin can be located anywhere, preferably at the forward tip + of the vehicle or in front (to make all X-coordinates of vehicle positive). + 5. Written for OpenVSP 3.24 + + Inputs: + 0. Pre-loaded VSP vehicle in memory, via vsp_read. + 1. VSP 10-digit geom ID for fuselage. + + Outputs: + aerosandbox fuselage + """ + + print("Converting fuselage: " + fuselage_id) + # get total length of fuselage + total_length = vsp.GetParmVal(fuselage_id, 'Length', 'Design') + + # read the xsec data + xsec_root_id = vsp.GetXSecSurf(fuselage_id, 0) + xsec_num = vsp.GetNumXSec(xsec_root_id) + xsecs = [] + for increment in range(0, xsec_num): + xsecs.append(getVspXSec(xsec_root_id, xsec_num, total_length, increment)) + # get the name + if vsp.GetGeomName(fuselage_id): + tag = vsp.GetGeomName(fuselage_id) + else: + tag = 'FuselageGeom' + # get leading edge + xyz_le = np.array([0, 0, 0]) + xyz_le[0] = vsp.GetParmVal(fuselage_id, 'X_Location', 'XForm') + xyz_le[1] = vsp.GetParmVal(fuselage_id, 'Y_Location', 'XForm') + xyz_le[2] = vsp.GetParmVal(fuselage_id, 'Z_Location', 'XForm') + # create the fuselage + return Fuselage(tag, xyz_le, xsecs) + +# Get Fuselage segments +def getVspXSec(xsec_root_id, xsec_num, total_length, increment): + print(" Processing xsec: " + str(increment)) + # Create the segment + # + xsec = vsp.GetXSec(xsec_root_id, increment) # VSP XSec ID. + xyz_c = getXsecCenter(xsec) + print(" center: " + str(xyz_c)) + + X_Loc_P = vsp.GetXSecParm(xsec, 'XLocPercent') + Y_Loc_P = vsp.GetXSecParm(xsec, 'YLocPercent') + Z_Loc_P = vsp.GetXSecParm(xsec, 'ZLocPercent') + + height = vsp.GetXSecHeight(xsec) + width = vsp.GetXSecWidth(xsec) + percent_x_location = vsp.GetParmVal(X_Loc_P) # Along fuselage length. + percent_y_location = vsp.GetParmVal(Y_Loc_P) + percent_z_location = vsp.GetParmVal(Z_Loc_P ) # Vertical deviation of fuselage center. + effective_diameter = (height+width)/2. + radius = effective_diameter/2. + + #xsec shape not supported for aerosandbox FuselageXSec + #shape = vsp.GetXSecShape(segment.vsp_data.xsec_id) + #shape_dict = {0:'point',1:'circle',2:'ellipse',3:'super ellipse',4:'rounded rectangle',5:'general fuse',6:'fuse file'} + #vsp_data.shape = shape_dict[shape] + return FuselageXSec(xyz_c, radius) + +def getXsecCenter(xsec): + x = [] + y = [] + z = [] + for increment in np.linspace(0,1,100): + point = vsp.ComputeXSecPnt(xsec, increment) # get xsec point at leading edge of tip chord + p = (c_double * 3).from_address(int(np.ctypeslib.as_array(point.data()))) + x.append(p[0]) + y.append(p[1]) + z.append(p[2]) + return np.array([sum(x) / len(x), sum(y) / len(y), sum(z) / len(z)]) + + +def get_fuselage_height(fuselage, location): + """This linearly estimates fuselage height at any percentage point (0,100) along fuselage length. + + Inputs: + 0. Pre-loaded VSP vehicle in memory, via vsp_read. + 1. Suave fuselage [object], containing fuselage.vsp_data.xsec_num in its data structure. + 2. Fuselage percentage point [float]. + + Outputs: + height [m] + """ + for jj in range(1, fuselage.vsp_data.xsec_num): # Begin at second section, working toward tail. + if fuselage.Segments[jj].percent_x_location>=location and fuselage.Segments[jj-1].percent_x_location