diff --git a/.gitignore b/.gitignore index cdd06fd..910c9b8 100644 --- a/.gitignore +++ b/.gitignore @@ -177,4 +177,7 @@ cython_debug/ #.idea/ #Ignore old thesis -Aerostructural_model_LEI \ No newline at end of file +Aerostructural_model_LEI + +# Generated cache from the fancy-plot SurfplanAdapter conversion +vsm_processed/ \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..fe3990e --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,46 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Repository purpose + +Python implementation of the Vortex Step Method (VSM): an enhanced lifting-line aerodynamic solver coupled with 2D viscous airfoil polars, aimed at low-aspect-ratio wings and leading-edge inflatable (LEI) kites. Scientific software — physical correctness, numerical stability, and reproducibility outrank abstraction and stylistic churn. **Read `AGENTS.md` before changing solver/aerodynamic logic** (sign conventions, reference frames, and circulation definitions must never change silently). + +## Commands + +```bash +source venv/bin/activate # project venv at ./venv — required; system python lacks deps +pip install -e .[dev] # editable install (dev = pytest, pytest-cov, black) + +pytest # full suite (~35 s), config in pytest.ini (testpaths = tests) +pytest tests/Solver/test_solver.py # one file +pytest tests/Solver/test_solver.py::test_name # one test +black src tests # formatting (used by the project) +``` + +Examples run as plain scripts: `python examples/TUDELFT_V3_KITE/tutorial.py`. CI (`.github/workflows/testing.yml`) runs pytest with coverage on Python 3.10. + +## Architecture (big picture) + +Data flow: **geometry YAML → `BodyAerodynamics.instantiate()` → `Wing`/`Panel` mesh → `Solver.solve()` → results dict → plotting**. + +- `src/VSM/core/BodyAerodynamics.py` — central orchestrator. `BodyAerodynamics.instantiate(n_panels, file_path=, spanwise_panel_distribution=..., bridle_path=...)` reads the YAML's `wing_sections` (headers `[airfoil_id, LE_x..z, TE_x..z]`) and `wing_airfoils` tables, builds polars per airfoil, adds sections to a `Wing`, and refines to `n_panels`. YAML readers index columns **by header name**, so extra columns (e.g. `VUP_x..z` from newer SurfplanAdapter exports) are ignored harmlessly. +- `src/VSM/core/AirfoilAerodynamics.py` — polar generation per airfoil type: `breukels_regression`, `masure_regression` (needs `ml_models_dir`; ~2.3 GB models downloaded from Zenodo into `data/ml_models/`, not in git), `neuralfoil`, `polars` (CSV), `inviscid`. Batch entry point: `from_yaml_entry_batch()`. +- `src/VSM/core/WingGeometry.py` (`Wing`), `Panel.py`, `Filament.py`, `Wake.py` — mesh & vortex elements. **Panels retain only LE/TE points + interpolated polar data**; airfoil `info_dict` parameters (`t`, `chord`, …) are discarded during instantiation — anything needing them later must re-read the YAML. +- `src/VSM/core/Solver.py` — iterative circulation (gamma) loop; optional Anderson acceleration and Li/Gaunaa spanwise artificial viscosity for post-stall stabilization. Returns a results dict (forces, moments, distributions). +- Top-level `src/VSM/` modules build on core: `stability_derivatives.py`, `trim_angle.py`, plus plotting: `plotting.py` (polars/distributions), `plot_geometry_matplotlib.py` (`plot_geometry`), `plot_geometry_plotly.py` (`interactive_plot` — the primary interactive 3D wing view, incl. bridle rendering), `plot_styling.py`. +- Performance-critical vector math is `numba`-jitted (`core/utils.py`). + +## Data layout + +`data//` holds per-kite inputs, e.g. `data/TUDELFT_V3_KITE/`: +- `CAD_derived_geometry/` and `Surfplan_derived_geometry/` — `aero_geometry_*.yaml` variants (one per airfoil-model type) + `struc_geometry_*.yaml` (bridle/particle data, used via `bridle_path`). +- `Surfplan_export/` — raw Surfplan export (`.txt` + `profiles/*.dat`). Converted to VSM YAMLs by the sibling [SurfplanAdapter](https://github.com/awegroup/SurfplanAdapter) project, which also handles the Surfplan→VSM coordinate transform. +- Different YAML sources use different global frames (CAD-derived vs Surfplan-derived origins differ by ~10 m in z) — never mix coordinates across YAML families. + +## Conventions that matter + +- Body-fixed reference frame: **x rearward (LE→TE), y right wing, z up**; α positive nose-up, β positive wind from port. Aircraft-frame conversion via `stability_derivatives.map_derivatives_to_aircraft_frame()`. +- Wing sections are ordered/symmetric tip-to-tip; `airfoil_id` in `wing_sections` must match `wing_airfoils`. +- Style (`docs/style_guide.md`): typed Google docstrings, `snake_case`, path variables end `_path`, directories end `_dir`. +- Tests include verification cases against analytical solutions (`tests/verification_cases/`: elliptical, swept, curved wing, horseshoe vortices) — solver changes must keep these passing; add regression tests when numerical behaviour intentionally changes and explain what changed and why in the commit. diff --git a/README.md b/README.md index 1d42c90..2376db6 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,18 @@ The Vortex Step Method (VSM) is an enhanced lifting line method that improves up A Julia version of this project is available at [VortexStepMethod.jl](https://github.com/Albatross-Kite-Transport/VortexStepMethod.jl) +## Key Features +- **Accurate low-aspect-ratio wing modeling** with enhanced lifting line theory +- **Viscous-inviscid coupling** using 2D airfoil polars (inviscid, CFD, ML-based) +- **Complex geometry support**: sweep, dihedral, anhedral, leading-edge inflatable (LEI) kites +- **Rigid-body stability derivatives**: automatic computation of dCx/dα, dCMy/dq, etc. +- **Trim angle solver**: automatic determination of trimmed angle of attack +- **Non-dimensional rate derivatives**: controls-friendly output (hat_p, hat_q, hat_r) +- **Reference point flexibility**: correctly handles moment reference point for rotational velocities +- **Interactive visualization**: Plotly and Matplotlib geometry and results plotting + +![](docs/plotly_render.png) + ## Reference Frame The VSM uses a body-fixed reference frame with the following conventions: @@ -30,17 +42,6 @@ The reference frame is illustrated below for the open-source example kite, the [ **Aircraft Frame Transformation:** For stability derivatives in standard aircraft coordinates (x-forward, y-right, z-down), use `map_derivatives_to_aircraft_frame()` from the `VSM.stability_derivatives` module. See `examples/TUDELFT_V3_KITE/evaluate_stability_derivatives.py` for usage. -## Key Features - -- **Accurate low-aspect-ratio wing modeling** with enhanced lifting line theory -- **Viscous-inviscid coupling** using 2D airfoil polars (inviscid, CFD, ML-based) -- **Complex geometry support**: sweep, dihedral, anhedral, leading-edge inflatable (LEI) kites -- **Rigid-body stability derivatives**: automatic computation of dCx/dα, dCMy/dq, etc. -- **Trim angle solver**: automatic determination of trimmed angle of attack -- **Non-dimensional rate derivatives**: controls-friendly output (hat_p, hat_q, hat_r) -- **Reference point flexibility**: correctly handles moment reference point for rotational velocities -- **Interactive visualization**: Plotly and Matplotlib geometry and results plotting - ## Documentation For detailed documentation, please refer to the following resources. diff --git a/changelog.md b/changelog.md index 410043c..707ff01 100644 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,162 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [2.3.0] - 03/11/2026 + +**Default results change.** The lifting-line coupling now follows the consistent +formulation of Gaunaa, Li & Pirrung (TORQUE 2026): circulation from the inner +velocity, span-perpendicular sections, quarter-chord force directions, +attached-trailed-vortex forces and a chord-regularised curved bound vortex. +Forces, moments and coefficients computed with the defaults differ from 2.2.0, +most on swept and curved wings (kites). Each new default can be switched back +individually where an option exists (listed below). See +`docs/aerodynamic_model` for the formulation and its validation. + +This release also contains everything shipped in 2.2.0. `v2.2.0` was tagged on +the `improve_plotting` branch, which had not been merged into `develop`/`main`. + +### Changed (results) +- Circulation target is `Gamma = 0.5 |V_inner| c Cl` with the inner + (span-perpendicular, own-2D-induction-removed) velocity, instead of + `0.5 Umag^2 / Umagw c Cl`. On the V3 kite the old map put the circulation + 2-6 % low. +- Panel frame and chord follow the Crossflow Principle: the chord direction is + perpendicular to the bound vortex, the normal is chord x span, and the panel + chord is the perpendicular one. Unswept panels are unchanged; on swept panels + the chord shrinks (V3 tip panels by 56 % and 19 %). +- `is_aoa_corrected=True` by default: lift and drag directions come from the + flow at the quarter chord (TAT3), with magnitudes from the 3/4-chord angle. + `is_aoa_corrected=False` restores the 3/4-chord directions. +- The quarter-chord flow includes the influence of a curved or swept bound + vortex through a chord-regularised correction (the actual minus a locally + unrolled vortex system). On-blade induced drag matches the Trefftz plane + within about 1 % on arc, swept and kite geometries, independently of the + panel count. It is computed once per shape and rotated under rigid motion. +- Drag acts along the local inner flow, not the global span axis. +- `is_with_attached_trailed_vortex_force=True` by default: Kutta-Joukowski force + on the chordwise attached trailed vortex segments (`F_attached_trailed_distribution` + in the results). +- `relaxation_factor=None` by default: 0.8 x the fixed-point stability limit of + Li et al. (2026), evaluated per solve (`Solver.compute_relaxation_factor_limit`). + 2.2.0 used a fixed 0.01; pass a number to keep a fixed value. +- Each wake ring follows its own panel's apparent velocity. Results in uniform + translating inflow are unchanged to round-off; they change under body rates or + distributed inflow. +- Bridle line segments are charged at their own inflow, not the wing's mean. +- With `is_with_viscous_drag_correction=True`, the Gaunaa 2024 correction receives + the full 3D relative velocity (the spanwise flow it models). +- With `is_with_artificial_viscosity=True`, the regularization is also gated on + negative stall. +- `Wing.preserve_section_order` (default False): skip the section re-sorting; + `update_wing_from_points` sets it, since its arrays already define the arc order. + +### Removed / breaking +- `Solver` options `is_smooth_circulation`, `smoothness_factor`, + `is_artificial_damping`, `artificial_damping`, + `is_with_simonet_artificial_viscosity`, `simonet_artificial_viscosity_fva` + (stored, never read) and the `gamma_loop_type` values `simonet_stall`, + `non_linear_simonet_stall`, `non_linear_simonet_stall_newton_raphson` (they + raised `ImportError`). An unknown `gamma_loop_type` now raises `ValueError`. +- `Solver.compute_aerodynamic_quantities` no longer returns `Umagw`; the relative + velocity is available from `Solver.compute_relative_velocity(gamma)`. +- The Anderson loop no longer falls back to the base loop by default + (`anderson_fallback_to_base=True` restores it). + +### Added +- `gamma_loop_type="casadi_newton"`: Newton solve of the circulation residual + with an exact CasADi Jacobian, globalised by pseudo-transient continuation, + with fallback to the base loop (`newton_max_iterations`, + `newton_pseudo_time_step`, `newton_fallback_to_base`, `polar_interpolation`). + Requires the optional `casadi` package. +- `stagnation_patience` / `stagnation_rtol`: optional early stop of stagnating + circulation loops. +- `BodyAerodynamics(..., leg_direction="rib" | "streamwise" | "perpendicular")` + for the attached-trailed legs (default `"rib"`, unchanged). +- `results["drag_induced_trefftz"]`: Trefftz-plane induced drag as a far-wake + reference (uniform inflow only). +- `VSM.core.panel_kernels`: the section force law as functions over an `xp` + namespace (`numpy` or `casadi`): `relative_flow`, `quarter_chord_velocity`, + `direction_angle`, `panel_forces`, `attached_trailed_forces` and row helpers. + `compute_results` uses them itself. +- `BodyAerodynamics.compute_quarter_chord_AIC_matrices`: the induction of the + force-direction flow at the quarter chords, for callers that freeze it. +- `Solver.casadi_panel_force_function()`: per-panel forces as a CasADi function. + With `table_inputs=True` (also on the residual function) the polar tables are + inputs, so one graph serves every deformed shape; + `Solver.casadi_polar_table_inputs()` and `casadi_stall_angle_inputs()` return + them in the input layout. +- Documentation: aerodynamic model document (formulation, conventions, + validation) and the curved bound-vortex validation report. + +### Fixed +- Circulation loops stop on non-finite circulation instead of running on. +- `Solver`: SyntaxError in the pseudo-transient reject condition (hotfix on `main` + after 2.2.0, now also here). +- `Wing.compute_projected_area` no longer normalises the caller's + `z_plane_vector` in place. + +### Performance +- AIC assembly JIT-compiled with numba, then made allocation-free (vectors as + tuples): about 1 us -> 20 ns per filament evaluation, 7.3 -> 0.1 ms for a + 36-panel AIC. +- Curved bound-vortex correction computed once per shape and rotated under + rigid motion. +- Faster Li/Gaunaa artificial viscosity, vectorised force law, Trefftz drag and + projected area, cheaper `casadi_newton` residual calls. A 36-panel V3 + `casadi_newton` solve takes about 17 ms (about 50 ms before these changes). + +## [2.2.0] - 02/09/2026 + +Release cut from the `improve_plotting` branch. This is the version used to +generate the rigid-body stability derivatives (Table 8) of Poland et al. (2026), +Wind Energ. Sci., "Computational aerodynamics for soft-wing kite design". + +### Fixed +- `BodyAerodynamics.va` setter: the rotational inflow due to body rates is now + `va -= omega x (r - r0)` (apparent wind = freestream minus body-point velocity). + v2.1.2 used `+=`, i.e. the body rotating at `-omega`. **This flips the sign of + every rate derivative** (`d/dp_hat`, `d/dq_hat`, `d/dr_hat`) computed with + `stability_derivatives.py`; angle derivatives and trim angles are unaffected. + Roll, pitch and yaw damping derivatives are now negative, as physics requires. +- `stability_derivatives.py::_evaluate_with_angles` now passes `body_axis`, so a + non-zero baseline roll/pitch rate is no longer silently applied about the z-axis. +- `trim_angle.py`: removed leftover debug `matplotlib` plot/`plt.show()` from the + coarse sweep (blocked headless pipelines). +- `Filament`: vortex-core regularization uses the radial projection of the + evaluation point onto the filament. +- `WingGeometry`: section meshing is orientation-agnostic. +- Elliptical-wing y-offset bug in tests fixed; bridle line index handling fixed. + +### Changed +- **Breaking:** `BodyAerodynamics.va_initialize` / `va` setter no longer accept + `roll_rate`, `pitch_rate`, `yaw_rate`. Use `body_rates` (scalar or list of + magnitudes, rad/s), `body_axis` (matching axes, default +z) and + `rates_in_body_frame`. `compute_trim_angle` and + `compute_rigid_body_stability_derivatives` keep their `roll_rate/pitch_rate/yaw_rate` + keyword arguments. +- **Breaking:** bridle-line dictionaries use header key `d` (line diameter) + instead of `diameter`, matching the SurfPlanAdapter export. +- `BodyAerodynamics.instantiate` accepts `aerodynamic_center_location` and + `control_point_location` (chord fractions, defaults 0.25 / 0.75); AC and CP + chord weights are decoupled. +- `Solver`: opt-in Anderson-accelerated inner circulation loop; opt-in + Li/Gaunaa spanwise artificial viscosity for post-stall stabilization; + `relaxation_factor` default restored to 0.01. +- `Solver.solve` results include `q_ref` (reference dynamic pressure); coefficients + are normalised with an area-weighted reference velocity for distributed inflow. +- Quasi-steady-state solver API and examples added and subsequently removed. + +### Added +- `BodyAerodynamics.rotate(angle_deg|angle_rad, axis, point)` to rotate the full + geometry; body rates follow the geometry unless `rates_in_body_frame=True`. +- `BodyAerodynamics.compute_flat_area`. +- `src/VSM/plotly/`: interactive Plotly geometry plots with inflatable tubes, + curved canopy, Cp-scaled force fields (`fancy_interactive_plot.py` example). +- Examples: `compute_trim_angle.py`, `calculate_max_roll.py`, + `attitude_and_yaw_rate_optimize.py`, `cmz_sensitivity_default.py`, + `varying_spanwise_va.py`. +- Tests for gamma initialization, tube geometry, updated filament/solver tests. + ## [2.1.0] - 11/02/2026 ### Added diff --git a/data/TUDELFT_V3_KITE/CAD_derived_geometry/struc_geometry_manually_adjusted.yaml b/data/TUDELFT_V3_KITE/CAD_derived_geometry/struc_geometry_manually_adjusted.yaml index fdc68f0..4987dc6 100644 --- a/data/TUDELFT_V3_KITE/CAD_derived_geometry/struc_geometry_manually_adjusted.yaml +++ b/data/TUDELFT_V3_KITE/CAD_derived_geometry/struc_geometry_manually_adjusted.yaml @@ -1,8 +1,7 @@ - ############################## ## General ################### ############################## -bridle_point_node: [0,0,0] #[x,y,z] --> location of kcu +bridle_point_node: [0, 0, 0] #[x,y,z] --> location of kcu ## Mass pulley_mass: !!float 0.1 #[kg] #TODO: would be better to define these per pulley @@ -15,7 +14,6 @@ dyneema: youngs_modulus: 550000000 #[Pa] -> k = E A / l0 damping_per_stiffness: 0.0 # [/s] -> c = damping_per_stiffness * k - ########################### ## Wing ################### ########################### @@ -117,38 +115,38 @@ wing_elements: # line name, measured rest length "l0" [m], stiffness "k" [N/m], damping "c" [N.s/m], mass "m" [kg], linktype headers: [name, l0, k, c, m, linktype] data: - # leading-edge tube (LE[i] -- LE[i+1]) - - [le_1, 0.984642, 2e3, 0, 1, "default"] - - [le_2, 1.327663, 2e3, 0, 1, "default"] - - [le_3, 1.326240, 2e3, 0, 1, "default"] - - [le_4, 1.323655, 2e3, 0, 1, "default"] - - [le_5, 1.347595, 2e3, 0, 1, "default"] - - # struts (chords) - - [strut_1, 1.074728, 2e3, 0, 1, "default"] - - [strut_2, 1.511911, 2e3, 0, 1, "default"] - - [strut_3, 1.960560, 2e3, 0, 1, "default"] - - [strut_4, 2.154185, 2e3, 0, 1, "default"] - - [strut_5, 2.265501, 2e3, 0, 1, "default"] - - # trailing-edge wire (TE[i] -- TE[i+1]) - - [te_1, 0.903854, 1e3, 0, 0.2, "default"] - - [te_2, 1.351057, 1e3, 0, 0.2, "default"] - - [te_3, 1.350587, 1e3, 0, 0.2, "default"] - - [te_4, 1.350462, 1e3, 0, 0.2, "default"] - - [te_5, 1.321072, 1e3, 0, 0.2, "default"] - - # diagonal springs (both directions per panel) - - [dia_1a, 1.360356, 1e3, 0, 0.1, "default"] - - [dia_1b, 1.780032, 1e3, 0, 0.1, "default"] - - [dia_2a, 2.108078, 1e3, 0, 0.1, "default"] - - [dia_2b, 2.251513, 1e3, 0, 0.1, "default"] - - [dia_3a, 2.449188, 1e3, 0, 0.1, "default"] - - [dia_3b, 2.455523, 1e3, 0, 0.1, "default"] - - [dia_4a, 2.601005, 1e3, 0, 0.1, "default"] - - [dia_4b, 2.563136, 1e3, 0, 0.1, "default"] - - [dia_5a, 2.629214, 1e3, 0, 0.1, "default"] - - [dia_5b, 2.629214, 1e3, 0, 0.1, "default"] + # leading-edge tube (LE[i] -- LE[i+1]) + - [le_1, 0.984642, 2e3, 0, 1, 'default'] + - [le_2, 1.327663, 2e3, 0, 1, 'default'] + - [le_3, 1.326240, 2e3, 0, 1, 'default'] + - [le_4, 1.323655, 2e3, 0, 1, 'default'] + - [le_5, 1.347595, 2e3, 0, 1, 'default'] + + # struts (chords) + - [strut_1, 1.074728, 2e3, 0, 1, 'default'] + - [strut_2, 1.511911, 2e3, 0, 1, 'default'] + - [strut_3, 1.960560, 2e3, 0, 1, 'default'] + - [strut_4, 2.154185, 2e3, 0, 1, 'default'] + - [strut_5, 2.265501, 2e3, 0, 1, 'default'] + + # trailing-edge wire (TE[i] -- TE[i+1]) + - [te_1, 0.903854, 1e3, 0, 0.2, 'default'] + - [te_2, 1.351057, 1e3, 0, 0.2, 'default'] + - [te_3, 1.350587, 1e3, 0, 0.2, 'default'] + - [te_4, 1.350462, 1e3, 0, 0.2, 'default'] + - [te_5, 1.321072, 1e3, 0, 0.2, 'default'] + + # diagonal springs (both directions per panel) + - [dia_1a, 1.360356, 1e3, 0, 0.1, 'default'] + - [dia_1b, 1.780032, 1e3, 0, 0.1, 'default'] + - [dia_2a, 2.108078, 1e3, 0, 0.1, 'default'] + - [dia_2b, 2.251513, 1e3, 0, 0.1, 'default'] + - [dia_3a, 2.449188, 1e3, 0, 0.1, 'default'] + - [dia_3b, 2.455523, 1e3, 0, 0.1, 'default'] + - [dia_4a, 2.601005, 1e3, 0, 0.1, 'default'] + - [dia_4b, 2.563136, 1e3, 0, 0.1, 'default'] + - [dia_5a, 2.629214, 1e3, 0, 0.1, 'default'] + - [dia_5b, 2.629214, 1e3, 0, 0.1, 'default'] ############################# ## Bridle ################### @@ -177,71 +175,71 @@ bridle_particles: bridle_connections: # name, ci, cj, ck (this is the third node, only exist when a pulley) - headers: [name, ci, cj, ck] + headers: [name, ci, cj, ck] data: # front lines - - [a6,25,1,33] # pulley - - [A5,3,21] - - [A3,5,21] - - [A2,7,22] - - [A1,9,22] - - [A1,11,23] - - [A2,13,23] - - [A3,15,24] - - [A5,17,24] - - [a6,26,19,35] # pulley - - [AII,21,25] - - [AI,22,25] - - [AI,23,26] - - [AII,24,26] - - [amain,25,0] - - [amain,26,0] + - [a6, 25, 1, 33] # pulley + - [A5, 3, 21] + - [A3, 5, 21] + - [A2, 7, 22] + - [A1, 9, 22] + - [A1, 11, 23] + - [A2, 13, 23] + - [A3, 15, 24] + - [A5, 17, 24] + - [a6, 26, 19, 35] # pulley + - [AII, 21, 25] + - [AI, 22, 25] + - [AI, 23, 26] + - [AII, 24, 26] + - [amain, 25, 0] + - [amain, 26, 0] # rear lines - - [br6,33,2,36] # pulley - - [br5,4,27] - - [br3,6,27] - - [br2,8,28] - - [br1,10,28] - - [br1,12,29] - - [br2,14,29] - - [br3,16,30] - - [br5,18,30] - - [br6,35,20,37] # pulley - - [BRII,27,31] - - [BRI,28,31] - - [BRI,29,32] - - [BRII,30,32] - - [BR-main-1,31,33] - - [BR-main-1,32,35] - - [M-line,36,33,34] # pulley - - [M-line,34,35,37] # pulley - - [Steering Tape,36,0] - - [Power Tape,34,0] - - [Steering Tape,37,0] + - [br6, 33, 2, 36] # pulley + - [br5, 4, 27] + - [br3, 6, 27] + - [br2, 8, 28] + - [br1, 10, 28] + - [br1, 12, 29] + - [br2, 14, 29] + - [br3, 16, 30] + - [br5, 18, 30] + - [br6, 35, 20, 37] # pulley + - [BRII, 27, 31] + - [BRI, 28, 31] + - [BRI, 29, 32] + - [BRII, 30, 32] + - [BR-main-1, 31, 33] + - [BR-main-1, 32, 35] + - [M-line, 36, 33, 34] # pulley + - [M-line, 34, 35, 37] # pulley + - [Steering Tape, 36, 0] + - [Power Tape, 34, 0] + - [Steering Tape, 37, 0] bridle_lines: # line name, measured rest length "l0" [m], stiffness "k" [N/m], damping "c" [N.s/m], diameter "d" [m], density "rho" [kg/m^3] # dyneema has rho = 970 - headers: [name, l0, diameter, material, linktype] + headers: [name, l0, d, material, linktype] data: - # front lines - - [a6, 11.372, 0.005, dyneema, "pulley"] - - [A5, 2.782, 0.005, dyneema, "noncompressive"] - - [A3, 3.253, 0.005, dyneema, "noncompressive"] - - [A2, 3.352, 0.005, dyneema, "noncompressive"] - - [A1, 3.520, 0.005, dyneema, "noncompressive"] - - [AII, 3.188, 0.01, dyneema, "noncompressive"] - - [AI, 3.420, 0.01, dyneema, "noncompressive"] - - [amain, 4.297, 0.02, dyneema, "noncompressive"] - # rear lines - - [br1, 4.448, 0.002, dyneema, "noncompressive"] - - [br2, 4.300, 0.002, dyneema, "noncompressive"] - - [br3, 4.560, 0.002, dyneema, "noncompressive"] - - [br5, 4.047, 0.002, dyneema, "noncompressive"] - - [br6, 14.172, 0.002, dyneema, "pulley"] - - [BRI, 2.388, 0.002, dyneema, "noncompressive"] - - [BRII, 1.890, 0.002, dyneema, "noncompressive"] - - [BR-main-1, 0.874, 0.002, dyneema, "noncompressive"] - - [M-line, 2.196, 0.002, dyneema, "pulley"] - - [Steering Tape, 1.506, 0.002, dyneema, "noncompressive"] - - [Power Tape, 3.129, 0.002, dyneema, "noncompressive"] \ No newline at end of file + # front lines + - [a6, 11.372, 0.005, dyneema, 'pulley'] + - [A5, 2.782, 0.005, dyneema, 'noncompressive'] + - [A3, 3.253, 0.005, dyneema, 'noncompressive'] + - [A2, 3.352, 0.005, dyneema, 'noncompressive'] + - [A1, 3.520, 0.005, dyneema, 'noncompressive'] + - [AII, 3.188, 0.01, dyneema, 'noncompressive'] + - [AI, 3.420, 0.01, dyneema, 'noncompressive'] + - [amain, 4.297, 0.02, dyneema, 'noncompressive'] + # rear lines + - [br1, 4.448, 0.002, dyneema, 'noncompressive'] + - [br2, 4.300, 0.002, dyneema, 'noncompressive'] + - [br3, 4.560, 0.002, dyneema, 'noncompressive'] + - [br5, 4.047, 0.002, dyneema, 'noncompressive'] + - [br6, 14.172, 0.002, dyneema, 'pulley'] + - [BRI, 2.388, 0.002, dyneema, 'noncompressive'] + - [BRII, 1.890, 0.002, dyneema, 'noncompressive'] + - [BR-main-1, 0.874, 0.002, dyneema, 'noncompressive'] + - [M-line, 2.196, 0.002, dyneema, 'pulley'] + - [Steering Tape, 1.506, 0.002, dyneema, 'noncompressive'] + - [Power Tape, 3.129, 0.002, dyneema, 'noncompressive'] diff --git a/data/TUDELFT_V3_KITE/Surfplan_export/TUDELFT_V3_KITE.txt b/data/TUDELFT_V3_KITE/Surfplan_export/TUDELFT_V3_KITE.txt new file mode 100644 index 0000000..717e097 --- /dev/null +++ b/data/TUDELFT_V3_KITE/Surfplan_export/TUDELFT_V3_KITE.txt @@ -0,0 +1,214 @@ +Filename: TUDELFT_V3.sle +Design name: 14 hydrav1 07 +Revision: 1.0 +Designer: +Export Date: jeu. 13/02/2025 16:57:57 + +3d rib positions + LE X; Y; Z; TE X; Y; Z; VUP X; Y; Z +24 + 4,156287; 0,916618; 0,520832; 4,156287; 0,916618; -0,864107; 1,352509; 0,297952; 0,000000 + 3,967978; 1,791356; 0,709238; 3,955506; 1,787553; -1,011852; 1,646267; 0,501952; -0,013040 + 3,805350; 2,209568; 0,790880; 3,789238; 2,199439; -1,189372; 1,676461; 1,053982; -0,019032 + 3,501223; 2,540205; 0,864219; 3,485499; 2,522089; -1,256822; 1,390325; 1,601815; -0,023989 + 3,134335; 2,796493; 0,929298; 3,116753; 2,773988; -1,310901; 1,379198; 1,765306; -0,028558 + 2,798300; 3,092998; 0,986158; 2,780285; 3,065722; -1,354755; 1,290075; 1,953351; -0,032688 + 2,393198; 3,288677; 1,034831; 2,380855; 3,254511; -1,390237; 0,824015; 2,280780; -0,036327 + 1,959729; 3,398648; 1,075348; 1,946587; 3,361473; -1,418530; 0,831221; 2,351276; -0,039430 + 1,553762; 3,587398; 1,107733; 1,540958; 3,547442; -1,440421; 0,777640; 2,426595; -0,041958 + 1,111239; 3,671702; 1,132004; 1,107796; 3,627957; -1,456442; 0,203071; 2,580468; -0,043881 + 0,664252; 3,658834; 1,148177; 0,660721; 3,613797; -1,466944; 0,204434; 2,607118; -0,045175 + 0,225013; 3,741837; 1,156262; 0,220752; 3,696209; -1,472144; 0,244401; 2,617018; -0,045826 + -0,225013; 3,741837; 1,156262; -0,220752; 3,696209; -1,472144; -0,244401; 2,617018; -0,045826 + -0,664252; 3,658834; 1,148177; -0,660721; 3,613797; -1,466944; -0,204434; 2,607118; -0,045175 + -1,111239; 3,671702; 1,132004; -1,107796; 3,627957; -1,456442; -0,203071; 2,580468; -0,043881 + -1,553762; 3,587398; 1,107733; -1,540958; 3,547442; -1,440421; -0,777640; 2,426595; -0,041958 + -1,959729; 3,398648; 1,075348; -1,946587; 3,361473; -1,418530; -0,831221; 2,351276; -0,039430 + -2,393198; 3,288677; 1,034831; -2,380855; 3,254511; -1,390237; -0,824015; 2,280780; -0,036327 + -2,798300; 3,092998; 0,986158; -2,780285; 3,065722; -1,354755; -1,290075; 1,953351; -0,032688 + -3,134335; 2,796493; 0,929298; -3,116753; 2,773988; -1,310901; -1,379198; 1,765306; -0,028558 + -3,501223; 2,540205; 0,864219; -3,485499; 2,522089; -1,256822; -1,390325; 1,601815; -0,023989 + -3,805350; 2,209568; 0,790880; -3,789238; 2,199439; -1,189372; -1,676461; 1,053982; -0,019032 + -3,967978; 1,791356; 0,709238; -3,955506; 1,787553; -1,011852; -1,646267; 0,501952; -0,013040 + -4,156287; 0,916618; 0,520832; -4,156287; 0,916618; -0,864107; -1,352509; 0,297952; 0,000000 + +3d curved wingtip positions + LE X; Y; Z; TE X; Y; Z; VUP X; Y; Z +7 + 4,100957; 0,921215; -0,522180; 4,152601; 0,932607; -0,866820; 1,357902; 0,301698; -0,000239 + 4,105533; 0,971623; -0,263184; 4,141856; 0,979216; -0,874727; 1,373623; 0,312615; -0,000937 + 4,099444; 1,049881; -0,028112; 4,124523; 1,054402; -0,887481; 1,398983; 0,330226; -0,002063 + 4,085799; 1,154511; 0,183243; 4,101072; 1,156125; -0,904737; 1,433293; 0,354052; -0,003586 + 4,065552; 1,283430; 0,368472; 4,071974; 1,282342; -0,926149; 1,475864; 0,383616; -0,005476 + 4,039112; 1,434330; 0,522929; 4,037700; 1,431014; -0,951369; 1,526010; 0,418440; -0,007702 + 4,006563; 1,604842; 0,639766; 3,998721; 1,600098; -0,980052; 1,583040; 0,458044; -0,010233 + +LE tube + Centre X; Y; Z; Diam(mm) +38 + 4,047384; 0,902987; -0,806316; 0,112452 + 4,053468; 0,918198; -0,576557; 0,113170 + 4,052015; 0,967544; -0,320048; 0,115263 + 4,042557; 1,045392; -0,087800; 0,118639 + 4,025810; 1,149644; 0,119933; 0,123207 + 4,002306; 1,277897; 0,300671; 0,128875 + 3,972396; 1,427448; 0,449839; 0,135551 + 3,936225; 1,595488; 0,560835; 0,143143 + 3,967429; 1,791188; 0,633460; 0,151561 + 3,804690; 2,209154; 0,709853; 0,162061 + 3,500590; 2,539476; 0,778809; 0,170831 + 3,133636; 2,795597; 0,840178; 0,178254 + 2,797590; 3,091923; 0,893902; 0,184529 + 2,392716; 3,287341; 0,939956; 0,189772 + 1,959218; 3,397202; 0,978330; 0,194060 + 1,553266; 3,585851; 1,009025; 0,197442 + 1,111106; 3,670013; 1,032043; 0,199952 + 0,664116; 3,657099; 1,047385; 0,201615 + 0,224849; 3,740081; 1,055068; 0,202418 + -0,224849; 3,740081; 1,055068; 0,202418 + -0,664116; 3,657099; 1,047385; 0,201615 + -1,111106; 3,670013; 1,032043; 0,199952 + -1,553266; 3,585851; 1,009025; 0,197442 + -1,959218; 3,397202; 0,978330; 0,194060 + -2,392716; 3,287341; 0,939956; 0,189772 + -2,797590; 3,091923; 0,893902; 0,184529 + -3,133636; 2,795597; 0,840178; 0,178254 + -3,500590; 2,539476; 0,778809; 0,170831 + -3,804690; 2,209154; 0,709853; 0,162061 + -3,967429; 1,791188; 0,633460; 0,151561 + -3,936225; 1,595488; 0,560835; 0,143143 + -3,972396; 1,427448; 0,449839; 0,135551 + -4,002306; 1,277897; 0,300671; 0,128875 + -4,025810; 1,149644; 0,119933; 0,123207 + -4,042557; 1,045392; -0,087800; 0,118639 + -4,052015; 0,967544; -0,320048; 0,115263 + -4,053468; 0,918198; -0,576557; 0,113170 + -4,047384; 0,902987; -0,806316; 0,112452 + +Strut 2 + Centre X; Y; Z; Diam(m) +7 + 3,992381; 1,798912; 0,562351; 0,105697 + 4,013188; 1,805140; 0,397453; 0,099891 + 4,000806; 1,801365; 0,121209; 0,097265 + 3,980386; 1,795139; -0,157776; 0,092566 + 3,959762; 1,788850; -0,439564; 0,087908 + 3,938932; 1,782499; -0,724157; 0,083203 + 3,919906; 1,776698; -0,984103; 0,078907 + +Strut 5 + Centre X; Y; Z; Diam(m) +7 + 3,165429; 2,835935; 0,767330; 0,110490 + 3,198103; 2,878112; 0,526561; 0,128371 + 3,183835; 2,859850; 0,166347; 0,124894 + 3,159895; 2,829208; -0,197326; 0,117965 + 3,135714; 2,798257; -0,564655; 0,111190 + 3,111292; 2,766998; -0,935639; 0,104346 + 3,088872; 2,738302; -1,276210; 0,098064 + +Strut 8 + Centre X; Y; Z; Diam(m) +8 + 1,979699; 3,454888; 0,903062; 0,131981 + 2,006196; 3,530089; 0,681343; 0,141738 + 2,001981; 3,518165; 0,340947; 0,141175 + 1,987651; 3,477629; -0,004804; 0,133769 + 1,973148; 3,436605; -0,353542; 0,126804 + 1,958521; 3,395229; -0,705269; 0,119783 + 1,943769; 3,353502; -1,059985; 0,112703 + 1,930428; 3,315762; -1,380803; 0,106299 + +Strut 11 + Centre X; Y; Z; Diam(m) +8 + 0,669201; 3,721894; 0,970331; 0,140834 + 0,676930; 3,820506; 0,734465; 0,142401 + 0,675849; 3,806722; 0,377913; 0,142268 + 0,672073; 3,758564; 0,015391; 0,134521 + 0,668249; 3,709798; -0,350263; 0,127292 + 0,664392; 3,660613; -0,719051; 0,120005 + 0,660502; 3,611011; -1,090973; 0,112657 + 0,656965; 3,565894; -1,429263; 0,105972 + +Strut 14 + Centre X; Y; Z; Diam(m) +8 + -0,669201; 3,721894; 0,970331; 0,140834 + -0,676930; 3,820506; 0,734465; 0,142401 + -0,675849; 3,806722; 0,377913; 0,142268 + -0,672073; 3,758564; 0,015391; 0,134521 + -0,668249; 3,709798; -0,350263; 0,127292 + -0,664392; 3,660613; -0,719051; 0,120005 + -0,660502; 3,611011; -1,090973; 0,112657 + -0,656965; 3,565894; -1,429263; 0,105972 + +Strut 17 + Centre X; Y; Z; Diam(m) +8 + -1,979699; 3,454888; 0,903062; 0,131981 + -2,006196; 3,530089; 0,681343; 0,141738 + -2,001981; 3,518165; 0,340947; 0,141175 + -1,987651; 3,477629; -0,004804; 0,133769 + -1,973148; 3,436605; -0,353542; 0,126804 + -1,958521; 3,395229; -0,705269; 0,119783 + -1,943769; 3,353502; -1,059985; 0,112703 + -1,930428; 3,315762; -1,380803; 0,106299 + +Strut 20 + Centre X; Y; Z; Diam(m) +7 + -3,165429; 2,835935; 0,767330; 0,110490 + -3,198103; 2,878112; 0,526561; 0,128371 + -3,183835; 2,859850; 0,166347; 0,124894 + -3,159895; 2,829208; -0,197326; 0,117965 + -3,135714; 2,798257; -0,564655; 0,111190 + -3,111292; 2,766998; -0,935639; 0,104346 + -3,088872; 2,738302; -1,276210; 0,098064 + +Strut 23 + Centre X; Y; Z; Diam(m) +7 + -3,992381; 1,798912; 0,562351; 0,105697 + -4,013188; 1,805140; 0,397453; 0,099891 + -4,000806; 1,801365; 0,121209; 0,097265 + -3,980386; 1,795139; -0,157776; 0,092566 + -3,959762; 1,788850; -0,439564; 0,087908 + -3,938932; 1,782499; -0,724157; 0,083203 + -3,919906; 1,776698; -0,984103; 0,078907 + +3d Bridle + TopX; Y; Z; BottomX; Y; Z; Name ; Length; Material; Diameter + 0,658922; 3,590858; 1,123196; 0,682069; 3,407109; 1,014197; a1 ; 0,214896; ; 2,000000 + 1,937954; 3,337051; 1,051436; 1,898143; 3,164487; 0,942461; a2 ; 0,207939; ; 2,000000 + 3,097783; 2,749708; 0,907650; 3,052266; 2,575994; 0,791988; a3 ; 0,213602; ; 2,000000 + 3,921859; 1,777294; 0,692391; 3,825134; 1,648422; 0,616812; a4 ; 0,177977; ; 2,000000 + 0,682069; 3,407109; 1,014197; 0,887905; 1,721653; 0,769331; A1 ; 1,715543; ; 2,000000 + 1,898143; 3,164487; 0,942461; 1,532504; 1,590653; 0,731702; A2 ; 1,629437; ; 2,000000 + 3,052266; 2,575994; 0,791988; 2,644934; 1,015550; 0,607355; A3 ; 1,623267; ; 2,000000 + 3,825134; 1,648422; 0,616812; 2,172485; -0,743695; 0,618402; A4 ; 2,907486; ; 2,000000 + 0,887905; 1,721653; 0,769331; 1,113164; -0,195037; 0,711827; AI ; 1,930738; ; 3,000000 + 1,532504; 1,590653; 0,731702; 1,113164; -0,195037; 0,711827; AII ; 1,834374; ; 3,000000 + 2,644934; 1,015550; 0,607355; 2,172485; -0,743695; 0,618402; AIII ; 1,821612; ; 3,000000 + 1,113164; -0,195037; 0,711827; 0,941442; -3,901961; 0,633144; amain ; 3,711733; ; 4,000000 + 2,172485; -0,743695; 0,618402; 0,941442; -3,901961; 0,633144; amain2 ; 3,389739; ; 4,000000 + 0,941442; -3,901961; 0,633144; 0,910001; -4,099458; 0,630632; a5.1 ; 0,200000; ; 5,000000 + 0,659453; 3,597625; 0,966125; 0,682069; 3,407109; 1,014197; b1 ; 0,197785; ; 2,000000 + 1,939381; 3,341088; 0,901698; 1,898143; 3,164487; 0,942461; b2 ; 0,185876; ; 2,000000 + 3,097418; 2,749241; 0,773223; 3,052266; 2,575994; 0,791988; b3 ; 0,180014; ; 2,000000 + 3,908574; 1,773243; 0,589225; 3,825134; 1,648422; 0,616812; b4 ; 0,152655; ; 2,000000 + 0,670112; 3,733556; 0,362112; 0,887905; 1,721653; 0,769331; e1 ; 2,064223; ; 2,000000 + 1,977801; 3,449767; 0,326140; 1,532504; 1,590653; 0,731702; e2 ; 1,954245; ; 2,000000 + 3,148563; 2,814703; 0,256835; 2,644934; 1,015550; 0,607355; e3 ; 1,900910; ; 2,000000 + 0,654966; 3,540403; -1,204082; 1,155974; -0,902939; -0,473306; br1 ; 4,530820; ; 2,000000 + 1,920720; 3,288303; -1,167853; 1,155974; -0,902939; -0,473306; br2 ; 4,316681; ; 2,000000 + 3,069960; 2,714095; -1,085876; 1,921865; -1,309919; -0,396111; br3 ; 4,241059; ; 2,000000 + 3,891515; 1,768042; -0,839226; 1,921865; -1,309919; -0,396111; br4 ; 3,680993; ; 2,000000 + 4,142726; 0,875716; -0,615380; 3,821673; 0,392361; -0,630484; br5 ; 0,580461; ; 2,000000 + 4,137746; 0,864315; -0,791897; 3,821673; 0,392361; -0,630484; br6 ; 0,590505; ; 2,000000 + 1,155974; -0,902939; -0,473306; 1,087879; -3,228214; -0,089481; BR1 ; 2,357724; ; 2,000000 + 1,921865; -1,309919; -0,396111; 1,087879; -3,228214; -0,089481; BR2 ; 2,114098; ; 2,000000 + 3,821673; 0,392361; -0,630484; 0,954417; -3,906964; 0,021095; BR3 ; 5,208639; ; 2,000000 + 1,087879; -3,228214; -0,089481; 0,954417; -3,906964; 0,021095; BRI ; 0,700529; ; 3,000000 + 0,954417; -3,906964; 0,021095; 0,910001; -4,099458; 0,052295; brmain ; 0,200000; ; 4,000000 + diff --git a/data/TUDELFT_V3_KITE/Surfplan_export/profiles/V3D_AVL.avl b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/V3D_AVL.avl new file mode 100644 index 0000000..4519c16 --- /dev/null +++ b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/V3D_AVL.avl @@ -0,0 +1,116 @@ +Empty Project +0.0 | Mach +0 0 0.0 | iYsym iZsym Zsym + + 25.000 2.630 11.180 | Sref Cref Bref + 0.00 0.00 0.00 | Xref Yref Zref + 0.00 | CDp (optional) + + + +#================================================= +SURFACE | (keyword) +14 hydrav1 07 +#Nchord Cspace [ Nspan Sspace ] + 10 1.0 + +INDEX | (keyword) +111 | Lsurf + +YDUPLICATE +0.0 + +SCALE +1.0 1.0 1.0 + +TRANSLATE +0.0 0.0 0.0 + +ANGLE +0.0 | dAinc + +#__________________________________ +SECTION | (keyword) + 0.0000 0.2250 0.0000 2.6288 0.9989 3 0 | Xle Yle Zle Chord Ainc [ Nspan Sspace ] + +AFIL 0.0 1.0 +G:\Shared drives\Hardware\01 - Kite and Bridle (KB)\Archived kites\V3.25\V3.25D\VSM Package\prof_1.dat + +#______________ +SECTION | (keyword) + 0.0081 0.6643 -0.0830 2.6155 0.9897 3 0 | Xle Yle Zle Chord Ainc [ Nspan Sspace ] + +AFIL 0.0 1.0 +G:\Shared drives\Hardware\01 - Kite and Bridle (KB)\Archived kites\V3.25\V3.25D\VSM Package\prof_2.dat + +#______________ +SECTION | (keyword) + 0.0243 1.1112 -0.0701 2.5888 0.9712 3 0 | Xle Yle Zle Chord Ainc [ Nspan Sspace ] + +AFIL 0.0 1.0 +G:\Shared drives\Hardware\01 - Kite and Bridle (KB)\Archived kites\V3.25\V3.25D\VSM Package\prof_3.dat + +#______________ +SECTION | (keyword) + 0.0485 1.5538 -0.1544 2.5485 0.9433 3 0 | Xle Yle Zle Chord Ainc [ Nspan Sspace ] + +AFIL 0.0 1.0 +G:\Shared drives\Hardware\01 - Kite and Bridle (KB)\Archived kites\V3.25\V3.25D\VSM Package\prof_4.dat + +#______________ +SECTION | (keyword) + 0.0809 1.9597 -0.3432 2.4942 0.9058 3 0 | Xle Yle Zle Chord Ainc [ Nspan Sspace ] + +AFIL 0.0 1.0 +G:\Shared drives\Hardware\01 - Kite and Bridle (KB)\Archived kites\V3.25\V3.25D\VSM Package\prof_5.dat + +#______________ +SECTION | (keyword) + 0.1214 2.3932 -0.4532 2.4253 0.8582 3 0 | Xle Yle Zle Chord Ainc [ Nspan Sspace ] + +AFIL 0.0 1.0 +G:\Shared drives\Hardware\01 - Kite and Bridle (KB)\Archived kites\V3.25\V3.25D\VSM Package\prof_6.dat + +#______________ +SECTION | (keyword) + 0.1701 2.7983 -0.6488 2.3411 0.8000 3 0 | Xle Yle Zle Chord Ainc [ Nspan Sspace ] + +AFIL 0.0 1.0 +G:\Shared drives\Hardware\01 - Kite and Bridle (KB)\Archived kites\V3.25\V3.25D\VSM Package\prof_7.dat + +#______________ +SECTION | (keyword) + 0.2270 3.1343 -0.9453 2.2404 0.7304 3 0 | Xle Yle Zle Chord Ainc [ Nspan Sspace ] + +AFIL 0.0 1.0 +G:\Shared drives\Hardware\01 - Kite and Bridle (KB)\Archived kites\V3.25\V3.25D\VSM Package\prof_8.dat + +#______________ +SECTION | (keyword) + 0.2920 3.5012 -1.2016 2.1212 0.6480 3 0 | Xle Yle Zle Chord Ainc [ Nspan Sspace ] + +AFIL 0.0 1.0 +G:\Shared drives\Hardware\01 - Kite and Bridle (KB)\Archived kites\V3.25\V3.25D\VSM Package\prof_9.dat + +#______________ +SECTION | (keyword) + 0.3654 3.8053 -1.5323 1.9803 0.5506 3 0 | Xle Yle Zle Chord Ainc [ Nspan Sspace ] + +AFIL 0.0 1.0 +G:\Shared drives\Hardware\01 - Kite and Bridle (KB)\Archived kites\V3.25\V3.25D\VSM Package\prof_10.dat + +#______________ +SECTION | (keyword) + 0.4470 3.9680 -1.9505 1.7211 0.4341 3 0 | Xle Yle Zle Chord Ainc [ Nspan Sspace ] + +AFIL 0.0 1.0 +G:\Shared drives\Hardware\01 - Kite and Bridle (KB)\Archived kites\V3.25\V3.25D\VSM Package\prof_11.dat + +#______________ +SECTION | (keyword) + 0.6354 4.1563 -2.8252 1.3849 0.0000 3 0 | Xle Yle Zle Chord Ainc [ Nspan Sspace ] + +AFIL 0.0 1.0 +G:\Shared drives\Hardware\01 - Kite and Bridle (KB)\Archived kites\V3.25\V3.25D\VSM Package\prof_12.dat + +#______________ diff --git a/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_1.dat b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_1.dat new file mode 100644 index 0000000..0c7db24 --- /dev/null +++ b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_1.dat @@ -0,0 +1,151 @@ +rib_1 + 1.0000000 0.0000000 + 0.9577769 0.0053610 + 0.9081751 0.0116589 + 0.8585733 0.0179567 + 0.8089715 0.0242546 + 0.7887696 0.0268196 + 0.7593697 0.0305525 + 0.7097680 0.0368503 + 0.6601662 0.0431482 + 0.6105644 0.0494461 + 0.5983523 0.0509966 + 0.5609626 0.0557439 + 0.5113608 0.0620418 + 0.4617590 0.0683397 + 0.4301315 0.0723554 + 0.4121573 0.0746375 + 0.3625555 0.0809354 + 0.3129537 0.0872333 + 0.2944570 0.0895817 + 0.2864751 0.0899438 + 0.2631344 0.0910025 + 0.2431549 0.0919087 + 0.2231755 0.0928149 + 0.2031960 0.0937211 + 0.1832166 0.0946273 + 0.1750000 0.0950000 + 0.1696407 0.0944874 + 0.1632797 0.0938790 + 0.1433727 0.0919749 + 0.1234657 0.0900708 + 0.1222984 0.0899592 + 0.1039075 0.0859758 + 0.0941392 0.0838600 + 0.0842841 0.0817254 + 0.0818568 0.0807479 + 0.0751055 0.0780291 + 0.0658445 0.0742997 + 0.0565836 0.0705702 + 0.0529896 0.0691229 + 0.0478730 0.0658051 + 0.0395115 0.0603832 + 0.0311500 0.0549613 + 0.0292787 0.0537479 + 0.0290956 0.0535627 + 0.0273482 0.0517953 + 0.0256109 0.0500381 + 0.0256009 0.0500279 + 0.0238535 0.0482605 + 0.0221062 0.0464932 + 0.0203588 0.0447258 + 0.0186115 0.0429584 + 0.0168641 0.0411910 + 0.0151168 0.0394236 + 0.0133694 0.0376563 + 0.0132474 0.0375328 + 0.0121593 0.0355013 + 0.0109895 0.0333173 + 0.0098198 0.0311333 + 0.0086500 0.0289492 + 0.0074877 0.0267791 + 0.0066048 0.0244503 + 0.0057275 0.0221362 + 0.0048502 0.0198222 + 0.0039729 0.0175081 + 0.0030956 0.0151940 + 0.0022184 0.0128799 + 0.0021212 0.0123489 + 0.0013119 0.0099645 + 0.0008206 0.0074949 + 0.0003294 0.0050253 + 0.0001647 0.0025126 + 0.0000000 0.0000000 + 0.0001647 -0.0025126 + 0.0003294 -0.0050253 + 0.0008206 -0.0074949 + 0.0013119 -0.0099645 + 0.0021212 -0.0123489 + 0.0029306 -0.0147333 + 0.0040443 -0.0169917 + 0.0051580 -0.0192500 + 0.0065570 -0.0213437 + 0.0079559 -0.0234373 + 0.0096161 -0.0253305 + 0.0112764 -0.0272236 + 0.0131695 -0.0288839 + 0.0150627 -0.0305441 + 0.0171563 -0.0319430 + 0.0192500 -0.0333420 + 0.0215083 -0.0344557 + 0.0237667 -0.0355694 + 0.0261511 -0.0363788 + 0.0285355 -0.0371881 + 0.0310051 -0.0376794 + 0.0334747 -0.0381706 + 0.0359874 -0.0383353 + 0.0385000 -0.0385000 + 0.0410126 -0.0383353 + 0.0435253 -0.0381706 + 0.0459949 -0.0376794 + 0.0484645 -0.0371881 + 0.0508489 -0.0363788 + 0.0532333 -0.0355694 + 0.0554917 -0.0344557 + 0.0577500 -0.0333420 + 0.0598437 -0.0319430 + 0.0619373 -0.0305441 + 0.0638305 -0.0288839 + 0.0657236 -0.0272236 + 0.0673839 -0.0253305 + 0.0690441 -0.0234373 + 0.0704430 -0.0213437 + 0.0718420 -0.0192500 + 0.0729557 -0.0169917 + 0.0740694 -0.0147333 + 0.0751055 -0.0127256 + 0.0818568 -0.0003134 + 0.0842841 0.0036228 + 0.0941392 0.0180415 + 0.1039075 0.0299185 + 0.1222984 0.0475825 + 0.1234657 0.0485200 + 0.1433727 0.0624018 + 0.1632797 0.0725964 + 0.1696407 0.0752301 + 0.1750000 0.0772601 + 0.1832166 0.0800012 + 0.2031960 0.0851738 + 0.2231755 0.0884966 + 0.2431549 0.0902580 + 0.2631344 0.0907111 + 0.2864751 0.0889438 + 0.2944570 0.0885817 + 0.3129537 0.0862333 + 0.3625555 0.0799354 + 0.4121573 0.0736375 + 0.4301315 0.0713554 + 0.4617590 0.0673397 + 0.5113608 0.0610418 + 0.5609626 0.0547439 + 0.5983523 0.0499966 + 0.6105644 0.0484461 + 0.6601662 0.0421482 + 0.7097680 0.0358503 + 0.7593697 0.0295525 + 0.7887696 0.0258196 + 0.8089715 0.0232546 + 0.8585733 0.0169567 + 0.9081751 0.0106589 + 0.9577769 0.0043610 + 1.0000000 -0.0010000 diff --git a/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_1.png b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_1.png new file mode 100644 index 0000000..6694b81 Binary files /dev/null and b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_1.png differ diff --git a/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_10.dat b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_10.dat new file mode 100644 index 0000000..5bec58d --- /dev/null +++ b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_10.dat @@ -0,0 +1,150 @@ +rib_10 + 1.0000000 0.0000000 + 0.9577769 0.0036927 + 0.9081751 0.0080307 + 0.8585733 0.0123687 + 0.8089715 0.0167067 + 0.7887696 0.0184735 + 0.7593697 0.0210447 + 0.7097680 0.0253827 + 0.6601662 0.0297207 + 0.6105644 0.0340587 + 0.5983523 0.0351267 + 0.5609626 0.0383967 + 0.5113608 0.0427347 + 0.4617590 0.0470727 + 0.4301315 0.0498387 + 0.4121573 0.0514107 + 0.3625555 0.0557487 + 0.3129537 0.0600867 + 0.2944570 0.0617043 + 0.2864751 0.0620433 + 0.2631344 0.0629229 + 0.2431549 0.0635471 + 0.2231755 0.0641713 + 0.2031960 0.0647955 + 0.1832166 0.0654197 + 0.1750000 0.0656764 + 0.1696407 0.0654557 + 0.1632797 0.0651937 + 0.1433727 0.0642449 + 0.1234657 0.0630378 + 0.1222984 0.0629670 + 0.1039075 0.0607509 + 0.0941392 0.0594096 + 0.0842841 0.0580564 + 0.0818568 0.0575028 + 0.0751055 0.0559631 + 0.0658445 0.0536768 + 0.0565836 0.0513126 + 0.0529896 0.0503951 + 0.0478730 0.0484757 + 0.0395115 0.0451268 + 0.0311500 0.0416897 + 0.0292787 0.0409204 + 0.0290956 0.0408128 + 0.0273482 0.0397851 + 0.0256109 0.0387634 + 0.0256009 0.0387575 + 0.0238535 0.0376639 + 0.0221062 0.0365435 + 0.0203588 0.0354231 + 0.0186115 0.0343027 + 0.0168641 0.0331009 + 0.0151168 0.0317578 + 0.0133694 0.0305365 + 0.0132474 0.0304573 + 0.0121593 0.0292922 + 0.0109895 0.0279934 + 0.0098198 0.0265927 + 0.0086500 0.0251605 + 0.0074877 0.0237382 + 0.0066048 0.0223433 + 0.0057275 0.0208784 + 0.0048502 0.0193234 + 0.0039729 0.0175881 + 0.0038336 0.0172924 + 0.0031147 0.0156584 + 0.0022544 0.0131243 + 0.0013942 0.0105902 + 0.0008721 0.0079655 + 0.0003501 0.0053408 + 0.0001750 0.0026704 + 0.0000000 0.0000000 + 0.0001750 -0.0026704 + 0.0003501 -0.0053408 + 0.0008721 -0.0079655 + 0.0013942 -0.0105902 + 0.0022544 -0.0131243 + 0.0031147 -0.0156584 + 0.0042983 -0.0180585 + 0.0054819 -0.0204587 + 0.0069687 -0.0226838 + 0.0084554 -0.0249089 + 0.0102199 -0.0269209 + 0.0119844 -0.0289330 + 0.0139964 -0.0306975 + 0.0160085 -0.0324619 + 0.0182336 -0.0339487 + 0.0204587 -0.0354355 + 0.0228588 -0.0366191 + 0.0252590 -0.0378027 + 0.0277931 -0.0386629 + 0.0303272 -0.0395232 + 0.0329519 -0.0400452 + 0.0355766 -0.0405673 + 0.0382470 -0.0407424 + 0.0409174 -0.0409174 + 0.0435878 -0.0407424 + 0.0462582 -0.0405673 + 0.0488829 -0.0400452 + 0.0515076 -0.0395232 + 0.0540417 -0.0386629 + 0.0565758 -0.0378027 + 0.0589759 -0.0366191 + 0.0613761 -0.0354355 + 0.0636012 -0.0339487 + 0.0658263 -0.0324619 + 0.0678383 -0.0306975 + 0.0698503 -0.0289330 + 0.0716148 -0.0269209 + 0.0733793 -0.0249089 + 0.0748661 -0.0226838 + 0.0763529 -0.0204587 + 0.0775365 -0.0180585 + 0.0787201 -0.0156584 + 0.0818568 -0.0097543 + 0.0842841 -0.0056869 + 0.0941392 0.0077849 + 0.1039075 0.0179685 + 0.1222984 0.0319512 + 0.1234657 0.0326843 + 0.1433727 0.0430118 + 0.1632797 0.0502675 + 0.1696407 0.0520978 + 0.1750000 0.0534903 + 0.1832166 0.0553847 + 0.2031960 0.0589190 + 0.2231755 0.0611435 + 0.2431549 0.0623109 + 0.2631344 0.0626067 + 0.2864751 0.0610433 + 0.2944570 0.0607043 + 0.3129537 0.0590867 + 0.3625555 0.0547487 + 0.4121573 0.0504107 + 0.4301315 0.0488387 + 0.4617590 0.0460727 + 0.5113608 0.0417347 + 0.5609626 0.0373967 + 0.5983523 0.0341267 + 0.6105644 0.0330587 + 0.6601662 0.0287207 + 0.7097680 0.0243827 + 0.7593697 0.0200447 + 0.7887696 0.0174735 + 0.8089715 0.0157067 + 0.8585733 0.0113687 + 0.9081751 0.0070307 + 0.9577769 0.0026927 + 1.0000000 -0.0010000 diff --git a/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_10.png b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_10.png new file mode 100644 index 0000000..0591d8d Binary files /dev/null and b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_10.png differ diff --git a/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_11.dat b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_11.dat new file mode 100644 index 0000000..fa35a81 --- /dev/null +++ b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_11.dat @@ -0,0 +1,147 @@ +rib_11 + 1.0000000 0.0000000 + 0.9577769 0.0032589 + 0.9081751 0.0070873 + 0.8585733 0.0109156 + 0.8089715 0.0147440 + 0.7887696 0.0163032 + 0.7593697 0.0185724 + 0.7097680 0.0224008 + 0.6601662 0.0262292 + 0.6105644 0.0300575 + 0.5983523 0.0310001 + 0.5609626 0.0338859 + 0.5113608 0.0377143 + 0.4617590 0.0415427 + 0.4301315 0.0439838 + 0.4121573 0.0453711 + 0.3625555 0.0491994 + 0.3129537 0.0530278 + 0.2944570 0.0544554 + 0.2864751 0.0547884 + 0.2631344 0.0556214 + 0.2431549 0.0561723 + 0.2231755 0.0567232 + 0.2031960 0.0572741 + 0.1832166 0.0578249 + 0.1750000 0.0580515 + 0.1696407 0.0579299 + 0.1632797 0.0577855 + 0.1433727 0.0572106 + 0.1234657 0.0563887 + 0.1222984 0.0563406 + 0.1039075 0.0549167 + 0.0941392 0.0540035 + 0.0842841 0.0530821 + 0.0818568 0.0527233 + 0.0751055 0.0517256 + 0.0658445 0.0501903 + 0.0565836 0.0485806 + 0.0529896 0.0479558 + 0.0478730 0.0466996 + 0.0395115 0.0444437 + 0.0311500 0.0421034 + 0.0292787 0.0414955 + 0.0290956 0.0414245 + 0.0273482 0.0407469 + 0.0256109 0.0399917 + 0.0256009 0.0399871 + 0.0238535 0.0391345 + 0.0221062 0.0381831 + 0.0203588 0.0371252 + 0.0186115 0.0359515 + 0.0168641 0.0346501 + 0.0151168 0.0332060 + 0.0133694 0.0315998 + 0.0132474 0.0314809 + 0.0121593 0.0303789 + 0.0109895 0.0291024 + 0.0098198 0.0277180 + 0.0086500 0.0262085 + 0.0074877 0.0245619 + 0.0066048 0.0231945 + 0.0057275 0.0217152 + 0.0048502 0.0200893 + 0.0041252 0.0186075 + 0.0033515 0.0168492 + 0.0024259 0.0141224 + 0.0015003 0.0113956 + 0.0009385 0.0085713 + 0.0003767 0.0057470 + 0.0001883 0.0028735 + 0.0000000 0.0000000 + 0.0001883 -0.0028735 + 0.0003767 -0.0057470 + 0.0009385 -0.0085713 + 0.0015003 -0.0113956 + 0.0024259 -0.0141224 + 0.0033515 -0.0168492 + 0.0046252 -0.0194319 + 0.0058988 -0.0220146 + 0.0074986 -0.0244089 + 0.0090985 -0.0268033 + 0.0109972 -0.0289683 + 0.0128958 -0.0311333 + 0.0150609 -0.0330320 + 0.0172259 -0.0349307 + 0.0196202 -0.0365305 + 0.0220146 -0.0381304 + 0.0245973 -0.0394040 + 0.0271799 -0.0406776 + 0.0299068 -0.0416033 + 0.0326336 -0.0425289 + 0.0354579 -0.0430907 + 0.0382822 -0.0436525 + 0.0411557 -0.0438408 + 0.0440292 -0.0440292 + 0.0469026 -0.0438408 + 0.0497761 -0.0436525 + 0.0526004 -0.0430907 + 0.0554248 -0.0425289 + 0.0581516 -0.0416033 + 0.0608784 -0.0406776 + 0.0634611 -0.0394040 + 0.0660438 -0.0381304 + 0.0684381 -0.0365305 + 0.0708324 -0.0349307 + 0.0729975 -0.0330320 + 0.0751625 -0.0311333 + 0.0770612 -0.0289683 + 0.0789599 -0.0268033 + 0.0805597 -0.0244089 + 0.0821595 -0.0220146 + 0.0834332 -0.0194319 + 0.0847068 -0.0168492 + 0.0941392 -0.0014548 + 0.1039075 0.0097812 + 0.1222984 0.0245290 + 0.1234657 0.0252929 + 0.1433727 0.0357364 + 0.1632797 0.0429857 + 0.1696407 0.0448194 + 0.1750000 0.0461895 + 0.1832166 0.0480633 + 0.2031960 0.0515148 + 0.2231755 0.0537148 + 0.2431549 0.0549061 + 0.2631344 0.0552536 + 0.2864751 0.0547754 + 0.2944570 0.0534554 + 0.3129537 0.0520278 + 0.3625555 0.0481994 + 0.4121573 0.0443711 + 0.4301315 0.0429838 + 0.4617590 0.0405427 + 0.5113608 0.0367143 + 0.5609626 0.0328859 + 0.5983523 0.0300001 + 0.6105644 0.0290575 + 0.6601662 0.0252292 + 0.7097680 0.0214008 + 0.7593697 0.0175724 + 0.7887696 0.0153032 + 0.8089715 0.0137440 + 0.8585733 0.0099156 + 0.9081751 0.0060873 + 0.9577769 0.0022589 + 1.0000000 -0.0010000 diff --git a/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_11.png b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_11.png new file mode 100644 index 0000000..97f608f Binary files /dev/null and b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_11.png differ diff --git a/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_12.dat b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_12.dat new file mode 100644 index 0000000..68cc973 --- /dev/null +++ b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_12.dat @@ -0,0 +1,150 @@ +rib_12 + 1.0000000 0.0000000 + 0.9577769 0.0017841 + 0.9081751 0.0038799 + 0.8585733 0.0059757 + 0.8089715 0.0080716 + 0.7887696 0.0089252 + 0.7593697 0.0101674 + 0.7097680 0.0122632 + 0.6601662 0.0143591 + 0.6105644 0.0164549 + 0.5983523 0.0169709 + 0.5609626 0.0185507 + 0.5113608 0.0206466 + 0.4617590 0.0227424 + 0.4301315 0.0240788 + 0.4121573 0.0248383 + 0.3625555 0.0269341 + 0.3129537 0.0290299 + 0.2944570 0.0298115 + 0.2864751 0.0301487 + 0.2631344 0.0311350 + 0.2431549 0.0319791 + 0.2231755 0.0328233 + 0.2031960 0.0336675 + 0.1832166 0.0345117 + 0.1750000 0.0348589 + 0.1696407 0.0350854 + 0.1632797 0.0353541 + 0.1433727 0.0361953 + 0.1234657 0.0370364 + 0.1222984 0.0370857 + 0.1039075 0.0378628 + 0.0941392 0.0382755 + 0.0842841 0.0386920 + 0.0818568 0.0387945 + 0.0751055 0.0390798 + 0.0658445 0.0394711 + 0.0565836 0.0398624 + 0.0529896 0.0400142 + 0.0478730 0.0402304 + 0.0395115 0.0405837 + 0.0311500 0.0394836 + 0.0292787 0.0389883 + 0.0290956 0.0389347 + 0.0273482 0.0383752 + 0.0256109 0.0377306 + 0.0256009 0.0377266 + 0.0238535 0.0369842 + 0.0221062 0.0361423 + 0.0203588 0.0351935 + 0.0186115 0.0341292 + 0.0168641 0.0329380 + 0.0151168 0.0316056 + 0.0133694 0.0301133 + 0.0132474 0.0300025 + 0.0121593 0.0289732 + 0.0109895 0.0277767 + 0.0098198 0.0264746 + 0.0086500 0.0250505 + 0.0074877 0.0234927 + 0.0066048 0.0221960 + 0.0057275 0.0207906 + 0.0048502 0.0192431 + 0.0039729 0.0175158 + 0.0038037 0.0171576 + 0.0030904 0.0155363 + 0.0022369 0.0130220 + 0.0013834 0.0105076 + 0.0008653 0.0079034 + 0.0003473 0.0052991 + 0.0001737 0.0026496 + 0.0000000 0.0000000 + 0.0001737 -0.0026496 + 0.0003473 -0.0052991 + 0.0008653 -0.0079034 + 0.0013834 -0.0105076 + 0.0022369 -0.0130220 + 0.0030904 -0.0155363 + 0.0042648 -0.0179177 + 0.0054391 -0.0202991 + 0.0069143 -0.0225069 + 0.0083895 -0.0247147 + 0.0101402 -0.0267110 + 0.0118910 -0.0287073 + 0.0138873 -0.0304581 + 0.0158836 -0.0322088 + 0.0180914 -0.0336840 + 0.0202991 -0.0351592 + 0.0226806 -0.0363335 + 0.0250620 -0.0375079 + 0.0275763 -0.0383614 + 0.0300907 -0.0392149 + 0.0326949 -0.0397330 + 0.0352992 -0.0402510 + 0.0379487 -0.0404246 + 0.0405983 -0.0405983 + 0.0432479 -0.0404246 + 0.0458974 -0.0402510 + 0.0485017 -0.0397330 + 0.0511059 -0.0392149 + 0.0536202 -0.0383614 + 0.0561346 -0.0375079 + 0.0585160 -0.0363335 + 0.0608974 -0.0351592 + 0.0631052 -0.0336840 + 0.0653130 -0.0322088 + 0.0673093 -0.0304581 + 0.0693056 -0.0287073 + 0.0710563 -0.0267110 + 0.0728071 -0.0247147 + 0.0742823 -0.0225069 + 0.0757574 -0.0202991 + 0.0769318 -0.0179177 + 0.0781062 -0.0155363 + 0.0818568 -0.0093260 + 0.0842841 -0.0062041 + 0.0941392 0.0029782 + 0.1039075 0.0091624 + 0.1222984 0.0170947 + 0.1234657 0.0175025 + 0.1433727 0.0229102 + 0.1632797 0.0265216 + 0.1696407 0.0273820 + 0.1750000 0.0280259 + 0.1832166 0.0288782 + 0.2031960 0.0303387 + 0.2231755 0.0310903 + 0.2431549 0.0312669 + 0.2631344 0.0309707 + 0.2864751 0.0291487 + 0.2944570 0.0288115 + 0.3129537 0.0280299 + 0.3625555 0.0259341 + 0.4121573 0.0238383 + 0.4301315 0.0230788 + 0.4617590 0.0217424 + 0.5113608 0.0196466 + 0.5609626 0.0175507 + 0.5983523 0.0159709 + 0.6105644 0.0154549 + 0.6601662 0.0133591 + 0.7097680 0.0112632 + 0.7593697 0.0091674 + 0.7887696 0.0079252 + 0.8089715 0.0070716 + 0.8585733 0.0049757 + 0.9081751 0.0028799 + 0.9577769 0.0007841 + 1.0000000 -0.0010000 diff --git a/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_12.png b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_12.png new file mode 100644 index 0000000..2cdc56c Binary files /dev/null and b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_12.png differ diff --git a/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_2.dat b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_2.dat new file mode 100644 index 0000000..625d979 --- /dev/null +++ b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_2.dat @@ -0,0 +1,151 @@ +rib_2 + 1.0000000 0.0000000 + 0.9577769 0.0053268 + 0.9081751 0.0115845 + 0.8585733 0.0178422 + 0.8089715 0.0240999 + 0.7887696 0.0266485 + 0.7593697 0.0303576 + 0.7097680 0.0366152 + 0.6601662 0.0428729 + 0.6105644 0.0491306 + 0.5983523 0.0506713 + 0.5609626 0.0553883 + 0.5113608 0.0616460 + 0.4617590 0.0679037 + 0.4301315 0.0718938 + 0.4121573 0.0741614 + 0.3625555 0.0804191 + 0.3129537 0.0866768 + 0.2944570 0.0890103 + 0.2864751 0.0893718 + 0.2631344 0.0904268 + 0.2431549 0.0913273 + 0.2231755 0.0922277 + 0.2031960 0.0931281 + 0.1832166 0.0940286 + 0.1750000 0.0943989 + 0.1696407 0.0938922 + 0.1632797 0.0932908 + 0.1433727 0.0914058 + 0.1234657 0.0895147 + 0.1222984 0.0894039 + 0.1039075 0.0854549 + 0.0941392 0.0833536 + 0.0842841 0.0812336 + 0.0818568 0.0802643 + 0.0751055 0.0775684 + 0.0658445 0.0738664 + 0.0565836 0.0701625 + 0.0529896 0.0687251 + 0.0478730 0.0654343 + 0.0395115 0.0600516 + 0.0311500 0.0546668 + 0.0292787 0.0534617 + 0.0290956 0.0532780 + 0.0273482 0.0515249 + 0.0256109 0.0497818 + 0.0256009 0.0497717 + 0.0238535 0.0480171 + 0.0221062 0.0462618 + 0.0203588 0.0445065 + 0.0186115 0.0427513 + 0.0168641 0.0409960 + 0.0151168 0.0392407 + 0.0133694 0.0374855 + 0.0132474 0.0373629 + 0.0121593 0.0353485 + 0.0109895 0.0331817 + 0.0098198 0.0310127 + 0.0086500 0.0288436 + 0.0074877 0.0266883 + 0.0066048 0.0243778 + 0.0057275 0.0220801 + 0.0048502 0.0197818 + 0.0039729 0.0174836 + 0.0030956 0.0151854 + 0.0022184 0.0128872 + 0.0021236 0.0123624 + 0.0013133 0.0099754 + 0.0008215 0.0075031 + 0.0003297 0.0050308 + 0.0001649 0.0025154 + 0.0000000 0.0000000 + 0.0001649 -0.0025154 + 0.0003297 -0.0050308 + 0.0008215 -0.0075031 + 0.0013133 -0.0099754 + 0.0021236 -0.0123624 + 0.0029338 -0.0147494 + 0.0040488 -0.0170102 + 0.0051637 -0.0192710 + 0.0065641 -0.0213670 + 0.0079646 -0.0234629 + 0.0096267 -0.0253582 + 0.0112887 -0.0272534 + 0.0131839 -0.0289154 + 0.0150792 -0.0305775 + 0.0171751 -0.0319780 + 0.0192710 -0.0333784 + 0.0215319 -0.0344933 + 0.0237927 -0.0356083 + 0.0261797 -0.0364185 + 0.0285667 -0.0372288 + 0.0310390 -0.0377206 + 0.0335113 -0.0382124 + 0.0360267 -0.0383772 + 0.0385421 -0.0385421 + 0.0410575 -0.0383772 + 0.0435728 -0.0382124 + 0.0460452 -0.0377206 + 0.0485175 -0.0372288 + 0.0509045 -0.0364185 + 0.0532915 -0.0356083 + 0.0555523 -0.0344933 + 0.0578131 -0.0333784 + 0.0599091 -0.0319780 + 0.0620050 -0.0305775 + 0.0639003 -0.0289154 + 0.0657955 -0.0272534 + 0.0674575 -0.0253582 + 0.0691196 -0.0234629 + 0.0705201 -0.0213670 + 0.0719205 -0.0192710 + 0.0730354 -0.0170102 + 0.0741504 -0.0147494 + 0.0751055 -0.0128992 + 0.0818568 -0.0004809 + 0.0842841 0.0034531 + 0.0941392 0.0178500 + 0.1039075 0.0296932 + 0.1222984 0.0472811 + 0.1234657 0.0482146 + 0.1433727 0.0620213 + 0.1632797 0.0721516 + 0.1696407 0.0747674 + 0.1750000 0.0767829 + 0.1832166 0.0795051 + 0.2031960 0.0846408 + 0.2231755 0.0879378 + 0.2431549 0.0896854 + 0.2631344 0.0901347 + 0.2864751 0.0883718 + 0.2944570 0.0880103 + 0.3129537 0.0856768 + 0.3625555 0.0794191 + 0.4121573 0.0731614 + 0.4301315 0.0708938 + 0.4617590 0.0669037 + 0.5113608 0.0606460 + 0.5609626 0.0543883 + 0.5983523 0.0496713 + 0.6105644 0.0481306 + 0.6601662 0.0418729 + 0.7097680 0.0356152 + 0.7593697 0.0293576 + 0.7887696 0.0256485 + 0.8089715 0.0230999 + 0.8585733 0.0168422 + 0.9081751 0.0105845 + 0.9577769 0.0043268 + 1.0000000 -0.0010000 diff --git a/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_2.png b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_2.png new file mode 100644 index 0000000..196bda3 Binary files /dev/null and b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_2.png differ diff --git a/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_3.dat b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_3.dat new file mode 100644 index 0000000..b4aaa32 --- /dev/null +++ b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_3.dat @@ -0,0 +1,151 @@ +rib_3 + 1.0000000 0.0000000 + 0.9577769 0.0052581 + 0.9081751 0.0114351 + 0.8585733 0.0176122 + 0.8089715 0.0237892 + 0.7887696 0.0263049 + 0.7593697 0.0299662 + 0.7097680 0.0361432 + 0.6601662 0.0423202 + 0.6105644 0.0484972 + 0.5983523 0.0500180 + 0.5609626 0.0546742 + 0.5113608 0.0608512 + 0.4617590 0.0670282 + 0.4301315 0.0709669 + 0.4121573 0.0732053 + 0.3625555 0.0793823 + 0.3129537 0.0855593 + 0.2944570 0.0878627 + 0.2864751 0.0882233 + 0.2631344 0.0892710 + 0.2431549 0.0901598 + 0.2231755 0.0910486 + 0.2031960 0.0919374 + 0.1832166 0.0928263 + 0.1750000 0.0931918 + 0.1696407 0.0926970 + 0.1632797 0.0921097 + 0.1433727 0.0902628 + 0.1234657 0.0883979 + 0.1222984 0.0882886 + 0.1039075 0.0844087 + 0.0941392 0.0823364 + 0.0842841 0.0802458 + 0.0818568 0.0792930 + 0.0751055 0.0766430 + 0.0658445 0.0729958 + 0.0565836 0.0693432 + 0.0529896 0.0679257 + 0.0478730 0.0646890 + 0.0395115 0.0593849 + 0.0311500 0.0540747 + 0.0292787 0.0528862 + 0.0290956 0.0527055 + 0.0273482 0.0509810 + 0.0256109 0.0492664 + 0.0256009 0.0492565 + 0.0238535 0.0475274 + 0.0221062 0.0457964 + 0.0203588 0.0440654 + 0.0186115 0.0423344 + 0.0168641 0.0406034 + 0.0151168 0.0388725 + 0.0133694 0.0371415 + 0.0132474 0.0370206 + 0.0121593 0.0350404 + 0.0109895 0.0329084 + 0.0098198 0.0307693 + 0.0086500 0.0286303 + 0.0074877 0.0265048 + 0.0066048 0.0242309 + 0.0057275 0.0219660 + 0.0048502 0.0196995 + 0.0039729 0.0174331 + 0.0030956 0.0151667 + 0.0022184 0.0129003 + 0.0021278 0.0123869 + 0.0013159 0.0099952 + 0.0008231 0.0075180 + 0.0003304 0.0050407 + 0.0001652 0.0025204 + 0.0000000 0.0000000 + 0.0001652 -0.0025204 + 0.0003304 -0.0050407 + 0.0008231 -0.0075180 + 0.0013159 -0.0099952 + 0.0021278 -0.0123869 + 0.0029397 -0.0147786 + 0.0040568 -0.0170439 + 0.0051739 -0.0193092 + 0.0065771 -0.0214093 + 0.0079804 -0.0235094 + 0.0096457 -0.0254084 + 0.0113111 -0.0273074 + 0.0132101 -0.0289727 + 0.0151090 -0.0306381 + 0.0172091 -0.0320413 + 0.0193092 -0.0334446 + 0.0215745 -0.0345617 + 0.0238398 -0.0356788 + 0.0262315 -0.0364907 + 0.0286233 -0.0373026 + 0.0311005 -0.0377953 + 0.0335777 -0.0382881 + 0.0360981 -0.0384533 + 0.0386185 -0.0386185 + 0.0411388 -0.0384533 + 0.0436592 -0.0382881 + 0.0461364 -0.0377953 + 0.0486137 -0.0373026 + 0.0510054 -0.0364907 + 0.0533971 -0.0356788 + 0.0556624 -0.0345617 + 0.0579277 -0.0334446 + 0.0600278 -0.0320413 + 0.0621279 -0.0306381 + 0.0640269 -0.0289727 + 0.0659258 -0.0273074 + 0.0675912 -0.0254084 + 0.0692565 -0.0235094 + 0.0706598 -0.0214093 + 0.0720630 -0.0193092 + 0.0731801 -0.0170439 + 0.0742973 -0.0147786 + 0.0751055 -0.0132140 + 0.0818568 -0.0007931 + 0.0842841 0.0031413 + 0.0941392 0.0174898 + 0.1039075 0.0292607 + 0.1222984 0.0466897 + 0.1234657 0.0476151 + 0.1433727 0.0612674 + 0.1632797 0.0712660 + 0.1696407 0.0738449 + 0.1750000 0.0758307 + 0.1832166 0.0785142 + 0.2031960 0.0835748 + 0.2231755 0.0868185 + 0.2431549 0.0885374 + 0.2631344 0.0889779 + 0.2864751 0.0872233 + 0.2944570 0.0868627 + 0.3129537 0.0845593 + 0.3625555 0.0783823 + 0.4121573 0.0722053 + 0.4301315 0.0699669 + 0.4617590 0.0660282 + 0.5113608 0.0598512 + 0.5609626 0.0536742 + 0.5983523 0.0490180 + 0.6105644 0.0474972 + 0.6601662 0.0413202 + 0.7097680 0.0351432 + 0.7593697 0.0289662 + 0.7887696 0.0253049 + 0.8089715 0.0227892 + 0.8585733 0.0166122 + 0.9081751 0.0104351 + 0.9577769 0.0042581 + 1.0000000 -0.0010000 diff --git a/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_3.png b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_3.png new file mode 100644 index 0000000..d0b09ba Binary files /dev/null and b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_3.png differ diff --git a/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_4.dat b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_4.dat new file mode 100644 index 0000000..3da0617 --- /dev/null +++ b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_4.dat @@ -0,0 +1,151 @@ +rib_4 + 1.0000000 0.0000000 + 0.9577769 0.0051544 + 0.9081751 0.0112096 + 0.8585733 0.0172647 + 0.8089715 0.0233199 + 0.7887696 0.0257860 + 0.7593697 0.0293750 + 0.7097680 0.0354302 + 0.6601662 0.0414853 + 0.6105644 0.0475405 + 0.5983523 0.0490313 + 0.5609626 0.0535956 + 0.5113608 0.0596508 + 0.4617590 0.0657060 + 0.4301315 0.0695669 + 0.4121573 0.0717611 + 0.3625555 0.0778163 + 0.3129537 0.0838714 + 0.2944570 0.0861294 + 0.2864751 0.0864886 + 0.2631344 0.0875251 + 0.2431549 0.0883964 + 0.2231755 0.0892677 + 0.2031960 0.0901390 + 0.1832166 0.0910102 + 0.1750000 0.0913686 + 0.1696407 0.0908919 + 0.1632797 0.0903261 + 0.1433727 0.0885375 + 0.1234657 0.0867131 + 0.1222984 0.0866061 + 0.1039075 0.0828322 + 0.0941392 0.0808049 + 0.0842841 0.0787595 + 0.0818568 0.0778321 + 0.0751055 0.0752525 + 0.0658445 0.0716898 + 0.0565836 0.0681163 + 0.0529896 0.0667296 + 0.0478730 0.0635761 + 0.0395115 0.0583931 + 0.0311500 0.0531979 + 0.0292787 0.0520352 + 0.0290956 0.0518591 + 0.0273482 0.0501785 + 0.0256109 0.0485076 + 0.0256009 0.0484979 + 0.0238535 0.0468082 + 0.0221062 0.0451147 + 0.0203588 0.0434212 + 0.0186115 0.0417277 + 0.0168641 0.0400343 + 0.0151168 0.0383408 + 0.0133694 0.0366473 + 0.0132474 0.0365290 + 0.0121593 0.0346014 + 0.0109895 0.0325227 + 0.0098198 0.0304300 + 0.0086500 0.0283373 + 0.0074877 0.0262578 + 0.0066048 0.0240403 + 0.0057275 0.0218259 + 0.0048502 0.0196086 + 0.0039729 0.0173912 + 0.0030956 0.0151739 + 0.0029487 0.0148239 + 0.0021343 0.0124249 + 0.0013199 0.0100258 + 0.0008257 0.0075410 + 0.0003314 0.0050562 + 0.0001657 0.0025281 + 0.0000000 0.0000000 + 0.0001657 -0.0025281 + 0.0003314 -0.0050562 + 0.0008257 -0.0075410 + 0.0013199 -0.0100258 + 0.0021343 -0.0124249 + 0.0029487 -0.0148239 + 0.0040692 -0.0170962 + 0.0051898 -0.0193684 + 0.0065973 -0.0214750 + 0.0080048 -0.0235815 + 0.0096753 -0.0254863 + 0.0113458 -0.0273911 + 0.0132505 -0.0290615 + 0.0151553 -0.0307320 + 0.0172619 -0.0321395 + 0.0193684 -0.0335471 + 0.0216407 -0.0346676 + 0.0239129 -0.0357882 + 0.0263119 -0.0366025 + 0.0287110 -0.0374169 + 0.0311958 -0.0379112 + 0.0336807 -0.0384054 + 0.0362087 -0.0385711 + 0.0387368 -0.0387368 + 0.0412649 -0.0385711 + 0.0437930 -0.0384054 + 0.0462778 -0.0379112 + 0.0487627 -0.0374169 + 0.0511617 -0.0366025 + 0.0535608 -0.0357882 + 0.0558330 -0.0346676 + 0.0581052 -0.0335471 + 0.0602118 -0.0321395 + 0.0623183 -0.0307320 + 0.0642231 -0.0290615 + 0.0661279 -0.0273911 + 0.0677984 -0.0254863 + 0.0694688 -0.0235815 + 0.0708764 -0.0214750 + 0.0722839 -0.0193684 + 0.0734045 -0.0170962 + 0.0745250 -0.0148239 + 0.0751055 -0.0137013 + 0.0818568 -0.0012751 + 0.0842841 0.0026609 + 0.0941392 0.0169366 + 0.1039075 0.0285984 + 0.1222984 0.0457880 + 0.1234657 0.0467011 + 0.1433727 0.0601213 + 0.1632797 0.0699225 + 0.1696407 0.0724465 + 0.1750000 0.0743880 + 0.1832166 0.0770136 + 0.2031960 0.0819619 + 0.2231755 0.0851268 + 0.2431549 0.0868029 + 0.2631344 0.0872307 + 0.2864751 0.0854886 + 0.2944570 0.0851294 + 0.3129537 0.0828714 + 0.3625555 0.0768163 + 0.4121573 0.0707611 + 0.4301315 0.0685669 + 0.4617590 0.0647060 + 0.5113608 0.0586508 + 0.5609626 0.0525956 + 0.5983523 0.0480313 + 0.6105644 0.0465405 + 0.6601662 0.0404853 + 0.7097680 0.0344302 + 0.7593697 0.0283750 + 0.7887696 0.0247860 + 0.8089715 0.0223199 + 0.8585733 0.0162647 + 0.9081751 0.0102096 + 0.9577769 0.0041544 + 1.0000000 -0.0010000 diff --git a/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_4.png b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_4.png new file mode 100644 index 0000000..84a484e Binary files /dev/null and b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_4.png differ diff --git a/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_5.dat b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_5.dat new file mode 100644 index 0000000..9f4e0b2 --- /dev/null +++ b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_5.dat @@ -0,0 +1,151 @@ +rib_5 + 1.0000000 0.0000000 + 0.9577769 0.0050147 + 0.9081751 0.0109057 + 0.8585733 0.0167967 + 0.8089715 0.0226877 + 0.7887696 0.0250870 + 0.7593697 0.0285787 + 0.7097680 0.0344697 + 0.6601662 0.0403608 + 0.6105644 0.0462518 + 0.5983523 0.0477022 + 0.5609626 0.0521428 + 0.5113608 0.0580338 + 0.4617590 0.0639248 + 0.4301315 0.0676811 + 0.4121573 0.0698158 + 0.3625555 0.0757068 + 0.3129537 0.0815979 + 0.2944570 0.0837946 + 0.2864751 0.0841519 + 0.2631344 0.0851734 + 0.2431549 0.0860211 + 0.2231755 0.0868687 + 0.2031960 0.0877164 + 0.1832166 0.0885641 + 0.1750000 0.0889127 + 0.1696407 0.0884605 + 0.1632797 0.0879237 + 0.1433727 0.0862141 + 0.1234657 0.0844447 + 0.1222984 0.0843410 + 0.1039075 0.0807108 + 0.0941392 0.0787447 + 0.0842841 0.0767610 + 0.0818568 0.0758679 + 0.0751055 0.0733840 + 0.0658445 0.0699363 + 0.0565836 0.0664706 + 0.0529896 0.0651257 + 0.0478730 0.0620851 + 0.0395115 0.0570670 + 0.0311500 0.0520285 + 0.0292787 0.0509009 + 0.0290956 0.0507311 + 0.0273482 0.0491102 + 0.0256109 0.0474986 + 0.0256009 0.0474893 + 0.0238535 0.0458531 + 0.0221062 0.0442107 + 0.0203588 0.0425683 + 0.0186115 0.0409259 + 0.0168641 0.0392835 + 0.0151168 0.0376411 + 0.0133694 0.0359987 + 0.0132474 0.0358840 + 0.0121593 0.0340277 + 0.0109895 0.0320214 + 0.0098198 0.0299918 + 0.0086500 0.0279623 + 0.0074877 0.0259455 + 0.0066048 0.0238045 + 0.0057275 0.0216590 + 0.0048502 0.0195085 + 0.0039729 0.0173581 + 0.0030956 0.0152076 + 0.0029613 0.0148873 + 0.0021434 0.0124780 + 0.0013256 0.0100687 + 0.0008292 0.0075732 + 0.0003328 0.0050778 + 0.0001664 0.0025389 + 0.0000000 0.0000000 + 0.0001664 -0.0025389 + 0.0003328 -0.0050778 + 0.0008292 -0.0075732 + 0.0013256 -0.0100687 + 0.0021434 -0.0124780 + 0.0029613 -0.0148873 + 0.0040866 -0.0171692 + 0.0052119 -0.0194512 + 0.0066255 -0.0215667 + 0.0080390 -0.0236822 + 0.0097166 -0.0255952 + 0.0113942 -0.0275081 + 0.0133072 -0.0291857 + 0.0152201 -0.0308633 + 0.0173356 -0.0322768 + 0.0194512 -0.0336904 + 0.0217331 -0.0348157 + 0.0240150 -0.0359411 + 0.0264244 -0.0367589 + 0.0288337 -0.0375768 + 0.0313291 -0.0380731 + 0.0338246 -0.0385695 + 0.0363634 -0.0387359 + 0.0389023 -0.0389023 + 0.0414412 -0.0387359 + 0.0439801 -0.0385695 + 0.0464755 -0.0380731 + 0.0489710 -0.0375768 + 0.0513803 -0.0367589 + 0.0537896 -0.0359411 + 0.0560715 -0.0348157 + 0.0583535 -0.0336904 + 0.0604690 -0.0322768 + 0.0625846 -0.0308633 + 0.0644975 -0.0291857 + 0.0664104 -0.0275081 + 0.0680880 -0.0255952 + 0.0697656 -0.0236822 + 0.0711792 -0.0215667 + 0.0725927 -0.0194512 + 0.0737181 -0.0171692 + 0.0748434 -0.0148873 + 0.0751055 -0.0143810 + 0.0818568 -0.0019452 + 0.0842841 0.0019951 + 0.0941392 0.0161737 + 0.1039075 0.0276889 + 0.1222984 0.0445574 + 0.1234657 0.0454539 + 0.1433727 0.0585635 + 0.1632797 0.0681022 + 0.1696407 0.0705533 + 0.1750000 0.0724360 + 0.1832166 0.0749851 + 0.2031960 0.0797846 + 0.2231755 0.0828457 + 0.2431549 0.0844658 + 0.2631344 0.0848772 + 0.2864751 0.0831519 + 0.2944570 0.0827946 + 0.3129537 0.0805979 + 0.3625555 0.0747068 + 0.4121573 0.0688158 + 0.4301315 0.0666811 + 0.4617590 0.0629248 + 0.5113608 0.0570338 + 0.5609626 0.0511428 + 0.5983523 0.0467022 + 0.6105644 0.0452518 + 0.6601662 0.0393608 + 0.7097680 0.0334697 + 0.7593697 0.0275787 + 0.7887696 0.0240870 + 0.8089715 0.0216877 + 0.8585733 0.0157967 + 0.9081751 0.0099057 + 0.9577769 0.0040147 + 1.0000000 -0.0010000 diff --git a/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_5.png b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_5.png new file mode 100644 index 0000000..025394e Binary files /dev/null and b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_5.png differ diff --git a/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_6.dat b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_6.dat new file mode 100644 index 0000000..2b003ea --- /dev/null +++ b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_6.dat @@ -0,0 +1,150 @@ +rib_6 + 1.0000000 0.0000000 + 0.9577769 0.0048375 + 0.9081751 0.0105205 + 0.8585733 0.0162034 + 0.8089715 0.0218863 + 0.7887696 0.0242009 + 0.7593697 0.0275693 + 0.7097680 0.0332522 + 0.6601662 0.0389351 + 0.6105644 0.0446180 + 0.5983523 0.0460172 + 0.5609626 0.0503010 + 0.5113608 0.0559839 + 0.4617590 0.0616668 + 0.4301315 0.0652904 + 0.4121573 0.0673498 + 0.3625555 0.0730327 + 0.3129537 0.0787156 + 0.2944570 0.0808348 + 0.2864751 0.0811896 + 0.2631344 0.0821921 + 0.2431549 0.0830098 + 0.2231755 0.0838275 + 0.2031960 0.0846453 + 0.1832166 0.0854630 + 0.1750000 0.0857993 + 0.1696407 0.0853780 + 0.1632797 0.0848779 + 0.1433727 0.0832681 + 0.1234657 0.0815686 + 0.1222984 0.0814689 + 0.1039075 0.0780208 + 0.0941392 0.0761323 + 0.0842841 0.0742270 + 0.0818568 0.0733774 + 0.0751055 0.0710146 + 0.0658445 0.0677128 + 0.0565836 0.0643840 + 0.0529896 0.0630922 + 0.0478730 0.0601947 + 0.0395115 0.0553859 + 0.0311500 0.0505464 + 0.0292787 0.0494634 + 0.0290956 0.0493014 + 0.0273482 0.0477561 + 0.0256109 0.0462198 + 0.0256009 0.0462109 + 0.0238535 0.0446427 + 0.0221062 0.0430651 + 0.0203588 0.0414876 + 0.0186115 0.0399101 + 0.0168641 0.0383326 + 0.0151168 0.0367551 + 0.0133694 0.0351775 + 0.0132474 0.0350674 + 0.0121593 0.0333014 + 0.0109895 0.0313870 + 0.0098198 0.0294376 + 0.0086500 0.0274882 + 0.0074877 0.0255511 + 0.0066048 0.0235071 + 0.0057275 0.0214489 + 0.0048502 0.0193834 + 0.0039729 0.0173179 + 0.0030956 0.0152525 + 0.0029780 0.0149716 + 0.0021556 0.0125487 + 0.0013331 0.0101257 + 0.0008339 0.0076161 + 0.0003347 0.0051065 + 0.0001674 0.0025533 + 0.0000000 0.0000000 + 0.0001674 -0.0025533 + 0.0003347 -0.0051065 + 0.0008339 -0.0076161 + 0.0013331 -0.0101257 + 0.0021556 -0.0125487 + 0.0029780 -0.0149716 + 0.0041098 -0.0172665 + 0.0052415 -0.0195614 + 0.0066630 -0.0216889 + 0.0080846 -0.0238164 + 0.0097717 -0.0257402 + 0.0114588 -0.0276640 + 0.0133826 -0.0293511 + 0.0153063 -0.0310382 + 0.0174339 -0.0324598 + 0.0195614 -0.0338813 + 0.0218563 -0.0350130 + 0.0241511 -0.0361447 + 0.0265741 -0.0369672 + 0.0289971 -0.0377897 + 0.0315066 -0.0382889 + 0.0340162 -0.0387881 + 0.0365695 -0.0389554 + 0.0391228 -0.0391228 + 0.0416761 -0.0389554 + 0.0442293 -0.0387881 + 0.0467389 -0.0382889 + 0.0492485 -0.0377897 + 0.0516715 -0.0369672 + 0.0540944 -0.0361447 + 0.0563893 -0.0350130 + 0.0586842 -0.0338813 + 0.0608117 -0.0324598 + 0.0629392 -0.0310382 + 0.0648630 -0.0293511 + 0.0667868 -0.0276640 + 0.0684739 -0.0257402 + 0.0701610 -0.0238164 + 0.0715825 -0.0216889 + 0.0730041 -0.0195614 + 0.0741358 -0.0172665 + 0.0752675 -0.0149716 + 0.0818568 -0.0028308 + 0.0842841 0.0011187 + 0.0941392 0.0151761 + 0.1039075 0.0265060 + 0.1222984 0.0429705 + 0.1234657 0.0438457 + 0.1433727 0.0565655 + 0.1632797 0.0657766 + 0.1696407 0.0681373 + 0.1750000 0.0699475 + 0.1832166 0.0724014 + 0.2031960 0.0770165 + 0.2231755 0.0799503 + 0.2431549 0.0815017 + 0.2631344 0.0818934 + 0.2864751 0.0801896 + 0.2944570 0.0798348 + 0.3129537 0.0777156 + 0.3625555 0.0720327 + 0.4121573 0.0663498 + 0.4301315 0.0642904 + 0.4617590 0.0606668 + 0.5113608 0.0549839 + 0.5609626 0.0493010 + 0.5983523 0.0450172 + 0.6105644 0.0436180 + 0.6601662 0.0379351 + 0.7097680 0.0322522 + 0.7593697 0.0265693 + 0.7887696 0.0232009 + 0.8089715 0.0208863 + 0.8585733 0.0152034 + 0.9081751 0.0095205 + 0.9577769 0.0038375 + 1.0000000 -0.0010000 diff --git a/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_6.png b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_6.png new file mode 100644 index 0000000..23f1b56 Binary files /dev/null and b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_6.png differ diff --git a/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_7.dat b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_7.dat new file mode 100644 index 0000000..546acd6 --- /dev/null +++ b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_7.dat @@ -0,0 +1,150 @@ +rib_7 + 1.0000000 0.0000000 + 0.9577769 0.0046209 + 0.9081751 0.0100494 + 0.8585733 0.0154778 + 0.8089715 0.0209063 + 0.7887696 0.0231172 + 0.7593697 0.0263347 + 0.7097680 0.0317632 + 0.6601662 0.0371916 + 0.6105644 0.0426201 + 0.5983523 0.0439566 + 0.5609626 0.0480485 + 0.5113608 0.0534770 + 0.4617590 0.0589054 + 0.4301315 0.0623667 + 0.4121573 0.0643339 + 0.3625555 0.0697623 + 0.3129537 0.0751908 + 0.2944570 0.0772150 + 0.2864751 0.0775668 + 0.2631344 0.0785461 + 0.2431549 0.0793272 + 0.2231755 0.0801083 + 0.2031960 0.0808894 + 0.1832166 0.0816705 + 0.1750000 0.0819918 + 0.1696407 0.0816080 + 0.1632797 0.0811524 + 0.1433727 0.0796641 + 0.1234657 0.0780503 + 0.1222984 0.0779557 + 0.1039075 0.0747297 + 0.0941392 0.0729364 + 0.0842841 0.0711272 + 0.0818568 0.0703308 + 0.0751055 0.0681158 + 0.0658445 0.0649927 + 0.0565836 0.0618318 + 0.0529896 0.0606051 + 0.0478730 0.0578824 + 0.0395115 0.0533298 + 0.0311500 0.0487344 + 0.0292787 0.0477059 + 0.0290956 0.0475537 + 0.0273482 0.0461008 + 0.0256109 0.0446563 + 0.0256009 0.0446480 + 0.0238535 0.0431630 + 0.0221062 0.0416650 + 0.0203588 0.0401671 + 0.0186115 0.0386691 + 0.0168641 0.0371711 + 0.0151168 0.0356732 + 0.0133694 0.0341752 + 0.0132474 0.0340706 + 0.0121593 0.0324149 + 0.0109895 0.0306127 + 0.0098198 0.0287616 + 0.0086500 0.0269105 + 0.0074877 0.0250712 + 0.0066048 0.0231458 + 0.0057275 0.0211947 + 0.0048502 0.0192333 + 0.0039729 0.0172720 + 0.0030956 0.0153106 + 0.0029999 0.0150816 + 0.0021714 0.0126408 + 0.0013429 0.0102001 + 0.0008400 0.0076721 + 0.0003372 0.0051440 + 0.0001686 0.0025720 + 0.0000000 0.0000000 + 0.0001686 -0.0025720 + 0.0003372 -0.0051440 + 0.0008400 -0.0076721 + 0.0013429 -0.0102001 + 0.0021714 -0.0126408 + 0.0029999 -0.0150816 + 0.0041399 -0.0173933 + 0.0052799 -0.0197050 + 0.0067119 -0.0218482 + 0.0081439 -0.0239913 + 0.0098434 -0.0259292 + 0.0115429 -0.0278671 + 0.0134808 -0.0295666 + 0.0154187 -0.0312661 + 0.0175619 -0.0326981 + 0.0197050 -0.0341301 + 0.0220167 -0.0352701 + 0.0243285 -0.0364101 + 0.0267692 -0.0372386 + 0.0292100 -0.0380672 + 0.0317380 -0.0385700 + 0.0342660 -0.0390729 + 0.0368380 -0.0392414 + 0.0394100 -0.0394100 + 0.0419820 -0.0392414 + 0.0445541 -0.0390729 + 0.0470821 -0.0385700 + 0.0496101 -0.0380672 + 0.0520508 -0.0372386 + 0.0544916 -0.0364101 + 0.0568033 -0.0352701 + 0.0591150 -0.0341301 + 0.0612582 -0.0326981 + 0.0634013 -0.0312661 + 0.0653392 -0.0295666 + 0.0672771 -0.0278671 + 0.0689766 -0.0259292 + 0.0706761 -0.0239913 + 0.0721081 -0.0218482 + 0.0735401 -0.0197050 + 0.0746801 -0.0173933 + 0.0758201 -0.0150816 + 0.0818568 -0.0039728 + 0.0842841 -0.0000056 + 0.0941392 0.0139076 + 0.1039075 0.0250125 + 0.1222984 0.0409883 + 0.1234657 0.0418372 + 0.1433727 0.0540865 + 0.1632797 0.0629056 + 0.1696407 0.0651586 + 0.1750000 0.0668829 + 0.1832166 0.0692235 + 0.2031960 0.0736194 + 0.2231755 0.0764040 + 0.2431549 0.0778748 + 0.2631344 0.0782444 + 0.2864751 0.0765668 + 0.2944570 0.0762150 + 0.3129537 0.0741908 + 0.3625555 0.0687623 + 0.4121573 0.0633339 + 0.4301315 0.0613667 + 0.4617590 0.0579054 + 0.5113608 0.0524770 + 0.5609626 0.0470485 + 0.5983523 0.0429566 + 0.6105644 0.0416201 + 0.6601662 0.0361916 + 0.7097680 0.0307632 + 0.7593697 0.0253347 + 0.7887696 0.0221172 + 0.8089715 0.0199063 + 0.8585733 0.0144778 + 0.9081751 0.0090494 + 0.9577769 0.0036209 + 1.0000000 -0.0010000 diff --git a/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_7.png b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_7.png new file mode 100644 index 0000000..adeb036 Binary files /dev/null and b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_7.png differ diff --git a/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_8.dat b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_8.dat new file mode 100644 index 0000000..f2d3b06 --- /dev/null +++ b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_8.dat @@ -0,0 +1,150 @@ +rib_8 + 1.0000000 0.0000000 + 0.9577769 0.0043617 + 0.9081751 0.0094856 + 0.8585733 0.0146095 + 0.8089715 0.0197335 + 0.7887696 0.0218203 + 0.7593697 0.0248574 + 0.7097680 0.0299813 + 0.6601662 0.0351052 + 0.6105644 0.0402291 + 0.5983523 0.0414907 + 0.5609626 0.0453531 + 0.5113608 0.0504770 + 0.4617590 0.0556009 + 0.4301315 0.0588681 + 0.4121573 0.0607248 + 0.3625555 0.0658488 + 0.3129537 0.0709727 + 0.2944570 0.0728834 + 0.2864751 0.0732316 + 0.2631344 0.0741830 + 0.2431549 0.0749203 + 0.2231755 0.0756576 + 0.2031960 0.0763949 + 0.1832166 0.0771322 + 0.1750000 0.0774354 + 0.1696407 0.0770967 + 0.1632797 0.0766947 + 0.1433727 0.0753533 + 0.1234657 0.0738451 + 0.1222984 0.0737566 + 0.1039075 0.0708010 + 0.0941392 0.0691251 + 0.0842841 0.0674343 + 0.0818568 0.0667027 + 0.0751055 0.0646679 + 0.0658445 0.0617641 + 0.0565836 0.0588100 + 0.0529896 0.0576636 + 0.0478730 0.0551544 + 0.0395115 0.0509167 + 0.0311500 0.0466220 + 0.0292787 0.0456608 + 0.0290956 0.0455204 + 0.0273482 0.0441804 + 0.0256109 0.0428481 + 0.0256009 0.0428404 + 0.0238535 0.0414578 + 0.0221062 0.0400579 + 0.0203588 0.0386579 + 0.0186115 0.0372580 + 0.0168641 0.0358580 + 0.0151168 0.0344581 + 0.0133694 0.0330582 + 0.0132474 0.0329604 + 0.0121593 0.0314392 + 0.0109895 0.0297743 + 0.0098198 0.0280443 + 0.0086500 0.0263144 + 0.0074877 0.0245954 + 0.0066048 0.0228151 + 0.0057275 0.0209957 + 0.0048502 0.0191627 + 0.0039729 0.0173297 + 0.0037273 0.0168126 + 0.0030282 0.0152240 + 0.0021919 0.0127602 + 0.0013555 0.0102964 + 0.0008479 0.0077445 + 0.0003403 0.0051926 + 0.0001702 0.0025963 + 0.0000000 0.0000000 + 0.0001702 -0.0025963 + 0.0003403 -0.0051926 + 0.0008479 -0.0077445 + 0.0013555 -0.0102964 + 0.0021919 -0.0127602 + 0.0030282 -0.0152240 + 0.0041790 -0.0175575 + 0.0053298 -0.0198911 + 0.0067753 -0.0220544 + 0.0082208 -0.0242178 + 0.0099364 -0.0261740 + 0.0116519 -0.0281302 + 0.0136081 -0.0298457 + 0.0155643 -0.0315613 + 0.0177277 -0.0330068 + 0.0198911 -0.0344523 + 0.0222246 -0.0356031 + 0.0245582 -0.0367539 + 0.0270220 -0.0375902 + 0.0294857 -0.0384266 + 0.0320376 -0.0389342 + 0.0345895 -0.0394418 + 0.0371858 -0.0396119 + 0.0397821 -0.0397821 + 0.0423784 -0.0396119 + 0.0449747 -0.0394418 + 0.0475266 -0.0389342 + 0.0500785 -0.0384266 + 0.0525423 -0.0375902 + 0.0550061 -0.0367539 + 0.0573396 -0.0356031 + 0.0596732 -0.0344523 + 0.0618366 -0.0330068 + 0.0639999 -0.0315613 + 0.0659561 -0.0298457 + 0.0679123 -0.0281302 + 0.0696279 -0.0261740 + 0.0713434 -0.0242178 + 0.0727889 -0.0220544 + 0.0742344 -0.0198911 + 0.0753852 -0.0175575 + 0.0765360 -0.0152240 + 0.0818568 -0.0054326 + 0.0842841 -0.0014328 + 0.0941392 0.0123151 + 0.1039075 0.0231541 + 0.1222984 0.0385548 + 0.1234657 0.0393719 + 0.1433727 0.0510680 + 0.1632797 0.0594310 + 0.1696407 0.0615595 + 0.1750000 0.0631851 + 0.1832166 0.0653948 + 0.2031960 0.0695372 + 0.2231755 0.0721527 + 0.2431549 0.0735319 + 0.2631344 0.0738775 + 0.2864751 0.0722316 + 0.2944570 0.0718834 + 0.3129537 0.0699727 + 0.3625555 0.0648488 + 0.4121573 0.0597248 + 0.4301315 0.0578681 + 0.4617590 0.0546009 + 0.5113608 0.0494770 + 0.5609626 0.0443531 + 0.5983523 0.0404907 + 0.6105644 0.0392291 + 0.6601662 0.0341052 + 0.7097680 0.0289813 + 0.7593697 0.0238574 + 0.7887696 0.0208203 + 0.8089715 0.0187335 + 0.8585733 0.0136095 + 0.9081751 0.0084856 + 0.9577769 0.0033617 + 1.0000000 -0.0010000 diff --git a/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_8.png b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_8.png new file mode 100644 index 0000000..9fb8db6 Binary files /dev/null and b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_8.png differ diff --git a/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_9.dat b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_9.dat new file mode 100644 index 0000000..146d5b9 --- /dev/null +++ b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_9.dat @@ -0,0 +1,150 @@ +rib_9 + 1.0000000 0.0000000 + 0.9577769 0.0040550 + 0.9081751 0.0088187 + 0.8585733 0.0135823 + 0.8089715 0.0183459 + 0.7887696 0.0202861 + 0.7593697 0.0231096 + 0.7097680 0.0278732 + 0.6601662 0.0326369 + 0.6105644 0.0374005 + 0.5983523 0.0385733 + 0.5609626 0.0421642 + 0.5113608 0.0469278 + 0.4617590 0.0516915 + 0.4301315 0.0547289 + 0.4121573 0.0564551 + 0.3625555 0.0612187 + 0.3129537 0.0659824 + 0.2944570 0.0677588 + 0.2864751 0.0681027 + 0.2631344 0.0690212 + 0.2431549 0.0697067 + 0.2231755 0.0703921 + 0.2031960 0.0710776 + 0.1832166 0.0717630 + 0.1750000 0.0720449 + 0.1696407 0.0717592 + 0.1632797 0.0714200 + 0.1433727 0.0702522 + 0.1234657 0.0688715 + 0.1222984 0.0687906 + 0.1039075 0.0661569 + 0.0941392 0.0646227 + 0.0842841 0.0630749 + 0.0818568 0.0624205 + 0.0751055 0.0606006 + 0.0658445 0.0579605 + 0.0565836 0.0552562 + 0.0529896 0.0542067 + 0.0478730 0.0519526 + 0.0395115 0.0480937 + 0.0311500 0.0441621 + 0.0292787 0.0432822 + 0.0290956 0.0431560 + 0.0273482 0.0419509 + 0.0256109 0.0407528 + 0.0256009 0.0407458 + 0.0238535 0.0394864 + 0.0221062 0.0382048 + 0.0203588 0.0369232 + 0.0186115 0.0356416 + 0.0168641 0.0343601 + 0.0151168 0.0330785 + 0.0133694 0.0317969 + 0.0132474 0.0317074 + 0.0121593 0.0303466 + 0.0109895 0.0288459 + 0.0098198 0.0272622 + 0.0086500 0.0256785 + 0.0074877 0.0241049 + 0.0066048 0.0224983 + 0.0057275 0.0208377 + 0.0048502 0.0191597 + 0.0039729 0.0174408 + 0.0037728 0.0170180 + 0.0030652 0.0154099 + 0.0022187 0.0129160 + 0.0013721 0.0104221 + 0.0008583 0.0078391 + 0.0003445 0.0052560 + 0.0001722 0.0026280 + 0.0000000 0.0000000 + 0.0001722 -0.0026280 + 0.0003445 -0.0052560 + 0.0008583 -0.0078391 + 0.0013721 -0.0104221 + 0.0022187 -0.0129160 + 0.0030652 -0.0154099 + 0.0042301 -0.0177719 + 0.0053949 -0.0201340 + 0.0068581 -0.0223238 + 0.0083212 -0.0245136 + 0.0100577 -0.0264937 + 0.0117942 -0.0284738 + 0.0137743 -0.0302103 + 0.0157544 -0.0319467 + 0.0179442 -0.0334099 + 0.0201340 -0.0348731 + 0.0224960 -0.0360379 + 0.0248581 -0.0372028 + 0.0273520 -0.0380493 + 0.0298459 -0.0388959 + 0.0324289 -0.0394097 + 0.0350120 -0.0399235 + 0.0376400 -0.0400957 + 0.0402680 -0.0402680 + 0.0428960 -0.0400957 + 0.0455240 -0.0399235 + 0.0481071 -0.0394097 + 0.0506901 -0.0388959 + 0.0531840 -0.0380493 + 0.0556779 -0.0372028 + 0.0580399 -0.0360379 + 0.0604020 -0.0348731 + 0.0625918 -0.0334099 + 0.0647816 -0.0319467 + 0.0667617 -0.0302103 + 0.0687418 -0.0284738 + 0.0704782 -0.0264937 + 0.0722147 -0.0245136 + 0.0736779 -0.0223238 + 0.0751411 -0.0201340 + 0.0763059 -0.0177719 + 0.0774708 -0.0154099 + 0.0818568 -0.0073062 + 0.0842841 -0.0032485 + 0.0941392 0.0103179 + 0.1039075 0.0208497 + 0.1222984 0.0355868 + 0.1234657 0.0363657 + 0.1433727 0.0474230 + 0.1632797 0.0552655 + 0.1696407 0.0572531 + 0.1750000 0.0587679 + 0.1832166 0.0608289 + 0.2031960 0.0646844 + 0.2231755 0.0671125 + 0.2431549 0.0683904 + 0.2631344 0.0687109 + 0.2864751 0.0671027 + 0.2944570 0.0667588 + 0.3129537 0.0649824 + 0.3625555 0.0602187 + 0.4121573 0.0554551 + 0.4301315 0.0537289 + 0.4617590 0.0506915 + 0.5113608 0.0459278 + 0.5609626 0.0411642 + 0.5983523 0.0375733 + 0.6105644 0.0364005 + 0.6601662 0.0316369 + 0.7097680 0.0268732 + 0.7593697 0.0221096 + 0.7887696 0.0192861 + 0.8089715 0.0173459 + 0.8585733 0.0125823 + 0.9081751 0.0078187 + 0.9577769 0.0030550 + 1.0000000 -0.0010000 diff --git a/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_9.png b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_9.png new file mode 100644 index 0000000..90ebec8 Binary files /dev/null and b/data/TUDELFT_V3_KITE/Surfplan_export/profiles/prof_9.png differ diff --git a/data/TUDELFT_V3_KITE/cpx_distributions/README.md b/data/TUDELFT_V3_KITE/cpx_distributions/README.md new file mode 100644 index 0000000..d328489 --- /dev/null +++ b/data/TUDELFT_V3_KITE/cpx_distributions/README.md @@ -0,0 +1,34 @@ +# TU Delft V3 kite — 2D Cp(x/c) distributions + +Chordwise pressure-coefficient (`cp_AOA_*.dat`) and skin-friction (`cf_AOA_*.dat`) +distributions for the **TU Delft V3 LEI kite midspan airfoil section** (rigid 2D), +from RANS CFD (Thijs/Kasper). + +File format: whitespace columns `# x y Cp` (or `... Cf`), i.e. column 0 = x/c, +column 1 = y/c, column 2 = Cp (or Cf). + +## Contents + +| Angle of attack | Cp file | Cf file | +|---|---|---| +| 2° | `cp_AOA_2.dat` | `cf_AOA_2.dat` | +| 6° | `cp_AOA_6.dat` | `cf_AOA_6.dat` | +| 8° | `cp_AOA_8.dat` | `cf_AOA_8.dat` | + +These are the only angles for which the genuine V3 midspan section Cp distribution +is available locally. + +## Provenance + +Copied from `WES_aero_sim_for_kite_design/data/`: +- AoA 2° and 8°: `cp_cf_kasper/` +- AoA 6°: `THIJS_CFD_alpha_6_V3_turbulent/postProcessing/surfaces/624/` + +The V3 section is identified by its airfoil geometry embedded in the files: +~13.4% thickness, max camber y ≈ 0.0953 (chord normalised to 1). + +## Not included + +`Pointwise-Openfoam-toolchain/.../cp_AOA_10.dat` was **excluded**: it is a generic +demo LEI profile from the Pointwise→OpenFOAM toolchain (~12.4% thickness, +`tube_size=0.08, c_x=0.15, c_y=0.08`), **not** the V3 section. diff --git a/data/TUDELFT_V3_KITE/cpx_distributions/cf_AOA_2.dat b/data/TUDELFT_V3_KITE/cpx_distributions/cf_AOA_2.dat new file mode 100644 index 0000000..2d874b9 --- /dev/null +++ b/data/TUDELFT_V3_KITE/cpx_distributions/cf_AOA_2.dat @@ -0,0 +1,78 @@ +# x y Cf +0.04276388755929555 0.06356556861711357 -5.3685492635529e-05 +0.03309840970977389 0.04825624850833229 -3.582704828999522e-08 +0.06326602268098154 0.03073779662338421 6.167786129151704e-05 +0.07384327410599092 0.01793640188203612 -0.0003192609764164146 +0.07660775307689469 0.01104437306879618 -0.000228764133061211 +0.07819996384396966 4.127780662861551e-05 -2.991057168005374e-06 +0.07202763627549755 -0.02108505542368243 -0.0004650481933519364 +0.06030740026776515 -0.03284898406007216 -0.000575092613061699 +0.05197415418742954 -0.03691972333053082 -0.0009533577132157866 +0.04841011551193194 -0.03797539582415126 -0.0012756171634708763 +0.04106259222822374 -0.03905070100038476 -0.002640959490398662 +0.0392041678823387 -0.03909985971367359 -0.002617383818930308 +0.03734550757143083 -0.03906061142774211 -0.0019512487320989223 +0.03549081466558211 -0.03893305307143092 -0.0004661649781977114 +0.02999235773064849 -0.03802447036198644 0.005671899478682422 +0.02819522910180473 -0.03754856560901278 0.007255265503267772 +0.02642274895181671 -0.03698778874565389 0.008437998523398466 +0.02467892578294952 -0.03634339841365394 0.009272998170146079 +0.02296771065963648 -0.03561683055468438 0.009818887434565653 +0.0212929661235993 -0.034809744871696 0.010121505031131855 +0.01965847119772771 -0.03392397792845034 0.010215736624071617 +0.01652493718577972 -0.03192453294918682 0.00988329670009842 +0.01503298224681096 -0.03081537691891334 0.009500925488249457 +0.01221552866592811 -0.02839075860041346 0.008406802228970986 +0.009641067793166569 -0.0257095339280482 0.00700174990074151 +0.0073328241508335 -0.02279597455813496 0.005439446132599986 +0.0004212849451338721 -0.0057241591407998 -9.821423814390917e-05 +0.0001929063854338941 -0.00387916574954884 -0.0001677770129790615 +3.715759961701779e-07 -0.000167057565479412 8.866677575636765e-06 +0.0003737770984357588 0.005393438845063324 0.0009648191807615032 +0.00106093352703043 0.009046469258067052 0.001906286770070402 +0.002091866108723892 0.01261774277269358 0.002862841031091262 +0.002733467311930074 0.01436259362075018 0.003226329655069884 +0.003457264975454637 0.01607498180921493 0.003373864267305056 +0.005118832334056953 0.01943119745211363 0.003244479859277398 +0.01551627563955031 0.03736815990984928 0.00640642847178748 +0.01999837151218267 0.04343539867217488 0.00746451057835034 +0.02363912034760315 0.04776628085227682 0.008134354582056486 +0.03738856887789012 0.0606405766812684 0.009765309341371863 +0.04832587972692123 0.06818926940941658 0.0116451778915289 +0.05399675986092924 0.07144915477192883 0.012011259579708933 +0.06072447947721454 0.07485584093822345 0.012147363864742813 +0.07472263851285585 0.08065345333266476 0.011970806903965334 +0.08887221045490147 0.08513218823611196 0.011442401741641892 +0.1793062895712234 0.09530077013388079 0.006856086486232446 +0.2117339898587441 0.09495067176705572 0.006628380949299338 +0.2581032333418144 0.09215324744689607 0.005192207194632046 +0.2996809993542806 0.08811305015450889 0.004457266541411632 +0.3567078053717327 0.08168243973732783 0.00394706807980882 +0.4309247818702319 0.07270373419804112 0.003581264429982738 +0.859748396615083 0.01806363700433209 0.002415898765630274 +0.990135014604125 0.00127095875812223 0.0018193027298150943 +0.9980660976826226 0.000249320580680477 0.00202920416730058 +0.9995004786446735 6.447374315074194e-05 0.002407312125848668 +0.9998066004065816 2.499749945161417e-05 0.002663732027943266 +1.0 0.0 0.003108088175078052 +1.000175570344442 -0.0003873748714666402 -0.0005394350261954327 +0.9999361185511577 -0.0004959023699215505 0.007227548955785272 +0.9997426931776413 -0.0004710692940610273 0.006070740947186466 +0.9992898615857166 -0.0004127908100313096 0.00523084111183753 +0.9984027748023451 -0.0002985631337174143 0.004596763351448572 +0.9969850747884935 -0.0001159634273836383 0.004125445965553146 +0.9925102286323966 0.0004604996516486448 0.003608634550155278 +0.9828366693570219 0.001706780446830773 0.003290951256256768 +0.8007931164398834 0.02514688172946307 0.0015438641554223705 +0.6227248105276376 0.04799391426786268 -0.000934765929254101 +0.5467873640304859 0.05766507732352074 -0.0017082583043725305 +0.4557607131227709 0.06912084908337753 -0.002057381475396368 +0.3439219432772413 0.08267370547347265 -0.001909630199411214 +0.2878908640215147 0.0888395395812468 -0.0016403223905841638 +0.2472404119045495 0.0925291412707685 -0.0012439644700339358 +0.2284148315498924 0.09375889647752622 -0.0008998106920897766 +0.1938680582880188 0.09475868486167134 6.862163771827565e-05 +0.1565453765113058 0.0943433883278578 3.083217355968188e-05 +0.1049056581150844 0.0884277941269339 0.0003608467874276816 +0.0596424493908252 0.07383844242791567 -7.284237499479304e-05 +0.04571639419151958 0.06586873418008751 -7.147357452011414e-05 diff --git a/data/TUDELFT_V3_KITE/cpx_distributions/cf_AOA_6.dat b/data/TUDELFT_V3_KITE/cpx_distributions/cf_AOA_6.dat new file mode 100755 index 0000000..a415d17 --- /dev/null +++ b/data/TUDELFT_V3_KITE/cpx_distributions/cf_AOA_6.dat @@ -0,0 +1,103 @@ +# x y Cf +0.04276388755929555 0.06356556861711357 -0.0001879585975700373 +0.03309840970977389 0.04825624850833229 -5.458507980308168e-07 +0.04467938624112796 0.03869402682902678 0.0002106766326490412 +0.06326602268098154 0.03073779662338421 0.00010683943074351959 +0.06739054964049444 0.0269898872199461 -5.94499259365639e-05 +0.07295141860002202 0.01956758285390561 -0.000747380127932433 +0.07465658066274587 0.01626466884529217 -0.000804869041349509 +0.07660775307689469 0.01104437306879618 -0.0005439448493615928 +0.07819996384396966 4.127780662861551e-05 -7.537638609578638e-06 +0.07606735331573551 -0.0127367285437733 -0.0003803835335502698 +0.0698766379503123 -0.02411656177727725 5.957956843802754e-05 +0.06744746907549139 -0.0269300909630248 -6.499428398172114e-05 +0.0647621273625294 -0.02950025730032107 -0.0005852397077900648 +0.0618448413245353 -0.03180378944934589 -0.0014060038402179437 +0.06030740026776515 -0.03284898406007216 -0.001682074499723797 +0.05872201224623478 -0.03381991329602371 -0.0016918762225057683 +0.05709225787431717 -0.03471437303988962 -0.0013994803680989652 +0.05371451517243328 -0.03626603972395559 -0.000359979333924828 +0.05197415418742954 -0.03691972333053082 -0.00011495450593594156 +0.05020468513322063 -0.0374899309873329 -0.000475260545252736 +0.04841011551193194 -0.03797539582415126 -0.0011372551904624323 +0.04659450074871872 -0.03837502119181638 -0.0013395688525546072 +0.04476194045990555 -0.03868788115484954 -0.0008744136341935016 +0.04291657957939413 -0.03891327036966102 9.44628992832468e-05 +0.03734550757143083 -0.03906061142774211 0.004816902116601814 +0.03549081466558211 -0.03893305307143092 0.00608226725442228 +0.03364428007162458 -0.03871749253601962 0.007029206136024282 +0.03181007738878976 -0.03841441105310851 0.007703113708221444 +0.02819522910180473 -0.03754856560901278 0.008392342601560906 +0.02642274895181671 -0.03698778874565389 0.008463133797438408 +0.02296771065963648 -0.03561683055468438 0.008153228574274977 +0.0212929661235993 -0.034809744871696 0.007803711062963378 +0.01806792542873962 -0.03296152056688918 0.006792102588333668 +0.0135954264885147 -0.02963656804659672 0.004726720235922244 +0.004414604365619548 -0.01804807451598938 -0.0008341513405588942 +0.002201042597788599 -0.01293351709306329 -0.0017822205566014335 +0.001139618108146739 -0.009371187244292476 -0.0018498189272529702 +0.0007370940576948314 -0.007556212895409316 -0.0017046704323901173 +0.0001929063854338941 -0.00387916574954884 -0.0010666383323894113 +3.662379381053231e-05 0.001691663021906976 0.0006406888791772092 +0.0003737770984357588 0.005393438845063324 0.00210616838314834 +0.00106093352703043 0.009046469258067052 0.003601831254573682 +0.002091866108723892 0.01261774277269358 0.004786120411857886 +0.002733467311930074 0.01436259362075018 0.00505463342148768 +0.003457264975454637 0.01607498180921493 0.00486538638803568 +0.004261644904234816 0.01775102853981628 0.00428531391161938 +0.005118832334056953 0.01943119745211363 0.004020012931537072 +0.007736073021854499 0.02444803701163283 0.005587124190501672 +0.01144677152350567 0.0310164207846188 0.007294117885428082 +0.02118547842718027 0.04490114709827182 0.011234697506728442 +0.02619677866261684 0.05053895293504266 0.012975279747399983 +0.03022079287608805 0.05451623977328293 0.01396098034110171 +0.03591200766144063 0.05946694354457675 0.01489134876295948 +0.04193582911527939 0.06400735561950383 0.01544564631552075 +0.04832587972692123 0.06818926940941658 0.01567609512254134 +0.05835478956095485 0.07370803105654668 0.015522115657526089 +0.07161579828052492 0.07949795292659076 0.014806722607193528 +0.1266732729257711 0.09241218332941833 0.010715983170303985 +0.1793062895712234 0.09530077013388079 0.007118694040138372 +0.2032346417643243 0.09514644933414897 0.00694971658395249 +0.2117339898587441 0.09495067176705572 0.006746221951628926 +0.2483205990229831 0.09294692974194078 0.005363551999315196 +0.2784114042826095 0.09028704743105777 0.00457644175844203 +0.3448969536063716 0.08306230186370865 0.003728681582367628 +0.4695323861056724 0.06790513097604357 0.003075773887487842 +0.9829114078197247 0.002201372895802811 0.0013510704629247409 +0.9925774208648203 0.000956364758547776 0.0014009684429290416 +0.9972419199369482 0.0003555045445397644 0.0016173392531597238 +0.998466960240747 0.0001976703981867997 0.0018179918899748826 +0.9993538313714398 8.3377965966741e-05 0.002157499247928278 +0.9998066004065816 2.499749945161417e-05 0.002622578724939286 +1.0 0.0 0.003238787035356226 +1.000175570344442 -0.0003873748714666402 -0.0006918343168879954 +0.9999361185511577 -0.0004959023699215505 0.00839085568823664 +0.999687684844556 -0.0004639930247993733 0.007152364057113668 +0.999127051009862 -0.000391829623832507 0.006354581004969732 +0.9978535823824828 -0.0002278312280225523 0.005627694100734124 +0.9961081405376435 -3.002163743061166e-06 0.005163856287339768 +0.9900661326324484 0.0007753784135630709 0.004588401666849688 +0.9775963350348024 0.002381866402965103 0.004222134720252326 +0.9160973581448664 0.01030344679663074 0.003731346418568748 +0.6931632727720849 0.03897495614094905 0.00279072966272386 +0.5978723947463842 0.05116649706761253 0.001947719684416495 +0.5208166631398357 0.06095334182358406 0.0006613558809871008 +0.3922624879189146 0.07693223386512749 -0.002536736281764828 +0.3322904223011696 0.08401082699279248 -0.003535543029570178 +0.2878908640215147 0.0888395395812468 -0.003634970950225882 +0.2570330318015585 0.09174362234289557 -0.003128121342685052 +0.237700137204438 0.09320459962989104 -0.002248277989597068 +0.2284148315498924 0.09375889647752622 -0.001515708017509105 +0.2106187027457731 0.09448229827809655 0.0002175146509524326 +0.2021120932873841 0.09466535260951009 0.0005338654385894822 +0.1781651575110249 0.09480029283794308 0.0003016745162148726 +0.1634966219696773 0.09459572113384887 -0.0001420950660125579 +0.1498463100440565 0.09399700027730548 -0.00026465443382224 +0.1433965052540251 0.09356118961842605 -0.00013273574782980165 +0.1200181280886662 0.09102301972975237 0.0012194013974886953 +0.1097221213345114 0.08934836918478271 0.0014867965630896767 +0.1003032010680039 0.08745876557389623 0.0015071913473745859 +0.08772421797878553 0.08431213223263931 0.0012033990065049374 +0.0596424493908252 0.07383844242791567 -0.0001087650154666008 +0.04571639419151958 0.06586873418008751 -0.000230212940186696 diff --git a/data/TUDELFT_V3_KITE/cpx_distributions/cf_AOA_8.dat b/data/TUDELFT_V3_KITE/cpx_distributions/cf_AOA_8.dat new file mode 100644 index 0000000..8b35b73 --- /dev/null +++ b/data/TUDELFT_V3_KITE/cpx_distributions/cf_AOA_8.dat @@ -0,0 +1,104 @@ +# x y Cf +0.04276388755929555 0.06356556861711357 -7.490305597749076e-05 +0.0332272197474035 0.0464048575148447 5.489757479981662e-06 +0.04832943775882318 0.03799302181036906 7.648519645172773e-05 +0.06326602268098154 0.03073779662338421 -0.0004285934373756168 +0.06739054964049444 0.0269898872199461 -0.00010477936600927875 +0.07465658066274587 0.01626466884529217 0.0008238438806884038 +0.07660775307689469 0.01104437306879618 0.0007641332622482074 +0.07779692838209128 0.005599704515728495 0.0003944887276465724 +0.07815773161604739 -0.001817316543405341 5.485397046001178e-06 +0.07299266376766034 -0.01949606863865545 0.0003637595759240324 +0.06869550509015264 -0.02555220766066108 -0.0002755839837928688 +0.0647621273625294 -0.02950025730032107 -0.000300275706798375 +0.06030740026776515 -0.03284898406007216 -0.001077784609359678 +0.05872201224623478 -0.03381991329602371 -0.0011923736195542543 +0.05709225787431717 -0.03471437303988962 -0.0009765999835591377 +0.05371451517243328 -0.03626603972395559 -0.00017016364552131216 +0.05197415418742954 -0.03691972333053082 -0.000316934051102485 +0.05020468513322063 -0.0374899309873329 -0.0007186906753935714 +0.04841011551193194 -0.03797539582415126 -0.0008223291733746979 +0.04659450074871872 -0.03837502119181638 -0.0003321541027406026 +0.04476194045990555 -0.03868788115484954 0.000708909299360625 +0.04106259222822374 -0.03905070100038476 0.003411477405885782 +0.0392041678823387 -0.03909985971367359 0.004562676628395266 +0.03734550757143083 -0.03906061142774211 0.005450530699843342 +0.03549081466558211 -0.03893305307143092 0.006097005211431964 +0.03181007738878976 -0.03841441105310851 0.006796980228486176 +0.02999235773064849 -0.03802447036198644 0.00689567806772039 +0.02642274895181671 -0.03698778874565389 0.006668905710799834 +0.02467892578294952 -0.03634339841365394 0.006369310395537206 +0.0212929661235993 -0.034809744871696 0.005459364209591542 +0.01806792542873962 -0.03296152056688918 0.00422525671759978 +0.01503298224681096 -0.03081537691891334 0.002783959272415512 +0.006285174856966845 -0.02126020489420191 -0.002040075274130408 +0.004414604365619548 -0.01804807451598938 -0.002852651469250982 +0.002857522257900208 -0.01467282465727422 -0.003236282554861442 +0.001627968848605174 -0.01116497425581786 -0.003138053712873696 +0.001139618108146739 -0.009371187244292476 -0.002901560687168934 +0.0004212849451338721 -0.0057241591407998 -0.0020642851403826 +0.0001929063854338941 -0.00387916574954884 -0.001476146818952331 +3.715759961701779e-07 -0.000167057565479412 -1.644386489545432e-05 +0.0003737770984357588 0.005393438845063324 0.002603987753563134 +0.00106093352703043 0.009046469258067052 0.004308258634942198 +0.002091866108723892 0.01261774277269358 0.005527573864036678 +0.002733467311930074 0.01436259362075018 0.005706970896045456 +0.003457264975454637 0.01607498180921493 0.005309936058614546 +0.004261644904234816 0.01775102853981628 0.004473939386915608 +0.005118832334056953 0.01943119745211363 0.004158086084503602 +0.008635223554445109 0.02610611957064 0.007385511866532802 +0.01242751415010598 0.03262757549417115 0.00978259046760362 +0.01770420762625004 0.04044102676627263 0.012222338373033381 +0.02363912034760315 0.04776628085227682 0.0143235871004763 +0.02885514542407597 0.05321522293646556 0.015645352546840394 +0.03445624798895486 0.05826760669966152 0.016566055170825496 +0.04193582911527939 0.06400735561950383 0.017172923889848403 +0.04832587972692123 0.06818926940941658 0.017252283211256866 +0.05611419408811015 0.07257157145324583 0.01696439047089495 +0.06587533521034071 0.07717347429902713 0.016298119454783315 +0.1266732729257711 0.09241218332941833 0.010882410759841941 +0.1793062895712234 0.09530077013388079 0.006945473027203948 +0.2032346417643243 0.09514644933414897 0.006722973839658798 +0.2117339898587441 0.09495067176705572 0.006496660844552578 +0.2581032333418144 0.09215324744689607 0.004671284441683954 +0.2889286523063009 0.08923439218084046 0.003906579283388574 +0.3567078053717327 0.08168243973732783 0.002987260139238476 +0.4565948443542798 0.06951968856030251 0.002289062476511266 +0.9694031190419137 0.003941412459140006 0.0009043310373034418 +0.9889687389773781 0.001421178244630541 0.0009258776436256234 +0.9964097252760367 0.0004627131567309691 0.0011074669809813652 +0.99820673288432 0.0002312005004618359 0.0013175655760859263 +0.9991910520319729 0.0001043590734837622 0.0016309343514974571 +0.999751597840673 3.209277336096525e-05 0.002140587170106048 +1.0 0.0 0.00296276751106508 +1.000175570344442 -0.0003873748714666402 -0.0007331902544534668 +0.9999361185511577 -0.0004959023699215505 0.008659718621007185 +0.9996297267405945 -0.000456535816824477 0.007355613923448616 +0.999039047920008 -0.0003804987101077882 0.00666339865975267 +0.9976974643155097 -0.0002077232855182258 0.00597884279573205 +0.9958589617720149 2.909655168354372e-05 0.005537713550330318 +0.9894974877848076 0.0008486392223196442 0.00499683301592507 +0.9763814569182623 0.002538361569031684 0.004630244339792308 +0.9076252478663309 0.01139458185905065 0.00387386270301527 +0.6469580977353171 0.04489502959157916 0.00241912394509005 +0.5338234109222841 0.05930804868788935 0.0013982687254335613 +0.468715778144382 0.06750438704698408 0.0003543146735410334 +0.4047377992866207 0.0754159191024797 -0.0009780189748020292 +0.3677534071070601 0.07987434616211128 -0.002030172351450874 +0.2986548580999721 0.08772078502814058 -0.004599280878719982 +0.2773623986528947 0.08988862416845661 -0.005113680899771088 +0.2670749342797846 0.09085981280457657 -0.005208437747751682 +0.2472404119045495 0.0925291412707685 -0.004988841818438576 +0.237700137204438 0.09320459962989104 -0.004674488674257146 +0.2193869169365694 0.09418446720371103 -0.003633045552383154 +0.2106187027457731 0.09448229827809655 -0.002856229566245596 +0.2021120932873841 0.09466535260951009 -0.0018243984739640291 +0.18588626433171 0.09479362983589781 0.0004778349305169992 +0.1781651575110249 0.09480029283794308 0.0008614761575344938 +0.1565453765113058 0.0943433883278578 0.000433684342034735 +0.1371926332586585 0.09304091952856827 0.0007608862155223732 +0.1049056581150844 0.0884277941269339 0.0005783583684360126 +0.08772421797878553 0.08431213223263931 0.0010117873864341426 +0.07686484046880157 0.08091124513785357 0.001067302452619815 +0.06477532213063705 0.07619793801987683 0.0008009974026476422 +0.04571639419151958 0.06586873418008751 3.101420874588672e-06 diff --git a/data/TUDELFT_V3_KITE/cpx_distributions/cp_AOA_2.dat b/data/TUDELFT_V3_KITE/cpx_distributions/cp_AOA_2.dat new file mode 100644 index 0000000..547e619 --- /dev/null +++ b/data/TUDELFT_V3_KITE/cpx_distributions/cp_AOA_2.dat @@ -0,0 +1,45 @@ +# x y Cp +0.04276388755929555 0.06356556861711357 -0.04348704157614704 +0.07295141860002202 0.01956758285390561 -0.05140991436041962 +0.07819996384396966 4.127780662861551e-05 -0.03197150713685026 +0.05197415418742954 -0.03691972333053082 -0.06706167387722287 +0.04476194045990555 -0.03868788115484954 -0.093378926915626 +0.02642274895181671 -0.03698778874565389 -0.3570219353414122 +3.715759961701779e-07 -0.000167057565479412 0.9276509910347256 +0.003457264975454637 0.01607498180921493 0.4005836069123416 +0.03445624798895486 0.05826760669966152 -1.2301611547758755 +0.05399675986092924 0.07144915477192883 -1.6426557894762874 +0.06587533521034071 0.07717347429902713 -1.7332221980026814 +0.07799552636294979 0.08179901473482278 -1.754837005226387 +0.09287072676787013 0.0861933468529139 -1.7249851475023648 +0.1159231761269345 0.09090247468259746 -1.6181096392589778 +0.1870216529466714 0.09529101859089956 -1.1515796372398734 +0.2204942894351738 0.09463860823428381 -1.0426813364294585 +0.2996809993542806 0.08811305015450889 -0.7066174109381166 +0.3332789074473506 0.08439957244416332 -0.6088681071632966 +0.3687000900536647 0.08026370487218884 -0.5287719422111052 +0.4056386418146471 0.07580832798897223 -0.4621736253348554 +0.4565948443542798 0.06951968856030251 -0.3890242262205662 +0.5603789570099812 0.0564429374001441 -0.2790317212774662 +0.6709889816587014 0.04232145397591842 -0.19137133392787326 +0.8985775863007591 0.0130638909219138 -0.036892685638763 +0.9944657367345657 0.000713131800744486 0.0471956570293942 +1.000043909301717 -9.851122780894417e-06 0.04151915164349936 +1.000216009864362 -0.000279890227385287 0.04597681465716156 +0.9955965259210384 6.290342386802663e-05 0.0850038637137151 +0.9837355911010561 0.001590970086407752 0.1131781895823859 +0.9160973581448664 0.01030344679663074 0.1857913336595896 +0.8247212136041724 0.02206815331132695 0.2449938402700558 +0.7835335477130784 0.02736682776276132 0.2578459502494688 +0.7458784011853928 0.03220726993377351 0.2591066500658992 +0.704181333714036 0.03756145616169322 0.2463676060298896 +0.6588094047445807 0.04337787974553729 0.2145042006489572 +0.4817165482691285 0.06587661484209784 0.004772282753139308 +0.4300565639732108 0.07230837544542264 -0.04186437951123492 +0.3799297067584973 0.0784194374404138 -0.07216280351532364 +0.3322904223011696 0.08401082699279248 -0.08709921870404717 +0.2878908640215147 0.0888395395812468 -0.08863313732386507 +0.237700137204438 0.09320459962989104 -0.0728984865243349 +0.1938680582880188 0.09475868486167134 -0.03756026982500518 +0.1781651575110249 0.09480029283794308 -0.03241020524223182 +0.04571639419151958 0.06586873418008751 -0.0433295353537881 diff --git a/data/TUDELFT_V3_KITE/cpx_distributions/cp_AOA_6.dat b/data/TUDELFT_V3_KITE/cpx_distributions/cp_AOA_6.dat new file mode 100755 index 0000000..7da2c81 --- /dev/null +++ b/data/TUDELFT_V3_KITE/cpx_distributions/cp_AOA_6.dat @@ -0,0 +1,55 @@ +# x y Cp +0.04276388755929555 0.06356556861711357 0.2831765988277916 +0.06177764393266522 0.03185174700135322 0.280866185252911 +0.07295141860002202 0.01956758285390561 0.25963219479103 +0.07815773161604739 -0.001817316543405341 0.3226553243711208 +0.07606735331573551 -0.0127367285437733 0.3146385901820192 +0.06744746907549139 -0.0269300909630248 0.342684532314071 +0.05709225787431717 -0.03471437303988962 0.234853067095021 +0.05020468513322063 -0.0374899309873329 0.284736464587777 +0.03734550757143083 -0.03906061142774211 0.15553190031453823 +0.03364428007162458 -0.03871749253601962 0.16954883769738988 +0.004414604365619548 -0.01804807451598938 0.9863929885981783 +0.002091866108723892 0.01261774277269358 -0.572820273285277 +0.03160995824607752 0.05579211544351708 -2.432245537380402 +0.04505807310843256 0.06612426369258909 -2.774111357485588 +0.05199666670865696 0.07034321801435707 -2.844549106106646 +0.06072447947721454 0.07485584093822345 -2.867363498348572 +0.07161579828052492 0.07949795292659076 -2.829796764734134 +0.08887221045490147 0.08513218823611196 -2.690124986734862 +0.1870216529466714 0.09529101859089956 -1.6416426798621342 +0.2889286523063009 0.08923439218084046 -1.0436464025123768 +0.3218640927071186 0.08569020328035809 -0.9017119855459382 +0.3567078053717327 0.08168243973732783 -0.7850028154244542 +0.3931790397022614 0.07732341274896151 -0.6877927119270986 +0.4437200012730869 0.07112018838118041 -0.5809462948976902 +0.5085472107892827 0.06300526498795977 -0.4741340541213232 +0.5731865715040237 0.05481455085516451 -0.388873206174795 +0.7259539967148313 0.03527045550632674 -0.232643572741015 +0.9854751216984708 0.0018711616089445 -0.003723889307472904 +0.9964097252760367 0.0004627131567309691 0.002753800976306192 +1.000216009864362 -0.000279890227385287 -0.004826394769678074 +0.9943997089817618 0.0002170811127827793 0.04868571797704922 +0.9808969261659528 0.001956677923828914 0.09055972385002321 +0.9537275624931341 0.005456518791413356 0.13731493299292824 +0.9119523907158397 0.01083728805011287 0.19026388337497901 +0.6469580977353171 0.04489502959157916 0.4433304082027882 +0.5852500607119644 0.05277533327406693 0.4920978858473208 +0.5467873640304859 0.05766507732352074 0.5105806429851942 +0.5208166631398357 0.06095334182358406 0.5137794956559404 +0.4947454515238927 0.06424033472807196 0.5061160251107084 +0.4557607131227709 0.06912084908337753 0.4691184029843818 +0.4173409525953314 0.07387363386197163 0.4012507759782244 +0.3208625976738804 0.08530086466606873 0.17770486868504992 +0.2986548580999721 0.08772078502814058 0.14367991508003292 +0.2773623986528947 0.08988862416845661 0.12653828962832644 +0.2570330318015585 0.09174362234289557 0.12949031449777215 +0.237700137204438 0.09320459962989104 0.16011249221611834 +0.2106187027457731 0.09448229827809655 0.269821412704075 +0.1938680582880188 0.09475868486167134 0.3075080023769274 +0.1634966219696773 0.09459572113384887 0.3147298735128626 +0.1498463100440565 0.09399700027730548 0.305568189083699 +0.1255076224116574 0.09176672636466693 0.270229612624369 +0.1147579180611961 0.0902151563016211 0.2651942739621602 +0.07359886901176031 0.07974335793549313 0.297013291348983 +0.04571639419151958 0.06586873418008751 0.284032411033685 diff --git a/data/TUDELFT_V3_KITE/cpx_distributions/cp_AOA_8.dat b/data/TUDELFT_V3_KITE/cpx_distributions/cp_AOA_8.dat new file mode 100644 index 0000000..ce2f14c --- /dev/null +++ b/data/TUDELFT_V3_KITE/cpx_distributions/cp_AOA_8.dat @@ -0,0 +1,59 @@ +# x y Cp +0.04276388755929555 0.06356556861711357 0.369754792445869 +0.05363791167027467 0.03629681937611275 0.369512825076681 +0.06023799864411321 0.03289369258862966 0.3803300404656592 +0.07295141860002202 0.01956758285390561 0.4525002946292716 +0.07538949147001935 0.0145561611100228 0.4564693840993162 +0.07801937057148131 0.003753985959382214 0.446882922242024 +0.07780867321095557 -0.005517988661109592 0.4783581156255348 +0.06613535838326418 -0.02824710359527738 0.5311348004808492 +0.05709225787431717 -0.03471437303988962 0.484086890575362 +0.05197415418742954 -0.03691972333053082 0.508181593031596 +0.04106259222822374 -0.03905070100038476 0.42315148127032 +0.03734550757143083 -0.03906061142774211 0.428382483023469 +0.0135954264885147 -0.02963656804659672 0.9678991392350302 +0.009641067793166569 -0.0257095339280482 1.0009297902025698 +0.006285174856966845 -0.02126020489420191 0.9426534686983296 +0.00106093352703043 0.009046469258067052 -1.0429979768839335 +0.02885514542407597 0.05321522293646556 -2.97701516742313 +0.04040120622143255 0.06291072902875927 -3.321950362170818 +0.05399675986092924 0.07144915477192883 -3.429003607616636 +0.06587533521034071 0.07717347429902713 -3.376878702524202 +0.07799552636294979 0.08179901473482278 -3.254197144337922 +0.1870216529466714 0.09529101859089956 -1.8327874863459042 +0.2784114042826095 0.09028704743105777 -1.1996806915789198 +0.3106619665783404 0.08692971356968779 -1.0297320028124064 +0.3448969536063716 0.08306230186370865 -0.8915062484382064 +0.3931790397022614 0.07732341274896151 -0.7482650769295596 +0.4437200012730869 0.07112018838118041 -0.6372226271092232 +0.5085472107892827 0.06300526498795977 -0.5230590187758586 +0.5859002138042717 0.05319596110636245 -0.4121258594586978 +0.6824259822772941 0.04085560183950949 -0.298421444448391 +0.8093373719000714 0.02455167460498058 -0.17313674963583736 +0.9660562316502389 0.004372544645865283 -0.0386406821187513 +0.993768270458061 0.000802973252239986 -0.02128853044019792 +1.000216009864362 -0.000279890227385287 -0.03062803960527396 +0.9943997089817618 0.0002170811127827793 0.0244731662116314 +0.9818912176243451 0.001828584093814853 0.06971150300101164 +0.960286674485311 0.004611622190581761 0.11726533977474018 +0.9274984236403104 0.00883501341243748 0.1677274935281196 +0.8530499807859604 0.01842190075726221 0.2484797697022198 +0.6588094047445807 0.04337787974553729 0.4164065158335706 +0.5852500607119644 0.05277533327406693 0.4895377938887346 +0.5208166631398357 0.06095334182358406 0.571407956570911 +0.4173409525953314 0.07387363386197163 0.7448626251611238 +0.3922624879189146 0.07693223386512749 0.7709991898354575 +0.3799297067584973 0.0784194374404138 0.7726402699823532 +0.3677534071070601 0.07987434616211128 0.7634659759410414 +0.3557467275473164 0.08129361982160917 0.741382005954384 +0.3322904223011696 0.08401082699279248 0.654753629482031 +0.2570330318015585 0.09174362234289557 0.14088055807058458 +0.237700137204438 0.09320459962989104 0.05907810318650006 +0.2284148315498924 0.09375889647752622 0.04303112368943488 +0.2193869169365694 0.09418446720371103 0.04419401824806442 +0.2106187027457731 0.09448229827809655 0.06265088115711293 +0.1707025886246218 0.09474637369056492 0.2715407898968978 +0.1565453765113058 0.0943433883278578 0.2872016129793902 +0.09590926783756276 0.08644641392015195 0.2638217468843654 +0.0596424493908252 0.07383844242791567 0.350286588153118 +0.04571639419151958 0.06586873418008751 0.3689025452585844 diff --git a/docs/plotly_render.png b/docs/plotly_render.png new file mode 100644 index 0000000..404ef33 Binary files /dev/null and b/docs/plotly_render.png differ diff --git a/examples/TUDELFT_V3_KITE/fancy_interactive_plot.py b/examples/TUDELFT_V3_KITE/fancy_interactive_plot.py new file mode 100644 index 0000000..ab0b13a --- /dev/null +++ b/examples/TUDELFT_V3_KITE/fancy_interactive_plot.py @@ -0,0 +1,85 @@ +"""Interactive "fancy" 3D Plotly plot of the TU Delft V3 kite. + +Renders the physical inflatable structure of the LEI kite -- the leading-edge +tube, the chordwise strut tubes and the curved single-skin canopy -- reconstructed +from a raw Surfplan export, together with the VSM aerodynamic loading drawn as +chord-normal force vectors distributed over the chord by a measured Cp(x/c). + +Requirements (already handled by the data shipped in the repo): + - a Surfplan export directory: a ``.txt`` plus a ``profiles/`` directory, + - (optional) a directory of ``cp_AOA_.dat`` chordwise Cp distributions. + +SurfplanAdapter must be installed (a dependency of VSM) -- it converts the raw +export into the VSM geometry the plot needs (cached on first run). +""" + +from pathlib import Path + +from VSM.core.BodyAerodynamics import BodyAerodynamics +from VSM.plot_geometry_plotly import interactive_plot, save_high_res_render + +PROJECT_DIR = Path(__file__).resolve().parents[2] +DATA_DIR = PROJECT_DIR / "data" / "TUDELFT_V3_KITE" +RESULTS_DIR = PROJECT_DIR / "results" / "TUDELFT_V3_KITE" + +# Set True to also write a high-resolution PNG (results/.../plotly_render.png) +SAVE_HIGH_RES_PNG = True + + +def main(): + # --- inflow / operating point ------------------------------------------- + Umag = 10.0 # [m/s] + angle_of_attack = 8.0 # [deg] + side_slip = 0.0 # [deg] + + # --- build the aerodynamic model ---------------------------------------- + # The Breukels-regression CAD geometry needs no ML models, so it runs + # out of the box. Any other aero_geometry_*.yaml works too. + aero_geometry = ( + DATA_DIR / "CAD_derived_geometry" / "aero_geometry_CAD_breukels_regression.yaml" + ) + body_aero = BodyAerodynamics.instantiate( + n_panels=36, + file_path=aero_geometry, + spanwise_panel_distribution="uniform", + ) + + # --- data for the fancy plot -------------------------------------------- + # Raw Surfplan export (tubes + airfoil profiles) and the Cp database. + surfplan_dir = DATA_DIR / "Surfplan_export" + cp_distributions_dir = DATA_DIR / "cpx_distributions" + + # --- interactive fancy plot --------------------------------------------- + # Passing ``surfplan_dir`` switches on the inflatable tubes + curved canopy. + # Passing ``cp_distributions_dir`` distributes each panel's VSM force over the + # chord using the closest-AoA measured Cp(x/c). Omit either to fall back. + fig = interactive_plot( + body_aero, + vel=Umag, + angle_of_attack=angle_of_attack, + side_slip=side_slip, + title="TUDELFT_V3_KITE", + surfplan_dir=surfplan_dir, + cp_distributions_dir=cp_distributions_dir, + is_with_canopy=True, # curved canopy over the tubes (False -> tubes only) + is_with_tube_rings=False, # True -> also draw the tube construction rings + is_show=True, # open the interactive plot in a browser + # is_save=True, # uncomment to also write an interactive .html + # save_path=str(RESULTS_DIR / "fancy_plot.html"), + ) + + # --- optional: high-resolution PNG (matches the interactive view) -------- + if SAVE_HIGH_RES_PNG: + save_high_res_render( + fig, + RESULTS_DIR / "plotly_render.png", + base_size=2000, # square canvas -> undistorted; then cropped to the kite + scale=4, + show_legend=False, + show_axes=False, + show_annotations=False, + ) + + +if __name__ == "__main__": + main() diff --git a/examples/TUDELFT_V3_KITE/tutorial.py b/examples/TUDELFT_V3_KITE/tutorial.py index f5460fd..edf75a0 100644 --- a/examples/TUDELFT_V3_KITE/tutorial.py +++ b/examples/TUDELFT_V3_KITE/tutorial.py @@ -49,7 +49,7 @@ def main(): PROJECT_DIR = Path(__file__).resolve().parents[2] ### 2. defining settings - n_panels = 50 + n_panels = 100 spanwise_panel_distribution = "uniform" solver_base_version = Solver(reference_point=np.array([0.0, 0.0, 0.0])) @@ -62,28 +62,30 @@ def main(): file_path=(cad_derived_geometry_dir / "aero_geometry_CAD_CFD_polars.yaml"), spanwise_panel_distribution=spanwise_panel_distribution, ) - body_aero_CAD_CFD_polars_with_bridles = BodyAerodynamics.instantiate( - n_panels=n_panels, - # file_path=(cad_derived_geometry_dir / "config_kite_CAD_CFD_polars.yaml"), - file_path=(cad_derived_geometry_dir / "config_kite_CAD_CFD_polars.yaml"), - spanwise_panel_distribution=spanwise_panel_distribution, - bridle_path=( - cad_derived_geometry_dir / "struc_geometry_manually_adjusted.yaml" - ), - ) - body_aero_CAD_neuralfoil = BodyAerodynamics.instantiate( - n_panels=n_panels, - file_path=(cad_derived_geometry_dir / "aero_geometry_CAD_neuralfoil.yaml"), - spanwise_panel_distribution=spanwise_panel_distribution, - ) - body_aero_masure_regression = BodyAerodynamics.instantiate( - n_panels=n_panels, - file_path=( - cad_derived_geometry_dir / "aero_geometry_CAD_masure_regression.yaml" - ), - ml_models_dir=(Path(PROJECT_DIR) / "data" / "ml_models"), - spanwise_panel_distribution=spanwise_panel_distribution, - ) + # body_aero_CAD_CFD_polars_with_bridles = BodyAerodynamics.instantiate( + # n_panels=n_panels, + # file_path=( + # cad_derived_geometry_dir + # / "config_kite_CAD_CFD_polars_converged_fitted_pchip.yaml" + # ), + # spanwise_panel_distribution=spanwise_panel_distribution, + # bridle_path=( + # cad_derived_geometry_dir / "struc_geometry_manually_adjusted.yaml" + # ), + # ) + # body_aero_CAD_neuralfoil = BodyAerodynamics.instantiate( + # n_panels=n_panels, + # file_path=(cad_derived_geometry_dir / "aero_geometry_CAD_neuralfoil.yaml"), + # spanwise_panel_distribution=spanwise_panel_distribution, + # ) + # body_aero_masure_regression = BodyAerodynamics.instantiate( + # n_panels=n_panels, + # file_path=( + # cad_derived_geometry_dir / "aero_geometry_CAD_masure_regression.yaml" + # ), + # ml_models_dir=(Path(PROJECT_DIR) / "data" / "ml_models"), + # spanwise_panel_distribution=spanwise_panel_distribution, + # ) # Step 2: Set inflow conditions for each aerodynamic object """ @@ -97,40 +99,40 @@ def main(): body_aero_CAD_CFD_polars.va_initialize( Umag, angle_of_attack, side_slip, body_rates=yaw_rate ) - body_aero_CAD_CFD_polars_with_bridles.va_initialize( - Umag, - angle_of_attack, - side_slip, - body_rates=yaw_rate, - body_axis=np.array([0, 0, 1]), - ) - body_aero_CAD_neuralfoil.va_initialize( - Umag, - angle_of_attack, - side_slip, - body_rates=yaw_rate, - body_axis=np.array([0, 0, 1]), - ) - body_aero_masure_regression.va_initialize( - Umag, - angle_of_attack, - side_slip, - body_rates=yaw_rate, - body_axis=np.array([0, 0, 1]), - ) + # body_aero_CAD_CFD_polars_with_bridles.va_initialize( + # Umag, + # angle_of_attack, + # side_slip, + # body_rates=yaw_rate, + # body_axis=np.array([0, 0, 1]), + # ) + # body_aero_CAD_neuralfoil.va_initialize( + # Umag, + # angle_of_attack, + # side_slip, + # body_rates=yaw_rate, + # body_axis=np.array([0, 0, 1]), + # ) + # body_aero_masure_regression.va_initialize( + # Umag, + # angle_of_attack, + # side_slip, + # body_rates=yaw_rate, + # body_axis=np.array([0, 0, 1]), + # ) # Step 3: Plot the kite geometry using Matplotlib """ Visualize the panel mesh, control points, and aerodynamic centers for the selected BodyAerodynamics object. """ - plot_geometry( - body_aero_CAD_CFD_polars, - title="TUDELFT_V3_KITE", - data_type=".pdf", - save_path=".", - is_save=False, - is_show=True, - ) + # plot_geometry( + # body_aero_CAD_CFD_polars, + # title="TUDELFT_V3_KITE", + # data_type=".pdf", + # save_path=".", + # is_save=False, + # is_show=True, + # ) # Step 4: Create an interactive plot using Plotly """ @@ -138,7 +140,7 @@ def main(): This allows for interactive exploration of the geometry and panel arrangement. """ interactive_plot( - body_aero_CAD_CFD_polars_with_bridles, + body_aero_CAD_CFD_polars, vel=Umag, angle_of_attack=angle_of_attack, side_slip=side_slip, @@ -154,105 +156,128 @@ def main(): # / "interactive_plot.html", ) - # Step 5: Plot polar curves for different angles of attack and side slip angles, and save results + # Step 4b: "Fancy" plot -- render the inflatable leading-edge and strut tubes + # and the curved single-skin canopy from a raw Surfplan export. """ - Compare the aerodynamic performance of different models by plotting lift, drag, and side force coefficients - as a function of angle of attack (alpha sweep) and side slip (beta sweep). - Literature/CFD data can be included for validation. + Passing ``surfplan_dir`` (a directory holding the Surfplan '.txt' export + and its 'profiles/' directory) switches on the physically-detailed plot. The + export is converted with SurfplanAdapter (cached under the export directory), + and the tube diameters + airfoil top-surfaces are drawn on the wing. The plain + plot above is unaffected when ``surfplan_dir`` is omitted. """ - save_folder = Path(PROJECT_DIR) / "results" / "TUDELFT_V3_KITE" + # interactive_plot( + # body_aero_CAD_CFD_polars, + # vel=Umag, + # angle_of_attack=angle_of_attack, + # side_slip=side_slip, + # title="TUDELFT_V3_KITE (inflatable tubes + canopy)", + # surfplan_dir=Path(PROJECT_DIR) + # / "data" + # / "TUDELFT_V3_KITE" + # / "Surfplan_export", + # is_with_canopy=True, # curved canopy over the tubes; False -> tubes only + # is_with_tube_rings=False, # True -> also show tube construction rings + # ) - # Step 5a: Plot alpha sweep (angle of attack) - path_cfd_lebesque_alpha = ( - Path(PROJECT_DIR) - / "data" - / "TUDELFT_V3_KITE" - / "3D_polars_literature" - / "CFD_RANS_Rey_10e5_Poland2025_alpha_sweep_beta_0.csv" - ) - path_wt_data = ( - Path(PROJECT_DIR) - / "data" - / "TUDELFT_V3_KITE" - / "3D_polars_literature" - / "V3_CL_CD_CS_alpha_sweep_for_beta_0_WindTunnel_Poland_2025_Rey_560e4.csv" - ) - plot_polars( - solver_list=[ - solver_base_version, - solver_base_version, - solver_base_version, - solver_base_version, - ], - body_aero_list=[ - body_aero_CAD_CFD_polars, - body_aero_CAD_CFD_polars_with_bridles, - body_aero_CAD_neuralfoil, - body_aero_masure_regression, - ], - label_list=[ - "VSM CAD CFD Polars", - "VSM CAD CFD Polars with Bridles", - "VSM CAD NeuralFoil", - "VSM CAD Masure Regression", - "CFD_RANS_Rey_10e5_Poland2025_alpha_sweep_beta_0", - "Wind Tunnel Data Poland 2025", - ], - literature_path_list=[path_cfd_lebesque_alpha, path_wt_data], - angle_range=[0, 5, 8, 10, 12, 15, 20, 25], - angle_type="angle_of_attack", - angle_of_attack=0, - side_slip=0, - yaw_rate=0, - Umag=Umag, - title="alphasweep", - data_type=".pdf", - save_path=Path(save_folder), - is_save=True, - is_show=False, - ) + # # Step 5: Plot polar curves for different angles of attack and side slip angles, and save results + # """ + # Compare the aerodynamic performance of different models by plotting lift, drag, and side force coefficients + # as a function of angle of attack (alpha sweep) and side slip (beta sweep). + # Literature/CFD data can be included for validation. + # """ + # save_folder = Path(PROJECT_DIR) / "results" / "TUDELFT_V3_KITE" - # Step 5b: Plot beta sweep (side slip) - path_cfd_lebesque_beta = ( - Path(PROJECT_DIR) - / "data" - / "TUDELFT_V3_KITE" - / "3D_polars_literature" - / "CFD_RANS_Rey_10e5_Poland2025_beta_sweep_alpha_13_02.csv" - ) - plot_polars( - solver_list=[ - solver_base_version, - solver_base_version, - solver_base_version, - solver_base_version, - ], - body_aero_list=[ - body_aero_CAD_CFD_polars, - body_aero_CAD_CFD_polars_with_bridles, - body_aero_CAD_neuralfoil, - body_aero_masure_regression, - ], - label_list=[ - "VSM CAD CFD Polars", - "VSM CAD CFD Polars with Bridles", - "VSM CAD NeuralFoil", - "VSM CAD Masure Regression", - "CFD_RANS_Rey_10e5_Poland2025_beta_sweep_alpha_13_02", - ], - literature_path_list=[path_cfd_lebesque_beta], - angle_range=[0, 3, 6, 9, 12], - angle_type="side_slip", - angle_of_attack=6.8, - side_slip=0, - yaw_rate=0, - Umag=3.15, - title="betasweep", - data_type=".pdf", - save_path=Path(save_folder), - is_save=True, - is_show=True, - ) + # # Step 5a: Plot alpha sweep (angle of attack) + # path_cfd_lebesque_alpha = ( + # Path(PROJECT_DIR) + # / "data" + # / "TUDELFT_V3_KITE" + # / "3D_polars_literature" + # / "CFD_RANS_Rey_10e5_Poland2025_alpha_sweep_beta_0.csv" + # ) + # path_wt_data = ( + # Path(PROJECT_DIR) + # / "data" + # / "TUDELFT_V3_KITE" + # / "3D_polars_literature" + # / "windtunnel_alpha_sweep_beta_00_0_Poland_2025_Rey_5e5.csv" + # ) + # plot_polars( + # solver_list=[ + # solver_base_version, + # solver_base_version, + # solver_base_version, + # solver_base_version, + # ], + # body_aero_list=[ + # body_aero_CAD_CFD_polars, + # body_aero_CAD_CFD_polars_with_bridles, + # body_aero_CAD_neuralfoil, + # body_aero_masure_regression, + # ], + # label_list=[ + # "VSM CAD CFD Polars", + # "VSM CAD CFD Polars with Bridles", + # "VSM CAD NeuralFoil", + # "VSM CAD Masure Regression", + # "CFD_RANS_Rey_10e5_Poland2025_alpha_sweep_beta_0", + # "Wind Tunnel Data Poland 2025", + # ], + # literature_path_list=[path_cfd_lebesque_alpha, path_wt_data], + # angle_range=[0, 5, 8, 10, 12, 15, 20, 25], + # angle_type="angle_of_attack", + # angle_of_attack=0, + # side_slip=0, + # yaw_rate=0, + # Umag=Umag, + # title="alphasweep", + # data_type=".pdf", + # save_path=Path(save_folder), + # is_save=True, + # is_show=False, + # ) + + # # Step 5b: Plot beta sweep (side slip) + # path_cfd_lebesque_beta = ( + # Path(PROJECT_DIR) + # / "data" + # / "TUDELFT_V3_KITE" + # / "3D_polars_literature" + # / "CFD_RANS_Re1e6_beta_sweep_alpha_13_Vire2022_CorrectedByPoland2025.csv" + # ) + # plot_polars( + # solver_list=[ + # solver_base_version, + # solver_base_version, + # solver_base_version, + # solver_base_version, + # ], + # body_aero_list=[ + # body_aero_CAD_CFD_polars, + # body_aero_CAD_CFD_polars_with_bridles, + # body_aero_CAD_neuralfoil, + # body_aero_masure_regression, + # ], + # label_list=[ + # "VSM CAD CFD Polars", + # "VSM CAD CFD Polars with Bridles", + # "VSM CAD NeuralFoil", + # "VSM CAD Masure Regression", + # "CFD_RANS_Re1e6_beta_sweep_alpha_13_Vire2022_CorrectedByPoland2025", + # ], + # literature_path_list=[path_cfd_lebesque_beta], + # angle_range=[0, 3, 6, 9, 12], + # angle_type="side_slip", + # angle_of_attack=6.8, + # side_slip=0, + # yaw_rate=0, + # Umag=3.15, + # title="betasweep", + # data_type=".pdf", + # save_path=Path(save_folder), + # is_save=True, + # is_show=True, + # ) if __name__ == "__main__": diff --git a/pyproject.toml b/pyproject.toml index 0f938d5..e7eea64 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,7 @@ dependencies = [ "numba", "screeninfo", "odfpy", + "SurfplanAdapter", ] [project.optional-dependencies] dev = [ diff --git a/src/VSM/core/Solver.py b/src/VSM/core/Solver.py index 96d3766..bb03468 100644 --- a/src/VSM/core/Solver.py +++ b/src/VSM/core/Solver.py @@ -6,6 +6,26 @@ from VSM.core import panel_kernels + +def _table_symbols(n_panels: int, alpha_tables) -> dict: + """Symbolic ``(n_panels, n_alpha)`` polar tables for ``table_inputs``.""" + import casadi as ca + + n_alpha = int(np.asarray(alpha_tables[0]).size) + return {name: ca.MX.sym(f"{name}_table", n_panels, n_alpha) for name in ("cl", "cd", "cm")} + + +def _with_table_inputs(inputs: list, input_names: list, table_syms): + """Append the table inputs (if any) and default them to NaN, so a call + that omits them (CasADi fills omitted inputs with 0 by default) gives NaN + rather than a silently zero polar.""" + if table_syms is None: + return inputs, input_names, {} + inputs = inputs + [table_syms[k] for k in ("cl", "cd", "cm")] + input_names = input_names + ["cl_table", "cd_table", "cm_table"] + defaults = [0.0] * (len(inputs) - 3) + [float("nan")] * 3 + return inputs, input_names, {"default_in": defaults} + class Solver: """Solver for aerodynamic circulation distribution and force computation. @@ -203,6 +223,7 @@ def __init__( self._newton_reject_ratio = 0.5 self._casadi_newton_cache: dict = {} self._casadi_newton_loop_cache: dict = {} + self._casadi_table_input_cache: dict = {} # polar tables as inputs #: Diagnostic: did the last casadi_newton solve fall back to base? self.last_fallback = False #: Diagnostic: residual evaluations used by the last casadi_newton solve. @@ -1030,12 +1051,91 @@ def _casadi_newton_key(self, alpha_tables, cl_tables, cd_tables, cm_tables) -> t digest.hexdigest(), ) - def _casadi_polar_lookups(self, alpha_tables, cl_tables, cd_tables=None, cm_tables=None): + def _casadi_table_input_key(self, alpha_tables) -> tuple: + """Cache key of the functions that take the polar tables as INPUTS + (``table_inputs=True``): the graph depends on the panel count, the + shared alpha grid, the interpolation and the regularization settings, + not on the table values, so one function serves every shape whose + blended polars change but keep the grid.""" + self._check_table_inputs_supported(alpha_tables) + grid = np.ascontiguousarray(alpha_tables[0], dtype=float) + return ( + "table_inputs", + self.n_panels, + bool(self.is_with_artificial_viscosity), + float(self.artificial_viscosity_factor), + self.polar_interpolation, + hashlib.blake2b(grid.tobytes(), digest_size=16).hexdigest(), + ) + + def _check_table_inputs_supported(self, alpha_tables) -> None: + if self.polar_interpolation != "linear": + # A parametric cubic spline refits its coefficients on every call + # (~0.1 s per evaluation), which defeats the purpose. + raise ValueError( + "Polar tables as CasADi inputs need polar_interpolation='linear' " + f"(got {self.polar_interpolation!r})." + ) + if not all( + table.shape == alpha_tables[0].shape and np.array_equal(table, alpha_tables[0]) + for table in alpha_tables[1:] + ): + raise ValueError( + "Polar tables as CasADi inputs need every panel polar on one " + "shared alpha grid." + ) + + def casadi_polar_table_inputs(self) -> dict: + """The current panels' polar tables in the layout of the ``table_inputs`` + CasADi functions (:meth:`_casadi_newton_function` and + :meth:`casadi_panel_force_function` with ``table_inputs=True``): + ``{"cl_table", "cd_table", "cm_table"}``, each ``(n_panels, n_alpha)``, + row ``i`` = panel ``i``'s coefficients on the shared alpha grid + (``panel.panel_polar_data[:, 0]``, radians). + + Pass them on every call, next to the geometry: a deformed shape blends + its section polars with new weights, so the values change while the + grid, and with it the function, stays the same. + """ + alpha_tables, cl_tables, cd_tables, cm_tables = self._polar_tables() + self._check_table_inputs_supported(alpha_tables) + return { + "cl_table": np.vstack(cl_tables), + "cd_table": np.vstack(cd_tables), + "cm_table": np.vstack(cm_tables), + } + + def casadi_stall_angle_inputs(self) -> dict: + """``{"stall_angles", "stall_angles_neg"}`` [rad] of the current panel + polars, the gate inputs of the residual function as + :meth:`gamma_loop_casadi_newton` passes them (plus / minus infinity + without artificial viscosity). They are derived from the tables, so a + caller passing new tables passes these new values with them.""" + if self.is_with_artificial_viscosity: + return { + "stall_angles": self._panel_stall_angles(), + "stall_angles_neg": self._panel_negative_stall_angles(), + } + return { + "stall_angles": np.full(self.n_panels, np.inf), + "stall_angles_neg": np.full(self.n_panels, -np.inf), + } + + def _casadi_polar_lookups( + self, alpha_tables, cl_tables, cd_tables=None, cm_tables=None, table_symbols=None + ): """Clamped CasADi lookups ``alpha (n) -> cl, cd, cm (n)`` of the per-panel polars, shared by :meth:`_build_casadi_newton_function` and :meth:`_build_casadi_panel_force_function` (see the former for the interpolation). Returns ``(cl_of, cd_of, cm_of, smooth)``, ``smooth`` True for "bspline" polars. + + ``table_symbols`` (``{"cl": MX, "cd": MX, "cm": MX}``, each + ``(n, n_alpha)``) makes the table VALUES symbolic inputs instead of + constants: one 1-D parametric linear interpolant on the shared grid, + mapped over the panels. It reproduces the constant table exactly + (same nodes, same clamping); only the shared-grid linear case is + supported (see :meth:`_check_table_inputs_supported`). """ import casadi as ca @@ -1059,6 +1159,33 @@ def _casadi_polar_lookups(self, alpha_tables, cl_tables, cd_tables=None, cm_tabl kind = self.polar_interpolation smooth = kind == "bspline" + if table_symbols is not None: + self._check_table_inputs_supported(alpha_tables) + grid = np.asarray(alpha_tables[0], dtype=float) + lo, hi = float(grid[0]), float(grid[-1]) + row_interpolant = ca.interpolant("polar_row", "linear", [grid], 1) + alpha_row = ca.MX.sym("alpha_row") + values_row = ca.MX.sym("values_row", grid.size) + # One panel's lookup; .map(n) evaluates it column-wise over panels. + row_lookup = ca.Function( + "polar_row_lookup", + [alpha_row, values_row], + [row_interpolant(ca.fmin(ca.fmax(alpha_row, lo), hi), values_row)], + ).map(n) + + def make_parametric_lookup(table): + def lookup(alpha_vec): + return row_lookup(alpha_vec.T, table.T).T + + return lookup + + return ( + make_parametric_lookup(table_symbols["cl"]), + make_parametric_lookup(table_symbols["cd"]), + make_parametric_lookup(table_symbols["cm"]), + smooth, + ) + def make_lookup(name, value_tables): """Clamped lookup ``alpha_vec (n) -> values (n)`` of one polar column.""" if shared_grid: @@ -1104,7 +1231,9 @@ def lookup(alpha_vec): cm_of = make_lookup("cm", cm_tables) return cl_of, cd_of, cm_of, smooth - def _build_casadi_newton_function(self, alpha_tables, cl_tables, cd_tables=None, cm_tables=None): + def _build_casadi_newton_function( + self, alpha_tables, cl_tables, cd_tables=None, cm_tables=None, table_inputs=False + ): """Build the CasADi function of the PER-PANEL section physics that :meth:`gamma_loop_casadi_newton` assembles its residual and exact Jacobian from. @@ -1154,12 +1283,18 @@ def _build_casadi_newton_function(self, alpha_tables, cl_tables, cd_tables=None, Jacobian), ``cl``, ``cd``, ``cm`` at ``alpha`` and their corner- averaged counterparts ``cl_avg``, ``cd_avg``, ``cm_avg``. With "bspline" polars the averaged outputs equal the exact ones. + + ``table_inputs=True`` appends the inputs ``cl_table``, ``cd_table``, + ``cm_table`` (each ``(n, n_alpha)``, :meth:`casadi_polar_table_inputs`) + and takes the table values from them instead of embedding them; see + :meth:`_casadi_newton_function`. """ import casadi as ca n = self.n_panels + table_syms = _table_symbols(n, alpha_tables) if table_inputs else None cl_of, cd_of, cm_of, smooth = self._casadi_polar_lookups( - alpha_tables, cl_tables, cd_tables, cm_tables + alpha_tables, cl_tables, cd_tables, cm_tables, table_symbols=table_syms ) v_rel = ca.MX.sym("v_rel", n, 3) @@ -1244,9 +1379,12 @@ def averaged(lookup): seeds = [ca.DM(np.eye(3)[k][None, :].repeat(n, axis=0)) for k in range(3)] dh_dvrel = ca.horzcat(*[ca.jtimes(h_for_jacobian, v_rel, seed) for seed in seeds]) + inputs = [v_rel, x_airf, y_airf, z_airf, chord, width, stall_angles, stall_angles_neg, l_gamma] + input_names = ["v_rel", "x_airf", "y_airf", "z_airf", "chord", "width", "stall_angles", "stall_angles_neg", "l_gamma"] + inputs, input_names, options = _with_table_inputs(inputs, input_names, table_syms) return ca.Function( - "vsm_panel_residual", - [v_rel, x_airf, y_airf, z_airf, chord, width, stall_angles, stall_angles_neg, l_gamma], + "vsm_panel_residual_tables" if table_inputs else "vsm_panel_residual", + inputs, [ h, dh_dvrel, @@ -1262,11 +1400,29 @@ def averaged(lookup): averaged(cd_of), averaged(cm_of), ], - ["v_rel", "x_airf", "y_airf", "z_airf", "chord", "width", "stall_angles", "stall_angles_neg", "l_gamma"], + input_names, ["h", "dh_dvrel", "mu", "alpha", "umag", "gate", "h_avg", "cl", "cd", "cm", "cl_avg", "cd_avg", "cm_avg"], + options, ) - def _casadi_newton_function(self): + def _casadi_newton_function(self, table_inputs: bool = False): + """The per-panel residual function (:meth:`_build_casadi_newton_function`) + of the current panels, cached. + + ``table_inputs=False`` (default, what :meth:`gamma_loop_casadi_newton` + uses): the polar tables are constants of the graph, cached per table + set. ``table_inputs=True``: the tables are the extra inputs + ``cl_table, cd_table, cm_table``, cached per alpha grid, so a caller + that re-trims deformed shapes builds the function once and passes + :meth:`casadi_polar_table_inputs` (and :meth:`casadi_stall_angle_inputs`) + per shape. Same outputs to round-off; needs ``polar_interpolation= + "linear"`` and one shared alpha grid. Omitted table inputs default to + NaN, so a call that forgets them fails loudly instead of using zeros. + """ + if table_inputs: + return self._cached_table_input_function( + "newton", self._build_casadi_newton_function + ) alpha_tables, cl_tables, cd_tables, cm_tables = self._polar_tables() key = self._casadi_newton_key(alpha_tables, cl_tables, cd_tables, cm_tables) fn = self._casadi_newton_cache.get(key) @@ -1282,7 +1438,7 @@ def _casadi_newton_function(self): return fn def _build_casadi_panel_force_function( - self, alpha_tables, cl_tables, cd_tables=None, cm_tables=None + self, alpha_tables, cl_tables, cd_tables=None, cm_tables=None, table_inputs=False ): """CasADi function of the per-panel section forces, the force law of ``compute_results`` (``VSM.core.panel_kernels.panel_forces``) on the @@ -1291,8 +1447,9 @@ def _build_casadi_panel_force_function( import casadi as ca n = self.n_panels + table_syms = _table_symbols(n, alpha_tables) if table_inputs else None cl_of, cd_of, cm_of, _ = self._casadi_polar_lookups( - alpha_tables, cl_tables, cd_tables, cm_tables + alpha_tables, cl_tables, cd_tables, cm_tables, table_symbols=table_syms ) v_rel = ca.MX.sym("v_rel", n, 3) v_qc = ca.MX.sym("v_qc", n, 3) @@ -1310,15 +1467,21 @@ def _build_casadi_panel_force_function( alpha, umag, cl, cd, cm, chord, width, x_airf, y_airf, z_airf, rho, alpha_dir=alpha_dir, xp=ca, ) - return ca.Function( - "vsm_panel_forces", + inputs, input_names, options = _with_table_inputs( [v_rel, v_qc, x_airf, y_airf, z_airf, chord, width, rho], - [force, moment_local, alpha, alpha_dir, umag, cl, cd, cm], ["v_rel", "v_qc", "x_airf", "y_airf", "z_airf", "chord", "width", "rho"], + table_syms, + ) + return ca.Function( + "vsm_panel_forces_tables" if table_inputs else "vsm_panel_forces", + inputs, + [force, moment_local, alpha, alpha_dir, umag, cl, cd, cm], + input_names, ["force", "moment_local", "alpha", "alpha_dir", "umag", "cl", "cd", "cm"], + options, ) - def casadi_panel_force_function(self): + def casadi_panel_force_function(self, table_inputs: bool = False): """CasADi function of the per-panel section forces of the current panels (the ones of the last :meth:`solve`), cached per polar set like the ``casadi_newton`` residual function. @@ -1343,9 +1506,16 @@ def casadi_panel_force_function(self): vortex force (``panel_kernels.attached_trailed_forces``) and the viscous drag correction. Taking ``v_qc`` rather than the matrices keeps the graph O(n); the caller forms the dense product. + + ``table_inputs=True`` appends ``cl_table, cd_table, cm_table`` as + inputs (see :meth:`_casadi_newton_function`). """ import casadi # noqa: F401 (clear error when the optional dep is missing) + if table_inputs: + return self._cached_table_input_function( + "panel_forces", self._build_casadi_panel_force_function + ) alpha_tables, cl_tables, cd_tables, cm_tables = self._polar_tables() key = ("panel_forces",) + self._casadi_newton_key( alpha_tables, cl_tables, cd_tables, cm_tables @@ -1360,6 +1530,20 @@ def casadi_panel_force_function(self): self._casadi_newton_cache[key] = fn return fn + def _cached_table_input_function(self, kind: str, builder): + """Function of ``kind`` with the polar tables as inputs, built once + per (panel count, alpha grid, settings) and then reused for any + table values.""" + alpha_tables, cl_tables, cd_tables, cm_tables = self._polar_tables() + key = (kind,) + self._casadi_table_input_key(alpha_tables) + fn = self._casadi_table_input_cache.get(key) + if fn is None: + fn = builder(alpha_tables, cl_tables, cd_tables, cm_tables, table_inputs=True) + if len(self._casadi_table_input_cache) >= 32: + self._casadi_table_input_cache.pop(next(iter(self._casadi_table_input_cache))) + self._casadi_table_input_cache[key] = fn + return fn + def _casadi_newton_loop_function(self): """The residual function restricted to the six outputs the Newton loop reads (``h, dh_dvrel, mu, alpha, umag, gate``): the same graph, minus diff --git a/src/VSM/plot_geometry_plotly.py b/src/VSM/plot_geometry_plotly.py index 236b62b..0d99386 100644 --- a/src/VSM/plot_geometry_plotly.py +++ b/src/VSM/plot_geometry_plotly.py @@ -1,46 +1,62 @@ import os -from typing import List, Tuple, Dict, Any +from pathlib import Path +from typing import List, Tuple, Dict, Any, Optional import numpy as np import plotly.graph_objects as go from VSM.core.Solver import Solver import yaml -def add_panel_edges(fig: go.Figure, panel: Any, is_first: bool, is_last: bool) -> None: - """Add panel edges to the figure with different thicknesses.""" - leadinge_edge_line_color = "black" - leadinge_edge_line_width = 15 - # Leading edge - if is_first: - # Include 1-side edge - fig.add_trace( - go.Scatter3d( - x=[panel.LE_point_1[0], panel.TE_point_1[0]], - y=[panel.LE_point_1[1], panel.TE_point_1[1]], - z=[panel.LE_point_1[2], panel.TE_point_1[2]], - mode="lines", - line=dict( - color=leadinge_edge_line_color, width=leadinge_edge_line_width - ), - name="Leading Edge", - showlegend=False, +def add_panel_edges( + fig: go.Figure, + panel: Any, + is_first: bool, + is_last: bool, + is_thin_outline: bool = False, +) -> None: + """Add panel edges to the figure. + + By default the leading edge is drawn as a thick black line. With + ``is_thin_outline`` (used by the fancy plot, where the inflatable leading-edge + tube already marks the leading edge) all panel edges are thin grey outlines + and the thick leading-edge line is omitted. + """ + edge_color = "blue" if is_thin_outline else "black" + if is_thin_outline: + leadinge_edge_line_color = "blue" + leadinge_edge_line_width = 1.5 + else: + leadinge_edge_line_color = "black" + leadinge_edge_line_width = 15 + # Thick tip edges only make sense for the thick leading-edge style. + if is_first: + fig.add_trace( + go.Scatter3d( + x=[panel.LE_point_1[0], panel.TE_point_1[0]], + y=[panel.LE_point_1[1], panel.TE_point_1[1]], + z=[panel.LE_point_1[2], panel.TE_point_1[2]], + mode="lines", + line=dict( + color=leadinge_edge_line_color, width=leadinge_edge_line_width + ), + name="Leading Edge", + showlegend=False, + ) ) - ) - elif is_last: - # Include 1-side edge - fig.add_trace( - go.Scatter3d( - x=[panel.LE_point_2[0], panel.TE_point_2[0]], - y=[panel.LE_point_2[1], panel.TE_point_2[1]], - z=[panel.LE_point_2[2], panel.TE_point_2[2]], - mode="lines", - line=dict( - color=leadinge_edge_line_color, width=leadinge_edge_line_width - ), - name="Leading Edge", - showlegend=False, + elif is_last: + fig.add_trace( + go.Scatter3d( + x=[panel.LE_point_2[0], panel.TE_point_2[0]], + y=[panel.LE_point_2[1], panel.TE_point_2[1]], + z=[panel.LE_point_2[2], panel.TE_point_2[2]], + mode="lines", + line=dict( + color=leadinge_edge_line_color, width=leadinge_edge_line_width + ), + name="Leading Edge", + showlegend=False, + ) ) - ) # Standard leading edge fig.add_trace( go.Scatter3d( @@ -61,7 +77,7 @@ def add_panel_edges(fig: go.Figure, panel: Any, is_first: bool, is_last: bool) - y=[panel.TE_point_1[1], panel.TE_point_2[1]], z=[panel.TE_point_1[2], panel.TE_point_2[2]], mode="lines", - line=dict(color="black", width=2), + line=dict(color=edge_color, width=2), name="Trailing Edge", showlegend=is_first, ) @@ -77,7 +93,7 @@ def add_panel_edges(fig: go.Figure, panel: Any, is_first: bool, is_last: bool) - y=[points[0][1], points[1][1]], z=[points[0][2], points[1][2]], mode="lines", - line=dict(color="black", width=0.8), + line=dict(color=edge_color, width=0.8), name=( "Side Edge" if is_first and i == 0 else None ), # Legend only for the first side edge @@ -175,6 +191,7 @@ def add_aerodynamic_vectors( force_vector: np.ndarray, scale: float = 0.5, max_force: float = 1.0, + origin: np.ndarray = None, ): """ Add aerodynamic force vectors to a given panel on the plot. @@ -184,9 +201,12 @@ def add_aerodynamic_vectors( panel: Panel object force_of_panel: Aerodynamic force vector scale: Scaling factor for the force vector + origin: Optional vector origin. Defaults to the panel aerodynamic centre; + pass a point on the canopy surface to make the vectors emanate from + the surface when the panels are hidden. """ # Compute vector endpoint - aerodynamic_center = panel.aerodynamic_center + aerodynamic_center = panel.aerodynamic_center if origin is None else origin vector = (force_vector / max_force) * scale * 0.5 vector_endpoint = aerodynamic_center + vector @@ -411,7 +431,7 @@ def add_bridle_system(fig: go.Figure, wing_aero: object, is_first: bool = True) elif hasattr(wing_aero, "_bridle_line_system") and wing_aero._bridle_line_system: # Handle basic bridle line system (backward compatibility) for i, bridle_line in enumerate(wing_aero._bridle_line_system): - p1, p2, _, _, diameter, _ = bridle_line + p1, p2, diameter = bridle_line # Calculate thickness based on diameter (simple scaling) thickness = max(2, min(12, diameter * 1000)) # Assume diameter in meters @@ -435,6 +455,11 @@ def create_3D_plot( forces_of_panels: List[np.ndarray], is_with_aerodynamic_details: bool, is_with_bridles: bool = False, + tube_data: Optional[Dict[str, Any]] = None, + canopy_grid: Optional[Tuple[np.ndarray, np.ndarray, np.ndarray]] = None, + is_with_tube_rings: bool = False, + is_with_panels: bool = True, + cp_magnitude_fn=None, ) -> go.Figure: """ Creates an interactive 3D plot of wing geometry using Plotly. @@ -444,10 +469,26 @@ def create_3D_plot( forces_of_panels: List of force vectors for each panel is_with_aerodynamic_details: Boolean to show/hide aerodynamic visualization details is_with_bridles: Boolean to show/hide bridle system visualization + tube_data: Optional inflatable-tube geometry from + ``VSM.plotly.build_tube_data``; when given, the leading-edge and strut + tubes are drawn as surfaces. + canopy_grid: Optional ``(X, Y, Z)`` curved-canopy surface from + ``VSM.plotly.build_canopy_grid``; when given, it replaces the flat + panel surfaces. + is_with_tube_rings: Also draw the tube construction rings (for inspection). + is_with_panels: Draw the panel outlines. When ``False`` and a canopy is + present, the force vectors are lifted onto the canopy surface instead + of starting at the flat panel aerodynamic centres. Returns: plotly.graph_objects.Figure """ + from VSM.plotly.tube_geometry import ( + add_strut_surfaces, + add_le_tube_surface, + add_tube_rings, + ) + panels = wing_aero.panels chord_average = np.max([panel.chord for panel in panels]) @@ -460,6 +501,14 @@ def create_3D_plot( if is_with_bridles: add_bridle_system(fig, wing_aero, is_first=True) + draw_flat_surface = canopy_grid is None + # In the fancy plot the inflatable LE tube marks the leading edge, so draw the + # panels as thin grey outlines instead of the thick black leading-edge line. + is_thin_outline = tube_data is not None + # On the canopy the per-panel force vectors are replaced by a set of equal, + # surface-normal vectors distributed across the span (see below). + use_distributed_vectors = canopy_grid is not None + # Add geometric elements for i, panel in enumerate(panels): is_first = False @@ -472,20 +521,158 @@ def create_3D_plot( if is_with_aerodynamic_details: add_filaments(fig, panel, is_first) - add_panel_edges(fig, panel, is_first, is_last) - add_panel_surface(fig, panel, is_first) - add_aerodynamic_vectors( + if is_with_panels: + add_panel_edges( + fig, panel, is_first, is_last, is_thin_outline=is_thin_outline + ) + if draw_flat_surface and is_with_panels: + add_panel_surface(fig, panel, is_first) + + if not use_distributed_vectors: + add_aerodynamic_vectors( + fig, + panel, + is_first, + np.array(forces_of_panels[i]), + scale=chord_average, + max_force=max_force, + ) + + # Draw order (so the leading-edge tube ends up rendered on top): struts sit + # under the canopy, then the canopy, then the force vectors, and finally the + # leading-edge tube last so it is never occluded by the semi-transparent canopy. + if tube_data is not None: + add_strut_surfaces(fig, tube_data) + + if canopy_grid is not None: + add_canopy_surface(fig, canopy_grid) + + if use_distributed_vectors: + add_distributed_surface_vectors( fig, - panel, - is_first, - np.array(forces_of_panels[i]), + canopy_grid, + forces_of_panels, scale=chord_average, - max_force=max_force, + cp_magnitude_fn=cp_magnitude_fn, ) + if tube_data is not None: + add_le_tube_surface(fig, tube_data) + if is_with_tube_rings: + add_tube_rings(fig, tube_data) + return fig +def add_canopy_surface( + fig: go.Figure, canopy_grid: Tuple[np.ndarray, np.ndarray, np.ndarray] +) -> None: + """Add the curved single-skin canopy as a uniformly coloured surface.""" + canopy_color = "lightgrey" + x, y, z = canopy_grid + fig.add_trace( + go.Surface( + x=x, + y=y, + z=z, + surfacecolor=np.zeros_like(z), + colorscale=[[0, canopy_color], [1, canopy_color]], + showscale=False, + opacity=0.85, + name="Canopy", + showlegend=True, + lighting=dict(ambient=0.75, diffuse=0.8, specular=0.05, roughness=0.9), + ) + ) + + +def add_distributed_surface_vectors( + fig: go.Figure, + canopy_grid: Tuple[np.ndarray, np.ndarray, np.ndarray], + forces_of_panels: List[np.ndarray], + scale: float, + n_chord: int = 10, + n_span: int = None, + cp_magnitude_fn=None, +) -> None: + """Draw a grid of chord-normal force vectors on the canopy. + + One row of ``n_chord`` nodes is placed at each panel centre (uniformly over + the chord); every node carries one arrow oriented normal to the local chord + line (the flat-panel "up", perpendicular to chord and span), pointing to the + outward/suction side. This gives a regular ``n_span x n_chord`` grid of + vectors covering the whole canopy. + + Each panel's VSM force is distributed over its chord nodes: the arrow lengths + reflect both the spanwise loading (the panel force from VSM) and the chordwise + shape (the ``|Cp|`` weighting, or an even split without a Cp distribution). + The node forces of a panel sum back to that panel's force. + + Args: + fig: Plotly figure. + canopy_grid: ``(X, Y, Z)`` canopy surface arrays, each ``(S, P)`` with + ``S`` spanwise stations and ``P`` chordwise points. + forces_of_panels: Per-panel aerodynamic force vectors from VSM; their + magnitudes set the total per panel that is distributed chordwise. + scale: Length scale (typically the average chord). + n_chord: Nodes per row, distributed over the chord. Defaults to 10. + n_span: Number of spanwise rows. Defaults to one row per panel (the + midpoints between the ``S`` canopy stations, i.e. ``S - 1`` rows). + cp_magnitude_fn: Optional callable mapping chord fraction ``x/c`` to a + (positive) pressure-coefficient magnitude, used as the chordwise + weighting of each panel's force. Without it the force is spread evenly. + """ + from VSM.plotly.canopy_geometry import distributed_surface_vector_field + + origins, normals, lengths_1d = distributed_surface_vector_field( + canopy_grid, + forces_of_panels, + scale, + n_chord=n_chord, + n_span=n_span, + cp_magnitude_fn=cp_magnitude_fn, + ) + if len(origins) == 0: + return + lengths = lengths_1d[:, None] + endpoints = origins + normals * lengths + + # All stems as one trace (None-separated segments), all heads as one cone trace. + stem_x, stem_y, stem_z = [], [], [] + for start, end in zip(origins, endpoints): + stem_x += [start[0], end[0], None] + stem_y += [start[1], end[1], None] + stem_z += [start[2], end[2], None] + fig.add_trace( + go.Scatter3d( + x=stem_x, + y=stem_y, + z=stem_z, + mode="lines", + line=dict(color="red", width=3.9), + name="Aerodynamic Vectors", + showlegend=True, + ) + ) + fig.add_trace( + go.Cone( + x=endpoints[:, 0], + y=endpoints[:, 1], + z=endpoints[:, 2], + u=normals[:, 0] * lengths[:, 0], + v=normals[:, 1] * lengths[:, 0], + w=normals[:, 2] * lengths[:, 0], + sizemode="absolute", + sizeref=0.048 * scale, + anchor="tip", + colorscale=[[0, "red"], [1, "red"]], + showscale=False, + name="Arrowheads", + showlegend=False, + ) + ) + + def compute_kite_geometry_ranges(panels: List[Any]) -> Tuple[Dict[str, float], float]: """Calculate axis ranges and tick spacing.""" all_points = [] @@ -507,14 +694,18 @@ def compute_axis_parameters_from_fig( fig: go.Figure, ) -> Tuple[Dict[str, float], float]: """Calculate axis ranges and tick spacing based on the figure's data.""" - # Extract all data points from the figure traces + # Extract all data points from the figure traces, skipping gap separators + # (``None``/NaN) that traces may use to draw multiple segments at once. all_points = [] for trace in fig.data: if isinstance(trace, go.Scatter3d): - all_points.extend(zip(trace.x, trace.y, trace.z)) + for px, py, pz in zip(trace.x, trace.y, trace.z): + if px is None or py is None or pz is None: + continue + all_points.append((px, py, pz)) # Convert to numpy array for calculations - all_points = np.array(all_points) + all_points = np.array(all_points, dtype=float) # Calculate ranges for each axis ranges = { @@ -598,12 +789,15 @@ def update_fig_layout( f"{kite_geometry_ranges['z'][1]:.2f}", ], # Label for the ticks ), - aspectmode="data", # Allow different axis lengths - aspectratio=dict(x=1, y=1, z=1), # Keep aspect ratio 1:1:1 + # "data" keeps the scene true-to-scale (equal units on every axis); + # do NOT force a cube aspectratio here, that squeezes the wide wing. + aspectmode="data", camera=dict( up=dict(x=0, y=0, z=1), center=dict(x=0, y=0, z=0), - eye=dict(x=1.5, y=1.5, z=1.5), + eye=dict( + x=-1.92, y=-1.44, z=0.30 + ), # rotated ~180 deg, low and further out ), bgcolor="white", ), @@ -754,6 +948,11 @@ def update_plot( is_with_aerodynamic_details: bool, is_with_bridles: bool, title: str, + tube_data: Optional[Dict[str, Any]] = None, + canopy_grid: Optional[Tuple[np.ndarray, np.ndarray, np.ndarray]] = None, + is_with_tube_rings: bool = False, + is_with_panels: bool = True, + cp_magnitude_fn=None, ): # Update AoA and rerun VSM results = running_VSM( @@ -773,6 +972,11 @@ def update_plot( results["F_distribution"], is_with_aerodynamic_details, is_with_bridles, + tube_data=tube_data, + canopy_grid=canopy_grid, + is_with_tube_rings=is_with_tube_rings, + is_with_panels=is_with_panels, + cp_magnitude_fn=cp_magnitude_fn, ) fig = add_case_information( fig, @@ -804,6 +1008,11 @@ def interactive_plot( is_save: bool = False, filename="wing_geometry", is_show: bool = True, + surfplan_dir: Optional[Path] = None, + is_with_canopy: bool = True, + is_with_tube_rings: bool = False, + is_with_panels: Optional[bool] = None, + cp_distributions_dir: Optional[Path] = None, ): """ Creates and optionally saves multiple views of the wing geometry with interactive AoA slider. @@ -823,7 +1032,57 @@ def interactive_plot( is_save: Whether to save the plot filename: Base filename for saved files is_show: Whether to display the plot + surfplan_dir: Optional path to a raw Surfplan export (a ``.txt`` plus + a ``profiles/`` directory). When given, the "fancy" plot is drawn: the + export is converted with SurfplanAdapter (cached) and the inflatable + leading-edge and strut tubes -- and, by default, the curved single-skin + canopy -- are rendered on the wing. Leaving it ``None`` keeps the plain + plot unchanged. + is_with_canopy: When a Surfplan export is given, draw the curved canopy + (lofted airfoil top-surfaces) in place of the flat panel surfaces. + is_with_tube_rings: Also draw the tube construction rings (for inspection). + is_with_panels: Draw the panel outlines. Defaults to ``False`` for the + fancy plot (a Surfplan export is given) and ``True`` otherwise. When + off, the force vectors are lifted onto the canopy surface. + cp_distributions_dir: Optional directory of ``cp_AOA_.dat`` chordwise + pressure-coefficient files. When given, the closest available angle of + attack is selected and its ``|Cp|(x/c)`` scales the chordwise force + vector magnitudes (the single mid-span distribution is applied at + every span station). + + Returns: + plotly.graph_objects.Figure: The created figure. """ + # Default: hide the panel outlines for the fancy plot, show them otherwise. + if is_with_panels is None: + is_with_panels = surfplan_dir is None + + # Build the optional fancy-plot geometry once (reused on every slider update). + tube_data = None + canopy_grid = None + if surfplan_dir is not None: + from VSM.plotly.surfplan_runner import ensure_surfplan_processed + from VSM.plotly.tube_geometry import build_tube_data + from VSM.plotly.canopy_geometry import load_contour_table, build_canopy_grid + + processed_dir = ensure_surfplan_processed(Path(surfplan_dir)) + tube_data = build_tube_data(wing_aero.panels, processed_dir) + if is_with_canopy: + contour_table = load_contour_table(processed_dir) + canopy_grid = build_canopy_grid(wing_aero.panels, contour_table) + + # Optional Cp-based scaling of the chordwise force vectors. + cp_magnitude_fn = None + if cp_distributions_dir is not None: + from VSM.plotly.cp_distributions import build_cp_magnitude_fn + + cp_magnitude_fn, matched_aoa = build_cp_magnitude_fn( + Path(cp_distributions_dir), angle_of_attack + ) + print( + f"Cp loading from closest available AoA = {matched_aoa:g} deg " + f"(operating AoA = {angle_of_attack:g} deg)" + ) # Create the figure with a default orientation fig = go.Figure() @@ -848,6 +1107,11 @@ def interactive_plot( is_with_aerodynamic_details, is_with_bridles, title, + tube_data=tube_data, + canopy_grid=canopy_grid, + is_with_tube_rings=is_with_tube_rings, + is_with_panels=is_with_panels, + cp_magnitude_fn=cp_magnitude_fn, ) # Save or show the plot if requested @@ -859,4 +1123,95 @@ def interactive_plot( fig.write_html(save_path) if is_show: - fig.show() + import tempfile + import webbrowser + + with tempfile.NamedTemporaryFile( + suffix=".html", delete=False, mode="w", encoding="utf-8" + ) as f: + tmp_path = f.name + fig.write_html(f, include_plotlyjs=True, full_html=True) + print(f"Interactive plot saved to: {tmp_path}") + webbrowser.open(f"file://{tmp_path}") + + return fig + + +def save_high_res_render( + fig: go.Figure, + save_path, + base_size: int = 2000, + scale: int = 4, + show_legend: bool = False, + show_axes: bool = False, + show_annotations: bool = False, + transparent_background: bool = False, + pad_frac: float = 0.02, +) -> str: + """Export a clean, to-scale high-resolution PNG of the kite. + + A Plotly 3D scene is only rendered undistorted on a **square** canvas (on a + non-square canvas Plotly stretches it, which squeezes the kite). So this + renders on a square canvas -- guaranteeing correct proportions -- then crops + tightly to the drawn content. The legend, axes and annotations are removed by + default for a clean figure. + + Args: + fig: The Plotly figure (e.g. from :func:`interactive_plot`). + save_path: Output ``.png`` path; parent directories are created. + base_size: Square canvas size in pixels before supersampling. + scale: Supersampling factor (final render is ``base_size * scale`` square, + then cropped). Defaults to 4. + show_legend: Keep the trace legend. Defaults to False. + show_axes: Keep the 3D axes. Defaults to False. + show_annotations: Keep the corner case-info text. Defaults to False. + transparent_background: Transparent instead of white background. + pad_frac: Padding around the cropped content, as a fraction of its size. + + Returns: + str: The path the PNG was written to. + """ + import io + from PIL import Image + + render_fig = go.Figure(fig) + render_fig.update_layout(showlegend=show_legend, margin=dict(l=0, r=0, t=0, b=0)) + if not show_annotations: + render_fig.layout.annotations = () + render_fig.layout.title = None + if not show_axes: + render_fig.update_scenes( + xaxis_visible=False, yaxis_visible=False, zaxis_visible=False + ) + bg = "rgba(0,0,0,0)" if transparent_background else "white" + render_fig.update_layout(paper_bgcolor=bg, plot_bgcolor=bg) + + # Square canvas => Plotly does not distort the 3D scene. + png = render_fig.to_image( + format="png", width=base_size, height=base_size, scale=scale + ) + image = Image.open(io.BytesIO(png)).convert("RGBA") + + # Crop tightly to the drawn content (non-background pixels). + rgb = np.asarray(image)[:, :, :3].astype(int) + alpha = np.asarray(image)[:, :, 3] + if transparent_background: + content = alpha > 0 + else: + content = rgb.sum(axis=2) < 3 * 250 # anything not near-white + ys, xs = np.where(content) + if len(xs): + pad = int(pad_frac * max(xs.max() - xs.min(), ys.max() - ys.min())) + left = max(xs.min() - pad, 0) + right = min(xs.max() + pad, image.width) + top = max(ys.min() - pad, 0) + bottom = min(ys.max() + pad, image.height) + image = image.crop((left, top, right, bottom)) + + save_path = Path(save_path) + save_path.parent.mkdir(parents=True, exist_ok=True) + image.save(str(save_path)) + print( + f"High-resolution render saved to: {save_path} ({image.width} x {image.height} px)" + ) + return str(save_path) diff --git a/src/VSM/plotly/__init__.py b/src/VSM/plotly/__init__.py new file mode 100644 index 0000000..a204699 --- /dev/null +++ b/src/VSM/plotly/__init__.py @@ -0,0 +1,24 @@ +"""Helpers for the interactive Plotly wing plot. + +The public ``interactive_plot`` entry point lives in ``VSM.plot_geometry_plotly`` +(unchanged location for backward compatibility). This subpackage holds the +supporting machinery for the opt-in "fancy" plot that renders the inflatable +tubes and the curved single-skin canopy of a LEI kite from a raw Surfplan export. +""" + +from VSM.plotly.surfplan_runner import ensure_surfplan_processed +from VSM.plotly.tube_geometry import ( + add_tube_rings, + add_tube_surfaces, + build_tube_data, +) +from VSM.plotly.canopy_geometry import build_canopy_grid, load_contour_table + +__all__ = [ + "ensure_surfplan_processed", + "build_tube_data", + "add_tube_surfaces", + "add_tube_rings", + "build_canopy_grid", + "load_contour_table", +] diff --git a/src/VSM/plotly/canopy_geometry.py b/src/VSM/plotly/canopy_geometry.py new file mode 100644 index 0000000..419c6bc --- /dev/null +++ b/src/VSM/plotly/canopy_geometry.py @@ -0,0 +1,332 @@ +"""Curved single-skin canopy geometry for the interactive Plotly wing plot. + +The canopy is rendered as a loft of the real airfoil top-surfaces (the ``.dat`` +profiles produced by SurfplanAdapter) placed on the *drawn* wing panels. Only the +upper contour is used (LEI kites are single-skin), so the inflatable tubes stay +visible from below. See ``VSM.plotly.tube_geometry`` for the tubes and +``VSM.plotly.surfplan_runner`` for how the source files are generated. + +All geometry is anchored on the drawn ``Panel`` objects (their LE/TE points and +local frame), so the result is independent of the coordinate frame of the source +Surfplan export. +""" + +from pathlib import Path +from typing import Any, List, Tuple + +import numpy as np +import yaml + + +def read_upper_contour(dat_file_path: Path) -> np.ndarray: + """Read an airfoil ``.dat`` file and return its upper contour, LE-to-TE. + + The ``.dat`` files use the standard ordering starting at the trailing edge + ``(1, 0)``, running forward over the upper surface to the leading edge + ``(0, 0)``, then back over the lower surface to the trailing edge. The upper + surface is the run up to the minimum-x (leading edge) point. + + Args: + dat_file_path (Path): Path to the airfoil coordinate file. The first line + is a name label and is skipped; remaining lines are ``x y`` pairs + normalised by the chord. + + Returns: + np.ndarray: Upper contour as an ``(n, 2)`` array ordered from the leading + edge ``(0, 0)`` to the trailing edge ``(1, 0)``. + """ + points = [] + for line in Path(dat_file_path).read_text().splitlines(): + parts = line.split() + if len(parts) != 2: + continue + try: + points.append((float(parts[0]), float(parts[1]))) + except ValueError: + continue + points = np.asarray(points, dtype=float) + le_index = int(np.argmin(points[:, 0])) + upper = points[: le_index + 1] # TE -> LE + return upper[::-1] # LE -> TE + + +def resample_contour(contour: np.ndarray, n_points: int = 40) -> np.ndarray: + """Resample a contour to a fixed point count by arc length. + + Uses cumulative chord-length parameterisation so that the leading- and + trailing-edge endpoints are preserved. This lets contours from different + airfoils be blended point-by-point. + + Args: + contour (np.ndarray): ``(n, 2)`` contour ordered LE-to-TE. + n_points (int): Number of output points. Defaults to 40. + + Returns: + np.ndarray: ``(n_points, 2)`` resampled contour. + """ + segment_lengths = np.linalg.norm(np.diff(contour, axis=0), axis=1) + arc_length = np.concatenate([[0.0], np.cumsum(segment_lengths)]) + total = arc_length[-1] + if total <= 0: + return np.repeat(contour[:1], n_points, axis=0) + sample = np.linspace(0.0, total, n_points) + x = np.interp(sample, arc_length, contour[:, 0]) + y = np.interp(sample, arc_length, contour[:, 1]) + return np.column_stack([x, y]) + + +def load_contour_table( + processed_dir: Path, n_points: int = 40 +) -> List[Tuple[float, np.ndarray]]: + """Build a span-fraction -> upper-contour table from the generated aero yaml. + + Reads ``aero_geometry.yaml`` written by SurfplanAdapter: each wing section + provides its span position (``LE_y``) and an ``airfoil_id`` whose + ``info_dict`` carries the ``dat_file_path``. Span positions are normalised to + fractions of the half-span so the table can be sampled against any drawn wing + regardless of scale or frame. + + Args: + processed_dir (Path): Directory holding ``aero_geometry.yaml`` and the + ``profiles/`` referenced by relative ``dat_file_path`` entries. + n_points (int): Point count each contour is resampled to. Defaults to 40. + + Returns: + List[Tuple[float, np.ndarray]]: ``(span_fraction, contour)`` pairs sorted + by ascending span fraction, each contour an ``(n_points, 2)`` array. + """ + processed_dir = Path(processed_dir) + aero = yaml.safe_load((processed_dir / "aero_geometry.yaml").read_text()) + sections = aero["wing_sections"] + airfoils = aero["wing_airfoils"] + + section_headers = sections["headers"] + idx_id = section_headers.index("airfoil_id") + idx_le_y = section_headers.index("LE_y") + + airfoil_headers = airfoils["headers"] + a_idx_id = airfoil_headers.index("airfoil_id") + a_idx_info = airfoil_headers.index("info_dict") + dat_by_id = { + row[a_idx_id]: row[a_idx_info]["dat_file_path"] for row in airfoils["data"] + } + + contour_cache = {} + span_y = np.array([abs(row[idx_le_y]) for row in sections["data"]]) + half_span = span_y.max() if span_y.max() > 0 else 1.0 + + table = [] + for row in sections["data"]: + airfoil_id = row[idx_id] + if airfoil_id not in contour_cache: + dat_path = processed_dir / dat_by_id[airfoil_id] + contour_cache[airfoil_id] = resample_contour( + read_upper_contour(dat_path), n_points + ) + fraction = abs(row[idx_le_y]) / half_span + table.append((fraction, contour_cache[airfoil_id])) + + table.sort(key=lambda item: item[0]) + return table + + +def interpolate_contour_at( + span_fraction: float, contour_table: List[Tuple[float, np.ndarray]] +) -> np.ndarray: + """Linearly blend the two contours bracketing a span fraction. + + Args: + span_fraction (float): Target fraction of the half-span, in ``[0, 1]``. + contour_table (List[Tuple[float, np.ndarray]]): Output of + :func:`load_contour_table`; contours share a common point count. + + Returns: + np.ndarray: Interpolated contour, clamped to the end contours outside the + table's fraction range. + """ + fractions = [f for f, _ in contour_table] + if span_fraction <= fractions[0]: + return contour_table[0][1] + if span_fraction >= fractions[-1]: + return contour_table[-1][1] + upper = int(np.searchsorted(fractions, span_fraction)) + lower = upper - 1 + f_lo, c_lo = contour_table[lower] + f_hi, c_hi = contour_table[upper] + weight = (span_fraction - f_lo) / (f_hi - f_lo) if f_hi > f_lo else 0.0 + return (1.0 - weight) * c_lo + weight * c_hi + + +def _edge_stations(panels: List[Any]) -> List[dict]: + """Collect the ``n_panels + 1`` spanwise edge stations of the drawn wing. + + Each station carries its leading- and trailing-edge points, the chord vector + and length, and the local up-vector (averaged across adjacent panels), all + taken from the ``Panel`` objects so the canopy sits exactly on the drawn wing. + """ + stations = [] + + def make_station(le_point, te_point, up_vectors): + chord_vector = np.asarray(te_point) - np.asarray(le_point) + chord_length = float(np.linalg.norm(chord_vector)) + chord_hat = chord_vector / max(chord_length, 1e-12) + up = np.mean(up_vectors, axis=0) + up = up / max(np.linalg.norm(up), 1e-12) + return { + "le_point": np.asarray(le_point, dtype=float), + "te_point": np.asarray(te_point, dtype=float), + "chord_length": chord_length, + "chord_hat": chord_hat, + "up": up, + } + + stations.append( + make_station(panels[0].LE_point_1, panels[0].TE_point_1, [panels[0].x_airf]) + ) + for index, panel in enumerate(panels): + neighbours = [panel.x_airf] + if index + 1 < len(panels): + neighbours.append(panels[index + 1].x_airf) + stations.append(make_station(panel.LE_point_2, panel.TE_point_2, neighbours)) + return stations + + +def build_canopy_grid( + panels: List[Any], contour_table: List[Tuple[float, np.ndarray]] +) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + """Build the lofted canopy surface grid over the drawn wing panels. + + For every panel-edge station the local upper contour (blended from the rib + profiles at that span fraction) is placed on the drawn chord: the contour's + chordwise coordinate runs along the station chord and its height along the + local up-vector, both scaled by the station chord length. + + Args: + panels (List[Any]): Drawn ``Panel`` objects, ordered tip to tip. + contour_table (List[Tuple[float, np.ndarray]]): Output of + :func:`load_contour_table`. + + Returns: + Tuple[np.ndarray, np.ndarray, np.ndarray]: ``X``, ``Y``, ``Z`` arrays of + shape ``(n_panels + 1, n_points)`` suitable for a Plotly surface. + """ + stations = _edge_stations(panels) + all_y = np.array([abs(station["le_point"][1]) for station in stations]) + half_span = all_y.max() if all_y.max() > 0 else 1.0 + + rows = [] + for station in stations: + fraction = abs(station["le_point"][1]) / half_span + contour = interpolate_contour_at(fraction, contour_table) + chord_length = station["chord_length"] + points = ( + station["le_point"][None, :] + + contour[:, 0:1] * chord_length * station["chord_hat"][None, :] + + contour[:, 1:2] * chord_length * station["up"][None, :] + ) + rows.append(points) + + grid = np.stack(rows, axis=0) # (n_stations, n_points, 3) + return grid[:, :, 0], grid[:, :, 1], grid[:, :, 2] + + +def distributed_surface_vector_field( + canopy_grid: Tuple[np.ndarray, np.ndarray, np.ndarray], + forces_of_panels: List[np.ndarray], + scale: float, + n_chord: int = 10, + n_span: int = None, + cp_magnitude_fn=None, +) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + """Chord-normal force-vector field over the canopy (origins, directions, lengths). + + One row of ``n_chord`` nodes is placed at each panel centre, uniformly over + the chord (at the true 5%-95% chord fractions). Every node's arrow points + normal to the local chord line (the flat-panel "up", outward/suction side). + Each panel's VSM force is distributed over its chord nodes, weighted by the + ``|Cp|`` chordwise loading (an even split without a Cp distribution) and + normalised to length so the largest panel force plots at the maximum chord + length (``scale``). + + Args: + canopy_grid: ``(X, Y, Z)`` canopy arrays, each ``(S, P)``. + forces_of_panels: Per-panel VSM force vectors. + scale: Length scale (typically the maximum chord); the largest panel + force plots at this length. + n_chord: Nodes per row over the chord. Defaults to 10. + n_span: Number of spanwise rows. Defaults to one per panel (``S - 1``). + cp_magnitude_fn: Optional ``x/c -> |Cp|`` weighting callable. + + Returns: + Tuple[np.ndarray, np.ndarray, np.ndarray]: ``origins`` ``(N, 3)``, + unit ``directions`` ``(N, 3)`` and ``lengths`` ``(N,)``. Empty arrays + if there is no force. + """ + x, y, z = canopy_grid + grid = np.stack([x, y, z], axis=-1) # (S, P, 3) + + force_magnitudes = np.linalg.norm(np.asarray(forces_of_panels), axis=1) + if force_magnitudes.size == 0 or np.max(force_magnitudes) <= 0: + return np.empty((0, 3)), np.empty((0, 3)), np.empty((0,)) + + panel_grid = 0.5 * (grid[:-1] + grid[1:]) # (S - 1, P, 3), one row per panel + n_rows, n_points, _ = panel_grid.shape + + if n_span is None: + n_span = n_rows + span_rows = np.unique( + np.clip(np.round(np.linspace(0, n_rows - 1, n_span)), 0, n_rows - 1).astype(int) + ) + d_span = np.gradient(panel_grid, axis=0) # (R, P, 3) + + targets = np.linspace(0.05, 0.95, n_chord) + if cp_magnitude_fn is not None: + chord_weights = np.asarray(cp_magnitude_fn(targets), dtype=float) + else: + chord_weights = np.ones(n_chord) + chord_weights = chord_weights / max(chord_weights.sum(), 1e-12) + + origins = [] + directions = [] + node_forces = [] + for row in span_rows: + pts = panel_grid[row] + chord_vector = pts[-1] - pts[0] + chord_length = np.linalg.norm(chord_vector) + chord_hat = chord_vector / max(chord_length, 1e-12) + + span_vector = d_span[row].mean(axis=0) + chord_normal = np.cross(chord_vector, span_vector) + cn = np.linalg.norm(chord_normal) + if cn < 1e-9: + continue + chord_normal = chord_normal / cn + if chord_normal[2] < 0: # outward (suction) side + chord_normal = -chord_normal + + panel_force = force_magnitudes[row] if row < len(force_magnitudes) else 0.0 + + chord_fraction = ((pts - pts[0]) @ chord_hat) / max(chord_length, 1e-12) + order = np.argsort(chord_fraction) + frac_sorted = chord_fraction[order] + pts_sorted = pts[order] + for k, target in enumerate(targets): + point = np.array( + [np.interp(target, frac_sorted, pts_sorted[:, j]) for j in range(3)] + ) + origins.append(point) + directions.append(chord_normal) + node_forces.append(panel_force * chord_weights[k]) + + if not origins: + return np.empty((0, 3)), np.empty((0, 3)), np.empty((0,)) + + origins = np.array(origins) + directions = np.array(directions) + node_forces = np.array(node_forces) + newton_to_metre = scale / max(float(force_magnitudes.max()), 1e-12) + # Anchor the per-vector size to a base of 10 chordwise nodes, so adding more + # nodes makes the field denser without shrinking each arrow (at n_chord=10 the + # factor is 1 and the largest panel's arrows still sum to the max chord). + lengths = node_forces * newton_to_metre * (n_chord / 10.0) + return origins, directions, lengths diff --git a/src/VSM/plotly/cp_distributions.py b/src/VSM/plotly/cp_distributions.py new file mode 100644 index 0000000..29c1ad0 --- /dev/null +++ b/src/VSM/plotly/cp_distributions.py @@ -0,0 +1,97 @@ +"""Chordwise pressure-coefficient (Cp) database for the fancy Plotly plot. + +A directory of ``cp_AOA_.dat`` files (whitespace columns ``x/c, y/c, Cp`` +around the 2D airfoil section) is treated as a small database keyed by angle of +attack. Given an operating angle of attack, the closest available distribution is +selected and its **upper (suction) surface** ``|Cp|(x/c)`` is used to scale the +magnitude of the chordwise force vectors on the canopy. + +Per the modelling assumption in use, the single mid-span section distribution is +applied to every spanwise station (all airfoils share this geometry). +""" + +import re +from pathlib import Path +from typing import Callable, Dict, Tuple + +import numpy as np + + +def load_upper_surface_cp(dat_file_path: Path) -> Tuple[np.ndarray, np.ndarray]: + """Extract the upper (suction) surface ``Cp(x/c)`` from a Cp ``.dat`` loop. + + The file lists ``x/c, y/c, Cp`` around the closed airfoil section. The upper + surface is the leading-edge-to-trailing-edge branch with the more negative + (suction) mean Cp; it is returned sorted by ``x/c``. + + Args: + dat_file_path (Path): Path to a ``cp_AOA_*.dat`` file. + + Returns: + Tuple[np.ndarray, np.ndarray]: ``x/c`` (ascending) and ``Cp`` arrays. + """ + data = np.loadtxt(dat_file_path) + x = data[:, 0] + cp = data[:, 2] + n = len(x) + i_le = int(np.argmin(x)) + i_te = int(np.argmax(x)) + + def branch(a: int, b: int) -> np.ndarray: + if a <= b: + return np.arange(a, b + 1) + return np.concatenate([np.arange(a, n), np.arange(0, b + 1)]) + + branch_a = branch(i_le, i_te) + branch_b = branch(i_te, i_le) + upper = branch_a if cp[branch_a].mean() < cp[branch_b].mean() else branch_b + + x_upper = x[upper] + cp_upper = cp[upper] + order = np.argsort(x_upper) + return x_upper[order], cp_upper[order] + + +def load_cp_database(cp_dir: Path) -> Dict[float, Tuple[np.ndarray, np.ndarray]]: + """Load all ``cp_AOA_.dat`` files, keyed by angle of attack in degrees. + + Args: + cp_dir (Path): Directory of ``cp_AOA_*.dat`` files. + + Returns: + Dict[float, Tuple[np.ndarray, np.ndarray]]: Mapping ``aoa_deg -> (x/c, Cp)`` + for the upper surface. + """ + database = {} + for path in sorted(Path(cp_dir).glob("cp_AOA_*.dat")): + match = re.search(r"AOA_(-?\d+(?:\.\d+)?)", path.stem) + if not match: + continue + database[float(match.group(1))] = load_upper_surface_cp(path) + if not database: + raise FileNotFoundError(f"No 'cp_AOA_*.dat' files found in {cp_dir}.") + return database + + +def build_cp_magnitude_fn( + cp_dir: Path, angle_of_attack: float +) -> Tuple[Callable[[np.ndarray], np.ndarray], float]: + """Return a ``|Cp|(x/c)`` interpolator for the closest available AoA. + + Args: + cp_dir (Path): Directory of ``cp_AOA_*.dat`` files. + angle_of_attack (float): Operating angle of attack in degrees. + + Returns: + Tuple[Callable, float]: A function mapping ``x/c`` (array) to ``|Cp|`` and + the matched database angle of attack. + """ + database = load_cp_database(cp_dir) + available = np.array(sorted(database)) + matched_aoa = float(available[np.argmin(np.abs(available - angle_of_attack))]) + x_upper, cp_upper = database[matched_aoa] + + def magnitude(x_over_c: np.ndarray) -> np.ndarray: + return np.abs(np.interp(x_over_c, x_upper, cp_upper)) + + return magnitude, matched_aoa diff --git a/src/VSM/plotly/surfplan_runner.py b/src/VSM/plotly/surfplan_runner.py new file mode 100644 index 0000000..f7680fb --- /dev/null +++ b/src/VSM/plotly/surfplan_runner.py @@ -0,0 +1,95 @@ +"""Run SurfplanAdapter to produce the geometry files the fancy plot needs. + +The interactive "fancy" plot needs physical tube diameters and airfoil profiles +that are not present in a plain VSM aero-geometry yaml. They live in a raw +Surfplan export (a ``.txt`` plus a ``profiles/`` directory). This module drives +SurfplanAdapter to convert that export into VSM-readable yaml files, caching the +result so the conversion only runs when the export changes. +""" + +from pathlib import Path +from typing import Optional + + +def _find_surfplan_txt(surfplan_dir: Path) -> Path: + """Return the single ``.txt`` export in ``surfplan_dir`` (error if not one).""" + txt_files = sorted(surfplan_dir.glob("*.txt")) + if not txt_files: + raise FileNotFoundError( + f"No Surfplan '.txt' export found in {surfplan_dir}. Expected a raw " + "Surfplan export (a '.txt' plus a 'profiles/' directory)." + ) + if len(txt_files) > 1: + raise ValueError( + f"Multiple '.txt' files found in {surfplan_dir}: " + f"{[f.name for f in txt_files]}. Keep only the Surfplan export." + ) + return txt_files[0] + + +def ensure_surfplan_processed( + surfplan_dir: Path, output_dir: Optional[Path] = None +) -> Path: + """Convert a raw Surfplan export into VSM geometry yamls, with caching. + + Args: + surfplan_dir (Path): Directory containing the Surfplan ``.txt`` export and + a ``profiles/`` subdirectory of airfoil ``.dat`` files. + output_dir (Optional[Path]): Where to write the generated files. Defaults + to ``surfplan_dir / "vsm_processed"``. + + Returns: + Path: The directory holding the generated ``aero_geometry.yaml``, + ``struc_geometry_all_in_surfplan.yaml`` and ``profiles/``. + + Raises: + ImportError: If SurfplanAdapter is not installed. + FileNotFoundError: If the export ``.txt`` or ``profiles/`` is missing. + """ + surfplan_dir = Path(surfplan_dir) + if output_dir is None: + output_dir = surfplan_dir / "vsm_processed" + output_dir = Path(output_dir) + + txt_path = _find_surfplan_txt(surfplan_dir) + profile_load_dir = surfplan_dir / "profiles" + if not profile_load_dir.is_dir(): + raise FileNotFoundError( + f"No 'profiles/' directory in {surfplan_dir}; the fancy plot needs the " + "airfoil '.dat' files from the Surfplan export." + ) + + aero_yaml = output_dir / "aero_geometry.yaml" + struc_yaml = output_dir / "struc_geometry_all_in_surfplan.yaml" + if ( + aero_yaml.exists() + and struc_yaml.exists() + and aero_yaml.stat().st_mtime >= txt_path.stat().st_mtime + and struc_yaml.stat().st_mtime >= txt_path.stat().st_mtime + ): + return output_dir + + try: + from SurfplanAdapter.process_wing import main_process_wing + from SurfplanAdapter.process_bridle_lines import main_process_bridle_lines + from SurfplanAdapter.generate_yaml import main_generate_yaml + except ImportError as error: + raise ImportError( + "The fancy plot requires SurfplanAdapter to convert the raw Surfplan " + "export. Install it with 'pip install SurfplanAdapter'." + ) from error + + output_dir.mkdir(parents=True, exist_ok=True) + ribs_data = main_process_wing.main( + surfplan_txt_file_path=txt_path, + profile_load_dir=profile_load_dir, + profile_save_dir=output_dir / "profiles", + is_make_plots=False, + ) + bridle_lines = main_process_bridle_lines.main(txt_path) + main_generate_yaml.main( + ribs_data=ribs_data, + bridle_lines=bridle_lines, + yaml_file_path=output_dir / "config_kite.yaml", + ) + return output_dir diff --git a/src/VSM/plotly/tube_geometry.py b/src/VSM/plotly/tube_geometry.py new file mode 100644 index 0000000..0609302 --- /dev/null +++ b/src/VSM/plotly/tube_geometry.py @@ -0,0 +1,557 @@ +"""Inflatable-tube geometry for the interactive Plotly wing plot. + +Builds lofted 3D surfaces for the leading-edge tube and the chordwise strut tubes +of a LEI kite, using the physical tube-diameter tables produced by SurfplanAdapter +(``leading_edge_tubes`` and ``strut_tubes`` in +``struc_geometry_all_in_surfplan.yaml``) together with the airfoil top-surface +contours (see ``VSM.plotly.canopy_geometry``). + +Diameters are physical (metres) and sourced from the generated tables; positions +are anchored on the *drawn* ``Panel`` objects, so tubes always sit on the wing +that is actually plotted, independent of the Surfplan export's coordinate frame: + +* Leading-edge tube: centred just aft of the drawn LE polyline, diameter + interpolated from the ``leading_edge_tubes`` table by span fraction (this + captures the tapering tip, which ``t * chord`` does not). +* Struts: placed at the ``is_strut`` rib span fractions, shaped to follow the + local airfoil top-surface and offset downward so the tube top touches the + canopy underside. Per-strut LE/TE diameters come from the ``strut_tubes`` table + paired tip-to-tip with the strut ribs (both are symmetric and span-ordered, so + the pairing does not rely on the table's node indices). +""" + +from pathlib import Path +from typing import Any, Callable, Dict, List, Tuple + +import numpy as np +import plotly.graph_objects as go +import yaml + +from VSM.plotly.canopy_geometry import interpolate_contour_at, load_contour_table + +TUBE_COLOR = "black" +TUBE_OPACITY = 1.0 +RING_COLOR = "#08306b" + + +def circle_points( + center: np.ndarray, + axis: np.ndarray, + diameter: float, + n: int = 24, + ref: np.ndarray = None, +) -> np.ndarray: + """Ring of ``n`` points of the given diameter, perpendicular to ``axis``. + + Args: + center (np.ndarray): Ring centre, shape ``(3,)``. + axis (np.ndarray): Tube axis at this station; the ring lies in the plane + normal to it. + diameter (float): Ring diameter. + n (int): Number of points around the ring. Defaults to 24. + ref (np.ndarray): Optional reference vector fixing the azimuthal origin of + the ring. Passing the *same* ``ref`` for every ring of a tube keeps + consecutive rings consistently oriented, which avoids the surface + twisting where the tube tangent swings (e.g. at a wingtip). Falls back + to an automatic helper when ``ref`` is parallel to the axis. + + Returns: + np.ndarray: ``(n, 3)`` ring points, closed (first point repeated at end). + """ + axis = np.asarray(axis, dtype=float) + axis = axis / max(np.linalg.norm(axis), 1e-12) + if ref is not None: + helper = np.asarray(ref, dtype=float) + if abs(axis @ (helper / max(np.linalg.norm(helper), 1e-12))) > 0.98: + helper = np.array([0.0, 0.0, 1.0]) + if abs(axis @ helper) > 0.9: + helper = np.array([0.0, 1.0, 0.0]) + else: + helper = np.array([0.0, 0.0, 1.0]) + if abs(axis @ helper) > 0.9: + helper = np.array([0.0, 1.0, 0.0]) + e1 = np.cross(axis, helper) + e1 = e1 / max(np.linalg.norm(e1), 1e-12) + e2 = np.cross(axis, e1) + theta = np.linspace(0.0, 2.0 * np.pi, n) + radius = 0.5 * float(diameter) + return ( + np.asarray(center, dtype=float)[None, :] + + radius * np.cos(theta)[:, None] * e1[None, :] + + radius * np.sin(theta)[:, None] * e2[None, :] + ) + + +def rings_along_centerline( + centers: np.ndarray, diameters: np.ndarray, n: int = 24 +) -> List[np.ndarray]: + """Sweep circular rings along a centreline with twist-free framing. + + The ring frame is parallel-transported from one station to the next (the + previous in-plane axis is projected onto the new normal plane) instead of + being chosen independently per ring. This keeps the swept surface from + twisting or pinching where the centreline curves sharply -- e.g. where the + leading-edge tube turns chordwise at a wingtip. + + Args: + centers (np.ndarray): ``(m, 3)`` centreline points. + diameters (np.ndarray): ``(m,)`` local tube diameters. + n (int): Number of points around each ring. Defaults to 24. + + Returns: + List[np.ndarray]: One ``(n, 3)`` ring per centreline station. + """ + centers = np.asarray(centers, dtype=float) + diameters = np.asarray(diameters, dtype=float) + m = len(centers) + + tangents = np.zeros_like(centers) + tangents[1:-1] = centers[2:] - centers[:-2] + tangents[0] = centers[1] - centers[0] + tangents[-1] = centers[-1] - centers[-2] + tangents /= np.linalg.norm(tangents, axis=1, keepdims=True).clip(1e-12) + + def _seed_axis(tangent): + ref = np.array([0.0, 0.0, 1.0]) + if abs(tangent @ ref) > 0.9: + ref = np.array([0.0, 1.0, 0.0]) + axis = ref - (ref @ tangent) * tangent + return axis / max(np.linalg.norm(axis), 1e-12) + + theta = np.linspace(0.0, 2.0 * np.pi, n) + e1 = _seed_axis(tangents[0]) + rings = [] + for index in range(m): + # Parallel-transport the previous in-plane axis onto this normal plane. + e1 = e1 - (e1 @ tangents[index]) * tangents[index] + if np.linalg.norm(e1) < 1e-8: + e1 = _seed_axis(tangents[index]) + e1 /= max(np.linalg.norm(e1), 1e-12) + e2 = np.cross(tangents[index], e1) + radius = 0.5 * diameters[index] + rings.append( + centers[index][None, :] + + radius * np.cos(theta)[:, None] * e1[None, :] + + radius * np.sin(theta)[:, None] * e2[None, :] + ) + return rings + + +def load_le_diameter_interp(processed_dir: Path) -> Callable[[float], float]: + """Return a leading-edge diameter(span-fraction) interpolator. + + Builds the interpolator from the ``leading_edge_tubes`` segment table: each + segment midpoint gives a ``(|y| fraction, diameter)`` sample. Fractions are + of the half-span so the curve can be sampled against any drawn wing. + + Args: + processed_dir (Path): Directory holding + ``struc_geometry_all_in_surfplan.yaml``. + + Returns: + Callable[[float], float]: Function mapping a span fraction in ``[0, 1]`` + to a leading-edge tube diameter in metres. + """ + struc = yaml.safe_load( + (Path(processed_dir) / "struc_geometry_all_in_surfplan.yaml").read_text() + ) + particles = { + int(row[0]): np.asarray(row[1:4], dtype=float) + for row in struc["wing_particles"]["data"] + } + le = struc["leading_edge_tubes"] + idx_ci = le["headers"].index("ci") + idx_cj = le["headers"].index("cj") + idx_d = le["headers"].index("diameter") + + y_mid = [] + diam = [] + for row in le["data"]: + ci = particles[int(row[idx_ci])] + cj = particles[int(row[idx_cj])] + y_mid.append(0.5 * (ci[1] + cj[1])) + diam.append(float(row[idx_d])) + y_mid = np.abs(np.asarray(y_mid)) + diam = np.asarray(diam) + half_span = y_mid.max() if y_mid.max() > 0 else 1.0 + fractions = y_mid / half_span + + order = np.argsort(fractions) + frac_sorted = fractions[order] + diam_sorted = diam[order] + + def interp(span_fraction: float) -> float: + return float(np.interp(span_fraction, frac_sorted, diam_sorted)) + + return interp + + +def load_strut_diameter_table(processed_dir: Path) -> List[Tuple[float, float]]: + """Per-strut ``(diam_le, diam_te)`` pairs in span order (tip to tip). + + Args: + processed_dir (Path): Directory holding + ``struc_geometry_all_in_surfplan.yaml``. + + Returns: + List[Tuple[float, float]]: Leading- and trailing-edge strut-tube diameters + in metres, in the table's listed (span) order. + """ + struc = yaml.safe_load( + (Path(processed_dir) / "struc_geometry_all_in_surfplan.yaml").read_text() + ) + st = struc["strut_tubes"] + idx_le = st["headers"].index("strut_diam_le") + idx_te = st["headers"].index("strut_diam_te") + return [(float(row[idx_le]), float(row[idx_te])) for row in st["data"]] + + +def load_strut_span_fractions(processed_dir: Path) -> np.ndarray: + """Signed span fractions of the strut ribs, from the generated aero yaml. + + Uses the ``is_strut`` flag in each section's airfoil ``info_dict``. Fractions + are ``LE_y`` normalised by the half-span, returned sorted by descending span + (``+tip`` to ``-tip``) to match the ``strut_tubes`` table order. + + Args: + processed_dir (Path): Directory holding ``aero_geometry.yaml``. + + Returns: + np.ndarray: Signed span fractions of the strut ribs, high to low. + """ + aero = yaml.safe_load((Path(processed_dir) / "aero_geometry.yaml").read_text()) + sections = aero["wing_sections"] + airfoils = aero["wing_airfoils"] + idx_id = sections["headers"].index("airfoil_id") + idx_le_y = sections["headers"].index("LE_y") + a_idx_id = airfoils["headers"].index("airfoil_id") + a_idx_info = airfoils["headers"].index("info_dict") + is_strut_by_id = { + row[a_idx_id]: bool(row[a_idx_info].get("is_strut", False)) + for row in airfoils["data"] + } + + span_y = np.array([abs(row[idx_le_y]) for row in sections["data"]]) + half_span = span_y.max() if span_y.max() > 0 else 1.0 + strut_y = [ + row[idx_le_y] + for row in sections["data"] + if is_strut_by_id.get(row[idx_id], False) + ] + fractions = np.array(sorted(strut_y, reverse=True)) / half_span + return fractions + + +def _drawn_stations(panels: List[Any]) -> Dict[str, np.ndarray]: + """Spanwise edge stations of the drawn wing as stacked arrays. + + Returns leading/trailing points, chord lengths, chord and up unit vectors, and + signed span fractions for the ``n_panels + 1`` panel edges, sorted by span. + """ + le = [panels[0].LE_point_1] + [p.LE_point_2 for p in panels] + te = [panels[0].TE_point_1] + [p.TE_point_2 for p in panels] + up = [panels[0].x_airf] + for index, panel in enumerate(panels): + neighbours = [panel.x_airf] + if index + 1 < len(panels): + neighbours.append(panels[index + 1].x_airf) + up.append(np.mean(neighbours, axis=0)) + + le = np.asarray(le, dtype=float) + te = np.asarray(te, dtype=float) + up = np.asarray(up, dtype=float) + up = up / np.linalg.norm(up, axis=1, keepdims=True).clip(1e-12) + chord_vec = te - le + chord_len = np.linalg.norm(chord_vec, axis=1) + chord_hat = chord_vec / chord_len[:, None].clip(1e-12) + + half_span = np.abs(le[:, 1]).max() + half_span = half_span if half_span > 0 else 1.0 + fraction = le[:, 1] / half_span + + order = np.argsort(fraction) + return { + "le": le[order], + "te": te[order], + "up": up[order], + "chord_len": chord_len[order], + "chord_hat": chord_hat[order], + "fraction": fraction[order], + } + + +def _station_at(stations: Dict[str, np.ndarray], signed_fraction: float) -> dict: + """Interpolate a drawn-wing station at a signed span fraction.""" + frac = stations["fraction"] + le = np.array( + [np.interp(signed_fraction, frac, stations["le"][:, i]) for i in range(3)] + ) + te = np.array( + [np.interp(signed_fraction, frac, stations["te"][:, i]) for i in range(3)] + ) + up = np.array( + [np.interp(signed_fraction, frac, stations["up"][:, i]) for i in range(3)] + ) + up = up / max(np.linalg.norm(up), 1e-12) + chord_vec = te - le + chord_len = float(np.linalg.norm(chord_vec)) + chord_hat = chord_vec / max(chord_len, 1e-12) + return { + "le": le, + "te": te, + "up": up, + "chord_len": chord_len, + "chord_hat": chord_hat, + } + + +def _tip_extension(le_center, te_point, le_diameter, n=8, taper=1.0): + """Centreline points + diameters continuing from a tip LE to the TE. + + Extends the leading-edge tube at the wingtip so it runs on to the trailing + edge of the outer panel, at (by default) constant diameter for a clean tube. + """ + ts = np.linspace(0.0, 1.0, n + 1)[1:] # exclude 0 (the LE centre already exists) + le_center = np.asarray(le_center, dtype=float) + te_point = np.asarray(te_point, dtype=float) + points = [le_center + t * (te_point - le_center) for t in ts] + diams = [le_diameter * (1.0 - (1.0 - taper) * t) for t in ts] + return points, diams + + +def build_le_tube( + panels: List[Any], + le_diameter_interp: Callable[[float], float], + extend_tips: bool = True, +) -> List[np.ndarray]: + """Build the leading-edge tube as a list of cross-section rings. + + The tube runs spanwise along the drawn LE polyline; each ring is centred just + aft of the LE point (so the tube front touches the drawn leading edge) and its + diameter is looked up from the LE table at the local span fraction. With + ``extend_tips`` the tube wraps around each wingtip and continues toward the + trailing edge, tapering closed. + + Args: + panels (List[Any]): Drawn ``Panel`` objects, ordered tip to tip. + le_diameter_interp (Callable[[float], float]): Output of + :func:`load_le_diameter_interp`. + extend_tips (bool): Wrap the tube from each tip toward the trailing edge. + + Returns: + List[np.ndarray]: One ``(n, 3)`` ring per centreline station. + """ + stations = _drawn_stations(panels) + le = stations["le"] + te = stations["te"] + fraction = np.abs(stations["fraction"]) + diameters = np.array([le_diameter_interp(f) for f in fraction]) + centers = le + 0.5 * diameters[:, None] * stations["chord_hat"] + + center_list = list(centers) + diam_list = list(diameters) + if extend_tips: + neg_pts, neg_d = _tip_extension(centers[0], te[0], diameters[0]) + pos_pts, pos_d = _tip_extension(centers[-1], te[-1], diameters[-1]) + # Prepend the -tip wrap (reversed: TE -> LE) and append the +tip wrap. + center_list = neg_pts[::-1] + center_list + pos_pts + diam_list = neg_d[::-1] + diam_list + pos_d + + return rings_along_centerline(np.array(center_list), np.array(diam_list)) + + +def build_strut( + stations: Dict[str, np.ndarray], + contour_table, + signed_fraction: float, + diam_le: float, + diam_te: float, + le_tube_diameter: float = 0.0, +) -> List[np.ndarray]: + """Build one chordwise strut tube hugging the canopy underside. + + The local airfoil top-surface is placed on the drawn wing at the strut span, + then each chord station is offset downward by the local strut radius so the + tube top touches the canopy. Diameter tapers linearly from ``diam_le`` at the + leading edge to ``diam_te`` at the trailing edge. + + The strut starts at the aft edge of the leading-edge tube (chord fraction + ``le_tube_diameter / chord`` -- the LE tube spans that far back from the drawn + LE) rather than at the leading edge, so it butts against the LE tube instead + of passing straight through it. + + Args: + stations (Dict[str, np.ndarray]): Output of :func:`_drawn_stations`. + contour_table: Output of ``canopy_geometry.load_contour_table``. + signed_fraction (float): Signed span fraction of the strut rib. + diam_le (float): Strut-tube diameter at the leading edge, metres. + diam_te (float): Strut-tube diameter at the trailing edge, metres. + le_tube_diameter (float): Local leading-edge tube diameter, metres; sets + where the strut starts. Zero means start at the leading edge. + + Returns: + List[np.ndarray]: One ``(n, 3)`` ring per chordwise station. + """ + station = _station_at(stations, signed_fraction) + contour = interpolate_contour_at(abs(signed_fraction), contour_table) + chord_len = station["chord_len"] + + # Start the strut partway into the leading-edge tube (half its depth) so the + # forward cap is buried inside the LE tube -- no gap, no through-poke. + le_tube_depth_fraction = le_tube_diameter / max(chord_len, 1e-12) + start_fraction = min(0.5 * le_tube_depth_fraction, 0.9) + cx = contour[:, 0] + cy = contour[:, 1] + keep = cx > start_fraction + if start_fraction > 0 and keep.any(): + cx = np.concatenate([[start_fraction], cx[keep]]) + cy = np.concatenate( + [[float(np.interp(start_fraction, contour[:, 0], cy))], cy[keep]] + ) + + canopy_points = ( + station["le"][None, :] + + cx[:, None] * chord_len * station["chord_hat"][None, :] + + cy[:, None] * chord_len * station["up"][None, :] + ) + diameters = diam_le + (diam_te - diam_le) * cx + centers = canopy_points - 0.5 * diameters[:, None] * station["up"][None, :] + return rings_along_centerline(centers, diameters) + + +def build_tube_data(panels: List[Any], processed_dir: Path) -> Dict[str, Any]: + """Assemble leading-edge and strut tube rings for the drawn wing. + + Args: + panels (List[Any]): Drawn ``Panel`` objects, ordered tip to tip. + processed_dir (Path): Directory of SurfplanAdapter-generated files. + + Returns: + Dict[str, Any]: ``{"le": [rings], "struts": [[rings], ...]}``. + """ + processed_dir = Path(processed_dir) + le_interp = load_le_diameter_interp(processed_dir) + contour_table = load_contour_table(processed_dir) + strut_fractions = load_strut_span_fractions(processed_dir) + strut_table = load_strut_diameter_table(processed_dir) + + le_rings = build_le_tube(panels, le_interp) + + stations = _drawn_stations(panels) + struts = [] + for index, signed_fraction in enumerate(strut_fractions): + if index < len(strut_table): + diam_le, diam_te = strut_table[index] + else: + # Fallback: size the strut from the local LE tube diameter. + diam_le = le_interp(abs(signed_fraction)) + diam_te = 0.6 * diam_le + le_tube_diameter = le_interp(abs(signed_fraction)) + struts.append( + build_strut( + stations, + contour_table, + signed_fraction, + diam_le, + diam_te, + le_tube_diameter=le_tube_diameter, + ) + ) + return {"le": le_rings, "struts": struts} + + +def _rings_to_surface( + rings: List[np.ndarray], +) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + """Stack a list of rings into ``X``, ``Y``, ``Z`` surface arrays.""" + grid = np.stack(rings, axis=0) # (n_rings, n_circle, 3) + return grid[:, :, 0], grid[:, :, 1], grid[:, :, 2] + + +def _add_surface(fig: go.Figure, rings: List[np.ndarray], name: str, show_legend: bool): + """Add one tube as a uniformly coloured, semi-transparent surface.""" + x, y, z = _rings_to_surface(rings) + fig.add_trace( + go.Surface( + x=x, + y=y, + z=z, + surfacecolor=np.zeros_like(z), + colorscale=[[0, TUBE_COLOR], [1, TUBE_COLOR]], + showscale=False, + opacity=TUBE_OPACITY, + name=name, + showlegend=show_legend, + lighting=dict(ambient=0.75, diffuse=0.8, specular=0.05, roughness=0.9), + ) + ) + + +def _add_ring_cap(fig: go.Figure, ring: np.ndarray) -> None: + """Close an open tube end with a filled (fan-triangulated) cap disc.""" + ring = np.asarray(ring)[:-1] # drop the duplicated closing point + n = len(ring) + center = ring.mean(axis=0) + verts = np.vstack([center[None, :], ring]) # index 0 is the centre + i = [0] * n + j = [1 + t for t in range(n)] + k = [1 + (t + 1) % n for t in range(n)] + fig.add_trace( + go.Mesh3d( + x=verts[:, 0], + y=verts[:, 1], + z=verts[:, 2], + i=i, + j=j, + k=k, + color=TUBE_COLOR, + opacity=TUBE_OPACITY, + name="Tube cap", + showlegend=False, + ) + ) + + +def _add_tube_with_caps( + fig: go.Figure, rings: List[np.ndarray], name: str, show_legend: bool +): + """Add a tube surface and close both open ends with caps.""" + _add_surface(fig, rings, name, show_legend) + _add_ring_cap(fig, rings[0]) + _add_ring_cap(fig, rings[-1]) + + +def add_strut_surfaces(fig: go.Figure, tube_data: Dict[str, Any]) -> None: + """Add the strut tube surfaces (with end caps) to the figure.""" + for index, strut in enumerate(tube_data["struts"]): + _add_tube_with_caps(fig, strut, "Strut tube", show_legend=(index == 0)) + + +def add_le_tube_surface(fig: go.Figure, tube_data: Dict[str, Any]) -> None: + """Add the leading-edge tube surface (with end caps) to the figure.""" + _add_tube_with_caps(fig, tube_data["le"], "Leading-edge tube", show_legend=True) + + +def add_tube_surfaces(fig: go.Figure, tube_data: Dict[str, Any]) -> None: + """Add the leading-edge and strut tube surfaces to the figure.""" + add_le_tube_surface(fig, tube_data) + add_strut_surfaces(fig, tube_data) + + +def add_tube_rings(fig: go.Figure, tube_data: Dict[str, Any]) -> None: + """Add the construction rings (wireframe circles) for inspection.""" + all_rings = list(tube_data["le"]) + [ + r for strut in tube_data["struts"] for r in strut + ] + for index, ring in enumerate(all_rings): + fig.add_trace( + go.Scatter3d( + x=ring[:, 0], + y=ring[:, 1], + z=ring[:, 2], + mode="lines", + line=dict(color=RING_COLOR, width=1), + name="Tube rings", + showlegend=(index == 0), + ) + ) diff --git a/src/VSM/stability_derivatives.py b/src/VSM/stability_derivatives.py index 0489689..527008c 100644 --- a/src/VSM/stability_derivatives.py +++ b/src/VSM/stability_derivatives.py @@ -183,6 +183,7 @@ def _evaluate_with_angles(alpha_deg: float, beta_deg: float) -> CoeffDict: angle_of_attack=alpha_deg, side_slip=beta_deg, body_rates=[rates["r"], rates["q"], rates["p"]], + body_axis=[[0, 0, 1], [0, 1, 0], [1, 0, 0]], reference_point=reference_point, ) return _solve_and_extract() diff --git a/src/VSM/trim_angle.py b/src/VSM/trim_angle.py index 1207c8c..cb2402e 100644 --- a/src/VSM/trim_angle.py +++ b/src/VSM/trim_angle.py @@ -132,10 +132,6 @@ def _compute_derivative(alpha_deg: float, delta_deg: float) -> float: cmy_coarse.append(cmy_value) cmy_coarse_array = np.asarray(cmy_coarse) - import matplotlib.pyplot as plt - - plt.plot(alpha_coarse, cmy_coarse_array, marker="o") - plt.show() valid_mask = ~np.isnan(cmy_coarse_array) valid_alphas = alpha_coarse[valid_mask] valid_cmy = cmy_coarse_array[valid_mask] diff --git a/tests/AirfoilAerodynamics/test_masure.py b/tests/AirfoilAerodynamics/test_masure.py index 76abfc7..f4e9148 100644 --- a/tests/AirfoilAerodynamics/test_masure.py +++ b/tests/AirfoilAerodynamics/test_masure.py @@ -6,6 +6,14 @@ from pathlib import Path from VSM.core.AirfoilAerodynamics import AirfoilAerodynamics +# The regression models are git-ignored, so they are absent on a clean checkout (CI). +pytestmark = pytest.mark.skipif( + not Path(__file__) + .parent.parent.parent.joinpath("data", "ml_models", "ET_re1e6.pkl") + .exists(), + reason="Real model files not available", +) + class TestMasureRegressionReal: """Test cases for masure_regression with real model file integration.""" @@ -196,12 +204,6 @@ def test_masure_regression_different_parameters( assert airfoil.CD is not None, f"CD should not be None for param set {i}" assert airfoil.CM is not None, f"CM should not be None for param set {i}" - @pytest.mark.skipif( - not Path(__file__) - .parent.parent.parent.joinpath("data", "models", "ET_re1e6.pkl") - .exists(), - reason="Real model files not available", - ) def test_masure_regression_requires_model_files( self, masure_params, small_alpha_range, test_reynolds, masure_config_path ): diff --git a/tests/Solver/test_casadi_polar_table_inputs.py b/tests/Solver/test_casadi_polar_table_inputs.py new file mode 100644 index 0000000..34ed806 --- /dev/null +++ b/tests/Solver/test_casadi_polar_table_inputs.py @@ -0,0 +1,168 @@ +"""Polar tables as inputs of the CasADi panel functions (``table_inputs=True``). + +Contract: with the tables passed as inputs, the residual and panel-force +functions give the same outputs as the functions with the tables embedded; +one function, built once, serves a second shape whose blended polars differ +(no rebuild); omitted tables give NaN, not a silently zero polar; and the +function still expands to SX. +""" + +import sys +from pathlib import Path + +import numpy as np +import pytest + +ca = pytest.importorskip("casadi") + +REPO = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(REPO / "tests" / "BodyAerodynamics")) + +from panel_kernel_cases import V3_FILE # noqa: E402 +from VSM.core.BodyAerodynamics import BodyAerodynamics # noqa: E402 +from VSM.core.Solver import Solver # noqa: E402 + +RTOL = 1e-12 + + +def _v3(distribution): + body = BodyAerodynamics.instantiate( + n_panels=20, file_path=V3_FILE, spanwise_panel_distribution=distribution + ) + body.va_initialize(10.0, 8.0, 3.0) + return body + + +def _solved(body, **solver_kwargs): + solver = Solver(gamma_loop_type="casadi_newton", allowed_error=1e-10, **solver_kwargs) + results = solver.solve(body) + return solver, np.asarray(results["gamma_distribution"], dtype=float) + + +def _newton_inputs(solver, gamma): + v_rel = solver.compute_relative_velocity(gamma) + n = solver.n_panels + laplacian = ( + solver._build_spanwise_laplacian() + if solver.is_with_artificial_viscosity + else np.zeros((n, n)) + ) + return { + "v_rel": v_rel, + "x_airf": solver.x_airf_array, + "y_airf": solver.y_airf_array, + "z_airf": solver.z_airf_array, + "chord": solver.chord_array, + "width": solver.width_array, + "l_gamma": laplacian @ gamma, + **solver.casadi_stall_angle_inputs(), + } + + +def _force_inputs(solver, gamma): + v_rel = solver.compute_relative_velocity(gamma) + return { + "v_rel": v_rel, + "v_qc": v_rel, + "x_airf": solver.x_airf_array, + "y_airf": solver.y_airf_array, + "z_airf": solver.z_airf_array, + "chord": solver.chord_array, + "width": solver.width_array, + "rho": solver.rho, + } + + +def _assert_outputs_equal(out_tables, out_const): + assert set(out_tables) == set(out_const) + for key, value in out_const.items(): + expected = np.array(value, dtype=float) + actual = np.array(out_tables[key], dtype=float) + scale = max(np.max(np.abs(expected)), 1e-300) + np.testing.assert_allclose(actual, expected, rtol=0, atol=RTOL * scale, err_msg=key) + + +@pytest.mark.parametrize("viscosity", [False, True]) +def test_table_inputs_reproduce_embedded_tables(viscosity): + """Residual function (all 13 outputs, incl. the corner-averaged ones and + the viscosity lift slope) and panel-force function, against the + embedded-table functions of the same shape.""" + solver, gamma = _solved(_v3("uniform"), is_with_artificial_viscosity=viscosity) + tables = solver.casadi_polar_table_inputs() + assert tables["cl_table"].shape == (solver.n_panels, solver.panels[0].panel_polar_data.shape[0]) + + args = _newton_inputs(solver, gamma) + if viscosity: # put a panel past stall so the gate and mu are exercised + args["stall_angles"] = args["stall_angles"] - 0.3 + _assert_outputs_equal( + solver._casadi_newton_function(table_inputs=True)(**args, **tables), + solver._casadi_newton_function()(**args), + ) + f_args = _force_inputs(solver, gamma) + _assert_outputs_equal( + solver.casadi_panel_force_function(table_inputs=True)(**f_args, **tables), + solver.casadi_panel_force_function()(**f_args), + ) + + +def test_one_function_serves_a_second_shape_without_rebuild(): + """Shapes A and B have the same panel count and alpha grid but differently + blended polars. The table-input function built for A, given B's tables and + geometry, equals a fresh embedded-table build for B, and no new + table-input function is built.""" + solver_a, gamma_a = _solved(_v3("uniform")) + fn = solver_a._casadi_newton_function(table_inputs=True) + force_fn = solver_a.casadi_panel_force_function(table_inputs=True) + cache_size = len(solver_a._casadi_table_input_cache) + + body_b = _v3("cosine") + tables_a = solver_a.casadi_polar_table_inputs() + solver_a.solve(body_b) # same solver object, now on shape B + gamma_b = solver_a.solve(body_b)["gamma_distribution"] + tables_b = solver_a.casadi_polar_table_inputs() + assert np.max(np.abs(tables_b["cl_table"] - tables_a["cl_table"])) > 1e-3 # the polars differ + + assert solver_a._casadi_newton_function(table_inputs=True) is fn + assert solver_a.casadi_panel_force_function(table_inputs=True) is force_fn + assert len(solver_a._casadi_table_input_cache) == cache_size + + fresh_b, _ = _solved(_v3("cosine")) + _assert_outputs_equal( + fn(**_newton_inputs(solver_a, gamma_b), **tables_b), + fresh_b._casadi_newton_function()(**_newton_inputs(solver_a, gamma_b)), + ) + _assert_outputs_equal( + force_fn(**_force_inputs(solver_a, gamma_b), **tables_b), + fresh_b.casadi_panel_force_function()(**_force_inputs(solver_a, gamma_b)), + ) + + +def test_omitted_tables_give_nan_not_zero(): + solver, gamma = _solved(_v3("uniform")) + out = solver._casadi_newton_function(table_inputs=True)(**_newton_inputs(solver, gamma)) + assert np.all(np.isnan(np.array(out["h"], dtype=float))) + out = solver.casadi_panel_force_function(table_inputs=True)(**_force_inputs(solver, gamma)) + assert np.all(np.isnan(np.array(out["force"], dtype=float))) + + +def test_table_input_functions_expand_to_sx(): + solver, gamma = _solved(_v3("uniform"), is_with_artificial_viscosity=True) + tables = solver.casadi_polar_table_inputs() + for fn, args in ( + (solver._casadi_newton_function(table_inputs=True), _newton_inputs(solver, gamma)), + (solver.casadi_panel_force_function(table_inputs=True), _force_inputs(solver, gamma)), + ): + _assert_outputs_equal(fn.expand()(**args, **tables), fn(**args, **tables)) + + +def test_newton_loop_is_unchanged_and_does_not_build_table_functions(): + solver, _ = _solved(_v3("uniform")) + assert solver._casadi_table_input_cache == {} + + +def test_unsupported_cases_raise_clearly(): + solver, _ = _solved(_v3("uniform"), polar_interpolation="bspline") + with pytest.raises(ValueError, match="polar_interpolation='linear'"): + solver._casadi_newton_function(table_inputs=True) + with pytest.raises(ValueError, match="polar_interpolation='linear'"): + solver.casadi_polar_table_inputs() diff --git a/tests/plotting/test_tube_geometry.py b/tests/plotting/test_tube_geometry.py new file mode 100644 index 0000000..a518998 --- /dev/null +++ b/tests/plotting/test_tube_geometry.py @@ -0,0 +1,261 @@ +"""Tests for the fancy Plotly plot: inflatable tubes and curved canopy. + +Unit tests cover the pure geometry helpers (ring math, contour handling, +interpolation). The integration tests exercise the full SurfplanAdapter +conversion and figure assembly against the bundled TUDELFT V3 Surfplan export; +they are skipped if SurfplanAdapter or the export data are unavailable. +""" + +from pathlib import Path + +import numpy as np +import pytest + +from VSM.plotly.canopy_geometry import ( + interpolate_contour_at, + read_upper_contour, + resample_contour, +) +from VSM.plotly.tube_geometry import circle_points + +REPO_ROOT = Path(__file__).resolve().parents[2] +SURFPLAN_DIR = REPO_ROOT / "data" / "TUDELFT_V3_KITE" / "Surfplan_export" +CP_DIR = REPO_ROOT / "data" / "TUDELFT_V3_KITE" / "cpx_distributions" +DRAWN_YAML = ( + REPO_ROOT + / "data" + / "TUDELFT_V3_KITE" + / "CAD_derived_geometry" + / "aero_geometry_CAD_breukels_regression.yaml" +) + + +def _has_surfplan(): + try: + import SurfplanAdapter # noqa: F401 + except ImportError: + return False + return SURFPLAN_DIR.is_dir() + + +requires_surfplan = pytest.mark.skipif( + not _has_surfplan(), reason="SurfplanAdapter or Surfplan export data unavailable" +) + + +# --------------------------------------------------------------------------- # +# Unit tests -- pure geometry helpers +# --------------------------------------------------------------------------- # + + +def test_circle_points_radius_and_orthogonality(): + center = np.array([1.0, 2.0, 3.0]) + axis = np.array([0.0, 1.0, 0.0]) + diameter = 0.4 + ring = circle_points(center, axis, diameter, n=32) + + # Every point lies at the radius from the centre. + radii = np.linalg.norm(ring - center, axis=1) + assert np.allclose(radii, 0.5 * diameter, atol=1e-9) + + # The ring lies in the plane normal to the axis (constant projection). + projections = (ring - center) @ axis + assert np.allclose(projections, 0.0, atol=1e-9) + + +def test_circle_points_axis_normalisation_invariant(): + center = np.zeros(3) + ring_unit = circle_points(center, np.array([0.0, 0.0, 1.0]), 1.0) + ring_scaled = circle_points(center, np.array([0.0, 0.0, 5.0]), 1.0) + assert np.allclose(ring_unit, ring_scaled, atol=1e-9) + + +def test_resample_contour_count_and_endpoints(): + contour = np.array([[0.0, 0.0], [0.3, 0.08], [0.7, 0.05], [1.0, 0.0]]) + resampled = resample_contour(contour, n_points=25) + assert resampled.shape == (25, 2) + assert np.allclose(resampled[0], contour[0], atol=1e-9) + assert np.allclose(resampled[-1], contour[-1], atol=1e-9) + + +def test_interpolate_contour_at_blends_and_clamps(): + c_lo = np.array([[0.0, 0.0], [1.0, 0.0]]) + c_hi = np.array([[0.0, 0.0], [1.0, 1.0]]) + table = [(0.0, c_lo), (1.0, c_hi)] + + # Midpoint is the average of the two contours. + mid = interpolate_contour_at(0.5, table) + assert np.allclose(mid, 0.5 * (c_lo + c_hi)) + + # Outside the range clamps to the end contours. + assert np.allclose(interpolate_contour_at(-1.0, table), c_lo) + assert np.allclose(interpolate_contour_at(2.0, table), c_hi) + + +def test_read_upper_contour_ordering(tmp_path): + # Minimal Selig-style .dat: TE -> upper -> LE -> lower -> TE. + dat = tmp_path / "prof.dat" + dat.write_text( + "test_profile\n" "1.0 0.0\n" "0.5 0.08\n" "0.0 0.0\n" "0.5 -0.04\n" "1.0 0.0\n" + ) + upper = read_upper_contour(dat) + # Ordered LE (x=0) to TE (x=1), all upper-surface points. + assert upper[0, 0] == pytest.approx(0.0) + assert upper[-1, 0] == pytest.approx(1.0) + assert np.all(np.diff(upper[:, 0]) > 0) + assert np.all(upper[:, 1] >= 0.0) + + +# --------------------------------------------------------------------------- # +# Integration tests -- full conversion + figure assembly +# --------------------------------------------------------------------------- # + + +@requires_surfplan +def test_ensure_surfplan_processed_and_cache(): + from VSM.plotly.surfplan_runner import ensure_surfplan_processed + + processed = ensure_surfplan_processed(SURFPLAN_DIR) + aero = processed / "aero_geometry.yaml" + struc = processed / "struc_geometry_all_in_surfplan.yaml" + assert aero.exists() and struc.exists() + + # Second call is a cache hit: the generated file is not rewritten. + first_mtime = aero.stat().st_mtime + ensure_surfplan_processed(SURFPLAN_DIR) + assert aero.stat().st_mtime == first_mtime + + +@requires_surfplan +def test_build_tube_and_canopy_shapes(): + from VSM.core.BodyAerodynamics import BodyAerodynamics + from VSM.plotly.surfplan_runner import ensure_surfplan_processed + from VSM.plotly.tube_geometry import build_tube_data, load_strut_span_fractions + from VSM.plotly.canopy_geometry import build_canopy_grid, load_contour_table + + n_panels = 36 + body = BodyAerodynamics.instantiate( + n_panels=n_panels, + file_path=DRAWN_YAML, + spanwise_panel_distribution="uniform", + ) + panels = body.panels + processed = ensure_surfplan_processed(SURFPLAN_DIR) + + tube = build_tube_data(panels, processed) + n_struts = len(load_strut_span_fractions(processed)) + # LE tube has one ring per panel edge, plus the wrap-around tip extensions. + assert len(tube["le"]) >= n_panels + 1 + assert len(tube["struts"]) == n_struts + + x, y, z = build_canopy_grid(panels, load_contour_table(processed)) + assert x.shape[0] == n_panels + 1 + assert x.shape == y.shape == z.shape + + # Tubes track the drawn wing span (within a tube radius at the tips). + wing_le = np.array([p.LE_point_1 for p in panels] + [p.LE_point_2 for p in panels]) + tube_pts = np.vstack(list(tube["le"])) + assert abs(tube_pts[:, 1].max() - wing_le[:, 1].max()) < 0.2 + + +@requires_surfplan +def test_interactive_plot_fancy_traces(): + import plotly.graph_objects as go + from VSM.core.BodyAerodynamics import BodyAerodynamics + from VSM.plot_geometry_plotly import interactive_plot + from VSM.plotly.tube_geometry import load_strut_span_fractions + from VSM.plotly.surfplan_runner import ensure_surfplan_processed + + body = BodyAerodynamics.instantiate( + n_panels=36, + file_path=DRAWN_YAML, + spanwise_panel_distribution="uniform", + ) + n_struts = len(load_strut_span_fractions(ensure_surfplan_processed(SURFPLAN_DIR))) + + # Fancy plot with canopy (panels default off): surfaces = canopy + LE + struts. + fig = interactive_plot(body, surfplan_dir=SURFPLAN_DIR, is_show=False) + surfaces = [t for t in fig.data if isinstance(t, go.Surface)] + flat_panels = [ + t for t in fig.data if isinstance(t, go.Mesh3d) and t.name == "Panel Surface" + ] + le_outline = [t for t in fig.data if getattr(t, "name", None) == "Leading Edge"] + assert len(surfaces) == 1 + 1 + n_struts + assert len(flat_panels) == 0 + assert len(le_outline) == 0 # panel outlines hidden by default in fancy mode + + # Tubes only with panels explicitly on: flat panels kept, no canopy surface. + fig2 = interactive_plot( + body, + surfplan_dir=SURFPLAN_DIR, + is_with_canopy=False, + is_with_panels=True, + is_show=False, + ) + surfaces2 = [t for t in fig2.data if isinstance(t, go.Surface)] + flat_panels2 = [ + t for t in fig2.data if isinstance(t, go.Mesh3d) and t.name == "Panel Surface" + ] + assert len(surfaces2) == 1 + n_struts + assert len(flat_panels2) == len(body.panels) + + # Plain plot is unchanged: panels shown, no surfaces at all. + fig3 = interactive_plot(body, is_show=False) + assert not [t for t in fig3.data if isinstance(t, go.Surface)] + assert [t for t in fig3.data if getattr(t, "name", None) == "Leading Edge"] + + +# --------------------------------------------------------------------------- # +# Cp distribution database +# --------------------------------------------------------------------------- # + + +@pytest.mark.skipif(not CP_DIR.is_dir(), reason="Cp distributions unavailable") +def test_load_upper_surface_cp_ordering(): + from VSM.plotly.cp_distributions import load_upper_surface_cp + + x, cp = load_upper_surface_cp(CP_DIR / "cp_AOA_8.dat") + assert np.all(np.diff(x) >= 0) # ascending x/c + assert x.min() >= 0.0 and x.max() <= 1.01 # normalised chord (small CFD overshoot) + assert cp.min() < 0 # suction side has negative Cp + + +@pytest.mark.skipif(not CP_DIR.is_dir(), reason="Cp distributions unavailable") +def test_build_cp_magnitude_fn_closest_aoa(): + from VSM.plotly.cp_distributions import build_cp_magnitude_fn + + # Exact and nearest matches over the available {2, 6, 8} degrees. + assert build_cp_magnitude_fn(CP_DIR, 8)[1] == 8 + assert build_cp_magnitude_fn(CP_DIR, 5)[1] == 6 + assert build_cp_magnitude_fn(CP_DIR, 3)[1] == 2 + assert build_cp_magnitude_fn(CP_DIR, 100)[1] == 8 + + magnitude, _ = build_cp_magnitude_fn(CP_DIR, 8) + values = magnitude(np.array([0.05, 0.5, 0.95])) + assert np.all(values >= 0) + # Loading is front-heavy: near-LE |Cp| exceeds near-TE |Cp|. + assert values[0] > values[-1] + + +@pytest.mark.skipif( + not (_has_surfplan() and CP_DIR.is_dir()), + reason="SurfplanAdapter or data unavailable", +) +def test_interactive_plot_cp_scaled_vectors_run(): + from VSM.core.BodyAerodynamics import BodyAerodynamics + from VSM.plot_geometry_plotly import interactive_plot + + body = BodyAerodynamics.instantiate( + n_panels=36, + file_path=DRAWN_YAML, + spanwise_panel_distribution="uniform", + ) + fig = interactive_plot( + body, + angle_of_attack=8, + surfplan_dir=SURFPLAN_DIR, + cp_distributions_dir=CP_DIR, + is_show=False, + ) + # The vector grid is still drawn (stems + arrowheads present). + assert [t for t in fig.data if getattr(t, "name", None) == "Aerodynamic Vectors"]