Experimenting with OpenVSP Python API

Level: Intermediate

This notebook explores the basic geometric modeling capabilities of OpenVSP.

Topics covered

  • Finite wing geometry parameters

  • Wing shape variations

Python project setup

[1]:
import sys, os

print("Setting up environment...")
print(f"Current working directory: {os.getcwd()}")

# Resolve project root from this notebook location and add src/ to import path
this_notebook_dir = os.getcwd()
project_root = os.path.abspath(os.path.join(this_notebook_dir, "..", ".."))
src_root = os.path.join(project_root, "src")

print(f"Resolved project root: {project_root}")
if not os.path.isdir(src_root):
    raise FileNotFoundError(f"Could not find src directory at: {src_root}")

# Add src/ to Python path for imports
if src_root not in sys.path:
    sys.path.insert(0, src_root)

print(f"Using src path: {src_root}")

#=== Verify vsp-related directories exist and add to path ===
print("Verifying vsppytools and related directories...")

# Verify vsppytools directory exists
vsptools_path = os.path.join(src_root, "vsppytools")
if not os.path.isdir(vsptools_path):
    raise FileNotFoundError(f"Could not find vsppytools directory at: {vsptools_path}")

# Add vsppytools to path for imports
if vsptools_path not in sys.path:
    sys.path.insert(0, vsptools_path)

# Verify vsppytools/openvsp_config exists
vsp_config_path = os.path.join(vsptools_path, "openvsp_config")
if not os.path.isdir(vsp_config_path):
    raise FileNotFoundError(f"Could not find openvsp_config directory at: {vsp_config_path}")

# Add vsppytools/openvsp_config to path for imports
if vsp_config_path not in sys.path:
    sys.path.insert(0, vsp_config_path)

# Verify vsppytools/openvsp exists
vsp_path = os.path.join(vsptools_path, "openvsp")
if not os.path.isdir(vsp_path):
    raise FileNotFoundError(f"Could not find openvsp directory at: {vsp_path}")

# Add vsppytools/openvsp to path for imports
if vsp_path not in sys.path:
    sys.path.insert(0, vsp_path)

print(f"Using vsppytools paths: {vsptools_path}, {vsp_config_path}, {vsp_path}")

# test imports
try:
    # To go with the new structure, we need to import the config and vsp modules from their respective subdirectories
    import vsppytools.openvsp_config.openvsp_config as openvsp_config
    import vsppytools.openvsp.openvsp as vsp
    print("Successfully imported vsppytools modules.")
except ImportError as e:
    print(f"Error importing vsppytools modules: {e}")
Setting up environment...
Current working directory: f:\agodemar\AeroDemonstrator\notebooks\02_finite_wing
Resolved project root: f:\agodemar\AeroDemonstrator
Using src path: f:\agodemar\AeroDemonstrator\src
Verifying vsppytools and related directories...
Using vsppytools paths: f:\agodemar\AeroDemonstrator\src\vsppytools, f:\agodemar\AeroDemonstrator\src\vsppytools\openvsp_config, f:\agodemar\AeroDemonstrator\src\vsppytools\openvsp
Successfully imported vsppytools modules.

First attempt to use vsppytools API

Load the openvsp module

[2]:
# Set OpenVSP config options for headless operation
openvsp_config.LOAD_GRAPHICS = False
openvsp_config.LOAD_FACADE = False # Single Instance of OpenVSP

Create a wing

[3]:
vsp.ClearVSPModel() # Clear any existing model in OpenVSP

wing_id = vsp.AddGeom( 'WING', '' )

# Set Wing Section
vsp.SetDriverGroup( wing_id,
    1, # Wing "section" (panel) driver
    vsp.AR_WSECT_DRIVER,    # Aspect-Ratio driver
    vsp.ROOTC_WSECT_DRIVER, # Root Chord driver
    vsp.TIPC_WSECT_DRIVER   # Tip Chord driver
    )

# Set NACA 0012 Airfoil and Common Parms

# Thickness to chord ratio is set via the "ThickChord" parameter on the section curve driver.
vsp.SetParmVal( wing_id,
    'ThickChord',  # Parameter name: thickness to chord ratio
    'XSecCurve_0', # Section 0 (root) driver
    0.12           # Value: 12% thickness
    )
# Same as above, but for section 1 (tip)
vsp.SetParmVal( wing_id, 'ThickChord', 'XSecCurve_1', 0.12 )

# Set root and tip chord lengths
vsp.SetParmVal( wing_id,
    'Root_Chord', # Parameter name: root chord length
    'XSec_1',     # Section 1 (root) driver
    2.0           # Value: 2.0 unit root chord length
    )
vsp.SetParmVal( wing_id,
    'Tip_Chord', # Parameter name: tip chord length
    'XSec_1',     # Section 1 (tip) driver
    1.0           # Value: 1.0 unit tip chord length
    )

# Wing tip tessellation parameters - set to coarse for faster meshing
# See in GUI: WingGeom -> Plan -> Tessssellation Controls -> TECluster, LECluster for more details
vsp.SetParmVal( wing_id, 'TECluster', 'WingGeom', 1.0 )
vsp.SetParmVal( wing_id, 'LECluster', 'WingGeom', 0.2 )

# Set up parameterization for sweep, aspect ratio, and tip clustering
vsp.SetParmVal( wing_id,
    'Aspect', 'XSec_1', # Aspect ratio driver parameter, XSec_1 is the section (panel) driver for the wing
    8                   # AR value b^2/S, where b is span and S is planform area
    )
vsp.SetParmVal( wing_id,
    'Sweep_Location', 'XSec_1', # Sweep line reference location,
    0.25                        # Percentage position along airfoil from leading edge (0.25 for quarter-chord sweep)
    )
vsp.SetParmVal( wing_id,
    'Sweep', 'XSec_1', # Sweep angle driver parameter
    0                  # Sweep angle in degrees (0 for unswept wing)
    )
vsp.SetParmVal( wing_id,
    'OutCluster', 'XSec_1', # Tip clustering driver parameter
    0.6                     # Clustering value (1.0 for uniform spacing, <1.0 for more points near tip)
    )

# === Tip cap shaping parameters ===
# TODO

# === Tessellation parameters ===
vsp.SetParmVal( wing_id,
    'SectTess_U', 'XSec_1',
    64          # U direction tessellation for wing sections (root to tip)
    )
vsp.SetParmVal( wing_id,
    'Tess_W', 'Shape',
    16          # Tessellation in the width direction
    )

# Update the model
vsp.Update()

Save the model

[4]:
# Save outputs to local subdirectory
output_dir = os.path.join(this_notebook_dir, "00_finite_fing_vsp")
os.makedirs(output_dir, exist_ok=True)

# Save the model
vsp_file_path = os.path.join(output_dir, "00_finite_wing.vsp3")
vsp.WriteVSPFile(vsp_file_path)
print(f"Saved model to: {vsp_file_path}")
Saved model to: f:\agodemar\AeroDemonstrator\notebooks\02_finite_wing\00_finite_fing_vsp\00_finite_wing.vsp3

VSPAero

[5]:
import io
from contextlib import redirect_stdout

# Ensure all VSPAero artifacts are written to local subdirectory
output_dir = os.path.join(this_notebook_dir, "00_finite_fing_vsp")
os.makedirs(output_dir, exist_ok=True)

# Run VSPAero from output directory so generated files land there
previous_cwd = os.getcwd()
os.chdir(output_dir)
try:
    # Set defaults
    vsp.SetAnalysisInputDefaults( "VSPAEROComputeGeometry" )
    vsp.SetIntAnalysisInput("VSPAEROComputeGeometry", 'GeomSet', [vsp.SET_NONE], 0)
    vsp.SetIntAnalysisInput("VSPAEROComputeGeometry", 'ThinGeomSet', [vsp.SET_ALL], 0)  # Thin geometry - VLM

    # Capture logs from stdout
    buf = io.StringIO()
    with redirect_stdout(buf):
        vsp.PrintAnalysisInputs( "VSPAEROComputeGeometry" )
        # Execute
        print( '\tExecuting compute geometry...' )
        compgeom_resid = vsp.ExecAnalysis( "VSPAEROComputeGeometry" )
        print( '\tCOMPLETE' )

        # Get & Display Results
        vsp.PrintResults( compgeom_resid )

        #==== Analysis: VSPAero Single Point ====#
        # Set defaults
        vsp.SetAnalysisInputDefaults("VSPAEROSweep")
        print("\tVSPAEROSweep")

        # Reference geometry set
        vsp.SetIntAnalysisInput("VSPAEROSweep", 'GeomSet', [vsp.SET_NONE], 0)
        vsp.SetIntAnalysisInput("VSPAEROSweep", 'ThinGeomSet', [vsp.SET_ALL], 0)  # Thin geometry - VLM
        vsp.SetIntAnalysisInput("VSPAEROSweep",
                                'RefFlag', [1], # Wing Reference
                                0)
        vsp.SetIntAnalysisInput("VSPAEROSweep",
                                'Symmetry', [1],
                                0)

        wid = vsp.FindGeomsWithName( 'WingGeom' )
        vsp.SetStringAnalysisInput("VSPAEROSweep", 'WingID', wid, 0)

        # Freestream Parameters
        vsp.SetDoubleAnalysisInput("VSPAEROSweep",
                                   'AlphaStart', [1.0],
                                   0)
        AlphaNpts = [1]
        vsp.SetIntAnalysisInput("VSPAEROSweep", 'AlphaNpts', AlphaNpts, 0)
        vsp.SetDoubleAnalysisInput("VSPAEROSweep",
                                   'Machstart', [0.1], # subsonic, uncompressible
                                   0)
        MachNpts = [1]
        vsp.SetIntAnalysisInput("VSPAEROSweep", 'MachNpts', MachNpts, 0)
        vsp.SetIntAnalysisInput("VSPAEROSweep",
                                'WakeNumIter', [8], # wake iterations
                                0)

        vsp.Update()

        # list inputs, type, and current values
        vsp.PrintAnalysisInputs( "VSPAEROSweep" )
        print( '\t___' )

        # Execute
        print( '\tExecuting aero-sweep-analysis...' )
        rid = vsp.ExecAnalysis( "VSPAEROSweep" )
        print( '\tCOMPLETE' )

        # Get & Display Results
        vsp.PrintResults( rid )
        csv_file_path = os.path.join(output_dir, "00_finite_wing_analysis_results.csv")
        vsp.WriteResultsCSVFile( rid, csv_file_path)

    captured_logs = buf.getvalue()
    print("================= Results:")
    print(captured_logs)
    print(f"Saved CSV to: {csv_file_path}")
    print(f"VSPAero-generated files are in: {output_dir}")
finally:
    os.chdir(previous_cwd)
================= Results:
        Executing compute geometry...
        COMPLETE
        VSPAEROSweep
        ___
        Executing aero-sweep-analysis...
        COMPLETE

Saved CSV to: f:\agodemar\AeroDemonstrator\notebooks\02_finite_wing\00_finite_fing_vsp\00_finite_wing_analysis_results.csv
VSPAero-generated files are in: f:\agodemar\AeroDemonstrator\notebooks\02_finite_wing\00_finite_fing_vsp