gmshairfoil2d 0.2.2__tar.gz → 0.2.3__tar.gz

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.
Files changed (24) hide show
  1. {gmshairfoil2d-0.2.2 → gmshairfoil2d-0.2.3}/PKG-INFO +104 -16
  2. gmshairfoil2d-0.2.3/README.md +196 -0
  3. gmshairfoil2d-0.2.3/gmshairfoil2d/__init__.py +3 -0
  4. gmshairfoil2d-0.2.3/gmshairfoil2d/__main__.py +6 -0
  5. {gmshairfoil2d-0.2.2 → gmshairfoil2d-0.2.3}/gmshairfoil2d/airfoil_func.py +126 -71
  6. gmshairfoil2d-0.2.3/gmshairfoil2d/config_handler.py +198 -0
  7. {gmshairfoil2d-0.2.2 → gmshairfoil2d-0.2.3}/gmshairfoil2d/geometry_def.py +331 -277
  8. gmshairfoil2d-0.2.3/gmshairfoil2d/gmshairfoil2d.py +546 -0
  9. {gmshairfoil2d-0.2.2 → gmshairfoil2d-0.2.3}/gmshairfoil2d.egg-info/PKG-INFO +104 -16
  10. {gmshairfoil2d-0.2.2 → gmshairfoil2d-0.2.3}/gmshairfoil2d.egg-info/SOURCES.txt +3 -0
  11. {gmshairfoil2d-0.2.2 → gmshairfoil2d-0.2.3}/setup.py +1 -1
  12. {gmshairfoil2d-0.2.2 → gmshairfoil2d-0.2.3}/tests/test_airfoil_func.py +52 -1
  13. gmshairfoil2d-0.2.3/tests/test_config_handler.py +260 -0
  14. {gmshairfoil2d-0.2.2 → gmshairfoil2d-0.2.3}/tests/test_geometry_def.py +11 -7
  15. gmshairfoil2d-0.2.2/README.md +0 -108
  16. gmshairfoil2d-0.2.2/gmshairfoil2d/gmshairfoil2d.py +0 -310
  17. gmshairfoil2d-0.2.2/tests/__init__.py +0 -0
  18. {gmshairfoil2d-0.2.2 → gmshairfoil2d-0.2.3}/LICENSE +0 -0
  19. {gmshairfoil2d-0.2.2 → gmshairfoil2d-0.2.3}/gmshairfoil2d.egg-info/dependency_links.txt +0 -0
  20. {gmshairfoil2d-0.2.2 → gmshairfoil2d-0.2.3}/gmshairfoil2d.egg-info/entry_points.txt +0 -0
  21. {gmshairfoil2d-0.2.2 → gmshairfoil2d-0.2.3}/gmshairfoil2d.egg-info/requires.txt +0 -0
  22. {gmshairfoil2d-0.2.2 → gmshairfoil2d-0.2.3}/gmshairfoil2d.egg-info/top_level.txt +0 -0
  23. {gmshairfoil2d-0.2.2 → gmshairfoil2d-0.2.3}/setup.cfg +0 -0
  24. {gmshairfoil2d-0.2.2/gmshairfoil2d → gmshairfoil2d-0.2.3/tests}/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gmshairfoil2d
3
- Version: 0.2.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) (for normal, bl
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 structural)
78
- --ratio [RATIO] Growth ratio of layers (default 1.2) (for bl and structural)
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
- --structural Generate a structural mesh
83
- --arg_struc [LxLxL] Parameters for the structural mesh [leading (axis x)]x[wake (axis
84
- x)]x[total height (axis y)] [m] (default 1x10x10)
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 defauld chord lenght is 1 meter.
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
  ![GMSH user interface with the 2D mesh](images/example_mesh.png)
107
151
 
108
- To create a circular farfield mesh with boudary layer around a Drela DAE11 airfoil (the name in the database is "dae11") of 4m or radius with a mesh size of 0.005m on the airfoil (but to not open on the interface):
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
- To create mesh around a Eppler E220 airfoil (the name in the database is "e211") with an angle of attack of 8 degree in a box of 12x4m (lenght x height) and save it as a vtk mesh and see the result with GMSH user interface:
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
- ![GMSH user interface with the 2D mesh, rectangular box](images/example_mesh_box.png)
174
+ Or using config file:
121
175
 
176
+ ```bash
177
+ gmshairfoil2d --config example/example3_e211_box.cfg
178
+ ```
122
179
 
180
+ ![GMSH user interface with the 2D mesh, rectangular box](images/example_mesh_box.png)
181
+
182
+ ### Example 4: CH10SM with box and boundary layer
123
183
 
124
- To create a boxed mesh around a Chuch Hollinger CH 10-48-13 smoothed 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) :
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
  ![GMSH result with 2D mesh with boundary layer, rectangular box](images/example_ch10sm_bl.png)
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
+ ![GMSH result with 2D structured mesh](images/example_structured_naca4220.png)
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
- To create a structural mesh around a Naca 4220 airfoil (the 4 digits code is obviously "4220"), 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 :
221
+ Or using config file:
134
222
 
135
223
  ```bash
136
- gmshairfoil2d --naca 4220 --airfoil_mesh_size 0.08 --ui --structural --first_layer 0.01 --arg_struc 6x7 --aoa 6
224
+ gmshairfoil2d --config example/example6_custom_airfoil_flap.cfg
137
225
  ```
138
226
 
139
- ![GMSH result with 2D structural mesh](images/example_structural_naca4220.png)
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,196 @@
1
+ [![Pytest](https://github.com/cfsengineering/GMSH-Airfoil-2D/actions/workflows/pytest.yml/badge.svg?branch=main)](https://github.com/cfsengineering/GMSH-Airfoil-2D/actions/workflows/pytest.yml)
2
+ [![PyPi version](https://img.shields.io/pypi/v/gmshairfoil2d.svg)](https://pypi.python.org/pypi/gmshairfoil2d)
3
+ [![License](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://github.com/cfsengineering/GMSH-Airfoil-2D/blob/main/LICENSE)
4
+ [![Black code style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
5
+
6
+ # GMSH-Airfoil-2D
7
+
8
+ Python tool to genreate 2D unstructured, hybrid and structured mesh around an airfoil with [GMSH](https://gmsh.info/) in one command line.
9
+
10
+ ## Installation
11
+
12
+ You can install this package from PyPi:
13
+
14
+ ```bash
15
+ pip install gmshairfoil2d
16
+ ```
17
+
18
+ Or you can clone and install this repository with the following commands:
19
+
20
+ ```bash
21
+ git clone https://github.com/cfsengineering/GMSH-Airfoil-2D.git
22
+ cd GMSH-Airfoil-2D
23
+ pip install -e .
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ ```text
29
+ gmshairfoil2d -h
30
+
31
+ optional arguments:
32
+ -h, --help Show this help message and exit
33
+ --config [PATH] Path to configuration file (key=value format)
34
+ --save_config [PATH] Save current configuration to file
35
+ --example_config Create an example configuration file
36
+ --list Display all airfoil available in the database :
37
+ https://m-selig.ae.illinois.edu/ads/coord_database.html
38
+ --naca [4DIGITS] NACA airfoil 4 digit
39
+ --airfoil [NAME] Name of an airfoil profile in the database (database available with
40
+ the --list argument)
41
+ --airfoil_path [PATH] Path to custom airfoil .dat file
42
+ --flap_path [PATH] Path to custom flap .dat file
43
+ --aoa [AOA] Angle of attack [deg] (default: 0 [deg])
44
+ --deflection [DEFLECTION] Flap deflection angle [deg] (default: 0 [deg])
45
+ --farfield [RADIUS] Create a circular farfield mesh of given radius [m] (default 10m)
46
+ --farfield_ctype Generate a C-type structured farfield for hybrid meshes
47
+ --box [LENGTHxWIDTH] Create a box mesh of dimensions [length]x[height] [m]
48
+ --airfoil_mesh_size [SIZE] Mesh size of the airfoil contour [m] (default 0.01m)
49
+ --ext_mesh_size [SIZE] Mesh size of the external domain [m] (default 0.2m)
50
+ --no_bl Do the unstructured meshing (with triangles), without a boundary
51
+ layer
52
+ --first_layer [HEIGHT] Height of the first layer [m] (default 3e-5m) (for bl and structured)
53
+ --ratio [RATIO] Growth ratio of layers (default 1.2) (for bl and structured)
54
+ --nb_layers [INT] Total number of layers in the boundary layer (default 35)
55
+ --format [FORMAT] Format of the mesh file, e.g: msh, vtk, wrl, stl, mesh, cgns, su2,
56
+ dat (default su2)
57
+ --structured Generate a structured mesh
58
+ --arg_struc [LxL] Parameters for the structured mesh [wake length (axis x)]x[total
59
+ height (axis y)] [m] (default 10x10)
60
+ --output [PATH] Output path for the mesh file (default : current dir)
61
+ --ui Open GMSH user interface to see the mesh
62
+
63
+ ```
64
+
65
+ ## Configuration Files
66
+
67
+ 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.
68
+
69
+ **Create an example configuration file:**
70
+
71
+ ```bash
72
+ gmshairfoil2d --example_config
73
+ ```
74
+
75
+ **Use a configuration file:**
76
+
77
+ ```bash
78
+ gmshairfoil2d --config my_config.cfg
79
+ ```
80
+
81
+ **Override config values with CLI arguments:**
82
+
83
+ ```bash
84
+ gmshairfoil2d --config my_config.cfg --aoa 5.0
85
+ ```
86
+
87
+ **Save current configuration to file:**
88
+
89
+ ```bash
90
+ gmshairfoil2d --naca 0012 --aoa 5.0 --save_config my_config.cfg
91
+ ```
92
+
93
+ 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).
94
+
95
+ ## Examples of use
96
+
97
+ To check all airfoil available in the [database](https://m-selig.ae.illinois.edu/ads/coord_database.html):
98
+
99
+ ```bash
100
+ gmshairfoil2d --list
101
+ ```
102
+
103
+ For all the following examples, the default chord length is 1 meter.
104
+
105
+ ### Example 1: NACA0012 with circular farfield
106
+
107
+ To create a circular farfield mesh around a NACA0012 of 10m of radius and see the result with GMSH user interface:
108
+
109
+ ```bash
110
+ gmshairfoil2d --naca 0012 --farfield 10 --ui --no_bl
111
+ ```
112
+
113
+ Or using config file:
114
+
115
+ ```bash
116
+ gmshairfoil2d --config example/example1_naca0012.cfg
117
+ ```
118
+
119
+ ![GMSH user interface with the 2D mesh](images/example_mesh.png)
120
+
121
+ ### Example 2: DAE11 with boundary layer
122
+
123
+ 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:
124
+
125
+ ```bash
126
+ gmshairfoil2d --airfoil dae11 --farfield 4 --airfoil_mesh_size 0.005
127
+ ```
128
+
129
+ Or using config file:
130
+
131
+ ```bash
132
+ gmshairfoil2d --config example/example2_dae11.cfg
133
+ ```
134
+
135
+ ### Example 3: E211 in box with angle of attack
136
+
137
+ 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:
138
+
139
+ ```bash
140
+ gmshairfoil2d --airfoil e211 --aoa 8 --box 12x4 --format vtk --ui --no_bl
141
+ ```
142
+
143
+ Or using config file:
144
+
145
+ ```bash
146
+ gmshairfoil2d --config example/example3_e211_box.cfg
147
+ ```
148
+
149
+ ![GMSH user interface with the 2D mesh, rectangular box](images/example_mesh_box.png)
150
+
151
+ ### Example 4: CH10SM with box and boundary layer
152
+
153
+ 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):
154
+
155
+ ```bash
156
+ gmshairfoil2d --airfoil ch10sm --ui --box 2x1.4
157
+ ```
158
+
159
+ Or using config file:
160
+
161
+ ```bash
162
+ gmshairfoil2d --config example/example4_ch10sm_bl.cfg
163
+ ```
164
+
165
+ ![GMSH result with 2D mesh with boundary layer, rectangular box](images/example_ch10sm_bl.png)
166
+
167
+ ### Example 5: NACA4220 structured mesh
168
+
169
+ 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:
170
+
171
+ ```bash
172
+ gmshairfoil2d --naca 4220 --airfoil_mesh_size 0.08 --ui --structured --first_layer 0.01 --arg_struc 6x7 --aoa 6
173
+ ```
174
+
175
+ Or using config file:
176
+
177
+ ```bash
178
+ gmshairfoil2d --config example/example5_naca4220_structured.cfg
179
+ ```
180
+
181
+ ![GMSH result with 2D structured mesh](images/example_structured_naca4220.png)
182
+ ### Example 6: Custom airfoil from file
183
+
184
+ To create a mesh around a custom airfoil profile with a deflected flap:
185
+
186
+ ```bash
187
+ 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
188
+ ```
189
+
190
+ Or using config file:
191
+
192
+ ```bash
193
+ gmshairfoil2d --config example/example6_custom_airfoil_flap.cfg
194
+ ```
195
+
196
+ 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,3 @@
1
+ """GMSH-Airfoil-2D: 2D airfoil mesh generation with GMSH."""
2
+
3
+ __version__ = "1.0.0"
@@ -0,0 +1,6 @@
1
+ """Entry point for the gmshairfoil2d package."""
2
+
3
+ from gmshairfoil2d.gmshairfoil2d import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -1,8 +1,8 @@
1
+ import sys
1
2
  from pathlib import Path
2
3
 
3
4
  import numpy as np
4
5
  import requests
5
- import sys
6
6
 
7
7
  import gmshairfoil2d.__init__
8
8
 
@@ -10,6 +10,71 @@ LIB_DIR = Path(gmshairfoil2d.__init__.__file__).parents[1]
10
10
  database_dir = Path(LIB_DIR, "database")
11
11
 
12
12
 
13
+ def read_airfoil_from_file(file_path):
14
+ """Read airfoil coordinates from a .dat file.
15
+
16
+ Parameters
17
+ ----------
18
+ file_path : str or Path
19
+ Path to airfoil data file
20
+
21
+ Returns
22
+ -------
23
+ list
24
+ List of unique (x, y, 0) points sorted by original order
25
+
26
+ Raises
27
+ ------
28
+ FileNotFoundError
29
+ If file does not exist
30
+ ValueError
31
+ If no valid airfoil points found
32
+ """
33
+ file_path = Path(file_path)
34
+ if not file_path.exists():
35
+ raise FileNotFoundError(f"File {file_path} not found.")
36
+
37
+ airfoil_points = []
38
+ with open(file_path, 'r') as f:
39
+ for line in f:
40
+ line = line.strip()
41
+ if not line or line.startswith(('#', 'Airfoil')):
42
+ continue
43
+ parts = line.split()
44
+ if len(parts) != 2:
45
+ continue
46
+ try:
47
+ x, y = map(float, parts)
48
+ except ValueError:
49
+ continue
50
+ if x > 1 and y > 1:
51
+ continue
52
+ airfoil_points.append((x, y))
53
+
54
+ if not airfoil_points:
55
+ raise ValueError(f"No valid airfoil points found in {file_path}")
56
+
57
+ # Split upper and lower surfaces
58
+ try:
59
+ split_index = next(i for i, (x, y) in enumerate(airfoil_points) if x >= 1.0)
60
+ except StopIteration:
61
+ split_index = len(airfoil_points) // 2
62
+
63
+ upper_points = airfoil_points[:split_index + 1]
64
+ lower_points = airfoil_points[split_index + 1:]
65
+
66
+ # Ensure lower points start from trailing edge
67
+ if lower_points and lower_points[0][0] == 0.0:
68
+ lower_points = lower_points[::-1]
69
+
70
+ # Combine and remove duplicates
71
+ x_up, y_up = zip(*upper_points) if upper_points else ([], [])
72
+ x_lo, y_lo = zip(*lower_points) if lower_points else ([], [])
73
+
74
+ cloud_points = [(x, y, 0) for x, y in zip([*x_up, *x_lo], [*y_up, *y_lo])]
75
+ return sorted(set(cloud_points), key=cloud_points.index)
76
+
77
+
13
78
  def get_all_available_airfoil_names():
14
79
  """
15
80
  Request the airfoil list available at m-selig.ae.illinois.edu
@@ -34,102 +99,92 @@ def get_all_available_airfoil_names():
34
99
 
35
100
  def get_airfoil_file(airfoil_name):
36
101
  """
37
- Request the airfoil .dat file at m-selig.ae.illinois.edu and stores it (if found) in the
38
- database folder
102
+ Request the airfoil .dat file from m-selig.ae.illinois.edu and store it in database folder.
39
103
 
40
104
  Parameters
41
105
  ----------
42
- airfoil_name : srt
43
- name of the airfoil
106
+ airfoil_name : str
107
+ Name of the airfoil
108
+
109
+ Raises
110
+ ------
111
+ SystemExit
112
+ If airfoil not found or network error occurs
44
113
  """
45
-
46
114
  if not database_dir.exists():
47
115
  database_dir.mkdir()
48
116
 
49
- url = f"https://m-selig.ae.illinois.edu/ads/coord/{airfoil_name}.dat"
50
-
51
- r = requests.get(url)
117
+ file_path = Path(database_dir, f"{airfoil_name}.dat")
118
+ if file_path.exists():
119
+ return
52
120
 
121
+ url = f"https://m-selig.ae.illinois.edu/ads/coord/{airfoil_name}.dat"
53
122
  try:
54
- r = requests.get(url, timeout=10) # Aggiungi sempre un timeout
55
- if r.status_code != 200:
56
- # Invece di raise Exception, print e exit pulito
57
- print(f"❌ Error: Could not find airfoil '{airfoil_name}' on UIUC database.")
58
- import sys
59
- sys.exit(1)
60
- except requests.exceptions.RequestException as e:
123
+ response = requests.get(url, timeout=10)
124
+ if response.status_code != 200:
125
+ print(f"❌ Error: Could not find airfoil '{airfoil_name}' on UIUC database.")
126
+ sys.exit(1)
127
+ with open(file_path, "wb") as f:
128
+ f.write(response.content)
129
+ except requests.exceptions.RequestException:
61
130
  print(f"❌ Network Error: Could not connect to the database. Check your internet.")
62
- import sys
63
131
  sys.exit(1)
64
132
 
65
- file_path = Path(database_dir, f"{airfoil_name}.dat")
66
-
67
- if not file_path.exists():
68
- with open(file_path, "wb") as f:
69
- f.write(r.content)
70
-
71
133
 
72
134
  def get_airfoil_points(airfoil_name):
73
-
74
- airfoil_points = []
75
- upper_points = []
76
- lower_points = []
77
- upper_len = 0
78
- lower_len = 0
79
- reverse_lower = False
80
-
135
+ """Load airfoil points from the database.
136
+
137
+ Parameters
138
+ ----------
139
+ airfoil_name : str
140
+ Name of the airfoil in the database
141
+
142
+ Returns
143
+ -------
144
+ list
145
+ List of unique (x, y, 0) points
146
+
147
+ Raises
148
+ ------
149
+ ValueError
150
+ If no valid points found for the airfoil
151
+ """
81
152
  get_airfoil_file(airfoil_name)
82
- airfoil_file = Path(database_dir, airfoil_name + ".dat")
153
+ airfoil_file = Path(database_dir, f"{airfoil_name}.dat")
83
154
 
155
+ airfoil_points = []
84
156
  with open(airfoil_file) as f:
85
- lines = f.readlines()
86
-
87
- for line in lines:
88
-
89
- # Catch the text lines
90
- try:
91
- x, y = map(float, line.strip("\n").split())
92
- except ValueError:
93
- continue
94
-
95
- # Catch the line with the upper and lower number of points
96
- if x > 1 and y > 1:
97
- upper_len = int(x)
98
- lower_len = int(y)
99
- continue
100
-
101
- # Catch the x, y coordinates
102
- airfoil_points.append((x, y))
157
+ for line in f:
158
+ try:
159
+ x, y = map(float, line.strip().split())
160
+ except ValueError:
161
+ continue
162
+ if x > 1 and y > 1:
163
+ continue
164
+ airfoil_points.append((x, y))
165
+
166
+ if not airfoil_points:
167
+ raise ValueError(f"No valid points found for airfoil {airfoil_name}")
103
168
 
104
169
  n_points = len(airfoil_points)
170
+ upper_len = n_points // 2
105
171
 
106
- if not upper_len or not lower_len:
107
-
108
- upper_len = n_points // 2
109
-
110
- for i, (x, y) in enumerate(airfoil_points):
111
- if x == y == 0:
112
- upper_len = i
113
- break
114
- else:
115
- reverse_lower = True
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
116
177
 
117
178
  upper_points = airfoil_points[:upper_len]
118
179
  lower_points = airfoil_points[upper_len:]
119
-
120
- if reverse_lower:
180
+
181
+ if lower_points and lower_points[0][0] == 0:
121
182
  lower_points = lower_points[::-1]
122
183
 
123
- assert len(upper_points) + len(lower_points) == n_points
124
-
125
- x_up, y_up = zip(*[points for points in upper_points])
126
- x_lo, y_lo = zip(*[points for points in lower_points])
127
-
128
- x = [*x_up, *x_lo]
129
- y = [*y_up, *y_lo]
184
+ x_up, y_up = zip(*upper_points) if upper_points else ([], [])
185
+ x_lo, y_lo = zip(*lower_points) if lower_points else ([], [])
130
186
 
131
- cloud_points = [(x[k], y[k], 0) for k in range(0, len(x))]
132
- # remove duplicated points
187
+ cloud_points = [(x, y, 0) for x, y in zip([*x_up, *x_lo], [*y_up, *y_lo])]
133
188
  return sorted(set(cloud_points), key=cloud_points.index)
134
189
 
135
190