Exports surface(s) as a .obj file (string).

    Keyword Arguments:
        * ``vertex_spacing``: size of the triangle edge in terms of surface points sampled. *Default: 2*
        * ``vertex_normals``: if True, then computes vertex normals. *Default: False*
        * ``parametric_vertices``: if True, then adds parameter space vertices. *Default: False*
        * ``update_delta``: use multi-surface evaluation delta for all surfaces. *Default: True*

    :param surface: surface or surfaces to be saved
    :type surface: abstract.Surface or multi.SurfaceContainer
    :return: contents of the .obj file generated
    :rtype: str

def export_obj_str(surface, **kwargs):
    """ Exports surface(s) as a .obj file (string).

    Keyword Arguments:
        * ``vertex_spacing``: size of the triangle edge in terms of surface points sampled. *Default: 2*
        * ``vertex_normals``: if True, then computes vertex normals. *Default: False*
        * ``parametric_vertices``: if True, then adds parameter space vertices. *Default: False*
        * ``update_delta``: use multi-surface evaluation delta for all surfaces. *Default: True*

    :param surface: surface or surfaces to be saved
    :type surface: abstract.Surface or multi.SurfaceContainer
    :return: contents of the .obj file generated
    :rtype: str
    """
    # Get keyword arguments
    vertex_spacing = int(kwargs.get('vertex_spacing', 1))
    include_vertex_normal = kwargs.get('vertex_normals', False)
    include_param_vertex = kwargs.get('parametric_vertices', False)
    update_delta = kwargs.get('update_delta', True)

    # Input validity checking
    if surface.pdimension != 2:
        raise exch.GeomdlException("Can only export surfaces")
    if vertex_spacing < 1:
        raise exch.GeomdlException("Vertex spacing should be bigger than zero")

    # Create the string and start adding triangulated surface points
    line = "# Generated by geomdl\n"
    vertex_offset = 0  # count the vertices to update the face numbers correctly

    # Initialize lists for geometry data
    str_v = []  # vertices
    str_vn = []  # vertex normals
    str_vp = []  # parameter space vertices
    str_f = []  # faces

    # Loop through SurfaceContainer object
    for srf in surface:
        # Set surface evaluation delta
        if update_delta:
            srf.sample_size_u = surface.sample_size_u
            srf.sample_size_v = surface.sample_size_v

        # Tessellate surface
        srf.tessellate(vertex_spacing=vertex_spacing)
        vertices = srf.tessellator.vertices
        triangles = srf.tessellator.faces

        # Collect vertices
        for vert in vertices:
            temp = "v " + str(vert.x) + " " + str(vert.y) + " " + str(vert.z) + "\n"
            str_v.append(temp)

        # Collect parameter space vertices
        if include_param_vertex:
            for vert in vertices:
                temp = "vp " + str(vert.uv[0]) + " " + str(vert.uv[1]) + "\n"
                str_vp.append(temp)

        # Compute vertex normals
        if include_vertex_normal:
            for vert in vertices:
                sn = operations.normal(srf, vert.uv)
                temp = "vn " + str(sn[1][0]) + " " + str(sn[1][1]) + " " + str(sn[1][2]) + "\n"
                str_vn.append(temp)

        # Collect faces (1-indexed)
        for t in triangles:
            vl = t.data
            temp = "f " + \
                   str(vl[0] + 1 + vertex_offset) + " " + \
                   str(vl[1] + 1 + vertex_offset) + " " + \
                   str(vl[2] + 1 + vertex_offset) + "\n"
            str_f.append(temp)

        # Update vertex offset
        vertex_offset = len(str_v)

    # Write all collected data to the return string
    for lv in str_v:
        line += lv
    for lvn in str_vn:
        line += lvn
    for lvp in str_vp:
        line += lvp
    for lf in str_f:
        line += lf

    return line