@@ -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 \t N_point\t \t \t X_crds\t \t \t \t '
522+ output_string += 'Y_crds\t \t \t \t \t Z_crds\t \t \t \t \t DISTANCE'
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 \t N_point\t \t \t X_crds\t \t \t \t '
550+ output_string += 'Y_crds\t \t \t \t \t Z_crds\t \t \t \t \t DISTANCE'
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
0 commit comments