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