Finite Wing with OOP (FiniteWing)

This notebook recreates the finite wing workflow using the FiniteWing class from src/aerodemo.

Python project setup

Configure Python paths so modules in src/ are discoverable in a clean, explicit way.

[1]:
import os
import sys
from pathlib import Path

print("Setting up environment...")
this_notebook_dir = Path.cwd().resolve()
project_root = (this_notebook_dir / ".." / "..").resolve()
src_root = project_root / "src"
aerodemo_root = src_root / "aerodemo"

print(f"Notebook directory: {this_notebook_dir}")
print(f"Project root: {project_root}")

if not src_root.is_dir():
    raise FileNotFoundError(f"Could not find src directory at: {src_root}")
if not aerodemo_root.is_dir():
    raise FileNotFoundError(f"Could not find aerodemo directory at: {aerodemo_root}")

# Add both roots: src for package imports, src/aerodemo for module-style imports
for p in (src_root, aerodemo_root):
    p_str = str(p)
    if p_str not in sys.path:
        sys.path.insert(0, p_str)

print(f"Using src path: {src_root}")
print(f"Using aerodemo module path: {aerodemo_root}")
Setting up environment...
Notebook directory: F:\agodemar\AeroDemonstrator\notebooks\02_finite_wing
Project root: F:\agodemar\AeroDemonstrator
Using src path: F:\agodemar\AeroDemonstrator\src
Using aerodemo module path: F:\agodemar\AeroDemonstrator\src\aerodemo
[2]:
# Import OOP wing builder and related types
import FiniteWing as finitewing_module
from FiniteWing import FiniteWing
from WingSegment import WingSegment
from AirfoilSpec import AirfoilSpec

# OpenVSP handle used by FiniteWing module
vsp = finitewing_module.vsp

print("Successfully imported FiniteWing, WingSegment, and AirfoilSpec.")
Successfully imported FiniteWing, WingSegment, and AirfoilSpec.

Build wing geometry with FiniteWing

[3]:
c_r  = 2.0  # m
c_t  = 1.2  # m
span = 14.0 # m
dihedral_deg = 0.0 # deg

# Define twist values relative to rigging angle, and compute absolute twist values
rigging_deg = 0.0 # deg
twist_rel_1_deg = -2.0 # deg
twist_1_deg = rigging_deg + twist_rel_1_deg
twist_rel_2_deg = -2.0 # deg
twist_2_deg = rigging_deg + twist_rel_1_deg + twist_rel_2_deg

segments = [
    WingSegment(
        id="WS_INNER",
        span=span/2,
        root_chord=c_r,
        tip_chord=c_r,
        sweep_deg=0.0,
        dihedral_deg=dihedral_deg,
        twist_deg=twist_1_deg,
        num_U=10, # Optional: set number of chordwise points for this segment
        cluster_root=1.0, # Optional: cluster points towards root (0.0 to 1.0)
        cluster_tip=1.0, # Optional: cluster points towards tip (0.0 to 1.0)
    ),
    WingSegment(
        id="WS_OUTER",
        span=span/2,
        root_chord=c_r,
        tip_chord=c_t,
        sweep_deg=0.0,
        dihedral_deg=dihedral_deg,
        twist_deg=twist_2_deg,
        num_U=16, # Optional: set number of chordwise points for this segment
        cluster_root=1.0, # Optional: cluster points towards root (0.0 to 1.0)
        cluster_tip=0.2, # Optional: cluster points towards tip (0.0 to 1.0)
    )
]

station_airfoils = [
    AirfoilSpec(kind="naca4", camber=0.02, camber_loc=0.4, thickness=0.12),
    AirfoilSpec(kind="naca4", camber=0.02, camber_loc=0.4, thickness=0.12),
    AirfoilSpec(kind="naca4", camber=0.00, camber_loc=0.4, thickness=0.10),
]

wing = FiniteWing(
    name="FW_OOP_01",
    mirrored=True,
    segments=segments,
    station_airfoils=station_airfoils,
    root_incidence_deg=rigging_deg,
    tip_cap_shape="Round",
    tip_cap_length=0.20,
)

print(wing)
FiniteWing:
  name: FW_OOP_01
  wing_id: None
  mirrored: True
  root_incidence_deg: 0.000 deg
  rotate_airfoils_with_dihedral: True
  root_cap_shape: Flat
  tip_cap_shape: Round
  root_cap_length: 0.000 m
  tip_cap_length: 0.200 m
  root_cap_strength: 1.000 (-)
  tip_cap_strength: 1.000 (-)
  location_xyz: (0.000, 0.000, 0.000) m
  rotation_xyz: (0.000, 0.000, 0.000) deg
  clear_model_first: True
  num_segments: 2
  segments:
    WingSegment:
      id: WS_INNER
      span: 7.000 m
      root_chord: 2.000 m
      tip_chord: 2.000 m
      sweep_deg: 0.000 deg
      dihedral_deg: 0.000 deg
      twist_deg: -2.000 deg
      sweep_ref_loc: 0.250 (-)
      twist_ref_loc: 0.250 (-)
      num_U: 10 (-)
      cluster_root: 1.000 (-)
      cluster_tip: 1.000 (-)
    WingSegment:
      id: WS_OUTER
      span: 7.000 m
      root_chord: 2.000 m
      tip_chord: 1.200 m
      sweep_deg: 0.000 deg
      dihedral_deg: 0.000 deg
      twist_deg: -4.000 deg
      sweep_ref_loc: 0.250 (-)
      twist_ref_loc: 0.250 (-)
      num_U: 16 (-)
      cluster_root: 1.000 (-)
      cluster_tip: 0.200 (-)
  num_station_airfoils: 3
  station_airfoils:
    AirfoilSpec:
      kind: naca4
      thickness: 0.120 (-)
      camber: 0.020 (-)
      camber_loc: 0.400 x/c
      sharp_te: True
      file_path: None
      xsec_type: None
      extra_parms: {}
    AirfoilSpec:
      kind: naca4
      thickness: 0.120 (-)
      camber: 0.020 (-)
      camber_loc: 0.400 x/c
      sharp_te: True
      file_path: None
      xsec_type: None
      extra_parms: {}
    AirfoilSpec:
      kind: naca4
      thickness: 0.100 (-)
      camber: 0.000 (-)
      camber_loc: 0.400 x/c
      sharp_te: True
      file_path: None
      xsec_type: None
      extra_parms: {}

Build and save outputs

[4]:
# Store all notebook outputs in a dedicated local folder
output_dir = this_notebook_dir / "01_finite_wing_vsp_oop"
output_dir.mkdir(parents=True, exist_ok=True)

wing_id = wing.build()
print(f"Built wing geometry ID: {wing_id}")

vsp3_path = output_dir / "01_finite_wing_oop.vsp3"
wing.save(str(vsp3_path))
print(f"Saved model to: {vsp3_path}")
Built wing geometry ID: KWJRABRPTV
Saved model to: F:\agodemar\AeroDemonstrator\notebooks\02_finite_wing\01_finite_wing_vsp_oop\01_finite_wing_oop.vsp3

Run VSPAero and save analysis files

[5]:
import io
from contextlib import redirect_stdout

previous_cwd = Path.cwd()
os.chdir(output_dir)
try:
    # Compute geometry
    vsp.SetAnalysisInputDefaults("VSPAEROComputeGeometry")
    vsp.SetIntAnalysisInput("VSPAEROComputeGeometry", "GeomSet", [vsp.SET_NONE], 0)
    vsp.SetIntAnalysisInput("VSPAEROComputeGeometry", "ThinGeomSet", [vsp.SET_ALL], 0)

    buf = io.StringIO()
    with redirect_stdout(buf):
        print("\tExecuting compute geometry...")
        compgeom_resid = vsp.ExecAnalysis("VSPAEROComputeGeometry")
        print("\tCOMPLETE")
        vsp.PrintResults(compgeom_resid)

        # Sweep setup
        vsp.SetAnalysisInputDefaults("VSPAEROSweep")
        vsp.SetIntAnalysisInput("VSPAEROSweep", "GeomSet", [vsp.SET_NONE], 0)
        vsp.SetIntAnalysisInput("VSPAEROSweep", "ThinGeomSet", [vsp.SET_ALL], 0)
        vsp.SetIntAnalysisInput("VSPAEROSweep", "RefFlag", [1], 0)
        vsp.SetIntAnalysisInput("VSPAEROSweep", "Symmetry", [1], 0)

        wid = vsp.FindGeomsWithName("FW_OOP_01")
        if not wid:
            wid = vsp.FindGeomsWithName("WingGeom")
        vsp.SetStringAnalysisInput("VSPAEROSweep", "WingID", wid, 0)

        vsp.SetDoubleAnalysisInput("VSPAEROSweep", "AlphaStart", [1.0], 0)
        vsp.SetIntAnalysisInput("VSPAEROSweep", "AlphaNpts", [1], 0)
        vsp.SetDoubleAnalysisInput("VSPAEROSweep", "MachStart", [0.1], 0)
        vsp.SetIntAnalysisInput("VSPAEROSweep", "MachNpts", [1], 0)
        vsp.SetIntAnalysisInput("VSPAEROSweep", "WakeNumIter", [8], 0)

        vsp.Update()
        print("\tExecuting aero-sweep-analysis...")
        rid = vsp.ExecAnalysis("VSPAEROSweep")
        print("\tCOMPLETE")
        vsp.PrintResults(rid)

        csv_file_path = output_dir / "01_finite_wing_oop_analysis_results.csv"
        vsp.WriteResultsCSVFile(rid, str(csv_file_path))

    captured_logs = buf.getvalue()
    print("================= Results:")
    print(captured_logs)
    print(f"Saved CSV to: {csv_file_path}")

    generated = sorted([p.name for p in output_dir.iterdir() if p.is_file()])
    print("Files in output directory:")
    for name in generated:
        print(f"  - {name}")
finally:
    os.chdir(previous_cwd)
================= Results:
        Executing compute geometry...
        COMPLETE
        Executing aero-sweep-analysis...
        COMPLETE

Saved CSV to: F:\agodemar\AeroDemonstrator\notebooks\02_finite_wing\01_finite_wing_vsp_oop\01_finite_wing_oop_analysis_results.csv
Files in output directory:
  - 01_finite_wing_oop.adb
  - 01_finite_wing_oop.adb.cases
  - 01_finite_wing_oop.case.1.quad.1.dat
  - 01_finite_wing_oop.csf
  - 01_finite_wing_oop.cuts
  - 01_finite_wing_oop.group.0
  - 01_finite_wing_oop.group.1
  - 01_finite_wing_oop.history
  - 01_finite_wing_oop.lod
  - 01_finite_wing_oop.polar
  - 01_finite_wing_oop.quad.cases
  - 01_finite_wing_oop.slc
  - 01_finite_wing_oop.vkey
  - 01_finite_wing_oop.vsp3
  - 01_finite_wing_oop.vspaero
  - 01_finite_wing_oop.vspgeom
  - 01_finite_wing_oop_analysis_results.csv