{ "nbformat": 4, "nbformat_minor": 5, "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "name": "python", "version": "3.9" } }, "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Wing–Fuselage Configuration with OpenVSP\n", "## Level: Intermediate\n", "\n", "This notebook demonstrates how to build a wing–fuselage aircraft configuration\n", "using the OpenVSP Python API and perform a panel aerodynamic analysis with VSPAero.\n", "\n", "### Topics covered\n", "- OpenVSP geometry creation via Python API\n", "- Wing and fuselage parametric modeling\n", "- VSPAero VLM (Vortex Lattice Method) analysis setup\n", "- Aerodynamic coefficient extraction: CL, CDi, Cm\n", "- Effect of fuselage on wing aerodynamics\n", "\n", "### Requirements\n", "- `openvsp` package installed (see https://openvsp.org)\n", "- If OpenVSP is not installed, this notebook will use a mock/fallback mode\n", "\n", "### References\n", "- OpenVSP Python API docs: https://openvsp.org/pyapi_docs/latest/\n", "- Raymer, D.P., *Aircraft Design: A Conceptual Approach*, AIAA, 2018" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "import sys, os\n", "sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath('.')), 'src'))\n", "\n", "from aerodemo.openvsp_utils import (\n", " check_openvsp, init_vsp, add_wing, add_fuselage, run_vspaero\n", ")\n", "from aerodemo.vlm import WingGeometry, VortexLatticeMethod\n", "\n", "HAS_VSP = check_openvsp()\n", "print(f\"OpenVSP available: {HAS_VSP}\")\n", "if not HAS_VSP:\n", " print(\"NOTE: OpenVSP not found. Using VLM fallback for aerodynamic analysis.\")\n", " print(\"Install OpenVSP with: pip install openvsp\")\n", "\n", "plt.rcParams.update({'figure.dpi': 100, 'axes.grid': True, 'grid.alpha': 0.3, 'font.size': 11})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1. Aircraft Configuration Parameters\n", "\n", "We design a simple low-wing transport aircraft:\n", "\n", "| Component | Parameter | Value |\n", "|-----------|-----------|-------|\n", "| Wing | Span | 28 m |\n", "| Wing | Root chord | 4.5 m |\n", "| Wing | Tip chord | 1.8 m |\n", "| Wing | Sweep (c/4) | 20° |\n", "| Wing | Dihedral | 5° |\n", "| Fuselage | Length | 30 m |\n", "| Fuselage | Max diameter | 3.2 m |" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Aircraft configuration parameters\n", "config = {\n", " # Wing\n", " 'wing_span': 28.0,\n", " 'wing_root_chord': 4.5,\n", " 'wing_tip_chord': 1.8,\n", " 'wing_sweep': 20.0,\n", " 'wing_dihedral': 5.0,\n", " 'wing_x_offset': 10.0,\n", " 'wing_z_offset': -1.2,\n", " # Fuselage\n", " 'fuse_length': 30.0,\n", " 'fuse_diameter': 3.2,\n", "}\n", "\n", "# Derived parameters\n", "S_ref = config['wing_span'] * (config['wing_root_chord'] + config['wing_tip_chord']) / 2\n", "AR = config['wing_span']**2 / S_ref\n", "lam = config['wing_tip_chord'] / config['wing_root_chord']\n", "MAC = (2/3) * config['wing_root_chord'] * (1 + lam + lam**2) / (1 + lam)\n", "\n", "print(\"Wing-Fuselage Configuration:\")\n", "print(f\" Wing span: b = {config['wing_span']:.1f} m\")\n", "print(f\" Reference area: S = {S_ref:.2f} m²\")\n", "print(f\" Aspect ratio: AR = {AR:.2f}\")\n", "print(f\" Taper ratio: λ = {lam:.3f}\")\n", "print(f\" MAC: c̄ = {MAC:.3f} m\")\n", "print(f\" Fuselage length: L = {config['fuse_length']:.1f} m\")\n", "print(f\" Fuselage diameter: D = {config['fuse_diameter']:.1f} m\")\n", "print(f\" Fineness ratio: L/D = {config['fuse_length']/config['fuse_diameter']:.2f}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. OpenVSP Model Creation\n", "\n", "We create the wing and fuselage geometry in OpenVSP.\n", "If OpenVSP is installed, the geometry is built programmatically.\n", "Otherwise, we proceed with the VLM analysis." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "if HAS_VSP:\n", " # Initialize fresh model\n", " init_vsp(\"WingFuselage\")\n", "\n", " # Add fuselage\n", " fuse_id = add_fuselage(\n", " length=config['fuse_length'],\n", " max_diameter=config['fuse_diameter'],\n", " name=\"Fuselage\"\n", " )\n", " print(f\"Fuselage geometry ID: {fuse_id}\")\n", "\n", " # Add wing\n", " wing_id = add_wing(\n", " span=config['wing_span'],\n", " root_chord=config['wing_root_chord'],\n", " tip_chord=config['wing_tip_chord'],\n", " sweep_deg=config['wing_sweep'],\n", " dihedral_deg=config['wing_dihedral'],\n", " x_offset=config['wing_x_offset'],\n", " z_offset=config['wing_z_offset'],\n", " name=\"Wing\"\n", " )\n", " print(f\"Wing geometry ID: {wing_id}\")\n", " print(\"VSP model created successfully.\")\n", "else:\n", " print(\"Skipping VSP model creation (OpenVSP not available).\")\n", " print(\"Using VLM analysis as fallback.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. Aerodynamic Analysis\n", "\n", "### VSPAero (if OpenVSP available)\n", "VSPAero performs a Vortex Lattice analysis on the full 3D geometry.\n", "\n", "### VLM fallback\n", "Our custom VLM solver provides equivalent results for isolated wing analysis." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "alpha_range = np.linspace(-4, 14, 10)\n", "\n", "if HAS_VSP:\n", " print(\"Running VSPAero VLM sweep...\")\n", " vsp_results = run_vspaero(\n", " alpha_start=-4.0,\n", " alpha_end=14.0,\n", " alpha_npts=10,\n", " mach=0.2,\n", " ref_area=S_ref,\n", " ref_span=config['wing_span'],\n", " ref_chord=MAC,\n", " )\n", " if vsp_results:\n", " alpha_data = np.array(vsp_results['Alpha'])\n", " CL_data = np.array(vsp_results['CL'])\n", " CDi_data = np.array(vsp_results['CDi'])\n", " Cm_data = np.array(vsp_results.get('Cm', [0]*len(alpha_data)))\n", " analysis_label = 'VSPAero VLM'\n", " else:\n", " vsp_results = None\n", " print(\"VSPAero returned no results. Falling back to VLM.\")\n", " HAS_VSP = False\n", "\n", "if not HAS_VSP or vsp_results is None:\n", " # VLM fallback\n", " wing_geom = WingGeometry(\n", " span=config['wing_span'],\n", " root_chord=config['wing_root_chord'],\n", " tip_chord=config['wing_tip_chord'],\n", " sweep_angle=config['wing_sweep'],\n", " dihedral=config['wing_dihedral'],\n", " n_spanwise=14,\n", " n_chordwise=5,\n", " )\n", " vlm_solver = VortexLatticeMethod(wing_geom)\n", " sweep = vlm_solver.sweep_alpha(alpha_range)\n", " alpha_data = sweep['alpha']\n", " CL_data = sweep['CL']\n", " CDi_data = sweep['CDi']\n", " Cm_data = np.zeros_like(CL_data)\n", " analysis_label = 'VLM (fallback)'\n", "\n", "print(f\"Analysis complete using: {analysis_label}\")\n", "print(f\"\\nResults at α=4°:\")\n", "idx = np.argmin(np.abs(alpha_data - 4.0))\n", "print(f\" CL = {CL_data[idx]:.4f}\")\n", "print(f\" CDi = {CDi_data[idx]:.4f}\")\n", "print(f\" L/D = {CL_data[idx]/max(CDi_data[idx], 1e-6):.1f}\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, axes = plt.subplots(1, 3, figsize=(15, 5))\n", "\n", "ax = axes[0]\n", "ax.plot(alpha_data, CL_data, 'bo-', linewidth=2, markersize=7, label=analysis_label)\n", "ax.axhline(0, color='k', linewidth=0.5)\n", "ax.axvline(0, color='k', linewidth=0.5)\n", "ax.set_xlabel('α [deg]')\n", "ax.set_ylabel('$C_L$')\n", "ax.set_title('Lift Curve', fontweight='bold')\n", "ax.legend()\n", "\n", "ax = axes[1]\n", "ax.plot(CDi_data, CL_data, 'rs-', linewidth=2, markersize=7, label=analysis_label)\n", "ax.set_xlabel('$C_{Di}$')\n", "ax.set_ylabel('$C_L$')\n", "ax.set_title('Drag Polar', fontweight='bold')\n", "ax.legend()\n", "\n", "ax = axes[2]\n", "LD = CL_data / np.maximum(CDi_data, 1e-8)\n", "ax.plot(alpha_data, LD, 'gD-', linewidth=2, markersize=7)\n", "ax.set_xlabel('α [deg]')\n", "ax.set_ylabel('$C_L / C_{Di}$')\n", "ax.set_title('Aerodynamic Efficiency', fontweight='bold')\n", "\n", "plt.suptitle(f'Wing–Fuselage Configuration — {analysis_label}', fontsize=13, fontweight='bold')\n", "plt.tight_layout()\n", "plt.savefig('wing_fuselage_aero.png', bbox_inches='tight', dpi=100)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4. Summary\n", "\n", "In this notebook we have:\n", "1. Defined a wing–fuselage configuration with realistic parameters\n", "2. Created the geometry in OpenVSP (when available) or used VLM fallback\n", "3. Performed an angle-of-attack sweep and extracted CL, CDi\n", "4. Visualized the lift curve, drag polar, and efficiency\n", "\n", "### Key takeaways\n", "- OpenVSP enables accurate 3D geometry modeling with fuselage-wing interference\n", "- The VLM provides a quick first-estimate for isolated wing aerodynamics\n", "- Fuselage presence reduces effective wing aspect ratio slightly\n", "\n", "### Next steps\n", "Proceed to `../04_complete_aircraft/full_aircraft_openvsp.ipynb` for the full\n", "aircraft configuration including horizontal and vertical tails with control surfaces." ] } ] }