Skip to content

Commit 59c7a2b

Browse files
authored
Merge pull request #27 from mahgadalla/dev
Add method generate_iges inside Blade class
2 parents af10fd1 + e394a50 commit 59c7a2b

File tree

8 files changed

+407
-3
lines changed

8 files changed

+407
-3
lines changed

.travis.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ install:
6868
fi
6969
- source activate test
7070
- conda install --yes numpy scipy matplotlib pip nose sip=4.18
71+
- if [[ "$TOXENV" == "py27" ]]; then
72+
conda install --yes -c conda-forge -c dlr-sc -c pythonocc -c oce pythonocc-core=0.18.1 python=2.7;
73+
else
74+
conda install --yes -c conda-forge -c dlr-sc -c pythonocc -c oce pythonocc-core=0.18.1 python=3.6;
75+
fi
7176
- pip install setuptools
7277
- pip install enum34
7378
- pip install coveralls

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,13 @@
4040
See the [**Examples**](#examples) section below and the [**Tutorials**](tutorials/README.md) to have an idea of the potential of this package.
4141

4242
## Dependencies and installation
43-
**BladeX** requires requires `numpy`, `scipy`, `matplotlib`, and `sphinx` (for the documentation). The code is compatible with Python 2.7 and Python 3.6. It can be installed using `pip` or directly from the source code.
43+
**BladeX** requires `numpy`, `scipy`, `matplotlib`, `sphinx` (for the documentation), and `nose` (for the local test). They can be easily installed using `pip`.
44+
**BladeX** is compatible with Python 2.7 and Python 3.6. Moreover, some of the modules require `OCC` to be installed for the `.iges` or `.stl` CAD generation. Please see the table below for instructions on how to satisfy the `OCC` requirements. You can also refer to `pythonocc.org` or `github.com/tpaviot/pythonocc-core` for further instructions.
45+
46+
| Package | Version | How to install (precompiled binaries via conda) |
47+
|---------|-------------|----------------------------------------------------------------------------------------------------------|
48+
| OCC | ==0.18.1 | Python2.7 `conda install -c conda-forge -c dlr-sc -c pythonocc -c oce pythonocc-core==0.18.1 python=2.7` |
49+
| OCC | ==0.18.1 | Python3.6 `conda install -c conda-forge -c dlr-sc -c pythonocc -c oce pythonocc-core==0.18.1 python=3.6` |
4450

4551

4652
### Installing from source

bladex/blade.py

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,9 +344,275 @@ def plot(self, elev=None, azim=None, outfile=None):
344344
if outfile:
345345
plt.savefig(outfile)
346346

347+
def generate_iges(self,
348+
upper_face=None,
349+
lower_face=None,
350+
tip=None,
351+
display=False,
352+
errors=None):
353+
"""
354+
Generate and export the .iges CAD for the blade upper face, lower face,
355+
and tip. This method requires PythonOCC to be installed.
356+
357+
:param string upper_face: if string is passed then the method generates
358+
the blade upper surface using the BRepOffsetAPI_ThruSections
359+
algorithm, then exports the generated CAD into .iges file holding
360+
the name <upper_face_string>.iges. Default value is None
361+
:param string lower_face: if string is passed then the method generates
362+
the blade lower surface using the BRepOffsetAPI_ThruSections
363+
algorithm, then exports the generated CAD into .iges file holding
364+
the name <lower_face_string>.iges. Default value is None
365+
:param string tip: if string is passed then the method generates
366+
the blade tip using the BRepOffsetAPI_ThruSections algorithm
367+
in order to close the blade, then exports the generated CAD into
368+
.iges file holding the name <tip_string>.iges. Default value is None
369+
:param bool display: if True, then display the generated CAD. Default
370+
value is False
371+
:param string errors: if string is passed then the method writes out
372+
the distances between each discrete point used to construct the
373+
blade and the nearest point on the CAD that is perpendicular to
374+
that point. Default value is None
375+
376+
We note that the blade object must have its radial sections be arranged
377+
in order from the blade root to the blade tip, so that generate_iges
378+
method can build the CAD surface that passes through the corresponding
379+
airfoils. Also to be able to identify and close the blade tip.
380+
"""
381+
382+
from OCC.BRepBuilderAPI import BRepBuilderAPI_MakeEdge,\
383+
BRepBuilderAPI_MakeWire, BRepBuilderAPI_MakeVertex
384+
from OCC.Display.SimpleGui import init_display
385+
from OCC.gp import gp_Pnt
386+
from OCC.TColgp import TColgp_HArray1OfPnt
387+
from OCC.GeomAPI import GeomAPI_Interpolate
388+
from OCC.IGESControl import IGESControl_Writer
389+
from OCC.BRepOffsetAPI import BRepOffsetAPI_ThruSections
390+
from OCC.BRepExtrema import BRepExtrema_DistShapeShape
391+
392+
if upper_face:
393+
self._check_string(filename=upper_face)
394+
# Initializes ThruSections algorithm for building a shell passing
395+
# through a set of sections (wires). The generated faces between
396+
# the edges of every two consecutive wires are smoothed out with
397+
# a precision criterion = 1e-10
398+
generator = BRepOffsetAPI_ThruSections(False, False, 1e-10)
399+
# Define upper edges (wires) for the face generation
400+
for i in range(self.n_sections):
401+
npoints = len(self.blade_coordinates_up[i][0])
402+
vertices = TColgp_HArray1OfPnt(1, npoints)
403+
for j in range(npoints):
404+
vertices.SetValue(
405+
j + 1,
406+
gp_Pnt(1000 * self.blade_coordinates_up[i][0][j],
407+
1000 * self.blade_coordinates_up[i][1][j],
408+
1000 * self.blade_coordinates_up[i][2][j]))
409+
# Initializes an algorithm for constructing a constrained
410+
# BSpline curve passing through the points of the blade i-th
411+
# section, with tolerance = 1e-9
412+
bspline = GeomAPI_Interpolate(vertices.GetHandle(), False, 1e-9)
413+
bspline.Perform()
414+
edge = BRepBuilderAPI_MakeEdge(bspline.Curve()).Edge()
415+
if i == 0:
416+
bound_root_edge = edge
417+
# Add BSpline wire to the generator constructor
418+
generator.AddWire(BRepBuilderAPI_MakeWire(edge).Wire())
419+
# Returns the shape built by the shape construction algorithm
420+
generator.Build()
421+
# Returns the Face generated by each edge of the first section
422+
generated_upper_face = generator.GeneratedFace(bound_root_edge)
423+
424+
# Write IGES
425+
iges_writer = IGESControl_Writer()
426+
iges_writer.AddShape(generated_upper_face)
427+
iges_writer.Write(upper_face + '.iges')
428+
429+
if lower_face:
430+
self._check_string(filename=lower_face)
431+
# Initializes ThruSections algorithm for building a shell passing
432+
# through a set of sections (wires). The generated faces between
433+
# the edges of every two consecutive wires are smoothed out with
434+
# a precision criterion = 1e-10
435+
generator = BRepOffsetAPI_ThruSections(False, False, 1e-10)
436+
# Define upper edges (wires) for the face generation
437+
for i in range(self.n_sections):
438+
npoints = len(self.blade_coordinates_down[i][0])
439+
vertices = TColgp_HArray1OfPnt(1, npoints)
440+
for j in range(npoints):
441+
vertices.SetValue(
442+
j + 1,
443+
gp_Pnt(1000 * self.blade_coordinates_down[i][0][j],
444+
1000 * self.blade_coordinates_down[i][1][j],
445+
1000 * self.blade_coordinates_down[i][2][j]))
446+
# Initializes an algorithm for constructing a constrained
447+
# BSpline curve passing through the points of the blade i-th
448+
# section, with tolerance = 1e-9
449+
bspline = GeomAPI_Interpolate(vertices.GetHandle(), False, 1e-9)
450+
bspline.Perform()
451+
edge = BRepBuilderAPI_MakeEdge(bspline.Curve()).Edge()
452+
if i == 0:
453+
bound_root_edge = edge
454+
# Add BSpline wire to the generator constructor
455+
generator.AddWire(BRepBuilderAPI_MakeWire(edge).Wire())
456+
# Returns the shape built by the shape construction algorithm
457+
generator.Build()
458+
# Returns the Face generated by each edge of the first section
459+
generated_lower_face = generator.GeneratedFace(bound_root_edge)
460+
461+
# Write IGES
462+
iges_writer = IGESControl_Writer()
463+
iges_writer.AddShape(generated_lower_face)
464+
iges_writer.Write(lower_face + '.iges')
465+
466+
if tip:
467+
self._check_string(filename=tip)
468+
generator = BRepOffsetAPI_ThruSections(False, False, 1e-10)
469+
generator.SetMaxDegree(1)
470+
471+
# npoints_up == npoints_down
472+
npoints = len(self.blade_coordinates_down[-1][0])
473+
vertices_1 = TColgp_HArray1OfPnt(1, npoints)
474+
vertices_2 = TColgp_HArray1OfPnt(1, npoints)
475+
for j in range(npoints):
476+
vertices_1.SetValue(
477+
j + 1,
478+
gp_Pnt(1000 * self.blade_coordinates_down[-1][0][j],
479+
1000 * self.blade_coordinates_down[-1][1][j],
480+
1000 * self.blade_coordinates_down[-1][2][j]))
481+
482+
vertices_2.SetValue(
483+
j + 1,
484+
gp_Pnt(1000 * self.blade_coordinates_up[-1][0][j],
485+
1000 * self.blade_coordinates_up[-1][1][j],
486+
1000 * self.blade_coordinates_up[-1][2][j]))
487+
488+
# Initializes an algorithm for constructing a constrained
489+
# BSpline curve passing through the points of the blade last
490+
# section, with tolerance = 1e-9
491+
bspline_1 = GeomAPI_Interpolate(vertices_1.GetHandle(), False, 1e-9)
492+
bspline_1.Perform()
493+
494+
bspline_2 = GeomAPI_Interpolate(vertices_2.GetHandle(), False, 1e-9)
495+
bspline_2.Perform()
496+
497+
edge_1 = BRepBuilderAPI_MakeEdge(bspline_1.Curve()).Edge()
498+
edge_2 = BRepBuilderAPI_MakeEdge(bspline_2.Curve()).Edge()
499+
500+
# Add BSpline wire to the generator constructor
501+
generator.AddWire(BRepBuilderAPI_MakeWire(edge_1).Wire())
502+
generator.AddWire(BRepBuilderAPI_MakeWire(edge_2).Wire())
503+
# Returns the shape built by the shape construction algorithm
504+
generator.Build()
505+
# Returns the Face generated by each edge of the first section
506+
generated_tip = generator.GeneratedFace(edge_1)
507+
508+
iges_writer = IGESControl_Writer()
509+
iges_writer.AddShape(generated_tip)
510+
iges_writer.Write(tip + '.iges')
511+
512+
if errors:
513+
# Write out errors between discrete points and constructed faces
514+
self._check_string(filename=errors)
515+
self._check_errors(upper_face=upper_face, lower_face=lower_face)
516+
517+
output_string = '\n'
518+
with open(errors + '.txt', 'w') as f:
519+
if upper_face:
520+
output_string += '########## UPPER FACE ##########\n\n'
521+
output_string += 'N_section\t\tN_point\t\t\tX_crds\t\t\t\t'
522+
output_string += 'Y_crds\t\t\t\t\tZ_crds\t\t\t\t\tDISTANCE'
523+
output_string += '\n\n'
524+
for i in range(self.n_sections):
525+
alength = len(self.blade_coordinates_up[i][0])
526+
for j in range(alength):
527+
vertex = BRepBuilderAPI_MakeVertex(
528+
gp_Pnt(
529+
1000 * self.blade_coordinates_up[i][0][j],
530+
1000 * self.blade_coordinates_up[i][1][j],
531+
1000 * self.blade_coordinates_up[i][2][
532+
j])).Vertex()
533+
projection = BRepExtrema_DistShapeShape(
534+
generated_upper_face, vertex)
535+
projection.Perform()
536+
output_string += str(
537+
i) + '\t\t\t' + str(j) + '\t\t\t' + str(
538+
1000 * self.blade_coordinates_up[i][0]
539+
[j]) + '\t\t\t'
540+
output_string += str(
541+
1000 * self.blade_coordinates_up[i]
542+
[1][j]) + '\t\t\t' + str(
543+
1000 * self.blade_coordinates_up[i][2]
544+
[j]) + '\t\t\t' + str(projection.Value())
545+
output_string += '\n'
546+
547+
if lower_face:
548+
output_string += '########## LOWER FACE ##########\n\n'
549+
output_string += 'N_section\t\tN_point\t\t\tX_crds\t\t\t\t'
550+
output_string += 'Y_crds\t\t\t\t\tZ_crds\t\t\t\t\tDISTANCE'
551+
output_string += '\n\n'
552+
for i in range(self.n_sections):
553+
alength = len(self.blade_coordinates_down[i][0])
554+
for j in range(alength):
555+
vertex = BRepBuilderAPI_MakeVertex(
556+
gp_Pnt(
557+
1000 * self.blade_coordinates_down[i][0][j],
558+
1000 * self.blade_coordinates_down[i][1][j],
559+
1000 * self.blade_coordinates_down[i][2][
560+
j])).Vertex()
561+
projection = BRepExtrema_DistShapeShape(
562+
generated_lower_face, vertex)
563+
projection.Perform()
564+
output_string += str(
565+
i) + '\t\t\t' + str(j) + '\t\t\t' + str(
566+
1000 * self.blade_coordinates_down[i][0]
567+
[j]) + '\t\t\t'
568+
output_string += str(
569+
1000 * self.blade_coordinates_down[i]
570+
[1][j]) + '\t\t\t' + str(
571+
1000 * self.blade_coordinates_down[i][2]
572+
[j]) + '\t\t\t' + str(projection.Value())
573+
output_string += '\n'
574+
f.write(output_string)
575+
576+
if display:
577+
display, start_display, add_menu, add_function_to_menu = init_display(
578+
)
579+
580+
## DISPLAY FACES
581+
if upper_face:
582+
display.DisplayShape(generated_upper_face, update=True)
583+
if lower_face:
584+
display.DisplayShape(generated_lower_face, update=True)
585+
if tip:
586+
display.DisplayShape(generated_tip, update=True)
587+
start_display()
588+
589+
@staticmethod
590+
def _check_string(filename):
591+
"""
592+
Private method to check if the parameter type is string
593+
594+
:param string filename: filename of the generated .iges surface
595+
"""
596+
if not isinstance(filename, str):
597+
raise TypeError('IGES filename must be a valid string.')
598+
599+
@staticmethod
600+
def _check_errors(upper_face, lower_face):
601+
"""
602+
Private method to check if either the blade upper face or lower face
603+
is passed in the generate_iges method. Otherwise it raises an exception
604+
605+
:param string upper_face: blade upper face.
606+
:param string lower_face: blade lower face.
607+
"""
608+
if not (upper_face or lower_face):
609+
raise ValueError('Either upper_face or lower_face must not be None.')
610+
347611
def _abs_to_norm(self, D_prop):
348612
"""
349613
Private method to normalize the blade parameters.
614+
615+
:param float D_prop: propeller diameter
350616
"""
351617
self.radii = self.radii * 2. / D_prop
352618
self.chord_lengths = self.chord_lengths / D_prop
@@ -357,6 +623,8 @@ def _norm_to_abs(self, D_prop):
357623
"""
358624
Private method that converts the normalized blade parameters into the
359625
actual values.
626+
627+
:param float D_prop: propeller diameter
360628
"""
361629
self.radii = self.radii * D_prop / 2.
362630
self.chord_lengths = self.chord_lengths * D_prop
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
bladex.blade.Blade._check_errors
2+
================================
3+
4+
.. currentmodule:: bladex.blade
5+
6+
.. automethod:: Blade._check_errors
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
bladex.blade.Blade._check_string
2+
================================
3+
4+
.. currentmodule:: bladex.blade
5+
6+
.. automethod:: Blade._check_string
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
bladex.blade.Blade.generate_iges
2+
================================
3+
4+
.. currentmodule:: bladex.blade
5+
6+
.. automethod:: Blade.generate_iges

docs/source/blade.rst

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,18 @@ Blade
1010
:nosignatures:
1111

1212
Blade._abs_to_norm
13+
Blade._check_errors
1314
Blade._check_params
15+
Blade._check_string
1416
Blade._compute_pitch_angle
1517
Blade._induced_rake_from_skew
1618
Blade._norm_to_abs
1719
Blade._planar_to_cylindrical
1820
Blade.apply_transformations
19-
Blade.plot
21+
Blade.generate_iges
2022
Blade.export_ppg
21-
23+
Blade.plot
24+
2225

2326
.. autoclass:: Blade
2427
:members:

0 commit comments

Comments
 (0)