{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "1b8ba1b6",
   "metadata": {},
   "source": [
    "# F1 Car Demo\n",
    "\n",
    "This notebook demonstrates how to set up and run an F1 car simulation using the Flow360 Python API.\n",
    "\n",
    "The project starts from the geometry and goes through:\n",
    "- meshing using the Snappy Mesher:\n",
    "  - setting up the meshing task using `ModularMeshingWorkflow`,\n",
    "  - setting multiple zones to be created for porous media,\n",
    "  - generating a surface mesh using snappy\n",
    "- setting up a RANS simulation:\n",
    "  - defining operating conditions (freestream speed and car attitude),\n",
    "  - setting up physical models (walls, moving ground, rotating wheels, porous media, freestream boundaries),\n",
    "  - defining a custom output variable and outputs,\n",
    "  - submitting a simulation run through `project.run_case()`.\n",
    "\n",
    "![F1 car geometry](figures/F1_car_mesh.png)\n",
    "\n",
    "**Note:** The settings in this example are by no means a validation setup; they are crafted to showcase the capabilities of Flow360 and we have intentionally reduced node count and example FC cost. For rigorous validation, modify the settings as needed."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1db742e9",
   "metadata": {},
   "source": [
    "## 1. Imports\n",
    "\n",
    "The next code cell imports the Flow360 Python API and some math helpers that will be reused throughout the notebook.\n",
    "\n",
    "Specifically, we will:\n",
    "- Import `flow360` as `fl`, which provides access to all high-level API objects (projects, boundary conditions, models, outputs, etc.).\n",
    "- Import `cos`, `sin`, and `radians` from the standard `math` module to construct moving-ground and rotating-wheel velocities.\n",
    "- Import `numpy` as `np` for creating arrays of slice positions for post-processing outputs."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "db389704",
   "metadata": {},
   "outputs": [],
   "source": [
    "import flow360 as fl\n",
    "from math import cos, sin, radians\n",
    "import numpy as np\n",
    "from flow360.examples import F1_2025"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cf8d5ba6",
   "metadata": {},
   "source": [
    "## 2. Project Setup\n",
    "\n",
    "The next code cell creates a Flow360 `Project` from a geometry. The geometry used is an example Formula 1 car from the 2025 regulations era. It is supplied as a set of component STL files — one per snappy body — which `F1_2025.get_files()` downloads automatically when the cell runs.\n",
    "\n",
    "The geometry is grouped for use with snappy. The grouping produces `BodyGroups` — the equivalent of a *searchableSurfaceWithGaps* in a *snappyHexMesh* config — and `Surface` objects, which are the named faces of those bodies. Bodies are grouped **by file**, so each component STL file becomes a `BodyGroup`, and faces are grouped **by face** into the regions of those bodies. This is handled by the `group_bodies_by_tag(\"groupByFile\")` and `group_faces_by_tag(\"faceId\")` calls in the next cell; no special face-naming convention is required.\n",
    "\n",
    "The geometry consists of the following files, which you can also download directly:\n",
    "\n",
    "- [body.stl.zst](https://simcloud-public-1.s3.amazonaws.com/examples/f1_2025_split/body.stl.zst)\n",
    "- [eb.stl.zst](https://simcloud-public-1.s3.amazonaws.com/examples/f1_2025_split/eb.stl.zst)\n",
    "- [fr-int.stl.zst](https://simcloud-public-1.s3.amazonaws.com/examples/f1_2025_split/fr-int.stl.zst)\n",
    "- [fr-susp.stl.zst](https://simcloud-public-1.s3.amazonaws.com/examples/f1_2025_split/fr-susp.stl.zst)\n",
    "- [fr-wh.stl.zst](https://simcloud-public-1.s3.amazonaws.com/examples/f1_2025_split/fr-wh.stl.zst)\n",
    "- [fw.stl.zst](https://simcloud-public-1.s3.amazonaws.com/examples/f1_2025_split/fw.stl.zst)\n",
    "- [inflows_outflows.stl.zst](https://simcloud-public-1.s3.amazonaws.com/examples/f1_2025_split/inflows_outflows.stl.zst)\n",
    "- [rr-int.stl.zst](https://simcloud-public-1.s3.amazonaws.com/examples/f1_2025_split/rr-int.stl.zst)\n",
    "- [rr-susp.stl.zst](https://simcloud-public-1.s3.amazonaws.com/examples/f1_2025_split/rr-susp.stl.zst)\n",
    "- [rr-wh.stl.zst](https://simcloud-public-1.s3.amazonaws.com/examples/f1_2025_split/rr-wh.stl.zst)\n",
    "- [rw.stl.zst](https://simcloud-public-1.s3.amazonaws.com/examples/f1_2025_split/rw.stl.zst)\n",
    "- [toint-rad.stl.zst](https://simcloud-public-1.s3.amazonaws.com/examples/f1_2025_split/toint-rad.stl.zst)\n",
    "- [tunnel.stl.zst](https://simcloud-public-1.s3.amazonaws.com/examples/f1_2025_split/tunnel.stl.zst)\n",
    "- [uf.stl.zst](https://simcloud-public-1.s3.amazonaws.com/examples/f1_2025_split/uf.stl.zst)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9402b9d8",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">[15:30:36] </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: The file <span style=\"font-weight: bold\">(</span>body.stl.zst<span style=\"font-weight: bold\">)</span> is being downloaded, please wait.                                         \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m[15:30:36]\u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: The file \u001b[1m(\u001b[0mbody.stl.zst\u001b[1m)\u001b[0m is being downloaded, please wait.                                         \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">[15:30:41] </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: The file <span style=\"font-weight: bold\">(</span>eb.stl.zst<span style=\"font-weight: bold\">)</span> is being downloaded, please wait.                                           \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m[15:30:41]\u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: The file \u001b[1m(\u001b[0meb.stl.zst\u001b[1m)\u001b[0m is being downloaded, please wait.                                           \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">[15:30:48] </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: The file <span style=\"font-weight: bold\">(</span>fr-int.stl.zst<span style=\"font-weight: bold\">)</span> is being downloaded, please wait.                                       \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m[15:30:48]\u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: The file \u001b[1m(\u001b[0mfr-int.stl.zst\u001b[1m)\u001b[0m is being downloaded, please wait.                                       \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">[15:30:57] </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: The file <span style=\"font-weight: bold\">(</span>fr-susp.stl.zst<span style=\"font-weight: bold\">)</span> is being downloaded, please wait.                                      \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m[15:30:57]\u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: The file \u001b[1m(\u001b[0mfr-susp.stl.zst\u001b[1m)\u001b[0m is being downloaded, please wait.                                      \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">[15:31:39] </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: The file <span style=\"font-weight: bold\">(</span>fr-wh.stl.zst<span style=\"font-weight: bold\">)</span> is being downloaded, please wait.                                        \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m[15:31:39]\u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: The file \u001b[1m(\u001b[0mfr-wh.stl.zst\u001b[1m)\u001b[0m is being downloaded, please wait.                                        \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">[15:31:43] </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: The file <span style=\"font-weight: bold\">(</span>fw.stl.zst<span style=\"font-weight: bold\">)</span> is being downloaded, please wait.                                           \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m[15:31:43]\u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: The file \u001b[1m(\u001b[0mfw.stl.zst\u001b[1m)\u001b[0m is being downloaded, please wait.                                           \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">[15:31:44] </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: The file <span style=\"font-weight: bold\">(</span>inflows_outflows.stl.zst<span style=\"font-weight: bold\">)</span> is being downloaded, please wait.                             \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m[15:31:44]\u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: The file \u001b[1m(\u001b[0minflows_outflows.stl.zst\u001b[1m)\u001b[0m is being downloaded, please wait.                             \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">[15:31:53] </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: The file <span style=\"font-weight: bold\">(</span>rr-int.stl.zst<span style=\"font-weight: bold\">)</span> is being downloaded, please wait.                                       \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m[15:31:53]\u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: The file \u001b[1m(\u001b[0mrr-int.stl.zst\u001b[1m)\u001b[0m is being downloaded, please wait.                                       \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">[15:32:02] </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: The file <span style=\"font-weight: bold\">(</span>rr-susp.stl.zst<span style=\"font-weight: bold\">)</span> is being downloaded, please wait.                                      \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m[15:32:02]\u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: The file \u001b[1m(\u001b[0mrr-susp.stl.zst\u001b[1m)\u001b[0m is being downloaded, please wait.                                      \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">[15:32:49] </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: The file <span style=\"font-weight: bold\">(</span>rr-wh.stl.zst<span style=\"font-weight: bold\">)</span> is being downloaded, please wait.                                        \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m[15:32:49]\u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: The file \u001b[1m(\u001b[0mrr-wh.stl.zst\u001b[1m)\u001b[0m is being downloaded, please wait.                                        \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">[15:32:52] </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: The file <span style=\"font-weight: bold\">(</span>rw.stl.zst<span style=\"font-weight: bold\">)</span> is being downloaded, please wait.                                           \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m[15:32:52]\u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: The file \u001b[1m(\u001b[0mrw.stl.zst\u001b[1m)\u001b[0m is being downloaded, please wait.                                           \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">[15:32:53] </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: The file <span style=\"font-weight: bold\">(</span>toint-rad.stl.zst<span style=\"font-weight: bold\">)</span> is being downloaded, please wait.                                    \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m[15:32:53]\u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: The file \u001b[1m(\u001b[0mtoint-rad.stl.zst\u001b[1m)\u001b[0m is being downloaded, please wait.                                    \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">[15:32:57] </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: The file <span style=\"font-weight: bold\">(</span>tunnel.stl.zst<span style=\"font-weight: bold\">)</span> is being downloaded, please wait.                                       \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m[15:32:57]\u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: The file \u001b[1m(\u001b[0mtunnel.stl.zst\u001b[1m)\u001b[0m is being downloaded, please wait.                                       \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">[15:33:11] </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: The file <span style=\"font-weight: bold\">(</span>uf.stl.zst<span style=\"font-weight: bold\">)</span> is being downloaded, please wait.                                           \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m[15:33:11]\u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: The file \u001b[1m(\u001b[0muf.stl.zst\u001b[1m)\u001b[0m is being downloaded, please wait.                                           \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">[15:36:49] </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: Geometry successfully submitted:                                                                  \n",
       "<span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span>        type        = Geometry                                                                          \n",
       "<span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span>        name        = F1 racecar                                                                        \n",
       "<span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span>        id          = geo-<span style=\"color: #ffff00; text-decoration-color: #ffff00\">b81b051e-4f19-462c-9232-f26e4a39f7ff</span>                                          \n",
       "<span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span>        status      = uploaded                                                                          \n",
       "<span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span>        project id  = prj-<span style=\"color: #ffff00; text-decoration-color: #ffff00\">bde06929-783f-4489-8fe1-567801c58404</span>                                          \n",
       "<span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span>                                                                                                        \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m[15:36:49]\u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: Geometry successfully submitted:                                                                  \n",
       "\u001b[2;36m           \u001b[0m        type        = Geometry                                                                          \n",
       "\u001b[2;36m           \u001b[0m        name        = F1 racecar                                                                        \n",
       "\u001b[2;36m           \u001b[0m        id          = geo-\u001b[93mb81b051e-4f19-462c-9232-f26e4a39f7ff\u001b[0m                                          \n",
       "\u001b[2;36m           \u001b[0m        status      = uploaded                                                                          \n",
       "\u001b[2;36m           \u001b[0m        project id  = prj-\u001b[93mbde06929-783f-4489-8fe1-567801c58404\u001b[0m                                          \n",
       "\u001b[2;36m           \u001b[0m                                                                                                        \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: Waiting for geometry to be processed.                                                             \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m          \u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: Waiting for geometry to be processed.                                                             \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "F1_2025.get_files()\n",
    "\n",
    "project = fl.Project.from_geometry(F1_2025.geometry, name=\"F1 Car Demo\", length_unit=\"m\")\n",
    "\n",
    "# uncomment the following line if you already have a project uploaded\n",
    "# project = fl.Project.from_cloud(project_id=\"your-project-id\")\n",
    "\n",
    "geometry = project.geometry\n",
    "\n",
    "geometry.group_bodies_by_tag(\"groupByFile\")\n",
    "geometry.group_faces_by_tag(\"faceId\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "684430dd",
   "metadata": {},
   "source": [
    "## 3. Meshing setup\n",
    "\n",
    "In the following cells the meshing task will be set up using the `ModularMeshingWorkflow`. \n",
    "It needs three parameters to be defined: `surface_meshing`, `volume_meshing` and `zones`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "84ee62c4",
   "metadata": {},
   "source": [
    "### 3.1 Zones\n",
    "\n",
    "First the volume zones will be specified. With **Snappy** the volume zones are defined through the `SeedpointVolume` objects by providing a point that belongs to that volume. It is recommended that this point does not lay on \"round\" (integer) coordinates for the *snappyHexMesh* utility not to break.\n",
    "\n",
    "In this case three volume zones will be defined:\n",
    "- left hand side radiator\n",
    "- right hand side radiator\n",
    "- fluid\n",
    "\n",
    "Additionally to the point in mesh, the principal axes are defined to define th coefficients of the `PorousMedium` later on."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "b4a9a552",
   "metadata": {},
   "outputs": [],
   "source": [
    "rad_lhs = fl.SeedpointVolume(\n",
    "    name=\"rad_lhs\",\n",
    "    point_in_mesh=[(2040.53452, -405.234, 395.34243) * fl.u.mm],\n",
    "    axes=[[19.98, 6.62, 29.21], [48.076066, -210.05, 14.72]],\n",
    ")\n",
    "\n",
    "rad_rhs = fl.SeedpointVolume(\n",
    "    name=\"rad_rhs\",\n",
    "    point_in_mesh=[(2040.53452, 405.234, 395.34243) * fl.u.mm],\n",
    "    axes=[[19.98, -6.62, 29.21], [48.076066, 210.05, 14.72]],\n",
    ")\n",
    "\n",
    "fluid = fl.SeedpointVolume(point_in_mesh=[(0.234, 0.18172, 2.1324) * fl.u.m], name=\"fluid\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9783c8d3",
   "metadata": {},
   "source": [
    "### 3.2 Surface meshing\n",
    "\n",
    "The surface mesh is generated using the Snappy mesher. The `fl.snappy.SurfaceMeshingParams` object controls the surface mesh generation.\n",
    "\n",
    "#### 3.2.1 General settings\n",
    "\n",
    "Firstly general meshing settings (or specifications) will be defined, those include:\n",
    "\n",
    "- **`base_spacing`**: as an instance of `fl.OctreeSpacing` is specified to achieve a fine control over the spacings.*snappyHexMesh* is an octree based algorithm, meaning that all of the spacings will be the powers of 2 of the master spacing. `fl.OctreeSpacing` allows to set that master spacing. Individual refinement levels can be then accessed using array indexing (e.g. `spacing[1]` the master cell divided once), but they can be also defined using numerical values (those values will be cast to the first lower spacing in the octree series).\n",
    "- **`defaults`**: `fl.snappy.SurfaceMeshingDefaults` sets the global minimum and maximum spacing, as well as the gap resolution for proximity detection.\n",
    "- **`smooth_controls`**: Laplacian smoothing parameters (`lambda_factor`, `mu_factor`, `iterations`) to improve surface mesh quality.\n",
    "- **`castellated_mesh_controls`**: controls for the castellated mesh phase, including `resolve_feature_angle` which determines how sharp edges are captured."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "2be72259",
   "metadata": {},
   "outputs": [],
   "source": [
    "surface_defaults = fl.snappy.SurfaceMeshingDefaults(\n",
    "    min_spacing=8 * fl.u.mm,\n",
    "    max_spacing=16 * fl.u.mm,\n",
    "    gap_resolution=0.001\n",
    "    * fl.u.mm,  # gaps of this length should be sealed by the mesher, can be specified per face\n",
    ")\n",
    "\n",
    "spacing = fl.OctreeSpacing(base_spacing=1 * fl.u.mm)\n",
    "\n",
    "smooth_controls = fl.snappy.SmoothControls(\n",
    "    lambda_factor=0.7,\n",
    "    mu_factor=0.71,\n",
    "    iterations=5,\n",
    ")\n",
    "\n",
    "castellated_mesh_controls = fl.snappy.CastellatedMeshControls(\n",
    "    resolve_feature_angle=18.0 * fl.u.deg,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0446275e",
   "metadata": {},
   "source": [
    "#### 3.2.2 Uniform surface refinements\n",
    "\n",
    "Refinements are defined as a list of objects that override the defaults on specific bodies, regions, or edges in the `refinements` parameter. As first refinements, to represent the contact patch and the plinths well, `fl.UniformRefinement` objects with hockey puck like cylinders (`plinth_pucks`) are defined only for the surface mesher around the plinths to avoid bridging between the ground and the tire. \n",
    "\n",
    "This is a refinement type applied to a specific volume (if its defined only in the surface mesher, it will not affect the volume mesh directly)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "88c04d82",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">[15:41:11] </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: using: SI unit system for unit inference.                                                         \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m[15:41:11]\u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: using: SI unit system for unit inference.                                                         \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "with fl.SI_unit_system:\n",
    "    plinth_pucks = fl.UniformRefinement(\n",
    "        entities=[\n",
    "            fl.Cylinder(\n",
    "                name=\"fr-lhs-puck\",\n",
    "                center=[200, -800, 10] * fl.u.mm,\n",
    "                axis=[0, 0, 1],\n",
    "                outer_radius=150 * fl.u.mm,\n",
    "                height=15 * fl.u.mm,\n",
    "            ),\n",
    "            fl.Cylinder(\n",
    "                name=\"fr-rhs-puck\",\n",
    "                center=[200, 800, 10] * fl.u.mm,\n",
    "                axis=[0, 0, 1],\n",
    "                outer_radius=150 * fl.u.mm,\n",
    "                height=15 * fl.u.mm,\n",
    "            ),\n",
    "            fl.Cylinder(\n",
    "                name=\"rr-lhs-puck\",\n",
    "                center=[3650, -765, -10] * fl.u.mm,\n",
    "                axis=[0, 0, 1],\n",
    "                outer_radius=200 * fl.u.mm,\n",
    "                height=15 * fl.u.mm,\n",
    "            ),\n",
    "            fl.Cylinder(\n",
    "                name=\"rr-rhs-puck\",\n",
    "                center=[3650, 765, -10] * fl.u.mm,\n",
    "                axis=[0, 0, 1],\n",
    "                outer_radius=200 * fl.u.mm,\n",
    "                height=15 * fl.u.mm,\n",
    "            ),\n",
    "        ],\n",
    "        spacing=0.5 * fl.u.mm,\n",
    "    )"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "47dfd0ef",
   "metadata": {},
   "source": [
    "#### 3.2.3 Specific surface refinements\n",
    "\n",
    "Finally refinements on specific surfaces are defined. Bodies and faces are addressed through the regular geometry registry — `geometry[\"<file>.stl\"]` returns a body group and `geometry[\"<solid>\"]` returns a face/`Surface` (with wildcard support on both). Available types of surface specific refinements include:\n",
    "  - `fl.snappy.BodyRefinement` sets min/max spacing on entire snappy bodies (i.e. whole component files such as `tunnel.stl` or `fw.stl`); its `bodies=` argument takes body groups.\n",
    "  - `fl.snappy.RegionRefinement` targets named faces within a body (e.g. plinth pads, the helmet, individual rim faces); its `regions=` argument takes `Surface` entities.\n",
    "  - `fl.snappy.SurfaceEdgeRefinement` refines near sharp edges, with optional angle thresholds and distance-based specification; its `entities=` argument accepts either body groups or faces."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "67e2505f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: using: SI unit system for unit inference.                                                         \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m          \u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: using: SI unit system for unit inference.                                                         \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "with fl.SI_unit_system:\n",
    "    surface_refinements = [\n",
    "        # --- Tunnel (farfield) — refines the whole tunnel body ---\n",
    "        fl.snappy.BodyRefinement(\n",
    "            bodies=[geometry[\"tunnel.stl\"]],\n",
    "            min_spacing=spacing[-11],\n",
    "            max_spacing=spacing[-11],\n",
    "        ),\n",
    "        # --- Plinth regions  ---\n",
    "        fl.snappy.RegionRefinement(\n",
    "            regions=geometry[\"*plinth*\"],\n",
    "            min_spacing=spacing[1],\n",
    "            max_spacing=spacing[-2],\n",
    "        ),\n",
    "        fl.snappy.SurfaceEdgeRefinement(\n",
    "            entities=geometry[\"*plinth*\"],\n",
    "            spacing=[2 * fl.u.mm],\n",
    "            distances=[5 * fl.u.mm],\n",
    "        ),\n",
    "        fl.snappy.SurfaceEdgeRefinement(\n",
    "            entities=[geometry[\"tunnel.stl\"]],\n",
    "            spacing=[spacing[-11]],\n",
    "            distances=[2 * fl.u.mm],\n",
    "            included_angle=140 * fl.u.deg,\n",
    "        ),\n",
    "        # --- Radiator and brake-duct edges ---\n",
    "        fl.snappy.SurfaceEdgeRefinement(\n",
    "            entities=[\n",
    "                geometry[\"toint-rad-main-*\"],\n",
    "                geometry[\"body-main-rad-duct\"],\n",
    "                geometry[\"*-int-brake-duct-*\"],\n",
    "            ],\n",
    "            spacing=[1 * fl.u.mm],\n",
    "            distances=[2 * fl.u.mm],\n",
    "        ),\n",
    "        # --- Wheels (rims only), helmet, pit ---\n",
    "        fl.snappy.RegionRefinement(\n",
    "            regions=[\n",
    "                geometry[\"*wh-rim*\"],\n",
    "                geometry[\"body-helmet\"],\n",
    "                geometry[\"body-pit\"],\n",
    "            ],\n",
    "            min_spacing=1 * fl.u.mm,\n",
    "            max_spacing=4 * fl.u.mm,\n",
    "        ),\n",
    "        # --- Body miscellaneous  ---\n",
    "        fl.snappy.RegionRefinement(\n",
    "            regions=[geometry[\"body-misc\"]],\n",
    "            min_spacing=0.5 * fl.u.mm,\n",
    "            max_spacing=3 * fl.u.mm,\n",
    "        ),\n",
    "        # --- Cameras, mirrors, strakes, pillars, vanes, etc. ---\n",
    "        fl.snappy.RegionRefinement(\n",
    "            regions=[\n",
    "                geometry[\"body-camera\"],\n",
    "                geometry[\"body-aero-camera\"],\n",
    "                geometry[\"body-steering-wheel\"],\n",
    "                geometry[\"uf-strakes\"],\n",
    "                geometry[\"body-mirror\"],\n",
    "                geometry[\"rw-pillar\"],\n",
    "                geometry[\"rr-int-vanes-*\"],\n",
    "                geometry[\"body-front-af\"],\n",
    "                geometry[\"body-main-rad-duct\"],\n",
    "            ],\n",
    "            min_spacing=2 * fl.u.mm,\n",
    "            max_spacing=8 * fl.u.mm,\n",
    "        ),\n",
    "        # --- Suspension ---\n",
    "        fl.snappy.BodyRefinement(\n",
    "            bodies=[geometry[\"fr-susp.stl\"], geometry[\"rr-susp.stl\"]],\n",
    "            min_spacing=4 * fl.u.mm,\n",
    "            max_spacing=8 * fl.u.mm,\n",
    "            proximity_spacing=0.5 * fl.u.mm,\n",
    "        ),\n",
    "        # --- Front intake winglets & caketins ---\n",
    "        fl.snappy.RegionRefinement(\n",
    "            regions=[\n",
    "                geometry[\"fr-int-winglet-*\"],\n",
    "                geometry[\"fr-int-caketin-*\"],\n",
    "            ],\n",
    "            min_spacing=2 * fl.u.mm,\n",
    "            max_spacing=8 * fl.u.mm,\n",
    "            proximity_spacing=0.5 * fl.u.mm,\n",
    "        ),\n",
    "        # --- Main body + nose ---\n",
    "        fl.snappy.RegionRefinement(\n",
    "            regions=[\n",
    "                geometry[\"body\"],\n",
    "                geometry[\"body-nose\"],\n",
    "            ],\n",
    "            min_spacing=4 * fl.u.mm,\n",
    "            max_spacing=8 * fl.u.mm,\n",
    "        ),\n",
    "        # --- Front wing ---\n",
    "        fl.snappy.BodyRefinement(\n",
    "            bodies=[geometry[\"fw.stl\"]],\n",
    "            min_spacing=2 * fl.u.mm,\n",
    "            max_spacing=8 * fl.u.mm,\n",
    "        ),\n",
    "        # --- Wheel covers and selected rear-wing elements ---\n",
    "        fl.snappy.RegionRefinement(\n",
    "            regions=[\n",
    "                geometry[\"*wh-cover-OB*\"],\n",
    "                geometry[\"rw-ep\"],\n",
    "                geometry[\"rw-mp\"],\n",
    "                geometry[\"rw-flap\"],\n",
    "            ],\n",
    "            min_spacing=2 * fl.u.mm,\n",
    "            max_spacing=8 * fl.u.mm,\n",
    "        ),\n",
    "        # --- Underfloor + diffuser ---\n",
    "        fl.snappy.RegionRefinement(\n",
    "            regions=[\n",
    "                geometry[\"uf\"],\n",
    "                geometry[\"diffuser\"],\n",
    "            ],\n",
    "            min_spacing=2 * fl.u.mm,\n",
    "            max_spacing=8 * fl.u.mm,\n",
    "        ),\n",
    "        # --- Edge refinement on aero surfaces ---\n",
    "        fl.snappy.SurfaceEdgeRefinement(\n",
    "            entities=[\n",
    "                geometry[\"fw-flap-*\"],\n",
    "                geometry[\"fw-fine\"],\n",
    "                geometry[\"fw-ep\"],\n",
    "                geometry[\"rw-mp\"],\n",
    "                geometry[\"rw-flap\"],\n",
    "                geometry[\"diffuser\"],\n",
    "                geometry[\"uf-strakes\"],\n",
    "                geometry[\"body-pit\"],\n",
    "            ],\n",
    "            spacing=1 * fl.u.mm,\n",
    "            min_elem=3,\n",
    "            retain_on_smoothing=True,\n",
    "        ),\n",
    "        # --- Fine edge refinement on critical aero edges ---\n",
    "        fl.snappy.SurfaceEdgeRefinement(\n",
    "            entities=[\n",
    "                geometry[\"fw-mp\"],\n",
    "                geometry[\"rw-gf\"],\n",
    "                geometry[\"rw-ep\"],\n",
    "                geometry[\"velocity-*\"],\n",
    "                geometry[\"eb-exhaust\"],\n",
    "            ],\n",
    "            spacing=[0.5 * fl.u.mm],\n",
    "            distances=[1 * fl.u.mm],\n",
    "            min_elem=10,\n",
    "            retain_on_smoothing=True,\n",
    "        ),\n",
    "    ]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "80f2215c",
   "metadata": {},
   "source": [
    "The next code cell assembles the `fl.snappy.SurfaceMeshingParams` object using the defaults and refinements defined above.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "116f488b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: using: SI unit system for unit inference.                                                         \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m          \u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: using: SI unit system for unit inference.                                                         \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span><span style=\"color: #808000; text-decoration-color: #808000\">WARNING</span>: The spacing of <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">3</span> mm specified in RegionRefinement will be cast to the first lower refinement in\n",
       "<span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span>the octree series <span style=\"font-weight: bold\">(</span><span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">2</span> mm<span style=\"font-weight: bold\">)</span>.                                                                               \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m          \u001b[0m\u001b[2;36m \u001b[0m\u001b[33mWARNING\u001b[0m: The spacing of \u001b[1;36m3\u001b[0m mm specified in RegionRefinement will be cast to the first lower refinement in\n",
       "\u001b[2;36m           \u001b[0mthe octree series \u001b[1m(\u001b[0m\u001b[1;36m2\u001b[0m mm\u001b[1m)\u001b[0m.                                                                               \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "with fl.SI_unit_system:\n",
    "    # -----------------------------------------------------------------\n",
    "    # Assemble surface meshing parameters\n",
    "    # -----------------------------------------------------------------\n",
    "    surface_meshing = fl.snappy.SurfaceMeshingParams(\n",
    "        defaults=surface_defaults,\n",
    "        refinements=surface_refinements + [plinth_pucks],\n",
    "        octree_spacing=spacing,\n",
    "        smooth_controls=smooth_controls,\n",
    "        castellated_mesh_controls=castellated_mesh_controls,\n",
    "    )"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "093e6023",
   "metadata": {},
   "source": [
    "### 3.3 Volume meshing\n",
    "\n",
    "The volume mesh is generated after the surface mesh. `fl.VolumeMeshingParams` controls the volume mesh generation, including:\n",
    "\n",
    "- **Defaults**: `fl.VolumeMeshingDefaults` sets the global boundary-layer first-layer thickness and growth rate applied to all walls unless overridden.\n",
    "- **Refinements**: a list of refinement objects that control local mesh density:\n",
    "  - `fl.PassiveSpacing` controls howthe  boundary layer should be generated (e.g. farfield boundaries). The `type` can be `\"unchanged\"` (do not project) or `\"projected\"` (project boundary layer on the surface - for example on tunnel inlet or outlet).\n",
    "  - `fl.BoundaryLayer` overrides the first-layer thickness and growth rate on specific surfaces (e.g. finer layers on aero surfaces, coarser on the ground).\n",
    "  - `fl.UniformRefinement` defines volumetric refinement zones using primitive shapes (`fl.Box`, `fl.Cylinder`) to increase resolution in regions of interest (e.g. around the front and rear wings, or under the floor).\n",
    "- **Planar face tolerance**: controls the tolerance of planar face detection for restoring their planarity..\n",
    "- **Gap treatment strength**: adjusts boundary layer generation behaviour in narrow gaps. For automotive applications, setting to 1 is recommended.\n",
    "\n",
    "The code cell below defines volumetric refinement boxes around the front and rear wings, and boundary-layer settings for the ground and aero surfaces."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "b215519d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: using: SI unit system for unit inference.                                                         \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m          \u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: using: SI unit system for unit inference.                                                         \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "with fl.SI_unit_system:\n",
    "    # -----------------------------------------------------------------\n",
    "    # Volume refinements: boxes around regions of interest\n",
    "    # -----------------------------------------------------------------\n",
    "    volume_refinements = [\n",
    "        fl.UniformRefinement(\n",
    "            entities=[\n",
    "                fl.Box(\n",
    "                    name=\"fr_wing\",\n",
    "                    center=[-650, 0, 250] * fl.u.mm,\n",
    "                    size=[900, 2100, 600] * fl.u.mm,\n",
    "                ),\n",
    "                fl.Box(\n",
    "                    name=\"rr_wing\",\n",
    "                    center=[4050, 0, 700] * fl.u.mm,\n",
    "                    size=[700, 1400, 800] * fl.u.mm,\n",
    "                ),\n",
    "            ],\n",
    "            spacing=16 * fl.u.mm,\n",
    "        ),\n",
    "        fl.UniformRefinement(\n",
    "            entities=[\n",
    "                fl.Box(\n",
    "                    name=\"whole\",\n",
    "                    center=[2000, 0, 750] * fl.u.mm,\n",
    "                    size=[6500, 2300, 1600] * fl.u.mm,\n",
    "                )\n",
    "            ],\n",
    "            spacing=32 * fl.u.mm,\n",
    "        ),\n",
    "    ]\n",
    "\n",
    "    # -----------------------------------------------------------------\n",
    "    # Volume meshing parameters\n",
    "    # -----------------------------------------------------------------\n",
    "    volume_meshing = fl.VolumeMeshingParams(\n",
    "        defaults=fl.VolumeMeshingDefaults(\n",
    "            boundary_layer_first_layer_thickness=0.5 * fl.u.mm,\n",
    "            boundary_layer_growth_rate=1.2,\n",
    "            octree_spacing=spacing,\n",
    "            number_of_boundary_layers=5,\n",
    "        ),\n",
    "        refinements=[\n",
    "            # --- Passive spacing on farfield faces ---\n",
    "            fl.PassiveSpacing(\n",
    "                faces=[\n",
    "                    geometry[\"tunnel-top\"],\n",
    "                    geometry[\"tunnel-side*\"],\n",
    "                ],\n",
    "                type=\"unchanged\",\n",
    "            ),\n",
    "            fl.PassiveSpacing(\n",
    "                faces=[\n",
    "                    geometry[\"tunnel-inlet\"],\n",
    "                    geometry[\"tunnel-outlet\"],\n",
    "                ],\n",
    "                type=\"projected\",\n",
    "            ),\n",
    "            # --- Boundary layer on ground ---\n",
    "            fl.BoundaryLayer(\n",
    "                faces=[geometry[\"tunnel-ground\"]],\n",
    "                first_layer_thickness=1 * fl.u.mm,\n",
    "                growth_rate=1.4,\n",
    "            ),\n",
    "            # --- Finer boundary layer on aero surfaces ---\n",
    "            fl.BoundaryLayer(\n",
    "                faces=[\n",
    "                    geometry[\"fw*\"],\n",
    "                    geometry[\"rw*\"],\n",
    "                    geometry[\"uf*\"],\n",
    "                    geometry[\"diffuser\"],\n",
    "                ],\n",
    "                first_layer_thickness=0.2 * fl.u.mm,\n",
    "            ),\n",
    "            # --- Suspension boundary layer ---\n",
    "            fl.BoundaryLayer(\n",
    "                faces=[geometry[\"*susp*\"]],\n",
    "                first_layer_thickness=0.35 * fl.u.mm,\n",
    "            ),\n",
    "            # --- Volume refinements ---\n",
    "            *volume_refinements,\n",
    "        ],\n",
    "        planar_face_tolerance=1e-3,\n",
    "        gap_treatment_strength=1,\n",
    "    )"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d6861e73",
   "metadata": {},
   "source": [
    "### 3.4 Full workflow\n",
    "\n",
    "Finally, all the meshing components are assembled into a single `fl.ModularMeshingWorkflow` object:\n",
    "\n",
    "- `surface_meshing`: the `fl.snappy.SurfaceMeshingParams` object defined in Section 3.2, controlling the snappy surface mesh generation.\n",
    "- `volume_meshing`: the `fl.VolumeMeshingParams` object defined in Section 3.3, controlling boundary layers and volume refinements.\n",
    "- `zones`: a `fl.CustomZones` object containing the three `SeedpointVolume` entities (`fluid`, `rad_lhs`, `rad_rhs`) defined in Section 3.1.\n",
    "\n",
    "This `meshing` object will later be passed to `fl.SimulationParams` to define the complete meshing pipeline."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "5c80a289",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span><span style=\"color: #808000; text-decoration-color: #808000\">WARNING</span>: The spacing of <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">3</span> mm specified in RegionRefinement will be cast to the first lower refinement in\n",
       "<span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span>the octree series <span style=\"font-weight: bold\">(</span><span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">2</span> mm<span style=\"font-weight: bold\">)</span>.                                                                               \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m          \u001b[0m\u001b[2;36m \u001b[0m\u001b[33mWARNING\u001b[0m: The spacing of \u001b[1;36m3\u001b[0m mm specified in RegionRefinement will be cast to the first lower refinement in\n",
       "\u001b[2;36m           \u001b[0mthe octree series \u001b[1m(\u001b[0m\u001b[1;36m2\u001b[0m mm\u001b[1m)\u001b[0m.                                                                               \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "meshing = fl.ModularMeshingWorkflow(\n",
    "    surface_meshing=surface_meshing,\n",
    "    volume_meshing=volume_meshing,\n",
    "    zones=[fl.CustomZones(entities=[fluid, rad_lhs, rad_rhs])],\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e19b79eb",
   "metadata": {},
   "source": [
    "## 4. Simulation setup"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e5a99f17",
   "metadata": {},
   "source": [
    "### 4.1 Operating condition and time stepping\n",
    "The next code cell defines the aerodynamic operating condition (freestream velocity and car attitude) and the steady-state time-stepping strategy with an adaptive CFL:\n",
    "\n",
    "- `fl.AerospaceCondition(...)` sets the free-stream speed (`velocity_magnitude`), angle of attack `alpha`, and sideslip `beta` using Flow360’s unit system (`fl.u.m / fl.u.s`, `fl.u.deg`). The `alpha` angle is set to match the incidence of a wind tunnel that was applied t the geometry to achieve rake.\n",
    "- `fl.Steady(...)` selects a steady-state solver with an adaptive CFL strategy (`fl.AdaptiveCFL`) and a maximum number of pseudo-time steps.\n",
    "- `AdaptiveCFL` automatically adjusts the CFL number between `min` and `max` while respecting `max_relative_change` and `convergence_limiting_factor` to achieve fast but stable convergence.\n",
    "\n",
    "These `operating_condition` and `time_stepping` objects will later be passed into `fl.SimulationParams` to define the global flow conditions and marching strategy."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "7d320e7d",
   "metadata": {},
   "outputs": [],
   "source": [
    "operating_condition = fl.AerospaceCondition(\n",
    "    velocity_magnitude=50 * fl.u.m / fl.u.s,\n",
    "    alpha=-0.273031 * fl.u.deg,\n",
    "    beta=0.5 * fl.u.deg,\n",
    ")\n",
    "\n",
    "time_stepping = fl.Steady(\n",
    "    CFL=fl.AdaptiveCFL(\n",
    "        min=0.1,\n",
    "        max=15000,\n",
    "        max_relative_change=1,\n",
    "        convergence_limiting_factor=0.25,\n",
    "    ),\n",
    "    max_steps=6000,  # more steps than the mesh-only setup\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eaf46205",
   "metadata": {},
   "source": [
    "### 4.2. Boundary Conditions\n",
    "\n",
    "The next group of cells defines all boundary-condition models that will be passed into `SimulationParams.models`:\n",
    "\n",
    "- Wall boundaries for the car body and internal components.\n",
    "- A moving-ground wall to mimic a rolling-road wind tunnel.\n",
    "- Rotating-wall models for all four wheels.\n",
    "- A slip wall on the tunnel top and multiple freestream conditions on inlets, outlets, and ducts.\n",
    "\n",
    "Each subsection below introduces a specific boundary-condition type and the corresponding code that builds the Flow360 objects."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cd31203c",
   "metadata": {},
   "source": [
    "#### 4.2.1 No-slip Wall Function\n",
    "\n",
    "In the following code cell, we define a `fl.Wall` boundary condition for all **solid car surfaces** that use a wall-function model:\n",
    "\n",
    "- `surfaces=[geometry[\"fw*\"], geometry[\"rw*\"], ...]` selects groups of surfaces on the front wing, rear wing, body, underfloor, endplates, suspension, etc., using wildcard patterns over the face names exposed by `group_faces_by_tag(\"faceId\")`. (`Wall.surfaces` accepts `Surface` entities, so face-name wildcards are used regardless of how the underlying STL bodies are grouped.)\n",
    "- `use_wall_function=True` tells Flow360 to use a wall-function approach rather than fully resolving the viscous sublayer.\n",
    "\n",
    "The resulting `wall_model_BC` object will be included later in the `models` list inside `SimulationParams`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "b549b096",
   "metadata": {},
   "outputs": [],
   "source": [
    "wall_model_BC = [\n",
    "    fl.Wall(\n",
    "        surfaces=[\n",
    "            geometry[\"fw*\"],\n",
    "            geometry[\"rw*\"],\n",
    "            geometry[\"body*\"],\n",
    "            geometry[\"uf*\"],\n",
    "            geometry[\"eb*\"],\n",
    "            geometry[\"fr-int*\"],\n",
    "            geometry[\"fr-susp*\"],\n",
    "            geometry[\"rr-int*\"],\n",
    "            geometry[\"rr-susp*\"],\n",
    "            geometry[\"diffuser*\"],\n",
    "        ],\n",
    "        use_wall_function=fl.WallFunction(),\n",
    "    )\n",
    "]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5529b54b",
   "metadata": {},
   "source": [
    "### 4.2.2 Moving Walls\n",
    "\n",
    "The next code cell creates a **moving ground** boundary condition using another `fl.Wall` object:\n",
    "\n",
    "- `surfaces=[geometry[\"tunnel-ground\"]]` targets the wind-tunnel floor beneath the car (the `tunnel-ground` face inside `tunnel.stl`).\n",
    "- `velocity=[ ... ] * fl.u.m / fl.u.s` specifies the wall velocity vector in the tunnel frame.\n",
    "- The velocity components are constructed from the freestream speed (`50 m/s`) combined with the yaw and pitch angles (`alpha`, `beta`) using `cos`, `sin`, and `radians`.\n",
    "\n",
    "This `moving_wall` model is later added to the `models` list so the ground moves with the same speed as the free stream, emulating a rolling road."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "67e65459",
   "metadata": {},
   "outputs": [],
   "source": [
    "moving_wall = [\n",
    "    fl.Wall(\n",
    "        surfaces=[geometry[\"tunnel-ground\"]],\n",
    "        use_wall_function=fl.WallFunction(),\n",
    "        velocity=[\n",
    "            50 * cos(radians(0.5)) + 50 * cos(radians(-0.273031)),\n",
    "            50 * sin(radians(0.5)),\n",
    "            50 * sin(radians(-0.273031)),\n",
    "        ]\n",
    "        * fl.u.m\n",
    "        / fl.u.s,\n",
    "    )\n",
    "]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d4b55b43",
   "metadata": {},
   "source": [
    "### 4.2.3 Rotating Walls\n",
    "\n",
    "Here we define rotating-wall boundary conditions for the four wheels using a list of `fl.Wall` objects, each with a `fl.WallRotation` velocity model:\n",
    "\n",
    "- Each wheel (`front-left`, `front-right`, `rear-left`, `rear-right`) is defined as a separate `fl.Wall` with a `surfaces=[geometry[\"fr-wh*lhs\"], ...]` face wildcard that picks up that wheel's brake-disc, cover, plinth, rim, and tyre surfaces from `fr-wh.stl` / `rr-wh.stl`.\n",
    "- `fl.WallRotation` takes a `center` (rotation center in meters), an `axis` (unit vector of the rotation axis), and an `angular_velocity` (rad/s) to define rigid-body rotation.\n",
    "- `roughness_height=1 * fl.u.mm` sets a simple roughness model on the tire surfaces.\n",
    "\n",
    "All four wheel walls are collected into the `rotating_walls` list, which is unpacked later into the `models` list in `SimulationParams`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "2d136cd9",
   "metadata": {},
   "outputs": [],
   "source": [
    "rotating_walls = [\n",
    "    # --- Rotating front-left wheel ---\n",
    "    fl.Wall(\n",
    "        surfaces=[geometry[\"fr-wh*lhs\"]],\n",
    "        use_wall_function=fl.WallFunction(),\n",
    "        velocity=fl.WallRotation(\n",
    "            center=[\n",
    "                0.20344675860875966,\n",
    "                -0.7650740158453145,\n",
    "                0.3592397445075839,\n",
    "            ]\n",
    "            * fl.u.m,\n",
    "            axis=[\n",
    "                -0.015622695450066178,\n",
    "                -0.995547991275173,\n",
    "                0.09295229128344651,\n",
    "            ],\n",
    "            angular_velocity=141.50663390275048 * fl.u.rad / fl.u.s,\n",
    "        ),\n",
    "        roughness_height=1 * fl.u.mm,\n",
    "    ),\n",
    "    # --- Rotating front-right wheel ---\n",
    "    fl.Wall(\n",
    "        surfaces=[geometry[\"fr-wh*rhs\"]],\n",
    "        use_wall_function=fl.WallFunction(),\n",
    "        roughness_height=1 * fl.u.mm,\n",
    "        velocity=fl.WallRotation(\n",
    "            center=[\n",
    "                0.20344675860875977,\n",
    "                0.7650740158453145,\n",
    "                0.3592397445075839,\n",
    "            ]\n",
    "            * fl.u.m,\n",
    "            axis=[\n",
    "                0.015622695450066141,\n",
    "                -0.995547991275173,\n",
    "                -0.0929522912834463,\n",
    "            ],\n",
    "            angular_velocity=141.50663390275048 * fl.u.rad / fl.u.s,\n",
    "        ),\n",
    "    ),\n",
    "    # --- Rotating rear-left wheel ---\n",
    "    fl.Wall(\n",
    "        surfaces=[geometry[\"rr-wh*lhs\"]],\n",
    "        use_wall_function=fl.WallFunction(),\n",
    "        roughness_height=1 * fl.u.mm,\n",
    "        velocity=fl.WallRotation(\n",
    "            center=[\n",
    "                3.6466031503561447,\n",
    "                -0.742088395684798,\n",
    "                0.34259442839962195,\n",
    "            ]\n",
    "            * fl.u.m,\n",
    "            axis=[\n",
    "                0.001488022855271076,\n",
    "                -0.9993832015999016,\n",
    "                0.03508564019528322,\n",
    "            ],\n",
    "            angular_velocity=142.1293673304936 * fl.u.rad / fl.u.s,\n",
    "        ),\n",
    "    ),\n",
    "    # --- Rotating rear-right wheel ---\n",
    "    fl.Wall(\n",
    "        surfaces=[geometry[\"rr-wh*rhs\"]],\n",
    "        use_wall_function=fl.WallFunction(),\n",
    "        roughness_height=1 * fl.u.mm,\n",
    "        velocity=fl.WallRotation(\n",
    "            center=[\n",
    "                3.6466031503561456,\n",
    "                0.7420883956847978,\n",
    "                0.342594428389622,\n",
    "            ]\n",
    "            * fl.u.m,\n",
    "            axis=[\n",
    "                -0.001488022855274553,\n",
    "                -0.9993832015999016,\n",
    "                -0.0350856401952817,\n",
    "            ],\n",
    "            angular_velocity=142.1293673304936 * fl.u.rad / fl.u.s,\n",
    "        ),\n",
    "    ),\n",
    "]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8d3b45c2",
   "metadata": {},
   "source": [
    "### 4.2.4 Slip Wall and Freestream Condition\n",
    "\n",
    "The next code cell adds a slip-wall boundary on the tunnel top and several freestream boundary conditions on inlets, outlets, and internal ducts:\n",
    "\n",
    "- `slip_wall = fl.SlipWall(surfaces=[geometry[\"tunnel-top\"]])` creates a slip condition (no normal flow, no shear stress) on the tunnel ceiling.\n",
    "- The `freestream` list contains multiple `fl.Freestream` objects, each targeting the duct inlet/outlet surfaces inside `inflows_outflows.stl` (`velocity-inlet-*`, `velocity-outlet-*`) and, where needed, a prescribed velocity vector.\n",
    "\n",
    "Both `slip_wall` and all entries in `freestream` will be included in the `models` list to complete the external and internal boundary-condition setup."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "6762ec42",
   "metadata": {},
   "outputs": [],
   "source": [
    "slip_wall = [fl.SlipWall(surfaces=[geometry[\"tunnel-top\"]])]\n",
    "freestream = [\n",
    "    # --- Freestream on tunnel inlet, sides, outlet ---\n",
    "    fl.Freestream(\n",
    "        surfaces=[\n",
    "            geometry[\"tunnel-inlet\"],\n",
    "            geometry[\"tunnel-side*\"],\n",
    "            geometry[\"tunnel-outlet\"],\n",
    "        ]\n",
    "    ),\n",
    "    # --- Additional freestream conditions for ducts / exhausts ---\n",
    "    fl.Freestream(\n",
    "        surfaces=[geometry[\"velocity-inlet-exhaust-out\"]],\n",
    "        velocity=[100, 0, 0] * fl.u.m / fl.u.s,\n",
    "    ),\n",
    "    fl.Freestream(\n",
    "        surfaces=geometry[\"velocity-inlet-fr-int-duct-*-out\"],\n",
    "        velocity=[25.38, 0, 0] * fl.u.m / fl.u.s,\n",
    "    ),\n",
    "    fl.Freestream(\n",
    "        surfaces=geometry[\"velocity-inlet-rr-int-duct-*-out\"],\n",
    "        velocity=[25.38, 0, 0] * fl.u.m / fl.u.s,\n",
    "    ),\n",
    "    fl.Freestream(\n",
    "        surfaces=[geometry[\"velocity-outlet-engine-intake-in\"]],\n",
    "        velocity=[21.991020894901634, 0, -21.991020894901634] * fl.u.m / fl.u.s,\n",
    "    ),\n",
    "    fl.Freestream(\n",
    "        surfaces=geometry[\"velocity-outlet-fr-int-duct-*-in\"],\n",
    "        velocity=[15.36, 0, 0] * fl.u.m / fl.u.s,\n",
    "    ),\n",
    "    fl.Freestream(\n",
    "        surfaces=geometry[\"velocity-outlet-rr-int-duct-*-in\"],\n",
    "        velocity=[24.93, 0, 0] * fl.u.m / fl.u.s,\n",
    "    ),\n",
    "]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a511844d",
   "metadata": {},
   "source": [
    "### 4.3 Porous Medium\n",
    "\n",
    "The next cell defines a porous-medium model to represent flow resistance through the radiators:\n",
    "\n",
    "- `rad_lhs` and `rad_rhs` are volume zones defined in Section 3.1 that correspond to the left-hand and right-hand radiators.\n",
    "- Their `axes` are set to describe the porous block orientation in space.\n",
    "- `fl.PorousMedium(...)` is then created with:\n",
    "  - `volumes=[rad_lhs, rad_rhs]` to apply the model to both radiator volumes.\n",
    "  - `darcy_coefficient` and `forchheimer_coefficient` specifying the linear and quadratic resistance terms in the porous-medium law.\n",
    "\n",
    "This `porous_medium` object is later added to the `models` list so that radiator pressure losses are captured in the simulation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "f84191ae",
   "metadata": {},
   "outputs": [],
   "source": [
    "porous_medium = [\n",
    "    fl.PorousMedium(\n",
    "        volumes=[rad_lhs, rad_rhs],\n",
    "        darcy_coefficient=(3.75e7, 3.75e8, 3.75e8) / fl.u.m**2,\n",
    "        forchheimer_coefficient=(212.8, 2128, 2128) / fl.u.m,\n",
    "    )\n",
    "]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ce5e7c9a",
   "metadata": {},
   "source": [
    "### 4.4 Custom Output Variable\n",
    "\n",
    "The next cell defines a **user variable** for the non-dimensional velocity, which will be written alongside the standard flow variables.\n",
    "\n",
    "`vel_adim`: the velocity vector non-dimensionalised by the free-stream velocity magnitude. It is built with the `fl.UserVariable` syntax directly from the `fl.solution.velocity` solver variable and `operating_condition.velocity_magnitude`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "c1245276",
   "metadata": {},
   "outputs": [],
   "source": [
    "vel_adim = fl.UserVariable(\n",
    "    name=\"vel_adim\",\n",
    "    value=[\n",
    "        fl.solution.velocity[0] / operating_condition.velocity_magnitude,\n",
    "        fl.solution.velocity[1] / operating_condition.velocity_magnitude,\n",
    "        fl.solution.velocity[2] / operating_condition.velocity_magnitude,\n",
    "    ],\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "20ea5c1b",
   "metadata": {},
   "source": [
    "### 4.5 Outputs\n",
    "\n",
    "The following cell sets up all post-processing outputs for the case:\n",
    "\n",
    "- **Slice positions**: `x_positions`, `y_positions`, and `z_positions` are created with `numpy` to define where planes will be cut through the flow field.\n",
    "- **Surface output**: a `fl.SurfaceOutput` on all surfaces (`geometry[\"*\"]`) in ParaView format, writing standard fields such as `primitiveVars`, `Cp`, `yPlus`, and `CfVec`.\n",
    "- **Volume output**: a `fl.VolumeOutput` that exports the full 3D field in ParaView format, including built-in solution variables (e.g. `vorticity`, `fl.solution.Cpt_auto`) and the `vel_adim` user variable.\n",
    "- **Slice outputs**: a `fl.SliceOutput` named `\"slices\"` that contains:\n",
    "  - X-normal slices (slightly rotated) for each `x` in `x_positions`.\n",
    "  - Y-normal slices for each `y` in `y_positions`.\n",
    "  - Z-normal slices for each `z` in `z_positions`.\n",
    "\n",
    "These outputs are collected in the `outputs` list, which will be passed to `SimulationParams` so that all requested data is generated during the run."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "53b6ac06",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: using: SI unit system for unit inference.                                                         \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m          \u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: using: SI unit system for unit inference.                                                         \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "with fl.SI_unit_system:\n",
    "    # -----------------------------------------------------------------\n",
    "    # 4.1 Slice positions\n",
    "    # -----------------------------------------------------------------\n",
    "    # These positions define where we will cut the flow field later\n",
    "    # using SliceOutput (x-normal, y-normal, z-normal planes).\n",
    "    x_positions = np.arange(-1.1, 5.475, 0.025)\n",
    "    y_positions = np.arange(-1.1, 1.1, 0.025)\n",
    "    z_positions = np.arange(-0.05, 1.25, 0.025)\n",
    "\n",
    "    # Surface output on all surfaces\n",
    "    outputs = [\n",
    "        fl.SurfaceOutput(\n",
    "            surfaces=geometry[\"*\"],\n",
    "            output_format=[\"paraview\"],\n",
    "            output_fields=[\"primitiveVars\", \"Cp\", \"yPlus\", \"CfVec\"],\n",
    "        ),\n",
    "        # Volume output\n",
    "        fl.VolumeOutput(\n",
    "            name=\"volume\",\n",
    "            output_format=[\"paraview\"],\n",
    "            output_fields=[\n",
    "                \"primitiveVars\",\n",
    "                \"vorticity\",\n",
    "                \"residualNavierStokes\",\n",
    "                \"residualTurbulence\",\n",
    "                \"Cp\",\n",
    "                fl.solution.Cpt_auto,\n",
    "                \"qcriterion\",\n",
    "                vel_adim,\n",
    "            ],\n",
    "        ),\n",
    "        # Slice outputs: x, y, z planes\n",
    "        fl.SliceOutput(\n",
    "            name=\"slices\",\n",
    "            entities=[\n",
    "                # X-direction slices (slightly rotated around z)\n",
    "                *[\n",
    "                    fl.Slice(\n",
    "                        name=f\"slice_x_{i}\",\n",
    "                        normal=(\n",
    "                            cos(radians(-0.2594)),\n",
    "                            0,\n",
    "                            sin(radians(-0.2594)),\n",
    "                        ),\n",
    "                        origin=(x, 0, 0),\n",
    "                    )\n",
    "                    for i, x in enumerate(x_positions)\n",
    "                ],\n",
    "                # Y-direction slices\n",
    "                *[\n",
    "                    fl.Slice(\n",
    "                        name=f\"slice_y_{i}\",\n",
    "                        normal=(0, 1, 0),\n",
    "                        origin=(0, y, 0),\n",
    "                    )\n",
    "                    for i, y in enumerate(y_positions)\n",
    "                ],\n",
    "                # Z-direction slices\n",
    "                *[\n",
    "                    fl.Slice(\n",
    "                        name=f\"slice_z_{i}\",\n",
    "                        normal=(0, 0, 1),\n",
    "                        origin=(0, 0, z),\n",
    "                    )\n",
    "                    for i, z in enumerate(z_positions)\n",
    "                ],\n",
    "            ],\n",
    "            output_format=[\"paraview\"],\n",
    "            output_fields=[fl.solution.Cpt_auto, \"Cp\", vel_adim, \"vorticity\", \"qcriterion\"],\n",
    "        ),\n",
    "    ]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "62091b6f",
   "metadata": {},
   "source": [
    "## 5. Simulation Parameters\n",
    "\n",
    "We now build the core `SimulationParams` object, which collects all pieces defined so far into a single configuration passed to the solver:\n",
    "\n",
    "- **Meshing**: the `meshing` workflow from Section 3, defining the complete Snappy-based meshing pipeline.\n",
    "- **Operating condition**: `operating_condition` from Section 4, defining freestream speed and car attitude.\n",
    "- **Time-stepping**: `time_stepping` from Section 4, using a steady solver with adaptive CFL.\n",
    "- **Models**: the `models` list includes:\n",
    "  - Boundary-condition models: `moving_wall`, `rotating_walls`, `slip_wall`, `freestream`, and `porous_medium` defined in Sections 5–6 (unpacked with `*` in the list).\n",
    "  - Fluid and turbulence models: a `fl.Fluid` object with a `NavierStokesSolver` and a `KOmegaSST` turbulence model.\n",
    "    - `kappa_MUSCL = 0.33` blends central and upwind schemes to reduce numerical dissipation.\n",
    "    - `C_alpha1 = 0.4` in `KOmegaSSTModelConstants` is chosen to help delay separation.\n",
    "- **Custom output variable**: the `vel_adim` user variable (Section 4.4) is referenced directly in the output fields, and the total-pressure coefficient uses the built-in `fl.solution.Cpt_auto`. No `user_defined_fields` argument is needed.\n",
    "- **Outputs**: the `outputs` list from Section 8 (surface, volume, and slice outputs).\n",
    "- **Reference geometry**: `fl.ReferenceGeometry` sets the reference moment center, lengths, and area used to compute non-dimensional forces and moments.\n",
    "\n",
    "The resulting `params` object is the single input that fully describes the simulation setup for `project.run_case`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "483abb91",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: using: SI unit system for unit inference.                                                         \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m          \u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: using: SI unit system for unit inference.                                                         \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "with fl.SI_unit_system:\n",
    "    params = fl.SimulationParams(\n",
    "        meshing=meshing,\n",
    "        operating_condition=operating_condition,\n",
    "        time_stepping=time_stepping,\n",
    "        models=[\n",
    "            # --- Models defined previously ---\n",
    "            *wall_model_BC,\n",
    "            *moving_wall,\n",
    "            *rotating_walls,\n",
    "            *slip_wall,\n",
    "            *freestream,\n",
    "            *porous_medium,\n",
    "            # --- Fluid + turbulence (k-omega SST) ---\n",
    "            fl.Fluid(\n",
    "                navier_stokes_solver=fl.NavierStokesSolver(\n",
    "                    absolute_tolerance=1e-10,\n",
    "                    linear_solver=fl.LinearSolver(max_iterations=50),\n",
    "                    kappa_MUSCL=0.33,\n",
    "                    low_mach_preconditioner=True,\n",
    "                ),\n",
    "                turbulence_model_solver=fl.KOmegaSST(\n",
    "                    absolute_tolerance=1e-8,\n",
    "                    linear_solver=fl.LinearSolver(max_iterations=25),\n",
    "                    equation_evaluation_frequency=4,\n",
    "                    rotation_correction=True,\n",
    "                    modeling_constants=fl.KOmegaSSTModelConstants(C_alpha1=0.4),\n",
    "                ),\n",
    "            ),\n",
    "        ],\n",
    "        outputs=outputs,\n",
    "        reference_geometry=fl.ReferenceGeometry(\n",
    "            moment_center=(1.4001, 0, -0.3176),  # reference moment center\n",
    "            moment_length=(1, 2.7862, 1),  # reference lengths\n",
    "            area=1.7095,  # reference area\n",
    "        ),\n",
    "    )"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b37859cd",
   "metadata": {},
   "source": [
    "## 6. Run Simulation\n",
    "\n",
    "The final code cell submits the case to Flow360 using the `project.run_case` API:\n",
    "\n",
    "- `project.run_case(params=params, name=\"Tutorial F1 Car from Python\")` sends the full `params` object (constructed in Section 5) to the Flow360 service and starts a simulation run.\n",
    "- The `name` argument provides a human-readable label for this specific run within the project.\n",
    "\n",
    "Before executing this cell, you should double-check all previous sections (meshing, BCs, models, outputs, and reference geometry), as running this cell will launch a real simulation in your Flow360 workspace and consume computational resources."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e68622fd",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">[15:41:12] </span><span style=\"color: #808000; text-decoration-color: #808000\">WARNING</span>: The spacing of <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">3</span> mm specified in RegionRefinement will be cast to the first lower refinement in\n",
       "<span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span>the octree series <span style=\"font-weight: bold\">(</span><span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">2</span> mm<span style=\"font-weight: bold\">)</span>.                                                                               \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m[15:41:12]\u001b[0m\u001b[2;36m \u001b[0m\u001b[33mWARNING\u001b[0m: The spacing of \u001b[1;36m3\u001b[0m mm specified in RegionRefinement will be cast to the first lower refinement in\n",
       "\u001b[2;36m           \u001b[0mthe octree series \u001b[1m(\u001b[0m\u001b[1;36m2\u001b[0m mm\u001b[1m)\u001b[0m.                                                                               \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span><span style=\"color: #808000; text-decoration-color: #808000\">WARNING</span>: <span style=\"color: #c0c0c0; text-decoration-color: #c0c0c0\">Validation warnings found during local validation: </span>                                            \n",
       "<span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span><span style=\"color: #c0c0c0; text-decoration-color: #c0c0c0\">        </span><span style=\"color: #c0c0c0; text-decoration-color: #c0c0c0; font-weight: bold\">(</span><span style=\"color: #c0c0c0; text-decoration-color: #c0c0c0; font-weight: bold\">1</span><span style=\"color: #c0c0c0; text-decoration-color: #c0c0c0; font-weight: bold\">)</span><span style=\"color: #c0c0c0; text-decoration-color: #c0c0c0\"> Multiple symmetry plane surfaces have different boundary conditions </span><span style=\"color: #c0c0c0; text-decoration-color: #c0c0c0; font-weight: bold\">(</span><span style=\"color: #c0c0c0; text-decoration-color: #c0c0c0\">Freestream, Wall</span><span style=\"color: #c0c0c0; text-decoration-color: #c0c0c0; font-weight: bold\">)</span><span style=\"color: #c0c0c0; text-decoration-color: #c0c0c0\">. </span>    \n",
       "<span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span><span style=\"color: #c0c0c0; text-decoration-color: #c0c0c0\">Please check if this is intended.</span>                                                                       \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m          \u001b[0m\u001b[2;36m \u001b[0m\u001b[33mWARNING\u001b[0m: \u001b[37mValidation warnings found during local validation: \u001b[0m                                            \n",
       "\u001b[2;36m           \u001b[0m\u001b[37m        \u001b[0m\u001b[1;37m(\u001b[0m\u001b[1;37m1\u001b[0m\u001b[1;37m)\u001b[0m\u001b[37m Multiple symmetry plane surfaces have different boundary conditions \u001b[0m\u001b[1;37m(\u001b[0m\u001b[37mFreestream, Wall\u001b[0m\u001b[1;37m)\u001b[0m\u001b[37m. \u001b[0m    \n",
       "\u001b[2;36m           \u001b[0m\u001b[37mPlease check if this is intended.\u001b[0m                                                                       \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>:                                                                                                   \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m          \u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m:                                                                                                   \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: Units of output `UserVariables`:                                                                  \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m          \u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: Units of output `UserVariables`:                                                                  \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: -------------------------------                                                                   \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m          \u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: -------------------------------                                                                   \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: Variable Name   | Unit                                                                            \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m          \u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: Variable Name   | Unit                                                                            \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: -------------------------------                                                                   \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m          \u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: -------------------------------                                                                   \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: Cpt_auto_SI     | dimensionless                                                                   \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m          \u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: Cpt_auto_SI     | dimensionless                                                                   \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: vel_adim        | dimensionless                                                                   \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m          \u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: vel_adim        | dimensionless                                                                   \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: -------------------------------                                                                   \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m          \u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: -------------------------------                                                                   \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>:                                                                                                   \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m          \u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m:                                                                                                   \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">[15:41:13] </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: Selecting beta/in-house mesher for possible meshing tasks.                                        \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m[15:41:13]\u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: Selecting beta/in-house mesher for possible meshing tasks.                                        \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">[15:41:15] </span><span style=\"color: #008080; text-decoration-color: #008080\">INFO</span>: Successfully submitted:                                                                           \n",
       "<span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span>        type        = Case                                                                              \n",
       "<span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span>        name        = Tutorial F1 Car from Python                                                       \n",
       "<span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span>        id          = case-<span style=\"color: #ffff00; text-decoration-color: #ffff00\">bd5651a0-1001-4120-9781-a4dee36905c8</span>                                         \n",
       "<span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span>        status      = pending                                                                           \n",
       "<span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span>        project id  = prj-<span style=\"color: #ffff00; text-decoration-color: #ffff00\">bde06929-783f-4489-8fe1-567801c58404</span>                                          \n",
       "<span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">           </span>                                                                                                        \n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[2;36m[15:41:15]\u001b[0m\u001b[2;36m \u001b[0m\u001b[36mINFO\u001b[0m: Successfully submitted:                                                                           \n",
       "\u001b[2;36m           \u001b[0m        type        = Case                                                                              \n",
       "\u001b[2;36m           \u001b[0m        name        = Tutorial F1 Car from Python                                                       \n",
       "\u001b[2;36m           \u001b[0m        id          = case-\u001b[93mbd5651a0-1001-4120-9781-a4dee36905c8\u001b[0m                                         \n",
       "\u001b[2;36m           \u001b[0m        status      = pending                                                                           \n",
       "\u001b[2;36m           \u001b[0m        project id  = prj-\u001b[93mbde06929-783f-4489-8fe1-567801c58404\u001b[0m                                          \n",
       "\u001b[2;36m           \u001b[0m                                                                                                        \n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "case = project.run_case(params=params, name=\"Steady RANS\", use_beta_mesher=True)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv (3.11.9)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
