Skip to content

Commit de92e45

Browse files
authored
Set parametrization in sections (#95)
* set parametrized sections * improve CustomProfile
1 parent 28e5ec1 commit de92e45

File tree

3 files changed

+147
-117
lines changed

3 files changed

+147
-117
lines changed

bladex/blade.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,24 +1031,22 @@ def generate_stl_blade(self, filename):
10311031
"""
10321032
from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_Sewing
10331033
from OCC.Extend.DataExchange import write_stl_file
1034-
1035-
self.apply_transformations(reflect=True)
1036-
1034+
10371035
self._generate_upper_face(max_deg=1)
10381036
self._generate_lower_face(max_deg=1)
10391037
self._generate_root(max_deg=1)
10401038
self._generate_tip(max_deg=1)
1041-
1039+
10421040
sewer = BRepBuilderAPI_Sewing(1e-2)
1043-
sewer.Add(self.generated_upper_face)
1044-
sewer.Add(self.generated_lower_face)
1041+
sewer.Add(self.generated_upper_face)
1042+
sewer.Add(self.generated_lower_face)
10451043
sewer.Add(self.generated_root)
1046-
sewer.Add(self.generated_tip)
1044+
sewer.Add(self.generated_tip)
10471045
sewer.Perform()
10481046
self.sewed_full = sewer.SewedShape()
1049-
1050-
write_stl_file(self.sewed_full, filename)
1051-
1047+
1048+
write_stl_file(self.sewed_full, filename)
1049+
10521050
def generate_iges_blade(self, filename):
10531051
"""
10541052
Generate and export the .IGES file for the entire blade.

bladex/customprofile.py

Lines changed: 115 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class CustomProfile(ProfileBase):
1717
i.e. , chord percentages and length, nondimensional and maximum camber,
1818
nondimensional and maximum thickness.
1919
20-
If coordinates are direclty given as input:
20+
If coordinates are directly given as input:
2121
2222
:param numpy.ndarray xup: 1D array that contains the X-components of the
2323
airfoil's upper surface
@@ -27,6 +27,7 @@ class CustomProfile(ProfileBase):
2727
airfoil's upper surface
2828
:param numpy.ndarray ydown: 1D array that contains the Y-components of the
2929
airfoil's lower surface
30+
:param float chord_len: the chord length of the airfoil section
3031
3132
If section parameters are given as input:
3233
@@ -38,74 +39,76 @@ class CustomProfile(ProfileBase):
3839
:param numpy.ndarray thickness_perc: 1D array that contains the thickness
3940
percentage of an airfoil section at all considered chord percentages.
4041
The percentage is with respect to the section maximum thickness
41-
:param numpy.ndarray chord_len: 1-element array expressing the length of the chord
42-
line of a certain airfoil section
43-
:param numpy.ndarray camber_max: 1-element array expressing the maximum camber at a
44-
certain airfoil section
45-
:param numpy.ndarray thickness_max: 1-element array expressing the maximum thickness
46-
at a certain airfoil section
42+
:param float chord_len: the length of the chord line of a certain airfoil section
43+
:param float camber_max: the maximum camber at a certain airfoil section
44+
:param float thickness_max: the maximum thickness at a certain airfoil section
4745
"""
48-
4946
def __init__(self, **kwargs):
5047
super(CustomProfile, self).__init__()
51-
52-
if len(kwargs) == 4:
53-
xup, yup, xdown, ydown = kwargs.values()
54-
self.xup_coordinates = xup
55-
self.yup_coordinates = yup
56-
self.xdown_coordinates = xdown
57-
self.ydown_coordinates = ydown
58-
59-
if len(kwargs) == 6:
60-
chord_perc, camber_perc, thickness_perc, chord_len, camber_max, thickness_max = kwargs.values()
61-
self.chord_percentage = chord_perc
62-
self.camber_percentage = camber_perc
63-
self.thickness_percentage = thickness_perc
64-
self.chord_len = chord_len
65-
self.camber_max = camber_max
66-
self.thickness_max = thickness_max
67-
68-
self._check_args()
48+
49+
if all([key in ['xup', 'yup', 'xdown', 'ydown'] for key in kwargs]):
50+
self.xup_coordinates = kwargs['xup']
51+
self.yup_coordinates = kwargs['yup']
52+
self.xdown_coordinates = kwargs['xdown']
53+
self.ydown_coordinates = kwargs['ydown']
54+
self.chord_len = kwargs.get('chord_len', 1)
55+
56+
self._generate_parameters()
57+
58+
elif set(kwargs.keys()) == set([
59+
'chord_perc', 'camber_perc', 'thickness_perc', 'chord_len',
60+
'camber_max', 'thickness_max'
61+
]):
62+
63+
self.chord_percentage = kwargs['chord_perc']
64+
self.camber_percentage = kwargs['camber_perc']
65+
self.thickness_percentage = kwargs['thickness_perc']
66+
self.chord_len = kwargs['chord_len']
67+
self.camber_max = kwargs['camber_max']
68+
self.thickness_max = kwargs['thickness_max']
69+
6970
self._generate_coordinates()
70-
71+
else:
72+
raise RuntimeError(
73+
"""Input arguments should be the section coordinates
74+
(xup, yup, xdown, ydown) and chord_len (optional)
75+
or the section parameters (camber_perc, thickness_perc,
76+
camber_max, thickness_max, chord_perc, chord_len).""")
77+
78+
self._check_args()
7179
self._check_coordinates()
72-
73-
80+
7481
def _check_args(self):
7582
"""
7683
Private method that checks whether the airfoil parameters defined
7784
are provided correctly.
7885
In particular, the chord, camber and thickness percentages are consistent and
7986
have the same length.
8087
"""
81-
82-
88+
8389
if self.chord_percentage is None:
84-
raise ValueError(
85-
'object "chord_perc" refers to an empty array.')
90+
raise ValueError('object "chord_perc" refers to an empty array.')
8691
if self.camber_percentage is None:
87-
raise ValueError(
88-
'object "camber_perc" refers to an empty array.')
92+
raise ValueError('object "camber_perc" refers to an empty array.')
8993
if self.thickness_percentage is None:
9094
raise ValueError(
9195
'object "thickness_perc" refers to an empty array.')
9296
if self.chord_len is None:
93-
raise ValueError(
94-
'object "chorf_len" refers to an empty array.')
97+
raise ValueError('object "chorf_len" refers to an empty array.')
9598
if self.camber_max is None:
96-
raise ValueError(
97-
'object "camber_max" refers to an empty array.')
99+
raise ValueError('object "camber_max" refers to an empty array.')
98100
if self.thickness_max is None:
99-
raise ValueError(
100-
'object "thickness_max" refers to an empty array.')
101-
101+
raise ValueError('object "thickness_max" refers to an empty array.')
102102

103103
if not isinstance(self.chord_percentage, np.ndarray):
104-
self.chord_percentage = np.asarray(self.chord_percentage, dtype=float)
104+
self.chord_percentage = np.asarray(self.chord_percentage,
105+
dtype=float)
105106
if not isinstance(self.camber_percentage, np.ndarray):
106-
self.camber_percentage = np.asarray(self.camber_percentage, dtype=float)
107+
self.camber_percentage = np.asarray(self.camber_percentage,
108+
dtype=float)
107109
if not isinstance(self.thickness_percentage, np.ndarray):
108-
self.thickness_percentage = np.asarray(self.thickness_percentage, dtype=float)
110+
self.thickness_percentage = np.asarray(self.thickness_percentage,
111+
dtype=float)
109112
if not isinstance(self.chord_len, np.ndarray):
110113
self.chord_len = np.asarray(self.chord_len, dtype=float)
111114
if not isinstance(self.camber_max, np.ndarray):
@@ -120,47 +123,81 @@ def _check_args(self):
120123
raise ValueError('thickness_max must be positive.')
121124

122125
# Therefore the arrays camber_percentage and thickness_percentage
123-
# should have the same length of chord_percentage, equal to n_pos,
126+
# should have the same length of chord_percentage, equal to n_pos,
124127
# which is the number of cuts along the chord line
125128
if self.camber_percentage.shape != self.chord_percentage.shape:
126-
raise ValueError(
127-
'camber_perc and chord_perc must have same shape.')
129+
raise ValueError('camber_perc and chord_perc must have same shape.')
128130
if self.thickness_percentage.shape != self.chord_percentage.shape:
129131
raise ValueError(
130132
'thickness_perc and chord_perc must have same shape.')
131-
132-
133+
133134
def _generate_coordinates(self):
134135
"""
135136
Private method that generates the coordinates of a general airfoil profile,
136137
starting from the chord percantages and the related nondimensional
137138
camber and thickness. input data should be integrated with the information
138139
of chord length, camber and thickness of specific sections.
139140
"""
140-
141+
141142
# compute the angular coefficient of the camber line at each chord
142143
# percentage and convert it from degrees to radiant
143144
n_pos = len(self.chord_percentage)
144145
m = np.zeros(n_pos)
145146
m[0] = 0
146-
for i in range(1,len(self.chord_percentage),1):
147-
m[i] = (self.camber_percentage[i]-self.camber_percentage[i-1])/(self.chord_percentage[i]-self.chord_percentage[i-1])
148-
149-
m_angle = m*np.pi/180
147+
for i in range(1, len(self.chord_percentage), 1):
148+
m[i] = (self.camber_percentage[i] -
149+
self.camber_percentage[i - 1]) / (
150+
self.chord_percentage[i] - self.chord_percentage[i - 1])
151+
152+
m_angle = m * np.pi / 180
150153
self.xup_coordinates = np.zeros(n_pos)
151154
self.xdown_coordinates = np.zeros(n_pos)
152155
self.yup_coordinates = np.zeros(n_pos)
153156
self.ydown_coordinates = np.zeros(n_pos)
154-
157+
155158
#compute the coordinates starting from a single section data and parameters
156-
for j in range(0,n_pos,1):
157-
self.xup_coordinates[j] = (self.chord_percentage[j]-self.thickness_percentage[j]*np.sin(m_angle[j])*self.thickness_max)
158-
self.xdown_coordinates[j] = (self.chord_percentage[j]+self.thickness_percentage[j]*np.sin(m_angle[j])*self.thickness_max)
159-
self.yup_coordinates[j] = (self.camber_percentage[j]*self.camber_max+self.thickness_percentage[j]*self.thickness_max*np.cos(m_angle[j]))*self.chord_len
160-
self.ydown_coordinates[j] = (self.camber_percentage[j]*self.camber_max-self.thickness_percentage[j]*self.thickness_max*np.cos(m_angle[j]))*self.chord_len
161-
162-
163-
159+
for j in range(0, n_pos, 1):
160+
self.xup_coordinates[j] = self.chord_percentage[j]
161+
self.xdown_coordinates[j] = self.chord_percentage[j]
162+
self.yup_coordinates[j] = (
163+
self.camber_percentage[j] * self.camber_max +
164+
self.thickness_percentage[j] * self.thickness_max *
165+
np.cos(m_angle[j])) * self.chord_len
166+
self.ydown_coordinates[j] = (
167+
self.camber_percentage[j] * self.camber_max -
168+
self.thickness_percentage[j] * self.thickness_max *
169+
np.cos(m_angle[j])) * self.chord_len
170+
171+
def _generate_parameters(self):
172+
'''
173+
Private method to find parameters related to each section
174+
(chord percentages, camber max, camber percentages,
175+
thickness max and thickness percentage) starting from the
176+
xup, yup, xdown, ydown coordinates of the section.
177+
Useful for parametrization and deformation.
178+
'''
179+
self.chord_percentage = self.xup_coordinates
180+
181+
camber = (self.yup_coordinates +
182+
self.ydown_coordinates) / (2 * self.chord_len)
183+
self.camber_max = np.max(camber)
184+
self.camber_percentage = camber / self.camber_max
185+
186+
delta_camber = np.zeros(len(camber))
187+
delta_x = np.zeros(len(camber))
188+
m_rad = np.zeros(len(camber))
189+
for i in range(1, len(camber), 1):
190+
delta_camber[
191+
i] = self.camber_percentage[i] - self.camber_percentage[i - 1]
192+
delta_x[i] = self.chord_percentage[i] - self.chord_percentage[i - 1]
193+
194+
m_rad[1:] = delta_camber[1:] * np.pi / (delta_x[1:] * 180)
195+
196+
thickness = (self.yup_coordinates - self.ydown_coordinates) / (
197+
2 * self.chord_len * np.cos(m_rad))
198+
self.thickness_max = np.max(thickness)
199+
self.thickness_percentage = thickness / self.thickness_max
200+
164201
def _check_coordinates(self):
165202
"""
166203
Private method that checks whether the airfoil coordinates defined (or
@@ -183,53 +220,42 @@ def _check_coordinates(self):
183220
or xdown[-1] != xup[-1]
184221
"""
185222
if self.xup_coordinates is None:
186-
raise ValueError(
187-
'object "xup" refers to an empty array.')
223+
raise ValueError('object "xup" refers to an empty array.')
188224
if self.xdown_coordinates is None:
189-
raise ValueError(
190-
'object "xdown" refers to an empty array.')
225+
raise ValueError('object "xdown" refers to an empty array.')
191226
if self.yup_coordinates is None:
192-
raise ValueError(
193-
'object "yup" refers to an empty array.')
227+
raise ValueError('object "yup" refers to an empty array.')
194228
if self.ydown_coordinates is None:
195-
raise ValueError(
196-
'object "ydown" refers to an empty array.')
229+
raise ValueError('object "ydown" refers to an empty array.')
197230

198231
if not isinstance(self.xup_coordinates, np.ndarray):
199232
self.xup_coordinates = np.asarray(self.xup_coordinates, dtype=float)
200233
if not isinstance(self.xdown_coordinates, np.ndarray):
201-
self.xdown_coordinates = np.asarray(
202-
self.xdown_coordinates, dtype=float)
234+
self.xdown_coordinates = np.asarray(self.xdown_coordinates,
235+
dtype=float)
203236
if not isinstance(self.yup_coordinates, np.ndarray):
204237
self.yup_coordinates = np.asarray(self.yup_coordinates, dtype=float)
205238
if not isinstance(self.ydown_coordinates, np.ndarray):
206-
self.ydown_coordinates = np.asarray(
207-
self.ydown_coordinates, dtype=float)
239+
self.ydown_coordinates = np.asarray(self.ydown_coordinates,
240+
dtype=float)
208241

209242
# Therefore the arrays xup_coordinates and yup_coordinates must have
210243
# the same length = N, same holds for the arrays xdown_coordinates
211244
# and ydown_coordinates.
212245
if self.xup_coordinates.shape != self.yup_coordinates.shape:
213-
raise ValueError(
214-
'xup and yup must have same shape.')
246+
raise ValueError('xup and yup must have same shape.')
215247
if self.xdown_coordinates.shape != self.ydown_coordinates.shape:
216-
raise ValueError(
217-
'xdown and ydown must have same shape.')
248+
raise ValueError('xdown and ydown must have same shape.')
218249

219250
# The condition yup_coordinates >= ydown_coordinates must be satisfied
220251
# element-wise to the whole elements in the mentioned arrays.
221252
if not all(
222253
np.greater_equal(self.yup_coordinates, self.ydown_coordinates)):
223-
raise ValueError(
224-
'yup is not >= ydown elementwise.')
254+
raise ValueError('yup is not >= ydown elementwise.')
225255

226256
if not self.xdown_coordinates[0] == self.xup_coordinates[0]:
227-
raise ValueError(
228-
'(xdown[0]=xup[0]) not satisfied.')
257+
raise ValueError('(xdown[0]=xup[0]) not satisfied.')
229258
if not self.ydown_coordinates[0] == self.yup_coordinates[0]:
230-
raise ValueError(
231-
'(ydown[0]=yup[0]) not satisfied.')
259+
raise ValueError('(ydown[0]=yup[0]) not satisfied.')
232260
if not self.xdown_coordinates[-1] == self.xup_coordinates[-1]:
233-
raise ValueError(
234-
'(xdown[-1]=xup[-1]) not satisfied.')
235-
261+
raise ValueError('(xdown[-1]=xup[-1]) not satisfied.')

0 commit comments

Comments
 (0)