Ejemplo n.º 1
0
def export_grid_msh(gid, fname, periodic_pairs=[]):
    """Exports grid to fluent msh format.

    :param gid: 2d grid file identifier or list of identifiers.

    :param str fname: output filename

    :param list periodic_pairs:
      ``[b-periodic0, b-shadow0, is_reversed0, b-periodic1,
      b-shadow1, is_reversed1, ...]`` list defining periodic boundaries.

      Each periodic condition is defined by three values:

      * ``b-periodic`` - boundary identifier for periodic contour segment
      * ``b-shadow`` - boundary identifier for shadow contour segment
      * ``is_reversed`` - boolean which defines whether shadow contour segment
        should be reversed so that first point of periodic segment be
        equivalent to last point of shadow segment

      Periodic and shadow boundary segments should be singly connected and
      topologically equivalent.

    :returns: None

    Only grids with triangle/quadrangle cells could be exported.
    """
    icheck(0, UListOr1(Grid2D()))
    icheck(1, String())
    icheck(2, CompoundList(ZType(), ZType(), Bool()))

    cb = flow.interface.ask_for_callback()
    grid = _grid2_from_id(gid)
    bt = flow.receiver.get_zone_types()
    fluent_export.grid2(fname, grid, bt, periodic_pairs, cb)
Ejemplo n.º 2
0
def export3d_grid_msh(gid, fname, periodic_pairs=[]):
    """Exports 3D grid to fluent msh ascii format.

    :param gid: 3D grid file identifier or list of identifiers

    :param str grid: filename for output

    :param list periodic_pairs:
       ``[periodic-0, shadow-0, periodic-point-0, shadow-point-0,
       periodic-1, shadow-1, periodic-point-1, ...]``

       Each periodic pair is defined by four values:

       * ``periodic`` - boundary identifier for periodic surface
       * ``shadow`` - boundary identifier for shadow surface
       * ``periodic-point`` - point in [x, y, z] format on periodic contour
       * ``shadow-point`` - point in [x, y, z] format on shadow contour

       Given points will be projected to closest vertex on the boundaries
       of respective subsurfaces.

       Periodic and shadow subsurfaces should be singly connected and
       topologically equivalent with respect to given points.
       For surface 2D topology definition periodic/shadow surfaces are taken
       with outside/inside normals respectively.

    """
    icheck(0, UListOr1(Grid3D()))
    icheck(1, String())
    icheck(2, CompoundList(ZType(), ZType(), Point3D(), Point3D()))

    cb = flow.interface.ask_for_callback()
    grid = _grid3_from_id(gid)
    bt = flow.receiver.get_zone_types()
    fluent_export.grid3(fname, grid, bt, periodic_pairs, cb)
Ejemplo n.º 3
0
def create_spline_contour(pnts, bnds=0, nedges=100):
    """ Creates singly connected contour as a parametric cubic spline.

    :param list-of-list-of-floats pnts: sequence of points.
         If coordinates of first and last points are equal
         then resulting contour will be closed.

    :param single-or-list-of-boundary-identifiers bnds: boundary type for
         each contour segment bounded by **pnts**
         or single identifier for the whole contour.

    :param int nedges: number of line segments of resulting contour.
         Should be equal or greater than the number of sections defined by
         **pnts**.

    :returns: contour identifier
    """
    icheck(0, List(Point2D(), minlen=3))
    icheck(1, ListOr1(ZType(), llen=len(pnts) - 1))
    icheck(2, UInt(minv=len(pnts) - 1))

    b = bnds if isinstance(bnds, list) else [bnds]
    c = com.contcom.CreateSpline({"points": pnts, "bnds": b, "nedges": nedges})
    flow.exec_command(c)
    return c.added_contours2()[0]
Ejemplo n.º 4
0
def add_triangle_grid(p0, p1, p2, nedge, bnd=0):
    """Creates structured grid in triangle area

    :param list-of-floats p0:

    :param list-of-floats p1:

    :param list-of-floats p2: triangle vertices in [x, y] format

    :param int nedge: partition of triangle edges

    :param int-or-list-of-int bnd: boundary types for outer contour

    :return: identifier of newly created grid

    Resulting grid will contain quadrangle cells everywhere except
    area near ``p0``-``p2`` edge where triangle cells will be built.
    """
    icheck(0, Point2D())
    icheck(1, Point2D(noteq=[p0]))
    icheck(2, Point2D(noteq=[p0, p1]))
    icheck(3, UInt(minv=1))
    icheck(4, ListOr1(ZType(), llen=3))

    bnd = bnd[:3] if isinstance(bnd, list) else [bnd, bnd, bnd]
    c = com.gridcom.AddTriGrid({
        "vertices": [p0, p1, p2],
        "nedge": nedge,
        "bnd": bnd
    })
    flow.exec_command(c)
    return c.added_grids2()[0]
Ejemplo n.º 5
0
def add_rect_contour(p0, p1, bnd=0):
    """Adds four point closed rectangular contour.

    :param list-of-floats p0:

    :param list-of-floats p1: bottom left and top right coordinates of
       the contour

    :param bnd: single or list of 4 boundary
       identifiers (bottom, right, top, left) for contour segments.
       With the default value no boundary types will be set.

    :return: Contour identifier
    """
    icheck(0, Point2D())
    icheck(1, Point2D(grthan=p0))
    icheck(2, ListOr1(ZType(), llen=4))

    if isinstance(bnd, list):
        b = bnd[0:4]
    else:
        b = [bnd, bnd, bnd, bnd]

    c = com.contcom.AddRectCont({"p0": p0, "p1": p1, "bnds": b})
    flow.exec_command(c)
    return c.added_contours2()[0]
Ejemplo n.º 6
0
def add_circ_contour(p0, rad, n_arc, bnd=0):
    """Adds circle contour from given center and radius.

    :param list-of-floats p0: circle center in [x, y] format

    :param float rad: circle radius

    :param int n_arc: partition of circle arc

    :param bnd: boundary identifier for contour.

    :return: Contour identifier
    """
    icheck(0, Point2D())
    icheck(1, Float(grthan=0.0))
    icheck(2, UInt(minv=3))
    icheck(3, ZType())

    c = com.contcom.AddCircCont({
        "p0": p0,
        "rad": rad,
        "na": n_arc,
        "bnd": bnd
    })
    flow.exec_command(c)
    return c.added_contours2()[0]
Ejemplo n.º 7
0
def set_btypes(obj, bt, segm=None):
    icheck(0, AObject())
    icheck(1, ZType())
    icheck(2, NoneOr(List(UInt())))
    if segm is None:
        generalfun.set_boundary_type(obj, bt)
    else:
        generalfun.set_boundary_type(obj, bdict={bt: segm})
Ejemplo n.º 8
0
def add_unf_ring_grid(p0, radinner, radouter, na, nr, coef=1.0, bnd=0):
    """Builds ring grid

    :param list-of-floats p0: center coordinates as [x, y]

    :param float radinner:

    :param float radouter: inner and outer radii

    :param int na:

    :param int nr: arc and radius partition respectively

    :param float coef: refinement coefficient:
       * ``coef = 1``: equidistant radius division
       * ``coef > 1``: refinement towards center of circle
       * ``0 < coef < 1``: refinement towards outer arc

    :param int-or-list-of-int bnd: boundary types for inner and outer
       ring boundaries

    :return: created grid identifier

    """
    if radinner > radouter:
        radinner, radouter = radouter, radinner
    icheck(0, Point2D())
    icheck(1, Float(grthan=0.0))
    icheck(2, Float(grthan=0.0))
    icheck(3, UInt(minv=3))
    icheck(4, UInt(minv=1))
    icheck(5, Float(grthan=0.0))
    icheck(6, ListOr1(ZType(), llen=2))

    bnd = bnd[:2] if isinstance(bnd, list) else [bnd, bnd]
    c = com.gridcom.AddUnfRingGrid({
        "p0": p0,
        "radinner": radinner,
        "radouter": radouter,
        "na": na,
        "nr": nr,
        "coef": coef,
        "bnd": bnd
    })
    flow.exec_command(c)
    return c.added_grids2()[0]
Ejemplo n.º 9
0
def add_circ_contour3(p0, p1, curv, n_arc, bnd=0):
    """Adds circle contour from given arc points and curvature.

    :param list-of-floats p0:

    :param list-of-floats p1: circle arc points in [x, y] format

    :param float curv: circle curvature. Equals ``1.0/radius``.

    :param int n_arc: partition of circle arc

    :param bnd: boundary identifier for contour.
       With the default value no boundary types will be set.

    :return: Contour identifier

    In the resulting circle ``p0``-``p1`` arc
    with counterclockwise direction will be shorter then
    ``p1``-``p0`` arc.
    """
    from hybmeshpack.basic.geom import angle_3pnt
    icheck(0, Point2D())
    icheck(1, Point2D(noteq=[p0]))
    icheck(2, Float(grthan=0.0))
    icheck(3, UInt(minv=3))
    icheck(4, ZType())

    p0, p1, curv = map(float, p0), map(float, p1), float(curv)
    xa, ya = p1[0] - p0[0], p1[1] - p0[1]
    r = abs(1.0 / curv)
    a, b, c = -2.0 * xa, -2.0 * ya, xa * xa + ya * ya
    s = a * a + b * b
    x0, y0 = -a * c / s, -b * c / s
    d = r * r - c * c / s
    mult = math.sqrt(d / s)
    cx1 = x0 + b * mult
    cy1 = y0 - a * mult
    cx2 = x0 - b * mult
    cy2 = y0 + a * mult
    a1 = angle_3pnt((0.0, 0.0), (cx1, cx2), (xa, ya))
    if (a1 < math.pi):
        cx, cy = cx1, cy1
    else:
        cx, cy = cx2, cy2
    return add_circ_contour([cx + p0[0], cy + p0[1]], r, n_arc, bnd)
Ejemplo n.º 10
0
def add_boundary_type(index, name="boundary1"):
    """ Register boundary type name.

    :param int index: index of boundary (>0)

    :param str name: user defined name of the boundary

    :returns: integer boundary identifier

    If boundary with ``index`` already exists it will be overwritten.
    Name of the boundary should be unique, if name already exists it will
    be changed automatically.

    """
    icheck(0, ZType())
    icheck(1, String())

    c = com.contcom.EditBoundaryType({"index": index, "name": name})
    flow.exec_command(c)
    return index
Ejemplo n.º 11
0
def create_contour(pnts, bnds=0):
    """ Create singly connected contour from sequence of points.

    :param list-of-list-of-floats pnts: sequence of points.
       If coordinates of first and last points are equal
       then contour is considered closed.

    :param single-or-list-of-boundary-identifiers bnds: boundary type for
       each contour segment or single identifier for the whole contour.

    :returns: contour identifier

    Example:
       >>> hmscript.create_contour([[0, 0], [1, 0], [1, 1], [0, 0]],
                                  [b1, b2, b3])
    """
    icheck(0, List(Point2D(), minlen=2))
    icheck(1, ListOr1(ZType()))

    b = bnds if isinstance(bnds, list) else [bnds]
    c = com.contcom.CreateContour({"points": pnts, "bnds": b})
    flow.exec_command(c)
    return c.added_contours2()[0]
Ejemplo n.º 12
0
def stripe(cont, partition, tip='no', bnd=0):
    """ Build a structured grid to the both sides of contour line

    :param cont: closed or open contour identifier

    :param ascending-list-of-double partition: partition perpendicular
       to source contour

    :param str tip: stripe endings meshing algorithm

       * ``"no"`` - no grid at endings
       * ``"radial"`` - radial grid at endings

    :param float-or-list-of-floats bnd: boundary types for input grid.
       List of four values provides respective values for bottom, left,
       right, top sides of resulting grid with respect to contour direction.

    :return: grid identifier

    Horizontal partition is taken from contour partition.
    Vertical partition is given by user with ``partition`` list parameter.
    If it starts with non zero value then grid will not contain
    contour nodes as its vertices.

    Use :func:`partition_segment` to define non-equidistant
    **partition** with any desired refinement if needed.
    """
    icheck(0, ACont2D())
    icheck(1, IncList(Float(grthan=0.0)))
    icheck(2, OneOf('no', 'radial'))
    icheck(3, ListOr1(ZType(), llen=4))

    bnd = bnd[:4] if isinstance(bnd, list) else [bnd, bnd, bnd, bnd]
    arg = {"source": cont, "partition": partition, "tip": tip, "bnd": bnd}
    c = com.gridcom.StripeGrid(arg)
    flow.exec_command(c)
    return c.added_grids2()[0]
Ejemplo n.º 13
0
def add_circ_contour2(p0, p1, p2, n_arc, bnd=0):
    """Adds circle contour from given arc points.

    :param list-of-floats p0:

    :param list-of-floats p1:

    :param list-of-floats p2: circle arc points as [x, y] format

    :param int n_arc: partition of circle arc

    :param bnd: boundary identifier for contour.
       With the default value no boundary types will be set.

    :return:  Contour identifier

    """
    icheck(0, Point2D())
    icheck(1, Point2D(noteq=[p0]))
    icheck(2, Point2D(noteq=[p0, p1]))
    icheck(3, UInt(minv=3))
    icheck(4, ZType())

    p0, p1, p2 = map(float, p0), map(float, p1), map(float, p2)
    xb, yb = p1[0] - p0[0], p1[1] - p0[1]
    xc, yc = p2[0] - p0[0], p2[1] - p0[1]
    A11, A12 = 2.0 * xb, 2.0 * yb
    A21, A22 = 2.0 * xc, 2.0 * yc
    B1, B2 = xb * xb + yb * yb, xc * xc + yc * yc
    d = A11 * A22 - A12 * A21
    I11, I12, I21, I22 = A22 / d, -A12 / d, -A21 / d, A11 / d
    cx = I11 * B1 + I12 * B2
    cy = I21 * B1 + I22 * B2
    rad = math.sqrt((cx - xb) * (cx - xb) + (cy - yb) * (cy - yb))

    return add_circ_contour([cx + p0[0], cy + p0[1]], rad, n_arc, bnd)
Ejemplo n.º 14
0
def set_boundary_type(obj, btps=None, bfun=None, bdict=None):
    """ Mark geometrical object with boundary types.

    :param obj: geometric object identifier

    :param btps: single identifier for the whole object or list
       of identifiers for each boundary segment.

    :param  bfun: function which returns boundary type taking segment
       coordinates and old boundary type as arguments.

    :param bdict: {btype: [list-of-segment indicies]} dictionary
       which maps boundary type with object segments indicies

    Only one of **btps**, **bfun**, **bdict** arguments should be defined.

    **bfun** signature is:

       * ``(x0, y0, x1, y1, bt) -> btype`` for 2D objects, where
         *x0, y0, x1, y1* are edge end point coordinates,
         bt - old boundary type
       * ``(xc, yc, zc, bt) -> btype`` for 3D objects, where
         *xc, yc, zc* - approximate face center coordinates,
         bt - old boundary type

    If **obj** is a grid then only boundary segments will be passed
    to **bfun** function and **btps** list entries will
    be associated with boundary segments only.
    However **bdict** entries should contain global edge or face indicies.

    Example:

      .. literalinclude:: ../../testing/py/fromdoc/ex_setbtype.py
          :start-after: START OF EXAMPLE
          :end-before: END OF EXAMPLE

    """
    icheck(0, AObject())
    icheck(1, NoneOr(ListOr1(ZType())))
    icheck(3, NoneOr(Dict(ZType(), List(UInt()))))
    t = flow.receiver.whatis(obj)
    if t in ['g2', 'c2']:
        icheck(2, NoneOr(Func(narg=5)))
    else:
        icheck(2, NoneOr(Func(narg=4)))
    if [btps, bfun, bdict].count(None) != 2:
        raise InvalidArgument("One of [btps, bfun, bdict] should be not None")

    args = {'name': obj, 'whole': None, 'btypes': {}}

    if isinstance(btps, int):
        args['whole'] = btps
    elif bdict is not None:
        args['btypes'] = bdict
    else:
        g = flow.receiver.get_object(obj)
        if t == 'g2':
            if btps is not None:
                _setbt_args_g2g3btps(g, btps, args['btypes'])
            if bfun is not None:
                _setbt_args_g2bfun(g, bfun, args['btypes'])
        if t == 'g3':
            if btps is not None:
                _setbt_args_g2g3btps(g, btps, args['btypes'])
            if bfun is not None:
                _setbt_args_g3bfun(g, bfun, args['btypes'])
        if t == 'c2':
            if btps is not None:
                _setbt_args_c2s3btps(g, btps, args['btypes'])
            if bfun is not None:
                _setbt_args_c2bfun(g, bfun, args['btypes'])
        if t == 's3':
            if btps is not None:
                _setbt_args_c2s3btps(g, btps, args['btypes'])
            if bfun is not None:
                _setbt_args_s3bfun(g, bfun, args['btypes'])
    c = com.objcom.SetBType(args)
    flow.exec_command(c)
Ejemplo n.º 15
0
def add_unf_circ_grid(p0,
                      rad=1.0,
                      na=8,
                      nr=4,
                      coef=1.0,
                      is_trian=True,
                      custom_rads=[],
                      custom_arcs=[],
                      bnd=0):
    """Builds circular grid.

    :param list-of-floats p0: center coordinate as [x, y]

    :param float rad: radius

    :param int na:

    :param int nr: partitions of arc and radius respectively

    :param float coef: refinement coefficient:
         * ``coef = 1``: equidistant radius division
         * ``coef > 1``: refinement towards center of circle
         * ``0 < coef < 1``: refinement towards outer arc

    :param bool is_trian: True if center cell should be triangulated

    :param float-or-list-of-floats custom_rads:
        user defined radious partition

    :param float-or-list-of-floats custom_arcs:
        user defined arc partition

    :param int bnd: boundary type for outer contour

    :returns: created grid identifier

    Creates a radial grid with the center in **p0**.

    If **custom_rads** is given as a single value it will be used
    as a constant step along radial axis hence **nr** and **coef** arguments
    will be ignored. If it is given as a list of increasing values
    starting from zero
    then it is parsed as explicit radius partition. Hence the
    last entry of this list will be the radius of the resulting grid
    and **rad** parameter will also be ignored.

    If **custom_arcs** is given as a single value it shows the
    constant step along outer arc and **na** will be ignored.
    If it is an increasing list of floats, it shows partition of
    outer arc. It can be given in degrees or radians or any other
    relative units. Program treats **custom_arcs[-1]**-**custom_arcs[0]**
    difference as a full circle length and normalizes all other entries
    to this length. First and last entries of this array provides
    the same arc segment (last = first + 2*pi) hence to
    get partition of n segments you should define n+1 entries.

    Use :func:`partition_segment` to conveniently define **custom\_** fields
    if needed.
    """
    icheck(0, Point2D())
    icheck(1, Float(grthan=0.0))
    icheck(2, UInt(minv=3))
    icheck(3, UInt(minv=1))
    icheck(4, Float(grthan=0.0))
    icheck(5, Bool())
    icheck(6, Or(Float(grthan=0.0), IncList(Float())))
    icheck(7, Or(Float(grthan=0.0), IncList(Float())))
    icheck(8, ZType())

    custom_rads = custom_rads if isinstance(custom_rads, list)\
        else [custom_rads]
    custom_arcs = custom_arcs if isinstance(custom_arcs, list)\
        else [custom_arcs]
    c = com.gridcom.AddUnfCircGrid({
        "p0": p0,
        "rad": rad,
        "na": na,
        "nr": nr,
        "coef": coef,
        "is_trian": is_trian,
        "custom_r": custom_rads,
        "custom_a": custom_arcs,
        "bnd": bnd
    })
    flow.exec_command(c)
    return c.added_grids2()[0]
Ejemplo n.º 16
0
def add_unf_rect_grid(p0=[0, 0],
                      p1=[1, 1],
                      nx=3,
                      ny=3,
                      custom_x=[],
                      custom_y=[],
                      bnd=0):
    """Builds rectangular grid.

    :param list-of-floats p0:

    :param list-of-floats p1: bottom left, top right points in [x, y] format.

    :param int nx:

    :param int ny: partition in x and y directions.

    :param float-or-list-of-floats custom_x:

    :param float-or-list-of-floats custom_y: custom x and y coordinates

    :param int-or-list-of-int: boundary types for bottom, right, top, left
       rectangle sides

    :returns: created grid identifier

    Builds a grid in a rectangular area formed by points **p0** and **p1**.
    **nx** and **ny** provide grid partition in x and y direction.

    If **custom_x**/**custom_y** is given by a single float value
    than it shows a step size in respective direction,
    hence values given by **nx**/**ny** parameters will be omitted.

    If **custom_x**/**custom_y** is given by a list of increasing floats
    it explicitly shows the partition in respective direction.
    In the latter case the respective **p0**, **p1** coordinates
    will also be ignored.

    Use :func:`partition_segment` to conveniently define **custom\_** fields
    if needed.
    """
    icheck(0, Point2D())
    icheck(1, Point2D(grthan=p0))
    icheck(2, UInt(minv=1))
    icheck(3, UInt(minv=1))
    icheck(4, Or(Float(grthan=0.), IncList(Float())))
    icheck(5, Or(Float(grthan=0.), IncList(Float())))
    icheck(6, ListOr1(ZType(), llen=4))

    bnd = bnd[:4] if isinstance(bnd, list) else [bnd, bnd, bnd, bnd]
    custom_x = custom_x if isinstance(custom_x, list) else [custom_x]
    custom_y = custom_y if isinstance(custom_y, list) else [custom_y]
    c = com.gridcom.AddUnfRectGrid({
        "p0": p0,
        "p1": p1,
        "nx": nx,
        "ny": ny,
        "custom_x": custom_x,
        "custom_y": custom_y,
        "bnds": bnd
    })
    flow.exec_command(c)
    return c.added_grids2()[0]
Ejemplo n.º 17
0
def revolve_grid(obj,
                 p1,
                 p2,
                 n_phi=None,
                 phi=None,
                 btype1=0,
                 btype2=0,
                 merge_central=False):
    """ Creates 3D grid by revolution of 2D grid around a vector

    :param obj: 2d grid identifier

    :param p1:

    :param p2: points in [x, y] format which define vector of rotation

    :param int n_phi: partition along circular coordinate.
       If this parameter is defined then [0, 360] range will be divided
       into equal parts and full revolution solid will be build.

    :param list-of-floats phi: increasing vector defining
       custom partition of angular range.
       This parameter will be processed if **n_phi** is None.
       If the last value of **phi** is not equal to first one
       plus 360 degree than
       incomplete revolution solid will be built.

    :param btype1:

    :param btype2: boundary identifiers for surfaces which will be build
       as a result of incomplete rotation at end values of **phi** vector.

    :param bool merge_central: if rotation vector coincides
       with boundary edges of input grid then this parameter
       defines whether central cells derived from the revolution
       of respective boundary cells should be merged into one
       complex finite volume (True) or left as they are (False).

    :returns: 3D grid identifier

    All points of input grid should lie to the one side of rotation
    vector.

    Use :func:`partition_segment` to define non-equidistant
    **phi** with any desired refinement if needed.

    """
    icheck(0, Grid2D())
    icheck(1, Point2D())
    icheck(2, Point2D(noteq=p1))
    icheck(3, NoneOr(UInt(minv=3)))
    icheck(4, NoneOr(IncList(Float())))
    icheck(5, ZType())
    icheck(6, ZType())
    icheck(7, Bool())

    # calculate phi's
    if n_phi is None:
        inp_phi = copy.deepcopy(phi)
    else:
        inp_phi = []
        for i in range(n_phi + 1):
            inp_phi.append(360. * i / n_phi)

    c = com.grid3dcom.Revolve({
        "base": obj,
        "p1": p1,
        "p2": p2,
        "phi": inp_phi,
        "bt1": btype1,
        "bt2": btype2,
        "center_tri": not merge_central
    })
    flow.exec_command(c)
    return c.added_grids3()[0]
Ejemplo n.º 18
0
def extrude_grid(obj, zcoords, bottombc=0, topbc=0, sidebc=None):
    """ Creates 3D grid by extrusion of 2D grid along z-axis

    :param obj: 2d grid identifier

    :param list-of-floats zcoords: increasing vector of z values
      which will be used to create 3d points

    :param bottombc:

    :param topbc: values which define boundary features of
      3d grid at ``z=min(zcoords)`` and ``z=max(zcoords)``
      surfaces respectively.
      Could be either a single boundary identifier for a whole
      surface or a function: ``(float x, float y, int cell_index)->bindex``
      which takes central cell point x, y
      coordinates and cell index as arguments and returns boundary type
      (see example below).

    :param sidebc: defines boundary features for side surfaces.

      * If None than boundary types will be taken from corresponding
        edges of 2D grid
      * If single boundary identifier then whole side surface will
        have same boundary type

    :returns: 3D grid identifier

    Use :func:`partition_segment` to define non-equidistant
    **zcoords** with any desired refinement.

    Example:

      .. literalinclude:: ../../testing/py/fromdoc/ex_extrude.py
          :start-after: vvvvvvvvvvvvvvvvvvvvvvvv
          :end-before: ^^^^^^^^^^^^^^^^^^^^^^^^
    """
    icheck(0, Grid2D())
    icheck(1, IncList(Float()))
    icheck(2, Or(ZType(), Func(nargs=3)))
    icheck(3, Or(ZType(), Func(nargs=3)))
    icheck(4, NoneOr(ZType()))

    # calculate boundary types
    if not isinstance(bottombc, int) or not isinstance(topbc, int):
        grid = flow.receiver.get_grid2(obj)
        cc_pnt = grid.raw_data('cell_center')
    if isinstance(bottombc, int):
        bbot = [bottombc]
    elif callable(bottombc):
        it = iter(cc_pnt)
        bbot = [bottombc(p[0], p[1], i) for i, p in enumerate(zip(it, it))]
    if isinstance(topbc, int):
        btop = [topbc]
    elif callable(topbc):
        it = iter(cc_pnt)
        btop = [topbc(p[0], p[1], i) for i, p in enumerate(zip(it, it))]
    if sidebc is None:
        bside = None
    elif isinstance(sidebc, int):
        bside = sidebc

    c = com.grid3dcom.ExtrudeZ({
        "base": obj,
        "zvals": copy.deepcopy(zcoords),
        "bside": bside,
        "btop": btop,
        "bbot": bbot
    })
    flow.exec_command(c)
    return c.added_grids3()[0]