Before we delve into detail on how to generate rotor-stator type meshes in snappyHexMesh, I would like to clarify what I mean by that. A rotor-stator setup basically describes a mesh, where a certain region rotates around a given axis and the rest of the domain stays in place. To accomodate such large rotational motions, a sliding interface (AMI/GGI) is to be used, which in turn requires the mesh to be split into two parts.
There is a tutorial in the official release, on how to generate rotor-stator type meshes in snappyHexMesh. This tutorial uses snappyHexMeshonly to generate the mesh in a single run. This method turns out ot be very memory intensive with the mesh increasing in size. Additionally that method is fairly time intensive, when iterating towards a high quality mesh, as the entire mesh has to be kept in memory. Optimizing the rotor part of the propeller mesh is hence even more time intensive, than it is anyway. The method presented in this tutorial helps to circuumvent those memory issues, by generating the rotating and stationary parts of the mesh individually and combining them later on.
You might not see this now, but the general geometry is assumed to be a box-like ship, with a rudder attached to it. The rudder has to rotate freely and is hence located in the cylindral zone. As a ship is not very straight forward to mesh at a high mesh quality (in a short period of time, at least), the ship is replaced by a box. Though not being very sophisticated, it serves the purpose of showing how to mesh such things well.
Before we start, the geometry of the rotor (in this case the rudder) should be inspected visually. Special attention must be paid to the edges, extracted from the STL. It is important that all relevant edges are extracted properly. A short overview of how to extract the edges from a STL file and force snappyHexMesh to snap cell edges to geometry feature edges can be found
here.
Screenshot of the rudder geometry
Screenshot of the cylinder, used for the interface between the rotating and stationary part of the mesh.
Stator Part
The first step is to generate the stator part of the mesh, which is not going to rotate. For the sake of this tutorial, a block is used as a geometry, that has a cylinder underneath it, which serves as the stator part of the AMI/GGI mesh. This cylinder is empty and will remain so, while generating thestator part of the mesh.
Closeup view on the AMI/GGI interface (cylinder) and the ship-like body, the rudder will be attached to.
It does not matter what kind of geometry you choose, as long as there is a patch in there, that is suitable to be used with an AMI/GGI interface. The cylinder was created in paraview, triagulated and saved to disk as an ASCII STL. This geometry is then defined in the geometry
subdict ofsnappyHexMesh. The relevant parts for this meshing case are the following:
- Position the
locationInMesh
as such, that the area around the the cylinder is meshed and any cells inside get removed
- Set up
surfaceFeatureExtract
to extract any edges of the cylinder STL file
- Use the edge snapping feature of snappyHexMesh, in conjunction with the
constant/extendedFeatureEdgeMesh/<your file name>.extendedFeatureEdgeMesh
. It is important to create a symlink to <your file name>.eMesh
as snappyHexMesh does not read *.extendedFeatureEdgeMesh
files.
- For sake of simplicity, I like to use the
overwrite
option with snappyHexMesh
- It is important, that the cylindrically shaped stator patch has its edges aligned properly and is not crucket or something. Otherwise the rotation in the actual simulation will blow up fairly quickly.
Keep in mind, that using the overwrite option with snappyHexMesh will store the final mesh in constant/polyMesh
and no time directories will be generated. Therefore before eventually rerunning blockMesh, the constant/polyMesh directory should be cleaned properly, with keeping the blockMeshDict.
The remaining steps are fairly straight forward. Execute blockMesh and snappyHexMesh, iterate to the mesh you desire and that’s it for this step. It is advisable to put up a script that executes all requried steps, including using sed to replace some patch names:
- #!/bin/sh
- cd ${0%/*} || exit 1
-
- # Source tutorial run functions
- . $WM_PROJECT_DIR/bin/tools/RunFunctions
-
- runApplication blockMesh
- runApplication surfaceFeatureExtract
- runApplication snappyHexMesh -overwrite
-
- sed -i "s/cylinder_ascii/STATOR1/g" constant/polyMesh/boundar
Rotor Part
Compared to the stator mesh, a little bit more effort should be spend on the rotor mesh. Not only the cylinder must remain as symmetrical as possible, the rudder geometry inside should be as nicely discretised as possible. Copy the rudder geometry to constant/triSurface/rudder.stl
and either copy or symlink the cylinder to constant/triSurface/cylinder.stl
. The blockMeshDict
solely needs to consist of a single block, filled with cubic cells. The dimensions must be just as large as the cylinder.
View of the entire rotor mesh, containing the rudder and the outer boundary, which
represents the cylindrical interface for AMI/GGI
The geometry
subdictionary of the snappyHexMeshDict
must contain two STL based geometries: the cylinder and the rudder. Again, the surface features must be extracted and used for the meshing process. In order not to miss any steps, a script to execute each step may come in handy:
- #!/bin/sh
- cd ${0%/*} || exit 1
-
- # Source tutorial run functions
- . $WM_PROJECT_DIR/bin/tools/RunFunctions
-
- runApplication blockMesh
- runApplication surfaceFeatureExtract
- runApplication snappyHexMesh -overwrite
-
- sed -i "s/cylinder_ascii/ROTOR1/g" constant/polyMesh/boundary
- sed -i "s/rudder_ascii/RUDDER/g" constant/polyMesh/boundary
- sed -i "20,26d" constant/polyMesh/boundary
- sed -i "s/^3$/2/g" constant/polyMesh/boundary
It is important to rename the patches to something meaningful as well as to delete the defaultFaces
boundary, that does not contain any faces and adjust the length of the boundary list accordingly (to 2). Please not that this all hard coded and special attention needs to be paid, as soon as the mesh topology changes significantely. Especially in terms of number and names of the boundaries.
Combining both Meshes
Combining the two meshes is fairly straight forward. The first step is to copy the stator mesh and delete any obsolete files and dictionaries, such as snappyHexMeshDict
, the surfaceFeatureExtractDict
and constant/extendedFeatureEdgeMesh
. This case now contains solely the stator part. Using mergeMesh
, the rotor part is going to be meged into the stator mesh. Unfortunately this results in a single mesh zone, with internal faces, which is not going to work for the simulations. Using topoSet
, the faces and cells are split into two different zones, to account for the rotor and stator parts of the mesh.
The main issue with regards to automization of the entire process is that the boundary
file in the merged mesh must be extended, as the rotor and stator patches must get a type of cyclicAMI
and this requires additional entries to be added. Using sed, this can be automated to some degree, though I haven’t found a way to include proper linebreaks in a sed replacement. Currently this leads to a long line with spaces to seperate the added lines. The script currently looks like the following:
- #!/bin/sh
- cd ${0%/*} || exit 1 # run from this directory
-
- # Source tutorial run functions
- . $WM_PROJECT_DIR/bin/tools/RunFunctions
-
- # Perform the sed action on the boundary file
- function fixBoundary {
- echo "Fixing Patch $1"
- l=`grep -n -E "$1$" constant/polyMesh/boundary | grep -o -E "^[0-9]*"`
- echo $l
- typeLine=$[$l + 2]
- sed -i "${typeLine}s/wall/cyclicAMI/g" constant/polyMesh/boundary
- sed -i "${typeLine}a \
- inGroups 1(cyclicAMI); \
- matchTolerance 1e-4; \
- transform noOrdering; \
- neighbourPatch $2;" constant/polyMesh/boundary
- }
-
- export rotor='../rudder'
- export stator='../box'
-
- rm -rf constant/polyMesh
- rm log.*
- cp -r $stator/constant/polyMesh constant/
- cp -r $stator/constant/triSurface constant/
-
- runApplication mergeMeshes -overwrite . $rotor
- runApplication topoSet
-
- fixBoundary "ROTOR1" "STATOR1"
- fixBoundary "STATOR1" "ROTOR1"
As you can see, topoSet
is used as well. Setting up the topoSetDict
can be tricky, depending on how acustomed you are with that tool. The trick is to generate two different mesh zones, one for the rotating part and the other one for the static part. The faces of the cylindrical boundary, there should be two almost identical ones, for the rotor and stator boundary, respectively. The topoSetDict
looks like the following:
- /*--------------------------------*- C++ -*----------------------------------*\
- | ========= | |
- | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
- | \\ / O peration | Version: 2.1.x |
- | \\ / A nd | Web: www.OpenFOAM.org |
- | \\/ M anipulation | |
- \*---------------------------------------------------------------------------*/
- FoamFile
- {
- version 2.0;
- format ascii;
- class dictionary;
- object topoSetDict;
- }
-
- // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
- actions
- (
- // Convert stator patch to a faceSet
- {
- name AMI;
- type faceSet;
- action new;
- source patchToFace;
- sourceInfo
- {
- name "STATOR1";
- }
- }
- // Get all cells in the region representing the rotor
- {
- name rotor;
- type cellSet;
- action new;
- source regionToCell;
- sourceInfo
- {
- nErode 0;
- insidePoints ((-2.0 -0.4 -0.5));
- }
- }
- // Convert cellSet to zone
- {
- name rotor;
- type cellZoneSet;
- action new;
- source setToCellZone;
- sourceInfo
- {
- set rotor;
- }
- }
- // Get all cells and then get a subset of them, using regionToFace
- {
- name rotorFace;
- type faceSet;
- action new;
- source boxToFace;
- sourceInfo
- {
- boxes ((-1e5 -1e5 -1e5) (1e5 1e5 1e5));
- }
- }
- {
- name rotorFace;
- type faceSet;
- action subset;
- source regionToFace;
- sourceInfo
- {
- set rotorFace;
- nearPoint (-2.4 0 -1);
- }
- }
- {
- name rotorFace;
- type faceZoneSet;
- action new;
- source setToFaceZone;
- sourceInfo
- {
- faceSet rotorFace;
- }
- }
- );
-
- // ************************************************************************* //
Now it is only a matter of executing each individual meshing script one at a time and combing them in the end, as shown in this section.