Monday, August 4, 2014

Generate Rotor-Stator Type Meshes in snappyHexMesh

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.
ami ggi in multip runs
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.
rudder
Screenshot of the rudder geometry
cylinder
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.

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 theconstant/extendedFeatureEdgeMesh/<your file name>.extendedFeatureEdgeMesh. It is important to create a symlink to <your file name>.eMeshas 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:
  1. #!/bin/sh
  2. cd ${0%/*} || exit 1
  3. # Source tutorial run functions
  4. . $WM_PROJECT_DIR/bin/tools/RunFunctions
  5. runApplication blockMesh
  6. runApplication surfaceFeatureExtract
  7. runApplication snappyHexMesh -overwrite
  8. 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.
rotor
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:
  1. #!/bin/sh
  2. cd ${0%/*} || exit 1
  3. # Source tutorial run functions
  4. . $WM_PROJECT_DIR/bin/tools/RunFunctions
  5. runApplication blockMesh
  6. runApplication surfaceFeatureExtract
  7. runApplication snappyHexMesh -overwrite
  8. sed -i "s/cylinder_ascii/ROTOR1/g" constant/polyMesh/boundary
  9. sed -i "s/rudder_ascii/RUDDER/g" constant/polyMesh/boundary
  10. sed -i "20,26d" constant/polyMesh/boundary
  11. 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:

  1. #!/bin/sh
  2. cd ${0%/*} || exit 1 # run from this directory
  3. # Source tutorial run functions
  4. . $WM_PROJECT_DIR/bin/tools/RunFunctions
  5. # Perform the sed action on the boundary file
  6. function fixBoundary {
  7. echo "Fixing Patch $1"
  8. l=`grep -n -E "$1$" constant/polyMesh/boundary | grep -o -E "^[0-9]*"`
  9. echo $l
  10. typeLine=$[$l + 2]
  11. sed -i "${typeLine}s/wall/cyclicAMI/g" constant/polyMesh/boundary
  12. sed -i "${typeLine}a \
  13. inGroups 1(cyclicAMI); \
  14. matchTolerance 1e-4; \
  15. transform noOrdering; \
  16. neighbourPatch $2;" constant/polyMesh/boundary
  17. }
  18. export rotor='../rudder'
  19. export stator='../box'
  20. rm -rf constant/polyMesh
  21. rm log.*
  22. cp -r $stator/constant/polyMesh constant/
  23. cp -r $stator/constant/triSurface constant/
  24. runApplication mergeMeshes -overwrite . $rotor
  25. runApplication topoSet
  26. fixBoundary "ROTOR1" "STATOR1"
  27. 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:
  1. /*--------------------------------*- C++ -*----------------------------------*\
  2. | ========= | |
  3. | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
  4. | \\ / O peration | Version: 2.1.x |
  5. | \\ / A nd | Web: www.OpenFOAM.org |
  6. | \\/ M anipulation | |
  7. \*---------------------------------------------------------------------------*/
  8. FoamFile
  9. {
  10. version 2.0;
  11. format ascii;
  12. class dictionary;
  13. object topoSetDict;
  14. }
  15. // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
  16. actions
  17. (
  18. // Convert stator patch to a faceSet
  19. {
  20. name AMI;
  21. type faceSet;
  22. action new;
  23. source patchToFace;
  24. sourceInfo
  25. {
  26. name "STATOR1";
  27. }
  28. }
  29. // Get all cells in the region representing the rotor
  30. {
  31. name rotor;
  32. type cellSet;
  33. action new;
  34. source regionToCell;
  35. sourceInfo
  36. {
  37. nErode 0;
  38. insidePoints ((-2.0 -0.4 -0.5));
  39. }
  40. }
  41. // Convert cellSet to zone
  42. {
  43. name rotor;
  44. type cellZoneSet;
  45. action new;
  46. source setToCellZone;
  47. sourceInfo
  48. {
  49. set rotor;
  50. }
  51. }
  52. // Get all cells and then get a subset of them, using regionToFace
  53. {
  54. name rotorFace;
  55. type faceSet;
  56. action new;
  57. source boxToFace;
  58. sourceInfo
  59. {
  60. boxes ((-1e5 -1e5 -1e5) (1e5 1e5 1e5));
  61. }
  62. }
  63. {
  64. name rotorFace;
  65. type faceSet;
  66. action subset;
  67. source regionToFace;
  68. sourceInfo
  69. {
  70. set rotorFace;
  71. nearPoint (-2.4 0 -1);
  72. }
  73. }
  74. {
  75. name rotorFace;
  76. type faceZoneSet;
  77. action new;
  78. source setToFaceZone;
  79. sourceInfo
  80. {
  81. faceSet rotorFace;
  82. }
  83. }
  84. );
  85. // ************************************************************************* //
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.

No comments:

Post a Comment