An OpenVSP model goes in. A converged CFD solution comes out, on a mesh that was measured rather than trusted.
Three pipelines share that shape and differ in what they hand the flow to: a tetrahedral Euler/RANS solve in SU2, a lattice-Boltzmann solve in FluidX3D, and a meshing front-end that will argue with you about your own refinement requests.
.vsp3 -> watertight surface -> volume mesh -> solver -> forces, fields, pictures
| directory | what it does |
|---|---|
vsp2su2/ |
.vsp3 to a tetrahedral mesh to an SU2 Euler solution, in one command. Surface extraction, cleanup, volume meshing, config generation, then force validation and Trefftz-plane drag. |
vspmesh/ |
Meshing with a chat interface. Import a model, set conditions, build, and ask for changes in English — but a change is only kept if the mesh got measurably better. |
vsp2lbm/ |
The same geometry through a GPU lattice-Boltzmann solver. FluidX3D is configured at compile time, so the case is a generated setup.cpp. |
A fourth pipeline, vsp2rans — full RANS with boundary-layer extrusion, y+
control and a validation campaign — lands here shortly; it is mid-campaign on
another machine as of this commit.
Meshing is where CFD quietly goes wrong. A bad mesh does not announce itself: it produces a solution that converges, plots smoothly, and is incorrect. Every tool here is built on the assumption that the mesh must be measured, and that a change is not an improvement merely because someone asked for it.
vspmesh states this most directly. Ask it to refine the leading edge and it
applies the change, re-measures, and reverts if the numbers went the wrong way:
> ask "make the leading edge much finer 40"
leading edge 16 -> 40 cells across the nose arc
REVERTED -- that made the mesh worse:
under_covered_pct 19.8 -> 26.06 (+31.6%) WORSE
triangles 39,530 -> 55,698
The previous mesh is still in place.
That rule was not designed in advance. It is what two days of hand-building these meshes taught: every failure took the same shape — a change that looked right, produced a mesh that looked fine, and was worse on a number nobody had thought to print. Raising the layer count "obviously" improves a boundary layer; it produced 1,023 folded faces and 53% over-coverage. A per-column height scale "obviously" handles a thin section gracefully; it divided y+ by fifty at the trailing edge. Both passed every check that existed at the time. So the checks became the product.
Each pipeline has its own README with the real invocation. The short version:
# SU2: geometry to solution cd vsp2su2 .\.venv\Scripts\python.exe run.py path\to\model.vsp3 --mach 0.5 --aoa 3 --solve # Interactive meshing cd vspmesh python app.py --work runs/mywing # LBM: generate the compile-time case, then build FluidX3D cd vsp2lbm python scripts\make_case.py runs\wing.stl -o FluidX3D\src\setup.cpp \ --chord 4.0 --mach 0.5 --aoa 3 --cells-per-ref 100
Everything lands in that pipeline's runs/<case-name>/.
runs/ is ignored everywhere. A single RANS case carries a multi-GB volume
mesh, a restart file and a solution field, all of which are reproducible from
the spec and the model that made them. The .vsp3 models, specs and scripts
that do determine a result are tracked.
FluidX3D itself is also ignored. It is
Moritz Lehmann's solver, vendored
here as an unpacked release; redistributing it is not this repo's business and
its license is not mine to relicense. The two files FluidX3D expects a user to
write — defines.hpp and setup.cpp — are tracked, because those are the ones
that are actually mine.
SU2 8.5.0, OpenVSP, ParaView (for pvbatch post-processing), and an OpenCL
device for FluidX3D. Python dependencies are per-pipeline.