Skip to content

Commit 2a62364

Browse files
mahgadallamtezzele
authored andcommitted
Plot with options, __str__, flip, minor fixes
1 parent aa01fc6 commit 2a62364

File tree

5 files changed

+217
-60
lines changed

5 files changed

+217
-60
lines changed

bladex/blade.py

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import matplotlib.pyplot as plt
66
from mpl_toolkits.mplot3d import Axes3D
77

8+
89
class Blade(object):
910
"""
1011
Bottom-up parametrized blade construction.
@@ -185,8 +186,8 @@ def _check_params(self):
185186
if not (self.sections.shape == self.radii.shape ==
186187
self.chord_lengths.shape == self.pitch.shape == self.rake.shape
187188
== self.skew_angles.shape):
188-
raise ValueError('Arrays {sections, radii, chord_lengths, pitch, \
189-
rake, skew_angles} do not have the same shape.')
189+
raise ValueError('Arrays {sections, radii, chord_lengths, pitch, '\
190+
'rake, skew_angles} do not have the same shape.')
190191

191192
def _compute_pitch_angle(self):
192193
"""
@@ -244,7 +245,7 @@ def _planar_to_cylindrical(self):
244245
[section.xdown_coordinates, y_section_down,
245246
z_section_down]))
246247

247-
def apply_transformations(self, flip=True):
248+
def apply_transformations(self, reflect=True):
248249
"""
249250
Generate a bottom-up constructed propeller blade based on the airfoil
250251
transformations, see :ref:`mytransformation_operations`.
@@ -254,7 +255,7 @@ def apply_transformations(self, flip=True):
254255
1. Translate airfoils by reference points into origin.
255256
256257
2. Scale X, Y coordinates by a factor of the chord length. Also
257-
flip the airfoils if necessary.
258+
reflect the airfoils if necessary.
258259
259260
3. Rotate the airfoils counter-clockwise according to the local
260261
pitch angles. Beware of the orientation system.
@@ -268,15 +269,15 @@ def apply_transformations(self, flip=True):
268269
each foil on a cylinder of radius equals to the section radius,
269270
and the cylinder axis is the propeller axis of rotation.
270271
271-
:param bool flip: if true, then flip the coordinates of all the airfoils
272+
:param bool reflect: if true, then reflect the coordinates of all the airfoils
272273
about both X-axis and Y-axis. Default value is True.
273274
"""
274275
for i in range(self.n_sections):
275276
# Translate reference point into origin
276277
self.sections[i].translate(-self.sections[i].reference_point)
277278

278-
if flip:
279-
self.sections[i].flip()
279+
if reflect:
280+
self.sections[i].reflect()
280281

281282
# Scale the unit chord to actual length.
282283
self.sections[i].scale(self.chord_lengths[i])
@@ -286,7 +287,8 @@ def apply_transformations(self, flip=True):
286287
# left-handed Cartesian orientation system, where Y-axis points
287288
# downwards and X-axis points to the right), the standard rotation
288289
# matrix yields clockwise rotation.
289-
self.sections[i].rotate(rad_angle=np.pi/2.0 - self.pitch_angles[i])
290+
self.sections[i].rotate(
291+
rad_angle=np.pi / 2.0 - self.pitch_angles[i])
290292

291293
# Translation due to skew.
292294
self.sections[i].translate(
@@ -404,8 +406,9 @@ def export_ppg(self,
404406
output_string += 'number of sectional profiles = ' + str(
405407
self.n_sections) + '\n'
406408
output_string += 'description of sectional profiles = BNF\n'
407-
output_string += ' r/R c/D skew[deg] \
408-
rake/D P/D t/C f/C\n'
409+
output_string += ' r/R c/D skew[deg]'\
410+
' rake/D P/D t/C'\
411+
' f/C\n'
409412
for i in range(self.n_sections):
410413
output_string += ' ' + str("%.8e" % self.radii[i]) + ' ' + str(
411414
"%.8e" % self.chord_lengths[i]) + ' ' + str(
@@ -451,3 +454,25 @@ def export_ppg(self,
451454
if params_normalized is False:
452455
# Revert back normalized parameters into actual values.
453456
self._norm_to_abs(D_prop=D_prop)
457+
458+
def __str__(self):
459+
"""
460+
This method prints all the parameters on the screen. Its purpose is
461+
for debugging.
462+
"""
463+
string = ''
464+
string += 'Blade number of sections = {}\n'.format(self.n_sections)
465+
string += '\nBlade radii sections = {}\n'.format(self.radii)
466+
string += '\nChord lengths of the blade sectional profiles'\
467+
' = {}\n'.format(self.chord_lengths)
468+
string += '\nRadial distribution of the blade pitch (in unit lengths)'\
469+
' = {}\n'.format(self.pitch)
470+
string += '\nRadial distribution of the blade rake (in unit length)'\
471+
' = {}\n'.format(self.rake)
472+
string += '\nRadial distribution of the blade skew angles'\
473+
' (in degrees) = {}\n'.format(self.skew_angles)
474+
string += '\nComputed pitch angles (in radians) for the blade'\
475+
' sections = {}\n'.format(self.pitch_angles)
476+
string += '\nComputed induced rake from skew (in unit length),'\
477+
' for the blade sections = {}\n'.format(self.induced_rake)
478+
return string

bladex/profilebase.py

Lines changed: 78 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,13 @@ def _update_edges(self):
5858
trailing edge, hence both the leading and the trailing edges are always
5959
unique.
6060
"""
61-
if self.xup_coordinates[0] != self.xdown_coordinates[0]:
62-
raise ValueError('Airfoils must have xup_coordinates[0] \
63-
== xdown_coordinates[0]')
64-
if self.xup_coordinates[-1] != self.xdown_coordinates[-1]:
65-
raise ValueError('Airfoils must have xup_coordinates[-1] \
66-
== xdown_coordinates[-1]')
61+
if np.fabs(self.xup_coordinates[0] - self.xdown_coordinates[0]) > 1e-4:
62+
raise ValueError('Airfoils must have xup_coordinates[0] '\
63+
'almost equal to xdown_coordinates[0]')
64+
if np.fabs(
65+
self.xup_coordinates[-1] - self.xdown_coordinates[-1]) > 1e-4:
66+
raise ValueError('Airfoils must have xup_coordinates[-1] '\
67+
'almost equal to xdown_coordinates[-1]')
6768

6869
self.leading_edge[0] = self.xup_coordinates[0]
6970
self.leading_edge[1] = self.yup_coordinates[0]
@@ -153,7 +154,7 @@ def compute_chord_line(self, n_interpolated_points=None):
153154
aratio = ((self.trailing_edge[1] - self.leading_edge[1]) /
154155
(self.trailing_edge[0] - self.leading_edge[0]))
155156
if not (self.xup_coordinates == self.xdown_coordinates
156-
).all() and n_interpolated_points is None:
157+
).all() and n_interpolated_points is None:
157158
# If x_up != x_down element-wise, then the corresponding y_up and
158159
# y_down can not be comparable, hence a uniform interpolation is
159160
# required. Also in case the interpolated_points is None,
@@ -196,7 +197,7 @@ def compute_camber_line(self, n_interpolated_points=None):
196197
inaccurate measurements for obtaining the camber line.
197198
"""
198199
if not (self.xup_coordinates == self.xdown_coordinates
199-
).all() and n_interpolated_points is None:
200+
).all() and n_interpolated_points is None:
200201
# If x_up != x_down element-wise, then the corresponding y_up and
201202
# y_down can not be comparable, hence a uniform interpolation is
202203
# required. Also in case the interpolated_points is None,
@@ -222,8 +223,8 @@ def deform_camber_line(self, percent_change, n_interpolated_points=None):
222223
The percentage of change is defined as follows:
223224
224225
.. math::
225-
\\frac{new magnitude of max camber - old magnitude of maximum \
226-
camber}{old magnitude of maximum camber} * 100
226+
\\frac{\\text{new magnitude of max camber - old magnitude of maximum \
227+
camber}}{\\text{old magnitude of maximum camber}} * 100
227228
228229
A positive percentage means the new camber is larger than the max
229230
camber value, while a negative percentage indicates the new value
@@ -253,7 +254,7 @@ def deform_camber_line(self, percent_change, n_interpolated_points=None):
253254
self.camber_line[1] *= scaling_factor
254255

255256
if not (self.xup_coordinates == self.xdown_coordinates
256-
).all() and n_interpolated_points is None:
257+
).all() and n_interpolated_points is None:
257258
# If x_up != x_down element-wise, then the corresponding y_up and
258259
# y_down can not be comparable, hence a uniform interpolation is
259260
# required. Also in case the interpolated_points is None,
@@ -265,7 +266,7 @@ def deform_camber_line(self, percent_change, n_interpolated_points=None):
265266
if n_interpolated_points:
266267
(self.xup_coordinates, self.xdown_coordinates, self.yup_coordinates,
267268
self.ydown_coordinates
268-
) = self.interpolate_coordinates(num=n_interpolated_points)
269+
) = self.interpolate_coordinates(num=n_interpolated_points)
269270

270271
half_thickness = 0.5 * np.fabs(
271272
self.yup_coordinates - self.ydown_coordinates)
@@ -276,7 +277,7 @@ def deform_camber_line(self, percent_change, n_interpolated_points=None):
276277
@property
277278
def reference_point(self):
278279
"""
279-
Return the coordinates of the airfoil's geometric center.
280+
Return the coordinates of the chord's mid point.
280281
281282
:return: reference point in 2D
282283
:rtype: numpy.ndarray
@@ -338,7 +339,7 @@ def max_thickness(self, n_interpolated_points=None):
338339
:rtype: float
339340
"""
340341
if not (self.xup_coordinates == self.xdown_coordinates
341-
).all() and n_interpolated_points is None:
342+
).all() and n_interpolated_points is None:
342343
# If x_up != x_down element-wise, then the corresponding y_up and
343344
# y_down can not be comparable, hence a uniform interpolation is
344345
# required. Also in case the interpolated_points is None,
@@ -408,15 +409,23 @@ def rotate(self, rad_angle=None, deg_angle=None):
408409
409410
.. math::
410411
\\left(\\begin{matrix} cos (\\theta) & - sin (\\theta) \\
412+
411413
sin (\\theta) & cos (\\theta) \\end{matrix}\\right)
412414
413-
Given the coordinates of point
414-
:math:`(P) = \\left(\\begin{matrix} x \\ y \\end{matrix}\\right)`,
415-
the rotated coordinates will be:
415+
Given the coordinates of point :math:`P` such that
416416
417417
.. math::
418-
P^{'} = \\left(\\begin{matrix} x^{'} \\ y^{'} \\end{matrix}\\right)
419-
= R (\\theta) \\cdot P`
418+
(P) = \\left(\\begin{matrix} x \\
419+
420+
y \\end{matrix}\\right),
421+
422+
Then, the rotated coordinates will be:
423+
424+
.. math::
425+
P^{'} = \\left(\\begin{matrix} x^{'} \\
426+
427+
y^{'} \\end{matrix}\\right)
428+
= R (\\theta) \\cdot P
420429
421430
If a standard right-handed Cartesian coordinate system is used, with
422431
the X-axis to the right and the Y-axis up, the rotation
@@ -431,8 +440,8 @@ def rotate(self, rad_angle=None, deg_angle=None):
431440
"""
432441
if rad_angle is not None and deg_angle is not None:
433442
raise ValueError(
434-
'You have to pass either the angle in radians or in degrees, \
435-
not both.')
443+
'You have to pass either the angle in radians or in degrees,' \
444+
' not both.')
436445
if rad_angle:
437446
cosine = np.cos(rad_angle)
438447
sine = np.sin(rad_angle)
@@ -475,9 +484,13 @@ def translate(self, translation):
475484
self.yup_coordinates += translation[1]
476485
self.ydown_coordinates += translation[1]
477486

478-
def flip(self):
487+
def reflect(self):
479488
"""
480-
Flip the airfoil coordinates about both the X-axis and the Y-axis.
489+
Reflect the airfoil coordinates about the origin, i.e. a mirror
490+
transformation is performed about both the X-axis and the Y-axis.
491+
492+
We note that if the foil is cenetered by its reference point at the
493+
origin, then the reflection is just a simple flip.
481494
"""
482495
self.xup_coordinates *= -1
483496
self.xdown_coordinates *= -1
@@ -504,21 +517,59 @@ def scale(self, factor):
504517
self.ydown_coordinates *= factor
505518
self.translate(ref_point)
506519

507-
def plot(self, outfile=None):
520+
def plot(self,
521+
profile=True,
522+
chord_line=False,
523+
camber_line=False,
524+
ref_point=False,
525+
outfile=None):
508526
"""
509527
Plot the airfoil coordinates.
510528
511-
:param string outfile: outfile name. If a string is provided then the
529+
:param bool profile: if True, then plot the profile coordinates.
530+
Default value is True
531+
:param bool chord_line: if True, then plot the chord line. Default
532+
value is False
533+
:param bool camber_line: if True, then plot the camber line. Default
534+
value is False
535+
:param bool ref_point: if True, then scatter plot the reference point.
536+
Default value is False
537+
:param str outfile: outfile name. If a string is provided then the
512538
plot is saved with that name, otherwise the plot is not saved.
513539
Default value is None
514540
"""
515541
plt.figure()
516-
plt.plot(self.xup_coordinates, self.yup_coordinates)
517-
plt.plot(self.xdown_coordinates, self.ydown_coordinates)
542+
543+
if (self.xup_coordinates is None or self.yup_coordinates is None
544+
or self.xdown_coordinates is None
545+
or self.ydown_coordinates is None):
546+
raise ValueError('One or all the coordinates have None value.')
547+
548+
if profile:
549+
plt.plot(self.xup_coordinates, self.yup_coordinates)
550+
plt.plot(self.xdown_coordinates, self.ydown_coordinates)
551+
552+
if chord_line:
553+
if self.chord_line is None:
554+
raise ValueError(
555+
'Chord line is None. You must compute it first')
556+
plt.plot(self.chord_line[0], self.chord_line[1])
557+
558+
if camber_line:
559+
if self.camber_line is None:
560+
raise ValueError(
561+
'Camber line is None. You must compute it first')
562+
plt.plot(self.camber_line[0], self.camber_line[1])
563+
564+
if ref_point:
565+
plt.scatter(self.reference_point[0], self.reference_point[1])
566+
518567
plt.grid(linestyle='dotted')
519568
plt.axis('equal')
520569

521570
if outfile:
522571
if not isinstance(outfile, str):
523572
raise ValueError('Output file name must be string.')
524573
plt.savefig(outfile)
574+
else:
575+
plt.show()

bladex/profiles.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ class NacaProfile(ProfileBase):
145145
- Abbott, Ira (1959). Theory of Wing Sections: Including a Summary of
146146
Airfoil Data. New York: Dover Publications. p. 115. ISBN 978-0486605869.
147147
148-
:param string digits: 4 or 5 digits that describes the NACA profile
148+
:param str digits: 4 or 5 digits that describes the NACA profile
149149
:param int n_points: number of discrete points that represents the
150150
airfoil profile. Default value is 240
151151
:param bool cosine_spacing: if True, then a cosine spacing is used for the
@@ -205,8 +205,7 @@ def _generate_coordinates(self):
205205

206206
if p == 0:
207207
# Symmetric foil
208-
self.xup_coordinates = np.linspace(
209-
0.0, 1.0, num=self.n_points)
208+
self.xup_coordinates = np.linspace(0.0, 1.0, num=self.n_points)
210209
self.yup_coordinates = yt
211210
self.xdown_coordinates = np.linspace(
212211
0.0, 1.0, num=self.n_points)
@@ -267,8 +266,7 @@ def _generate_coordinates(self):
267266

268267
if p == 0:
269268
# Symmetric foil
270-
self.xup_coordinates = np.linspace(
271-
0.0, 1.0, num=self.n_points)
269+
self.xup_coordinates = np.linspace(0.0, 1.0, num=self.n_points)
272270
self.yup_coordinates = yt
273271
self.xdown_coordinates = np.linspace(
274272
0.0, 1.0, num=self.n_points)

0 commit comments

Comments
 (0)