├── .gitignore ├── SP2.STL ├── VP1304_geom.dat ├── LICENSE ├── README.md ├── gmshCylinder.py ├── gmshSphere.py ├── highOrderMeshing.py ├── SP2_geom.dat ├── yPlus_estimate.py ├── gmshMinimalCircle.py ├── gmshPropeller.py ├── gmshRodAirfoil_2D.py └── gmshRodAirfoil.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.msh 2 | *.vtk 3 | -------------------------------------------------------------------------------- /SP2.STL: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etiennespieser/Propeller_Gmsh/HEAD/SP2.STL -------------------------------------------------------------------------------- /VP1304_geom.dat: -------------------------------------------------------------------------------- 1 | Variables = "r [m]", "chord [m]", "twist/pitch [deg]", "total rake [m]"(??), "skew/swept [deg]" (source: www.sva-potsdam.de/pptc https://doi.org/10.1016/B978-0-08-100366-4.00003-1 ) 2 | 0.0375 0.045 56.051622172441 0.00425 0 3 | 0.0379 0.0458 55.8454302382455 0.00383 -0.0453528645089532 4 | 0.05 0.0654 50.1427152311097 -0.00608 -1.43254373756651 5 | 0.0625 0.0813 45.1310784530789 -0.0115 -3.07252618871683 6 | 0.075 0.0942 40.6769726809978 -0.01275 -4.49660360214234 7 | 0.0875 0.1042 36.6334553076189 -0.00958 -4.91710033552881 8 | 0.0938 0.1064 34.6675171427561 -0.00668 -4.69029672404225 9 | 0.1 0.1071 32.7081790720004 -0.00275 -4.23227627970429 10 | 0.1063 0.1065 30.6319900891568 0.00234 -3.57589595063632 11 | 0.1125 0.1042 28.4378142514803 0.00925 -2.7767490394153 12 | 0.1188 0.0942 26.0270111633725 0.01775 -1.94399241648656 13 | 0.125 0.025 23.3340900054814 0.02708 -1.14599199838859 14 | 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Étienne Spieser (Tiānài) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Propeller Gmsh 2 | 3 | Propeller Gmsh is a toolkit aimed a generating high-order partially structured meshes around simple geometries of rods, airfoils and propeller blades. It is based on the opensource library Gmsh ([gmsh.info](https://gmsh.info/)). Mesh parameters can be controlled at will and the mesh can be made fully hex. 4 | 5 | `gmshToolkit.py` contains the basic routines to generate the mesh. `gmshRodAirfoil.py`, `gmshRodAirfoil_2D.py` and `gmshPropeller.py` provide illustrations of the use of the toolkit in 2D, 2.5D and 3D respectively. The grid close to the rod/airfoil surfaces and in the airfoil wake are structured and unstructured elsewhere. 6 | 7 | `yPlus_estimate.py` compares different empirical friction coefficient laws from the literature to estimate the mesh y+ for the targeted application. 8 | 9 | `gmshSphere.py` and `gmshCylinder.py` are used to generate structured/unstructered spherical and cylindrical control surfaces (data interpolation). 10 | 11 | `highOrderMeshing.py` is a script to generate a minimalistic 2D high-order mesh to test mesh Gmsh's algorithms. 12 | 13 | These scripts run with python 3 and assume gmsh-api is installed, e.g. using, 14 | 15 | ``` 16 | pip install gmsh 17 | ``` 18 | 19 | To enable the parallel version of the gmsh-api, follow: https://gitlab.onelab.info/gmsh/gmsh/-/issues/1422 20 | 21 | Bug tracking and sugestions are welcome. 22 | For general documentation, refer to: 23 | + https://gmsh.info/doc/texinfo/gmsh.html 24 | + https://bthierry.pages.math.cnrs.fr/tutorial/gmsh/ 25 | -------------------------------------------------------------------------------- /gmshCylinder.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Étienne Spieser (Tiānài), member of AANTC (https://aantc.ust.hk/) 2 | # available under MIT licence at: https://github.com/etiennespieser 3 | # ------------------------------------------------------------------------------------ 4 | 5 | import sys 6 | import gmsh 7 | from gmshToolkit import * 8 | import shutil 9 | 10 | y_max = 0.2 11 | y_min = -0.2 12 | r_cyl = 0.3 13 | elemSize = 0.1 14 | 15 | elemOrder = 3 # 8 is max order supported my navier_mfem: github.com/mfem/mfem/issues/3759 16 | highOrderBLoptim = 4 # 0: none, 17 | # 1: optimization, 18 | # 2: elastic+optimization, 19 | # 3: elastic, 20 | # 4: fast curving 21 | # by default choose 4. If for small "gridPts_alongNACA", LE curvature fails, try other values. 22 | gmsh.initialize() 23 | 24 | pointTag = 0 25 | lineTag = 0 26 | surfaceTag = 0 27 | volumeTag = 0 28 | 29 | [cylSurf, pointTag, lineTag, surfaceTag] = gmeshed_cylinder_surf(y_min, y_max, r_cyl, elemSize, pointTag, lineTag, surfaceTag) 30 | 31 | gmsh.model.geo.synchronize() 32 | 33 | gmsh.option.setNumber("Mesh.RecombineAll", 1) 34 | gmsh.option.setNumber("Mesh.ElementOrder", elemOrder) # gmsh.model.mesh.setOrder(elemOrder) 35 | gmsh.option.setNumber("Mesh.SecondOrderLinear", 0) 36 | gmsh.option.setNumber("Mesh.HighOrderOptimize", highOrderBLoptim) # NB: Where straight layers in BL are satisfactory, use addPlaneSurface() instead of addSurfaceFilling() and remove this high-order optimisation. 37 | gmsh.option.setNumber("Mesh.NumSubEdges", elemOrder) # just visualisation ?? 38 | 39 | gmsh.model.mesh.generate(2) 40 | 41 | # Create the relevant Gmsh data structures from Gmsh model. 42 | gmsh.model.geo.synchronize() 43 | [nodePerEntity, elemPerEntity] = countDOF() 44 | 45 | gmsh.model.addPhysicalGroup(pb_2Dim, [*cylSurf], 1, "Cylindrical Grid") 46 | 47 | # Write mesh data: 48 | gmsh.option.setNumber("Mesh.MshFileVersion", 2.2) # when ASCII format 2.2 is selected "Mesh.SaveAll=1" discards the group definitions (to be avoided!). 49 | 50 | # export mesh with all tags for computation: 51 | gmsh.write("Cylinder_"+str(sum(elemPerEntity))+"elems.msh") 52 | 53 | # delete the "__pycache__" folder: 54 | try: 55 | shutil.rmtree("__pycache__") 56 | except OSError as e: 57 | print("Error: %s - %s." % (e.filename, e.strerror)) 58 | 59 | # Creates graphical user interface 60 | if 'close' not in sys.argv: 61 | gmsh.fltk.run() 62 | 63 | # It finalize the Gmsh API 64 | gmsh.finalize() -------------------------------------------------------------------------------- /gmshSphere.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Étienne Spieser (Tiānài), member of AANTC (https://aantc.ust.hk/) 2 | # available under MIT licence at: https://github.com/etiennespieser 3 | # ------------------------------------------------------------------------------------ 4 | 5 | import sys 6 | import gmsh 7 | from gmshToolkit import * 8 | import shutil 9 | 10 | x_center = 0.0 11 | y_center = 0.0 12 | z_center = 0.0 13 | radius = 1.0 14 | elemSize = 0.2 15 | 16 | elemOrder = 8 # 8 is max order supported my navier_mfem: github.com/mfem/mfem/issues/3759 17 | highOrderBLoptim = 4 # 0: none, 18 | # 1: optimization, 19 | # 2: elastic+optimization, 20 | # 3: elastic, 21 | # 4: fast curving 22 | # by default choose 4. If for small "gridPts_alongNACA", LE curvature fails, try other values. 23 | 24 | gmsh.initialize() 25 | 26 | pointTag = 0 27 | lineTag = 0 28 | surfaceTag = 0 29 | volumeTag = 0 30 | 31 | pb_2Dim = 2 32 | 33 | # [sphereSurfTri, pointTag, lineTag, surfaceTag] = gmeshed_sphereTri_surf(x_center-3*radius, y_center, z_center, radius, elemSize, pointTag, lineTag, surfaceTag) 34 | [sphereSurfQuad, pointTag, lineTag, surfaceTag] = gmeshed_sphereQuad_surf(x_center, y_center, z_center, radius, elemSize, pointTag, lineTag, surfaceTag) 35 | 36 | 37 | gmsh.model.geo.synchronize() 38 | 39 | gmsh.option.setNumber("Mesh.RecombineAll", 1) 40 | gmsh.option.setNumber("Mesh.ElementOrder", elemOrder) # gmsh.model.mesh.setOrder(elemOrder) 41 | gmsh.option.setNumber("Mesh.SecondOrderLinear", 0) 42 | gmsh.option.setNumber("Mesh.HighOrderOptimize", highOrderBLoptim) # NB: Where straight layers in BL are satisfactory, use addPlaneSurface() instead of addSurfaceFilling() and remove this high-order optimisation. 43 | gmsh.option.setNumber("Mesh.NumSubEdges", elemOrder) # just visualisation ?? 44 | 45 | gmsh.model.mesh.generate(2) 46 | 47 | # Create the relevant Gmsh data structures from Gmsh model. 48 | gmsh.model.geo.synchronize() 49 | [nodePerEntity, elemPerEntity] = countDOF() 50 | 51 | gmsh.model.addPhysicalGroup(pb_2Dim, [*sphereSurfQuad], 1, "Spherical Grid Quad") 52 | 53 | # gmsh.model.addPhysicalGroup(pb_2Dim, [*sphereSurfTri], 2, "Spherical Grid Tri") 54 | 55 | # Write mesh data: 56 | gmsh.option.setNumber("Mesh.MshFileVersion", 2.2) # when ASCII format 2.2 is selected "Mesh.SaveAll=1" discards the group definitions (to be avoided!). 57 | 58 | # export mesh with all tags for computation: 59 | gmsh.write("sphere_"+str(sum(elemPerEntity))+"elems_o"+str(elemOrder)+".msh") 60 | 61 | # delete the "__pycache__" folder: 62 | try: 63 | shutil.rmtree("__pycache__") 64 | except OSError as e: 65 | print("Error: %s - %s." % (e.filename, e.strerror)) 66 | 67 | # Creates graphical user interface 68 | if 'close' not in sys.argv: 69 | gmsh.fltk.run() 70 | 71 | # It finalize the Gmsh API 72 | gmsh.finalize() -------------------------------------------------------------------------------- /highOrderMeshing.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Étienne Spieser (Tiānài), member of AANTC (https://aantc.ust.hk/) 2 | # available under MIT licence at: https://github.com/etiennespieser 3 | # ------------------------------------------------------------------------------------ 4 | 5 | import sys 6 | import gmsh 7 | import shutil 8 | 9 | elemOrder = 10 # (1=linear elements, N (<6) = elements of higher order) 10 | 11 | # https://gitlab.onelab.info/gmsh/gmsh/-/issues/1128 12 | # https://github.com/mfem/mfem/issues/2032 13 | # https://www.sciencedirect.com/science/article/pii/S0021999113004956?ref=pdf_download&fr=RR-2&rr=7d66d5048c5e10ac 14 | 15 | # https://gitlab.onelab.info/gmsh/gmsh/-/issues/527 16 | 17 | # https://wiki.freecad.org/FEM_MeshGmshFromShape 18 | 19 | # https://gitlab.onelab.info/gmsh/gmsh/blob/gmsh_4_11_1/tutorials/python/t5.py 20 | 21 | # Initialize gmsh: 22 | gmsh.initialize() 23 | 24 | pointTag = 0 25 | lineTag = 0 26 | surfaceTag = 0 27 | 28 | gmsh.model.geo.addPoint(0.0, 1.0, 0.0, 0.1, pointTag+1) 29 | pointTag = pointTag+1 # store the last 'pointTag' from previous loop 30 | gmsh.model.geo.addPoint(1.0, 0.5, 0.0, 0.1, pointTag+1) 31 | pointTag = pointTag+1 # store the last 'pointTag' from previous loop 32 | gmsh.model.geo.addPoint(1.5, 2.0, 0.0, 0.1, pointTag+1) 33 | pointTag = pointTag+1 # store the last 'pointTag' from previous loop 34 | gmsh.model.geo.addPoint(1.5, 3.0, 0.0, 0.1, pointTag+1) 35 | pointTag = pointTag+1 # store the last 'pointTag' from previous loop 36 | gmsh.model.geo.addPoint(-0.5, 1.5, 0.0, 0.1, pointTag+1) 37 | pointTag = pointTag+1 # store the last 'pointTag' from previous loop 38 | 39 | # gmsh.model.geo.add_line(1, 2, lineTag+1) 40 | # lineTag = lineTag+1 41 | # gmsh.model.geo.add_line(2, 3, lineTag+1) 42 | # lineTag = lineTag+1 43 | # https://gitlab.onelab.info/gmsh/gmsh/blob/master/examples/api/spline.py 44 | gmsh.model.geo.addSpline([1,2,3], lineTag+1) 45 | lineTag = lineTag+1 46 | # gmsh.model.geo.addBSpline([1,2,3], lineTag+1) 47 | # lineTag = lineTag+1 48 | 49 | gmsh.model.geo.add_line(3, 4, lineTag+1) 50 | lineTag = lineTag+1 51 | gmsh.model.geo.add_line(4, 5, lineTag+1) 52 | lineTag = lineTag+1 53 | gmsh.model.geo.add_line(5, 1, lineTag+1) 54 | lineTag = lineTag+1 55 | 56 | for i in range(1,5): 57 | gmsh.model.geo.mesh.setTransfiniteCurve(i, 5) 58 | 59 | gmsh.model.geo.add_curve_loop([1, 2, 3, 4], surfaceTag+1) 60 | gmsh.model.geo.addPlaneSurface([surfaceTag+1], surfaceTag+1) 61 | gmsh.model.geo.mesh.setTransfiniteSurface(surfaceTag+1) 62 | surfaceTag = surfaceTag+1 63 | 64 | gmsh.model.geo.synchronize() 65 | 66 | gmsh.option.setNumber("Mesh.RecombineAll", 1) 67 | 68 | gmsh.model.mesh.generate(2) 69 | 70 | 71 | gmsh.model.mesh.setOrder(elemOrder) 72 | gmsh.option.setNumber("Mesh.NumSubEdges", elemOrder) # just visualisation ?? 73 | gmsh.option.setNumber("Mesh.SecondOrderLinear", 0) 74 | gmsh.option.setNumber("Mesh.HighOrderOptimize", 2) # (0: none, 1: optimization, 2: elastic+optimization, 3: elastic, 4: fast curving) 75 | 76 | # Write mesh data: 77 | gmsh.option.setNumber("Mesh.MshFileVersion", 2.2) # when ASCII format 2.2 is selected "Mesh.SaveAll=1" discards the group definitions (to be avoided!). 78 | 79 | gmsh.write("high-order_test.msh") 80 | gmsh.write("high-order_test.vtk") 81 | 82 | # delete the "__pycache__" folder: 83 | try: 84 | shutil.rmtree("__pycache__") 85 | except OSError as e: 86 | print("Error: %s - %s." % (e.filename, e.strerror)) 87 | 88 | # Creates graphical user interface 89 | if 'close' not in sys.argv: 90 | gmsh.fltk.run() 91 | 92 | # It finalize the Gmsh API 93 | gmsh.finalize() 94 | -------------------------------------------------------------------------------- /SP2_geom.dat: -------------------------------------------------------------------------------- 1 | Variables = "r [m]", "chord [m]", "twist [deg]", "total rake [m]", "skew [deg]" Twist ref. is located at mid chord. Blade section: NACA4412 airfoil which params are rebuilt from CAD planar sections 2 | 9.0000000e-03 1.0174790e-02 3.8066434e+01 4.8251748e-05 -3.4806294e+00 3 | 1.1040816e-02 1.1850344e-02 4.0447791e+01 8.6764256e-05 -3.4045517e+00 4 | 1.3081633e-02 1.3162700e-02 4.2122807e+01 1.2514085e-04 -3.3275757e+00 5 | 1.5122449e-02 1.4167062e-02 4.3184194e+01 1.6338152e-04 -3.2505098e+00 6 | 1.7163265e-02 1.4913918e-02 4.3717519e+01 2.0148627e-04 -3.1740942e+00 7 | 1.9204082e-02 1.5449213e-02 4.3801466e+01 2.3945510e-04 -3.0990016e+00 8 | 2.1244898e-02 1.5814513e-02 4.3508092e+01 2.7728802e-04 -3.0258364e+00 9 | 2.3285714e-02 1.6047170e-02 4.2903089e+01 3.1498501e-04 -2.9551351e+00 10 | 2.5326531e-02 1.6180495e-02 4.2046038e+01 3.5254609e-04 -2.8873666e+00 11 | 2.7367347e-02 1.6243920e-02 4.0990674e+01 3.8997125e-04 -2.8229315e+00 12 | 2.9408163e-02 1.6263166e-02 3.9785139e+01 4.2726049e-04 -2.7621625e+00 13 | 3.1448980e-02 1.6260412e-02 3.8472243e+01 4.6441382e-04 -2.7053247e+00 14 | 3.3489796e-02 1.6254461e-02 3.7089724e+01 5.0143122e-04 -2.6526149e+00 15 | 3.5530612e-02 1.6260905e-02 3.5670502e+01 5.3831271e-04 -2.6041621e+00 16 | 3.7571429e-02 1.6292296e-02 3.4242944e+01 5.7505828e-04 -2.5600275e+00 17 | 3.9612245e-02 1.6358311e-02 3.2831120e+01 6.1166792e-04 -2.5202042e+00 18 | 4.1653061e-02 1.6465917e-02 3.1455060e+01 6.4814165e-04 -2.4846173e+00 19 | 4.3693878e-02 1.6619542e-02 3.0131013e+01 6.8447947e-04 -2.4531243e+00 20 | 4.5734694e-02 1.6821240e-02 2.8871709e+01 7.2068136e-04 -2.4255143e+00 21 | 4.7775510e-02 1.7070857e-02 2.7686616e+01 7.5674733e-04 -2.4015090e+00 22 | 4.9816327e-02 1.7366202e-02 2.6582195e+01 7.9267739e-04 -2.3807617e+00 23 | 5.1857143e-02 1.7703209e-02 2.5562164e+01 8.2847153e-04 -2.3628580e+00 24 | 5.3897959e-02 1.8076108e-02 2.4627756e+01 8.6412975e-04 -2.3473156e+00 25 | 5.5938776e-02 1.8477589e-02 2.3777973e+01 8.9965205e-04 -2.3335842e+00 26 | 5.7979592e-02 1.8898971e-02 2.3009851e+01 9.3503843e-04 -2.3210455e+00 27 | 6.0020408e-02 1.9330371e-02 2.2318714e+01 9.7028889e-04 -2.3090134e+00 28 | 6.2061224e-02 1.9760866e-02 2.1698435e+01 1.0054034e-03 -2.2967337e+00 29 | 6.4102041e-02 2.0178663e-02 2.1141694e+01 1.0403821e-03 -2.2833846e+00 30 | 6.6142857e-02 2.0571268e-02 2.0640237e+01 1.0752248e-03 -2.2680759e+00 31 | 6.8183673e-02 2.0925650e-02 2.0185134e+01 1.1099316e-03 -2.2498499e+00 32 | 7.0224490e-02 2.1228407e-02 1.9767038e+01 1.1445024e-03 -2.2276807e+00 33 | 7.2265306e-02 2.1465938e-02 1.9376446e+01 1.1789374e-03 -2.2004746e+00 34 | 7.4306122e-02 2.1624605e-02 1.9003954e+01 1.2132364e-03 -2.1670699e+00 35 | 7.6346939e-02 2.1690905e-02 1.8640517e+01 1.2473995e-03 -2.1262369e+00 36 | 7.8387755e-02 2.1651631e-02 1.8277708e+01 1.2814267e-03 -2.0766782e+00 37 | 8.0428571e-02 2.1494045e-02 1.7907979e+01 1.3153180e-03 -2.0170283e+00 38 | 8.2469388e-02 2.1206040e-02 1.7524914e+01 1.3490734e-03 -1.9458538e+00 39 | 8.4510204e-02 2.0776311e-02 1.7123493e+01 1.3826928e-03 -1.8616534e+00 40 | 8.6551020e-02 2.0194522e-02 1.6700350e+01 1.4161763e-03 -1.7628577e+00 41 | 8.8591837e-02 1.9451468e-02 1.6254029e+01 1.4495239e-03 -1.6478296e+00 42 | 9.0632653e-02 1.8539249e-02 1.5785243e+01 1.4827356e-03 -1.5148640e+00 43 | 9.2673469e-02 1.7451432e-02 1.5297137e+01 1.5158114e-03 -1.3621879e+00 44 | 9.4714286e-02 1.6183220e-02 1.4795542e+01 1.5487512e-03 -1.1879601e+00 45 | 9.6755102e-02 1.4731621e-02 1.4289235e+01 1.5815552e-03 -9.9027192e-01 46 | 9.8795918e-02 1.3095609e-02 1.3790199e+01 1.6142232e-03 -7.6714639e-01 47 | 1.0083673e-01 1.1276298e-02 1.3313880e+01 1.6467553e-03 -5.1653874e-01 48 | 1.0287755e-01 9.2771053e-03 1.2879447e+01 1.6791515e-03 -2.3633629e-01 49 | 1.0491837e-01 7.1039199e-03 1.2510049e+01 1.7114117e-03 7.5641621e-02 50 | 1.0695918e-01 4.7652683e-03 1.2233078e+01 1.7435361e-03 4.2164355e-01 51 | 1.0900000e-01 2.2724825e-03 1.2080420e+01 1.7755245e-03 8.0398601e-01 52 | -------------------------------------------------------------------------------- /yPlus_estimate.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Étienne Spieser (Tiānài), member of AANTC (https://aantc.ust.hk/) 2 | # available under MIT licence at: https://github.com/etiennespieser 3 | # ------------------------------------------------------------------------------------ 4 | 5 | # source: Pope, Turbulent Flows, Cambridge university press, 2000 6 | # https://www.cfd-online.com/Wiki/Y_plus_wall_distance_estimation 7 | # https://www.cfd-online.com/Wiki/Skin_friction_coefficient 8 | 9 | import sys 10 | import numpy as np 11 | import matplotlib.pyplot as plt 12 | sys.path.append('../WaveMap59/') 13 | import grimoireOfPlots as gop 14 | 15 | Re = 5*10**4 16 | u_ext = 3.75 # m/s 17 | kin_vis = 0.000015 # m**2/s 18 | y_1stCell = 4.88e-4*0.2 19 | target_yPlus = 1.0 20 | 21 | plotCfModels = False 22 | 23 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 24 | # # Skin friction coefficient model # # 25 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 26 | 27 | Re_range = 10**np.linspace(4, 10, 10000) 28 | 29 | Cf_powerLaw_1_7 = 0.0576*Re_range**(-1/5) # for 5*10**5 < Re < 10**7 30 | Cf_powerLaw_1_7_recalibrated = 0.0592*Re_range**(-1/5) # for 5*10**5 < Re < 10**7 31 | Cf_Schlichting = (2.0*np.log10(Re_range)-0.65)**(-2.3) # for Re < 10**9 32 | Cf_SchultzGrunov = 0.37*np.log10(Re_range)**(-2.584) 33 | Cf_Prandtl = 0.074*Re_range**(-1/5) # year 1927 34 | Cf_Telfer = 0.34*Re_range**(-1/3) + 0.0012 # year 1927 35 | Cf_PrandtlSchlichting = 0.455*np.log10(Re_range)**(-2.58) # year 1932 36 | # Cf_Schoenherr = 0.0586*np.log10(Re_range*Cf_Schoenherr)**(-2) # year 1932 37 | # Cf_vonKarmanTheodore = (4.15*np.log10(Re_range*Cf_vonKarmanTheodore)+1.7)**(-2) # year 1934 38 | Cf_SchultzGrunov2 = 0.427*(np.log10(Re_range)-0.407)**(-2.64) # year 1940 39 | Cf_KempfKarman = 0.055*Re_range**(-0.182) # year 1951 40 | # Cf_LapTroost = 0.0648*(np.log10(Re_range*Cf_LapTroost**0.5)-0.9526)**(-2) # year 1952 41 | Cf_Landweber = 0.0816*(np.log10(Re_range)-1.703)**(-2) # year 1953 42 | Cf_Hughes = 0.067*(np.log10(Re_range)-2)**(-2) # year 1954 43 | Cf_Wieghard = 0.52*np.log10(Re_range)**(-2.685) # year 1955 44 | Cf_ITTC = 0.075*(np.log10(Re_range)-2)**(-2) # year 1957 45 | Cf_Gadd = 0.0113*(np.log10(Re_range)-3.7)**(-1.15) # year 1967 46 | Cf_Granville = 0.0776*(np.log10(Re_range)-1.88)**(-2) + 60/Re_range # year 1977 47 | # Cf_DateTurnock = (4.06*np.log10(Re_range*Cf_DateTurnock)-0.729)**(-2) # year 1999 48 | 49 | if plotCfModels: 50 | windowsWidth = 8 # cm 51 | axisEqual = False 52 | HV_ratio = 0.75 53 | fig, ax = gop.createPaintingFrame(windowsWidth, Re_range[0], Re_range[-1], 10**(-3), 0.1, axisEqual, HV_ratio) 54 | plt.loglog(Re_range, Cf_powerLaw_1_7, "") 55 | plt.loglog(Re_range, Cf_powerLaw_1_7_recalibrated, "") 56 | plt.loglog(Re_range, Cf_Schlichting, "") 57 | plt.loglog(Re_range, Cf_SchultzGrunov, "") 58 | plt.loglog(Re_range, Cf_Prandtl, "") 59 | plt.loglog(Re_range, Cf_Telfer, "") 60 | plt.loglog(Re_range, Cf_PrandtlSchlichting, "") 61 | plt.loglog(Re_range, Cf_SchultzGrunov2, "") 62 | plt.loglog(Re_range, Cf_KempfKarman, "") 63 | plt.loglog(Re_range, Cf_Landweber, "") 64 | plt.loglog(Re_range, Cf_Hughes, "") 65 | plt.loglog(Re_range, Cf_Wieghard, "+") 66 | plt.loglog(Re_range, Cf_ITTC, "") 67 | plt.loglog(Re_range, Cf_Gadd, "") 68 | plt.loglog(Re_range, Cf_Granville, "") 69 | plt.xlabel(r'$\rm{Re}$') 70 | plt.ylabel(r'$C_f$') 71 | plt.show() 72 | 73 | # selection of the model for the skin friction coefficient: 74 | Cf_range = Cf_Wieghard 75 | 76 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 77 | # # compute of the dimensional thickness y+ # # 78 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 79 | 80 | Cf = np.interp(Re, Re_range, Cf_range) 81 | frictionVelo = np.sqrt(Cf*u_ext**2/2) 82 | y_optimal = target_yPlus*kin_vis/frictionVelo 83 | yPlus_1stCell = y_1stCell*frictionVelo/kin_vis 84 | 85 | print("The dimensional thickness y+ at the wall measures "+'{:.2e}'.format(yPlus_1stCell)+",") 86 | print("for a target y+ of "+str(target_yPlus)+", the first cell height must be "'{:.2e}'.format(y_optimal)+" m.") 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /gmshMinimalCircle.py: -------------------------------------------------------------------------------- 1 | 2 | import sys 3 | import gmsh 4 | from gmshToolkit import * 5 | import shutil 6 | 7 | elemOrder = 8 # discretisation of the line 8 | highOrderBLoptim = 4 # 0: none, 9 | # 1: optimization, 10 | # 2: elastic+optimization, 11 | # 3: elastic, 12 | # 4: fast curving 13 | # by default choose 4. If for small "gridPts_alongNACA", LE curvature fails, try other values. 14 | 15 | R = 1 16 | dl = R/100 17 | center = [0.0, 0.0, 0.0] 18 | arcPts = int(np.pi/(2*dl)) 19 | 20 | # Initialize gmsh: 21 | gmsh.initialize() 22 | 23 | pointTag = 0 24 | lineTag = 0 25 | surfaceTag = 0 26 | 27 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$ 28 | # # creation of the Points # # 29 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$ 30 | 31 | # circle center: 32 | gmsh.model.geo.addPoint(center[0], center[1], center[2], dl, pointTag+1) 33 | pointTag = pointTag+1 # store the last 'pointTag' from previous loop 34 | point_rodCenter = pointTag 35 | # circle control points: 36 | gmsh.model.geo.addPoint(center[0]+R, center[1], center[2], dl, pointTag+1) 37 | pointTag = pointTag+1 # store the last 'pointTag' from previous loop 38 | point_rodPt1 = pointTag 39 | gmsh.model.geo.addPoint(center[0], center[1]+R, center[2], dl, pointTag+1) 40 | pointTag = pointTag+1 # store the last 'pointTag' from previous loop 41 | point_rodPt2 = pointTag 42 | gmsh.model.geo.addPoint(center[0]-R, center[1], center[2], dl, pointTag+1) 43 | pointTag = pointTag+1 # store the last 'pointTag' from previous loop 44 | point_rodPt3 = pointTag 45 | gmsh.model.geo.addPoint(center[0], center[1]-R, center[2], dl, pointTag+1) 46 | pointTag = pointTag+1 # store the last 'pointTag' from previous loop 47 | point_rodPt4 = pointTag 48 | 49 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$ 50 | # # creation of the Lines # # 51 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$ 52 | 53 | gmsh.model.geo.addCircleArc(point_rodPt1, point_rodCenter, point_rodPt2, lineTag+1) 54 | gmsh.model.geo.mesh.setTransfiniteCurve(lineTag+1, arcPts) 55 | lineTag = lineTag+1 # store the last 'lineTag' from previous loop 56 | line_rodArc1 = lineTag 57 | gmsh.model.geo.addCircleArc(point_rodPt2, point_rodCenter, point_rodPt3, lineTag+1) 58 | gmsh.model.geo.mesh.setTransfiniteCurve(lineTag+1, arcPts) 59 | lineTag = lineTag+1 # store the last 'lineTag' from previous loop 60 | line_rodArc2 = lineTag 61 | gmsh.model.geo.addCircleArc(point_rodPt3, point_rodCenter, point_rodPt4, lineTag+1) 62 | gmsh.model.geo.mesh.setTransfiniteCurve(lineTag+1, arcPts) 63 | lineTag = lineTag+1 # store the last 'lineTag' from previous loop 64 | line_rodArc3 = lineTag 65 | gmsh.model.geo.addCircleArc(point_rodPt4, point_rodCenter, point_rodPt1, lineTag+1) 66 | gmsh.model.geo.mesh.setTransfiniteCurve(lineTag+1, arcPts) 67 | lineTag = lineTag+1 # store the last 'lineTag' from previous loop 68 | line_rodArc4 = lineTag 69 | 70 | lineLoop = [line_rodArc1, line_rodArc2, line_rodArc3, line_rodArc4] 71 | 72 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 73 | # # creation of the surface (not exported, for visualisation only) # # 74 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 75 | 76 | gmsh.model.geo.add_curve_loop(lineLoop, surfaceTag+1) 77 | gmsh.model.geo.addPlaneSurface([surfaceTag+1], surfaceTag+1) 78 | gmsh.model.geo.mesh.setRecombine(pb_2Dim, surfaceTag+1) 79 | 80 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 81 | # # Generate visualise and export the mesh # # 82 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 83 | 84 | # https://gmsh.info/doc/texinfo/gmsh.html#Mesh-options 85 | 86 | gmsh.model.geo.synchronize() 87 | 88 | # high-order params 89 | gmsh.option.setNumber("Mesh.ElementOrder", elemOrder) # gmsh.model.mesh.setOrder(elemOrder) 90 | gmsh.option.setNumber("Mesh.HighOrderOptimize", highOrderBLoptim) 91 | gmsh.option.setNumber("Mesh.NumSubEdges", elemOrder) 92 | 93 | gmsh.model.mesh.generate() 94 | 95 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 96 | # # Creation of the physical group # # 97 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 98 | 99 | # Create the relevant Gmsh data structures from Gmsh model. 100 | gmsh.model.geo.synchronize() 101 | # gmsh.model.setColor([(2, 3)], 255, 0, 0) # Red 102 | 103 | gmsh.model.addPhysicalGroup(pb_1Dim, [*lineLoop], 1, "Circle") 104 | 105 | # Write mesh data: 106 | gmsh.option.setNumber("Mesh.MshFileVersion", 2.2) # when ASCII format 2.2 is selected "Mesh.SaveAll=1" discards the group definitions (to be avoided!). 107 | 108 | # export mesh with all tags for computation: 109 | gmsh.write("circle_"+str(np.max([4, 4*(arcPts-1)]))+"elems_mo"+str(elemOrder)+".msh") 110 | 111 | # delete the "__pycache__" folder: 112 | try: 113 | shutil.rmtree("__pycache__") 114 | except OSError as e: 115 | print("Error: %s - %s." % (e.filename, e.strerror)) 116 | 117 | # Creates graphical user interface 118 | if 'close' not in sys.argv: 119 | gmsh.fltk.run() 120 | 121 | # It finalize the Gmsh API 122 | gmsh.finalize() 123 | -------------------------------------------------------------------------------- /gmshPropeller.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Étienne Spieser (Tiānài), member of AANTC (https://aantc.ust.hk/) 2 | # available under MIT licence at: https://github.com/etiennespieser 3 | # ------------------------------------------------------------------------------------ 4 | 5 | import sys 6 | import gmsh 7 | from gmshToolkit import * 8 | import shutil 9 | 10 | NACA_type = '4412' 11 | 12 | geometry_file = "SP2_geom" # "VP1304_geom" , "SP2_geom" from https://doi.org/10.1063/5.0098891 13 | 14 | bluntTrailingEdge = False 15 | 16 | gridPts_alongNACA = 20 17 | 18 | gridPts_inBL = 5 # > 2 for split into fully hex mesh 19 | gridGeomProg_inBL = 1.1 20 | 21 | TEpatchGridFlaringAngle = 30 # deg 22 | gridPts_alongTEpatch = 7 # > 2 for split into fully hex mesh 23 | gridGeomProg_alongTEpatch = 1.05 24 | 25 | wakeGridFlaringAngle = 0 # deg 26 | gridPts_alongWake = 7 # > 2 for split into fully hex mesh 27 | gridGeomProg_alongWake = 1.0 28 | 29 | [radii_vec, chord_vec, pitch_vecAngle, rake_vec, skew_vecAngle] = read_geometry(geometry_file+".dat") # reads geometry and defines tip truncation 30 | 31 | skew_vec = np.sin(skew_vecAngle*np.pi/180)*radii_vec 32 | # "SP2_geom.dat" considers the total rake. rake_vec = generatorRake_vec + skew_vec*np.tan(pitch_vecAngle*np.pi/180) 33 | 34 | # # conversion from (m) to (mm) 35 | # radii_vec = 1000*radii_vec 36 | # chord_vec = 1000*chord_vec 37 | # rake_vec = 1000*rake_vec 38 | # skew_vec = 1000*skew_vec 39 | 40 | airfoilReferenceCoordinate = np.array([skew_vec, -rake_vec, -radii_vec]).transpose() 41 | 42 | # # for dummy geom, uncomment below: 43 | # radii_vec = [0.1, 0.8, 1.5] 44 | # pitch_vecAngle = [20.0, 30.0, 35.0] 45 | 46 | radii_step = [1] * len(radii_vec) # number of radial elements between to radial slices. 47 | 48 | airfoilReferenceAlongChord_c = 0.5 49 | TEpatchLength_c = 0.5 # length of the TEpatch in along the x-axis 50 | wakeLength_c = 0.5 # length of the wake in along the x-axis 51 | height_LE_c = 0.2 # Structured Grid offset layer gap at the leading edge 52 | height_TE_c = 0.3 # Structured Grid offset layer gap at the trailing edge 53 | gridPts_inTE = int(gridPts_inBL/4) # if the TE is blunt, number of cells in the TE half height. NB: for the Blossom algorithm to work an even number of faces must be given. 54 | airfoilReferenceAlongChord_c = 0.5 55 | 56 | # Initialize gmsh: 57 | gmsh.initialize() 58 | 59 | ## to dig to enable multi-threading: 60 | # gmsh.option.setNumber("General.NumThreads",4) 61 | # print("general nt: ", gmsh.option.getNumber("General.NumThreads")) 62 | # # https://gitlab.onelab.info/gmsh/gmsh/-/issues/1436 63 | # # https://gitlab.onelab.info/gmsh/gmsh/-/issues/1093 64 | # # https://gitlab.onelab.info/gmsh/gmsh/-/issues/1422 65 | 66 | 67 | pTS1 = [] # pointTag_struct -- blade 1 68 | lTS1 = [] # lineTag_struct -- blade 1 69 | sTS1 = [] # surfaceTag_struct -- blade 1 70 | 71 | pTS2 = [] # pointTag_struct -- blade 2 72 | lTS2 = [] # lineTag_struct -- blade 2 73 | sTS2 = [] # surfaceTag_struct -- blade 2 74 | 75 | pointTag = 0 76 | lineTag = 0 77 | surfaceTag = 0 78 | volumeTag = 0 79 | 80 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 81 | # # Creation of the propeller mesh # # 82 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 83 | 84 | # Creation of blade number 1 *** *** *** 85 | rotMat = rotationMatrix([0.0, 0.0, 0.0]) # angles in degree around [axisZ, axisY, axisX] 86 | shiftVec = [0.0, 0.0, 0.0] 87 | for i in range(len(radii_vec)): 88 | 89 | structTag = [pointTag, lineTag, surfaceTag] 90 | GeomSpec = [NACA_type, bluntTrailingEdge, pitch_vecAngle[i], chord_vec[i], airfoilReferenceAlongChord_c*chord_vec[i], airfoilReferenceCoordinate[i], height_LE_c*chord_vec[i], height_TE_c*chord_vec[i], TEpatchLength_c*chord_vec[i]*np.cos(pitch_vecAngle[i]*np.pi/180), TEpatchGridFlaringAngle, wakeLength_c*chord_vec[i]*np.cos(pitch_vecAngle[i]*np.pi/180), wakeGridFlaringAngle] 91 | GridPtsSpec = [gridPts_alongNACA, gridPts_inBL, gridPts_inTE, gridPts_alongTEpatch, gridPts_alongWake, gridGeomProg_inBL, gridGeomProg_alongTEpatch, gridGeomProg_alongWake] 92 | 93 | if i == len(radii_vec)-1: 94 | GeomSpec[0] = '0012' # force 'NACA_type' to be 0012 for the last slice 95 | [pointTag_list, lineTag_list, surfaceTag_list, pointTag, lineTag, surfaceTag] = gmeshed_airfoil(structTag, GeomSpec, GridPtsSpec, rotMat, shiftVec) 96 | 97 | pTS1.append(pointTag_list) 98 | lTS1.append(lineTag_list) 99 | sTS1.append(surfaceTag_list) 100 | 101 | [tlTS1, lineTag] = gmeshed_blade_tl(pTS1, gridPts_alongNACA, radii_step, bluntTrailingEdge, lineTag) 102 | 103 | [tsTS1, surfaceTag] = gmeshed_blade_ts(lTS1, tlTS1, gridPts_alongNACA, radii_step, bluntTrailingEdge, surfaceTag) 104 | 105 | [tsTS_tip1, pointTag, lineTag, surfaceTag] = gmeshed_bladeTip_ts(pTS1[-1], lTS1[-1], GeomSpec, GridPtsSpec, rotMat, shiftVec, pointTag, lineTag, surfaceTag) 106 | 107 | [sStructGridSkin1, sairfoilSkin1] = returnStructGridOuterShell(sTS1, tsTS1, tsTS_tip1, radii_step, bluntTrailingEdge) 108 | 109 | 110 | # Creation of blade number 2 *** *** *** 111 | rotMat = rotationMatrix([180.0, 0.0, 180.0]) # angles in degree around [axisZ, axisY, axisX] 112 | 113 | for i in range(len(radii_vec)): 114 | 115 | structTag = [pointTag, lineTag, surfaceTag] 116 | GeomSpec = [NACA_type, bluntTrailingEdge, pitch_vecAngle[i], chord_vec[i], airfoilReferenceAlongChord_c*chord_vec[i], airfoilReferenceCoordinate[i], height_LE_c*chord_vec[i], height_TE_c*chord_vec[i], TEpatchLength_c*chord_vec[i]*np.cos(pitch_vecAngle[i]*np.pi/180), TEpatchGridFlaringAngle, wakeLength_c*chord_vec[i]*np.cos(pitch_vecAngle[i]*np.pi/180), wakeGridFlaringAngle] 117 | GridPtsSpec = [gridPts_alongNACA, gridPts_inBL, gridPts_inTE, gridPts_alongTEpatch, gridPts_alongWake, gridGeomProg_inBL, gridGeomProg_alongTEpatch, gridGeomProg_alongWake] 118 | 119 | if i == len(radii_vec)-1: 120 | GeomSpec[0] = '0012' # force 'NACA_type' to be 0012 for the last slice 121 | [pointTag_list, lineTag_list, surfaceTag_list, pointTag, lineTag, surfaceTag] = gmeshed_airfoil(structTag, GeomSpec, GridPtsSpec, rotMat, shiftVec) 122 | 123 | pTS2.append(pointTag_list) 124 | lTS2.append(lineTag_list) 125 | sTS2.append(surfaceTag_list) 126 | 127 | [tlTS2, lineTag] = gmeshed_blade_tl(pTS2, gridPts_alongNACA, radii_step, bluntTrailingEdge, lineTag) 128 | 129 | [tsTS2, surfaceTag] = gmeshed_blade_ts(lTS2, tlTS2, gridPts_alongNACA, radii_step, bluntTrailingEdge, surfaceTag) 130 | 131 | [tsTS_tip2, pointTag, lineTag, surfaceTag] = gmeshed_bladeTip_ts(pTS2[-1], lTS2[-1], GeomSpec, GridPtsSpec, rotMat, shiftVec, pointTag, lineTag, surfaceTag) 132 | 133 | [sStructGridSkin2, sairfoilSkin2] = returnStructGridOuterShell(sTS2, tsTS2, tsTS_tip2, radii_step, bluntTrailingEdge) 134 | 135 | 136 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 137 | # # Creation of the transfinite blade volumes # # 138 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 139 | 140 | [vTS1, volumeTag] = gmeshed_blade_vol(sTS1, tsTS1, gridPts_alongNACA, radii_step, bluntTrailingEdge, volumeTag) 141 | [vTS_tip1, volumeTag] = gmeshed_bladeTip_vol(sTS1[-1], tsTS_tip1, gridPts_alongNACA, bluntTrailingEdge, volumeTag) 142 | 143 | [vTS2, volumeTag] = gmeshed_blade_vol(sTS2, tsTS2, gridPts_alongNACA, radii_step, bluntTrailingEdge, volumeTag) 144 | [vTS_tip2, volumeTag] = gmeshed_bladeTip_vol(sTS2[-1], tsTS_tip2, gridPts_alongNACA, bluntTrailingEdge, volumeTag) 145 | 146 | vol_blade_1 = returnStructGridVol(vTS1, vTS_tip1, bluntTrailingEdge) 147 | vol_blade_2 = returnStructGridVol(vTS2,vTS_tip2, bluntTrailingEdge) 148 | 149 | ## below to generate a blade propeller without tip correction: 150 | # [sStructGridSkin1, sairfoilSkin1] = returnStructGridOuterShell_withoutTip(sTS1, tsTS1, radii_step, bluntTrailingEdge) 151 | # vol_blade_1 = returnStructGridVol_withoutTip(vTS1, bluntTrailingEdge) 152 | 153 | 154 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 155 | # # Creation of the cylindrical volumes # # 156 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 157 | 158 | if geometry_file == "VP1304_geom" : 159 | y_max_cyl1 = 0.15 160 | y_min_cyl1 = -0.2 161 | r_cyl1 = 0.3 162 | elemSize_cyl1 = 0.01 163 | 164 | y_max_cyl2 = 0.3 165 | y_min_cyl2 = -0.3 166 | r_cyl2 = 0.5 167 | elemSize_cyl2 = 0.02 168 | 169 | elif geometry_file == "SP2_geom": 170 | y_max_cyl1 = 0.03 171 | y_min_cyl1 = -0.04 172 | r_cyl1 = 0.15 173 | elemSize_cyl1 = 0.002 174 | 175 | y_max_cyl2 = 0.075 176 | y_min_cyl2 = -0.15 177 | r_cyl2 = 0.2 178 | elemSize_cyl2 = 0.01 179 | 180 | [ cylSurf1, pointTag, lineTag, surfaceTag] = gmeshed_cylinder_surf(y_min_cyl1, y_max_cyl1, r_cyl1, elemSize_cyl1, pointTag, lineTag, surfaceTag) 181 | [ cylSurf2, pointTag, lineTag, surfaceTag] = gmeshed_cylinder_surf(y_min_cyl2, y_max_cyl2, r_cyl2, elemSize_cyl2, pointTag, lineTag, surfaceTag) 182 | 183 | gmsh.model.geo.addSurfaceLoop([*sStructGridSkin1, *sStructGridSkin2, *cylSurf1], volumeTag+1) 184 | gmsh.model.geo.addVolume([volumeTag+1], volumeTag+1) 185 | gmsh.model.geo.mesh.setRecombine(pb_3Dim, volumeTag+1) # To create quadrangles instead of triangles 186 | volumeTag = volumeTag+1 187 | vol_unstructCFD = volumeTag 188 | 189 | gmsh.model.geo.addSurfaceLoop([*cylSurf1, *cylSurf2], volumeTag+1) 190 | gmsh.model.geo.addVolume([volumeTag+1], volumeTag+1) 191 | gmsh.model.geo.mesh.setRecombine(pb_3Dim, volumeTag+1) # To create quadrangles instead of triangles 192 | volumeTag = volumeTag+1 193 | vol_unstructBUFF = volumeTag 194 | 195 | 196 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 197 | # # Generate visualise and export the mesh # # 198 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 199 | 200 | # https://gmsh.info/doc/texinfo/gmsh.html#Mesh-options 201 | 202 | gmsh.model.geo.synchronize() 203 | 204 | # 2D pavement 205 | # gmsh.option.setNumber("Mesh.Smoothing", 3) 206 | # gmsh.option.setNumber("Mesh.Algorithm", 11) # mesh 2D 207 | # gmsh.option.setNumber("Mesh.RecombineAll", 1) 208 | # gmsh.model.mesh.generate(2) 209 | 210 | # generating a high quality fully hex mesh is a tall order: 211 | # https://gitlab.onelab.info/gmsh/gmsh/-/issues/784 212 | 213 | # gmsh.option.setNumber('Mesh.Recombine3DLevel', 0) 214 | # gmsh.option.setNumber("Mesh.NbTetrahedra", 0) 215 | # gmsh.option.setNumber("Mesh.Algorithm3D", 4) # mesh 3D 216 | 217 | gmsh.option.setNumber("Mesh.SubdivisionAlgorithm", 2) # most robust way to obtain pure hex mesh: subdivise it 218 | # gmsh.option.setNumber('Mesh.RecombinationAlgorithm', 3) # perhaps better but conflict with transfinite mesh... to dig further 219 | 220 | gmsh.model.mesh.generate(3) 221 | 222 | # gmsh.model.mesh.recombine() 223 | # gmsh.model.mesh.refine() 224 | 225 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 226 | # # Creation of the physical group # # 227 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 228 | 229 | # Create the relevant Gmsh data structures from Gmsh model. 230 | gmsh.model.geo.synchronize() 231 | [nodePerEntity, elemPerEntity] = countDOF() 232 | 233 | gmsh.model.addPhysicalGroup(pb_3Dim, [*vol_blade_1, *vol_blade_2, vol_unstructCFD], 1, "Propeller Grid") 234 | gmsh.model.addPhysicalGroup(pb_3Dim, [vol_unstructBUFF], 2, "Outer Grid") 235 | 236 | # export volume mesh only for visualisation: 237 | gmsh.write(geometry_file+"_NACA"+NACA_type+"_foil_"+str(sum(elemPerEntity))+"elems.vtk") 238 | 239 | gmsh.model.addPhysicalGroup(pb_2Dim, [*sairfoilSkin1], 3, "Blade 1 Hard Wall") 240 | gmsh.model.addPhysicalGroup(pb_2Dim, [*sairfoilSkin2], 4, "Blade 2 Hard Wall") 241 | 242 | gmsh.model.addPhysicalGroup(pb_2Dim, [cylSurf1[0], cylSurf1[1], cylSurf1[2], cylSurf1[3]], 5, "Inner Cylinder Side") 243 | gmsh.model.addPhysicalGroup(pb_2Dim, [cylSurf1[4]], 6, "Inner Cylinder Top") 244 | gmsh.model.addPhysicalGroup(pb_2Dim, [cylSurf1[5]], 7, "Inner Cylinder Bottom") 245 | 246 | gmsh.model.addPhysicalGroup(pb_2Dim, [cylSurf2[0], cylSurf2[1], cylSurf2[2], cylSurf2[3]], 8, "Outer Cylinder Side") 247 | gmsh.model.addPhysicalGroup(pb_2Dim, [cylSurf2[4]], 9, "Inner Cylinder Top") 248 | gmsh.model.addPhysicalGroup(pb_2Dim, [cylSurf2[5]], 10, "Inner Cylinder Bottom") 249 | 250 | # gmsh.model.addPhysicalGroup(pb_2Dim, [*sStructGridSkin1], 9, "Blade 1 StructBL Wrap") 251 | # gmsh.model.addPhysicalGroup(pb_2Dim, [*sStructGridSkin2], 10, "Blade 2 StructBL Wrap") 252 | 253 | 254 | # Write mesh data: 255 | gmsh.option.setNumber("Mesh.MshFileVersion", 2.2) # when ASCII format 2.2 is selected "Mesh.SaveAll=1" discards the group definitions (to be avoided!). 256 | 257 | # export mesh with all tags for computation: 258 | gmsh.write(geometry_file+"_NACA"+NACA_type+"_foil_"+str(sum(elemPerEntity))+"elems.msh") 259 | 260 | 261 | # delete the "__pycache__" folder: 262 | try: 263 | shutil.rmtree("__pycache__") 264 | except OSError as e: 265 | print("Error: %s - %s." % (e.filename, e.strerror)) 266 | 267 | # Creates graphical user interface 268 | if 'close' not in sys.argv: 269 | gmsh.fltk.run() 270 | 271 | # It finalize the Gmsh API 272 | gmsh.finalize() -------------------------------------------------------------------------------- /gmshRodAirfoil_2D.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022-2023 Étienne Spieser (Tiānài), member of AANTC (https://aantc.ust.hk/) 2 | # available under MIT licence at: https://github.com/etiennespieser 3 | # ------------------------------------------------------------------------------------ 4 | # aims at reproducing the rod-airfoil benchmark, Casalino, Jacob and Roger aiaaj03 DOI: 10.2514/2.1959 5 | 6 | import sys 7 | import gmsh 8 | from gmshToolkit import * 9 | import shutil 10 | 11 | NACA_type = '0012' 12 | CONF = 'rodAirfoil' # airfoil, rod, rodAirfoil 13 | 14 | bluntTrailingEdge = False 15 | 16 | gridPts_alongNACA = 30 # "gridPts_alongNACA" pts makes "gridPts_alongNACA-1" elements 17 | # Other parameters scale with this one. 18 | elemOrder = 2 # 8 is max order supported my navier_mfem: github.com/mfem/mfem/issues/3759, 10 is the max order supported by Gmsh 19 | highOrderBLoptim = 4 # 0: none, 20 | # 1: optimization, 21 | # 2: elastic+optimization, 22 | # 3: elastic, 23 | # 4: fast curving 24 | # by default choose 4. If for small "gridPts_alongNACA", LE curvature fails, try other values. 25 | 26 | gridPts_inBL = int(0.3*gridPts_alongNACA) # > 2 for split into fully hex mesh 27 | gridGeomProg_inBL = 1.15 28 | 29 | TEpatchGridFlaringAngle = 30 # deg 30 | gridPts_alongTEpatch = int(13*gridPts_alongNACA/75.0) # > 2 for split into fully hex mesh 31 | gridGeomProg_alongTEpatch = 1.10 32 | 33 | wakeGridFlaringAngle = 10 # deg 34 | gridPts_alongWake = int(30*gridPts_alongNACA/75.0) # > 2 for split into fully hex mesh 35 | gridGeomProg_alongWake = 1.0 36 | 37 | pitch = 12.0 # deg 38 | chord = 0.2 # m 39 | 40 | # Initialize gmsh: 41 | gmsh.initialize() 42 | 43 | pointTag = 0 44 | lineTag = 0 45 | surfaceTag = 0 46 | volumeTag = 0 47 | 48 | rotMat = rotationMatrix([0.0, 0.0, 0.0]) # angles in degree 49 | shiftVec = [0.0, 0.0, 0.0] # shift of the airfoil origin 50 | 51 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 52 | # # Creation of the airfoil mesh # # 53 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 54 | 55 | if not (CONF == 'rod'): 56 | 57 | airfoilReferenceAlongChord = 0.5*chord 58 | TEpatchLength = 0.1*chord*np.cos(pitch*np.pi/180) # length of the TEpatch in along the x-axis 59 | wakeLength = 0.5*chord*np.cos(pitch*np.pi/180) # length of the wake in along the x-axis 60 | height_LE = 0.05*chord # Structured Grid offset layer gap at the leading edge 61 | height_TE = 0.1*chord # Structured Grid offset layer gap at the trailing edge 62 | gridPts_inTE = int(gridPts_inBL/7) # if the TE is blunt, number of cells in the TE half height. NB: for the Blossom algorithm to work an even number of faces must be given. 63 | 64 | airfoilReferenceAlongChord = 0.5*chord 65 | airfoilReferenceCoordinate = [-1.5*chord, 0.0, 0.0] 66 | 67 | structTag = [pointTag, lineTag, surfaceTag] 68 | GeomSpec = [NACA_type, bluntTrailingEdge, pitch, chord, airfoilReferenceAlongChord, airfoilReferenceCoordinate, height_LE, height_TE, TEpatchLength, TEpatchGridFlaringAngle, wakeLength, wakeGridFlaringAngle] 69 | GridPtsSpec = [gridPts_alongNACA, gridPts_inBL, gridPts_inTE, gridPts_alongTEpatch, gridPts_alongWake, gridGeomProg_inBL, gridGeomProg_alongTEpatch, gridGeomProg_alongWake] 70 | # [pointTag_list, lineTag_list, surfaceTag_list, pointTag, lineTag, surfaceTag] = gmeshed_airfoil(structTag, GeomSpec, GridPtsSpec, rotMat, shiftVec) 71 | [pointTag_list, lineTag_list, surfaceTag_list, pointTag, lineTag, surfaceTag] = gmeshed_airfoil_HO(structTag, GeomSpec, GridPtsSpec, rotMat, shiftVec) 72 | 73 | airfoilLine, airfoilLineSuction, airfoilLinePressure = returnAirfoilContour(lineTag_list, bluntTrailingEdge) 74 | bladeLine = returnStructGridOuterContour(lineTag_list, bluntTrailingEdge) 75 | structGridSurf = returnStructGridSide(surfaceTag_list, bluntTrailingEdge) 76 | 77 | # $$$$$$$$$$$$$$$$$$$$$ 78 | # # Creation of rod # # 79 | # $$$$$$$$$$$$$$$$$$$$$ 80 | 81 | if not (CONF == 'airfoil'): 82 | 83 | rodPos = [0.0, 0.0, 0.0] 84 | rodR = 0.05*chord 85 | rodElemSize = 0.01*chord/(gridPts_alongNACA/75.0) 86 | rodBLwidth = 4*rodR 87 | 88 | gridPts_alongRod = int(np.pi*rodR/rodElemSize) 89 | gridPts_inRodBL = int(gridPts_alongNACA/2.0) 90 | gridGeomProg_inRodBL = 1.1 91 | 92 | structTag = [pointTag, lineTag, surfaceTag] 93 | RodGeomSpec = [rodPos, rodR, rodBLwidth] 94 | RodGridPtsSpec = [gridPts_alongRod, gridPts_inRodBL, gridGeomProg_inRodBL] 95 | [pTL_rod, lTL_rod, sTL_rod, pointTag, lineTag, surfaceTag] = gmeshed_disk(structTag, RodGeomSpec, RodGridPtsSpec, rotMat, shiftVec) # works for high-order 96 | 97 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 98 | # # Creation of the exterior region # # 99 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 100 | 101 | x_min = - 1.0*chord 102 | x_max = 5.5*chord 103 | y_min = - 1.5*chord 104 | y_max = 1.5*chord 105 | elemSize_rect = chord/20/(gridPts_alongNACA/75.0) 106 | 107 | x_minBUFF = - 1.5*chord 108 | x_maxBUFF = 9.5*chord 109 | y_minBUFF = - 2.0*chord 110 | y_maxBUFF = 2.0*chord 111 | elemSize_rectBUFF = elemSize_rect 112 | 113 | x_minINF = - 10.0*chord 114 | x_maxINF = 15.0*chord 115 | y_minINF = - 10.0*chord 116 | y_maxINF = 10.0*chord 117 | elemSize_rectINF = np.min([50*elemSize_rect, (y_maxINF-y_minINF)/gridPts_alongNACA]) 118 | 119 | 120 | [ rectLine, pointTag, lineTag] = gmeshed_rectangle_contour(x_min, x_max, y_min, y_max, elemSize_rect, pointTag, lineTag, rotMat, shiftVec) 121 | [ rectLineBUFF, pointTag, lineTag] = gmeshed_rectangle_contour(x_minBUFF, x_maxBUFF, y_minBUFF, y_maxBUFF, elemSize_rectBUFF, pointTag, lineTag, rotMat, shiftVec) 122 | [ rectLineINF, pointTag, lineTag] = gmeshed_rectangle_contour(x_minINF, x_maxINF, y_minINF, y_maxINF, elemSize_rectINF, pointTag, lineTag, rotMat, shiftVec) 123 | 124 | lRodConn = 0 125 | lRodArc = 1 126 | lRodBL = 2 127 | 128 | if CONF == 'rodAirfoil': 129 | unstructCFD_curve = [*rectLine, *bladeLine, *lTL_rod[lRodBL]] 130 | elif CONF == 'airfoil': 131 | unstructCFD_curve = [*rectLine, *bladeLine] 132 | elif CONF == 'rod': 133 | unstructCFD_curve = [*rectLine, *lTL_rod[lRodBL]] 134 | 135 | gmsh.model.geo.add_curve_loop(unstructCFD_curve, surfaceTag+1) 136 | gmsh.model.geo.addPlaneSurface([surfaceTag+1], surfaceTag+1) # mesh inside the airfoil 137 | gmsh.model.geo.mesh.setRecombine(pb_2Dim, surfaceTag+1) # To create quadrangles instead of triangles 138 | surfaceTag = surfaceTag+1 139 | surf_unstructCFD = surfaceTag 140 | 141 | gmsh.model.geo.add_curve_loop( [*rectLine, *rectLineBUFF], surfaceTag+1) 142 | gmsh.model.geo.addPlaneSurface([surfaceTag+1], surfaceTag+1) # mesh inside the airfoil 143 | gmsh.model.geo.mesh.setRecombine(pb_2Dim, surfaceTag+1) # To create quadrangles instead of triangles 144 | surfaceTag = surfaceTag+1 145 | surf_unstructBUFF = surfaceTag 146 | 147 | gmsh.model.geo.add_curve_loop( [*rectLineBUFF, *rectLineINF], surfaceTag+1) 148 | gmsh.model.geo.addPlaneSurface([surfaceTag+1], surfaceTag+1) # mesh inside the airfoil 149 | gmsh.model.geo.mesh.setRecombine(pb_2Dim, surfaceTag+1) # To create quadrangles instead of triangles 150 | surfaceTag = surfaceTag+1 151 | surf_unstructINF = surfaceTag 152 | 153 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 154 | # # Generate visualise and export the mesh # # 155 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 156 | 157 | # https://gmsh.info/doc/texinfo/gmsh.html#Mesh-options 158 | 159 | gmsh.model.geo.synchronize() 160 | 161 | # 2D pavement 162 | # gmsh.option.setNumber("Mesh.Smoothing", 3) 163 | # gmsh.option.setNumber("Mesh.Algorithm", 11) # mesh 2D 164 | gmsh.option.setNumber("Mesh.RecombineAll", 1) 165 | gmsh.option.setNumber("Mesh.ElementOrder", elemOrder) # gmsh.model.mesh.setOrder(elemOrder) 166 | gmsh.option.setNumber("Mesh.SecondOrderLinear", 0) 167 | gmsh.option.setNumber("Mesh.HighOrderOptimize", highOrderBLoptim) # NB: Where straight layers in BL are satisfactory, use addPlaneSurface() instead of addSurfaceFilling() and remove this high-order optimisation. 168 | gmsh.option.setNumber("Mesh.NumSubEdges", elemOrder) # just visualisation ?? 169 | 170 | gmsh.model.mesh.generate() 171 | 172 | # generating a high quality fully hex mesh is a tall order: 173 | # https://gitlab.onelab.info/gmsh/gmsh/-/issues/784 174 | 175 | # gmsh.option.setNumber('Mesh.Recombine3DLevel', 0) 176 | # gmsh.option.setNumber("Mesh.NbTetrahedra", 0) 177 | # gmsh.option.setNumber("Mesh.Algorithm3D", 4) # mesh 3D 178 | 179 | # gmsh.option.setNumber("Mesh.SubdivisionAlgorithm", 2) # most robust way to obtain pure hex mesh: subdivise it 180 | # # gmsh.option.setNumber('Mesh.RecombinationAlgorithm', 3) # perhaps better but conflict with transfinite mesh... to dig further 181 | 182 | # gmsh.model.mesh.generate() 183 | 184 | # gmsh.model.mesh.recombine() 185 | # gmsh.model.mesh.refine() 186 | 187 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 188 | # # Creation of the physical group # # 189 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 190 | 191 | # Create the relevant Gmsh data structures from Gmsh model. 192 | gmsh.model.geo.synchronize() 193 | [nodePerEntity, elemPerEntity] = countDOF() 194 | # gmsh.model.setColor([(2, 3)], 255, 0, 0) # Red 195 | 196 | if CONF == 'rodAirfoil': 197 | surf_unstructConf = [*structGridSurf, *sTL_rod, surf_unstructCFD] 198 | elif CONF == 'airfoil': 199 | surf_unstructConf = [*structGridSurf, surf_unstructCFD] 200 | elif CONF == 'rod': 201 | surf_unstructConf = [*sTL_rod, surf_unstructCFD] 202 | 203 | gmsh.model.addPhysicalGroup(pb_2Dim, [*surf_unstructConf], 1, "CFD") 204 | gmsh.model.addPhysicalGroup(pb_2Dim, [surf_unstructBUFF, surf_unstructINF], 2, "Buff") 205 | 206 | # export volume mesh only for visualisation: 207 | if CONF == 'rod': 208 | gmsh.write("2D_rod_"+str(sum(elemPerEntity))+"elems_chordPts"+str(gridPts_alongNACA)+"_mo"+str(elemOrder)+".vtk") 209 | else: 210 | gmsh.write("2D_"+CONF+"_NACA"+NACA_type+"_"+str(sum(elemPerEntity))+"elems_"+str(int(pitch))+"degAoA_chordPts"+str(gridPts_alongNACA)+"_mo"+str(elemOrder)+".vtk") 211 | 212 | if not (CONF == 'airfoil'): 213 | gmsh.model.addPhysicalGroup(pb_1Dim, [*lTL_rod[lRodArc]], 4, "Rod Hard Wall") 214 | if not (CONF == 'rod'): 215 | gmsh.model.addPhysicalGroup(pb_1Dim, [*airfoilLine], 5, "Airfoil Hard Wall") 216 | 217 | gmsh.model.addPhysicalGroup(pb_1Dim, [*rectLine], 6, "BUFF inner Wrap") 218 | 219 | ExtrudUnstruct_bottom = rectLineINF[0] 220 | ExtrudUnstruct_outlet = rectLineINF[1] 221 | ExtrudUnstruct_top = rectLineINF[2] 222 | ExtrudUnstruct_inlet = rectLineINF[3] 223 | 224 | gmsh.model.addPhysicalGroup(pb_1Dim, [ExtrudUnstruct_inlet], 7, "Inlet BC") 225 | gmsh.model.addPhysicalGroup(pb_1Dim, [ExtrudUnstruct_outlet], 8, "Outlet BC") 226 | 227 | gmsh.model.addPhysicalGroup(pb_1Dim, [ExtrudUnstruct_bottom], 9, "Bottom BC") 228 | gmsh.model.addPhysicalGroup(pb_1Dim, [ExtrudUnstruct_top], 10, "Top BC") 229 | 230 | # Write mesh data: 231 | gmsh.option.setNumber("Mesh.MshFileVersion", 2.2) # when ASCII format 2.2 is selected "Mesh.SaveAll=1" discards the group definitions (to be avoided!). 232 | 233 | # export mesh with all tags for computation: 234 | if CONF == 'rod': 235 | gmsh.write("2D_rod_"+str(sum(elemPerEntity))+"elems_chordPts"+str(gridPts_alongNACA)+"_mo"+str(elemOrder)+".msh") 236 | else: 237 | gmsh.write("2D_"+CONF+"_NACA"+NACA_type+"_"+str(sum(elemPerEntity))+"elems_"+str(int(pitch))+"degAoA_chordPts"+str(gridPts_alongNACA)+"_mo"+str(elemOrder)+".msh") 238 | 239 | # export surfaces where the solution will be later interpolated. 240 | gmsh.model.removePhysicalGroups() 241 | if not (CONF == 'airfoil'): 242 | gmsh.model.addPhysicalGroup(pb_1Dim, [*lTL_rod[lRodArc]], 1, "Rod Hard Wall") 243 | gmsh.write("2D_rod_"+str(sum(elemPerEntity))+"elems_chordPts"+str(gridPts_alongNACA)+"_mo"+str(elemOrder)+"_rodSurf.msh") 244 | 245 | gmsh.model.removePhysicalGroups() 246 | if not (CONF == 'rod'): 247 | gmsh.model.addPhysicalGroup(pb_1Dim, [*airfoilLine], 1, "Airfoil Hard Wall") 248 | gmsh.write("2D_"+CONF+"_NACA"+NACA_type+"_"+str(sum(elemPerEntity))+"elems_"+str(int(pitch))+"degAoA_chordPts"+str(gridPts_alongNACA)+"_mo"+str(elemOrder)+"_airfoilSurf.msh") 249 | 250 | # paraview support for High-order meshes: https://www.kitware.com/high-order-using-gmsh-reader-plugin-in-paraview/ 251 | 252 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 253 | # # Calculate the first cell size # # 254 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 255 | 256 | if not (CONF == 'rod'): 257 | ### Computation of the 1st cell height 258 | if (gridGeomProg_inBL==1): 259 | height_firstCell_LE = height_LE/gridPts_inBL 260 | height_firstCell_TE = height_TE/gridPts_inBL 261 | else: 262 | # h_{i+1} = h_i*gridGeomProg_inBL 263 | # H_tot = Sum(h_i)_{i=1..gridPts_inBL} = firstCell * (1-gridGeomProg_inBL^gridPts_inBL)/(1-gridGeomProg_inBL) 264 | height_firstCell_LE = height_LE*(1-gridGeomProg_inBL)/(1-gridGeomProg_inBL**gridPts_inBL) 265 | height_firstCell_TE = height_TE*(1-gridGeomProg_inBL)/(1-gridGeomProg_inBL**gridPts_inBL) 266 | print("Quality : 1st cell size @LE = "+ '{:.2e}'.format(height_firstCell_LE/chord)+" * chord") 267 | print("Quality : 1st cell size @TE = "+ '{:.2e}'.format(height_firstCell_TE/chord)+" * chord") 268 | 269 | ### Computation of the NACA profile length (suction side) 270 | # arc length L of a function y=f(x) for x=a..b is L = int_{x=a..b} sqrt(1+ (df(x)/dx)^2) dx 271 | # the coordinates are accessed through the api intsead: https://bthierry.pages.math.cnrs.fr/tutorial/gmsh/api/detail/ 272 | suctionLine_PG_tag = 99 273 | gmsh.model.addPhysicalGroup(pb_1Dim, [*airfoilLineSuction], suctionLine_PG_tag, "airfoil suction line") 274 | line_airfoilUp_coord = gmsh.model.mesh.getNodesForPhysicalGroup(1, suctionLine_PG_tag)[1] 275 | line_airfoilUp_coord = line_airfoilUp_coord.reshape(elemOrder*(gridPts_alongNACA-1)+1,3) 276 | line_airfoilUp_coord = line_airfoilUp_coord[line_airfoilUp_coord[:,0].argsort()] # sorting by chordwise coordinates https://stackoverflow.com/questions/2828059/sorting-arrays-in-numpy-by-column 277 | suctionSideArcLength = 0 278 | for i in range(0, np.shape(line_airfoilUp_coord)[0]-1): 279 | suctionSideArcLength = suctionSideArcLength + np.sqrt( (line_airfoilUp_coord[i+1,0]-line_airfoilUp_coord[i,0])**2 + (line_airfoilUp_coord[i+1,1]-line_airfoilUp_coord[i,1])**2) 280 | # from matplotlib import pyplot as plt 281 | # plt.plot(line_airfoilUp_coord[:,0],line_airfoilUp_coord[:,1],'-+') 282 | # plt.show() 283 | print("Quality : cell size along chord = "+ '{:.2e}'.format(suctionSideArcLength/((gridPts_alongNACA-1)*chord))+ " * chord") 284 | 285 | 286 | # delete the "__pycache__" folder: 287 | try: 288 | shutil.rmtree("__pycache__") 289 | except OSError as e: 290 | print("Error: %s - %s." % (e.filename, e.strerror)) 291 | 292 | # Creates graphical user interface 293 | if 'close' not in sys.argv: 294 | gmsh.fltk.run() 295 | 296 | # It finalize the Gmsh API 297 | gmsh.finalize() 298 | -------------------------------------------------------------------------------- /gmshRodAirfoil.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022-2023 Étienne Spieser (Tiānài), member of AANTC (https://aantc.ust.hk/) 2 | # available under MIT licence at: https://github.com/etiennespieser 3 | # ------------------------------------------------------------------------------------ 4 | # aims at reproducing the rod-airfoil benchmark, Casalino, Jacob and Roger aiaaj03 DOI: 10.2514/2.1959 5 | 6 | import sys 7 | import gmsh 8 | from gmshToolkit import * 9 | import shutil 10 | 11 | NACA_type = '0012' 12 | CONF = 'rodAirfoil' # airfoil, rod, rodAirfoil 13 | 14 | bluntTrailingEdge = False 15 | 16 | gridPts_alongNACA = 30 # "gridPts_alongNACA" pts makes "gridPts_alongNACA-1" elements 17 | # Other parameters scale with this one. 18 | elemOrder = 2 # 8 is max order supported my navier_mfem: github.com/mfem/mfem/issues/3759 19 | highOrderBLoptim = 4 # 0: none, 20 | # 1: optimization, 21 | # 2: elastic+optimization, 22 | # 3: elastic, 23 | # 4: fast curving 24 | # by default choose 4. If for small "gridPts_alongNACA", LE curvature fails, try other values. 25 | 26 | gridPts_alongSpan = int(20*gridPts_alongNACA/75.0) 27 | 28 | gridPts_inBL = int(15*gridPts_alongNACA/75.0) # > 2 for split into fully hex mesh 29 | gridGeomProg_inBL = 1.15 30 | 31 | TEpatchGridFlaringAngle = 30 # deg 32 | gridPts_alongTEpatch = int(13*gridPts_alongNACA/75.0) # > 2 for split into fully hex mesh 33 | gridGeomProg_alongTEpatch = 1.10 34 | 35 | wakeGridFlaringAngle = 10 # deg 36 | gridPts_alongWake = int(30*gridPts_alongNACA/75.0) # > 2 for split into fully hex mesh 37 | gridGeomProg_alongWake = 1.0 38 | 39 | pitch = 12.0 # deg 40 | chord = 0.2 # m 41 | span = 0.2*chord # m 42 | 43 | # Initialize gmsh: 44 | gmsh.initialize() 45 | 46 | pointTag = 0 47 | lineTag = 0 48 | surfaceTag = 0 49 | volumeTag = 0 50 | 51 | rotMat = rotationMatrix([0.0, 0.0, 0.0]) # angles in degree around [axisZ, axisY, axisX] 52 | shiftVec = np.array([0.0, 0.0, 0.0]) # shift of the origin 53 | 54 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 55 | # # Creation of the airfoil mesh # # 56 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 57 | 58 | if not (CONF == 'rod'): 59 | 60 | airfoilReferenceAlongChord = 0.5*chord 61 | TEpatchLength = 0.1*chord*np.cos(pitch*np.pi/180) # length of the TEpatch in along the x-axis 62 | wakeLength = 0.5*chord*np.cos(pitch*np.pi/180) # length of the wake in along the x-axis 63 | height_LE = 0.05*chord # Structured Grid offset layer gap at the leading edge 64 | height_TE = 0.1*chord # Structured Grid offset layer gap at the trailing edge 65 | gridPts_inTE = int(gridPts_inBL/7) # if the TE is blunt, number of cells in the TE half height. NB: for the Blossom algorithm to work an even number of faces must be given. 66 | 67 | airfoilReferenceAlongChord = 0.5*chord 68 | airfoilReferenceCoordinate = [-1.5*chord, 0.0, 0.0] 69 | 70 | structTag = [pointTag, lineTag, surfaceTag] 71 | GeomSpec = [NACA_type, bluntTrailingEdge, pitch, chord, airfoilReferenceAlongChord, airfoilReferenceCoordinate, height_LE, height_TE, TEpatchLength, TEpatchGridFlaringAngle, wakeLength, wakeGridFlaringAngle] 72 | GridPtsSpec = [gridPts_alongNACA, gridPts_inBL, gridPts_inTE, gridPts_alongTEpatch, gridPts_alongWake, gridGeomProg_inBL, gridGeomProg_alongTEpatch, gridGeomProg_alongWake] 73 | # [pTL_airfoil, lTL_airfoil, sTL_airfoil, pointTag, lineTag, surfaceTag] = gmeshed_airfoil(structTag, GeomSpec, GridPtsSpec, rotMat, shiftVec) 74 | [pTL_airfoil, lTL_airfoil, sTL_airfoil, pointTag, lineTag, surfaceTag] = gmeshed_airfoil_HO(structTag, GeomSpec, GridPtsSpec, rotMat, shiftVec) 75 | 76 | bladeLine = returnStructGridOuterContour(lTL_airfoil, bluntTrailingEdge) 77 | structGridSurf = returnStructGridSide(sTL_airfoil, bluntTrailingEdge) 78 | 79 | # $$$$$$$$$$$$$$$$$$$$$ 80 | # # Creation of rod # # 81 | # $$$$$$$$$$$$$$$$$$$$$ 82 | 83 | if not (CONF == 'airfoil'): 84 | 85 | rodPos = [0.0, 0.0, 0.0] 86 | rodR = 0.05*chord 87 | rodElemSize = 0.01*chord/(gridPts_alongNACA/75.0) 88 | rodBLwidth = 4*rodR 89 | 90 | gridPts_alongRod = int(np.pi*rodR/rodElemSize) 91 | gridPts_inRodBL = int(gridPts_alongNACA/2.0) 92 | gridGeomProg_inRodBL = 1.1 93 | 94 | structTag = [pointTag, lineTag, surfaceTag] 95 | RodGeomSpec = [rodPos, rodR, rodBLwidth] 96 | RodGridPtsSpec = [gridPts_alongRod, gridPts_inRodBL, gridGeomProg_inRodBL] 97 | [pTL_rod, lTL_rod, sTL_rod, pointTag, lineTag, surfaceTag] = gmeshed_disk(structTag, RodGeomSpec, RodGridPtsSpec, rotMat, shiftVec) # works for high-order 98 | 99 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 100 | # # Creation of the exterior region # # 101 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 102 | 103 | x_min = - 1.0*chord 104 | x_max = 5.5*chord 105 | y_min = - 1.5*chord 106 | y_max = 1.5*chord 107 | elemSize_rect = chord/20/(gridPts_alongNACA/75.0) 108 | 109 | x_minBUFF = - 1.5*chord 110 | x_maxBUFF = 9.5*chord 111 | y_minBUFF = - 2.0*chord 112 | y_maxBUFF = 2.0*chord 113 | elemSize_rectBUFF = elemSize_rect 114 | 115 | x_minINF = - 10.0*chord 116 | x_maxINF = 15.0*chord 117 | y_minINF = - 10.0*chord 118 | y_maxINF = 10.0*chord 119 | elemSize_rectINF = np.min([50*elemSize_rect, (y_maxINF-y_minINF)/gridPts_alongNACA]) 120 | 121 | [rectLine, pointTag, lineTag] = gmeshed_rectangle_contour(x_min, x_max, y_min, y_max, elemSize_rect, pointTag, lineTag, rotMat, shiftVec) 122 | [rectLineBUFF, pointTag, lineTag] = gmeshed_rectangle_contour(x_minBUFF, x_maxBUFF, y_minBUFF, y_maxBUFF, elemSize_rectBUFF, pointTag, lineTag, rotMat, shiftVec) 123 | [ rectLineINF, pointTag, lineTag] = gmeshed_rectangle_contour(x_minINF, x_maxINF, y_minINF, y_maxINF, elemSize_rectINF, pointTag, lineTag, rotMat, shiftVec) 124 | 125 | lRodConn = 0 126 | lRodArc = 1 127 | lRodBL = 2 128 | 129 | if CONF == 'rodAirfoil': 130 | unstructCFD_curve = [*rectLine, *bladeLine, *lTL_rod[lRodBL]] 131 | elif CONF == 'airfoil': 132 | unstructCFD_curve = [*rectLine, *bladeLine] 133 | elif CONF == 'rod': 134 | unstructCFD_curve = [*rectLine, *lTL_rod[lRodBL]] 135 | 136 | gmsh.model.geo.add_curve_loop(unstructCFD_curve, surfaceTag+1) 137 | gmsh.model.geo.addPlaneSurface([surfaceTag+1], surfaceTag+1) # mesh inside the airfoil 138 | gmsh.model.geo.mesh.setRecombine(pb_2Dim, surfaceTag+1) # To create quadrangles instead of triangles 139 | surfaceTag = surfaceTag+1 140 | surf_unstructCFD = surfaceTag 141 | 142 | gmsh.model.geo.add_curve_loop( [*rectLine, *rectLineBUFF], surfaceTag+1) 143 | gmsh.model.geo.addPlaneSurface([surfaceTag+1], surfaceTag+1) # mesh inside the airfoil 144 | gmsh.model.geo.mesh.setRecombine(pb_2Dim, surfaceTag+1) # To create quadrangles instead of triangles 145 | surfaceTag = surfaceTag+1 146 | surf_unstructBUFF = surfaceTag 147 | 148 | gmsh.model.geo.add_curve_loop( [*rectLineBUFF, *rectLineINF], surfaceTag+1) 149 | gmsh.model.geo.addPlaneSurface([surfaceTag+1], surfaceTag+1) # mesh inside the airfoil 150 | gmsh.model.geo.mesh.setRecombine(pb_2Dim, surfaceTag+1) # To create quadrangles instead of triangles 151 | surfaceTag = surfaceTag+1 152 | surf_unstructINF = surfaceTag 153 | 154 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$ 155 | # # Extrusion of the mesh # # 156 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$ 157 | 158 | if not (CONF == 'airfoil'): 159 | [ExtrudRodBL_vol, ExtrudRodBL_symFace, ExtrudRodBL_skin] = extrude_rodBL(sTL_rod, span, gridPts_alongSpan-1) 160 | surfMesh_rodHardWall = [*ExtrudRodBL_skin] 161 | 162 | if not (CONF == 'rod'): 163 | # [ExtrudAirfoildStruct_vol, ExtrudStructAirfoil_symFace, ExtrudStructAirfoil_skin] = extrude_airfoilStruct(sTL_airfoil, bluntTrailingEdge, gridPts_alongNACA, span, gridPts_alongSpan) 164 | [ExtrudAirfoildStruct_vol, ExtrudStructAirfoil_symFace, ExtrudStructAirfoil_skin] = extrude_airfoilStruct_HO(sTL_airfoil, bluntTrailingEdge, gridPts_alongNACA, span, gridPts_alongSpan-1) 165 | surfMesh_airfoilHardWall = [*ExtrudStructAirfoil_skin] 166 | 167 | [ExtrudUnstructCFD_vol, ExtrudUnstructCFD_symFace] = extrude_unstructCFD(surf_unstructCFD, span, gridPts_alongSpan-1) 168 | [ExtrudUnstructBUFF_vol, ExtrudUnstructBUFF_symFace, ExtrudUnstructBUFF_innerSkin, ExtrudUnstructBUFF_outerSkin] = extrude_unstructBUFF(surf_unstructBUFF, span, gridPts_alongSpan-1) 169 | [ExtrudUnstructINF_vol, ExtrudUnstructINF_symFace, ExtrudUnstructINF_innerSkin, ExtrudUnstructINF_outerSkin] = extrude_unstructBUFF(surf_unstructINF, span, gridPts_alongSpan-1) 170 | 171 | if CONF == 'rodAirfoil': 172 | volMesh = [*ExtrudRodBL_vol, *ExtrudAirfoildStruct_vol, *ExtrudUnstructCFD_vol] 173 | surfMesh_original = [*sTL_rod, *structGridSurf, surf_unstructCFD, surf_unstructBUFF, surf_unstructINF ] 174 | surfMesh_symFace = [*ExtrudRodBL_symFace, *ExtrudStructAirfoil_symFace, *ExtrudUnstructCFD_symFace, *ExtrudUnstructBUFF_symFace, *ExtrudUnstructINF_symFace] 175 | elif CONF == 'airfoil': 176 | volMesh = [*ExtrudAirfoildStruct_vol, *ExtrudUnstructCFD_vol] 177 | surfMesh_original = [*structGridSurf, surf_unstructCFD, surf_unstructBUFF, surf_unstructINF ] 178 | surfMesh_symFace = [*ExtrudStructAirfoil_symFace, *ExtrudUnstructCFD_symFace, *ExtrudUnstructBUFF_symFace, *ExtrudUnstructINF_symFace] 179 | elif CONF == 'rod': 180 | volMesh = [*ExtrudRodBL_vol, *ExtrudUnstructCFD_vol] 181 | surfMesh_original = [*sTL_rod, surf_unstructCFD, surf_unstructBUFF, surf_unstructINF ] 182 | surfMesh_symFace = [*ExtrudRodBL_symFace, *ExtrudUnstructCFD_symFace, *ExtrudUnstructBUFF_symFace, *ExtrudUnstructINF_symFace] 183 | 184 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 185 | # # Set periodic bounday condition # # 186 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 187 | 188 | ### 189 | # Instead of enforcing the symmetry BC in Gmsh (periodic hex mesh not supported in mfem-4.5), 190 | ### # periodicity along z axis at separation of span 191 | ### gmsh.model.geo.synchronize() 192 | ### gmsh.model.mesh.setPeriodic(pb_2Dim, [*surfMesh_symFace], [*surfMesh_original], [1,0,0,0, 0,1,0,0, 0,0,1,span, 0,0,0,1]) 193 | ### # from here on, "surfMesh_symFace" and "surfMesh_original" refer to the same elements. 194 | # periodise the mesh in MFEM following https://mfem.org/howto/periodic-boundaries/ 195 | 196 | 197 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 198 | # # Generate visualise and export the mesh # # 199 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 200 | 201 | # https://gmsh.info/doc/texinfo/gmsh.html#Mesh-options 202 | 203 | gmsh.model.geo.synchronize() 204 | 205 | # 2D pavement 206 | # gmsh.option.setNumber("Mesh.Smoothing", 3) 207 | # gmsh.option.setNumber("Mesh.Algorithm", 11) # mesh 2D 208 | gmsh.option.setNumber("Mesh.RecombineAll", 1) 209 | gmsh.option.setNumber("Mesh.ElementOrder", elemOrder) # gmsh.model.mesh.setOrder(elemOrder) 210 | gmsh.option.setNumber("Mesh.SecondOrderLinear", 0) 211 | gmsh.option.setNumber("Mesh.HighOrderOptimize", highOrderBLoptim) # NB: Where straight layers in BL are satisfactory, use addPlaneSurface() instead of addSurfaceFilling() and remove this high-order optimisation. 212 | gmsh.option.setNumber("Mesh.NumSubEdges", elemOrder) # just visualisation ?? 213 | 214 | 215 | gmsh.model.mesh.generate() 216 | 217 | # generating a high quality fully hex mesh is a tall order: 218 | # https://gitlab.onelab.info/gmsh/gmsh/-/issues/784 219 | 220 | # gmsh.option.setNumber('Mesh.Recombine3DAll', 0) 221 | # gmsh.option.setNumber('Mesh.Recombine3DLevel', 0) 222 | # gmsh.option.setNumber("Mesh.NbTetrahedra", 0) 223 | # gmsh.option.setNumber("Mesh.Algorithm3D", 4) # mesh 3D 224 | 225 | # gmsh.option.setNumber("Mesh.SubdivisionAlgorithm", 2) # most robust way to obtain pure hex mesh: subdivise it 226 | # # gmsh.option.setNumber('Mesh.RecombinationAlgorithm', 3) # perhaps better but conflict with transfinite mesh... to dig further 227 | 228 | # gmsh.model.mesh.generate() 229 | 230 | # gmsh.model.mesh.refine() 231 | # gmsh.model.mesh.recombine() 232 | 233 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 234 | # # Creation of the physical group # # 235 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 236 | 237 | # Create the relevant Gmsh data structures from Gmsh model. 238 | gmsh.model.geo.synchronize() 239 | [nodePerEntity, elemPerEntity] = countDOF() 240 | # gmsh.model.setColor([(2, 3)], 255, 0, 0) # Red 241 | 242 | gmsh.model.addPhysicalGroup(pb_3Dim, [*volMesh], 1, "CFD Grid") 243 | gmsh.model.addPhysicalGroup(pb_3Dim, [*ExtrudUnstructBUFF_vol, *ExtrudUnstructINF_vol], 2, "BUFF Grid") 244 | 245 | # export volume mesh only for visualisation: 246 | if CONF == 'rod': 247 | gmsh.write("rod_"+str(sum(elemPerEntity))+"elems_"+str(int(pitch))+"degAoA_chordPts"+str(gridPts_alongNACA)+"_mo"+str(elemOrder)+".vtk") 248 | else: 249 | gmsh.write(CONF+"_NACA"+NACA_type+"_"+str(sum(elemPerEntity))+"elems_"+str(int(pitch))+"degAoA_chordPts"+str(gridPts_alongNACA)+"_mo"+str(elemOrder)+".vtk") 250 | 251 | if not (CONF == 'airfoil'): 252 | gmsh.model.addPhysicalGroup(pb_2Dim, [*surfMesh_rodHardWall], 4, "Rod Hard Wall") 253 | if not (CONF == 'rod'): 254 | gmsh.model.addPhysicalGroup(pb_2Dim, [*surfMesh_airfoilHardWall], 5, "Airfoil Hard Wall") 255 | 256 | gmsh.model.addPhysicalGroup(pb_2Dim, [*ExtrudUnstructBUFF_innerSkin], 6, "BUFF inner Wrap") 257 | 258 | ExtrudUnstructINF_inlet = ExtrudUnstructINF_outerSkin[0] 259 | ExtrudUnstructINF_bottom = ExtrudUnstructINF_outerSkin[1] 260 | ExtrudUnstructINF_outlet = ExtrudUnstructINF_outerSkin[2] 261 | ExtrudUnstructINF_top = ExtrudUnstructINF_outerSkin[3] 262 | 263 | gmsh.model.addPhysicalGroup(pb_2Dim, [*ExtrudUnstructINF_inlet], 7, "Inlet BC") 264 | gmsh.model.addPhysicalGroup(pb_2Dim, [*ExtrudUnstructINF_outlet], 8, "Outlet BC") 265 | 266 | gmsh.model.addPhysicalGroup(pb_2Dim, [*ExtrudUnstructINF_bottom], 9, "Bottom BC") 267 | gmsh.model.addPhysicalGroup(pb_2Dim, [*ExtrudUnstructINF_top], 10, "Top BC") 268 | 269 | gmsh.model.addPhysicalGroup(pb_2Dim, [*surfMesh_original], 11, "Periodic BC 1") 270 | gmsh.model.addPhysicalGroup(pb_2Dim, [*surfMesh_symFace], 12, "Periodic BC 2") 271 | 272 | # Write mesh data: 273 | gmsh.option.setNumber("Mesh.MshFileVersion", 2.2) # when ASCII format 2.2 is selected "Mesh.SaveAll=1" discards the group definitions (to be avoided!). 274 | 275 | # export mesh with all tags for computation: 276 | if CONF == 'rod': 277 | gmsh.write("rod_"+str(sum(elemPerEntity))+"elems_"+str(int(pitch))+"degAoA_chordPts"+str(gridPts_alongNACA)+"_mo"+str(elemOrder)+".msh") 278 | else: 279 | gmsh.write(CONF+"_NACA"+NACA_type+"_"+str(sum(elemPerEntity))+"elems_"+str(int(pitch))+"degAoA_chordPts"+str(gridPts_alongNACA)+"_mo"+str(elemOrder)+".msh") 280 | 281 | # export surfaces where the solution will be later interpolated. 282 | gmsh.model.removePhysicalGroups() 283 | if not (CONF == 'airfoil'): 284 | gmsh.model.addPhysicalGroup(pb_2Dim, [*surfMesh_rodHardWall], 1, "Rod Hard Wall") 285 | gmsh.write(CONF+"_NACA"+NACA_type+"_"+str(sum(elemPerEntity))+"elems_"+str(int(pitch))+"degAoA_chordPts"+str(gridPts_alongNACA)+"_mo"+str(elemOrder)+"_rodSurf.msh") 286 | 287 | gmsh.model.removePhysicalGroups() 288 | if not (CONF == 'rod'): 289 | gmsh.model.addPhysicalGroup(pb_2Dim, [*surfMesh_airfoilHardWall], 1, "Airfoil Hard Wall") 290 | gmsh.write(CONF+"_NACA"+NACA_type+"_"+str(sum(elemPerEntity))+"elems_"+str(int(pitch))+"degAoA_chordPts"+str(gridPts_alongNACA)+"_mo"+str(elemOrder)+"_airfoilSurf.msh") 291 | 292 | gmsh.model.removePhysicalGroups() 293 | gmsh.model.addPhysicalGroup(pb_2Dim, [*surfMesh_original], 1, "Periodic plan") 294 | gmsh.write(CONF+"_NACA"+NACA_type+"_"+str(sum(elemPerEntity))+"elems_"+str(int(pitch))+"degAoA_chordPts"+str(gridPts_alongNACA)+"_mo"+str(elemOrder)+"_sideSurf.msh") 295 | 296 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 297 | # # Calculate the first cell size # # 298 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 299 | 300 | if not (CONF == 'rod'): 301 | ### Computation of the 1st cell height 302 | if (gridGeomProg_inBL==1): 303 | height_firstCell_LE = height_LE/gridPts_inBL 304 | height_firstCell_TE = height_TE/gridPts_inBL 305 | else: 306 | # h_{i+1} = h_i*gridGeomProg_inBL 307 | # H_tot = Sum(h_i)_{i=1..gridPts_inBL} = firstCell * (1-gridGeomProg_inBL^gridPts_inBL)/(1-gridGeomProg_inBL) 308 | height_firstCell_LE = height_LE*(1-gridGeomProg_inBL)/(1-gridGeomProg_inBL**gridPts_inBL) 309 | height_firstCell_TE = height_TE*(1-gridGeomProg_inBL)/(1-gridGeomProg_inBL**gridPts_inBL) 310 | print("Quality : 1st cell size @LE = "+ '{:.2e}'.format(height_firstCell_LE/chord)+" * chord") 311 | print("Quality : 1st cell size @TE = "+ '{:.2e}'.format(height_firstCell_TE/chord)+" * chord") 312 | 313 | ### Computation of the cell size on the skin of the NACA profile (suction side) 314 | # arc length L of a function y=f(x) for x=a..b is L = int_{x=a..b} sqrt(1+ (df(x)/dx)^2) dx 315 | # the coordinates are accessed through the api intsead: https://bthierry.pages.math.cnrs.fr/tutorial/gmsh/api/detail/ 316 | airfoilLine, airfoilLineSuction, airfoilLinePressure = returnAirfoilContour(lTL_airfoil, bluntTrailingEdge) 317 | suctionLine_PG_tag = 99 318 | gmsh.model.addPhysicalGroup(pb_1Dim, [*airfoilLineSuction], suctionLine_PG_tag, "airfoil suction line") 319 | line_airfoilUp_coord = gmsh.model.mesh.getNodesForPhysicalGroup(1, suctionLine_PG_tag)[1] 320 | line_airfoilUp_coord = line_airfoilUp_coord.reshape(elemOrder*(gridPts_alongNACA-1)+1,3) 321 | line_airfoilUp_coord = line_airfoilUp_coord[line_airfoilUp_coord[:,0].argsort()] # sorting by chordwise coordinates https://stackoverflow.com/questions/2828059/sorting-arrays-in-numpy-by-column 322 | suctionSideArcLength = 0 323 | for i in range(0, np.shape(line_airfoilUp_coord)[0]-1): 324 | suctionSideArcLength = suctionSideArcLength + np.sqrt( (line_airfoilUp_coord[i+1,0]-line_airfoilUp_coord[i,0])**2 + (line_airfoilUp_coord[i+1,1]-line_airfoilUp_coord[i,1])**2) 325 | # from matplotlib import pyplot as plt 326 | # plt.plot(line_airfoilUp_coord[:,0],line_airfoilUp_coord[:,1],'-+') 327 | # plt.show() 328 | print("Quality : cell size along chord = "+ '{:.2e}'.format(suctionSideArcLength/((gridPts_alongNACA-1)*chord))+ " * chord") 329 | 330 | ### Computation of the cell size spanwise 331 | print("Quality : cell size along span = "+ '{:.2e}'.format(span/((gridPts_alongSpan-1)*chord))+ " * chord") 332 | 333 | 334 | # delete the "__pycache__" folder: 335 | try: 336 | shutil.rmtree("__pycache__") 337 | except OSError as e: 338 | print("Error: %s - %s." % (e.filename, e.strerror)) 339 | 340 | # Creates graphical user interface 341 | if 'close' not in sys.argv: 342 | gmsh.fltk.run() 343 | 344 | # It finalize the Gmsh API 345 | gmsh.finalize() 346 | --------------------------------------------------------------------------------