Wing–Fuselage Configuration with OpenVSP

Level: Intermediate

This notebook demonstrates how to build a wing–fuselage aircraft configuration using the OpenVSP Python API and perform a panel aerodynamic analysis with VSPAero.

Topics covered

  • OpenVSP geometry creation via Python API

  • Wing and fuselage parametric modeling

  • VSPAero VLM (Vortex Lattice Method) analysis setup

  • Aerodynamic coefficient extraction: CL, CDi, Cm

  • Effect of fuselage on wing aerodynamics

Requirements

  • openvsp package installed (see https://openvsp.org)

  • If OpenVSP is not installed, this notebook will use a mock/fallback mode

References

[ ]:
import numpy as np
import matplotlib.pyplot as plt
import sys, os
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath('.')), 'src'))

from aerodemo.openvsp_utils import (
    check_openvsp, init_vsp, add_wing, add_fuselage, run_vspaero
)
from aerodemo.vlm import WingGeometry, VortexLatticeMethod

HAS_VSP = check_openvsp()
print(f"OpenVSP available: {HAS_VSP}")
if not HAS_VSP:
    print("NOTE: OpenVSP not found. Using VLM fallback for aerodynamic analysis.")
    print("Install OpenVSP with: pip install openvsp")

plt.rcParams.update({'figure.dpi': 100, 'axes.grid': True, 'grid.alpha': 0.3, 'font.size': 11})

1. Aircraft Configuration Parameters

We design a simple low-wing transport aircraft:

Component

Parameter

Value

Wing

Span

28 m

Wing

Root chord

4.5 m

Wing

Tip chord

1.8 m

Wing

Sweep (c/4)

20°

Wing

Dihedral

Fuselage

Length

30 m

Fuselage

Max diameter

3.2 m

[ ]:
# Aircraft configuration parameters
config = {
    # Wing
    'wing_span': 28.0,
    'wing_root_chord': 4.5,
    'wing_tip_chord': 1.8,
    'wing_sweep': 20.0,
    'wing_dihedral': 5.0,
    'wing_x_offset': 10.0,
    'wing_z_offset': -1.2,
    # Fuselage
    'fuse_length': 30.0,
    'fuse_diameter': 3.2,
}

# Derived parameters
S_ref = config['wing_span'] * (config['wing_root_chord'] + config['wing_tip_chord']) / 2
AR = config['wing_span']**2 / S_ref
lam = config['wing_tip_chord'] / config['wing_root_chord']
MAC = (2/3) * config['wing_root_chord'] * (1 + lam + lam**2) / (1 + lam)

print("Wing-Fuselage Configuration:")
print(f"  Wing span:            b = {config['wing_span']:.1f} m")
print(f"  Reference area:       S = {S_ref:.2f} m²")
print(f"  Aspect ratio:        AR = {AR:.2f}")
print(f"  Taper ratio:          λ = {lam:.3f}")
print(f"  MAC:                  c̄ = {MAC:.3f} m")
print(f"  Fuselage length:      L = {config['fuse_length']:.1f} m")
print(f"  Fuselage diameter:    D = {config['fuse_diameter']:.1f} m")
print(f"  Fineness ratio:     L/D = {config['fuse_length']/config['fuse_diameter']:.2f}")

2. OpenVSP Model Creation

We create the wing and fuselage geometry in OpenVSP. If OpenVSP is installed, the geometry is built programmatically. Otherwise, we proceed with the VLM analysis.

[ ]:
if HAS_VSP:
    # Initialize fresh model
    init_vsp("WingFuselage")

    # Add fuselage
    fuse_id = add_fuselage(
        length=config['fuse_length'],
        max_diameter=config['fuse_diameter'],
        name="Fuselage"
    )
    print(f"Fuselage geometry ID: {fuse_id}")

    # Add wing
    wing_id = add_wing(
        span=config['wing_span'],
        root_chord=config['wing_root_chord'],
        tip_chord=config['wing_tip_chord'],
        sweep_deg=config['wing_sweep'],
        dihedral_deg=config['wing_dihedral'],
        x_offset=config['wing_x_offset'],
        z_offset=config['wing_z_offset'],
        name="Wing"
    )
    print(f"Wing geometry ID: {wing_id}")
    print("VSP model created successfully.")
else:
    print("Skipping VSP model creation (OpenVSP not available).")
    print("Using VLM analysis as fallback.")

3. Aerodynamic Analysis

VSPAero (if OpenVSP available)

VSPAero performs a Vortex Lattice analysis on the full 3D geometry.

VLM fallback

Our custom VLM solver provides equivalent results for isolated wing analysis.

[ ]:
alpha_range = np.linspace(-4, 14, 10)

if HAS_VSP:
    print("Running VSPAero VLM sweep...")
    vsp_results = run_vspaero(
        alpha_start=-4.0,
        alpha_end=14.0,
        alpha_npts=10,
        mach=0.2,
        ref_area=S_ref,
        ref_span=config['wing_span'],
        ref_chord=MAC,
    )
    if vsp_results:
        alpha_data = np.array(vsp_results['Alpha'])
        CL_data = np.array(vsp_results['CL'])
        CDi_data = np.array(vsp_results['CDi'])
        Cm_data = np.array(vsp_results.get('Cm', [0]*len(alpha_data)))
        analysis_label = 'VSPAero VLM'
    else:
        vsp_results = None
        print("VSPAero returned no results. Falling back to VLM.")
        HAS_VSP = False

if not HAS_VSP or vsp_results is None:
    # VLM fallback
    wing_geom = WingGeometry(
        span=config['wing_span'],
        root_chord=config['wing_root_chord'],
        tip_chord=config['wing_tip_chord'],
        sweep_angle=config['wing_sweep'],
        dihedral=config['wing_dihedral'],
        n_spanwise=14,
        n_chordwise=5,
    )
    vlm_solver = VortexLatticeMethod(wing_geom)
    sweep = vlm_solver.sweep_alpha(alpha_range)
    alpha_data = sweep['alpha']
    CL_data = sweep['CL']
    CDi_data = sweep['CDi']
    Cm_data = np.zeros_like(CL_data)
    analysis_label = 'VLM (fallback)'

print(f"Analysis complete using: {analysis_label}")
print(f"\nResults at α=4°:")
idx = np.argmin(np.abs(alpha_data - 4.0))
print(f"  CL  = {CL_data[idx]:.4f}")
print(f"  CDi = {CDi_data[idx]:.4f}")
print(f"  L/D = {CL_data[idx]/max(CDi_data[idx], 1e-6):.1f}")
[ ]:
fig, axes = plt.subplots(1, 3, figsize=(15, 5))

ax = axes[0]
ax.plot(alpha_data, CL_data, 'bo-', linewidth=2, markersize=7, label=analysis_label)
ax.axhline(0, color='k', linewidth=0.5)
ax.axvline(0, color='k', linewidth=0.5)
ax.set_xlabel('α [deg]')
ax.set_ylabel('$C_L$')
ax.set_title('Lift Curve', fontweight='bold')
ax.legend()

ax = axes[1]
ax.plot(CDi_data, CL_data, 'rs-', linewidth=2, markersize=7, label=analysis_label)
ax.set_xlabel('$C_{Di}$')
ax.set_ylabel('$C_L$')
ax.set_title('Drag Polar', fontweight='bold')
ax.legend()

ax = axes[2]
LD = CL_data / np.maximum(CDi_data, 1e-8)
ax.plot(alpha_data, LD, 'gD-', linewidth=2, markersize=7)
ax.set_xlabel('α [deg]')
ax.set_ylabel('$C_L / C_{Di}$')
ax.set_title('Aerodynamic Efficiency', fontweight='bold')

plt.suptitle(f'Wing–Fuselage Configuration — {analysis_label}', fontsize=13, fontweight='bold')
plt.tight_layout()
plt.savefig('wing_fuselage_aero.png', bbox_inches='tight', dpi=100)
plt.show()

4. Summary

In this notebook we have:

  1. Defined a wing–fuselage configuration with realistic parameters

  2. Created the geometry in OpenVSP (when available) or used VLM fallback

  3. Performed an angle-of-attack sweep and extracted CL, CDi

  4. Visualized the lift curve, drag polar, and efficiency

Key takeaways

  • OpenVSP enables accurate 3D geometry modeling with fuselage-wing interference

  • The VLM provides a quick first-estimate for isolated wing aerodynamics

  • Fuselage presence reduces effective wing aspect ratio slightly

Next steps

Proceed to ../04_complete_aircraft/full_aircraft_openvsp.ipynb for the full aircraft configuration including horizontal and vertical tails with control surfaces.