gmshairfoil2d 0.2.1__py3-none-any.whl → 0.2.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gmshairfoil2d/__init__.py +3 -0
- gmshairfoil2d/__main__.py +6 -0
- gmshairfoil2d/airfoil_func.py +126 -71
- gmshairfoil2d/config_handler.py +198 -0
- gmshairfoil2d/geometry_def.py +331 -277
- gmshairfoil2d/gmshairfoil2d.py +265 -29
- {gmshairfoil2d-0.2.1.dist-info → gmshairfoil2d-0.2.3.dist-info}/METADATA +104 -16
- gmshairfoil2d-0.2.3.dist-info/RECORD +16 -0
- tests/test_airfoil_func.py +52 -1
- tests/test_config_handler.py +260 -0
- tests/test_geometry_def.py +11 -7
- gmshairfoil2d-0.2.1.dist-info/RECORD +0 -13
- {gmshairfoil2d-0.2.1.dist-info → gmshairfoil2d-0.2.3.dist-info}/WHEEL +0 -0
- {gmshairfoil2d-0.2.1.dist-info → gmshairfoil2d-0.2.3.dist-info}/entry_points.txt +0 -0
- {gmshairfoil2d-0.2.1.dist-info → gmshairfoil2d-0.2.3.dist-info}/licenses/LICENSE +0 -0
- {gmshairfoil2d-0.2.1.dist-info → gmshairfoil2d-0.2.3.dist-info}/top_level.txt +0 -0
gmshairfoil2d/gmshairfoil2d.py
CHANGED
|
@@ -1,17 +1,109 @@
|
|
|
1
|
-
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
1
|
+
"""Main module for GMSH airfoil 2D mesh generation."""
|
|
3
2
|
|
|
4
3
|
import argparse
|
|
5
4
|
import math
|
|
6
5
|
import sys
|
|
7
6
|
from pathlib import Path
|
|
8
|
-
import numpy as np
|
|
9
7
|
|
|
10
8
|
import gmsh
|
|
11
9
|
from gmshairfoil2d.airfoil_func import (NACA_4_digit_geom, get_airfoil_points,
|
|
12
|
-
get_all_available_airfoil_names)
|
|
10
|
+
get_all_available_airfoil_names, read_airfoil_from_file)
|
|
13
11
|
from gmshairfoil2d.geometry_def import (AirfoilSpline, Circle, PlaneSurface,
|
|
14
12
|
Rectangle, outofbounds, CType)
|
|
13
|
+
from gmshairfoil2d.config_handler import read_config, merge_config_with_args, create_example_config
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _calculate_spline_length(spline):
|
|
17
|
+
"""Calculate the length of a spline based on its points.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
spline : object
|
|
22
|
+
Spline object with point_list attribute
|
|
23
|
+
|
|
24
|
+
Returns
|
|
25
|
+
-------
|
|
26
|
+
float
|
|
27
|
+
Total length of the spline
|
|
28
|
+
"""
|
|
29
|
+
if not hasattr(spline, 'point_list') or not spline or not spline.point_list:
|
|
30
|
+
return 0
|
|
31
|
+
|
|
32
|
+
points = spline.point_list
|
|
33
|
+
if len(points) < 2:
|
|
34
|
+
return 0
|
|
35
|
+
|
|
36
|
+
return sum(
|
|
37
|
+
math.sqrt((points[i].x - points[i+1].x)**2 +
|
|
38
|
+
(points[i].y - points[i+1].y)**2)
|
|
39
|
+
for i in range(len(points) - 1)
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def apply_transfinite_to_surfaces(airfoil_obj, airfoil_mesh_size, name=""):
|
|
44
|
+
"""
|
|
45
|
+
Apply transfinite meshing to all three splines (upper, lower, and front) of an
|
|
46
|
+
airfoil or flap object for smooth cell transitions based on edge lengths.
|
|
47
|
+
|
|
48
|
+
The key is to distribute nodes proportionally to each edge's length:
|
|
49
|
+
- longer edges get more points
|
|
50
|
+
- all edges get consistent cell sizing at junctions
|
|
51
|
+
|
|
52
|
+
Parameters
|
|
53
|
+
----------
|
|
54
|
+
airfoil_obj : AirfoilSpline
|
|
55
|
+
The airfoil or flap object containing front_spline, upper_spline, lower_spline
|
|
56
|
+
airfoil_mesh_size : float
|
|
57
|
+
The target mesh size to maintain consistent cell dimensions
|
|
58
|
+
name : str, optional
|
|
59
|
+
Name of the object (for logging purposes)
|
|
60
|
+
"""
|
|
61
|
+
# Calculate the length of each spline
|
|
62
|
+
l_front = _calculate_spline_length(airfoil_obj.front_spline) if hasattr(airfoil_obj, 'front_spline') else 0
|
|
63
|
+
l_upper = _calculate_spline_length(airfoil_obj.upper_spline) if hasattr(airfoil_obj, 'upper_spline') else 0
|
|
64
|
+
l_lower = _calculate_spline_length(airfoil_obj.lower_spline) if hasattr(airfoil_obj, 'lower_spline') else 0
|
|
65
|
+
|
|
66
|
+
# Calculate total perimeter
|
|
67
|
+
total_length = l_front + l_upper + l_lower
|
|
68
|
+
|
|
69
|
+
if total_length == 0:
|
|
70
|
+
print(f"Warning: {name} has zero total length, skipping transfinite meshing")
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
# Distribute points proportionally to edge lengths
|
|
74
|
+
# Target cell size should be approximately airfoil_mesh_size on all edges
|
|
75
|
+
total_points = max(20, int(total_length / airfoil_mesh_size))
|
|
76
|
+
|
|
77
|
+
# Distribute points based on proportion of each edge length
|
|
78
|
+
# Front gets a multiplier for higher density at leading edge (Bump effect)
|
|
79
|
+
front_multiplier = 2 # 100% extra density for front region
|
|
80
|
+
weighted_length = l_front * front_multiplier + l_upper + l_lower
|
|
81
|
+
|
|
82
|
+
nb_points_front = max(15, int((l_front * front_multiplier / weighted_length) * total_points))
|
|
83
|
+
nb_points_upper = max(15, int((l_upper / weighted_length) * total_points))
|
|
84
|
+
nb_points_lower = max(15, int((l_lower / weighted_length) * total_points))
|
|
85
|
+
|
|
86
|
+
# Apply transfinite curves
|
|
87
|
+
if hasattr(airfoil_obj, 'front_spline') and airfoil_obj.front_spline:
|
|
88
|
+
gmsh.model.mesh.setTransfiniteCurve(
|
|
89
|
+
airfoil_obj.front_spline.tag, nb_points_front, "Bump", 10)
|
|
90
|
+
|
|
91
|
+
if hasattr(airfoil_obj, 'upper_spline') and airfoil_obj.upper_spline:
|
|
92
|
+
gmsh.model.mesh.setTransfiniteCurve(airfoil_obj.upper_spline.tag, nb_points_upper)
|
|
93
|
+
|
|
94
|
+
if hasattr(airfoil_obj, 'lower_spline') and airfoil_obj.lower_spline:
|
|
95
|
+
gmsh.model.mesh.setTransfiniteCurve(airfoil_obj.lower_spline.tag, nb_points_lower)
|
|
96
|
+
|
|
97
|
+
if name:
|
|
98
|
+
# Calculate actual cell sizes for info
|
|
99
|
+
front_cell_size = l_front / (nb_points_front - 1) if nb_points_front > 1 else 0
|
|
100
|
+
upper_cell_size = l_upper / (nb_points_upper - 1) if nb_points_upper > 1 else 0
|
|
101
|
+
lower_cell_size = l_lower / (nb_points_lower - 1) if nb_points_lower > 1 else 0
|
|
102
|
+
|
|
103
|
+
print(f"Applied transfinite meshing to {name}:")
|
|
104
|
+
print(f" - Front spline: {nb_points_front:3d} points, length={l_front:.4f}, cell size ~ {front_cell_size:.6f}")
|
|
105
|
+
print(f" - Upper spline: {nb_points_upper:3d} points, length={l_upper:.4f}, cell size ~ {upper_cell_size:.6f}")
|
|
106
|
+
print(f" - Lower spline: {nb_points_lower:3d} points, length={l_lower:.4f}, cell size ~ {lower_cell_size:.6f}")
|
|
15
107
|
|
|
16
108
|
|
|
17
109
|
def main():
|
|
@@ -24,6 +116,26 @@ def main():
|
|
|
24
116
|
),
|
|
25
117
|
)
|
|
26
118
|
|
|
119
|
+
parser.add_argument(
|
|
120
|
+
"--config",
|
|
121
|
+
type=str,
|
|
122
|
+
metavar="PATH",
|
|
123
|
+
help="Path to YAML configuration file",
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
parser.add_argument(
|
|
127
|
+
"--save_config",
|
|
128
|
+
type=str,
|
|
129
|
+
metavar="PATH",
|
|
130
|
+
help="Save configuration to a YAML file",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
parser.add_argument(
|
|
134
|
+
"--example_config",
|
|
135
|
+
action="store_true",
|
|
136
|
+
help="Create an example configuration file (config_example.yaml)",
|
|
137
|
+
)
|
|
138
|
+
|
|
27
139
|
parser.add_argument(
|
|
28
140
|
"--list",
|
|
29
141
|
action="store_true",
|
|
@@ -46,6 +158,20 @@ def main():
|
|
|
46
158
|
help="Name of an airfoil profile in the database (database available with the --list argument)",
|
|
47
159
|
)
|
|
48
160
|
|
|
161
|
+
parser.add_argument(
|
|
162
|
+
"--airfoil_path",
|
|
163
|
+
type=str,
|
|
164
|
+
metavar="PATH",
|
|
165
|
+
help="Path to a custom .dat file with airfoil coordinates",
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
parser.add_argument(
|
|
169
|
+
"--flap_path",
|
|
170
|
+
type=str,
|
|
171
|
+
metavar="PATH",
|
|
172
|
+
help="Path to a custom .dat file with flap coordinates",
|
|
173
|
+
)
|
|
174
|
+
|
|
49
175
|
parser.add_argument(
|
|
50
176
|
"--aoa",
|
|
51
177
|
type=float,
|
|
@@ -54,6 +180,14 @@ def main():
|
|
|
54
180
|
default=0.0,
|
|
55
181
|
)
|
|
56
182
|
|
|
183
|
+
parser.add_argument(
|
|
184
|
+
"--deflection",
|
|
185
|
+
type=float,
|
|
186
|
+
nargs="?",
|
|
187
|
+
help="Angle of flap deflection [deg] (default: 0 [deg])",
|
|
188
|
+
default=0.0,
|
|
189
|
+
)
|
|
190
|
+
|
|
57
191
|
parser.add_argument(
|
|
58
192
|
"--farfield",
|
|
59
193
|
type=float,
|
|
@@ -62,6 +196,13 @@ def main():
|
|
|
62
196
|
default=10,
|
|
63
197
|
help="Create a circular farfield mesh of given radius [m] (default 10m)",
|
|
64
198
|
)
|
|
199
|
+
|
|
200
|
+
parser.add_argument(
|
|
201
|
+
"--farfield_ctype",
|
|
202
|
+
action="store_true",
|
|
203
|
+
help="Generate a structured circular farfield (CType) for hybrid meshes",
|
|
204
|
+
)
|
|
205
|
+
|
|
65
206
|
parser.add_argument(
|
|
66
207
|
"--box",
|
|
67
208
|
type=str,
|
|
@@ -69,13 +210,23 @@ def main():
|
|
|
69
210
|
nargs="?",
|
|
70
211
|
help="Create a box mesh of dimensions [length]x[height] [m]",
|
|
71
212
|
)
|
|
213
|
+
|
|
72
214
|
parser.add_argument(
|
|
73
215
|
"--airfoil_mesh_size",
|
|
74
216
|
type=float,
|
|
75
217
|
metavar="SIZE",
|
|
76
218
|
nargs="?",
|
|
77
219
|
default=0.01,
|
|
78
|
-
help="Mesh size of the airfoil contour [m] (default 0.01m) (for normal, bl and
|
|
220
|
+
help="Mesh size of the airfoil contour [m] (default 0.01m) (for normal, bl and structured)",
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
parser.add_argument(
|
|
224
|
+
"--flap_mesh_size",
|
|
225
|
+
type=float,
|
|
226
|
+
metavar="SIZE",
|
|
227
|
+
nargs="?",
|
|
228
|
+
default=None,
|
|
229
|
+
help="Mesh size of the flap contour [m] (if not provided, defaults to 85%% of airfoil_mesh_size)",
|
|
79
230
|
)
|
|
80
231
|
|
|
81
232
|
parser.add_argument(
|
|
@@ -99,7 +250,7 @@ def main():
|
|
|
99
250
|
metavar="HEIGHT",
|
|
100
251
|
nargs="?",
|
|
101
252
|
default=3e-5,
|
|
102
|
-
help="Height of the first layer [m] (default 3e-5m) (for bl and
|
|
253
|
+
help="Height of the first layer [m] (default 3e-5m) (for bl and structured)",
|
|
103
254
|
)
|
|
104
255
|
|
|
105
256
|
parser.add_argument(
|
|
@@ -108,7 +259,7 @@ def main():
|
|
|
108
259
|
metavar="RATIO",
|
|
109
260
|
nargs="?",
|
|
110
261
|
default=1.2,
|
|
111
|
-
help="Growth ratio of layers (default 1.2) (for bl and
|
|
262
|
+
help="Growth ratio of layers (default 1.2) (for bl and structured)",
|
|
112
263
|
)
|
|
113
264
|
|
|
114
265
|
parser.add_argument(
|
|
@@ -129,16 +280,16 @@ def main():
|
|
|
129
280
|
)
|
|
130
281
|
|
|
131
282
|
parser.add_argument(
|
|
132
|
-
"--
|
|
283
|
+
"--structured",
|
|
133
284
|
action="store_true",
|
|
134
|
-
help="Generate a
|
|
285
|
+
help="Generate a structured mesh",
|
|
135
286
|
)
|
|
136
287
|
parser.add_argument(
|
|
137
288
|
"--arg_struc",
|
|
138
289
|
type=str,
|
|
139
290
|
metavar="[LxL]",
|
|
140
291
|
default="10x10",
|
|
141
|
-
help="Parameters for the
|
|
292
|
+
help="Parameters for the structured mesh [wake length (axis x)]x[total height (axis y)] [m] (default 10x10)",
|
|
142
293
|
)
|
|
143
294
|
|
|
144
295
|
parser.add_argument(
|
|
@@ -157,6 +308,24 @@ def main():
|
|
|
157
308
|
)
|
|
158
309
|
args = parser.parse_args()
|
|
159
310
|
|
|
311
|
+
# Handle configuration file operations
|
|
312
|
+
if args.example_config:
|
|
313
|
+
create_example_config()
|
|
314
|
+
sys.exit()
|
|
315
|
+
|
|
316
|
+
# Load configuration from file if provided
|
|
317
|
+
if args.config:
|
|
318
|
+
try:
|
|
319
|
+
config_dict = read_config(args.config)
|
|
320
|
+
args = merge_config_with_args(config_dict, args)
|
|
321
|
+
print(f"Configuration loaded from: {args.config}")
|
|
322
|
+
except FileNotFoundError as e:
|
|
323
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
324
|
+
sys.exit(1)
|
|
325
|
+
except Exception as e:
|
|
326
|
+
print(f"Error reading configuration: {e}", file=sys.stderr)
|
|
327
|
+
sys.exit(1)
|
|
328
|
+
|
|
160
329
|
if len(sys.argv) == 1:
|
|
161
330
|
parser.print_help()
|
|
162
331
|
sys.exit()
|
|
@@ -167,6 +336,17 @@ def main():
|
|
|
167
336
|
|
|
168
337
|
# Airfoil choice
|
|
169
338
|
cloud_points = None
|
|
339
|
+
airfoil_name = None
|
|
340
|
+
|
|
341
|
+
# Check that only one airfoil source is specified
|
|
342
|
+
airfoil_sources = [args.naca, args.airfoil, args.airfoil_path]
|
|
343
|
+
specified_sources = [s for s in airfoil_sources if s is not None]
|
|
344
|
+
|
|
345
|
+
if len(specified_sources) > 1:
|
|
346
|
+
print("\nError: Only one airfoil source can be specified at a time!")
|
|
347
|
+
print("Choose one of: --naca, --airfoil, or --airfoil_path\n")
|
|
348
|
+
sys.exit(1)
|
|
349
|
+
|
|
170
350
|
if args.naca:
|
|
171
351
|
airfoil_name = args.naca
|
|
172
352
|
cloud_points = NACA_4_digit_geom(airfoil_name)
|
|
@@ -175,9 +355,17 @@ def main():
|
|
|
175
355
|
airfoil_name = args.airfoil
|
|
176
356
|
cloud_points = get_airfoil_points(airfoil_name)
|
|
177
357
|
|
|
358
|
+
if args.airfoil_path:
|
|
359
|
+
airfoil_name = Path(args.airfoil_path).stem
|
|
360
|
+
cloud_points = read_airfoil_from_file(args.airfoil_path)
|
|
361
|
+
|
|
362
|
+
if args.flap_path:
|
|
363
|
+
airfoil_name = Path(args.airfoil_path).stem
|
|
364
|
+
flap_points = read_airfoil_from_file(args.flap_path)
|
|
365
|
+
|
|
178
366
|
if cloud_points is None:
|
|
179
367
|
print("\nNo airfoil profile specified, exiting")
|
|
180
|
-
print("You must use --naca or --
|
|
368
|
+
print("You must use --naca, --airfoil, or --airfoil_path\n")
|
|
181
369
|
parser.print_help()
|
|
182
370
|
sys.exit()
|
|
183
371
|
|
|
@@ -200,12 +388,22 @@ def main():
|
|
|
200
388
|
|
|
201
389
|
# Airfoil
|
|
202
390
|
airfoil = AirfoilSpline(
|
|
203
|
-
cloud_points, args.airfoil_mesh_size)
|
|
391
|
+
cloud_points, args.airfoil_mesh_size, name="airfoil")
|
|
204
392
|
airfoil.rotation(aoa, (0.5, 0, 0), (0, 0, 1))
|
|
205
393
|
gmsh.model.geo.synchronize()
|
|
206
394
|
|
|
207
|
-
|
|
208
|
-
|
|
395
|
+
if args.flap_path:
|
|
396
|
+
# Use flap_mesh_size if provided, otherwise use 85% of airfoil_mesh_size
|
|
397
|
+
flap_mesh_size = args.flap_mesh_size if args.flap_mesh_size else args.airfoil_mesh_size * 0.85
|
|
398
|
+
flap = AirfoilSpline(
|
|
399
|
+
flap_points, flap_mesh_size, name="flap", is_flap=True)
|
|
400
|
+
flap.rotation(aoa, (0.5, 0, 0), (0, 0, 1))
|
|
401
|
+
if args.deflection:
|
|
402
|
+
flap.rotation(-args.deflection * (math.pi / 180), (flap.le.x, flap.le.y, 0), (0, 0, 1))
|
|
403
|
+
gmsh.model.geo.synchronize()
|
|
404
|
+
|
|
405
|
+
# If structured, all is done in CType
|
|
406
|
+
if args.structured:
|
|
209
407
|
dx_wake, dy = [float(value)for value in args.arg_struc.split("x")]
|
|
210
408
|
mesh = CType(airfoil, dx_wake, dy,
|
|
211
409
|
args.airfoil_mesh_size, args.first_layer, args.ratio, aoa)
|
|
@@ -213,6 +411,8 @@ def main():
|
|
|
213
411
|
|
|
214
412
|
else:
|
|
215
413
|
k1, k2 = airfoil.gen_skin()
|
|
414
|
+
if args.flap_path:
|
|
415
|
+
k1_flap, k2_flap = flap.gen_skin()
|
|
216
416
|
# Choose the parameters for bl (when exist)
|
|
217
417
|
if not args.no_bl:
|
|
218
418
|
N = args.nb_layers
|
|
@@ -228,7 +428,13 @@ def main():
|
|
|
228
428
|
outofbounds(airfoil, args.box, args.farfield, d[-1])
|
|
229
429
|
|
|
230
430
|
# External domain
|
|
231
|
-
if args.
|
|
431
|
+
if args.farfield_ctype:
|
|
432
|
+
# Use C-type farfield (unstructured) for hybrid meshes
|
|
433
|
+
ext_domain = CType(
|
|
434
|
+
airfoil, args.farfield, args.farfield, args.ext_mesh_size,
|
|
435
|
+
structured=args.structured
|
|
436
|
+
)
|
|
437
|
+
elif args.box:
|
|
232
438
|
length, width = [float(value) for value in args.box.split("x")]
|
|
233
439
|
ext_domain = Rectangle(0.5, 0, 0, length, width,
|
|
234
440
|
mesh_size=args.ext_mesh_size)
|
|
@@ -238,13 +444,21 @@ def main():
|
|
|
238
444
|
gmsh.model.geo.synchronize()
|
|
239
445
|
|
|
240
446
|
# Create the surface for the mesh
|
|
241
|
-
|
|
447
|
+
if args.flap_path:
|
|
448
|
+
surface = PlaneSurface([ext_domain, airfoil, flap])
|
|
449
|
+
|
|
450
|
+
else:
|
|
451
|
+
surface = PlaneSurface([ext_domain, airfoil])
|
|
452
|
+
|
|
242
453
|
gmsh.model.geo.synchronize()
|
|
243
454
|
|
|
244
455
|
# Create the boundary layer
|
|
245
456
|
if not args.no_bl:
|
|
246
457
|
curv = [airfoil.upper_spline.tag,
|
|
247
458
|
airfoil.lower_spline.tag, airfoil.front_spline.tag]
|
|
459
|
+
if args.flap_path:
|
|
460
|
+
curv += [flap.upper_spline.tag,
|
|
461
|
+
flap.lower_spline.tag, flap.front_spline.tag]
|
|
248
462
|
|
|
249
463
|
# Creates a new mesh field of type 'BoundaryLayer' and assigns it an ID (f).
|
|
250
464
|
f = gmsh.model.mesh.field.add('BoundaryLayer')
|
|
@@ -260,8 +474,13 @@ def main():
|
|
|
260
474
|
gmsh.model.mesh.field.setNumber(f, 'Quads', 1)
|
|
261
475
|
|
|
262
476
|
# Enter the points where we want a "fan" (points must be at end on line)(only te for us)
|
|
263
|
-
|
|
264
|
-
f
|
|
477
|
+
if args.flap_path:
|
|
478
|
+
print(f"airfoil.te.tag, flap.te.tag = {airfoil.te.tag, flap.te.tag}")
|
|
479
|
+
gmsh.model.mesh.field.setNumbers(
|
|
480
|
+
f, "FanPointsList", [airfoil.te.tag, flap.te.tag])
|
|
481
|
+
else:
|
|
482
|
+
gmsh.model.mesh.field.setNumbers(
|
|
483
|
+
f, "FanPointsList", [airfoil.te.tag])
|
|
265
484
|
|
|
266
485
|
gmsh.model.mesh.field.setAsBoundaryLayer(f)
|
|
267
486
|
|
|
@@ -269,23 +488,26 @@ def main():
|
|
|
269
488
|
ext_domain.define_bc()
|
|
270
489
|
surface.define_bc()
|
|
271
490
|
airfoil.define_bc()
|
|
491
|
+
if args.flap_path:
|
|
492
|
+
flap.define_bc()
|
|
272
493
|
|
|
273
494
|
gmsh.model.geo.synchronize()
|
|
274
495
|
|
|
275
496
|
# Choose the parameters of the mesh : we want the mesh size according to the points and not curvature (doesn't work with farfield)
|
|
276
497
|
gmsh.option.setNumber("Mesh.MeshSizeFromPoints", 1)
|
|
277
498
|
gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", 0)
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
499
|
+
|
|
500
|
+
if not args.structured and not args.no_bl:
|
|
501
|
+
# Apply transfinite meshing to all three splines (front, upper, lower)
|
|
502
|
+
# for consistent cell sizing around the airfoil/flap surfaces
|
|
503
|
+
|
|
504
|
+
# Apply to airfoil
|
|
505
|
+
apply_transfinite_to_surfaces(airfoil, args.airfoil_mesh_size, name="Airfoil")
|
|
506
|
+
|
|
507
|
+
# Apply to flap if present
|
|
508
|
+
if args.flap_path:
|
|
509
|
+
apply_transfinite_to_surfaces(flap, args.airfoil_mesh_size, name="Flap")
|
|
510
|
+
|
|
289
511
|
# Choose the nbs of points in the fan at the te:
|
|
290
512
|
# Compute coef : between 10 and 25, 15 when usual mesh size but adapted to mesh size
|
|
291
513
|
coef = max(10, min(25, 15*0.01/args.airfoil_mesh_size))
|
|
@@ -300,11 +522,25 @@ def main():
|
|
|
300
522
|
gmsh.fltk.run()
|
|
301
523
|
|
|
302
524
|
# Mesh file name and output
|
|
525
|
+
if airfoil_name:
|
|
526
|
+
airfoil_name = airfoil_name.replace(".dat", "")
|
|
527
|
+
|
|
528
|
+
if args.flap_path:
|
|
529
|
+
airfoil_name = airfoil_name + "_flap"
|
|
530
|
+
|
|
303
531
|
mesh_path = Path(
|
|
304
532
|
args.output, f"mesh_airfoil_{airfoil_name}.{args.format}")
|
|
305
533
|
gmsh.write(str(mesh_path))
|
|
306
534
|
gmsh.finalize()
|
|
307
535
|
|
|
536
|
+
# Save configuration if requested
|
|
537
|
+
if args.save_config:
|
|
538
|
+
# Remove None values and internal arguments from the config dict
|
|
539
|
+
config_dict = {k: v for k, v in vars(args).items()
|
|
540
|
+
if v is not None and v is not False
|
|
541
|
+
and k not in ['config', 'save_config', 'example_config', 'list']}
|
|
542
|
+
from gmshairfoil2d.config_handler import write_config
|
|
543
|
+
write_config(config_dict, args.save_config)
|
|
308
544
|
|
|
309
545
|
if __name__ == "__main__":
|
|
310
546
|
main()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gmshairfoil2d
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Python tool to generate 2D mesh around an airfoil
|
|
5
5
|
Home-page: https://github.com/cfsengineering/GMSH-Airfoil-2D
|
|
6
6
|
Author: Giacomo Benedetti
|
|
@@ -61,32 +61,68 @@ gmshairfoil2d -h
|
|
|
61
61
|
|
|
62
62
|
optional arguments:
|
|
63
63
|
-h, --help Show this help message and exit
|
|
64
|
+
--config [PATH] Path to configuration file (key=value format)
|
|
65
|
+
--save_config [PATH] Save current configuration to file
|
|
66
|
+
--example_config Create an example configuration file
|
|
64
67
|
--list Display all airfoil available in the database :
|
|
65
68
|
https://m-selig.ae.illinois.edu/ads/coord_database.html
|
|
66
69
|
--naca [4DIGITS] NACA airfoil 4 digit
|
|
67
70
|
--airfoil [NAME] Name of an airfoil profile in the database (database available with
|
|
68
71
|
the --list argument)
|
|
72
|
+
--airfoil_path [PATH] Path to custom airfoil .dat file
|
|
73
|
+
--flap_path [PATH] Path to custom flap .dat file
|
|
69
74
|
--aoa [AOA] Angle of attack [deg] (default: 0 [deg])
|
|
75
|
+
--deflection [DEFLECTION] Flap deflection angle [deg] (default: 0 [deg])
|
|
70
76
|
--farfield [RADIUS] Create a circular farfield mesh of given radius [m] (default 10m)
|
|
77
|
+
--farfield_ctype Generate a C-type structured farfield for hybrid meshes
|
|
71
78
|
--box [LENGTHxWIDTH] Create a box mesh of dimensions [length]x[height] [m]
|
|
72
79
|
--airfoil_mesh_size [SIZE] Mesh size of the airfoil contour [m] (default 0.01m)
|
|
73
|
-
--ext_mesh_size [SIZE] Mesh size of the external domain [m] (default 0.2m)
|
|
74
|
-
and structural)
|
|
80
|
+
--ext_mesh_size [SIZE] Mesh size of the external domain [m] (default 0.2m)
|
|
75
81
|
--no_bl Do the unstructured meshing (with triangles), without a boundary
|
|
76
82
|
layer
|
|
77
|
-
--first_layer [HEIGHT] Height of the first layer [m] (default 3e-5m) (for bl and
|
|
78
|
-
--ratio [RATIO] Growth ratio of layers (default 1.2) (for bl and
|
|
83
|
+
--first_layer [HEIGHT] Height of the first layer [m] (default 3e-5m) (for bl and structured)
|
|
84
|
+
--ratio [RATIO] Growth ratio of layers (default 1.2) (for bl and structured)
|
|
79
85
|
--nb_layers [INT] Total number of layers in the boundary layer (default 35)
|
|
80
86
|
--format [FORMAT] Format of the mesh file, e.g: msh, vtk, wrl, stl, mesh, cgns, su2,
|
|
81
87
|
dat (default su2)
|
|
82
|
-
--
|
|
83
|
-
--arg_struc [
|
|
84
|
-
|
|
88
|
+
--structured Generate a structured mesh
|
|
89
|
+
--arg_struc [LxL] Parameters for the structured mesh [wake length (axis x)]x[total
|
|
90
|
+
height (axis y)] [m] (default 10x10)
|
|
85
91
|
--output [PATH] Output path for the mesh file (default : current dir)
|
|
86
92
|
--ui Open GMSH user interface to see the mesh
|
|
87
93
|
|
|
88
94
|
```
|
|
89
95
|
|
|
96
|
+
## Configuration Files
|
|
97
|
+
|
|
98
|
+
Instead of using command-line arguments, you can use configuration files for batch processing. Configuration files use a simple `key=value` format with support for comments.
|
|
99
|
+
|
|
100
|
+
**Create an example configuration file:**
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
gmshairfoil2d --example_config
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Use a configuration file:**
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
gmshairfoil2d --config my_config.cfg
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Override config values with CLI arguments:**
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
gmshairfoil2d --config my_config.cfg --aoa 5.0
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Save current configuration to file:**
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
gmshairfoil2d --naca 0012 --aoa 5.0 --save_config my_config.cfg
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
For more details, see [example/CONFIG_README.md](example/CONFIG_README.md) and ready-to-use examples in [example/EXAMPLES_README.md](example/EXAMPLES_README.md).
|
|
125
|
+
|
|
90
126
|
## Examples of use
|
|
91
127
|
|
|
92
128
|
To check all airfoil available in the [database](https://m-selig.ae.illinois.edu/ads/coord_database.html):
|
|
@@ -95,7 +131,9 @@ To check all airfoil available in the [database](https://m-selig.ae.illinois.edu
|
|
|
95
131
|
gmshairfoil2d --list
|
|
96
132
|
```
|
|
97
133
|
|
|
98
|
-
For all the following examples, the
|
|
134
|
+
For all the following examples, the default chord length is 1 meter.
|
|
135
|
+
|
|
136
|
+
### Example 1: NACA0012 with circular farfield
|
|
99
137
|
|
|
100
138
|
To create a circular farfield mesh around a NACA0012 of 10m of radius and see the result with GMSH user interface:
|
|
101
139
|
|
|
@@ -103,37 +141,87 @@ To create a circular farfield mesh around a NACA0012 of 10m of radius and see th
|
|
|
103
141
|
gmshairfoil2d --naca 0012 --farfield 10 --ui --no_bl
|
|
104
142
|
```
|
|
105
143
|
|
|
144
|
+
Or using config file:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
gmshairfoil2d --config example/example1_naca0012.cfg
|
|
148
|
+
```
|
|
149
|
+
|
|
106
150
|

|
|
107
151
|
|
|
108
|
-
|
|
152
|
+
### Example 2: DAE11 with boundary layer
|
|
153
|
+
|
|
154
|
+
To create a circular farfield mesh with boundary layer around a Drela DAE11 airfoil (the name in the database is "dae11") of 4m of radius with a mesh size of 0.005m on the airfoil:
|
|
109
155
|
|
|
110
156
|
```bash
|
|
111
157
|
gmshairfoil2d --airfoil dae11 --farfield 4 --airfoil_mesh_size 0.005
|
|
112
158
|
```
|
|
113
159
|
|
|
114
|
-
|
|
160
|
+
Or using config file:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
gmshairfoil2d --config example/example2_dae11.cfg
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Example 3: E211 in box with angle of attack
|
|
167
|
+
|
|
168
|
+
To create mesh around an Eppler E211 airfoil (the name in the database is "e211") with an angle of attack of 8 degree in a box of 12x4m (length x height) and save it as a VTK mesh:
|
|
115
169
|
|
|
116
170
|
```bash
|
|
117
171
|
gmshairfoil2d --airfoil e211 --aoa 8 --box 12x4 --format vtk --ui --no_bl
|
|
118
172
|
```
|
|
119
173
|
|
|
120
|
-
|
|
174
|
+
Or using config file:
|
|
121
175
|
|
|
176
|
+
```bash
|
|
177
|
+
gmshairfoil2d --config example/example3_e211_box.cfg
|
|
178
|
+
```
|
|
122
179
|
|
|
180
|
+

|
|
181
|
+
|
|
182
|
+
### Example 4: CH10SM with box and boundary layer
|
|
123
183
|
|
|
124
|
-
To create a boxed mesh around a
|
|
184
|
+
To create a boxed mesh around a Chuck Hollinger CH10SM airfoil (the name in the database is "ch10sm"), using the boundary layer with default parameters (first layer of height 3e-5, 35 layers and growth ratio of 1.2):
|
|
125
185
|
|
|
126
186
|
```bash
|
|
127
187
|
gmshairfoil2d --airfoil ch10sm --ui --box 2x1.4
|
|
128
188
|
```
|
|
129
189
|
|
|
190
|
+
Or using config file:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
gmshairfoil2d --config example/example4_ch10sm_bl.cfg
|
|
194
|
+
```
|
|
195
|
+
|
|
130
196
|

|
|
131
197
|
|
|
198
|
+
### Example 5: NACA4220 structured mesh
|
|
199
|
+
|
|
200
|
+
To create a structured mesh around a NACA4220 airfoil with first layer height of 0.01, mesh size of 0.08, wake length of 6, height of 7, and angle of attack of 6 degrees:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
gmshairfoil2d --naca 4220 --airfoil_mesh_size 0.08 --ui --structured --first_layer 0.01 --arg_struc 6x7 --aoa 6
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Or using config file:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
gmshairfoil2d --config example/example5_naca4220_structured.cfg
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+

|
|
213
|
+
### Example 6: Custom airfoil from file
|
|
214
|
+
|
|
215
|
+
To create a mesh around a custom airfoil profile with a deflected flap:
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
gmshairfoil2d --airfoil_path tests/test_data/NLR_7301.dat --flap_path tests/test_data/Flap_NLR_7301.dat --deflection 10 --box 4x3 --ui --no_bl
|
|
219
|
+
```
|
|
132
220
|
|
|
133
|
-
|
|
221
|
+
Or using config file:
|
|
134
222
|
|
|
135
223
|
```bash
|
|
136
|
-
|
|
224
|
+
gmshairfoil2d --config example/example6_custom_airfoil_flap.cfg
|
|
137
225
|
```
|
|
138
226
|
|
|
139
|
-
|
|
227
|
+
This example shows how to load custom airfoil and flap profiles from external .dat files and generate a mesh with flap deflection, useful for using proprietary or custom-designed airfoil geometries.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
gmshairfoil2d/__init__.py,sha256=VEFFbq6EClONvPaNYBMGUnR64J06nJaO_ErPZi6WyRE,84
|
|
2
|
+
gmshairfoil2d/__main__.py,sha256=SdT5IPOCPld7yGCoC3cb6v55E9Ys3KeM1vrVocBqtG4,134
|
|
3
|
+
gmshairfoil2d/airfoil_func.py,sha256=UGugj2Djp-7JTv_AzEwmQcMQNMtqHSVzAdIIxZNwDyQ,7545
|
|
4
|
+
gmshairfoil2d/config_handler.py,sha256=WM10C_yxlAl6A0sAsgW9KbfIHnrWOhvwMbs7F_AQePI,5490
|
|
5
|
+
gmshairfoil2d/geometry_def.py,sha256=D3FuRfaZ_YgHTvDW3KWB6Cya_2tJEktFvZkTA13h7pQ,48800
|
|
6
|
+
gmshairfoil2d/gmshairfoil2d.py,sha256=acEOhx8IlhRkXfFP-C-3rtqlslXgbIW276Xcz3k5Tq8,18657
|
|
7
|
+
gmshairfoil2d-0.2.3.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
8
|
+
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
tests/test_airfoil_func.py,sha256=uCzaCbr4BHfKCHF_9gEQ7hzgwQSt7rEZRrRCbEOtOlw,4158
|
|
10
|
+
tests/test_config_handler.py,sha256=TI0OvZRbsho8mjzf1kUXLQ-rm_6dGY4b6w9RxYDxS5A,6449
|
|
11
|
+
tests/test_geometry_def.py,sha256=Ox_ePu1sZs5dOZTsPj87b3b_gHwrC3UzyoJsuL-VeUk,1325
|
|
12
|
+
gmshairfoil2d-0.2.3.dist-info/METADATA,sha256=WP9jWHSg8z1bSsyYFuYMdG36-AqspQWKO6T-vHlQzFc,8152
|
|
13
|
+
gmshairfoil2d-0.2.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
+
gmshairfoil2d-0.2.3.dist-info/entry_points.txt,sha256=6OBSsEXNhTICrsEGgfg30RGIkKRFXELizYtfZfT1_zk,67
|
|
15
|
+
gmshairfoil2d-0.2.3.dist-info/top_level.txt,sha256=OUzQHTQIzJHlW1k6tm_9PLfE4eEWkwH0oOYNUuUXekw,20
|
|
16
|
+
gmshairfoil2d-0.2.3.dist-info/RECORD,,
|