Beispiel #1
0
    def execute(self):
        """
        generate cross sections at every spanwise node of the st3d vartree
        """

        # clear list of outputs!
        self.cs2d = []

        ni = self.st3d.x.shape[0]
        for i in range(ni):
            x = self.st3d.x[i]
            # print 'adding section at r/R = %2.2f' % x
            st2d = CrossSectionStructureVT()
            st2d.s = x * self.blade_length
            st2d.DPs = []
            try:
                airfoil = self.surface.interpolate_profile(
                    x)[:, [0, 1]] * self.blade_length
                st2d.airfoil.initialize(airfoil)
            except:
                pass
            for ir, rname in enumerate(self.st3d.regions):
                reg = getattr(self.st3d, rname)
                if reg.thickness[i] == 0.:
                    print 'zero thickness region!', rname
                    # continue
                DP0 = getattr(self.st3d, 'DP%02d' % ir)
                DP1 = getattr(self.st3d, 'DP%02d' % (ir + 1))
                r = st2d.add_region(rname.upper())
                st2d.DPs.append(DP0[i])
                r.s0 = DP0[i]
                r.s1 = DP1[i]
                for lname in reg.layers:
                    lay = getattr(reg, lname)
                    if lay.thickness[i] > 1.e-5:
                        l = r.add_layer(lname)
                        # try:
                        lnamebase = lname.translate(None, digits)
                        st2d.add_material(lnamebase,
                                          self.get_material(lname).copy())
                        # except:
                        # raise RuntimeError('Material %s not in materials list' % lname)
                        l.materialname = lnamebase
                        l.thickness = max(0., lay.thickness[i])
                        try:
                            l.angle = lay.angle[i]
                        except:
                            l.angle = 0.

            st2d.DPs.append(DP1[i])
            for ir, rname in enumerate(self.st3d.webs):
                reg = getattr(self.st3d, rname)
                if reg.thickness[i] == 0.:
                    continue
                r = st2d.add_web(rname.upper())
                try:
                    DP0 = getattr(self.st3d, 'DP%02d' % self.st3d.iwebs[ir][0])
                except:
                    DP0 = getattr(
                        self.st3d, 'DP%02d' %
                        (len(self.st3d.regions) + self.st3d.iwebs[ir][0] + 1))
                try:
                    DP1 = getattr(self.st3d, 'DP%02d' % self.st3d.iwebs[ir][1])
                except:
                    DP1 = getattr(
                        self.st3d, 'DP%02d' %
                        (len(self.st3d.regions) + self.st3d.iwebs[ir][1] + 1))
                r.s0 = DP0[i]
                r.s1 = DP1[i]
                for lname in reg.layers:
                    lay = getattr(reg, lname)
                    if lay.thickness[i] > 1.e-5:
                        l = r.add_layer(lname)
                        try:
                            lnamebase = lname.translate(None, digits)
                            st2d.add_material(lnamebase,
                                              self.get_material(lname).copy())
                        except:
                            raise RuntimeError(
                                'Material %s not in materials list' % lname)
                        l.materialname = lnamebase
                        l.thickness = max(0., lay.thickness[i])
                        try:
                            l.angle = lay.angle[i]
                        except:
                            l.angle = 0.

            self.cs2d.append(st2d)
Beispiel #2
0
class CS2DtoBECAS(Component):
    """
    Component that generates a set of BECAS input files based on
    a fusedwind.turbine.structure_vt.CrossSectionStructureVT.

    This class uses shellexpander, which comes as part of the BECAS distribution.
    """

    path_shellexpander = Str(iotype='in',
                             desc='Absolute path to shellexpander.py')

    cs2d = VarTree(CrossSectionStructureVT(),
                   iotype='in',
                   desc='Cross-sectional properties vartree')
    total_points = Int(
        100,
        iotype='in',
        desc=
        'Number of total geometry points to define the shape of the cross-section'
    )
    max_layers = Int(0,
                     iotype='in',
                     desc='Maximum number of layers through thickness')
    open_te = Bool(False, iotype='in', desc='If True, TE will be left open')
    becas_inputs = Str('becas_inputs',
                       iotype='in',
                       desc='Relative path for the future BECAS input files')
    section_name = Str(
        'BECAS_SECTION',
        iotype='in',
        desc='Section name used by shellexpander, also by BECASWrapper')
    dominant_elsets = List(
        ['REGION03', 'REGION06'],
        iotype='in',
        desc='define the spar cap regions for correct meshing')

    path_input = Str(iotype='out',
                     desc='path to the generated BECAS input files')
    airfoil = Array(iotype='out',
                    desc='the re-distributed airfoil coordinates')
    web_coord = Array(iotype='out',
                      desc='the distributed shear web coordinates')
    nodes = Array(iotype='out',
                  desc='all the nodes, web+airfoil for the 2D cross section')
    elset_defs = Dict(iotype='out', desc='')
    elements = Array(iotype='out', desc='')
    nodes_3d = Array(iotype='out', desc='')
    el_3d = Array(iotype='out', desc='')
    te_ratio = Float(
        iotype='out',
        desc='ratio between outer TE planform and TE layup thickness')

    def execute(self):
        """  """

        self._logger.info('starting CS2DtoBECAS ...')
        if self.path_shellexpander == '':
            raise RuntimeError('path_shellexpander not specified')

        tt = time.time()

        self.path_input = os.path.join(self.becas_inputs, self.section_name)

        if self.cs2d.airfoil.points.shape[0] > 0:
            self.iCPs = []
            self.WCPs = []
            self.iWCPs = []
            self.CPs = []

        self.total_points_input = self.total_points

        try:
            self.compute_max_layers()
            self.compute_airfoil()
            # self.flatback()
            # self.adjust_te_panels()
            self.output_te_ratio()
            self.add_shearweb_nodes()
            self.create_elements()
            self.create_elements_3d(reverse_normals=False)
            self.write_abaqus_inp()
            self.write_becas_inp()
        except:
            self._logger.info('CS2DtoBECAS failed building BECAS mesh')

        self._logger.info('CS2DtoBECAS calc time: % 10.6f seconds' %
                          (time.time() - tt))

    def compute_max_layers(self):
        """
        The number of elements used to discretize the shell thickness.
        The minimum value is the largest number of layers of different
        material anywhere in the airfoil.
        """

        self.max_thickness = 0.
        for r_name in self.cs2d.regions:
            r = getattr(self.cs2d, r_name)
            self.max_layers = max(self.max_layers, len(r.layers))

            if r.thickness == 0.:
                # in case the total layer thickness for each region is not already computed
                for l_name in r.layers:
                    lay = getattr(r, l_name)
                    r.thickness += lay.thickness
            if r.thickness > self.max_thickness:
                self.max_thickness = r.thickness

    def compute_airfoil(self, debug=False):
        """Redistributed mesh points evenly among regions

        After defining different regions this method will assure that, given
        a total number of mesh points, the cell size in each region is similar.
        The region boundaries and shear web positions will be approximately
        on the same positions as defined.

        Region numbers can be added arbitrarely.
        """

        af = self.cs2d.airfoil

        if np.linalg.norm(self.cs2d.airfoil.points[0] -
                          self.cs2d.airfoil.points[-1]) > 0.:
            self.open_te = True

        # construct distfunc for airfoil curve
        dist = []

        # the distribution function only cares about control points and does
        # not have any notion of the regions. Prepare a sorted list for all
        # the control points, including the webs
        # add first point already: the TE
        CPs_s, WCPs_s = [-1], []
        self.iCPs.append(0)

        # because we want an evenly distributed mesh, calculate the cell
        # size note that distfunc ds does not relate to self._af.smax for
        # its length
        ds_const = 1.0 / self.total_points
        if ds_const * af.length < 1.2 * self.max_thickness:
            ds_old = ds_const
            new_ds = 1.2 * self.max_thickness
            self.total_points = np.maximum(int(af.length / new_ds), 70)
            ds_const = 1. / self.total_points
            self._logger.info('Increasing cell size from %5.3f to %5.3f '
                              'and reducing number of elements to %i' %
                              (ds_const, ds_old, self.total_points))
        self.ds_const = ds_const
        # add first point, TE, s0=0 in AirfoilShape curve fraction coordinates
        # dist.append([0.0, ds_const, 1])
        self.CPs.append(af.interp_s(af.s_to_01(-1.)))
        # keep track of all CPs and WCPs in one dictionary
        allCPs = {}
        # cycle through all the regions
        for name in self.cs2d.regions:
            r = getattr(self.cs2d, name)
            # save the coordinates of the CPs that served as inputs
            self.CPs.append(af.interp_s(af.s_to_01(r.s1)))
            # save to the dictionary for sorting
            if r.s1 in CPs_s:
                raise UserWarning, "Each CP's s1 value should be unique"
            CPs_s.append(r.s1)
            allCPs[r.s1] = r
        # and now the webs, if any
        for name in self.cs2d.webs:
            w = getattr(self.cs2d, name)
            if w.thickness == 0.: continue

            WCPs_s.append(w.s0)
            WCPs_s.append(w.s1)
            # save the coordinates of the WCPs that served as inputs
            self.WCPs.append(af.interp_s(af.s_to_01(w.s0)))
            self.WCPs.append(af.interp_s(af.s_to_01(w.s1)))
            # web control points are allowed to coincide with CP's. If so,
            # there is no need to add another control point
            if w.s0 not in allCPs:
                allCPs[w.s0] = w
            if w.s1 not in allCPs:
                allCPs[w.s1] = w

        # now sort the list so we can properly construct a Curve
        dist_ni = 0
        sorted_allCPs_keys = sorted(allCPs.keys())
        for r_nr, s in enumerate(sorted_allCPs_keys):
            r = allCPs[s]
            # keep track of region start and ending points seperately since
            # webs can interupt the regions
            s_end = s
            if r_nr == 0:
                # the starting point of the curve was already added
                s_start = -1
            else:
                # start is the end necesarely with the previous region,
                # because a WEB doesn't have a start. Consequently, just rely
                # on the sorted allCPs list.
                s_start = sorted_allCPs_keys[r_nr - 1]

            # only set the starting index of a region of the current point
            # actually belongs to region and not a shear web
            if not r.name.lower().startswith('web'):
                # explicitaly take the previous region end point
                r.s0_i = self.iCPs[-1]

            if debug:
                print '% 6.3f  % 6.3f  %8s' % (s_start, s_end, r.name)

            # for AirfoilShape we want surface fractions between 0 and 1
            s_start_ = af.s_to_01(s_start)
            s_end_ = af.s_to_01(s_end)

            # find the amount of points required for this region
            # and the dist_ni refers to the total number of nodes on the
            # curve when at the end of the region (s1)
            dist_ni += max(1, int(round((s_end_ - s_start_) / ds_const)))
            # add a distribution point to the Curve
            dist.append([s_end_, ds_const, dist_ni])

            # save the index of the end point of the region
            # is it a region, or s0 or s1 of a web?
            if r.name.lower().startswith('web'):
                if s_end == r.s0:
                    r.s0_i = dist_ni - 1
                elif s_end == r.s1:
                    r.s1_i = dist_ni - 1
            else:
                # some CPs might coincide with WCPs, in that case it would
                # not have been in allCPs
                if s_end in WCPs_s:
                    # now figure out to which region the current s_end belongs
                    for w_name in self.cs2d.webs:
                        w = getattr(self.cs2d, w_name)
                        if s_end == w.s0:
                            w.s0_i = dist_ni - 1
                        elif s_end == w.s1:
                            w.s1_i = dist_ni - 1
                # but also still add s1_i to the region object
                r.s1_i = dist_ni - 1
            # and save the index for later reference in a convienent list
            if r.s1 in CPs_s:
                self.iCPs.append(dist_ni - 1)
            # be adviced, a CP can also live on the web
            if r.s1 in WCPs_s:
                self.iWCPs.append(dist_ni - 1)

        # before executing, make sure all the points are in increasing order
        if np.diff(np.array(dist)[:, 0]).min() <= 0:
            raise ValueError, 'Points are not continiously increasing'
        afn = af.redistribute(dist_ni, dist=dist)
        # get the redistributed points
        self.airfoil = afn.points
        self.total_points = self.airfoil.shape[0]
        self.CPs_s = CPs_s
        self.WCPs_s = WCPs_s

    def mirror_airfoil(self):
        """
        mirror the airfoil, multiply x axis with -1
        """

        offset = 0.0

        self.web_coord[:, 0] *= -1.0
        self.airfoil[:, 0] *= -1.0

        # also for the control points
        for cp in self.CPs:
            cp[0] *= -1.0
            cp[0] += offset
        for wcp in self.WCPs:
            wcp[0] *= -1.0
            wcp[0] += offset

        if self.te_le_orientation == 'right-to-left':
            self.te_le_orientation = 'left-to-right'
        else:
            self.te_le_orientation = 'right-to-left'

    def add_shearweb_nodes(self):
        """
        Distribute nodes over the shear web. Use the same spacing as used for
        the airfoil nodes.
        """

        self.nr_webs = 0
        self.web_coord = np.array([])
        # find the tickness in the TE region to close with a web the TE: first and last region
        r_name = self.cs2d.regions[-1]
        r_TE_suc = getattr(self.cs2d, r_name)
        r_name = self.cs2d.regions[0]
        r_TE_pres = getattr(self.cs2d, r_name)
        # add a 50% thickness safety factor
        TE_thick_max = (r_TE_pres.thickness + r_TE_suc.thickness) * 1.5
        for w_name in self.cs2d.webs:
            w = getattr(self.cs2d, w_name)
            if w.thickness == 0.: continue
            self.nr_webs += 1
            # at this point there are no nodes on the shear web itself
            # add a node distribution on the shear web too, and base the node
            # spacing on the average spacing ds on the airfoil
            # TODO: should'nt the shear web elements be assigned in
            # compute_airfoil?
            ds_mean = np.maximum(self.ds_const * self.cs2d.airfoil.length,
                                 self.max_thickness * 1.2)
            node1 = self.airfoil[w.s0_i, :]
            node2 = self.airfoil[w.s1_i, :]
            # the length of the shear web is then
            len_web = np.linalg.norm(node1 - node2)
            nr_points = max(int(round(len_web / ds_mean, 0)), 3)
            # generate nodal coordinates on the shear web
            if TE_thick_max > len_web and self.nr_webs == 1:
                # if a web is used to close the TE and the the web is very
                # short, no extra nodes are placed along the web to avoid mesh issues
                x = np.array([node1[0], node2[0]])
                y = np.array([node1[1], node2[1]])
            else:
                x = np.linspace(node1[0], node2[0], nr_points)
                y = np.linspace(node1[1], node2[1], nr_points)
            # and add them to the shear web node collection, but ignore the
            # first and last nodes because they are already included in
            # the airfoil coordinates.
            # For small arrays this is slightly faster, but for big arrays
            # (which is already true for 30 items and up) it is better to first
            # create them instead of transposing
            # tmp = np.array([x[1:-1], y[1:-1]]).transpose()
            tmp = np.ndarray((len(x) - 2, 2))
            tmp[:, 0] = x[1:-1]
            tmp[:, 1] = y[1:-1]
            # remember to start and stop indices for the shear web nodes
            w.w0_i = len(self.web_coord)
            w.w1_i = len(tmp) + w.w0_i - 1
            try:
                self.web_coord = np.append(self.web_coord, tmp, axis=0)
            except:
                self.web_coord = tmp.copy()

    def adjust_te_panels(self):
        """
        adjust the thickness of the trailing edge panels according
        to the thickness of the trailing edge

        """

        if not self.open_te: return

        # pressure and suction side panels
        dTE = np.abs(self.airfoil[-1, 1] - self.airfoil[0, 1]) / 3.
        r_name = self.cs2d.regions[-1]
        r_TE_suc = getattr(self.cs2d, r_name)
        r_name = self.cs2d.regions[0]
        r_TE_pres = getattr(self.cs2d, r_name)
        thick_max = (r_TE_pres.thickness + r_TE_suc.thickness) / 2.
        ratio = thick_max / dTE
        self._logger.info('TE panel ratio %f %f %f' %
                          (self.cs2d.s, dTE * 3., ratio))
        if ratio > 1.:
            for lname in r_TE_suc.layers:
                layer = getattr(r_TE_suc, lname)
                layer.thickness = layer.thickness / ratio
            for lname in r_TE_pres.layers:
                layer = getattr(r_TE_pres, lname)
                layer.thickness = layer.thickness / ratio
            r_TE_suc.thickness /= ratio
            r_TE_pres.thickness /= ratio

        # trailing edge "web"
        for name in self.cs2d.webs:
            TEw = getattr(self.cs2d, name)
            if TEw.s0 in [-1., 1.]:
                dTE = dTE * 2.
                ratio = r_TE_suc.thickness / TEw.thickness
                for lname in TEw.layers:
                    layer = getattr(TEw, lname)
                    layer.thickness = layer.thickness * ratio
                TEw.thickness *= ratio
                break

    def flatback(self):
        """
        Instead of removing some meshing points, make the TE region as thick
        as the total defined layer thickness in that region.
        """

        # find the tickness in the TE region: first and last region
        r_name = self.cs2d.regions[-1]
        r_TE_suc = getattr(self.cs2d, r_name)
        r_name = self.cs2d.regions[0]
        r_TE_pres = getattr(self.cs2d, r_name)
        # add 10% margin as well for safety
        thick_max = (r_TE_pres.thickness + r_TE_suc.thickness) * 1.1

        # and enforce that thickness on the trailing edge node suction side
        # first, define the trailing edge vector.
        if np.allclose(self.airfoil[-1, :], self.airfoil[0, :]):
            # when TE suction = TE pressure, move upwards vertically
            flatback_thick = 0
            flatback_vect_norm = np.array([0, 1])
        else:
            flatback_vect = self.airfoil[-1, :] - self.airfoil[0, :]
            flatback_thick = np.linalg.norm(flatback_vect)
            flatback_vect_norm = flatback_vect / flatback_thick
        if flatback_thick < thick_max:
            dt_thick = thick_max - flatback_thick
            # add missing thickness by moving the TE suction side upwards
            # along the flatback vector
            self.airfoil[-1, :] += dt_thick * flatback_vect_norm * 0.5
            self.airfoil[0, :] -= dt_thick * flatback_vect_norm * 0.5

    def output_te_ratio(self):
        """
        outputs a ratio between the thickness of the trailing edge panels
        and the thickness of the trailing edge

        """

        if not self.open_te:
            self.te_ratio = 0.
            return

        # pressure and suction side panels
        dTE = np.abs(self.airfoil[-1, 1] - self.airfoil[0, 1]) / 2.
        r_name = self.cs2d.regions[-1]
        r_TE_suc = getattr(self.cs2d, r_name)
        r_name = self.cs2d.regions[0]
        r_TE_pres = getattr(self.cs2d, r_name)
        thick_max = (r_TE_pres.thickness + r_TE_suc.thickness) / 2.
        self.te_ratio = thick_max / dTE
        self._logger.info('TE ratio %f %f %f' %
                          (self.cs2d.s, dTE * 3., self.te_ratio))

    def _check_TE_thickness(self):
        """
        The last point before the trailing edge should still have a thickness
        that is equal or higher than the total layer thickness (suction and
        pressure side combined).

        Two solutions: either reduce the layer thickness, or more simple,
        move the last point before the TE forward (direction of LE) so the
        thickness over that mesh point increases.

        This method looks for the point where the layer thickness equals the
        chord thickness and takes that as the last mesh point before the TE.

        Possible problems arise if a whole region needs to be cut down.
        Also, all the indices in the regions have to be changed...
        From here onwards the two meshes will be disconnected.
        This approach will cause problems if there still is significant
        curvature in this trailing edge area. Maybe a boundary condition
        is more appropriate?
        Possibly verify if the reduced number of points results in a loss
        of area accuracy compared to the original input coordinates
        """

        # find the local tickness of the last trailing edge points
        # TE region is here defined as 15% of the chord
        # FIXME: this approach assumes the thickness can be approximated by
        # the distance between the mesh points with equal index offset from
        # the TE. However, this is by no means guaranteed by the mesh, and will
        # result in some cases a much higher percieved thickness in the TE.
        nr = int(round(len(self.airfoil) * 0.15, 0))
        # calculate the thickness at each pair of nodes
        deltas = self.airfoil[1:nr] - self.airfoil[-nr:-1][::-1]
        if np.__version__ >= '1.8.0':
            thick_loc = np.linalg.norm(deltas, axis=1)
        else:
            thick_loc = np.ndarray((deltas.shape[0], ))
            for i, d in enumerate(deltas):
                thick_loc[i] = np.linalg.norm(d)

        # find the tickness in the TE region: first and last region
        r_name = self.cs2d.regions[-1]
        r_TE_suc = getattr(self.cs2d, r_name)
        r_name = self.cs2d.regions[0]
        r_TE_pres = getattr(self.cs2d, r_name)
        # add 10% margin as well
        thick_max = (r_TE_pres.thickness + r_TE_suc.thickness) * 1.1

        # TODO: before removing we should check what happens if we by accident
        # remove a control point and/or a complete region

        # and see how many nodes we need to ditch in the TE area
        # delete one extra node just to be sure
        nr_remove = thick_loc.__gt__(thick_max).argmax() + 1
        sel = np.ndarray((self.airfoil[0], ), dtype=np.bool)
        sel[:] = True
        sel[1:nr_remove + 1] = False
        sel[-nr_remove - 1:-1] = False
        self.airfoil = self.airfoil[sel, :]

        print "number of removed mesh points at TE: %i" % nr_remove

        # and correct all the indices
        for name in self.cs2d.regions:
            r = getattr(self, name)
            if r.s0_i >= nr_remove:
                r.s0_i -= nr_remove
                r.s1_i -= nr_remove
            elif r.s1_i >= nr_remove:
                r.s1_i -= nr_remove
        # additionally, the TE on the suction side also loses on top of that
        r = getattr(self, self.cs2d.regions[-1])
        r.s1_i -= nr_remove

        for name in self.webs:
            w = getattr(self.cs2d.webs, name)
            w.s0_i -= nr_remove
            w.s1_i -= nr_remove
        for i in xrange(len(self.iWCPs)):
            self.iWCPs[i] -= (nr_remove)
        # first and last are the TE
        for i in xrange(len(self.iCPs[1:])):
            self.iCPs[i + 1] -= (nr_remove)

        # TODO: this should follow the same procedure as for the regions
        self.iCPs[-1] -= nr_remove

    def create_elements(self, debug=False):
        """
        Create the elements and assign element sets to the different regions.

        Assign node and element numbers for the current airfoil points and
        shear webs. Since the airfoil coordinates are ordered clockwise
        continuous the node and element numbering is trivial.

        Note when referring to node and element numbers array indices are used.
        BECAS uses 1-based counting instead of zero-based.
        """

        # by default, the node and element numbers are zero based numbering
        self.onebasednumbering = False

        # convert to BECAS standards if necessary
        # if self.te_le_orientation == 'right-to-left':
        #     self.mirror_airfoil()
        #     print 'forced airfoil input coordinates orientation left-to-right'

        # element definitions for 1D line elements
        # line_element_definitions = {}
        # corresponds to self.elements (index is the element number)

        # element numbers for each ELSET
        self.elset_defs = {}

        nr_air_n = len(self.airfoil)
        nr_web_n = len(self.web_coord)
        nr_nodes = nr_air_n + nr_web_n
        # for closed TE, nr_elements = nr_nodes, for open TE, 1 element less
        if self.open_te:
            nr_elements = nr_nodes + len(self.cs2d.webs) - 1
            nr_air_el = nr_air_n - 1
        else:
            nr_elements = nr_nodes + len(self.cs2d.webs)
            nr_air_el = nr_air_n

        # place all nodal coordinates in one array. The elements are defined
        # by the node index.
        self.nodes = np.zeros((nr_nodes, 3))
        self.nodes[:nr_air_n, :2] = self.airfoil[:, :]
        self.nodes[nr_air_n:, :2] = self.web_coord

        # Elements are bounded by two neighbouring nodes. By closing the
        # circle (connecting the TE suction side with the pressure side), we
        # have as many elements as there are nodes on the airfoil
        # elements[element_nr, (node1,node2)]: shape=(n,2)
        # for each web, we have nr_web_nodes+1 number of elements
        self.elements = np.ndarray((nr_elements, 2), dtype=np.int)
        if self.open_te:
            self.elements[:nr_air_el, 0] = np.arange(nr_air_n - 1,
                                                     dtype=np.int)
            self.elements[:nr_air_el, 1] = self.elements[:nr_air_el, 0] + 1
        else:
            # when the airfoil is closed, add one node number too much...
            self.elements[:nr_air_el, 0] = np.arange(nr_air_n, dtype=np.int)
            self.elements[:nr_air_el, 1] = self.elements[:nr_air_el, 0] + 1
            # last node on last element is first node, airfoil is now closed
            self.elements[nr_air_el - 1, 1] = 0

        if debug:
            print 'nr airfoil nodes: %4i' % (len(self.airfoil))
            print '    nr web nodes: %4i' % len(self.web_coord)

        web_el = []
        pre_side, suc_side = [], []

        # compute TE panel angle
        v0 = np.array(self.CPs[1] - self.CPs[0])
        v1 = np.array(self.CPs[-2] - self.CPs[-1])
        self.TEangle = np.arccos(
            np.dot(v0, v1) /
            (np.linalg.norm(v0) * np.linalg.norm(v1))) * 180. / np.pi

        self._logger.info('TE angle = %3.3f' % self.TEangle)

        # keep track of elements that have been added on the shear webs
        el_offset = 0
        # define el for each shear web, and create corresponding node groups
        for w_name in self.cs2d.webs:
            # starting index for the elements of the web
            iw_start = nr_air_el + el_offset

            w = getattr(self.cs2d, w_name)
            w.is_TE = False
            if w.thickness == 0.: continue

            # number of intermediate shear web elements (those that are not
            # connected to the airfoil)
            nr_el = w.w1_i - w.w0_i

            # define start/stop element indices
            w.e0_i = iw_start
            w.e1_i = nr_el + iw_start + 1

            # shear web nodes run from w.s0 towards w.s1
            # first element is connected to the starting shear web point that
            # sits on the airfoil: nr_el+1
            self.elements[w.e0_i, :] = [w.s0_i, w.w0_i + nr_air_n]

            # elements in between
            wnodes = np.arange(w.w0_i, w.w1_i, dtype=np.int) + nr_air_n
            self.elements[w.e0_i + 1:w.e1_i, 0] = wnodes
            self.elements[w.e0_i + 1:w.e1_i, 1] = wnodes + 1
            # and the final element that connects the web back to the airfoil
            # nr_el+2
            self.elements[w.e1_i, :] = [w.w1_i + nr_air_n, w.s1_i]

            if debug:
                print '%s i_el start: %i' % (w_name, iw_start)
                print '%4i %4i %4i' % (iw_start, w.s0_i, w.w0_i + nr_air_n)
                print '%4i %4i %4i' % (w.e1_i, w.w1_i + nr_air_n, w.s1_i)

            # and now we can populate the different regions with their
            # corresponding elements
            if w.s0 in [-1., 1.] and abs(self.TEangle) > 150.:
                w.is_TE = True
                self.elset_defs[w_name] = np.array([w.e0_i, w.e1_i] + [0],
                                                   dtype=int)
                suc_side.extend([w.e0_i, w.e1_i + 1] + [1])
                self._logger.info('TE web identified! s=%3.3f %s %i %i' %
                                  (self.cs2d.s, w_name, w.s0_i, w.s1_i))
            else:
                self.elset_defs[w_name] = np.arange(w.e0_i,
                                                    w.e1_i + 1,
                                                    dtype=np.int)
                web_el.extend(range(w.e0_i, w.e1_i + 1))

            # add the number of elements added for this web
            el_offset += nr_el + 2

        if len(web_el) > 0:
            self.elset_defs['WEBS'] = np.array(web_el, dtype=np.int)

        # element groups for the regions
        for r_name in self.cs2d.regions:
            r = getattr(self.cs2d, r_name)
            # do not include element r.s1_i, that is included in the next elset
            self.elset_defs[r_name] = np.arange(r.s0_i, r.s1_i, dtype=np.int)

            # group in suction and pressure side (s_LE=0)
            if r.s1 <= 0:
                pre_side.extend([r.s0_i, r.s1_i])
            else:
                suc_side.extend([r.s0_i, r.s1_i])

        tmp = np.array(list(pre_side) + list(suc_side))
        pre0, pre1 = tmp.min(), tmp.max()
        self.elset_defs['SURFACE'] = np.arange(pre0, pre1, dtype=np.int)

        # the last region and the suction side do not include the last element
        # for flatback airfoils. Fix this here.
        # TODO: now the region object s1_i is not updated. Is that relevant?
        # Or could that brake things since the index start-stop is not increasing
        # if not self.open_te:
        #     r_name = self.cs2d.regions[-1]
        #     r = getattr(self.cs2d, r_name)
        #     # last region is the suction side trailing edge
        #     elset = self.elset_defs[r_name]
        #     self.elset_defs[r_name] = np.append(elset, np.array([nr_air_n-1]))
        #     # suction side
        #     elset = self.elset_defs['SUCTION_SIDE']
        #     self.elset_defs['SUCTION_SIDE'] = np.append(elset,
        #                                                 np.array([nr_air_n-1]))

    def create_elements_3d(self, reverse_normals=False):
        """
        Shellexpander wants a 3D section as input. Create a 3D section
        which is just like the 2D version except with a depth defined as 1%
        of the chord length.
        """

        # Compute depth of 3D mesh as 1% of chord lenght
        depth = -0.01 * self.cs2d.airfoil.chord
        if reverse_normals:
            depth = depth * (-1.0)

        nr_nodes_2d = len(self.nodes)
        # Add nodes for 3D mesh
        self.nodes_3d = np.ndarray((nr_nodes_2d * 2, 3))
        self.nodes_3d[:nr_nodes_2d, :] = self.nodes
        self.nodes_3d[nr_nodes_2d:, :] = self.nodes + np.array([0, 0, depth])
        # Generate shell elements
        self.el_3d = np.ndarray((len(self.elements), 4), dtype=np.int)
        self.el_3d[:, :2] = self.elements
        self.el_3d[:, 2] = self.elements[:, 1] + nr_nodes_2d
        self.el_3d[:, 3] = self.elements[:, 0] + nr_nodes_2d

        # same as line_element_definitions, but now expanded over thickness
        # to create a 3D shell element
        # element_definitions = {}
        # corresponds to self.el_3d

    def one_based_numbering(self):
        """
        instead of 0, 1 is the first element and node number. All nodes and
        elements +1.

        Note that this does not affect the indices defined in the region
        and web attributes
        """
        if not self.onebasednumbering:

            self.elements += 1
            self.el_3d += 1
            for elset in self.elset_defs:
                self.elset_defs[elset] += 1

            for name in self.cs2d.regions:
                r = getattr(self.cs2d, name)
                r.s0_i += 1
                r.s1_i += 1
            # webs refer to indices in self.web_coord, which is independent
            # of self.airfoil
#            for name in self._webs:
#                w = getattr(self, name)
#                w.s0_i += 1
#                w.s1_i += 1
            for i in xrange(len(self.iWCPs)):
                self.iWCPs[i] += 1
            for i in xrange(len(self.iCPs)):
                self.iCPs[i] += 1

            self.onebasednumbering = True

    def zero_based_numbering(self):
        """
        switch back to 0 as first element and node number
        """

        if self.onebasednumbering:

            self.elements -= 1
            self.el_3d -= 1
            for elset in self.elset_defs:
                self.elset_defs[elset] -= 1

            for name in self.cs2d.regions:
                r = getattr(self, name)
                r.s0_i -= 1
                r.s1_i -= 1
            # webs refer to indices in self.web_coord, which is independent
            # of self.
#            for name in self._webs:
#                w = getattr(self, name)
#                w.s0_i -= 1
#                w.s1_i -= 1
            for i in xrange(len(self.iWCPs)):
                self.iWCPs[i] -= 1
            for i in xrange(len(self.iCPs)):
                self.iCPs[i] -= 1

            self.onebasednumbering = False

    def check_airfoil(self):

        for rname in self.cs2d.regions:
            r = getattr(self.cs2d, rname)
            print 'Checking %s' % rname
            for lname in r.layers:
                print '    Checking %s' % lname
                l = getattr(r, lname)
                if l.thickness <= 0.:
                    print 'ERROR! Layer %s in Region %s has negative thickness: %f' % (
                        lname, rname, l.thickness)
        for rname in self.cs2d.webs:
            print 'Checking %s' % rname
            r = getattr(self.cs2d, rname)
            for lname in r.layers:
                print '    Checking %s' % lname
                l = getattr(r, lname)
                if l.thickness <= 0.:
                    print 'ERROR! Layer %s in Region %s has negative thickness: %f' % (
                        lname, rname, l.thickness)

    def write_abaqus_inp(self, fname=False):
        """Create Abaqus inp file which will be served to shellexpander so
        the actual BECAS input can be created.
        """
        def write_n_int_per_line(list_of_int, f, n):
            """Write the integers in list_of_int to the output file - n integers
            per line, separated by commas"""
            i = 0
            for number in list_of_int:
                i = i + 1
                f.write('%d' % (number))
                if i < len(list_of_int):
                    f.write(',  ')
                if i % n == 0:
                    f.write('\n')
            if i % n != 0:
                f.write('\n')

        self.abaqus_inp_fname = 'airfoil_abaqus.inp'

        # FIXME: for now, force 1 based numbering, I don't think shellexpander
        # and/or BECAS like zero based node and element numbering
        self.one_based_numbering()

        # where to start node/element numbering, 0 or 1?
        if self.onebasednumbering:
            off = 1
        else:
            off = 0

        with open(self.abaqus_inp_fname, 'w') as f:

            # Write nodal coordinates
            f.write('**\n')
            f.write('********************\n')
            f.write('** NODAL COORDINATES\n')
            f.write('********************\n')
            f.write('*NODE\n')
            tmp = np.ndarray((len(self.nodes_3d), 4))
            tmp[:, 0] = np.arange(len(self.nodes_3d), dtype=np.int) + off
            tmp[:, 1:] = self.nodes_3d
            np.savetxt(f, tmp, fmt='%1.0f, %1.8e, %1.8e, %1.8e')

            # Write element definitions
            f.write('**\n')
            f.write('***********\n')
            f.write('** ELEMENTS\n')
            f.write('***********\n')
            f.write('*ELEMENT, TYPE=S4, ELSET=%s\n' % self.section_name)
            tmp = np.ndarray((len(self.el_3d), 5))
            tmp[:, 0] = np.arange(len(self.el_3d), dtype=np.int) + off
            tmp[:, 1:] = self.el_3d
            np.savetxt(f, tmp, fmt='%i, %i, %i, %i, %i')

            # Write new element sets
            f.write('**\n')
            f.write('***************\n')
            f.write('** ELEMENT SETS\n')
            f.write('***************\n')
            for elset in sorted(self.elset_defs.keys()):
                elements = self.elset_defs[elset]
                f.write('*ELSET, ELSET=%s\n' % (elset))
                #                np.savetxt(f, elements, fmt='%i', delimiter=', ')
                write_n_int_per_line(list(elements), f, 8)

            # Write Shell Section definitions
            # The first layer is the outer most layer.
            # The second item ("int. points") and the fifth item ("plyname")
            # are not relevant. The are kept for compatibility with the ABAQUS
            # input syntax. As an example, take this one:
            # [0.006, 3, 'TRIAX', 0.0, 'Ply01']
            f.write('**\n')
            f.write('****************************\n')
            f.write('** SHELL SECTION DEFINITIONS\n')
            f.write('****************************\n')
            for i, r_name in enumerate(self.cs2d.regions + self.cs2d.webs):
                r = getattr(self.cs2d, r_name)
                text = '*SHELL SECTION, ELSET=%s, COMPOSITE, OFFSET=-0.5\n'
                f.write(text % (r_name))
                for l_name in r.layers:
                    lay = getattr(r, l_name)
                    md = self.cs2d.materials[lay.materialname.lower()]
                    if md.failure_criterium == 'maximum_stress':
                        mname = lay.materialname + 'MAXSTRESS'
                    elif md.failure_criterium == 'maximum_strain':
                        mname = lay.materialname + 'MAXSTRAIN'
                    elif md.failure_criterium == 'tsai_wu':
                        mname = lay.materialname + 'TSAIWU'
                    else:
                        mname = lay.materialname
                    if lay.plyname == '': lay.plyname = 'ply%02d' % i

                    layer_def = (lay.thickness, 3, mname, lay.angle,
                                 lay.plyname)
                    f.write('%g, %d, %s, %g, %s\n' % layer_def)

            # Write material properties
            f.write('**\n')
            f.write('**********************\n')
            f.write('** MATERIAL PROPERTIES\n')
            f.write('**********************\n')
            for matname in sorted(self.cs2d.materials.keys()):
                md = self.cs2d.materials[matname]
                if md.failure_criterium == 'maximum_stress':
                    mname = matname + 'MAXSTRESS'
                elif md.failure_criterium == 'maximum_strain':
                    mname = matname + 'MAXSTRAIN'
                elif md.failure_criterium == 'tsai_wu':
                    mname = matname + 'TSAIWU'
                else:
                    mname = matname
                f.write('*MATERIAL, NAME=%s\n' % (mname))
                f.write('*ELASTIC, TYPE=ENGINEERING CONSTANTS\n')
                f.write('%g, %g, %g, %g, %g, %g, %g, %g\n' %
                        (md.E1, md.E2, md.E3, md.nu12, md.nu13, md.nu23,
                         md.G12, md.G13))
                f.write('%g\n' % (md.G23))
                f.write('*DENSITY\n')
                f.write('%g\n' % (md.rho))
                f.write('*FAIL STRESS\n')
                gMa = md.gM0 * (md.C1a + md.C2a + md.C3a + md.C4a)
                f.write('%g, %g, %g, %g, %g\n' %
                        (gMa * md.s11_t, gMa * md.s11_c, gMa * md.s22_t,
                         gMa * md.s22_c, gMa * md.t12))
                f.write('*FAIL STRAIN\n')
                f.write('%g, %g, %g, %g, %g\n' %
                        (gMa * md.e11_t, gMa * md.e11_c, gMa * md.e22_t,
                         gMa * md.e22_c, gMa * md.g12))
                f.write('**\n')
        print 'Abaqus input file written: %s' % self.abaqus_inp_fname

    def write_becas_inp(self):
        """
        When write_abaqus_inp has been executed we have the shellexpander
        script that can create the becas input

        Dominant regions should be the spar caps.
        """
        class args:
            pass

        # the he name of the input file containing the finite element shell model
        args.inputfile = self.abaqus_inp_fname  #--in
        # The element sets to be considered (required). If more than one
        # element set is given, nodes appearing in more the one element sets
        # are considered "corners". Should be pressureside, suction side and
        # the webs
        elsets = []
        target_sets = ['SURFACE', 'WEBS']
        for elset in target_sets:
            if elset in self.elset_defs:
                elsets.append(elset)
        if len(elsets) < 1:
            raise ValueError, 'badly defined element sets'
        args.elsets = elsets  #--elsets, list
        args.sections = self.section_name  #--sec
        args.layers = self.max_layers  #--layers
        args.nodal_thickness = 'min'  #--ntick, choices=['min','max','average']
        # TODO: take the most thick regions (spar caps) as dominant
        args.dominant_elsets = self.dominant_elsets  #--dom, list
        args.centerline = None  #--cline, string
        args.becasdir = self.becas_inputs  #--bdir
        args.debug = False  #--debug, if present switch to True

        import imp
        shellexpander = imp.load_source(
            'shellexpander',
            os.path.join(self.path_shellexpander, 'shellexpander.py'))

        shellexpander.main(args)

    def plot_airfoil(self, ax, line_el_nr=True):
        """
        """

        #        if self.te_le_orientation == 'left-to-right':
        #            self.mirror_airfoil()

        nr = self.total_points_input
        points_actual = self.airfoil.shape[0] + self.web_coord.shape[0]
        title = '%i results in %i actual points' % (nr, points_actual)
        ax.set_title(title)
        # the original coordinates from the file
        ax.plot(self.cs2d.airfoil.points[:, 0],
                self.cs2d.airfoil.points[:, 1],
                'b--o',
                alpha=0.3,
                label='airfoil')
        ax.plot(self.airfoil[:, 0],
                self.airfoil[:, 1],
                'k-s',
                mfc='k',
                label='distfunc',
                alpha=0.3,
                ms=10)

        # plot all (web)control points
        ax.plot(np.array(self.CPs)[:, 0],
                np.array(self.CPs)[:, 1],
                'rx',
                markersize=7,
                markeredgewidth=1.2,
                label='CPs')
        ax.plot(np.array(self.WCPs)[:, 0],
                np.array(self.WCPs)[:, 1],
                'gx',
                markersize=7,
                markeredgewidth=1.2,
                label='WCPs')

        # where to start node/element numbering, 0 or 1?
        if self.onebasednumbering:
            off = 1
        else:
            off = 0

        # see if the indices to the control points are what we think they are
        iCPs = np.array(self.iCPs, dtype=np.int) - off
        iWCPs = np.array(self.iWCPs, dtype=np.int) - off
        x, y = self.airfoil[iCPs, 0], self.airfoil[iCPs, 1]
        ax.plot(x, y, 'r+', markersize=12, markeredgewidth=1.2, label='iCPs')
        x, y = self.airfoil[iWCPs, 0], self.airfoil[iWCPs, 1]
        ax.plot(x, y, 'g+', markersize=12, markeredgewidth=1.2, label='iWCPs')

        # plot the webs
        for iweb, w_name in enumerate(self.cs2d.webs):
            w = getattr(self.cs2d, w_name)
            webx = [self.airfoil[self.iWCPs[iweb] - off, 0]]
            weby = [self.airfoil[self.iWCPs[iweb] - off, 1]]
            webx.extend(self.web_coord[w.w0_i:w.w1_i + 1, 0])
            weby.extend(self.web_coord[w.w0_i:w.w1_i + 1, 1])
            webx.append(self.airfoil[self.iWCPs[-iweb - 1] - off, 0])
            weby.append(self.airfoil[self.iWCPs[-iweb - 1] - off, 1])
            ax.plot(webx, weby, 'g.-')


#            ax.plot(self.web_coord[w.w0_i:w.w1_i+1,0],
#                    self.web_coord[w.w0_i:w.w1_i+1,1], 'g.-')

## add the element numbers
#print 'nr airfoil nodes: %i' % len(b.airfoil)
#print '    nr web nodes: %i' % len(b.web_coord)
#print 'el_nr  node1 node2'
#for nr, el in enumerate(b.elements):
#    print '%5i  %5i %5i' % (nr, el[0], el[1])
        bbox = dict(
            boxstyle="round",
            alpha=0.8,
            edgecolor=(1., 0.5, 0.5),
            facecolor=(1., 0.8, 0.8),
        )

        # verify all the element numbers
        if line_el_nr:
            for nr, el in enumerate(self.elements):
                # -1 to account for 1 based element numbers instead of 0-based
                p1, p2 = self.nodes[el[0] - off, :], self.nodes[el[1] - off, :]
                x, y, z = (p1 + p2) / 2.0
                ax.text(x,
                        y,
                        str(nr),
                        fontsize=7,
                        verticalalignment='bottom',
                        horizontalalignment='center',
                        bbox=bbox)

        return ax
Beispiel #3
0
def make_cs2d():

    cs2d = CrossSectionStructureVT()
    cs2d.airfoil.initialize(np.loadtxt('ffaw3301.dat'))
    # DEFINE MATERIALS
    uniax = MaterialProps()
    uniax.E1 = 40e9
    uniax.E2 = 10e9
    uniax.E3 = 10e9
    uniax.nu12 = 0.28
    uniax.nu13 = 0.28
    uniax.nu23 = 0.4
    uniax.G12 = 4e9
    uniax.G13 = 4e9
    uniax.G23 = 3.571e9
    uniax.rho = 1900
    uniax.materialname = 'uniax'

    biax = MaterialProps()
    biax.E1 = 12e9
    biax.E2 = 12e9
    biax.E3 = 10e9
    biax.nu12 = 0.5
    biax.nu13 = 0.28
    biax.nu23 = 0.28
    biax.G12 = 10e9
    biax.G13 = 3.8e9
    biax.G23 = 3.8e9
    biax.rho = 1890
    biax.materialname = 'biax'

    triax = MaterialProps()
    triax.E1 = 20e9
    triax.E2 = 10e9
    triax.E3 = 10e9
    triax.nu12 = 0.5
    triax.nu13 = 0.28
    triax.nu23 = 0.28
    triax.G12 = 7.5e9
    triax.G13 = 4e9
    triax.G23 = 4e9
    triax.rho = 1860
    triax.materialname = 'triax'

    core = MaterialProps()
    core.E1 = 50e6
    core.E2 = 50e6
    core.E3 = 50e6
    core.nu12 = 0.4
    core.nu13 = 0.4
    core.nu23 = 0.4
    core.G12 = 17.857e6
    core.G13 = 17.857e6
    core.G23 = 17.857e6
    core.rho = 80
    core.materialname = 'core'

    # add materials to airfoil
    cs2d.add_material('uniax', uniax)
    cs2d.add_material('biax', biax)
    cs2d.add_material('triax', triax)
    cs2d.add_material('core', core)

    # --------
    # Region 1: TE
    # --------
    r = cs2d.add_region('REGION01')
    r.s0 = -1.00
    r.s1 = -0.90

    l = r.add_layer('l1')
    l.thickness = .006
    l.angle = 0
    l.materialname = 'TRIAX'
    l.plyname = 'Ply01'

    # --------
    # Region 2: TE sandwich
    # --------
    r = cs2d.add_region('REGION02')
    r.s0 = -0.90
    r.s1 = -0.50

    l = r.add_layer('l1')
    l.thickness = .002
    l.angle = 0
    l.materialname = 'TRIAX'
    l.plyname = 'Ply02'

    l = r.add_layer('l2')
    l.thickness = .015
    l.angle = 0
    l.materialname = 'CORE'
    l.plyname = 'Ply03'

    l = r.add_layer('l3')
    l.thickness = .002
    l.angle = 0
    l.materialname = 'TRIAX'
    l.plyname = 'Ply04'

    # --------
    # Region 3: spar cap
    # --------
    r = cs2d.add_region('REGION03')
    r.s0 = -0.50
    r.s1 = -0.20

    l = r.add_layer('l1')
    l.thickness = .004
    l.angle = 0
    l.materialname = 'UNIAX'
    l.plyname = 'Ply02'

    # --------
    # Region 4: LE edge sandwich
    # --------
    r = cs2d.add_region('REGION04')
    r.s0 = -0.20
    r.s1 = 0.20

    l = r.add_layer('l1')
    l.thickness = .002
    l.angle = 0
    l.materialname = 'TRIAX'
    l.plyname = 'Ply06'

    l = r.add_layer('l2')
    l.thickness = .010
    l.angle = 0
    l.materialname = 'CORE'
    l.plyname = 'Ply07'

    l = r.add_layer('l3')
    l.thickness = .002
    l.angle = 0
    l.materialname = 'TRIAX'
    l.plyname = 'Ply08'

    # -----------
    # Regions 5-8
    # -----------
    r = cs2d.add_region('REGION05')
    cs2d.REGION05 = cs2d.REGION03.copy()
    cs2d.REGION05.s0 = 0.20
    cs2d.REGION05.s1 = 0.50
    r = cs2d.add_region('REGION06')
    cs2d.REGION06 = cs2d.REGION02.copy()
    cs2d.REGION06.s0 = 0.5
    cs2d.REGION06.s1 = 0.9
    r = cs2d.add_region('REGION07')
    cs2d.REGION07 = cs2d.REGION01.copy()
    cs2d.REGION07.s0 = 0.9
    cs2d.REGION07.s1 = 1.0

    # --------
    # Webs
    # --------
    r = cs2d.add_web('WEB00')
    r.s0 = 1.
    r.s1 = -1.
    l = r.add_layer('l1')
    l.thickness = .002
    l.angle = 0
    l.materialname = 'BIAX'
    l.plyname = 'Ply09'

    r = cs2d.add_web('WEB01')
    r.s0 = -.5
    r.s1 = 0.5
    l = r.add_layer('l1')
    l.thickness = .002
    l.angle = 0
    l.materialname = 'BIAX'
    l.plyname = 'Ply09'

    l = r.add_layer('l2')
    l.thickness = .010
    l.angle = 0
    l.materialname = 'CORE'
    l.plyname = 'Ply10'

    l = r.add_layer('l3')
    l.thickness = .002
    l.angle = 0
    l.materialname = 'BIAX'
    l.plyname = 'Ply11'

    r = cs2d.add_web('WEB02')
    cs2d.WEB02 = cs2d.WEB01.copy()
    cs2d.WEB02.s0 = -0.2
    cs2d.WEB02.s1 = 0.2

    # =============================================================================
    ### REMESH AIRFOIL, CREATE BECAS INPUT FILES
    # =============================================================================
    # Number of mesh points along the airfoil. Should be higher as the number of
    # points in the airfoil input file
    b = CS2DtoBECAS()
    b.cs2d = cs2d
    b.total_points = 150
    # dominant regions: spar caps
    b.dominant_elsets = ['REGION03', 'REGION05']
    b.path_shellexpander = '/Users/frza/svn/BECAS/shellexpander/src'
    b.run()
    return b
Beispiel #4
0
    def execute(self):
        """
        generate cross sections at every spanwise node of the st3d vartree
        """

        # clear list of outputs!
        self.cs2d = []

        ni = self.st3d.x.shape[0]
        for i in range(ni):
            x = self.st3d.x[i]
            # print 'adding section at r/R = %2.2f' % x 
            st2d = CrossSectionStructureVT()
            st2d.s = x * self.blade_length
            st2d.DPs = []
            try:
                airfoil = self.surface.interpolate_profile(x)[:, [0, 1]] * self.blade_length
                st2d.airfoil.initialize(airfoil)
            except:
                pass
            for ir, rname in enumerate(self.st3d.regions):
                reg = getattr(self.st3d, rname)
                if reg.thickness[i] == 0.:
                    print 'zero thickness region!', rname
                    # continue
                DP0 = getattr(self.st3d, 'DP%02d' % ir)
                DP1 = getattr(self.st3d, 'DP%02d' % (ir + 1))
                r = st2d.add_region(rname.upper())
                st2d.DPs.append(DP0[i])
                r.s0 = DP0[i]
                r.s1 = DP1[i]
                for lname in reg.layers:
                    lay = getattr(reg, lname)
                    if lay.thickness[i] > 1.e-5: 
                        l = r.add_layer(lname)
                        # try:
                        lnamebase = lname.translate(None, digits)
                        st2d.add_material(lnamebase, self.get_material(lname).copy())
                        # except:
                            # raise RuntimeError('Material %s not in materials list' % lname)
                        l.materialname = lnamebase
                        l.thickness = max(0., lay.thickness[i])
                        try:
                            l.angle = lay.angle[i]
                        except:
                            l.angle = 0.

            st2d.DPs.append(DP1[i])
            for ir, rname in enumerate(self.st3d.webs):
                reg = getattr(self.st3d, rname)
                if reg.thickness[i] == 0.:
                    continue
                r = st2d.add_web(rname.upper())
                try:
                    DP0 = getattr(self.st3d, 'DP%02d' % self.st3d.iwebs[ir][0])
                except:
                    DP0 = getattr(self.st3d, 'DP%02d' % (len(self.st3d.regions) + self.st3d.iwebs[ir][0] + 1))
                try:
                    DP1 = getattr(self.st3d, 'DP%02d' % self.st3d.iwebs[ir][1])
                except:
                    DP1 = getattr(self.st3d, 'DP%02d' % (len(self.st3d.regions) + self.st3d.iwebs[ir][1] + 1))
                r.s0 = DP0[i] 
                r.s1 = DP1[i]
                for lname in reg.layers:
                    lay = getattr(reg, lname)
                    if lay.thickness[i] > 1.e-5:
                        l = r.add_layer(lname)
                        try:
                            lnamebase = lname.translate(None, digits)
                            st2d.add_material(lnamebase, self.get_material(lname).copy())
                        except:
                            raise RuntimeError('Material %s not in materials list' % lname)
                        l.materialname = lnamebase
                        l.thickness = max(0., lay.thickness[i])
                        try:
                            l.angle = lay.angle[i]
                        except:
                            l.angle = 0.

            self.cs2d.append(st2d)
def make_cs2d():

    cs2d = CrossSectionStructureVT()
    cs2d.airfoil.initialize(np.loadtxt('ffaw3301.dat'))
    # DEFINE MATERIALS
    uniax = MaterialProps()
    uniax.E1 = 40e9
    uniax.E2 = 10e9
    uniax.E3 = 10e9
    uniax.nu12 = 0.28
    uniax.nu13 = 0.28
    uniax.nu23 = 0.4
    uniax.G12 = 4e9
    uniax.G13 = 4e9
    uniax.G23 = 3.571e9
    uniax.rho = 1900
    uniax.materialname = 'uniax'

    biax = MaterialProps()
    biax.E1 = 12e9
    biax.E2 = 12e9
    biax.E3 = 10e9
    biax.nu12 = 0.5
    biax.nu13 = 0.28
    biax.nu23 = 0.28
    biax.G12 = 10e9
    biax.G13 = 3.8e9
    biax.G23 = 3.8e9
    biax.rho = 1890
    biax.materialname = 'biax'

    triax = MaterialProps()
    triax.E1 = 20e9
    triax.E2 = 10e9
    triax.E3 = 10e9
    triax.nu12 = 0.5
    triax.nu13 = 0.28
    triax.nu23 = 0.28
    triax.G12 = 7.5e9
    triax.G13 = 4e9
    triax.G23 = 4e9
    triax.rho = 1860
    triax.materialname = 'triax'

    core = MaterialProps()
    core.E1 = 50e6
    core.E2 = 50e6
    core.E3 = 50e6
    core.nu12 = 0.4
    core.nu13 = 0.4
    core.nu23 = 0.4
    core.G12 = 17.857e6
    core.G13 = 17.857e6
    core.G23 = 17.857e6
    core.rho = 80
    core.materialname = 'core'

    # add materials to airfoil
    cs2d.add_material('uniax', uniax)
    cs2d.add_material('biax', biax)
    cs2d.add_material('triax', triax)
    cs2d.add_material('core', core)

    # --------
    # Region 1: TE
    # --------
    r = cs2d.add_region('REGION01')
    r.s0 = -1.00
    r.s1 = -0.90

    l = r.add_layer('l1')
    l.thickness = .006
    l.angle = 0
    l.materialname = 'TRIAX'
    l.plyname = 'Ply01'

    # --------
    # Region 2: TE sandwich
    # --------
    r = cs2d.add_region('REGION02')
    r.s0 = -0.90
    r.s1 = -0.50

    l = r.add_layer('l1')
    l.thickness = .002
    l.angle = 0
    l.materialname = 'TRIAX'
    l.plyname = 'Ply02'

    l = r.add_layer('l2')
    l.thickness = .015
    l.angle = 0
    l.materialname = 'CORE'
    l.plyname = 'Ply03'

    l = r.add_layer('l3')
    l.thickness = .002
    l.angle = 0
    l.materialname = 'TRIAX'
    l.plyname = 'Ply04'

    # --------
    # Region 3: spar cap
    # --------
    r = cs2d.add_region('REGION03')
    r.s0 = -0.50
    r.s1 = -0.20

    l = r.add_layer('l1')
    l.thickness = .004
    l.angle = 0
    l.materialname = 'UNIAX'
    l.plyname = 'Ply02'

    # --------
    # Region 4: LE edge sandwich
    # --------
    r = cs2d.add_region('REGION04')
    r.s0 = -0.20
    r.s1 = 0.20

    l = r.add_layer('l1')
    l.thickness = .002
    l.angle = 0
    l.materialname = 'TRIAX'
    l.plyname = 'Ply06'

    l = r.add_layer('l2')
    l.thickness = .010
    l.angle = 0
    l.materialname = 'CORE'
    l.plyname = 'Ply07'

    l = r.add_layer('l3')
    l.thickness = .002
    l.angle = 0
    l.materialname = 'TRIAX'
    l.plyname = 'Ply08'

    # -----------
    # Regions 5-8
    # -----------
    r = cs2d.add_region('REGION05')
    cs2d.REGION05 = cs2d.REGION03.copy()
    cs2d.REGION05.s0 = 0.20
    cs2d.REGION05.s1 = 0.50
    r = cs2d.add_region('REGION06')
    cs2d.REGION06 = cs2d.REGION02.copy()
    cs2d.REGION06.s0 = 0.5
    cs2d.REGION06.s1 = 0.9
    r = cs2d.add_region('REGION07')
    cs2d.REGION07 = cs2d.REGION01.copy()
    cs2d.REGION07.s0 = 0.9
    cs2d.REGION07.s1 = 1.0

    # --------
    # Webs
    # --------
    r = cs2d.add_web('WEB00')
    r.s0 = 1.
    r.s1 = -1.
    l = r.add_layer('l1')
    l.thickness = .002
    l.angle = 0
    l.materialname = 'BIAX'
    l.plyname = 'Ply09'

    r = cs2d.add_web('WEB01')
    r.s0 = -.5
    r.s1 = 0.5
    l = r.add_layer('l1')
    l.thickness = .002
    l.angle = 0
    l.materialname = 'BIAX'
    l.plyname = 'Ply09'

    l = r.add_layer('l2')
    l.thickness = .010
    l.angle = 0
    l.materialname = 'CORE'
    l.plyname = 'Ply10'

    l = r.add_layer('l3')
    l.thickness = .002
    l.angle = 0
    l.materialname = 'BIAX'
    l.plyname = 'Ply11'

    r = cs2d.add_web('WEB02')
    cs2d.WEB02 = cs2d.WEB01.copy()
    cs2d.WEB02.s0 = -0.2
    cs2d.WEB02.s1 =  0.2

    # =============================================================================
    ### REMESH AIRFOIL, CREATE BECAS INPUT FILES
    # =============================================================================
    # Number of mesh points along the airfoil. Should be higher as the number of
    # points in the airfoil input file
    b = CS2DtoBECAS()
    b.cs2d = cs2d
    b.total_points = 150
    # dominant regions: spar caps
    b.dominant_elsets = ['REGION03', 'REGION05']
    b.path_shellexpander = '/Users/frza/svn/BECAS/shellexpander/src'
    b.run()
    return b