{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "f19beeb3",
   "metadata": {},
   "source": [
    "# SRF with Cube\n",
    "\n",
    "This notebook demonstrates how to set up a **Single Rotating Frame (SRF)** simulation with a mesh of a simple cube using **snappyHexMesh** in Flow360.\n",
    "\n",
    "**Key features demonstrated:**\n",
    "- Integration of snappyHexMesh for surface meshing with Flow360's new volume mesher\n",
    "- Definition of rotating zones using the `Rotation` model\n",
    "- Setup of boundary conditions for SRF simulations\n",
    "- Use of the modular meshing workflow with custom zones\n",
    "\n",
    "![Cube Mesh](figures/cube_snappy.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": "c59cbfd1",
   "metadata": {},
   "source": [
    "## 1. Create Project from geometry\n",
    "\n",
    "The first step is to initialize the Flow360 client and create a project from the geometry file.\n",
    "\n",
    "**Key steps:**\n",
    "- **Load libraries**: Import `flow360` and the example geometry (`Cube`). The `Cube` example provides a pre-configured STL file for demonstration purposes — it contains two named solids, `cube` and `farfield`.\n",
    "- **Create project**: Use `fl.Project.from_geometry()` to create a project from the CAD file. Specify the `length_unit` (meters in this case).\n",
    "- **Group bodies and faces**: From release 25.10 onward, snappy uses the same conventional grouping mechanism as the other surface meshers. Call `geo.group_bodies_by_tag(\"groupByFile\")` to expose each input STL file as a body group (here, a single `cube.stl` body), and `geo.group_faces_by_tag(\"faceId\")` to expose each named STL solid as an addressable surface (here, `geo[\"cube\"]` and `geo[\"farfield\"]`)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "881341eb",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-14T10:17:13.834947Z",
     "iopub.status.busy": "2026-07-14T10:17:13.834746Z",
     "iopub.status.idle": "2026-07-14T10:17:52.874809Z",
     "shell.execute_reply": "2026-07-14T10:17:52.874289Z"
    }
   },
   "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\"></pre>\n"
      ],
      "text/plain": []
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[12:17:30] INFO: Geometry successfully submitted:                               \n",
      "                   type        = Geometry                                       \n",
      "                   name        = SRF with Cube                                  \n",
      "                   id          = geo-bc2ee0e0-1aab-4471-9d04-77b7a7d3754a       \n",
      "                   status      = uploaded                                       \n",
      "                   project id  = prj-163d4122-d397-4900-b11e-69698e9fd1fa       \n",
      "                                                                                \n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "           INFO: Waiting for geometry to be processed.                          \n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[?25l"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\u001b[?25h"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[12:17:52] INFO:  >> Available attribute tags for grouping **faces**:           \n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "           INFO:     >> Tag '0': groupByBodyId. Grouping with this tag results  \n",
      "           in:                                                                  \n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "           INFO:         >> [0]: cube.stl                                       \n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "           INFO:            IDs: ['cube', 'farfield']                           \n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "           INFO:  >> Available attribute tags for grouping **edges**:           \n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "           INFO:  >> Available attribute tags for grouping **bodies**:          \n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "           INFO:     >> Tag '1': groupByFile. Grouping with this tag results in:\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "           INFO:         >> [0]: cube.stl                                       \n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "           INFO:            IDs: ['cube.stl']                                   \n"
     ]
    }
   ],
   "source": [
    "import flow360 as fl\n",
    "from flow360.examples import Cube\n",
    "\n",
    "Cube.get_files()\n",
    "\n",
    "project = fl.Project.from_geometry(Cube.geometry, name=\"SRF with Cube\", length_unit=\"m\")\n",
    "\n",
    "geo = project.geometry\n",
    "geo.show_available_groupings(verbose_mode=True)\n",
    "geo.group_bodies_by_tag(\"groupByFile\")\n",
    "geo.group_faces_by_tag(\"faceId\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8206b22c",
   "metadata": {},
   "source": [
    "## 2. Meshing Parameters\n",
    "\n",
    "The meshing configuration uses Flow360's **modular meshing workflow**, which allows you to independently choose surface and volume meshers. This example combines:\n",
    "- **Surface meshing**: snappyHexMesh integration via `fl.snappy.SurfaceMeshingParams`\n",
    "- **Volume meshing**: Flow360's new volume mesher via `fl.VolumeMeshingParams`\n",
    "\n",
    "**Defining the fluid zone:**\n",
    "\n",
    "The fluid domain must be explicitly defined using a `SeedpointVolume`:\n",
    "- `point_in_mesh`: A list of one or more coordinate points that lie inside the fluid region (e.g., `[[10.1435, 0, 0]]`). The seed points help the mesher identify which volume is the fluid domain.\n",
    "- `center` and `axis`: Define the center and rotation axis for the volume. These are critical for SRF simulations, as they specify the rotation reference frame. Here, `center=(0, 0, 0)` and `axis=(1, 0, 0)` indicate rotation about the x-axis through the origin.\n",
    "- **Convert to zone**: Wrap the `SeedpointVolume` in a `CustomZones` object to make it available for meshing and physics models.\n",
    "\n",
    "**Surface meshing configuration:**\n",
    "\n",
    "- **`SurfaceMeshingDefaults`**: Global surface meshing parameters:\n",
    "  - `min_spacing` and `max_spacing`: Control the surface mesh resolution\n",
    "  - `gap_resolution`: Minimum feature size to capture (1 mm)\n",
    "- **`RegionRefinement`**: Refine a specific surface (region) of the geometry. Here it targets `geo[\"cube\"]` — the cube face exposed by the `faceId` grouping — to give the wall a finer mesh than the surrounding farfield. Faces are addressed by their STL solid name (`geo[\"<face>\"]`); bodies are addressed by their file name (`geo[\"<file>.stl\"]`) and accepted by `fl.snappy.BodyRefinement` when the refinement should cover the whole body.\n",
    "- **`SurfaceEdgeRefinement`**: Refine the mesh along surface edges to capture sharp features and geometric discontinuities. If this refinement is not used, the edges are not guaranteed to be kept by snappyHexMesh. The `spacing` parameter (50 mm in this example) controls the mesh size along edges, while `distances` (2 mm) defines the distance from the edge where this refinement is applied.\n",
    "\n",
    "**Volume meshing configuration:**\n",
    "\n",
    "- **`VolumeMeshingDefaults`**: Set boundary layer parameters:\n",
    "  - `boundary_layer_first_layer_thickness`: Thickness of the first boundary layer cell (1 mm), which affects near-wall resolution and is important for accurate wall-bounded flow predictions.\n",
    "- **Volume meshing refinements**:\n",
    "  - **`PassiveSpacing`**: Preserve the surface mesh spacing on specified faces. Applied to the farfield surface `geo[\"farfield\"]` with `type='unchanged'`, this prevents the volume mesher from modifying the mesh spacing on the farfield boundary, maintaining the resolution determined by the surface mesher.\n",
    "  - **`UniformRefinement`**: Apply uniform mesh spacing within a defined region. Here, a box region (center at (0.5, 0, 0) with size 3×2×2 m) is refined to 50 mm spacing to ensure adequate resolution around the cube geometry in the near-field region.\n",
    "\n",
    "**Important**: When using the new volume mesher, you must set `use_beta_mesher=True` when running the case (see Section 5)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "f79d204f",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-14T10:17:52.875794Z",
     "iopub.status.busy": "2026-07-14T10:17:52.875713Z",
     "iopub.status.idle": "2026-07-14T10:17:52.898162Z",
     "shell.execute_reply": "2026-07-14T10:17:52.897629Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "           INFO: using: SI unit system for unit inference.                      \n"
     ]
    }
   ],
   "source": [
    "with fl.SI_unit_system:\n",
    "    fluid = fl.SeedpointVolume(\n",
    "        name=\"fluid\", point_in_mesh=[(10.1435, 0, 0)], center=(0, 0, 0), axis=(1, 0, 0)\n",
    "    )\n",
    "    fluid_zone = fl.CustomZones(name=\"fluid\", entities=[fluid])\n",
    "\n",
    "    meshing_params = fl.ModularMeshingWorkflow(\n",
    "        surface_meshing=fl.snappy.SurfaceMeshingParams(\n",
    "            defaults=fl.snappy.SurfaceMeshingDefaults(\n",
    "                min_spacing=0.5 * fl.u.m, max_spacing=2 * fl.u.m, gap_resolution=1 * fl.u.mm\n",
    "            ),\n",
    "            refinements=[\n",
    "                fl.snappy.RegionRefinement(\n",
    "                    min_spacing=50 * fl.u.mm, max_spacing=100 * fl.u.mm, regions=[geo[\"cube\"]]\n",
    "                ),\n",
    "                fl.snappy.SurfaceEdgeRefinement(\n",
    "                    spacing=[50 * fl.u.mm], distances=[2 * fl.u.mm], entities=[geo[\"cube\"]]\n",
    "                ),\n",
    "            ],\n",
    "        ),\n",
    "        volume_meshing=fl.VolumeMeshingParams(\n",
    "            defaults=fl.VolumeMeshingDefaults(boundary_layer_first_layer_thickness=1 * fl.u.mm),\n",
    "            refinements=[\n",
    "                fl.PassiveSpacing(faces=[geo[\"farfield\"]], type=\"unchanged\"),\n",
    "                fl.UniformRefinement(\n",
    "                    entities=fl.Box(name=\"box\", center=(0.5, 0, 0), size=(3, 2, 2)),\n",
    "                    spacing=50 * fl.u.mm,\n",
    "                ),\n",
    "            ],\n",
    "        ),\n",
    "        zones=[fluid_zone],\n",
    "    )"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dd4ddb2b",
   "metadata": {},
   "source": [
    "## 3. Create Rotating Model\n",
    "\n",
    "In an SRF simulation, the entire computational domain rotates as a rigid body. The `Rotation` model defines which volumes rotate and at what angular velocity.\n",
    "\n",
    "**Key parameters:**\n",
    "- **`name`**: A descriptive name for the rotation model (e.g., `\"rotating_farfield\"`)\n",
    "- **`volumes`**: List of volumes that rotate. Here, we use the `fluid` volume defined earlier, which means the entire fluid domain rotates.\n",
    "- **`spec`**: The rotation specification. `AngularVelocity(10 * fl.u.rpm)` sets the domain to rotate at 10 revolutions per minute about the axis defined in the `SeedpointVolume` (x-axis through the origin).\n",
    "\n",
    "**Important considerations:**\n",
    "- The rotation axis and center must match those defined in the `SeedpointVolume` (Section 2) for consistency.\n",
    "- In SRF simulations, boundary conditions are applied in the rotating frame. For example, a freestream velocity is specified relative to the rotating domain.\n",
    "- The `Rotation` model is added to the `models` list in `SimulationParams` (Section 4), not as a boundary condition."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "f28ca0b1",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-14T10:17:52.899715Z",
     "iopub.status.busy": "2026-07-14T10:17:52.899630Z",
     "iopub.status.idle": "2026-07-14T10:17:52.902701Z",
     "shell.execute_reply": "2026-07-14T10:17:52.902364Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "           INFO: using: SI unit system for unit inference.                      \n"
     ]
    }
   ],
   "source": [
    "with fl.SI_unit_system:\n",
    "    rotating_farfield = fl.Rotation(\n",
    "        name=\"rotating_fluid\", volumes=[fluid], spec=fl.AngularVelocity(50 * fl.u.rpm)\n",
    "    )"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "53735fa8",
   "metadata": {},
   "source": [
    "## 4. Create `SimulationParams`\n",
    "\n",
    "The `SimulationParams` class consolidates all simulation settings: meshing, physics, boundary conditions, and solver options.\n",
    "\n",
    "**Configuration components:**\n",
    "\n",
    "1. **`meshing`**: Reference to the `ModularMeshingWorkflow` defined in Section 2.\n",
    "\n",
    "2. **`operating_condition`**: Defines the flow conditions:\n",
    "   - `AerospaceCondition(velocity_magnitude=10 * fl.u.m / fl.u.s)`: Sets a freestream velocity of 10 m/s. In SRF simulations, this velocity is relative to the rotating frame.\n",
    "\n",
    "3. **`time_stepping`**: `Steady()` indicates a steady-state simulation. For unsteady SRF cases, use `Unsteady()` with appropriate time step settings.\n",
    "\n",
    "4. **`models`**: List of physics models and boundary conditions:\n",
    "   - **`Wall`**: Applied to the cube surface using `geo[\"cube\"]`. Each STL solid is exposed as a `Surface` entity named after that solid, so the cube face is selected directly. No-slip boundary conditions are applied by default.\n",
    "   - **`Freestream`**: Applied to the farfield surface using `geo[\"farfield\"]`. This sets the farfield boundary condition with the velocity specified in `operating_condition`.\n",
    "   - **`rotating_farfield`**: The rotation model defined in Section 3. This makes the entire fluid domain rotate.\n",
    "\n",
    "5. **`reference_geometry`**: `ReferenceGeometry()` enables calculation of reference quantities (forces, moments) for the cube geometry.\n",
    "\n",
    "**Note**: The order of models in the list doesn't matter, but all relevant models must be included."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "3e796875",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-14T10:17:52.903731Z",
     "iopub.status.busy": "2026-07-14T10:17:52.903651Z",
     "iopub.status.idle": "2026-07-14T10:17:52.941278Z",
     "shell.execute_reply": "2026-07-14T10:17:52.940679Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "           INFO: using: SI unit system for unit inference.                      \n"
     ]
    }
   ],
   "source": [
    "with fl.SI_unit_system:\n",
    "    params = fl.SimulationParams(\n",
    "        meshing=meshing_params,  # Previously defined meshing parameters\n",
    "        operating_condition=fl.AerospaceCondition(velocity_magnitude=10 * fl.u.m / fl.u.s),\n",
    "        time_stepping=fl.Steady(),\n",
    "        models=[\n",
    "            fl.Wall(surfaces=[geo[\"cube\"]]),\n",
    "            fl.Freestream(surfaces=[geo[\"farfield\"]]),\n",
    "            rotating_farfield,\n",
    "        ],\n",
    "        reference_geometry=fl.ReferenceGeometry(),\n",
    "        outputs=[\n",
    "            fl.SurfaceOutput(surfaces=[geo[\"cube\"]], output_fields=[\"CfVec\", \"Cf\", \"yPlus\", \"Cp\"])\n",
    "        ],\n",
    "    )"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4c37e897",
   "metadata": {},
   "source": [
    "## 5. Run Case\n",
    "\n",
    "Submit the case to Flow360's cloud platform using `project.run_case()`.\n",
    "\n",
    "**Key parameters:**\n",
    "- **`params`**: The `SimulationParams` object containing all simulation configuration.\n",
    "- **`name`**: A descriptive name for the case (visible in the Flow360 web interface).\n",
    "- **`solver_version`**: Must match the version used when creating the project to ensure compatibility.\n",
    "- **`use_beta_mesher=True`**: **Required** when using `VolumeMeshingParams` (the new volume mesher). This flag tells Flow360 to use the beta meshing pipeline instead of the default mesher.\n",
    "\n",
    "**What happens next:**\n",
    "1. The case is submitted and queued for execution.\n",
    "2. Flow360 performs surface meshing using snappyHexMesh.\n",
    "3. Volume meshing is performed using the new volume mesher.\n",
    "4. The CFD solver runs the simulation.\n",
    "5. Results are available for download and visualization."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "842e3570",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-14T10:17:52.942629Z",
     "iopub.status.busy": "2026-07-14T10:17:52.942545Z",
     "iopub.status.idle": "2026-07-14T10:17:56.472007Z",
     "shell.execute_reply": "2026-07-14T10:17:56.471581Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[?25l"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\u001b[?25h"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[12:17:53] WARNING: The spacing of 50 mm specified in RegionRefinement will be  \n",
      "           cast to the first lower refinement in the octree series (31.25 mm).  \n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "           WARNING: The spacing of 100 mm specified in RegionRefinement will be \n",
      "           cast to the first lower refinement in the octree series (62.5 mm).   \n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "           WARNING: The spacing of 50 mm specified in SurfaceEdgeRefinement will\n",
      "           be cast to the first lower refinement in the octree series (31.25    \n",
      "           mm).                                                                 \n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "           WARNING: The spacing of 50 mm specified in UniformRefinement will be \n",
      "           cast to the first lower refinement in the octree series (31.25 mm).  \n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[12:17:54] INFO: Selecting beta/in-house mesher for possible meshing tasks.     \n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[12:17:56] INFO: Successfully submitted:                                        \n",
      "                   type        = Case                                           \n",
      "                   name        = Steady SRF                                     \n",
      "                   id          = case-b624c7c0-037d-4ca7-8d7b-44df3ac6b5a6      \n",
      "                   status      = pending                                        \n",
      "                   project id  = prj-163d4122-d397-4900-b11e-69698e9fd1fa       \n",
      "                                                                                \n"
     ]
    }
   ],
   "source": [
    "case = project.run_case(params=params, name=\"Steady SRF\", use_beta_mesher=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a534d917",
   "metadata": {},
   "source": [
    "Image showing the friction lines on the front face of the cube, notice there is a swirl component.\n",
    "![Flowfield](figures/flowfield.png)"
   ]
  }
 ],
 "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.15"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
