gmshairfoil2d 0.2.31__py3-none-any.whl → 0.2.33__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 CHANGED
@@ -1,3 +1,3 @@
1
1
  """GMSH-Airfoil-2D: 2D airfoil mesh generation with GMSH."""
2
2
 
3
- __version__ = "1.0.0"
3
+ __version__ = "0.2.33"
@@ -131,7 +131,7 @@ def get_airfoil_file(airfoil_name):
131
131
  sys.exit(1)
132
132
 
133
133
 
134
- def get_airfoil_points(airfoil_name):
134
+ def get_airfoil_points(airfoil_name: str) -> list[tuple[float, float, float]]:
135
135
  """Load airfoil points from the database.
136
136
 
137
137
  Parameters
@@ -149,14 +149,25 @@ def get_airfoil_points(airfoil_name):
149
149
  ValueError
150
150
  If no valid points found for the airfoil
151
151
  """
152
+ if len(airfoil_name) == 4 and airfoil_name.isdigit():
153
+ return four_digit_naca_airfoil(
154
+ naca_name=airfoil_name,
155
+ )
156
+
152
157
  get_airfoil_file(airfoil_name)
153
158
  airfoil_file = Path(database_dir, f"{airfoil_name}.dat")
154
159
 
155
160
  airfoil_points = []
156
161
  with open(airfoil_file) as f:
157
162
  for line in f:
163
+ line = line.strip()
164
+ if not line or line.startswith(('#', 'Airfoil')):
165
+ continue
166
+ parts = line.split()
167
+ if len(parts) != 2:
168
+ continue
158
169
  try:
159
- x, y = map(float, line.strip().split())
170
+ x, y = map(float, parts)
160
171
  except ValueError:
161
172
  continue
162
173
  if x > 1 and y > 1:
@@ -166,35 +177,83 @@ def get_airfoil_points(airfoil_name):
166
177
  if not airfoil_points:
167
178
  raise ValueError(f"No valid points found for airfoil {airfoil_name}")
168
179
 
169
- n_points = len(airfoil_points)
170
- upper_len = n_points // 2
180
+ def _dedupe_consecutive(points, tol=1e-9):
181
+ out = []
182
+ for x, y in points:
183
+ if not out:
184
+ out.append((x, y))
185
+ continue
186
+ if abs(x - out[-1][0]) <= tol and abs(y - out[-1][1]) <= tol:
187
+ continue
188
+ out.append((x, y))
189
+ return out
171
190
 
172
- # Try to find split point at (0, 0)
173
- for i, (x, y) in enumerate(airfoil_points):
174
- if x == y == 0:
175
- upper_len = i
176
- break
191
+ def _dedupe_any(points, tol=1e-9):
192
+ out = []
193
+ for x, y in points:
194
+ if any(abs(x - ux) <= tol and abs(y - uy) <= tol for ux, uy in out):
195
+ continue
196
+ out.append((x, y))
197
+ return out
177
198
 
178
- upper_points = airfoil_points[:upper_len]
179
- lower_points = airfoil_points[upper_len:]
180
-
181
- if lower_points and lower_points[0][0] == 0:
182
- lower_points = lower_points[::-1]
199
+ tol = 1e-9
200
+ airfoil_points = _dedupe_consecutive(airfoil_points, tol=tol)
183
201
 
184
- x_up, y_up = zip(*upper_points) if upper_points else ([], [])
185
- x_lo, y_lo = zip(*lower_points) if lower_points else ([], [])
202
+ if len(airfoil_points) < 3:
203
+ raise ValueError(f"Not enough unique points for airfoil {airfoil_name}")
186
204
 
187
- cloud_points = [(x, y, 0) for x, y in zip([*x_up, *x_lo], [*y_up, *y_lo])]
188
- return sorted(set(cloud_points), key=cloud_points.index)
205
+ # Split into upper/lower when a LE point repeats (common in UIUC files)
206
+ min_x = min(x for x, _ in airfoil_points)
207
+ le_indices = [i for i, (x, _) in enumerate(airfoil_points) if abs(x - min_x) <= tol]
208
+
209
+ if len(le_indices) >= 2:
210
+ split_idx = le_indices[1]
211
+ upper = airfoil_points[:split_idx]
212
+ lower = airfoil_points[split_idx:]
213
+ else:
214
+ # Fallback: split at first maximum x (trailing edge)
215
+ max_x = max(x for x, _ in airfoil_points)
216
+ split_idx = next(i for i, (x, _) in enumerate(airfoil_points) if abs(x - max_x) <= tol)
217
+ upper = airfoil_points[:split_idx + 1]
218
+ lower = airfoil_points[split_idx + 1:]
219
+
220
+ def _ensure_le_to_te(points):
221
+ if len(points) < 2:
222
+ return points
223
+ return points if points[0][0] <= points[-1][0] else points[::-1]
224
+
225
+ upper = _ensure_le_to_te(upper)
226
+ lower = _ensure_le_to_te(lower)
227
+
228
+ # Build a closed loop starting at TE: TE->LE (upper reversed) then LE->TE (lower)
229
+ if upper and lower:
230
+ upper = upper[::-1]
231
+ loop = upper + lower[1:]
232
+ else:
233
+ loop = airfoil_points
234
+
235
+ # Remove duplicate closing point if present
236
+ if len(loop) > 1:
237
+ x0, y0 = loop[0]
238
+ x1, y1 = loop[-1]
239
+ if abs(x0 - x1) <= tol and abs(y0 - y1) <= tol:
240
+ loop.pop()
241
+
242
+ loop = _dedupe_any(loop, tol=tol)
243
+
244
+ if len(loop) < 3:
245
+ raise ValueError(f"Not enough unique points for airfoil {airfoil_name}")
246
+
247
+ return [(x, y, 0) for x, y in loop]
189
248
 
190
249
 
191
- def NACA_4_digit_geom(NACA_name, nb_points=100):
250
+ def four_digit_naca_airfoil(naca_name: str, nb_points: int = 100):
192
251
  """
193
252
  Compute the profile of a NACA 4 digits airfoil
194
253
 
195
254
  Parameters
196
255
  ----------
197
- NACA_name : str
256
+ naca_name : str
198
257
  4 digit of the NACA airfoil
199
258
  nb_points : int, optional
200
259
  number of points for the disrcetisation of
@@ -208,9 +267,9 @@ def NACA_4_digit_geom(NACA_name, nb_points=100):
208
267
  theta_line = np.linspace(0, np.pi, nb_points)
209
268
  x_line = 0.5 * (1 - np.cos(theta_line))
210
269
 
211
- m = int(NACA_name[0]) / 100
212
- p = int(NACA_name[1]) / 10
213
- t = (int(NACA_name[2]) * 10 + int(NACA_name[3])) / 100
270
+ m = int(naca_name[0]) / 100
271
+ p = int(naca_name[1]) / 10
272
+ t = (int(naca_name[2]) * 10 + int(naca_name[3])) / 100
214
273
 
215
274
  # thickness line
216
275
  y_t = (
@@ -6,7 +6,7 @@ import sys
6
6
  from pathlib import Path
7
7
 
8
8
  import gmsh
9
- from gmshairfoil2d.airfoil_func import (NACA_4_digit_geom, get_airfoil_points,
9
+ from gmshairfoil2d.airfoil_func import (four_digit_naca_airfoil, get_airfoil_points,
10
10
  get_all_available_airfoil_names, read_airfoil_from_file)
11
11
  from gmshairfoil2d.geometry_def import (AirfoilSpline, Circle, PlaneSurface,
12
12
  Rectangle, outofbounds, CType)
@@ -349,7 +349,7 @@ def main():
349
349
 
350
350
  if args.naca:
351
351
  airfoil_name = args.naca
352
- cloud_points = NACA_4_digit_geom(airfoil_name)
352
+ cloud_points = four_digit_naca_airfoil(airfoil_name)
353
353
 
354
354
  if args.airfoil:
355
355
  airfoil_name = args.airfoil
@@ -542,5 +542,6 @@ def main():
542
542
  from gmshairfoil2d.config_handler import write_config
543
543
  write_config(config_dict, args.save_config)
544
544
 
545
+
545
546
  if __name__ == "__main__":
546
547
  main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gmshairfoil2d
3
- Version: 0.2.31
3
+ Version: 0.2.33
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
@@ -0,0 +1,16 @@
1
+ gmshairfoil2d/__init__.py,sha256=Z6ecfFqBGVmNzzlviwe1cnuzG-ArA79kykCVP4eveRE,85
2
+ gmshairfoil2d/__main__.py,sha256=SdT5IPOCPld7yGCoC3cb6v55E9Ys3KeM1vrVocBqtG4,134
3
+ gmshairfoil2d/airfoil_func.py,sha256=ddJMWhsdnjXANw1yDjKIsV9kEz0sWDTXRJmLpOFGou4,9525
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=2j3JqSsPyTzeV2nvaoBjqb18v-oD3tLM3dz8dabkAfI,18670
7
+ gmshairfoil2d-0.2.33.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
8
+ tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ tests/test_airfoil_func.py,sha256=Q7I9_9v97V52M-qzhV42IJnCDntA5TV3z_gif2a7ua8,4182
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.33.dist-info/METADATA,sha256=XyPHJ64FeiiQmNoVs0GJQ82AUUSjVtNqq-QhvZ4v9XM,8248
13
+ gmshairfoil2d-0.2.33.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
14
+ gmshairfoil2d-0.2.33.dist-info/entry_points.txt,sha256=6OBSsEXNhTICrsEGgfg30RGIkKRFXELizYtfZfT1_zk,67
15
+ gmshairfoil2d-0.2.33.dist-info/top_level.txt,sha256=OUzQHTQIzJHlW1k6tm_9PLfE4eEWkwH0oOYNUuUXekw,20
16
+ gmshairfoil2d-0.2.33.dist-info/RECORD,,
@@ -3,7 +3,7 @@ from pathlib import Path
3
3
  from unittest.mock import patch, Mock
4
4
 
5
5
  import gmshairfoil2d.__init__
6
- from gmshairfoil2d.airfoil_func import (NACA_4_digit_geom, get_airfoil_file,
6
+ from gmshairfoil2d.airfoil_func import (four_digit_naca_airfoil, get_airfoil_file,
7
7
  get_all_available_airfoil_names, read_airfoil_from_file)
8
8
  from pytest import approx
9
9
 
@@ -58,7 +58,7 @@ def test_get_airfoil_file(monkeypatch, tmp_path):
58
58
  assert expected_path.read_text() == fake_text
59
59
 
60
60
 
61
- def test_NACA_4_digit_geom():
61
+ def test_four_digit_naca_airfoil():
62
62
  with open(Path(test_data_dir, "naca0012.txt"), "rb") as f:
63
63
  naca0012 = pickle.load(f)
64
64
  with open(Path(test_data_dir, "naca4412.txt"), "rb") as f:
@@ -69,11 +69,11 @@ def test_NACA_4_digit_geom():
69
69
  """
70
70
 
71
71
  assert all(
72
- [a == approx(b, 1e-3) for a, b in zip(naca0012, NACA_4_digit_geom("0012"))]
72
+ [a == approx(b, 1e-3) for a, b in zip(naca0012, four_digit_naca_airfoil("0012"))]
73
73
  )
74
74
 
75
75
  assert all(
76
- [a == approx(b, 1e-3) for a, b in zip(naca4412, NACA_4_digit_geom("4412"))]
76
+ [a == approx(b, 1e-3) for a, b in zip(naca4412, four_digit_naca_airfoil("4412"))]
77
77
  )
78
78
 
79
79
  def test_read_airfoil_from_file(tmp_path):
@@ -1,16 +0,0 @@
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.31.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.31.dist-info/METADATA,sha256=lwQWTpntG0cPpQJdyMWms0OnQBeG6Oyd-li_fZ4fB44,8248
13
- gmshairfoil2d-0.2.31.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
14
- gmshairfoil2d-0.2.31.dist-info/entry_points.txt,sha256=6OBSsEXNhTICrsEGgfg30RGIkKRFXELizYtfZfT1_zk,67
15
- gmshairfoil2d-0.2.31.dist-info/top_level.txt,sha256=OUzQHTQIzJHlW1k6tm_9PLfE4eEWkwH0oOYNUuUXekw,20
16
- gmshairfoil2d-0.2.31.dist-info/RECORD,,