예제 #1
0
class ModifyBladePlanformBase(Component):
    """
    Base for classes that modify a blade planform object
    """

    pfIn = VarTree(BladePlanformVT(), iotype='in')
    pfOut = VarTree(BladePlanformVT(), iotype='out')
예제 #2
0
def redistribute_blade_planform(pfIn, x):

    pfOut = BladePlanformVT()
    pfOut.s = x.copy()

    for name in pfIn.list_vars():
        var = getattr(pfIn, name)
        if not isinstance(var, np.ndarray): continue
        tck = Akima1DInterpolator(pfIn.s, var)
        newvar = tck(x)
        setattr(pfOut, name, newvar)

    return pfOut
예제 #3
0
def redistribute_blade_planform(pfIn, x):

    pfOut = BladePlanformVT()
    pfOut.s = x.copy()

    for name in pfIn.list_vars():
        var = getattr(pfIn, name)
        if not isinstance(var, np.ndarray): continue
        tck = Akima1DInterpolator(pfIn.s, var)
        newvar = tck(x) 
        setattr(pfOut, name, newvar)

    return pfOut
예제 #4
0
class BladePlanformWriter(Component):

    filebase = Str('blade')
    pf = VarTree(BladePlanformVT(), iotype='in')

    def execute(self):

        name = self.filebase + self.itername + '.pfd'

        try:
            if '-fd' in self.itername or '-fd' in self.parent.itername:
                name = self.filebase + '.pfd'
        except:
            pass

        data = np.array([
            self.pf.x, self.pf.y, self.pf.z, self.pf.rot_x, self.pf.rot_y,
            self.pf.rot_z, self.pf.chord, self.pf.rthick, self.pf.p_le
        ]).T
        fid = open(name, 'w')
        header = [
            'main_axis_x', 'main_axis_y', 'main_axis_z', 'rot_x', 'rot_y',
            'rot_z', 'chord', 'rthick', 'p_le'
        ]

        exp_prec = 10  # exponential precesion
        col_width = exp_prec + 8  # column width required for exp precision
        header_full = '# ' + ''.join([(hh + ' [%i]').center(col_width + 2) % i
                                      for i, hh in enumerate(header)]) + '\n'

        fid.write(header_full)
        np.savetxt(fid, data, fmt='%' + ' %i.%ie' % (col_width, exp_prec))
        fid.close()
예제 #5
0
class RotorAeroCode(Component):
    """
    Wrapper for a code capable of predicting the distributed loads
    on a rotor
    """

    pf = VarTree(BladePlanformVT(),
                 iotype='in',
                 desc='Blade geometric definition')
    inflow = VarTree(TurbineEnvironmentVT(),
                     iotype='in',
                     desc='Rotor inflow conditions')

    oper = VarTree(RotorOperationalData(),
                   iotype='out',
                   desc='Operational data')
    rotor_loads = VarTree(RotorLoadsVT(),
                          iotype='out',
                          desc='Rotor torque, power, and thrust')
    blade_loads = VarTree(DistributedLoadsExtVT(),
                          iotype='out',
                          desc='Spanwise load distributions')

    def execute(self):

        print 'perform analysis here'
class RotorAeroBase(Component):
    """
    Base class for models that can predict the power and thrust of wind turbine rotor
    for a single inflow case
    """

    pf = VarTree(BladePlanformVT(), iotype='in', desc='Blade geometric definition')
    inflow = VarTree(TurbineEnvironmentVT(), iotype='in', desc='Rotor inflow conditions')

    oper = VarTree(RotorOperationalData(), iotype='out', desc='Operational data')
    rotor_loads = VarTree(RotorLoadsVT(), iotype='out', desc='Rotor torque, power, and thrust')
예제 #7
0
class RedistributedBladePlanform(Component):
    """
    Redistribute an existing planform onto a new distribution x
    """

    x = Array(iotype='in', desc='New spanwise discretization')

    pfIn = VarTree(BladePlanformVT(), iotype='in')
    pfOut = VarTree(BladePlanformVT(), iotype='out')

    def execute(self):

        self.pfOut.s = self.x.copy()
        self.pfIn._compute_s()
        for name in self.pfIn.list_vars():
            var = getattr(self.pfIn, name)
            if not isinstance(var, np.ndarray): continue
            tck = pchip(self.pfIn.s, var)
            newvar = tck(self.x)
            setattr(self.pfOut, name, newvar)
class RotorAero(Component):
    """
    Class that extends the TurbineAeroBase with distributed blade
    loads for a single inflow case
    """

    pf = VarTree(BladePlanformVT(), iotype='in', desc='Blade geometric definition')
    inflow = VarTree(TurbineEnvironmentVT(), iotype='in', desc='Rotor inflow conditions')

    oper = VarTree(RotorOperationalData(), iotype='out', desc='Operational data')
    rotor_loads = VarTree(RotorLoadsVT(), iotype='out', desc='Rotor torque, power, and thrust')
    blade_loads = VarTree(DistributedLoadsExtVT(), iotype='out', desc='Spanwise load distributions')
class CS2Dsolver(Component):

    cs2d = List(CrossSectionStructureVT, iotype='in', desc='Blade cross sectional structure geometry')
    pf = VarTree(BladePlanformVT(), iotype='in', desc='Blade planform discretized according to'
                                                      'the structural resolution')

    beam_structure = VarTree(BeamStructureVT(), iotype='out', desc='Structural beam properties')

    def execute(self):

        print ''
        for i, cs in enumerate(self.cs2d):
            print 'processing cross section %i' % i
예제 #10
0
class ComputeHAWC2BeamProps(Component):
    """
    Component that postprocesses the individual outputs from the
    BECAS case runs returning a BeamStructureVT, mass and mass moment.
    """

    pf = VarTree(BladePlanformVT(), iotype='in')
    hub_radius = Float(iotype='in')
    cs2d_cases = List(iotype='in')
    g = Float(9.81, iotype='in')
    beam_structure = VarTree(BeamStructureVT(), iotype='out')
    mass = Float(iotype='out')
    mass_moment = Float(iotype='out')

    def execute(self):

        self.beam_structure = self.beam_structure.copy()

        ni = len(self.cs2d_cases)

        for name in self.beam_structure.list_vars():
            var = getattr(self.beam_structure, name)
            if name == 'J':
                nname = 'K'
            else:
                nname = name
            if isinstance(var, np.ndarray):
                var = np.zeros(ni)
                for i, h2d in enumerate(self.cs2d_cases):
                    try:
                        var[i] = getattr(h2d, nname)
                    except:
                        pass

            setattr(self.beam_structure, name, var)

        self.beam_structure.s = self.pf.s * self.pf.smax * self.pf.blade_length
        self.mass = np.trapz(self.beam_structure.dm, self.beam_structure.s)
        self.mass_moment = np.trapz(
            self.g * self.beam_structure.dm *
            (self.beam_structure.s + self.hub_radius), self.beam_structure.s)

        print 'Mass: ', self.mass
        print 'Mass moment: ', self.mass_moment

        self.beam_structure.x_e += (
            0.5 - self.pf.p_le) * self.pf.chord * self.pf.blade_length
        self.beam_structure.x_cg += (
            0.5 - self.pf.p_le) * self.pf.chord * self.pf.blade_length
        self.beam_structure.x_sh += (
            0.5 - self.pf.p_le) * self.pf.chord * self.pf.blade_length
예제 #11
0
    def add_main_body(self, name, body=None):

        if body is None:

            body = MainBody()
            
            if 'blade' in name:
                body.remove('geom')
                body.add('geom', VarTree(BladePlanformVT()))
            if 'tower' in name:
                body.remove('geom')
                body.add('geom', VarTree(TubularTowerGeometryVT()))

        self.add(name, VarTree(body))
        self.bodies.append(name)

        return getattr(self, name)
예제 #12
0
class BeamStructureCSCode(Component):
    """
    Base class for computing beam structural properties using a cross-sectional
    code such as PreComp, BECAS or VABS.

    The analysis assumes that the list of CrossSectionStructureVT's and the
    BladePlanformVT are interpolated onto the structural grid, and that
    the code itself is responsible for the meshing of the cross sections.
    """

    cs2d = List(CrossSectionStructureVT,
                iotype='in',
                desc='Blade cross sectional structure geometry')
    pf = VarTree(BladePlanformVT(),
                 iotype='in',
                 desc='Blade planform discretized according to'
                 'the structural resolution')

    beam_structure = VarTree(BeamStructureVT(),
                             iotype='out',
                             desc='Structural beam properties')
예제 #13
0
class BECASBeamStructure(Assembly):
    """
    Assembly that implements the FUSED-Wind I/O interface 
    defined in BeamStructureCSCode.
    The assembly sets up a series of BECAS computations that iterate
    over a list of CrossSectionStructureVT's and computes beam
    structural properties suitable for an aeroelastic solver.
    """

    becas_inputs = Str('becas_inputs',
                       desc='Relative path to BECAS input files',
                       iotype='in')
    section_name = Str(
        'BECAS_SECTION',
        desc='Section name used by shellexpander and BECASWrapper',
        iotype='in')

    cs2d = List(iotype='in', desc='Blade cross sectional structure geometry')
    pf = VarTree(
        BladePlanformVT(),
        iotype='in',
        desc='Blade planform with same spanwise discretization as cs2d')

    beam_structure = VarTree(BeamStructureVT(),
                             iotype='out',
                             desc='Structural beam properties')

    def _pre_execute(self):
        super(BECASBeamStructure, self)._pre_execute()

        self.tt = time.time()

    def _post_execute(self):
        super(BECASBeamStructure, self)._post_execute()

        t = time.time() - self.tt
        self._logger.info('BECASBeamStructure time: %f' % t)
예제 #14
0
def read_blade_planform(filename):

    data = np.loadtxt(filename)
    s = calculate_length(data[:, [0, 1, 2]])

    pf = BladePlanformVT()
    pf.blade_length = data[-1, 2]
    pf.s = s / s[-1]
    pf.x = data[:, 0] / data[-1, 2]
    pf.y = data[:, 1] / data[-1, 2]
    pf.z = data[:, 2] / data[-1, 2]
    pf.rot_x = data[:, 3]
    pf.rot_y = data[:, 4]
    pf.rot_z = data[:, 5]
    pf.chord = data[:, 6] / data[-1, 2]
    pf.rthick = data[:, 7]
    pf.rthick /= pf.rthick.max()
    pf.athick = pf.rthick * pf.chord
    pf.p_le = data[:, 8]

    return pf
예제 #15
0
class SplinedBladePlanform(Assembly):

    x_dist = Array(iotype='in', desc='spanwise resolution of blade')
    nC = Int(8, iotype='in', desc='Number of spline control points along span')
    Cx = Array(iotype='in',
               desc='spanwise distribution of spline control points')

    blade_length = Float(iotype='in')

    span_ni = Int(50, iotype='in')

    pfIn = VarTree(BladePlanformVT(), iotype='in')
    pfOut = VarTree(BladePlanformVT(), iotype='out')

    def __init__(self):
        super(SplinedBladePlanform, self).__init__()

        self.blade_length_ref = 0.

    def _pre_execute(self):
        super(SplinedBladePlanform, self)._pre_execute()

        # set reference length first time this comp is executed
        if self.blade_length_ref == 0.:
            self.blade_length_ref = self.blade_length

        self.configure_splines()

    def compute_x(self):

        # simple distfunc for now
        self.x_dist = distfunc([[0., -1, 1],
                                [1., 0.2 * 1. / self.span_ni, self.span_ni]])

    def configure_splines(self):

        if hasattr(self, 'chord_C'):
            return
        if self.Cx.shape[0] == 0:
            self.Cx = np.linspace(0, 1, self.nC)
        else:
            self.nC = self.Cx.shape[0]

        self.compute_x()
        self.pfOut = self.pfIn.copy()
        for vname in self.pfIn.list_vars():
            if vname in ['athick', 'blade_length']:
                continue

            cIn = self.get('pfIn.' + vname)
            cOut = self.get('pfOut.' + vname)
            sname = vname.replace('.', '_')

            if vname == 's':
                self.connect('x_dist', 'pfOut.s')
            else:
                spl = self.add(sname, FFDSplineComponentBase(self.nC))
                self.driver.workflow.add(sname)
                # spl.log_level = logging.DEBUG
                self.connect('x_dist', sname + '.x')
                self.connect('Cx', sname + '.Cx')
                spl.xinit = self.get('pfIn.s')
                spl.Pinit = cIn
                self.connect(sname + '.P', 'pfOut.' + vname)
                self.create_passthrough(sname + '.C', alias=sname + '_C')
                self.create_passthrough(sname + '.dPds', alias=sname + '_dPds')

    def _post_execute(self):
        super(SplinedBladePlanform, self)._post_execute()

        self.pfOut.chord *= self.blade_length / self.blade_length_ref
        self.pfOut.athick = self.pfOut.chord * self.pfOut.rthick
예제 #16
0
class SplinedBladePlanform(Assembly):

    x_dist = Array(iotype='in', desc='spanwise resolution of blade')
    nC = Int(8, iotype='in', desc='Number of spline control points along span')
    Cx = Array(iotype='in',
               desc='spanwise distribution of spline control points')

    blade_length = Float(iotype='in')
    blade_length_ref = Float(iotype='in')

    span_ni = Int(50, iotype='in')

    pfIn = VarTree(BladePlanformVT(), iotype='in')
    pfOut = VarTree(BladePlanformVT(), iotype='out')

    def __init__(self):
        super(SplinedBladePlanform, self).__init__()

        self.blade_length_ref = 0.

    def _pre_execute(self):
        super(SplinedBladePlanform, self)._pre_execute()

        # set reference length first time this comp is executed
        if self.blade_length_ref == 0.:
            self.blade_length_ref = self.blade_length

    def configure_splines(self):

        if hasattr(self, 'chord_C'):
            return
        if self.Cx.shape[0] == 0:
            self.Cx = np.linspace(0, 1, self.nC)
        else:
            self.nC = self.Cx.shape[0]

        self.connect('blade_length', 'pfOut.blade_length')

        self.add('compute_x', ComputeDist())
        self.driver.workflow.add('compute_x')
        self.connect('span_ni', 'compute_x.span_ni')

        for vname in self.pfIn.list_vars():
            if vname in ['athick', 'blade_length']:
                continue

            cIn = self.get('pfIn.' + vname)
            cOut = self.get('pfOut.' + vname)
            sname = vname.replace('.', '_')

            if vname == 's':
                self.connect('compute_x.x', 'pfOut.s')
            else:
                spl = self.add(sname, FFDSplineComponentBase(self.nC))
                self.driver.workflow.add(sname)
                # spl.log_level = logging.DEBUG
                self.connect('compute_x.x', sname + '.x')
                self.connect('Cx', sname + '.Cx')
                spl.xinit = self.get('pfIn.s')
                spl.Pinit = cIn
                if vname == 'chord':
                    self.add('scaleC', ScaleChord())
                    self.driver.workflow.add('scaleC')
                    self.connect('chord.P', 'scaleC.cIn')
                    self.connect('blade_length/blade_length_ref',
                                 'scaleC.scaler')
                    self.connect('scaleC.cOut', 'pfOut.chord')
                    # self.connect(sname + '.P'+
                    #     '*blade_length/blade_length_ref', 'pfOut.' + vname)
                else:
                    self.connect(sname + '.P', 'pfOut.' + vname)
                self.create_passthrough(sname + '.C', alias=sname + '_C')
                self.create_passthrough(sname + '.dPds', alias=sname + '_dPds')

        self.add('athick', ComputeAthick())
        self.driver.workflow.add('athick')
        self.connect('chord.P', 'athick.chord')
        self.connect('rthick.P', 'athick.rthick')
        self.connect('athick.athick', 'pfOut.athick')
예제 #17
0
def read_blade_planform(filename):

    data = np.loadtxt(filename)
    s = calculate_length(data[:, [0, 1, 2]])

    pf = BladePlanformVT()
    pf.blade_length = data[-1, 2]
    pf.s = s / s[-1]
    pf.smax = s[-1]
    pf.x = data[:, 0] / data[-1, 2]
    pf.y = data[:, 1] / data[-1, 2]
    pf.z = data[:, 2] / data[-1, 2]
    pf.rot_x = data[:, 3]
    pf.rot_y = data[:, 4]
    pf.rot_z = data[:, 5]
    pf.chord = data[:, 6] / data[-1, 2]
    pf.rthick = data[:, 7]
    pf.rthick /= pf.rthick.max()
    pf.athick = pf.rthick * pf.chord
    pf.p_le = data[:, 8]

    return pf
예제 #18
0
    def configure(self):

        pf = BladePlanformVT()
        pf.x = np.zeros(7)
        pf.y = np.zeros(7)
        pf.z = np.array([0, 0.08, 0.25, 0.5, 0.7, 0.98, 1.])
        pf.rot_x = np.zeros(7)
        pf.rot_y = np.zeros(7)
        pf.rot_z = np.array([-14.5       , -14.46339951,  -8.39059169,  -3.80326048, -0.3093841 ,   3.24525784,   3.428     ])
        pf.chord = np.array([ 0.06229303,  0.06274697,  0.07170661,  0.05870869,  0.0417073 , 0.01726638,  0.00694718])
        pf.rthick = np.array([ 1.        ,  0.90303866,  0.40834206,  0.26394705,  0.24103804, 0.241     ,  0.241     ])
        pf.athick = pf.chord * pf.rthick
        pf.p_le = np.array([ 0.5       ,  0.49662459,  0.38370686,  0.35000317,  0.35      , 0.35      ,  0.35      ])

        self.add('pf', pf)

        self.add('redist', RedistributedBladePlanform())
        self.driver.workflow.add('redist')
        self.redist.pfIn = self.pf
        self.redist.x = np.linspace(0, 1, 20)
예제 #19
0
    def configure(self):

        pf = BladePlanformVT()
        pf.x = np.zeros(7)
        pf.y = np.zeros(7)
        pf.z = np.array([0, 0.08, 0.25, 0.5, 0.7, 0.98, 1.])
        pf.rot_x = np.zeros(7)
        pf.rot_y = np.zeros(7)
        pf.rot_z = np.array([
            -14.5, -14.46339951, -8.39059169, -3.80326048, -0.3093841,
            3.24525784, 3.428
        ])
        pf.chord = np.array([
            0.06229303, 0.06274697, 0.07170661, 0.05870869, 0.0417073,
            0.01726638, 0.00694718
        ])
        pf.rthick = np.array(
            [1., 0.90303866, 0.40834206, 0.26394705, 0.24103804, 0.241, 0.241])
        pf.athick = pf.chord * pf.rthick
        pf.p_le = np.array(
            [0.5, 0.49662459, 0.38370686, 0.35000317, 0.35, 0.35, 0.35])

        self.add('pf', pf)

        self.add('redist', RedistributedBladePlanform())
        self.driver.workflow.add('redist')
        self.redist.pfIn = self.pf
        self.redist.x = np.linspace(0, 1, 20)
예제 #20
0
class LoftedBladeSurface(Component):

    pf = VarTree(BladePlanformVT(), iotype='in')
    base_airfoils = List(iotype='in')
    blend_var = Array(iotype='in')
    chord_ni = Int(300, iotype='in')
    span_ni = Int(300, iotype='in')

    interp_type = Enum('rthick', ('rthick', 's'), iotype='in')
    surface_spline = Str('akima', iotype='in', desc='Spline')

    rot_order = Array(np.array([2, 1, 0]),
                      iotype='in',
                      desc='rotation order of airfoil sections'
                      'default z,y,x (twist,sweep,dihedral)')

    surfout = VarTree(BladeSurfaceVT(), iotype='out')
    surfnorot = VarTree(BladeSurfaceVT(), iotype='out')

    def execute(self):

        self.interpolator = BlendAirfoilShapes()
        self.interpolator.ni = self.chord_ni
        self.interpolator.spline = self.surface_spline
        self.interpolator.blend_var = self.blend_var
        self.interpolator.airfoil_list = self.base_airfoils
        self.interpolator.initialize()

        self.span_ni = self.pf.s.shape[0]
        x = np.zeros((self.chord_ni, self.span_ni, 3))

        for i in range(self.span_ni):

            s = self.pf.s[i]
            pos_x = self.pf.x[i]
            pos_y = self.pf.y[i]
            pos_z = self.pf.z[i]
            chord = self.pf.chord[i]
            p_le = self.pf.p_le[i]

            # generate the blended airfoil shape
            if self.interp_type == 'rthick':
                rthick = self.pf.rthick[i]
                points = self.interpolator(rthick)
            else:
                points = self.interpolator(s)

            points *= chord
            points[:, 0] += pos_x - chord * p_le

            # x-coordinate needs to be inverted for clockwise rotating blades
            x[:, i, :] = (np.array(
                [-points[:, 0], points[:, 1], x.shape[0] * [pos_z]]).T)

        # save non-rotated blade (only really applicable for straight blades)
        x_norm = x.copy()
        x[:, :, 1] += self.pf.y
        x = self.rotate(x)

        self.surfnorot.surface = x_norm
        self.surfout.surface = x

    def rotate(self, x):
        """
        rotate blade sections accounting for twist and main axis orientation
        
        the blade will be built with a "sheared" layout, ie no rotation around y
        in the case of sweep.
        if the main axis includes a winglet, the blade sections will be
        rotated accordingly. ensure that an adequate point distribution is
        used in this case to avoid sections colliding in the winglet junction!
        """

        main_axis = Curve(points=np.array([self.pf.x, self.pf.y, self.pf.z]).T)

        rot_normals = np.zeros((3, 3))
        x_rot = np.zeros(x.shape)

        for i in range(x.shape[1]):
            points = x[:, i, :]
            rot_center = main_axis.points[i]
            # rotation angles read from file
            angles = np.array([
                self.pf.rot_x[i], self.pf.rot_y[i], self.pf.rot_z[i]
            ]) * np.pi / 180.

            # compute rotation angles of main_axis
            t = main_axis.dp[i]
            rot = np.zeros(3)
            rot[0] = -np.arctan(t[1] / (t[2] + 1.e-20))
            v = np.array([t[2], t[1]])
            vt = np.dot(v, v)**0.5
            rot[1] = (np.arcsin(t[0] / vt))
            angles[0] += rot[0]
            angles[1] += rot[1]

            # compute x-y-z normal vectors of rotation
            n_y = np.cross(t, [1, 0, 0])
            n_y = n_y / norm(n_y)
            rot_normals[0, :] = np.array([1, 0, 0])
            rot_normals[1, :] = n_y
            rot_normals[2, :] = t

            # compute final rotation matrix
            rotation_matrix = np.matrix([[1., 0., 0.], [0., 1., 0.],
                                         [0., 0., 1.]])
            for n, ii in enumerate(self.rot_order):
                mat = np.matrix(RotMat(rot_normals[ii], angles[ii]))
                rotation_matrix = mat * rotation_matrix

            # apply rotation
            x_rot[:, i, :] = dotXC(rotation_matrix, points, rot_center)

        return x_rot
예제 #21
0
class HAWC2GeometryBuilder(Component):

    bladegeom = VarTree(BladePlanformVT(), iotype='in')
    c12axis_init = Array(iotype='in')
    blade_ae = VarTree(HAWC2BladeGeometry(), iotype='out')
    interp_from_htc = Bool(
        True,
        iotype='in',
        desc=
        'Interpolate blade onto the distribution defined in the htc master file'
    )
    blade_ni_span = Int(30,
                        iotype='in',
                        desc='spanwise distribution of blade planform')

    blade_length = Float(86.366, iotype='in')
    hub_radius = Float(2.8, iotype='in')

    def execute(self):

        c12axis = self.calculate_c12axis()

        if self.interp_from_htc:
            cni = self.c12axis_init.shape[0]
            if self.c12axis_init[-1, 2] > 1.:
                self.c12axis_init /= self.blade_length

            # interpolate blade_ae distribution onto c12 distribution
            self.blade_ae.c12axis = np.zeros((cni, 4))
            for i in range(4):
                tck = pchip(c12axis[:, 2], c12axis[:, i])
                self.blade_ae.c12axis[:, i] = tck(self.c12axis_init[:, 2])
        else:
            ds_root = 1. / self.blade_ni_span
            ds_tip = 1. / self.blade_ni_span / 3.
            dist = np.array([[0., ds_root, 1],
                             [1., ds_tip, self.blade_ni_span]])
            x = distfunc(dist)
            self.blade_ae.c12axis = np.zeros((x.shape[0], 4))
            for i in range(4):
                tck = pchip(c12axis[:, 2], c12axis[:, i])
                self.blade_ae.c12axis[:, i] = tck(x)

        # scale main axis according to radius
        self.blade_ae.c12axis[:, :3] *= self.blade_length

        self.blade_ae.radius = self.blade_length + self.hub_radius
        self.blade_ae.s = self.bladegeom.smax * self.bladegeom.s * self.blade_length
        self.blade_ae.rthick = self.bladegeom.rthick * 100.
        self.blade_ae.chord = self.bladegeom.chord * self.blade_length
        self.blade_ae.aeset = np.ones(len(self.blade_ae.s))

    def calculate_c12axis(self):
        """
        compute the 1/2 chord axis based on the blade axis and chordwise rotation point

        nb: this only works for straight blades!
        """

        # The HAWC2 blade axis is defined using the 1/2 chord points
        b = self.bladegeom
        c12axis = np.zeros((b.x.shape[0], 4))
        for i in range(b.x.shape[0]):
            xc12 = (0.5 - b.p_le[i]) * b.chord[i] * np.cos(
                b.rot_z[i] * np.pi / 180.)
            yc12 = -(0.5 - b.p_le[i]) * b.chord[i] * np.sin(
                b.rot_z[i] * np.pi / 180.)
            c12axis[i, 0] = -(b.x[i] + xc12)
            c12axis[i, 1] = b.y[i] + yc12
            c12axis[i, 2] = b.z[i]
        c12axis[:, 3] = b.rot_z
        return c12axis