Surface Slicer

The PGL.components.surfaceslicer.SlicedLoftedSurface class resamples the 3D lofted surface generated by the PGL.components.loftedblade.LoftedBladeSurface class. The LoftedBladeSurface() class defines the body cross-sections in surfaces normal to the body center-line. Whereas the surface resampled by SlicedLoftedSurface() class lies in parallel X-Y (Z = c) planes, where the X-Y-Z coordinate system is defined as the body coordinate system. As a consequence, the new cross-sectional surface definitions will no longer be normal to the body center-line, unless the body is straight with no rotations about either the body X-axis or the body Y-axis.

The SlicedLoftedSurface() class utilizes an adaptive Newton-Rhapson numerical method to slice the 3D lofted surface. A bilinear interpolation method is used to obtain the points on the slice from the lofted surface input, for each iteration of the solver. As such, it is recommended that the surface discretization (i.e. number of spanwise and chordwise sections) desired in the resampled lofted surface be either the same or less than that of the lofted surface input.

A walk-through of an example script to generate the re-sampled 3D lofted surface has been presented.

SlicedLoftedSurface example

In this example a DTU 10MW lofted blade surface is first generated using LoftedBladeSurface, which is then used as input to the SlicedLoftedSurface.

The example is located in PGL/examples/surfaceslicer_example.py.


import numpy as np

from PGL.components.loftedblade import LoftedBladeSurface
from PGL.main.planform import read_blade_planform, redistribute_planform
from PGL.components.surfaceslicer import SlicedLoftedSurface

pf = read_blade_planform('data/DTU_10MW_RWT_blade_axis_prebend.dat')

dist = [[0, 0.01, 1],
        [0.05, 0.01, 8],
        [0.98, 0.001, 119],
        [1., 0.0005, 140]]

pf = redistribute_planform(pf, dist=dist)

d = LoftedBladeSurface()
d.pf = pf
d.redistribute_flag = True
#d.minTE = 0.0

d.blend_var = [0.241, 0.301, 0.36, 1.0]
for f in ['data/ffaw3241.dat',
          'data/ffaw3301.dat',
          'data/ffaw3360.dat',
          'data/cylinder.dat']:

    d.base_airfoils.append(np.loadtxt(f))

d.update()
#d.domain.write_plot3d('exampleblade_lofted.xyz')

#=============================================================================
#===========surface_slicer implementation begins here=========================
#=============================================================================
# create an object of the surface slicer class
m = SlicedLoftedSurface()
m.verbose = True

# input the generated lofted surface using loftedblade.py
# alternatively a surface of shape: (Nchord, Nspan ,3) can be inputed manually
m.surface = d.surface 

# set the number of span sections: <=d.ni_span
m.ni_span = d.ni_span 

# set the number of points on the slice: <= d.ni_chord 
m.ni_slice = d.ni_chord 

# flag for including tip: default value = True
m.include_tip = True 

# set the blade length that it should be scaled with: default value = 1.0
m.blade_length = 1.0

#-------------- set the spanwise distribution------------------------------
# It is recommended that Option 1 and Option 2, both have same discretization
# as the LoftedSurface
# Option 1: dist (refer to docs for explanation). 
# m.dist = dist

# Option 2: s (spanwise distribution of blade curve length)
#m.s = pf['s']

#Option 3: Not setting a specific the above two, proceeds with linear distribution
#----------------------------------------------------------------------------- 
# set the residual tolerance according to the blade length. Unit is in [m]
# default value = 1.e-5
m.tol = 1e-3
 
# generate the surface
m.update()

# final surface in parallel Z= c planes
surface = m.sliced_surface

# uncomment to save the file
#filename = 'test'
#np.save(filename,surface)

# uncomment the code shown below to produce a .xyz file for visualizaion
#m.domain.write_plot3d('exampleblade_sliced.xyz')

Required modules

The modules shown below are required to run the example. PGL.main.planform.read_blade_planform and PGL.main.planform.redistribute_planform are needed to read the file containing the blade planform information and to redistribute the planform as desired.

import numpy as np
from PGL.components.loftedblade import LoftedBladeSurface
from PGL.main.planform import read_blade_planform, redistribute_planform
from PGL.components.surfaceslicer import SlicedLoftedSurface

Read blade planform and redistribute

The code shown below reads the blade planform data stored in a textfile and redistributes the planform.

pf = read_blade_planform('data/DTU_10MW_RWT_blade_axis_prebend.dat')

dist = [[0, 0.01, 1],
        [0.05, 0.01, 8],
        [0.98, 0.001, 119],
        [1., 0.0005, 140]]

pf = redistribute_planform(pf, dist=dist)

LoftedBladeSurface

The code block shown below first creates an object instance of the LoftedBladeSurface class.

d = LoftedBladeSurface()

The redistributed planform is then assigned as an instance variable.

d.pf = pf

The trailing edge can be opened by specifying the distance between the pressure side and suction side surfaces. It should be noted that this value is to be non-dimensionalized by the blade length.

d.redistribute_flag = True
d.minTE = 0.0

The airfoil data along with the blend order of the relative thicknesses are specified.

d.blend_var = [0.241, 0.301, 0.36, 1.0]
for f in ['data/ffaw3241.dat',
          'data/ffaw3301.dat',
          'data/ffaw3360.dat',
          'data/cylinder.dat']:

d.base_airfoils.append(np.loadtxt(f))

Finally, calling the update() method generates the 3D blade lofted surface with cross-sectional surfaces normal to the blade center line.

d.update()

NOTE: The generated lofted surface is stored in the instance variable d.surface. Additionally, the chordwise discretization (ie the number of cross-sectional points) can be altered using the instance variable d.ni_chord. By default this value is set to 257.

SlicedLoftedSurface

Next, an object of the SlicedLoftedSurface() class is created.

m = SlicedLoftedSurface()

The lofted blade surface created by the LoftedBladeSurface() class, is assigned as input.

m.surface = d.surface

The spanwise (i.e. axial) and the chordwise discretizations are then assigned. The discretizations should be either equal to or less than their counterparts in the lofted surface generated by LoftedBladeSurface(). The distributions are linear and as such the discrete quantities are equal for each discretized cross-section as well for the blade span.

m.ni_span = d.ni_span
m.ni_slice = d.ni_chord

Next, a spanwise distribution of the parallel planes is generated. There are three options available to the user.

Option 1:

Providing a list of control points as per the definition given in PGL.main.distfunc.distfunc. dist can be the same as the one used to redistribute the planform for generating the lofted surface using LoftedBladeSurface(). This ensures a preservation of the surface features in the sliced surface as captured by the lofted surface.

dist = [[0, 0.01, 1],
        [0.05, 0.01, 8],
        [0.98, 0.001, 119],
        [1., 0.0005, 140]]

m.dist = dist

Option 2:

Providing a 1D numpy array consisting of normalised spanwise distribution. This can be the same as the spanwise distribution of the planform supplied to the LoftedBladeSurface(). Again, this ensures a preservation of the surface features in the sliced surface as captured by the lofted surface.

m.s = pf['s']

Option 3:

If neither Option 1 nor Option 2 is selected, then the spanwise distirbution of the sliced surface is chosen as a linear discretization from the blade root to the blade tip with the number of spanwise section equal to the user input: m.ni_span. If both Option 1 and Option 2 are provided, then Option 1 takes precedence.

NOTE: If the number of spanwise sections for Option 1 and Option 2 are either more or less than the user input, the inputted distribution is linearly varied such that the span sections match m.ni_span.

An optional flag is provided to include the tip in the discretization. The default value is set to False. This flag is utilized if a linear spanwise distribution is used. It should be noted that the tip is subject to steep spanwise gradients which may lead to non-linearities in the Newton iteration causing the solver to not converge. If such a case occurs, the tip cros-section is generated through cubic spline extrapolation of the re-sampled blade surface.

m.include_tip = True

The blade length can be set. By default this value is set as unity, resulting in a solution that is scaled by the blade length. If this value is set to be either greater or less than unity, the lofted surface input is appropriately scaled with it and is subsequently re-sampled.

m.blade_length = 1.0

The convergence criteria for the residual of the Newton iteration method can be set. This tolerance is the maximum of the absolute difference between the final and the previous state of the residual vector. It has an S.I. unit of metres. As such its value should be appropriately set in accordance with the inputted blade length. For example, a millimetre accuracy would suffice for a blade whose length is equal to or greater than 1 m and as such the tolerance could be set as 0.001. By default its value is set as 0.00001.

NOTE: Lower the tolerance value higher will be the computation time.

m.tol = 1e-4

The resampled is generated by calling the update() method.

m.update()

The resampled sliced surface can now be accessed through the instance variable m.sliced_surface.

Additionally, the surface can be outputted for visualization in software such as ParaView by including the code shown below.

m.domain.write_plot3d('exampleblade_sliced.xyz')