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