Skip to content

Virtual Wind Tunnel ​

Advanced ▸ Virtual wind tunnel… takes surfaces (one body, or several) to a meshed tunnel and a prepared Fluid case in five steps plus an optional bodies step. Everything it does is also available headlessly as the wind_tunnel_plan and wind_tunnel_generate MCP tools, which call the same planner and builder, and every step of the build is an ordinary mesh operation (sdfDistance, remesh, levelset), so nothing here is a black box.

The steps ​

  1. Body — the surface, its unit and how to turn it. The flow runs along +X and up is +Z; rotate the body until its nose points at −X and its roof at +Z. The panel shows the size it measured (a model in millimetres read as metres is the usual mistake), the frontal area (the union of the shadow on the plane facing the flow, not the sum of the faces) and whether the surface is closed. An open or inward-wound body is reported, not repaired: the sign of the distance comes from the winding number, which tolerates small gaps.
  2. Tunnel — how far the walls are, in body lengths (upstream 3, downstream 6), widths (sides 4) and heights (top 5), and what the floor is: free air, a slip floor, a no-slip floor or a moving ground at the stream speed. A half model meshes only y ≥ 0 and closes the cut with a symmetry plane. The blockage (frontal area over the tunnel cross-section) is shown and warned above 5%, where the walls speed the flow up past the body.
  3. Flow — speed and fluid (air or water at 20 °C, or your own density and viscosity). The Reynolds number and the regime follow. Pick a turbulence model (none, k-epsilon, k-omega or k-omega SST) with the free-stream intensity and length scale: a model prepares the case for the Turbulent Flow (RANS) problemtype instead of Fluid.
  4. Mesh — body-fitted or embedded, the surface and far-field cell sizes, the target y+, the layer growth and aspect limit, the wake refinement (or a list of refinement regions), and the wall snap.
  5. More bodies (optional) — extra surfaces in the Body step, each with its own file, unit, rotation, tunnel-frame position (required) and name. The first body stays centred; position the others around it. Bodies must not overlap or touch: a pair closer than twice the surface size is refused up front.
  6. Generate — the folder and name, then the plan (cell count, memory and time estimates, every warning and error) and the build, with progress and Cancel.

What it builds ​

Body-fitted (the default):

  1. a graded tetrahedral lattice of the tunnel, with its six faces named (Inlet, Outlet, Floor, Ceiling, SideLow/SideHigh, or Symmetry);
  2. a few passes of refinement towards the body: the signed distance to the surface is sampled at the nodes (meshio++) and MMG refines to a size that grows with the distance, plus a refinement box behind the body for the wake — or, when refinement regions are given (one per line: box, cone/frustum or ellipsoid with a size and an optional growth), those caps replace the wake box;
  3. the carve: MMG's level-set split along the zero of the distance, then the body interior is dropped (the fluid side is the one touching the inlet, found from geometry, never from MMG's labels);
  4. the wall snap (body-fitted only, on by default): the carved wall is projected onto the surface and the displacement smoothed with the far-field and tunnel faces pinned, keeping the largest sub-step that inverts no cell — a size-preserving quality pass first, then an exact attempt, smoothed passes coarse-to-fine, and a raw polish of the residual. The report states the carve fit before and the fit after (on a sharp box, about 1.6% down to below 0.3% of the body length); when no sub-step is clean the wall stays at the carve and the report says so. Uncheck Wall snap to keep the carve;
  5. the wall layer: remesh in wall mode grades the cells by the distance to the wall — the size across it starts at the first height for the target y+ and grows geometrically, the size along it starts at the surface size and is limited by the maximum aspect ratio;
  6. hard checks (no inverted or zero-volume tetrahedra, every boundary face on a named part) and a measured wall fit.

The fluid becomes the Fluid SubModelPart and the wall the Body part — or, with several bodies, one Body_1…n part per body plus a Bodies parent holding the whole wall. The mesh is written as <name>.mdpa, with <name>_body.stl (the body as placed), <name>.kratosvwt.json (the parameters and the report) and <name>.kratoscase.json — the prepared case for the Fluid problemtype: the inlet at the stream speed along +X, a zero-pressure outlet, slip walls on the ceiling and sides, the floor you chose, the linear-logarithmic wall law on the body with the first node row as the wall distance (or no-slip when y+ is below 5), time stepping from the flow-through time of the body, and four monitors on the body: forces and moments, aerodynamic coefficients (reference area = the frontal area, reference length = the body length, dynamic pressure from the flow), the pressure coefficient and the wall y+. Open the mesh shows it in the preview with that case already in the Problemtype section; with Solver files ticked it also writes ProjectParameters.json, the materials and MainKratos.py.

Embedded skips the carve and the wall layer: the surface stays unfitted, the lattice is refined towards it and the signed DISTANCE is written for the Embedded Fluid problemtype, whose prepared case uses the embedded force-and-moment process.

Several bodies share one tunnel: the distance comes from the union of the surfaces, the wall is split per body after the snap, the first body gives the reference length, area and moment point, and every body gets its own force, coefficient, pressure-coefficient and y+ monitors (a RANS case puts its single wall-function wall on Bodies). Headlessly, wind_tunnel_plan/wind_tunnel_generate take a bodies array of {path, unit, rotation, position, name} instead of bodyPath.

What to expect ​

  • A stretched-tetrahedron layer, not prisms. MMG makes tetrahedra, so the boundary layer is a field of cells that are thin across the wall and long along it. That suits a wall function (y+ 30–100) well; a wall-resolved mesh (y+ ≈ 1) needs a low-Reynolds turbulence model and far more cells, and the plan says so.
  • The wall follows the surface to a stated tolerance. The carve cuts the zero of a sampled distance, so a sharp edge is followed to about 1–2% of the body length; the wall snap then pulls it below 0.3% (measured on a sharp box: carve fit 1.67% → 0.29%). The report states the carve fit before, the fit after, and the final measured largest and mean gap (as fractions of the body length), and warns when the largest exceeds half the surface size. A finer carve factor, surface size or fit lowers the carve fit at a cost; a smooth body is followed to a few tenths of a percent.
  • Turbulence. With None the prepared case is a Fluid case: at wind-tunnel Reynolds numbers it applies only a wall law and the dynamic subscales of QSVMS, so the forces are a first estimate (the plan says so). With a RANS model the case is a steady Turbulent Flow (RANS) case: the body is the single wall-function part (with its roughness material), the inlet carries the free-stream turbulence, a no-slip floor becomes a zero-velocity constraint (the wall function belongs to the body), the run is up to 300 pseudo-time steps and stops earlier when the solver converges. RANS needs the body-fitted mesh and a first node row at y+ 30–100.
  • Size. Measured in the extension's workers on a sharp box and a smooth sphere over a ladder of surface sizes (ten runs, each in a fresh process with the worker runners installed): about 100 000 tetrahedra take 25 s at 2 GB peak, 500 000 about 2 minutes at 2.9 GB, one million about 4 minutes at 3.5 GB. The plan refuses above 3 million tetrahedra (adjustable) before any meshing starts. The estimate is an ideal-tetrahedron integral of the sizing law plus each refinement region's volume at its own size, through a power law fitted on those runs (measured = 248.4669 * ideal^0.629; the calibration lives in src/test/fixtures/windTunnel/estimate-refit-2026-10-20.json): expect it within about a factor of two. Refinement regions dominate the count — a region at 0.06 m inside a 13.5 m³ box asks for half a million cells on its own.
  • Solver run. The wizard's cases have been run through Kratos in this repository: the body-fitted tunnel sphere runs with the Fluid case and with all three RANS models (the wall function converges with the first row in the logarithmic layer, and the Y_PLUS output reads back mean ~81 with 54–55% of faces in 30–300), the embedded mesh is accepted by the Embedded solver, and the wall y+ monitor is a condition-typed output on the wall's own part (a file beside the volume output). The verification harness is scripts/verify-fluid/run.mjs, and CHANGELOG.md records what it proved.

Headless ​

jsonc
// wind_tunnel_plan: read-only; nothing is meshed
{ "bodyPath": "car.stl", "unit": "mm", "rotation": [0, 0, 90],
  "tunnel": { "ground": "moving", "clearance": 0.1, "halfModel": true },
  "flow": { "speed": 40 },
  "mesh": { "surfaceSize": 0.05, "yPlus": 60 } }

wind_tunnel_generate takes the same object plus outputFolder, stem, overwrite and generateCase, reports progress through notifications/progress, stops between steps on cancellation and refuses to overwrite existing files unless asked.

Released under the GNU AGPL-3.0-or-later License.