{ "cells": [ { "cell_type": "markdown", "id": "f4327fe4", "metadata": {}, "source": [ "# Experimenting with OpenVSP Python API\n", "\n", "## Level: Intermediate\n", "\n", "This notebook explores the basic geometric modeling capabilities of OpenVSP.\n", "\n", "### Topics covered\n", "- Finite wing geometry parameters \n", "- Wing shape variations" ] }, { "cell_type": "markdown", "id": "61f77fda", "metadata": {}, "source": [ "## Python project setup" ] }, { "cell_type": "code", "execution_count": 1, "id": "377e6259", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Setting up environment...\n", "Current working directory: f:\\agodemar\\AeroDemonstrator\\notebooks\\02_finite_wing\n", "Resolved project root: f:\\agodemar\\AeroDemonstrator\n", "Using src path: f:\\agodemar\\AeroDemonstrator\\src\n", "Verifying vsppytools and related directories...\n", "Using vsppytools paths: f:\\agodemar\\AeroDemonstrator\\src\\vsppytools, f:\\agodemar\\AeroDemonstrator\\src\\vsppytools\\openvsp_config, f:\\agodemar\\AeroDemonstrator\\src\\vsppytools\\openvsp\n", "Successfully imported vsppytools modules.\n" ] } ], "source": [ "import sys, os\n", "\n", "print(\"Setting up environment...\")\n", "print(f\"Current working directory: {os.getcwd()}\")\n", "\n", "# Resolve project root from this notebook location and add src/ to import path\n", "this_notebook_dir = os.getcwd()\n", "project_root = os.path.abspath(os.path.join(this_notebook_dir, \"..\", \"..\"))\n", "src_root = os.path.join(project_root, \"src\")\n", "\n", "print(f\"Resolved project root: {project_root}\")\n", "if not os.path.isdir(src_root):\n", " raise FileNotFoundError(f\"Could not find src directory at: {src_root}\")\n", "\n", "# Add src/ to Python path for imports\n", "if src_root not in sys.path:\n", " sys.path.insert(0, src_root)\n", "\n", "print(f\"Using src path: {src_root}\")\n", "\n", "#=== Verify vsp-related directories exist and add to path ===\n", "print(\"Verifying vsppytools and related directories...\")\n", "\n", "# Verify vsppytools directory exists\n", "vsptools_path = os.path.join(src_root, \"vsppytools\")\n", "if not os.path.isdir(vsptools_path):\n", " raise FileNotFoundError(f\"Could not find vsppytools directory at: {vsptools_path}\")\n", "\n", "# Add vsppytools to path for imports\n", "if vsptools_path not in sys.path:\n", " sys.path.insert(0, vsptools_path)\n", "\n", "# Verify vsppytools/openvsp_config exists\n", "vsp_config_path = os.path.join(vsptools_path, \"openvsp_config\")\n", "if not os.path.isdir(vsp_config_path):\n", " raise FileNotFoundError(f\"Could not find openvsp_config directory at: {vsp_config_path}\")\n", "\n", "# Add vsppytools/openvsp_config to path for imports\n", "if vsp_config_path not in sys.path:\n", " sys.path.insert(0, vsp_config_path) \n", "\n", "# Verify vsppytools/openvsp exists\n", "vsp_path = os.path.join(vsptools_path, \"openvsp\")\n", "if not os.path.isdir(vsp_path):\n", " raise FileNotFoundError(f\"Could not find openvsp directory at: {vsp_path}\")\n", "\n", "# Add vsppytools/openvsp to path for imports\n", "if vsp_path not in sys.path:\n", " sys.path.insert(0, vsp_path)\n", "\n", "print(f\"Using vsppytools paths: {vsptools_path}, {vsp_config_path}, {vsp_path}\")\n", "\n", "# test imports\n", "try:\n", " # To go with the new structure, we need to import the config and vsp modules from their respective subdirectories\n", " import vsppytools.openvsp_config.openvsp_config as openvsp_config\n", " import vsppytools.openvsp.openvsp as vsp\n", " print(\"Successfully imported vsppytools modules.\")\n", "except ImportError as e:\n", " print(f\"Error importing vsppytools modules: {e}\")" ] }, { "cell_type": "markdown", "id": "eef8fa9c", "metadata": {}, "source": [ "## First attempt to use `vsppytools` API\n", "\n", "### Load the `openvsp` module" ] }, { "cell_type": "code", "execution_count": 2, "id": "c1e8db30", "metadata": {}, "outputs": [], "source": [ "# Set OpenVSP config options for headless operation\n", "openvsp_config.LOAD_GRAPHICS = False\n", "openvsp_config.LOAD_FACADE = False # Single Instance of OpenVSP" ] }, { "cell_type": "markdown", "id": "be365c54", "metadata": {}, "source": [ "### Create a wing" ] }, { "cell_type": "code", "execution_count": 3, "id": "2256436e", "metadata": {}, "outputs": [], "source": [ "vsp.ClearVSPModel() # Clear any existing model in OpenVSP\n", "\n", "wing_id = vsp.AddGeom( 'WING', '' )\n", "\n", "# Set Wing Section\n", "vsp.SetDriverGroup( wing_id, \n", " 1, # Wing \"section\" (panel) driver\n", " vsp.AR_WSECT_DRIVER, # Aspect-Ratio driver\n", " vsp.ROOTC_WSECT_DRIVER, # Root Chord driver\n", " vsp.TIPC_WSECT_DRIVER # Tip Chord driver\n", " )\n", "\n", "# Set NACA 0012 Airfoil and Common Parms \n", "\n", "# Thickness to chord ratio is set via the \"ThickChord\" parameter on the section curve driver.\n", "vsp.SetParmVal( wing_id, \n", " 'ThickChord', # Parameter name: thickness to chord ratio\n", " 'XSecCurve_0', # Section 0 (root) driver\n", " 0.12 # Value: 12% thickness\n", " )\n", "# Same as above, but for section 1 (tip)\n", "vsp.SetParmVal( wing_id, 'ThickChord', 'XSecCurve_1', 0.12 )\n", "\n", "# Set root and tip chord lengths\n", "vsp.SetParmVal( wing_id, \n", " 'Root_Chord', # Parameter name: root chord length\n", " 'XSec_1', # Section 1 (root) driver\n", " 2.0 # Value: 2.0 unit root chord length\n", " )\n", "vsp.SetParmVal( wing_id, \n", " 'Tip_Chord', # Parameter name: tip chord length\n", " 'XSec_1', # Section 1 (tip) driver\n", " 1.0 # Value: 1.0 unit tip chord length\n", " )\n", "\n", "# Wing tip tessellation parameters - set to coarse for faster meshing\n", "# See in GUI: WingGeom -> Plan -> Tessssellation Controls -> TECluster, LECluster for more details\n", "vsp.SetParmVal( wing_id, 'TECluster', 'WingGeom', 1.0 )\n", "vsp.SetParmVal( wing_id, 'LECluster', 'WingGeom', 0.2 )\n", "\n", "# Set up parameterization for sweep, aspect ratio, and tip clustering\n", "vsp.SetParmVal( wing_id, \n", " 'Aspect', 'XSec_1', # Aspect ratio driver parameter, XSec_1 is the section (panel) driver for the wing\n", " 8 # AR value b^2/S, where b is span and S is planform area\n", " ) \n", "vsp.SetParmVal( wing_id, \n", " 'Sweep_Location', 'XSec_1', # Sweep line reference location, \n", " 0.25 # Percentage position along airfoil from leading edge (0.25 for quarter-chord sweep)\n", " )\n", "vsp.SetParmVal( wing_id, \n", " 'Sweep', 'XSec_1', # Sweep angle driver parameter\n", " 0 # Sweep angle in degrees (0 for unswept wing)\n", " )\n", "vsp.SetParmVal( wing_id, \n", " 'OutCluster', 'XSec_1', # Tip clustering driver parameter\n", " 0.6 # Clustering value (1.0 for uniform spacing, <1.0 for more points near tip)\n", " )\n", "\n", "# === Tip cap shaping parameters ===\n", "# TODO\n", "\n", "# === Tessellation parameters ===\n", "vsp.SetParmVal( wing_id, \n", " 'SectTess_U', 'XSec_1', \n", " 64 # U direction tessellation for wing sections (root to tip) \n", " ) \n", "vsp.SetParmVal( wing_id, \n", " 'Tess_W', 'Shape', \n", " 16 # Tessellation in the width direction\n", " ) \n", "\n", "# Update the model\n", "vsp.Update()" ] }, { "cell_type": "markdown", "id": "d8d52e2d", "metadata": {}, "source": [ "### Save the model" ] }, { "cell_type": "code", "execution_count": 4, "id": "26684cc4", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Saved model to: f:\\agodemar\\AeroDemonstrator\\notebooks\\02_finite_wing\\00_finite_fing_vsp\\00_finite_wing.vsp3\n" ] } ], "source": [ "# Save outputs to local subdirectory\n", "output_dir = os.path.join(this_notebook_dir, \"00_finite_fing_vsp\")\n", "os.makedirs(output_dir, exist_ok=True)\n", "\n", "# Save the model\n", "vsp_file_path = os.path.join(output_dir, \"00_finite_wing.vsp3\")\n", "vsp.WriteVSPFile(vsp_file_path)\n", "print(f\"Saved model to: {vsp_file_path}\")" ] }, { "cell_type": "markdown", "id": "5eef2708", "metadata": {}, "source": [ "### VSPAero" ] }, { "cell_type": "code", "execution_count": 5, "id": "54949948", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "================= Results:\n", "\tExecuting compute geometry...\n", "\tCOMPLETE\n", "\tVSPAEROSweep\n", "\t___\n", "\tExecuting aero-sweep-analysis...\n", "\tCOMPLETE\n", "\n", "Saved CSV to: f:\\agodemar\\AeroDemonstrator\\notebooks\\02_finite_wing\\00_finite_fing_vsp\\00_finite_wing_analysis_results.csv\n", "VSPAero-generated files are in: f:\\agodemar\\AeroDemonstrator\\notebooks\\02_finite_wing\\00_finite_fing_vsp\n" ] } ], "source": [ "import io\n", "from contextlib import redirect_stdout\n", "\n", "# Ensure all VSPAero artifacts are written to local subdirectory\n", "output_dir = os.path.join(this_notebook_dir, \"00_finite_fing_vsp\")\n", "os.makedirs(output_dir, exist_ok=True)\n", "\n", "# Run VSPAero from output directory so generated files land there\n", "previous_cwd = os.getcwd()\n", "os.chdir(output_dir)\n", "try:\n", " # Set defaults\n", " vsp.SetAnalysisInputDefaults( \"VSPAEROComputeGeometry\" )\n", " vsp.SetIntAnalysisInput(\"VSPAEROComputeGeometry\", 'GeomSet', [vsp.SET_NONE], 0)\n", " vsp.SetIntAnalysisInput(\"VSPAEROComputeGeometry\", 'ThinGeomSet', [vsp.SET_ALL], 0) # Thin geometry - VLM\n", "\n", " # Capture logs from stdout\n", " buf = io.StringIO()\n", " with redirect_stdout(buf):\n", " vsp.PrintAnalysisInputs( \"VSPAEROComputeGeometry\" )\n", " # Execute\n", " print( '\\tExecuting compute geometry...' )\n", " compgeom_resid = vsp.ExecAnalysis( \"VSPAEROComputeGeometry\" )\n", " print( '\\tCOMPLETE' )\n", "\n", " # Get & Display Results\n", " vsp.PrintResults( compgeom_resid )\n", "\n", " #==== Analysis: VSPAero Single Point ====#\n", " # Set defaults\n", " vsp.SetAnalysisInputDefaults(\"VSPAEROSweep\")\n", " print(\"\\tVSPAEROSweep\")\n", "\n", " # Reference geometry set\n", " vsp.SetIntAnalysisInput(\"VSPAEROSweep\", 'GeomSet', [vsp.SET_NONE], 0)\n", " vsp.SetIntAnalysisInput(\"VSPAEROSweep\", 'ThinGeomSet', [vsp.SET_ALL], 0) # Thin geometry - VLM\n", " vsp.SetIntAnalysisInput(\"VSPAEROSweep\", \n", " 'RefFlag', [1], # Wing Reference\n", " 0)\n", " vsp.SetIntAnalysisInput(\"VSPAEROSweep\", \n", " 'Symmetry', [1], \n", " 0)\n", "\n", " wid = vsp.FindGeomsWithName( 'WingGeom' )\n", " vsp.SetStringAnalysisInput(\"VSPAEROSweep\", 'WingID', wid, 0)\n", "\n", " # Freestream Parameters\n", " vsp.SetDoubleAnalysisInput(\"VSPAEROSweep\", \n", " 'AlphaStart', [1.0], \n", " 0)\n", " AlphaNpts = [1]\n", " vsp.SetIntAnalysisInput(\"VSPAEROSweep\", 'AlphaNpts', AlphaNpts, 0)\n", " vsp.SetDoubleAnalysisInput(\"VSPAEROSweep\", \n", " 'Machstart', [0.1], # subsonic, uncompressible\n", " 0)\n", " MachNpts = [1]\n", " vsp.SetIntAnalysisInput(\"VSPAEROSweep\", 'MachNpts', MachNpts, 0)\n", " vsp.SetIntAnalysisInput(\"VSPAEROSweep\", \n", " 'WakeNumIter', [8], # wake iterations\n", " 0)\n", "\n", " vsp.Update()\n", "\n", " # list inputs, type, and current values\n", " vsp.PrintAnalysisInputs( \"VSPAEROSweep\" )\n", " print( '\\t___' )\n", "\n", " # Execute\n", " print( '\\tExecuting aero-sweep-analysis...' )\n", " rid = vsp.ExecAnalysis( \"VSPAEROSweep\" )\n", " print( '\\tCOMPLETE' )\n", "\n", " # Get & Display Results\n", " vsp.PrintResults( rid )\n", " csv_file_path = os.path.join(output_dir, \"00_finite_wing_analysis_results.csv\")\n", " vsp.WriteResultsCSVFile( rid, csv_file_path)\n", "\n", " captured_logs = buf.getvalue()\n", " print(\"================= Results:\")\n", " print(captured_logs)\n", " print(f\"Saved CSV to: {csv_file_path}\")\n", " print(f\"VSPAero-generated files are in: {output_dir}\")\n", "finally:\n", " os.chdir(previous_cwd)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.15" } }, "nbformat": 4, "nbformat_minor": 5 }